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