fw_binary_info.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. #!/usr/bin/env python
  2. # Copyright 2024 Google LLC
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from binascii import crc32
  16. import os
  17. import struct
  18. from functools import reduce
  19. import stm32_crc
  20. class PebbleFirmwareBinaryInfo(object):
  21. V1_STRUCT_VERSION = 1
  22. V1_STRUCT_DEFINTION = [
  23. ('20s', 'build_id'),
  24. ('L', 'version_timestamp'),
  25. ('32s', 'version_tag'),
  26. ('8s', 'version_short'),
  27. ('?', 'is_recovery_firmware'),
  28. ('B', 'hw_platform'),
  29. ('B', 'metadata_version')
  30. ]
  31. # The platforms which use a legacy defective crc32
  32. LEGACY_CRC_PLATFORMS = [
  33. 0, # unknown (assume legacy)
  34. 1, # OneEV1
  35. 2, # OneEV2
  36. 3, # OneEV2_3
  37. 4, # OneEV2_4
  38. 5, # OnePointFive
  39. 6, # TwoPointFive
  40. 7, # SnowyEVT2
  41. 8, # SnowyDVT
  42. 9, # SpaldingEVT
  43. 10, # BobbyDVT
  44. 11, # Spalding
  45. 0xff, # OneBigboard
  46. 0xfe, # OneBigboard2
  47. 0xfd, # SnowyBigboard
  48. 0xfc, # SnowyBigboard2
  49. 0xfb, # SpaldingBigboard
  50. ]
  51. def get_crc(self):
  52. _, ext = os.path.splitext(self.path)
  53. assert ext == '.bin', 'Can only calculate crc for .bin files'
  54. with open(self.path, 'rb') as f:
  55. image = f.read()
  56. if self.hw_platform in self.LEGACY_CRC_PLATFORMS:
  57. # use the legacy defective crc
  58. return stm32_crc.crc32(image)
  59. else:
  60. # use a regular crc
  61. return crc32(image) & 0xFFFFFFFF
  62. def _get_footer_struct(self):
  63. fmt = '<' + reduce(lambda s, t: s + t[0],
  64. PebbleFirmwareBinaryInfo.V1_STRUCT_DEFINTION, '')
  65. return struct.Struct(fmt)
  66. def _get_footer_data_from_elf(self, path):
  67. import binutils
  68. fw_version_data = binutils.section_bytes(path, '.fw_version')
  69. # The GNU Build ID has 16 bytes of header data, strip it off:
  70. build_id_data = binutils.section_bytes(path, '.note.gnu.build-id')[16:]
  71. return build_id_data + fw_version_data
  72. def _get_footer_data_from_bin(self, path):
  73. with open(path, 'rb') as f:
  74. struct_size = self.struct.size
  75. f.seek(-struct_size, 2)
  76. footer_data = f.read()
  77. return footer_data
  78. def _parse_footer_data(self, footer_data):
  79. z = zip(PebbleFirmwareBinaryInfo.V1_STRUCT_DEFINTION,
  80. self.struct.unpack(footer_data))
  81. return {entry[1]: data for entry, data in z}
  82. def __init__(self, elf_or_bin_path):
  83. self.path = elf_or_bin_path
  84. self.struct = self._get_footer_struct()
  85. _, ext = os.path.splitext(elf_or_bin_path)
  86. if ext == '.elf':
  87. footer_data = self._get_footer_data_from_elf(elf_or_bin_path)
  88. elif ext == '.bin':
  89. footer_data = self._get_footer_data_from_bin(elf_or_bin_path)
  90. else:
  91. raise ValueError('Unexpected extension. Must be ".bin" or ".elf"')
  92. self.info = self._parse_footer_data(footer_data)
  93. # Trim leading NULLS on the strings:
  94. for k in ["version_tag", "version_short"]:
  95. self.info[k] = self.info[k].rstrip(b"\x00")
  96. def __str__(self):
  97. return str(self.info)
  98. def __repr__(self):
  99. return self.info.__repr__()
  100. def __getattr__(self, name):
  101. if name in self.info:
  102. return self.info[name]
  103. raise AttributeError
  104. if __name__ == "__main__":
  105. import argparse
  106. import pprint
  107. parser = argparse.ArgumentParser()
  108. parser.add_argument('fw_bin_or_elf_path')
  109. args = parser.parse_args()
  110. fw_bin_info = PebbleFirmwareBinaryInfo(args.fw_bin_or_elf_path)
  111. pprint.pprint(fw_bin_info)