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 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.LookupSymbol(name, sym.weak, msg, base.address)
  118. if value is None:
  119. value = -1
  120. pack_string = pack_string.lower()
  121. value_bytes = struct.pack(pack_string, value)
  122. tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' %
  123. (msg, name, offset, value, len(value_bytes)))
  124. entry.data = (entry.data[:offset] + value_bytes +
  125. entry.data[offset + sym.size:])
  126. def MakeElf(elf_fname, text, data):
  127. """Make an elf file with the given data in a single section
  128. The output file has a several section including '.text' and '.data',
  129. containing the info provided in arguments.
  130. Args:
  131. elf_fname: Output filename
  132. text: Text (code) to put in the file's .text section
  133. data: Data to put in the file's .data section
  134. """
  135. outdir = tempfile.mkdtemp(prefix='binman.elf.')
  136. s_file = os.path.join(outdir, 'elf.S')
  137. # Spilt the text into two parts so that we can make the entry point two
  138. # bytes after the start of the text section
  139. text_bytes1 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[:2]]
  140. text_bytes2 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[2:]]
  141. data_bytes = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in data]
  142. with open(s_file, 'w') as fd:
  143. print('''/* Auto-generated C program to produce an ELF file for testing */
  144. .section .text
  145. .code32
  146. .globl _start
  147. .type _start, @function
  148. %s
  149. _start:
  150. %s
  151. .ident "comment"
  152. .comm fred,8,4
  153. .section .empty
  154. .globl _empty
  155. _empty:
  156. .byte 1
  157. .globl ernie
  158. .data
  159. .type ernie, @object
  160. .size ernie, 4
  161. ernie:
  162. %s
  163. ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
  164. file=fd)
  165. lds_file = os.path.join(outdir, 'elf.lds')
  166. # Use a linker script to set the alignment and text address.
  167. with open(lds_file, 'w') as fd:
  168. print('''/* Auto-generated linker script to produce an ELF file for testing */
  169. PHDRS
  170. {
  171. text PT_LOAD ;
  172. data PT_LOAD ;
  173. empty PT_LOAD FLAGS ( 6 ) ;
  174. note PT_NOTE ;
  175. }
  176. SECTIONS
  177. {
  178. . = 0xfef20000;
  179. ENTRY(_start)
  180. .text . : SUBALIGN(0)
  181. {
  182. *(.text)
  183. } :text
  184. .data : {
  185. *(.data)
  186. } :data
  187. _bss_start = .;
  188. .empty : {
  189. *(.empty)
  190. } :empty
  191. /DISCARD/ : {
  192. *(.note.gnu.property)
  193. }
  194. .note : {
  195. *(.comment)
  196. } :note
  197. .bss _bss_start (OVERLAY) : {
  198. *(.bss)
  199. }
  200. }
  201. ''', file=fd)
  202. # -static: Avoid requiring any shared libraries
  203. # -nostdlib: Don't link with C library
  204. # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
  205. # text section at the start
  206. # -m32: Build for 32-bit x86
  207. # -T...: Specifies the link script, which sets the start address
  208. cc, args = tools.GetTargetCompileTool('cc')
  209. args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
  210. lds_file, '-o', elf_fname, s_file]
  211. stdout = command.Output(cc, *args)
  212. shutil.rmtree(outdir)
  213. def DecodeElf(data, location):
  214. """Decode an ELF file and return information about it
  215. Args:
  216. data: Data from ELF file
  217. location: Start address of data to return
  218. Returns:
  219. ElfInfo object containing information about the decoded ELF file
  220. """
  221. file_size = len(data)
  222. with io.BytesIO(data) as fd:
  223. elf = ELFFile(fd)
  224. data_start = 0xffffffff;
  225. data_end = 0;
  226. mem_end = 0;
  227. virt_to_phys = 0;
  228. for i in range(elf.num_segments()):
  229. segment = elf.get_segment(i)
  230. if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
  231. skipped = 1 # To make code-coverage see this line
  232. continue
  233. start = segment['p_paddr']
  234. mend = start + segment['p_memsz']
  235. rend = start + segment['p_filesz']
  236. data_start = min(data_start, start)
  237. data_end = max(data_end, rend)
  238. mem_end = max(mem_end, mend)
  239. if not virt_to_phys:
  240. virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
  241. output = bytearray(data_end - data_start)
  242. for i in range(elf.num_segments()):
  243. segment = elf.get_segment(i)
  244. if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
  245. skipped = 1 # To make code-coverage see this line
  246. continue
  247. start = segment['p_paddr']
  248. offset = 0
  249. if start < location:
  250. offset = location - start
  251. start = location
  252. # A legal ELF file can have a program header with non-zero length
  253. # but zero-length file size and a non-zero offset which, added
  254. # together, are greater than input->size (i.e. the total file size).
  255. # So we need to not even test in the case that p_filesz is zero.
  256. # Note: All of this code is commented out since we don't have a test
  257. # case for it.
  258. size = segment['p_filesz']
  259. #if not size:
  260. #continue
  261. #end = segment['p_offset'] + segment['p_filesz']
  262. #if end > file_size:
  263. #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
  264. #file_size, end)
  265. output[start - data_start:start - data_start + size] = (
  266. segment.data()[offset:])
  267. return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
  268. mem_end - data_start)