elf.py 9.8 KB

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