elf.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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 collections import namedtuple, OrderedDict
  8. import io
  9. import os
  10. import re
  11. import shutil
  12. import struct
  13. import tempfile
  14. from patman import command
  15. from patman import tools
  16. from patman import tout
  17. ELF_TOOLS = True
  18. try:
  19. from elftools.elf.elffile import ELFFile
  20. from elftools.elf.sections import SymbolTableSection
  21. except: # pragma: no cover
  22. ELF_TOOLS = False
  23. Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
  24. # Information about an ELF file:
  25. # data: Extracted program contents of ELF file (this would be loaded by an
  26. # ELF loader when reading this file
  27. # load: Load address of code
  28. # entry: Entry address of code
  29. # memsize: Number of bytes in memory occupied by loading this ELF file
  30. ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
  31. def GetSymbols(fname, patterns):
  32. """Get the symbols from an ELF file
  33. Args:
  34. fname: Filename of the ELF file to read
  35. patterns: List of regex patterns to search for, each a string
  36. Returns:
  37. None, if the file does not exist, or Dict:
  38. key: Name of symbol
  39. value: Hex value of symbol
  40. """
  41. stdout = tools.Run('objdump', '-t', fname)
  42. lines = stdout.splitlines()
  43. if patterns:
  44. re_syms = re.compile('|'.join(patterns))
  45. else:
  46. re_syms = None
  47. syms = {}
  48. syms_started = False
  49. for line in lines:
  50. if not line or not syms_started:
  51. if 'SYMBOL TABLE' in line:
  52. syms_started = True
  53. line = None # Otherwise code coverage complains about 'continue'
  54. continue
  55. if re_syms and not re_syms.search(line):
  56. continue
  57. space_pos = line.find(' ')
  58. value, rest = line[:space_pos], line[space_pos + 1:]
  59. flags = rest[:7]
  60. parts = rest[7:].split()
  61. section, size = parts[:2]
  62. if len(parts) > 2:
  63. name = parts[2] if parts[2] != '.hidden' else parts[3]
  64. syms[name] = Symbol(section, int(value, 16), int(size,16),
  65. flags[1] == 'w')
  66. # Sort dict by address
  67. return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
  68. def GetSymbolAddress(fname, sym_name):
  69. """Get a value of a symbol from an ELF file
  70. Args:
  71. fname: Filename of the ELF file to read
  72. patterns: List of regex patterns to search for, each a string
  73. Returns:
  74. Symbol value (as an integer) or None if not found
  75. """
  76. syms = GetSymbols(fname, [sym_name])
  77. sym = syms.get(sym_name)
  78. if not sym:
  79. return None
  80. return sym.address
  81. def LookupAndWriteSymbols(elf_fname, entry, section):
  82. """Replace all symbols in an entry with their correct values
  83. The entry contents is updated so that values for referenced symbols will be
  84. visible at run time. This is done by finding out the symbols offsets in the
  85. entry (using the ELF file) and replacing them with values from binman's data
  86. structures.
  87. Args:
  88. elf_fname: Filename of ELF image containing the symbol information for
  89. entry
  90. entry: Entry to process
  91. section: Section which can be used to lookup symbol values
  92. """
  93. fname = tools.GetInputFilename(elf_fname)
  94. syms = GetSymbols(fname, ['image', 'binman'])
  95. if not syms:
  96. return
  97. base = syms.get('__image_copy_start')
  98. if not base:
  99. return
  100. for name, sym in syms.items():
  101. if name.startswith('_binman'):
  102. msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
  103. (section.GetPath(), name, entry.GetPath()))
  104. offset = sym.address - base.address
  105. if offset < 0 or offset + sym.size > entry.contents_size:
  106. raise ValueError('%s has offset %x (size %x) but the contents '
  107. 'size is %x' % (entry.GetPath(), offset,
  108. sym.size, entry.contents_size))
  109. if sym.size == 4:
  110. pack_string = '<I'
  111. elif sym.size == 8:
  112. pack_string = '<Q'
  113. else:
  114. raise ValueError('%s has size %d: only 4 and 8 are supported' %
  115. (msg, sym.size))
  116. # Look up the symbol in our entry tables.
  117. value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
  118. 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' % byte for byte in text[:2]]
  141. text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
  142. data_bytes = ['\t.byte\t%#x' % 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. cc, args = tools.GetTargetCompileTool('cc')
  210. args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
  211. lds_file, '-o', elf_fname, s_file]
  212. stdout = command.Output(cc, *args)
  213. shutil.rmtree(outdir)
  214. def DecodeElf(data, location):
  215. """Decode an ELF file and return information about it
  216. Args:
  217. data: Data from ELF file
  218. location: Start address of data to return
  219. Returns:
  220. ElfInfo object containing information about the decoded ELF file
  221. """
  222. file_size = len(data)
  223. with io.BytesIO(data) as fd:
  224. elf = ELFFile(fd)
  225. data_start = 0xffffffff;
  226. data_end = 0;
  227. mem_end = 0;
  228. virt_to_phys = 0;
  229. for i in range(elf.num_segments()):
  230. segment = elf.get_segment(i)
  231. if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
  232. skipped = 1 # To make code-coverage see this line
  233. continue
  234. start = segment['p_paddr']
  235. mend = start + segment['p_memsz']
  236. rend = start + segment['p_filesz']
  237. data_start = min(data_start, start)
  238. data_end = max(data_end, rend)
  239. mem_end = max(mem_end, mend)
  240. if not virt_to_phys:
  241. virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
  242. output = bytearray(data_end - data_start)
  243. for i in range(elf.num_segments()):
  244. segment = elf.get_segment(i)
  245. if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
  246. skipped = 1 # To make code-coverage see this line
  247. continue
  248. start = segment['p_paddr']
  249. offset = 0
  250. if start < location:
  251. offset = location - start
  252. start = location
  253. # A legal ELF file can have a program header with non-zero length
  254. # but zero-length file size and a non-zero offset which, added
  255. # together, are greater than input->size (i.e. the total file size).
  256. # So we need to not even test in the case that p_filesz is zero.
  257. # Note: All of this code is commented out since we don't have a test
  258. # case for it.
  259. size = segment['p_filesz']
  260. #if not size:
  261. #continue
  262. #end = segment['p_offset'] + segment['p_filesz']
  263. #if end > file_size:
  264. #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
  265. #file_size, end)
  266. output[start - data_start:start - data_start + size] = (
  267. segment.data()[offset:])
  268. return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
  269. mem_end - data_start)