Parse wasm-ld linker map for crate-level attribution. The wasm-ld --Map format has this structure: Addr Off Size Out In Symbol - CODE # top-level wasm section - :(<sect
(map_path)
| 228 | |
| 229 | |
| 230 | def parse_linker_map(map_path): |
| 231 | """Parse wasm-ld linker map for crate-level attribution. |
| 232 | |
| 233 | The wasm-ld --Map format has this structure: |
| 234 | Addr Off Size Out In Symbol |
| 235 | - <off> <size> CODE # top-level wasm section |
| 236 | - <off> <sz> <object>:(<section>) # object file contribution |
| 237 | - <off> <sz> <symbol> # symbol within object |
| 238 | <addr> <off> <size> .rodata # data sub-section |
| 239 | <addr> <off> <sz> <object>:(<section>) # object file contribution |
| 240 | |
| 241 | Returns (entries, section_sizes) where: |
| 242 | entries: list of (section, crate, symbol, size) tuples |
| 243 | section_sizes: dict mapping section name to total size from the map header |
| 244 | """ |
| 245 | if not map_path.exists(): |
| 246 | print(f"{YELLOW}Warning: linker map not found at {map_path}{RESET}", file=sys.stderr) |
| 247 | return [], {} |
| 248 | |
| 249 | entries = [] |
| 250 | section_sizes = {} |
| 251 | current_section = None # top-level: CODE, DATA |
| 252 | current_subsection = None # e.g. .rodata, .data |
| 253 | text = map_path.read_text() |
| 254 | |
| 255 | # Only match known wasm-ld section names — not arbitrary symbol lines |
| 256 | # like ".Lanon.<hash>" which would falsely trigger a section switch. |
| 257 | section_re = re.compile( |
| 258 | r"^\s+(?:-|[0-9a-fA-F]+)\s+[0-9a-fA-F]+\s+([0-9a-fA-F]+)\s+" |
| 259 | r"(CODE|DATA|CUSTOM\(.*?\)|\.rodata|\.data(?:\.rel\.ro)?|\.bss|" |
| 260 | r"TYPE|IMPORT|FUNCTION|TABLE|MEMORY|GLOBAL|EXPORT|ELEM)\s*$" |
| 261 | ) |
| 262 | obj_re = re.compile( |
| 263 | r"^\s+(?:-|[0-9a-fA-F]+)\s+[0-9a-fA-F]+\s+([0-9a-fA-F]+)\s+(.+:.+)$" |
| 264 | ) |
| 265 | for line in text.splitlines(): |
| 266 | sm = section_re.match(line) |
| 267 | if sm: |
| 268 | size = int(sm.group(1), 16) |
| 269 | name = sm.group(2) |
| 270 | section_sizes[name] = size |
| 271 | if name.startswith("."): |
| 272 | current_subsection = name |
| 273 | else: |
| 274 | current_section = name |
| 275 | current_subsection = None |
| 276 | continue |
| 277 | |
| 278 | om = obj_re.match(line) |
| 279 | if om: |
| 280 | size = int(om.group(1), 16) |
| 281 | detail = om.group(2).strip() |
| 282 | if size == 0: |
| 283 | continue |
| 284 | section = current_subsection or current_section |
| 285 | if section not in ("CODE", ".rodata", ".data", ".data.rel.ro", ".bss"): |
| 286 | continue |
| 287 | crate = extract_crate_name(detail) |
no test coverage detected