Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/scripts/firmware_version_decoder.py
9398 views
1
#!/usr/bin/env python3
2
3
# flake8: noqa
4
5
import enum
6
import io
7
import sys
8
import struct
9
from argparse import ArgumentParser
10
from dataclasses import dataclass
11
from elftools.elf.elffile import ELFFile
12
from typing import Any
13
14
15
class FirmwareVersionType(enum.Enum):
16
Dev = 0
17
Alpha = 64
18
Beta = 128
19
RC = 192
20
Official = 255
21
EnumEnd = 256
22
23
@staticmethod
24
def get_release(version: int) -> str:
25
"""
26
Return the closest release type for a given version type, going down.
27
This is required because it is common in ardupilot to increase the version type
28
for successive betas, such as here:
29
https://github.com/ArduPilot/ardupilot/blame/8890c44370a7cf27d5efc872ef6da288ae3bc41f/ArduCopter/version.h#L12
30
"""
31
for release in reversed(FirmwareVersionType):
32
if version >= release.value:
33
return release
34
return "Unknown"
35
36
class VehicleType(enum.Enum):
37
Rover = 1
38
ArduCopter = 2
39
ArduPlane = 3
40
AntennaTracker = 4
41
UNKNOWN = 5
42
Replay = 6
43
ArduSub = 7
44
iofirmware = 8
45
AP_Periph = 9
46
NONE = 256
47
48
49
class BoardType(enum.Enum):
50
SITL = 3
51
SMACCM = 4
52
PX4 = 5
53
LINUX = 7
54
VRBRAIN = 8
55
CHIBIOS = 10
56
F4LIGHT = 11
57
EMPTY = 99
58
59
60
class BoardSubType(enum.Enum):
61
NONE = 65535
62
63
LINUX_NONE = 1000
64
LINUX_ERLEBOARD = 1001
65
LINUX_PXF = 1002
66
LINUX_NAVIO = 1003
67
LINUX_ZYNQ = 1004
68
LINUX_BBBMINI = 1005
69
LINUX_BEBOP = 1006
70
LINUX_ERLEBRAIN2 = 1009
71
LINUX_BH = 1010
72
LINUX_PXFMINI = 1012
73
LINUX_NAVIO2 = 1013
74
LINUX_DISCO = 1014
75
LINUX_AERO = 1015
76
LINUX_DARK = 1016
77
LINUX_BLUE = 1018
78
LINUX_OCPOC_ZYNQ = 1019
79
LINUX_EDGE = 1020
80
LINUX_RST_ZYNQ = 1021
81
LINUX_POCKET = 1022
82
LINUX_NAVIGATOR = 1023
83
LINUX_VNAV = 1024
84
LINUX_OBAL = 1025
85
LINUX_CANZERO = 1026
86
LINUX_T3_GEM_O1 = 1029
87
CHIBIOS_SKYVIPER_F412 = 5000
88
CHIBIOS_FMUV3 = 5001
89
CHIBIOS_FMUV4 = 5002
90
CHIBIOS_GENERIC = 5009
91
CHIBIOS_FMUV5 = 5013
92
CHIBIOS_VRBRAIN_V51 = 5016
93
CHIBIOS_VRBRAIN_V52 = 5017
94
CHIBIOS_VRUBRAIN_V51 = 5018
95
CHIBIOS_VRCORE_V10 = 5019
96
CHIBIOS_VRBRAIN_V54 = 5020
97
98
99
@dataclass
100
class FWVersion:
101
header: int = 0x61706677766572FB
102
header_version: bytes = bytes([0, 0])
103
pointer_size: int = 0
104
vehicle_type: VehicleType = VehicleType.NONE
105
board_type: BoardType = BoardType.EMPTY
106
board_subtype: BoardSubType = BoardSubType.NONE
107
major: int = 0
108
minor: int = 0
109
patch: int = 0
110
firmware_type: FirmwareVersionType = FirmwareVersionType.EnumEnd
111
os_software_version: int = 0
112
firmware_string: str = ""
113
firmware_hash_string: str = ""
114
firmware_hash: int = 0
115
middleware_name: str = ""
116
middleware_hash_string: str = ""
117
os_name: str = ""
118
os_hash_string: str = ""
119
120
def __str__(self):
121
header = self.header.to_bytes(8, byteorder="big")
122
header_version = self.header_version.to_bytes(2, byteorder="big")
123
firmware_day = self.os_software_version % 100
124
firmware_month = self.os_software_version % 10000 - firmware_day
125
firmware_year = self.os_software_version - firmware_month - firmware_day
126
firmware_month = int(firmware_month / 100)
127
firmware_year = int(firmware_year / 10000)
128
return f"""
129
{self.__class__.__name__}:
130
header:
131
magic: {header[0:7].decode("utf-8")}
132
checksum: {hex(header[-1])}
133
version: {header_version[0]}.{header_version[1]}
134
pointer_size: {self.pointer_size}
135
firmware:
136
string: {self.firmware_string}
137
vehicle: {self.vehicle_type.name}
138
board: {self.board_type.name}
139
board subtype: {self.board_subtype.name}
140
hash: {self.firmware_hash_string}
141
hash integer: 0x{self.firmware_hash:02x}
142
version: {self.major}.{self.minor}.{self.patch}
143
type: {self.firmware_type.name}
144
os:
145
name: {self.os_name}
146
hash: {self.os_hash_string}
147
software_version: {firmware_day}/{firmware_month}/{firmware_year}
148
middleware:
149
name: {self.middleware_name}
150
hash: {self.middleware_hash_string}
151
"""
152
153
154
class Decoder:
155
def __init__(self) -> None:
156
self.bytesio = io.BytesIO()
157
self.fwversion = FWVersion()
158
self.byteorder = ""
159
self.pointer_size = 0
160
self.elffile = None
161
162
def unpack(self, struct_format: str) -> Any:
163
struct_format = f"{self.byteorder}{struct_format}"
164
size = struct.calcsize(struct_format)
165
return struct.unpack(struct_format, self.bytesio.read(size))[0]
166
167
def unpack_string_from_pointer(self) -> str:
168
pointer_format = "Q" if self.pointer_size == 8 else "I"
169
address = self.unpack(pointer_format)
170
171
# nullptr, return empty string
172
if address == 0:
173
return ""
174
175
# Calculate address offset for PIE (Position Independent Executables) binaries
176
address = next(self.elffile.address_offsets(address))
177
178
current_address = self.bytesio.seek(0, io.SEEK_CUR)
179
self.bytesio.seek(address)
180
string = []
181
while True:
182
string += self.bytesio.read(1)
183
if string[-1] == 0:
184
string = string[0 : len(string) - 1]
185
break
186
self.bytesio.seek(current_address)
187
return bytes(string).decode("UTF-8")
188
189
@staticmethod
190
def locate_header(data: bytes, byteorder: str) -> int:
191
return data.find(struct.pack(f"{byteorder}Q", FWVersion.header))
192
193
def unpack_fwversion(self) -> None:
194
assert self.bytesio.read(8) == struct.pack(
195
f"{self.byteorder}Q", FWVersion.header
196
)
197
198
self.fwversion.header_version = self.unpack("H")
199
major_version = self.fwversion.header_version >> 8
200
201
self.pointer_size = self.unpack("B")
202
self.fwversion.pointer_size = self.pointer_size
203
self.unpack("B") # reserved
204
self.fwversion.vehicle_type = VehicleType(self.unpack("B"))
205
self.fwversion.board_type = BoardType(self.unpack("B"))
206
self.fwversion.board_subtype = BoardSubType(self.unpack("H"))
207
208
self.fwversion.major = self.unpack("B")
209
self.fwversion.minor = self.unpack("B")
210
self.fwversion.patch = self.unpack("B")
211
self.fwversion.firmware_type = FirmwareVersionType.get_release(self.unpack("B"))
212
self.fwversion.os_software_version = self.unpack("I")
213
214
self.fwversion.firmware_string = self.unpack_string_from_pointer()
215
self.fwversion.firmware_hash_string = self.unpack_string_from_pointer()
216
if major_version >= 2:
217
self.fwversion.firmware_hash = self.unpack("I")
218
219
self.fwversion.middleware_name = self.unpack_string_from_pointer()
220
self.fwversion.middleware_hash_string = self.unpack_string_from_pointer()
221
self.fwversion.os_name = self.unpack_string_from_pointer()
222
self.fwversion.os_hash_string = self.unpack_string_from_pointer()
223
224
def process(self, filename) -> FWVersion:
225
# We need the file open for ELFFile
226
file = open(filename, "rb")
227
data = file.read()
228
self.elffile = ELFFile(file)
229
230
if not data:
231
raise RuntimeError("Failed to find FWVersion.")
232
233
# Detect endianness
234
for order in [">", "<"]:
235
position = Decoder.locate_header(data, order)
236
if position != -1:
237
self.byteorder = order
238
self.bytesio = io.BytesIO(data)
239
self.bytesio.seek(position)
240
break
241
else:
242
raise RuntimeError("Failed to find FWVersion.")
243
244
# Unpack struct and print it
245
self.unpack_fwversion()
246
return self.fwversion
247
248
249
if __name__ == "__main__":
250
assert (
251
sys.version_info.major >= 3 and sys.version_info.minor >= 7
252
), "Python version should be at least 3.7"
253
254
# Parse arguments
255
parser = ArgumentParser(description=__doc__)
256
parser.add_argument(
257
"-f",
258
dest="file",
259
required=True,
260
help="File that contains a valid ardupilot firmware in ELF format.",
261
)
262
parser.add_argument(
263
"--expected-hash",
264
dest="expected_hash",
265
help="Expected git hash. The script fails if this doesn't match the git hash in the binary file. Used in CI",
266
)
267
args = parser.parse_args()
268
269
decoder = Decoder()
270
try:
271
firmware_data = decoder.process(args.file)
272
except Exception as e:
273
print(f"Error decoding FWVersion: {type(e)}")
274
exit(-1)
275
276
print(firmware_data)
277
if args.expected_hash and args.expected_hash != firmware_data.firmware_hash_string:
278
print(f"Git hashes don't match! expected: {args.expected_hash}, got {firmware_data.firmware_hash_string}")
279
exit(-1)
280
281