image.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. # SPDX-License-Identifier: GPL-2.0+
  2. # Copyright (c) 2016 Google, Inc
  3. # Written by Simon Glass <sjg@chromium.org>
  4. #
  5. # Class for an image, the output of binman
  6. #
  7. from collections import OrderedDict
  8. import fnmatch
  9. from operator import attrgetter
  10. import os
  11. import re
  12. import sys
  13. from binman.entry import Entry
  14. from binman.etype import fdtmap
  15. from binman.etype import image_header
  16. from binman.etype import section
  17. from dtoc import fdt
  18. from dtoc import fdt_util
  19. from u_boot_pylib import tools
  20. from u_boot_pylib import tout
  21. class Image(section.Entry_section):
  22. """A Image, representing an output from binman
  23. An image is comprised of a collection of entries each containing binary
  24. data. The image size must be large enough to hold all of this data.
  25. This class implements the various operations needed for images.
  26. Attributes:
  27. filename: Output filename for image
  28. image_node: Name of node containing the description for this image
  29. fdtmap_dtb: Fdt object for the fdtmap when loading from a file
  30. fdtmap_data: Contents of the fdtmap when loading from a file
  31. allow_repack: True to add properties to allow the image to be safely
  32. repacked later
  33. test_section_timeout: Use a zero timeout for section multi-threading
  34. (for testing)
  35. symlink: Name of symlink to image
  36. Args:
  37. copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
  38. from the device tree
  39. test: True if this is being called from a test of Images. This this case
  40. there is no device tree defining the structure of the section, so
  41. we create a section manually.
  42. ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
  43. exception). This should be used if the Image is being loaded from
  44. a file rather than generated. In that case we obviously don't need
  45. the entry arguments since the contents already exists.
  46. use_expanded: True if we are updating the FDT wth entry offsets, etc.
  47. and should use the expanded versions of the U-Boot entries.
  48. Any entry type that includes a devicetree must put it in a
  49. separate entry so that it will be updated. For example. 'u-boot'
  50. normally just picks up 'u-boot.bin' which includes the
  51. devicetree, but this is not updateable, since it comes into
  52. binman as one piece and binman doesn't know that it is actually
  53. an executable followed by a devicetree. Of course it could be
  54. taught this, but then when reading an image (e.g. 'binman ls')
  55. it may need to be able to split the devicetree out of the image
  56. in order to determine the location of things. Instead we choose
  57. to ignore 'u-boot-bin' in this case, and build it ourselves in
  58. binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
  59. Entry_u_boot_expanded and Entry_blob_phase for details.
  60. missing_etype: Use a default entry type ('blob') if the requested one
  61. does not exist in binman. This is useful if an image was created by
  62. binman a newer version of binman but we want to list it in an older
  63. version which does not support all the entry types.
  64. generate: If true, generator nodes are processed. If false they are
  65. ignored which is useful when an existing image is read back from a
  66. file.
  67. """
  68. def __init__(self, name, node, copy_to_orig=True, test=False,
  69. ignore_missing=False, use_expanded=False, missing_etype=False,
  70. generate=True):
  71. super().__init__(None, 'section', node, test=test)
  72. self.copy_to_orig = copy_to_orig
  73. self.name = name
  74. self.image_name = name
  75. self._filename = '%s.bin' % self.image_name
  76. self.fdtmap_dtb = None
  77. self.fdtmap_data = None
  78. self.allow_repack = False
  79. self._ignore_missing = ignore_missing
  80. self.missing_etype = missing_etype
  81. self.use_expanded = use_expanded
  82. self.test_section_timeout = False
  83. self.bintools = {}
  84. self.generate = generate
  85. if not test:
  86. self.ReadNode()
  87. def ReadNode(self):
  88. super().ReadNode()
  89. self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
  90. self._symlink = fdt_util.GetString(self._node, 'symlink')
  91. @classmethod
  92. def FromFile(cls, fname):
  93. """Convert an image file into an Image for use in binman
  94. Args:
  95. fname: Filename of image file to read
  96. Returns:
  97. Image object on success
  98. Raises:
  99. ValueError if something goes wrong
  100. """
  101. data = tools.read_file(fname)
  102. size = len(data)
  103. # First look for an image header
  104. pos = image_header.LocateHeaderOffset(data)
  105. if pos is None:
  106. # Look for the FDT map
  107. pos = fdtmap.LocateFdtmap(data)
  108. if pos is None:
  109. raise ValueError('Cannot find FDT map in image')
  110. # We don't know the FDT size, so check its header first
  111. probe_dtb = fdt.Fdt.FromData(
  112. data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
  113. dtb_size = probe_dtb.GetFdtObj().totalsize()
  114. fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
  115. fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
  116. out_fname = tools.get_output_filename('fdtmap.in.dtb')
  117. tools.write_file(out_fname, fdt_data)
  118. dtb = fdt.Fdt(out_fname)
  119. dtb.Scan()
  120. # Return an Image with the associated nodes
  121. root = dtb.GetRoot()
  122. image = Image('image', root, copy_to_orig=False, ignore_missing=True,
  123. missing_etype=True, generate=False)
  124. image.image_node = fdt_util.GetString(root, 'image-node', 'image')
  125. image.fdtmap_dtb = dtb
  126. image.fdtmap_data = fdtmap_data
  127. image._data = data
  128. image._filename = fname
  129. image.image_name, _ = os.path.splitext(fname)
  130. return image
  131. def Raise(self, msg):
  132. """Convenience function to raise an error referencing an image"""
  133. raise ValueError("Image '%s': %s" % (self._node.path, msg))
  134. def PackEntries(self):
  135. """Pack all entries into the image"""
  136. super().Pack(0)
  137. def SetImagePos(self):
  138. # This first section in the image so it starts at 0
  139. super().SetImagePos(0)
  140. def ProcessEntryContents(self):
  141. """Call the ProcessContents() method for each entry
  142. This is intended to adjust the contents as needed by the entry type.
  143. Returns:
  144. True if the new data size is OK, False if expansion is needed
  145. """
  146. return super().ProcessContents()
  147. def WriteSymbols(self):
  148. """Write symbol values into binary files for access at run time"""
  149. super().WriteSymbols(self)
  150. def BuildImage(self):
  151. """Write the image to a file"""
  152. fname = tools.get_output_filename(self._filename)
  153. tout.info("Writing image to '%s'" % fname)
  154. with open(fname, 'wb') as fd:
  155. data = self.GetPaddedData()
  156. fd.write(data)
  157. tout.info("Wrote %#x bytes" % len(data))
  158. # Create symlink to file if symlink given
  159. if self._symlink is not None:
  160. sname = tools.get_output_filename(self._symlink)
  161. if os.path.islink(sname):
  162. os.remove(sname)
  163. os.symlink(fname, sname)
  164. def WriteMap(self):
  165. """Write a map of the image to a .map file
  166. Returns:
  167. Filename of map file written
  168. """
  169. filename = '%s.map' % self.image_name
  170. fname = tools.get_output_filename(filename)
  171. with open(fname, 'w') as fd:
  172. print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
  173. file=fd)
  174. super().WriteMap(fd, 0)
  175. return fname
  176. def BuildEntryList(self):
  177. """List the files in an image
  178. Returns:
  179. List of entry.EntryInfo objects describing all entries in the image
  180. """
  181. entries = []
  182. self.ListEntries(entries, 0)
  183. return entries
  184. def FindEntryPath(self, entry_path):
  185. """Find an entry at a given path in the image
  186. Args:
  187. entry_path: Path to entry (e.g. /ro-section/u-boot')
  188. Returns:
  189. Entry object corresponding to that past
  190. Raises:
  191. ValueError if no entry found
  192. """
  193. parts = entry_path.split('/')
  194. entries = self.GetEntries()
  195. parent = '/'
  196. for part in parts:
  197. entry = entries.get(part)
  198. if not entry:
  199. raise ValueError("Entry '%s' not found in '%s'" %
  200. (part, parent))
  201. parent = entry.GetPath()
  202. entries = entry.GetEntries()
  203. return entry
  204. def ReadData(self, decomp=True, alt_format=None):
  205. tout.debug("Image '%s' ReadData(), size=%#x" %
  206. (self.GetPath(), len(self._data)))
  207. return self._data
  208. def GetListEntries(self, entry_paths):
  209. """List the entries in an image
  210. This decodes the supplied image and returns a list of entries from that
  211. image, preceded by a header.
  212. Args:
  213. entry_paths: List of paths to match (each can have wildcards). Only
  214. entries whose names match one of these paths will be printed
  215. Returns:
  216. String error message if something went wrong, otherwise
  217. 3-Tuple:
  218. List of EntryInfo objects
  219. List of lines, each
  220. List of text columns, each a string
  221. List of widths of each column
  222. """
  223. def _EntryToStrings(entry):
  224. """Convert an entry to a list of strings, one for each column
  225. Args:
  226. entry: EntryInfo object containing information to output
  227. Returns:
  228. List of strings, one for each field in entry
  229. """
  230. def _AppendHex(val):
  231. """Append a hex value, or an empty string if val is None
  232. Args:
  233. val: Integer value, or None if none
  234. """
  235. args.append('' if val is None else '>%x' % val)
  236. args = [' ' * entry.indent + entry.name]
  237. _AppendHex(entry.image_pos)
  238. _AppendHex(entry.size)
  239. args.append(entry.etype)
  240. _AppendHex(entry.offset)
  241. _AppendHex(entry.uncomp_size)
  242. return args
  243. def _DoLine(lines, line):
  244. """Add a line to the output list
  245. This adds a line (a list of columns) to the output list. It also updates
  246. the widths[] array with the maximum width of each column
  247. Args:
  248. lines: List of lines to add to
  249. line: List of strings, one for each column
  250. """
  251. for i, item in enumerate(line):
  252. widths[i] = max(widths[i], len(item))
  253. lines.append(line)
  254. def _NameInPaths(fname, entry_paths):
  255. """Check if a filename is in a list of wildcarded paths
  256. Args:
  257. fname: Filename to check
  258. entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
  259. 'section/u-boot'])
  260. Returns:
  261. True if any wildcard matches the filename (using Unix filename
  262. pattern matching, not regular expressions)
  263. False if not
  264. """
  265. for path in entry_paths:
  266. if fnmatch.fnmatch(fname, path):
  267. return True
  268. return False
  269. entries = self.BuildEntryList()
  270. # This is our list of lines. Each item in the list is a list of strings, one
  271. # for each column
  272. lines = []
  273. HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
  274. 'Uncomp-size']
  275. num_columns = len(HEADER)
  276. # This records the width of each column, calculated as the maximum width of
  277. # all the strings in that column
  278. widths = [0] * num_columns
  279. _DoLine(lines, HEADER)
  280. # We won't print anything unless it has at least this indent. So at the
  281. # start we will print nothing, unless a path matches (or there are no
  282. # entry paths)
  283. MAX_INDENT = 100
  284. min_indent = MAX_INDENT
  285. path_stack = []
  286. path = ''
  287. indent = 0
  288. selected_entries = []
  289. for entry in entries:
  290. if entry.indent > indent:
  291. path_stack.append(path)
  292. elif entry.indent < indent:
  293. path_stack.pop()
  294. if path_stack:
  295. path = path_stack[-1] + '/' + entry.name
  296. indent = entry.indent
  297. # If there are entry paths to match and we are not looking at a
  298. # sub-entry of a previously matched entry, we need to check the path
  299. if entry_paths and indent <= min_indent:
  300. if _NameInPaths(path[1:], entry_paths):
  301. # Print this entry and all sub-entries (=higher indent)
  302. min_indent = indent
  303. else:
  304. # Don't print this entry, nor any following entries until we get
  305. # a path match
  306. min_indent = MAX_INDENT
  307. continue
  308. _DoLine(lines, _EntryToStrings(entry))
  309. selected_entries.append(entry)
  310. return selected_entries, lines, widths
  311. def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
  312. """Look up a symbol in an ELF file
  313. Looks up a symbol in an ELF file. Only entry types which come from an
  314. ELF image can be used by this function.
  315. This searches through this image including all of its subsections.
  316. At present the only entry properties supported are:
  317. offset
  318. image_pos - 'base_addr' is added if this is not an end-at-4gb image
  319. size
  320. Args:
  321. sym_name: Symbol name in the ELF file to look up in the format
  322. _binman_<entry>_prop_<property> where <entry> is the name of
  323. the entry and <property> is the property to find (e.g.
  324. _binman_u_boot_prop_offset). As a special case, you can append
  325. _any to <entry> to have it search for any matching entry. E.g.
  326. _binman_u_boot_any_prop_offset will match entries called u-boot,
  327. u-boot-img and u-boot-nodtb)
  328. optional: True if the symbol is optional. If False this function
  329. will raise if the symbol is not found
  330. msg: Message to display if an error occurs
  331. base_addr: Base address of image. This is added to the returned
  332. image_pos in most cases so that the returned position indicates
  333. where the targeted entry/binary has actually been loaded. But
  334. if end-at-4gb is used, this is not done, since the binary is
  335. already assumed to be linked to the ROM position and using
  336. execute-in-place (XIP).
  337. Returns:
  338. Value that should be assigned to that symbol, or None if it was
  339. optional and not found
  340. Raises:
  341. ValueError if the symbol is invalid or not found, or references a
  342. property which is not supported
  343. """
  344. entries = OrderedDict()
  345. entries_by_name = {}
  346. self._CollectEntries(entries, entries_by_name, self)
  347. return self.LookupSymbol(sym_name, optional, msg, base_addr,
  348. entries_by_name)
  349. def CollectBintools(self):
  350. """Collect all the bintools used by this image
  351. Returns:
  352. Dict of bintools:
  353. key: name of tool
  354. value: Bintool object
  355. """
  356. bintools = {}
  357. super().AddBintools(bintools)
  358. self.bintools = bintools
  359. return bintools