blob: 2598c541ba95f3bc1941b2f925e068f2fb860399 [file] [log] [blame]
Raef Coles59cf5d82024-12-09 15:41:13 +00001#!/usr/bin/env python3
2#-------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
4#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7#-------------------------------------------------------------------------------
8
9import clang.cindex as cl
10import struct
11from collections import Counter
12import os
Antonio de Angelis709309f2025-05-10 22:39:49 +010013import subprocess
14import platform
Raef Coles59cf5d82024-12-09 15:41:13 +000015
16import logging
Raef Colescfc31242025-04-04 09:38:47 +010017logger = logging.getLogger("TF-M.{}".format(__name__))
Raef Coles59cf5d82024-12-09 15:41:13 +000018
19from rich import inspect
20
21pad = 4
22
23format_chars = {
24 'uint8_t' : 'B',
25 'uint16_t' : 'H',
26 'uint32_t' : 'I',
27 'uint64_t' : 'Q',
28 'bool' : '?',
29 'char' : 'b',
30 'short' : 'H',
31 'int' : 'I',
32 'long' : 'I',
33 'long long' : 'Q',
34 'uintptr_t' : 'I',
35 'size_t' : 'I',
36 'float' : 'f',
37 'double' : 'd',
38 'long double' : 'd',
39}
40
Antonio de Angelis709309f2025-05-10 22:39:49 +010041
42def get_macos_sdk_path():
43 """Get the path to the macOS SDK via xcrun."""
44 try:
45 result = subprocess.run(
46 ['xcrun', '--sdk', 'macosx', '--show-sdk-path'],
47 capture_output=True,
48 text=True,
49 check=True
50 )
51 return result.stdout.strip()
52 except subprocess.CalledProcessError as e:
53 raise RuntimeError(f"Failed to get macOS SDK path: {e.stderr}")
54
55
Raef Coles59cf5d82024-12-09 15:41:13 +000056def _c_struct_or_union_from_cl_cursor(cursor, name, f):
57 def is_field(x):
58 if x.kind == cl.CursorKind.FIELD_DECL:
59 return True
60 if x.kind in [cl.CursorKind.STRUCT_DECL, cl.CursorKind.UNION_DECL]:
61 return True
62 return False
63
64 fields = [c for c in cursor.get_children() if is_field(c)]
65 packed = True if [c for c in cursor.get_children() if c.kind == cl.CursorKind.PACKED_ATTR] else False
66
67 c_type = cursor.spelling
68
69 fields = [_c_from_cl_cursor(f) for f in fields]
70 fields = [f for f in fields if f.get_size() != 0]
71
72 duplicate_types = Counter([f.c_type for f in fields if not isinstance(f, C_variable)])
73 duplicate_types = [k for k,v in duplicate_types.items() if v > 1]
74 fields = [f for f in fields if f.c_type not in duplicate_types or f.name != ""]
75
76 return f(name, c_type, fields, packed)
77
78def _pad_lines(text, size):
79 lines = text.split("\n")
80 lines = [" " * size + l for l in lines]
81 return "\n".join(lines)
82
83def _c_struct_or_union_get_value_str(self, struct_or_union):
84 string = "{\n"
85 fields_string = ""
86 for f in self._fields:
87 if f.to_bytes() != bytes(f.get_size()):
88 fields_string += _pad_lines(".{} = {},".format(f.name, f.get_value_str()), pad)
Raef Coles2e6539f2025-04-04 10:29:37 +010089 fields_string += "\n"
90 fields_string = fields_string[:-1]
91 if not fields_string:
92 return "{}"
Raef Coles59cf5d82024-12-09 15:41:13 +000093 string += fields_string
94 string += "\n}"
95 return string
96
97def _c_struct_or_union_str(self, struct_or_union):
98 string = ""
99 string += "{} ".format(struct_or_union)
100 string += "__attribute__((packed)) " if self._packed else ""
101 string += "{} ".format(self.c_type) if self.c_type != "" else ""
102 string += "{\n"
103
104 fields_string = "\n".join([_pad_lines(str(f), pad) for f in self._fields])
105 string += fields_string
106
107 if string[-1] != "\n":
108 string += "\n"
109
110 string += "}"
111 string += " {}".format(self.name) if self.name != "" else ""
112 string += ";"
113 return string
114
115def _c_struct_or_union_get_field_strings(self):
116 field_strings=[]
117
118 for f in self._fields:
119 name_format = "{}.".format(self.name) if self.name != "" else ""
120 field_strings += [name_format + s for s in f.get_field_strings()]
121
122 return field_strings
123
124def _c_struct_or_union_field_to_bytes(field):
125 if isinstance(field, C_variable):
126 return field._get_format_string(), field.get_value()
127 else:
128 return _binary_format_string(field.get_size()), field.to_bytes()
129
130def _c_struct_or_union_get_next_in_path(self, field_path):
131 field_separator = "."
132
133 if field_separator in field_path:
134 field_name, remainder = field_path.split(".", 1)
135 else:
136 field_name = field_path
137 remainder = None
138
139 try:
140 # Fields aren't allowed to duplicate names
141 field = [f for f in self._fields if f.name == field_name][0]
142 return field, remainder
143 except (KeyError, IndexError):
144 for f in self._fields:
145 try:
146 f.get_field(field_path)
147 return f, field_path
148 except KeyError:
149 continue
150 raise KeyError
151
152def _c_struct_or_union_get_direct_members(self):
153 subfields = []
154 for f in self._fields:
155 if f.name:
156 continue
157 subfields += _c_struct_or_union_get_direct_members(f)
158
159 return [f for f in self._fields if f.name] + subfields
160
161def _c_struct_or_union_get_field(self, field_path):
162 fields = [f for f in self._fields if f.name == field_path[:len(f.name)]]
163 for f in fields:
164 try:
165 remainder = field_path.replace(f.name + ".", "") if f.name else field_path
166 return f.get_field(remainder)
167 except (KeyError, ValueError):
168 continue
169 raise KeyError
170
171
172def _binary_format_string(size):
173 return "{}s".format(size)
174
175class C_enum:
176 def __init__(self, name, c_type, members):
177 self.name = name.replace("enum", "").lstrip()
178 self._members = members
179 self.dict = {m.name:m for m in self._members}
180 self.__dict__.update({m.name:m for m in self._members})
181
182 @staticmethod
183 def from_h_file(h_file, name, includes = [], defines = []):
184 return _c_from_h_file(h_file, name, includes, defines, C_enum, cl.CursorKind.ENUM_DECL)
185
186 @staticmethod
187 def from_cl_cursor(cursor, name=""):
188 definition = cursor.get_definition()
189 assert(definition.kind == cl.CursorKind.ENUM_DECL)
190
191 max_value = max([x.enum_value for x in definition.get_children()])
192 if (max_value <= 2^8):
193 c_type = 'uint8_t'
194 elif (max_value <= 2^16):
195 c_type = 'uint16_t'
196 else:
197 c_type = 'uint32_t'
198
199 members = [C_enum_member(x.spelling, c_type, x.enum_value) for x in definition.get_children()]
200 members = [m for m in members if m.name[0] != '_']
201 return C_enum(cursor.spelling, c_type, members)
202
203
204 def __str__(self):
205 pad = 4
206
207 string = ""
208
209 string += "enum "
210 string += "{} ".format(self.name)
211 string += "{\n"
212
213 fields_string = "\n".join([_pad_lines(str(f), pad) for f in self._members])
214 string += fields_string
215
216 if string[-1] != "\n":
217 string += "\n"
218
219 string += "};"
220 return string
221
222class C_enum_member:
223 def __init__(self, name, c_type, value):
224 self.name = name
225 self.c_type = c_type
226 self.value = value
227 self._format_string = self._get_format_string()
228 self._size = struct.calcsize(self._get_format_string());
229
230 def _get_format_string(self):
231 return format_chars[self.c_type]
232
233 def get_value(self):
234 return self.value
235
236 def to_bytes(self):
237 return struct.pack("<" + self._format_string, self.get_value())
238
239 def get_size(self):
240 return self._size
241
242 def __str__(self):
243 return self.name
244
245 def __repr__(self):
246 return self.name
247
248class C_array:
249 def __init__(self, name, c_type, members):
250 self.name = name
251 self.c_type = c_type
252 self._members = members
253
254 if self._members:
255 self._dimensions = [len(self._members)]
256 if isinstance(self._members[0], C_array):
257 self._dimensions += self._members[0]._dimensions
258 else:
259 self._dimensions = [0]
260
261 self._size = struct.calcsize(self._get_format_string());
262
263 @staticmethod
264 def from_h_file(h_file, name, includes = [], defines = []):
265 return _c_from_h_file(h_file, name, includes, defines, C_array, cl.CursorKind.TYPE_DECL)
266
267 @staticmethod
268 def from_cl_cursor(cursor, name="", dimensions = []):
269 c_type = cursor.type.spelling
270 c_type = c_type.replace("unsigned", "")
271 c_type = c_type.replace("volatile", "")
272 c_type = c_type.replace("const", "")
273 c_type = c_type.replace("static", "")
274
275 if not dimensions:
276 c_type, *dimensions = c_type.split("[")
277 dimensions = [0 if d == "]" else int(d.replace("]", "")) for d in dimensions]
278 c_type = c_type.strip()
279
280 assert(len(dimensions) > 0)
281
282 if (len(dimensions) > 1):
283 f = lambda x:C_array.from_cl_cursor(cursor, x, dimensions[1:])
284 elif c_type not in format_chars.keys():
285 f = lambda x:_c_from_cl_cursor(list(cursor.get_children())[0].get_definition(), x)
286 else:
287 f = lambda x:C_variable(x, c_type)
288
289 members = [f(name + "_{}".format(i)) for i in range(dimensions[0])]
290
291 return C_array(name, c_type, members)
292
293 def __getitem__(self, index):
294 return self._members[index]
295
296 # def __setitem__(self, index, value):
297 # return self._members[index].set_value_from_bytes(value)
298
299 def _get_format_string(self):
300 return "<" + "".join([_c_struct_or_union_field_to_bytes(m)[0] for m in self._members])
301
302 def get_value(self):
303 return self.to_bytes()
304
305 def set_value(self, value):
306 self.set_value_from_bytes(value)
307
308 def set_value_from_bytes(self, value):
309 assert(len(value) <= self._size), "{} of size {} cannot be set to value {} of size {}".format(self, self._size, value.hex(), len(value))
310 value_used = 0
311 for m in self._members:
312 if (value_used == len(value)):
313 break
314
315 m.set_value_from_bytes(value[value_used:value_used + m.get_size()])
316 value_used += m.get_size()
317
318 def get_field_strings(self):
319 return [self.name] + [f for m in self._members if not isinstance(m, C_variable) for f in m.get_field_strings()]
320
321 def get_field(self, field_path):
322 if field_path == self.name:
323 return self
324
325 field_path = field_path.replace(self.name + "_", "")
326
327 splits = [x for x in [field_path.find('.'), field_path.find("_")] if x != -1]
328
329 if not splits:
330 index = field_path
331 remainder = None
332 else:
333 split_idx = min(splits)
334 index = field_path[:split_idx]
335 remainder = field_path[split_idx + 1:]
336
337 index = int(index)
338
339 if (remainder):
340 return self._members[index].get_field(remainder)
341 else:
342 return self._members[index]
343
344 def to_bytes(self):
345 format_str = ""
346 values = []
347
348 for m in self._members:
349 field_string, field_data = _c_struct_or_union_field_to_bytes(m)
350 format_str += field_string
351 values.append(field_data)
352
353 return struct.pack(format_str, *values)
354
355 def get_size(self):
356 return self._size
357
358 def get_value_str(self):
359 string = ""
360 if self.to_bytes() != bytes(self.get_size()):
361 string += "{\n"
362 m_string = ""
Raef Coles2e6539f2025-04-04 10:29:37 +0100363 for idx,m in enumerate(self._members):
364 tmp = m.get_value_str() + ", "
365 m_string += tmp
366 if m_string[-3:] == "}, " or idx % 8 == 7:
367 m_string = m_string[:-1] + "\n"
368 m_string = m_string[:-1]
Raef Coles59cf5d82024-12-09 15:41:13 +0000369 string += _pad_lines(m_string, pad)
370 string += "\n}"
Raef Coles2e6539f2025-04-04 10:29:37 +0100371 return string
372 else:
373 return "{}"
Raef Coles59cf5d82024-12-09 15:41:13 +0000374
375 def __str__(self):
376 string = "{} {}".format(self.c_type, self.name)
377 string += "".join(["[{}]".format(a) for a in self._dimensions])
378
379 if self.to_bytes() != bytes(self.get_size()):
380 string += " = " + self.get_value_str()
381
382 string += ";"
383 return string
384
385class C_variable:
386 def __init__(self, name, c_type, value = None):
387 self.name = name
388 self.c_type = c_type
389 self._format_string = self._get_format_string()
390 self._size = struct.calcsize(self._format_string)
391 self.value = value
392
393 @staticmethod
394 def from_h_file(h_file, name, includes = [], defines = []):
395 return _c_from_h_file(h_file, name, includes, defines, C_variable, cl.CursorKind.TYPE_DECL)
396
397 @staticmethod
398 def from_cl_cursor(cursor, name=""):
399 c_type = cursor.type.spelling
400 c_type = c_type.replace("unsigned", "")
401 c_type = c_type.replace("volatile", "")
402 c_type = c_type.replace("const", "")
403 c_type = c_type.replace("static", "")
404
405 if "[" in c_type:
406 return C_array.from_cl_cursor(cursor, name)
407
408 return C_variable(name, c_type)
409
410 def _get_format_string(self):
411 if 'enum' in self.c_type:
412 return format_chars['uint32_t']
413
414 return format_chars[self.c_type]
415
416 def get_value(self):
417 value = self.value if self.value else 0
418 return value
419
420 def set_value(self, value):
421 if isinstance(value, str):
422 self.value = int(value, 0)
423 else:
424 self.value = value
425
426 #Sanity check the value
427 self.to_bytes()
428
429 def set_value_from_bytes(self, value):
430 self.set_value(struct.unpack(self._format_string, value)[0])
431
432 def get_field_strings(self):
433 return [self.name]
434
435 def get_field(self, field_path):
Jackson Cooper-Driveref9f7522025-02-18 12:04:04 +0000436 if field_path != self.name:
437 raise KeyError
Raef Coles59cf5d82024-12-09 15:41:13 +0000438 return self
439
440 def to_bytes(self):
441 return struct.pack("<" + self._format_string, self.get_value())
442
443 def get_size(self):
444 return self._size
445
446 def get_value_str(self):
Raef Coles2e6539f2025-04-04 10:29:37 +0100447 value = self.value or 0
448 return f"0x{value:02x}"
Raef Coles59cf5d82024-12-09 15:41:13 +0000449
450 def __str__(self):
451 string = "{} {}".format(self.c_type, self.name)
452
453 if self.value != None:
454 string += " = {}".format(self.get_value_str())
455
456 string += ";"
457 return string
458
459class C_union:
460 def __init__(self, name="", c_type="", fields=[], packed=False):
461 self.name = name
462 self.c_type = c_type.replace("union ", "")
463 self._fields = fields
464 self._packed = packed
465 self._format_strings = self._get_format_strings()
466 self._size = max(map(struct.calcsize, self._format_strings))
467 self._actual_value = bytes(self._size)
468 self.__dict__.update({f.name:f for f in _c_struct_or_union_get_direct_members(self)})
469
470 @staticmethod
471 def from_h_file(h_file, name, includes = [], defines = []):
472 return _c_from_h_file(h_file, name, includes, defines, C_union, cl.CursorKind.UNION_DECL)
473
474 @staticmethod
475 def from_cl_cursor(cursor, name=""):
476 return _c_struct_or_union_from_cl_cursor(cursor, name, C_union)
477
478 def _ensure_consistency(self):
479 binaries = [f.to_bytes() for f in self._fields]
480 new_binaries = [b for b in binaries if b != self._actual_value[:len(b)]]
481 new_binaries = list(set(new_binaries))
482
483 assert(len(new_binaries) < 2)
484
485 if new_binaries:
Jackson Cooper-Driver12c80b82025-04-29 13:55:09 +0100486 self._actual_value = new_binaries[0] + self._actual_value[len(new_binaries[0]):]
Raef Coles59cf5d82024-12-09 15:41:13 +0000487
488 for f in self._fields:
489 f.set_value_from_bytes(self._actual_value[:f._size])
490
491 def _get_format_strings(self):
492 format_endianness = "<" if self._packed else "@"
493 return ["{}{}".format(format_endianness, _binary_format_string(f._size)) for f in self._fields]
494
495 def set_value_from_bytes(self, value):
496 for f in self._fields:
497 f.set_value_from_bytes(value)
498 self._ensure_consistency()
499
500 def set_value(self, field_path, value):
501 field.set_value(self.get_field(field_path))
502 self._ensure_consistency()
503
504 def get_value(self, field_path):
505 self._ensure_consistency()
506 return self.get_field(field_path).value
507
508 def get_field_strings(self):
509 self._ensure_consistency()
510 return _c_struct_or_union_get_field_strings(self)
511
512 def get_field(self, field_path):
513 self._ensure_consistency()
514 return _c_struct_or_union_get_field(self, field_path)
515
516 def to_bytes(self):
517 self._ensure_consistency()
518 binaries = [f.to_bytes() for f in self._fields]
519
520 binaries = set(binaries)
521 return max(list(binaries))
522
523 def get_size(self):
524 return self._size
525
526 def get_value_str(self):
527 self._ensure_consistency()
528 return _c_struct_or_union_get_value_str(self, "union")
529
530 def __str__(self):
531 self._ensure_consistency()
532 return _c_struct_or_union_str(self, "union")
533
534class C_struct:
535 def __init__(self, name="", c_type="", fields=[], packed=False):
536 self.name = name
537 self.c_type = c_type.replace("struct ", "")
538 self._fields = fields
539 self._packed = packed
540 self._format_string = self._get_format_string()
541 self._size = struct.calcsize(self._format_string)
542 self.__dict__.update({f.name:f for f in _c_struct_or_union_get_direct_members(self)})
543
544 @staticmethod
545 def from_h_file(h_file, name, includes = [], defines = []):
546 return _c_from_h_file(h_file, name, includes, defines, C_struct, cl.CursorKind.STRUCT_DECL)
547
548 @staticmethod
549 def from_cl_cursor(cursor, name=""):
550 return _c_struct_or_union_from_cl_cursor(cursor, name, C_struct)
551
552 def _get_format_string(self):
553 format_endianness = "<" if self._packed else "@"
554 return format_endianness + "".join([_c_struct_or_union_field_to_bytes(f)[0] for f in self._fields])
555
556 def set_value_from_bytes(self, value):
557 value_used = 0
558 for f in self._fields:
559 f.set_value_from_bytes(value[value_used:value_used + f.get_size()])
560 value_used += f.get_size()
561 assert(value_used == len(value))
562
563 def set_value(self, field_path, value):
564 self.get_field(field_path).set_value(value)
565
566 def get_value(self, field_path):
567 return self.get_field(field_path).value
568
569 def get_field_strings(self):
570 return _c_struct_or_union_get_field_strings(self)
571
572 def get_field(self, field_path):
573 return _c_struct_or_union_get_field(self, field_path)
574
575 def to_bytes(self):
576 format_endianness = "<" if self._packed else "@"
577 format_str = ""
578 values = []
579
580 for f in self._fields:
581 field_string, field_data = _c_struct_or_union_field_to_bytes(f)
582 format_str += field_string
583 values.append(field_data)
584
585 return struct.pack(format_str, *values)
586
587 def get_size(self):
588 return self._size
589
590 def get_docs_table(self):
591 return _c_struct_or_union_get_docs_table(self)
592
593 def get_value_str(self):
594 return _c_struct_or_union_get_value_str(self, "struct")
595
596 def __str__(self):
597 return _c_struct_or_union_str(self, "struct")
598
599def _parse_field_dec(cursor):
600 return list(cursor.get_children())[0], cursor.spelling
601
602def _parse_type_ref(cursor, name):
603 return cursor.get_definition(), name
604
605def _c_from_cl_cursor(cursor, name = ""):
606 if cursor.kind == cl.CursorKind.STRUCT_DECL:
607 return C_struct.from_cl_cursor(cursor, name)
608
609 elif cursor.kind == cl.CursorKind.UNION_DECL:
610 return C_union.from_cl_cursor(cursor, name)
611
612 elif cursor.kind in [cl.CursorKind.TYPEDEF_DECL, cl.CursorKind.ENUM_DECL]:
613 return C_variable.from_cl_cursor(cursor, name)
614
615 elif cursor.kind == cl.CursorKind.FIELD_DECL:
616 if cursor.type.kind == cl.TypeKind.CONSTANTARRAY:
617 return C_variable.from_cl_cursor(cursor, cursor.spelling)
618
619 return _c_from_cl_cursor(*_parse_field_dec(cursor))
620
621 elif cursor.kind == cl.CursorKind.TYPE_REF:
622 return _c_from_cl_cursor(*_parse_type_ref(cursor, name))
623
624 raise NotImplementedError
625
626def _c_from_h_file(h_file, name, includes, defines, f, kind):
627 name = name.replace("struct ", "")
628 name = name.replace("union ", "")
629
630 args = ["-I{}".format(i) for i in includes if os.path.isdir(i)]
631 args += ["-D{}".format(d) for d in defines]
632
Antonio de Angelis709309f2025-05-10 22:39:49 +0100633 if platform.system() == 'Darwin':
634 args.extend(['-isysroot', get_macos_sdk_path()])
635
Raef Coles59cf5d82024-12-09 15:41:13 +0000636 if not os.path.isfile(h_file):
637 return FileNotFoundError
638
639 idx = cl.Index.create()
640 tu = idx.parse(h_file, args=args)
641
642 t = [cl.Cursor().from_location(tu, t.location) for t in tu.cursor.get_tokens() if t.spelling == name]
643 t = [x for x in t if x.kind == kind]
644
645 errors = ["{}: {} at {}:{}".format(d.category_name, d.spelling, d.location.file.name, d.location.line) for d in tu.diagnostics if d.severity > 2]
646 warnings = ["{}: {} at {}:{}".format(d.category_name, d.spelling, d.location.file.name, d.location.line) for d in tu.diagnostics if d.severity > 3]
647
648 for w in warnings:
649 print(w)
650
651 if errors:
652 for e in errors:
653 print(e)
654 exit(1)
655
656 if len(t) == 0:
657 print("Failed to find {} in {}".format(name, h_file))
658 exit(1)
659
660 assert(len(t) == 1)
661 t = t[0]
662
663 return f.from_cl_cursor(t, name)
664
665
666if __name__ == '__main__':
667 import argparse
668 import c_include
669
670 parser = argparse.ArgumentParser(allow_abbrev=False)
671 parser.add_argument("--h_file", help="header file to parse", required=True)
672 parser.add_argument("--struct_name", help="struct name to evaluate", required=True)
673 parser.add_argument("--compile_commands_file", help="header file to parse", required=True)
674 parser.add_argument("--c_file_to_mirror_includes_from", help="name of the c file to take", required=True)
675 parser.add_argument("--log_level", help="log level", required=False, default="ERROR", choices=logging._levelToName.values())
676 args = parser.parse_args()
Raef Colescfc31242025-04-04 09:38:47 +0100677 logging.getLogger("TF-M").setLevel(args.log_level)
678 logger.addHandler(logging.StreamHandler())
Raef Coles59cf5d82024-12-09 15:41:13 +0000679
680 includes = c_include.get_includes(args.compile_commands_file, args.c_file_to_mirror_includes_from)
681 defines = c_include.get_defines(args.compile_commands_file, args.c_file_to_mirror_includes_from)
682
683 s = C_struct.from_h_file(args.h_file, args.struct_name, includes, defines)
684 print(s)