also looking at this
perf(android): ship libcraft.so without its DWARF, and keep the DWARF for symbols
#217
6 files
+235
-14
| @@ -441,6 +441,65 @@ export function runtimePermissionGranted(dumpsysPackage: string, permission: str | ||
| 441 | 441 | return match ? match[1] === 'true' : undefined |
| 442 | 442 | } |
| 443 | 443 | |
| 444 | /** | |
| 445 | * The section names in an ELF file, or null when `bytes` is not a 64-bit | |
| 446 | * little-endian ELF. | |
| 447 | * | |
| 448 | * Read directly rather than through `readelf`, which a macOS host does not | |
| 449 | * have and a Linux runner has only by accident. Every Android ABI the harness | |
| 450 | * ships (arm64-v8a, x86_64) is ELF64 little-endian, so that is all this reads. | |
| 451 | */ | |
| 452 | export function elfSectionNames(bytes: Uint8Array): string[] | null { | |
| 453 | const ELF_MAGIC = [0x7F, 0x45, 0x4C, 0x46] | |
| 454 | if (bytes.length < 64 || ELF_MAGIC.some((byte, index) => bytes[index] !== byte)) return null | |
| 455 | if (bytes[4] !== 2 || bytes[5] !== 1) return null | |
| 456 | ||
| 457 | const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) | |
| 458 | const sectionTable = Number(view.getBigUint64(0x28, true)) | |
| 459 | const entrySize = view.getUint16(0x3A, true) | |
| 460 | const count = view.getUint16(0x3C, true) | |
| 461 | const namesIndex = view.getUint16(0x3E, true) | |
| 462 | if (entrySize < 0x28 || sectionTable + entrySize * count > bytes.length || namesIndex >= count) return null | |
| 463 | ||
| 464 | const header = (index: number) => sectionTable + entrySize * index | |
| 465 | const namesOffset = Number(view.getBigUint64(header(namesIndex) + 0x18, true)) | |
| 466 | const namesSize = Number(view.getBigUint64(header(namesIndex) + 0x20, true)) | |
| 467 | if (namesOffset + namesSize > bytes.length) return null | |
| 468 | ||
| 469 | const names: string[] = [] | |
| 470 | for (let index = 0; index < count; index++) { | |
| 471 | const start = namesOffset + view.getUint32(header(index), true) | |
| 472 | let end = start | |
| 473 | while (end < namesOffset + namesSize && bytes[end] !== 0) end++ | |
| 474 | names.push(new TextDecoder().decode(bytes.subarray(start, end))) | |
| 475 | } | |
| 476 | return names | |
| 477 | } | |
| 478 | ||
| 479 | /** | |
| 480 | * Why a shipped `libcraft.so` and its symbols file are not what #204 settled | |
| 481 | * on, one line per reason. | |
| 482 | * | |
| 483 | * The library must carry no DWARF: every generated app's APK includes it, and | |
| 484 | * with DWARF it was about 5.5 MB per ABI. It must carry `.gnu_debuglink`, so a | |
| 485 | * crash can be matched to the symbols file. And the symbols file must actually | |
| 486 | * hold the DWARF, or the strip threw it away rather than moving it. | |
| 487 | */ | |
| 488 | export function strippedLibraryProblems(library: string[] | null, symbols: string[] | null): string[] { | |
| 489 | const problems: string[] = [] | |
| 490 | if (!library) return ['libcraft.so is not an ELF64 little-endian file'] | |
| 491 | const dwarf = library.filter(name => name.startsWith('.debug_')) | |
| 492 | if (dwarf.length) | |
| 493 | problems.push(`libcraft.so still carries ${dwarf.join(', ')}; release builds ship without DWARF (#204)`) | |
| 494 | if (!library.includes('.gnu_debuglink')) | |
| 495 | problems.push('libcraft.so has no .gnu_debuglink, so nothing ties it to its symbols file') | |
| 496 | if (!symbols) | |
| 497 | problems.push('android-symbols has no libcraft.so.debug beside the stripped library') | |
| 498 | else if (!symbols.includes('.debug_info')) | |
| 499 | problems.push('libcraft.so.debug holds no .debug_info; the strip discarded the DWARF instead of moving it') | |
| 500 | return problems | |
| 501 | } | |
| 502 | ||
| 444 | 503 | /** |
| 445 | 504 | * The actions the Zig dispatcher saw, by name. |
| 446 | 505 | * |