microchip-devtools 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env python3
2
+ """Detect initialized static variables that would trigger XC32 __dinit_copy_val_data bug.
3
+
4
+ XC32 selects fmt=3 when all array elements equal the same non-zero constant.
5
+ The fmt=3 handler (__dinit_copy_val_data) decrements nbytes by 4 per iteration
6
+ and exits only when nbytes == 0. If nbytes % 4 != 0 the counter wraps to
7
+ 0xFFFFFFFC and the loop writes forever, corrupting all memory before crashing
8
+ with a Data Bus Error at an unmapped address.
9
+
10
+ The emulator build uses mipsel-linux-gnu-gcc and --gc-sections, so some
11
+ translation units are eliminated from the final ELF even though they ARE linked
12
+ in the real XC32 build. Scanning only the final ELF misses those. This script
13
+ scans individual object files (.o) where all symbols are present before GC.
14
+
15
+ Usage:
16
+ mchp-validate-xc32 <elf_or_obj> [<elf_or_obj> ...]
17
+ mchp-validate-xc32 --objects-dir <dir>
18
+
19
+ Exit codes:
20
+ 0 — no violations found
21
+ 1 — one or more XC32 fmt=3 risks detected (also exits 1 on parse error)
22
+ """
23
+
24
+ import os
25
+ import struct
26
+ import sys
27
+
28
+ SHT_SYMTAB = 2
29
+ SHT_STRTAB = 3
30
+ SHT_PROGBITS = 1
31
+ SHF_ALLOC = 0x2
32
+ SHF_WRITE = 0x1
33
+ STT_OBJECT = 1
34
+
35
+ MIN_SYM_SIZE = 4
36
+
37
+
38
+ def _read_shdr(f, endian, shoff, shentsize, idx):
39
+ f.seek(shoff + idx * shentsize)
40
+ raw = f.read(shentsize)
41
+ if len(raw) < 40:
42
+ return None
43
+ sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, \
44
+ sh_link, _info, _align, sh_entsize = \
45
+ struct.unpack_from(f"{endian}10I", raw)
46
+ return {
47
+ "name_idx": sh_name,
48
+ "type": sh_type,
49
+ "flags": sh_flags,
50
+ "addr": sh_addr,
51
+ "offset": sh_offset,
52
+ "size": sh_size,
53
+ "link": sh_link,
54
+ "entsize": sh_entsize,
55
+ }
56
+
57
+
58
+ def _section_name(f, shstrtab, name_idx):
59
+ f.seek(shstrtab["offset"] + name_idx)
60
+ buf = bytearray()
61
+ while True:
62
+ c = f.read(1)
63
+ if not c or c == b"\x00":
64
+ break
65
+ buf.extend(c)
66
+ return buf.decode("ascii", errors="replace")
67
+
68
+
69
+ def _read_cstr(data, offset):
70
+ end = data.index(b"\x00", offset)
71
+ return data[offset:end].decode("ascii", errors="replace")
72
+
73
+
74
+ def _is_fmt3_trigger(data):
75
+ """True if XC32 would choose fmt=3: all bytes non-zero and bytes[0:4] repeated."""
76
+ if len(data) < 4:
77
+ return False
78
+ if all(b == 0 for b in data):
79
+ return False
80
+ fill = data[0:4]
81
+ for i in range(0, len(data), 4):
82
+ chunk = data[i:i + 4]
83
+ if chunk != fill[:len(chunk)]:
84
+ return False
85
+ return True
86
+
87
+
88
+ def scan_elf(path):
89
+ """Scan one ELF32 file (.o or final ELF). Return list of violation dicts."""
90
+ violations = []
91
+
92
+ with open(path, "rb") as f:
93
+ e_ident = f.read(16)
94
+ if e_ident[:4] != b"\x7fELF" or e_ident[4] != 1:
95
+ return violations
96
+
97
+ endian = "<" if e_ident[5] == 1 else ">"
98
+ hdr = f.read(36)
99
+ e_shoff = struct.unpack_from(f"{endian}I", hdr, 16)[0]
100
+ e_shentsize = struct.unpack_from(f"{endian}H", hdr, 30)[0]
101
+ e_shnum = struct.unpack_from(f"{endian}H", hdr, 32)[0]
102
+ e_shstrndx = struct.unpack_from(f"{endian}H", hdr, 34)[0]
103
+
104
+ if e_shoff == 0 or e_shnum == 0:
105
+ return violations
106
+
107
+ shdrs = []
108
+ for i in range(e_shnum):
109
+ sh = _read_shdr(f, endian, e_shoff, e_shentsize, i)
110
+ if sh is None:
111
+ return violations
112
+ shdrs.append(sh)
113
+
114
+ shstrtab = shdrs[e_shstrndx]
115
+ for sh in shdrs:
116
+ sh["name"] = _section_name(f, shstrtab, sh["name_idx"])
117
+
118
+ symtab = next((s for s in shdrs if s["type"] == SHT_SYMTAB), None)
119
+ if symtab is None:
120
+ return violations
121
+
122
+ strtab_sh = shdrs[symtab["link"]]
123
+ f.seek(strtab_sh["offset"])
124
+ strtab = f.read(strtab_sh["size"])
125
+
126
+ data_sections = {}
127
+ for idx, sh in enumerate(shdrs):
128
+ if (sh["type"] == SHT_PROGBITS
129
+ and (sh["flags"] & SHF_ALLOC)
130
+ and (sh["flags"] & SHF_WRITE)
131
+ and sh["size"] > 0):
132
+ f.seek(sh["offset"])
133
+ data_sections[idx] = (sh, f.read(sh["size"]))
134
+
135
+ sym_entsize = symtab["entsize"] or 16
136
+ n_syms = symtab["size"] // sym_entsize
137
+ f.seek(symtab["offset"])
138
+ for _ in range(n_syms):
139
+ raw = f.read(sym_entsize)
140
+ st_name, st_value, st_size, st_info, _, st_shndx = \
141
+ struct.unpack_from(f"{endian}IIIBBH", raw)
142
+
143
+ if (st_info & 0x0F) != STT_OBJECT:
144
+ continue
145
+ if st_size < MIN_SYM_SIZE:
146
+ continue
147
+ if st_shndx not in data_sections:
148
+ continue
149
+
150
+ sh, sec_bytes = data_sections[st_shndx]
151
+ offset = st_value - sh["addr"]
152
+ if offset < 0 or offset + st_size > len(sec_bytes):
153
+ continue
154
+
155
+ sym_bytes = sec_bytes[offset:offset + st_size]
156
+ if not _is_fmt3_trigger(sym_bytes):
157
+ continue
158
+ if st_size % 4 == 0:
159
+ continue
160
+
161
+ name = _read_cstr(strtab, st_name)
162
+ fill_word = int.from_bytes(sym_bytes[:4], "little")
163
+ violations.append({
164
+ "file": path,
165
+ "name": name,
166
+ "size": st_size,
167
+ "fill_word": fill_word,
168
+ "addr": st_value,
169
+ "section": sh["name"],
170
+ })
171
+
172
+ return violations
173
+
174
+
175
+ def find_objects(root):
176
+ for dirpath, _, filenames in os.walk(root):
177
+ for fn in filenames:
178
+ if fn.endswith(".o"):
179
+ yield os.path.join(dirpath, fn)
180
+
181
+
182
+ def main():
183
+ args = sys.argv[1:]
184
+ if not args:
185
+ print(f"Usage: {sys.argv[0]} <file.o|file.elf> [...] | --objects-dir <dir>",
186
+ file=sys.stderr)
187
+ sys.exit(1)
188
+
189
+ paths = []
190
+ if args[0] == "--objects-dir":
191
+ if len(args) < 2:
192
+ print("--objects-dir requires a path", file=sys.stderr)
193
+ sys.exit(1)
194
+ paths = list(find_objects(args[1]))
195
+ if not paths:
196
+ print("validate_xc32_fmt3: no .o files found under " + args[1])
197
+ sys.exit(0)
198
+ else:
199
+ paths = args
200
+
201
+ all_violations = []
202
+ for p in paths:
203
+ try:
204
+ all_violations.extend(scan_elf(p))
205
+ except (OSError, struct.error, ValueError) as e:
206
+ print(f"validate_xc32_fmt3: warning: could not parse {p}: {e}",
207
+ file=sys.stderr)
208
+
209
+ if not all_violations:
210
+ sys.exit(0)
211
+
212
+ for v in all_violations:
213
+ leftover = v["size"] % 4
214
+ print(
215
+ f"ERROR: XC32 fmt=3 risk in '{os.path.basename(v['file'])}': "
216
+ f"symbol '{v['name']}'\n"
217
+ f" size={v['size']} bytes ({leftover} leftover after /4) "
218
+ f"fill_word=0x{v['fill_word']:08X} "
219
+ f"section={v['section']}\n"
220
+ f" __dinit_copy_val_data will loop forever on hardware.\n"
221
+ f" Fix: make sizeof({v['name']}) divisible by 4, "
222
+ f"or use non-uniform init, or change to uint32_t."
223
+ )
224
+
225
+ print(f"\nvalidate_xc32_fmt3: FAIL — {len(all_violations)} violation(s) found")
226
+ sys.exit(1)
227
+
228
+
229
+ if __name__ == "__main__":
230
+ main()
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: microchip-devtools
3
+ Version: 0.1.0
4
+ Summary: Shared build and dev environment scripts for Microchip firmware projects
5
+ Author: Ericson Joseph
6
+ Author-email: joseph.ericson@allthings.me
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: python-dotenv (>=1.2.2,<2.0.0)
15
+ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
16
+ Requires-Dist: rich (>=13.0,<15.0)
@@ -0,0 +1,21 @@
1
+ microchip_devtools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ microchip_devtools/_project.py,sha256=vkZ12yK0MJyBhAYS8t8W8VbcVbTL4zQWbqjGBp1QMvA,455
3
+ microchip_devtools/format/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ microchip_devtools/format/uncrustify.py,sha256=XYzEey4snVqhJhdGX5CkQNHuLM0rlhg_2O4EtP9290o,3378
5
+ microchip_devtools/list_cmds.py,sha256=cym2BSKMJiYX4jhEWs7YqOMB_OylknZLawagDKKhv6g,1026
6
+ microchip_devtools/mcc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ microchip_devtools/mcc/check_peripheral.py,sha256=KJWCIeRmnLnyyxggy7DTR6zyQGLgZTa9opQImb1XLA4,7065
8
+ microchip_devtools/mcc/mcc_refresh.py,sha256=28BDvw5jF5PORB84K9P0qtE_VcO78RHyy5hmhMtdmKs,11504
9
+ microchip_devtools/mcc/parse_hardware.py,sha256=s3_g7XyFXXzmkNoPsKVJdtPLCIXJO0_OH4Zx17ScAmE,12863
10
+ microchip_devtools/setup_env/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ microchip_devtools/setup_env/_ui.py,sha256=SWcayUN65EGCkAZBDj9QKXl6rBCOrBnIyX37BCIvroQ,1866
12
+ microchip_devtools/setup_env/checks.py,sha256=E-gEZBKQAVoPOnflGHTBBIPNjsXJSLtEKSeaIxnNZZI,5644
13
+ microchip_devtools/setup_env/defaults.py,sha256=9h4m7RB0CT0kSoZddHqxfBp1_cC05dRo4kMSNTFLETE,999
14
+ microchip_devtools/setup_env/runner.py,sha256=HjIJjM-8Dmf0E3XO-rBx4XsTFe1wZhBY0JqP0EAYj5w,10845
15
+ microchip_devtools/xc32/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ microchip_devtools/xc32/merge_hex.py,sha256=LGmsuPvTGEHz-9VF7LagoCFsY7p3j4bYwftSBYd3p1Q,7951
17
+ microchip_devtools/xc32/validate_fmt3.py,sha256=Su-3h3TTSjVOfRJymnbdBwkiVNm_oQLpiaIwxY6pYNg,7257
18
+ microchip_devtools-0.1.0.dist-info/METADATA,sha256=ruSXx5zYPqtopb9kHPdJkljaXYIuRbX3dwMbAFq3m6Q,661
19
+ microchip_devtools-0.1.0.dist-info/WHEEL,sha256=EGEvSphFYqXKs23-kQBeyNoJP1nrT8ZJKQoi5p5DYL8,88
20
+ microchip_devtools-0.1.0.dist-info/entry_points.txt,sha256=Lia67trwqHwmfgBXV8elcAYV00-u1lTLY2ogq7EPtnY,440
21
+ microchip_devtools-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.4.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,10 @@
1
+ [console_scripts]
2
+ check-peripheral=microchip_devtools.mcc.check_peripheral:main
3
+ format=microchip_devtools.format.uncrustify:main
4
+ list=microchip_devtools.list_cmds:main
5
+ mcc-refresh=microchip_devtools.mcc.mcc_refresh:main
6
+ merge-hex=microchip_devtools.xc32.merge_hex:main
7
+ parse-hardware=microchip_devtools.mcc.parse_hardware:main
8
+ project-setup=microchip_devtools.setup_env.runner:main
9
+ validate-fmt3=microchip_devtools.xc32.validate_fmt3:main
10
+