image.py 15 KB

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