ReviewOS

also looking at this

craft-native/craft

perf(android): ship libcraft.so without its DWARF, and keep the DWARF for symbols

#217
Merged glennmichael123 wants to merge fix/android-strip-libcraft into main
6 files +235 -14
scripts/mobile-e2e/protocol.tsmodified+59-0
Changes to scripts/mobile-e2e/protocol.ts
@@ -441,6 +441,65 @@ export function runtimePermissionGranted(dumpsysPackage: string, permission: str
441441 return match ? match[1] === 'true' : undefined
442442}
443443
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 */
452export 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 */
488export 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
444503/**
445504 * The actions the Zig dispatcher saw, by name.
446505 *