ar.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. #!/usr/bin/env python3
  2. # Copyright 2018 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Logic for reading .a files.
  6. Copied from //tools/binary_size/libsupersize/ar.py"""
  7. import os
  8. def _ResolveThinObjectPath(archive_path, subpath):
  9. """Given the .a path and .o subpath, returns the .o path."""
  10. # |subpath| is path complete under Gold, and incomplete under LLD. Check its
  11. # prefix to test completeness, and if not, use |archive_path| to supply the
  12. # required prefix.
  13. if subpath.startswith('obj/'):
  14. return subpath
  15. # .o subpaths in thin archives are relative to the directory of the .a.
  16. parent_path = os.path.dirname(archive_path)
  17. return os.path.normpath(os.path.join(parent_path, subpath))
  18. def _IterThinPaths(path):
  19. """Given the .a path, yields all nested .o paths."""
  20. # File format reference:
  21. # https://github.com/pathscale/binutils/blob/master/gold/archive.cc
  22. with open(path, 'rb') as f:
  23. header = f.read(8)
  24. is_thin = header == b'!<thin>\n'
  25. if not is_thin and header != b'!<arch>\n':
  26. raise Exception('Invalid .a: ' + path)
  27. if not is_thin:
  28. return
  29. def read_payload(size):
  30. ret = f.read(size)
  31. # Entries are 2-byte aligned.
  32. if size & 1 != 0:
  33. f.read(1)
  34. return ret
  35. while True:
  36. entry = f.read(60)
  37. if not entry:
  38. return
  39. entry_name = entry[:16].rstrip()
  40. entry_size = int(entry[48:58].rstrip())
  41. if entry_name in (b'', b'/', b'//', b'/SYM64/'):
  42. payload = read_payload(entry_size)
  43. # Metadata sections we don't care about.
  44. if entry_name == b'//':
  45. name_list = payload
  46. continue
  47. if entry_name[0:1] == b'/':
  48. # Name is specified as location in name table.
  49. # E.g.: /123
  50. name_offset = int(entry_name[1:])
  51. # String table enties are delimited by \n (e.g. "browser.o/\n").
  52. end_idx = name_list.index(b'\n', name_offset)
  53. entry_name = name_list[name_offset:end_idx]
  54. else:
  55. # Name specified inline with spaces for padding (e.g. "browser.o/ ").
  56. entry_name = entry_name.rstrip()
  57. yield entry_name.rstrip(b'/').decode('ascii')
  58. def ExpandThinArchives(paths):
  59. """Expands all thin archives found in |paths| into .o paths.
  60. Args:
  61. paths: List of paths relative to |output_directory|.
  62. output_directory: Output directory.
  63. Returns:
  64. * A new list of paths with all archives replaced by .o paths.
  65. """
  66. expanded_paths = []
  67. for path in paths:
  68. if not path.endswith('.a'):
  69. expanded_paths.append(path)
  70. continue
  71. with open(path, 'rb') as f:
  72. header = f.read(8)
  73. is_thin = header == b'!<thin>\n'
  74. if is_thin:
  75. for subpath in _IterThinPaths(path):
  76. expanded_paths.append(_ResolveThinObjectPath(path, subpath))
  77. elif header != b'!<arch>\n':
  78. raise Exception('Invalid .a: ' + path)
  79. return expanded_paths