Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/commands/debug-files/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ function appendIl2cppMapping(difs: DebugFileUpload[], file: PreparedDif): void {
try {
result = createIl2cppLineMapping(
new Uint8Array(file.content),
readSourceFile
readSourceFile,
file.debugId
);
} catch (err) {
log.debug(`Could not compute IL2CPP line mapping for ${file.path}`, err);
Expand All @@ -212,7 +213,9 @@ function appendIl2cppMapping(difs: DebugFileUpload[], file: PreparedDif): void {
}
difs.push({
name: `${basename(file.path)}.il2cpp`,
debugId: file.debugId ?? result.debugId,
// Use the mapped object's own debug id so the DIF's id always matches its
// contents (createIl2cppLineMapping maps the object with file.debugId).
debugId: result.debugId,
content: Buffer.from(result.mapping),
});
}
Expand Down
37 changes: 26 additions & 11 deletions src/lib/dif/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ function ensureInitialized(): void {
*/
export function parseDebugFile(data: Uint8Array): DifArchiveInfo {
ensureInitialized();
const archive = new Archive(data);
// `using` frees the WASM handle when this returns; the mapped result holds
// only plain values, so the archive is safe to release here.
using archive = new Archive(data);
return {
fileFormat: archive.fileFormat,
objects: archive.objects().map((o) => ({
Expand Down Expand Up @@ -195,9 +197,9 @@ export function extractEmbeddedPpdb(
data: Uint8Array
): EmbeddedPpdbResult | null {
ensureInitialized();
const archive = new Archive(data);
using archive = new Archive(data);
for (const object of archive.objects()) {
const pe = object.asPe();
using pe = object.asPe();
if (!pe) {
continue;
}
Expand Down Expand Up @@ -279,7 +281,7 @@ export function createSourceBundle(
options?: { collectIl2cppSources?: boolean }
): SourceBundleResult {
ensureInitialized();
const archive = new Archive(data);
using archive = new Archive(data);
const objects = archive.objects();
const objectCount = objects.length;
const object = selectBundledObject(objects);
Expand Down Expand Up @@ -327,25 +329,38 @@ export type Il2cppMappingResult = {
* parsed into a C++→C# mapping serialized as a JSON document — the format Sentry
* consumes for IL2CPP symbolication — which can be uploaded as a separate DIF.
*
* The mapping is computed for the single object chosen by
* The mapping is computed for the object identified by `targetDebugId` when
* given (so the returned debug id always matches the object the mapping
* describes), otherwise for the single object chosen by
* {@link selectBundledObject}, matching {@link createSourceBundle}. Nothing is
* read from disk by this function itself; `readSource` performs all I/O.
*
* @param data - The full contents of the debug information file.
* @param readSource - Supplies C++ source content for a referenced path, or
* `null` to skip. Invoked synchronously (e.g. `readFileSync`).
* @returns The serialized mapping bytes plus the object's debug id, or `null`
* when the archive has no objects or the object references no IL2CPP
* @param targetDebugId - Debug id of the specific object to map (typically the
* filter-matched primary). When omitted or not found, falls back to
* {@link selectBundledObject}.
* @returns The serialized mapping bytes plus the mapped object's debug id, or
* `null` when the archive has no objects or the object references no IL2CPP
* `source_info` markers (an empty mapping).
* @throws If the buffer cannot be parsed, or if `readSource` throws.
*/
export function createIl2cppLineMapping(
data: Uint8Array,
readSource: (path: string) => Uint8Array | null
readSource: (path: string) => Uint8Array | null,
targetDebugId?: string
): Il2cppMappingResult | null {
ensureInitialized();
const archive = new Archive(data);
const object = selectBundledObject(archive.objects());
using archive = new Archive(data);
const objects = archive.objects();
// Map the caller's targeted object so the mapping content always corresponds
// to the debug id the DIF advertises; without this a fat archive plus `--id`
// could stamp one slice's id onto another slice's line mappings.
const object =
(targetDebugId
? objects.find((candidate) => candidate.debugId === targetDebugId)
: undefined) ?? selectBundledObject(objects);
if (!object) {
return null;
}
Expand Down Expand Up @@ -419,7 +434,7 @@ export type DifSourcesInfo = {
*/
export function listSources(data: Uint8Array): DifSourcesInfo {
ensureInitialized();
const archive = new Archive(data);
using archive = new Archive(data);
const objects = archive.objects().map((object) => {
const files: DifSourceFile[] = [];
let enumerationError: string | null = null;
Expand Down
20 changes: 20 additions & 0 deletions test/lib/dif/il2cpp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,26 @@ describe("createIl2cppLineMapping", () => {
expect(doc[CPP_PATH]?.[CS_PATH]).toBeDefined();
});

test("returns the mapped object's own debug id when targeting by id", () => {
// The returned debug id must always describe the object the mapping came
// from, so the uploaded DIF can be stamped with a matching id.
const result = createIl2cppLineMapping(
bytes(BREAKPAD_WITH_CPP),
() => bytes(CPP_WITH_SOURCE_INFO),
KNOWN_DEBUG_ID
);
expect(result?.debugId).toBe(KNOWN_DEBUG_ID);
});

test("falls back to the primary object when targetDebugId is not found", () => {
const result = createIl2cppLineMapping(
bytes(BREAKPAD_WITH_CPP),
() => bytes(CPP_WITH_SOURCE_INFO),
"ffffffff-ffff-ffff-ffff-ffffffffffff"
);
expect(result?.debugId).toBe(KNOWN_DEBUG_ID);
});

test("returns null when no referenced source is available", () => {
expect(
createIl2cppLineMapping(bytes(BREAKPAD_WITH_CPP), () => null)
Expand Down
Loading