1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2016 Google, Inc 3# Written by Simon Glass <sjg@chromium.org> 4# 5# Handle various things related to ELF images 6# 7 8from __future__ import print_function 9 10from collections import namedtuple, OrderedDict 11import command 12import io 13import os 14import re 15import shutil 16import struct 17import tempfile 18 19import tools 20import tout 21 22ELF_TOOLS = True 23try: 24 from elftools.elf.elffile import ELFFile 25 from elftools.elf.sections import SymbolTableSection 26except: # pragma: no cover 27 ELF_TOOLS = False 28 29Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak']) 30 31# Information about an ELF file: 32# data: Extracted program contents of ELF file (this would be loaded by an 33# ELF loader when reading this file 34# load: Load address of code 35# entry: Entry address of code 36# memsize: Number of bytes in memory occupied by loading this ELF file 37ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize']) 38 39 40def GetSymbols(fname, patterns): 41 """Get the symbols from an ELF file 42 43 Args: 44 fname: Filename of the ELF file to read 45 patterns: List of regex patterns to search for, each a string 46 47 Returns: 48 None, if the file does not exist, or Dict: 49 key: Name of symbol 50 value: Hex value of symbol 51 """ 52 stdout = tools.Run('objdump', '-t', fname) 53 lines = stdout.splitlines() 54 if patterns: 55 re_syms = re.compile('|'.join(patterns)) 56 else: 57 re_syms = None 58 syms = {} 59 syms_started = False 60 for line in lines: 61 if not line or not syms_started: 62 if 'SYMBOL TABLE' in line: 63 syms_started = True 64 line = None # Otherwise code coverage complains about 'continue' 65 continue 66 if re_syms and not re_syms.search(line): 67 continue 68 69 space_pos = line.find(' ') 70 value, rest = line[:space_pos], line[space_pos + 1:] 71 flags = rest[:7] 72 parts = rest[7:].split() 73 section, size = parts[:2] 74 if len(parts) > 2: 75 name = parts[2] if parts[2] != '.hidden' else parts[3] 76 syms[name] = Symbol(section, int(value, 16), int(size,16), 77 flags[1] == 'w') 78 79 # Sort dict by address 80 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address)) 81 82def GetSymbolAddress(fname, sym_name): 83 """Get a value of a symbol from an ELF file 84 85 Args: 86 fname: Filename of the ELF file to read 87 patterns: List of regex patterns to search for, each a string 88 89 Returns: 90 Symbol value (as an integer) or None if not found 91 """ 92 syms = GetSymbols(fname, [sym_name]) 93 sym = syms.get(sym_name) 94 if not sym: 95 return None 96 return sym.address 97 98def LookupAndWriteSymbols(elf_fname, entry, section): 99 """Replace all symbols in an entry with their correct values 100 101 The entry contents is updated so that values for referenced symbols will be 102 visible at run time. This is done by finding out the symbols offsets in the 103 entry (using the ELF file) and replacing them with values from binman's data 104 structures. 105 106 Args: 107 elf_fname: Filename of ELF image containing the symbol information for 108 entry 109 entry: Entry to process 110 section: Section which can be used to lookup symbol values 111 """ 112 fname = tools.GetInputFilename(elf_fname) 113 syms = GetSymbols(fname, ['image', 'binman']) 114 if not syms: 115 return 116 base = syms.get('__image_copy_start') 117 if not base: 118 return 119 for name, sym in syms.items(): 120 if name.startswith('_binman'): 121 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" % 122 (section.GetPath(), name, entry.GetPath())) 123 offset = sym.address - base.address 124 if offset < 0 or offset + sym.size > entry.contents_size: 125 raise ValueError('%s has offset %x (size %x) but the contents ' 126 'size is %x' % (entry.GetPath(), offset, 127 sym.size, entry.contents_size)) 128 if sym.size == 4: 129 pack_string = '<I' 130 elif sym.size == 8: 131 pack_string = '<Q' 132 else: 133 raise ValueError('%s has size %d: only 4 and 8 are supported' % 134 (msg, sym.size)) 135 136 # Look up the symbol in our entry tables. 137 value = section.LookupSymbol(name, sym.weak, msg, base.address) 138 if value is None: 139 value = -1 140 pack_string = pack_string.lower() 141 value_bytes = struct.pack(pack_string, value) 142 tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' % 143 (msg, name, offset, value, len(value_bytes))) 144 entry.data = (entry.data[:offset] + value_bytes + 145 entry.data[offset + sym.size:]) 146 147def MakeElf(elf_fname, text, data): 148 """Make an elf file with the given data in a single section 149 150 The output file has a several section including '.text' and '.data', 151 containing the info provided in arguments. 152 153 Args: 154 elf_fname: Output filename 155 text: Text (code) to put in the file's .text section 156 data: Data to put in the file's .data section 157 """ 158 outdir = tempfile.mkdtemp(prefix='binman.elf.') 159 s_file = os.path.join(outdir, 'elf.S') 160 161 # Spilt the text into two parts so that we can make the entry point two 162 # bytes after the start of the text section 163 text_bytes1 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[:2]] 164 text_bytes2 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[2:]] 165 data_bytes = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in data] 166 with open(s_file, 'w') as fd: 167 print('''/* Auto-generated C program to produce an ELF file for testing */ 168 169.section .text 170.code32 171.globl _start 172.type _start, @function 173%s 174_start: 175%s 176.ident "comment" 177 178.comm fred,8,4 179 180.section .empty 181.globl _empty 182_empty: 183.byte 1 184 185.globl ernie 186.data 187.type ernie, @object 188.size ernie, 4 189ernie: 190%s 191''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)), 192 file=fd) 193 lds_file = os.path.join(outdir, 'elf.lds') 194 195 # Use a linker script to set the alignment and text address. 196 with open(lds_file, 'w') as fd: 197 print('''/* Auto-generated linker script to produce an ELF file for testing */ 198 199PHDRS 200{ 201 text PT_LOAD ; 202 data PT_LOAD ; 203 empty PT_LOAD FLAGS ( 6 ) ; 204 note PT_NOTE ; 205} 206 207SECTIONS 208{ 209 . = 0xfef20000; 210 ENTRY(_start) 211 .text . : SUBALIGN(0) 212 { 213 *(.text) 214 } :text 215 .data : { 216 *(.data) 217 } :data 218 _bss_start = .; 219 .empty : { 220 *(.empty) 221 } :empty 222 /DISCARD/ : { 223 *(.note.gnu.property) 224 } 225 .note : { 226 *(.comment) 227 } :note 228 .bss _bss_start (OVERLAY) : { 229 *(.bss) 230 } 231} 232''', file=fd) 233 # -static: Avoid requiring any shared libraries 234 # -nostdlib: Don't link with C library 235 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the 236 # text section at the start 237 # -m32: Build for 32-bit x86 238 # -T...: Specifies the link script, which sets the start address 239 stdout = command.Output('cc', '-static', '-nostdlib', '-Wl,--build-id=none', 240 '-m32','-T', lds_file, '-o', elf_fname, s_file) 241 shutil.rmtree(outdir) 242 243def DecodeElf(data, location): 244 """Decode an ELF file and return information about it 245 246 Args: 247 data: Data from ELF file 248 location: Start address of data to return 249 250 Returns: 251 ElfInfo object containing information about the decoded ELF file 252 """ 253 file_size = len(data) 254 with io.BytesIO(data) as fd: 255 elf = ELFFile(fd) 256 data_start = 0xffffffff; 257 data_end = 0; 258 mem_end = 0; 259 virt_to_phys = 0; 260 261 for i in range(elf.num_segments()): 262 segment = elf.get_segment(i) 263 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']: 264 skipped = 1 # To make code-coverage see this line 265 continue 266 start = segment['p_paddr'] 267 mend = start + segment['p_memsz'] 268 rend = start + segment['p_filesz'] 269 data_start = min(data_start, start) 270 data_end = max(data_end, rend) 271 mem_end = max(mem_end, mend) 272 if not virt_to_phys: 273 virt_to_phys = segment['p_paddr'] - segment['p_vaddr'] 274 275 output = bytearray(data_end - data_start) 276 for i in range(elf.num_segments()): 277 segment = elf.get_segment(i) 278 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']: 279 skipped = 1 # To make code-coverage see this line 280 continue 281 start = segment['p_paddr'] 282 offset = 0 283 if start < location: 284 offset = location - start 285 start = location 286 # A legal ELF file can have a program header with non-zero length 287 # but zero-length file size and a non-zero offset which, added 288 # together, are greater than input->size (i.e. the total file size). 289 # So we need to not even test in the case that p_filesz is zero. 290 # Note: All of this code is commented out since we don't have a test 291 # case for it. 292 size = segment['p_filesz'] 293 #if not size: 294 #continue 295 #end = segment['p_offset'] + segment['p_filesz'] 296 #if end > file_size: 297 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n', 298 #file_size, end) 299 output[start - data_start:start - data_start + size] = ( 300 segment.data()[offset:]) 301 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys, 302 mem_end - data_start) 303