fix(apple): keep the nested Rosetta registers of translated crash reports

A translated process records Rosetta's registers in a nested threadState.rosetta object. The parser read it as one register, so every valid report from a Rosetta process came back partial with a threadState.rosetta.value mismatch, and the nested registers were dropped. Each nested member is now reported as rosetta.<name> and counts toward the register budget.
This commit is contained in:
Adolanium
2026-10-24 11:35:01 +03:00
parent 3ce311757d
commit 1889ac1532
4 changed files with 68 additions and 4 deletions
+3
View File
@@ -38,6 +38,9 @@ address uses that row's own image base and offset. The recorded `inline` flag
does not supply a missing offset or authorize borrowing another row's address.
When `faultingThread` is missing or does not resolve to a recorded thread
object, row-zero roles are `unknown`; the recorded lookup remains available.
A translated process records Rosetta's registers in a nested
`threadState.rosetta` object; each one is reported as `rosetta.<name>` and
counts toward the register budget.
`process.os_version` keeps the producer's dictionary (`train`, `build`,
`release_type`, `is_embedded`) or the older string form. Image UUIDs are uppercase 8-4-4-4-12; an all-zero UUID
or a missing `arch` is `uuid_unknown`. Image `source` letters keep their
+7
View File
@@ -96,6 +96,13 @@ const threadShape = (
members: {
flavor: SCALAR,
x: { kind: "array", items: register, beforeItem: take.registers },
// Rosetta-translated processes nest their translator registers.
rosetta: {
kind: "object",
members: {},
rest: register,
beforeRest: take.registers,
},
},
rest: register,
beforeRest: take.registers,
+44 -4
View File
@@ -500,6 +500,46 @@ it("reports wrong-type collections as partial instead of empty and complete", ()
expect(empty.coverage.status).toBe("complete");
});
it("keeps the nested Rosetta registers of a translated process", () => {
const parsed = parse(
withBody(`{
"translated": true,
"threads": [
{
"threadState": {
"flavor": "x86_THREAD_STATE",
"rip": { "value": 4294983680 },
"rosetta": { "tmp0": { "value": 7 }, "tmp1": 8 }
}
}
],
"usedImages": []
}`),
);
expect(parsed.threads[0]?.registers).toEqual([
{
name: "rip",
value: { decimal: "4294983680", hex: "0x100004000" },
description: null,
},
{
name: "rosetta.tmp0",
value: { decimal: "7", hex: "0x7" },
description: null,
},
{
name: "rosetta.tmp1",
value: { decimal: "8", hex: "0x8" },
description: null,
},
]);
expect(parsed.coverage).toMatchObject({
status: "complete",
registers: 3,
type_mismatches: [],
});
});
it("records wrong-type rows, scalars and registers with their source path", () => {
const parsed = parse(
withBody(`{
@@ -511,7 +551,7 @@ it("records wrong-type rows, scalars and registers with their source path", () =
7,
{
"frames": [{ "imageIndex": 99999999999, "imageOffset": "8" }, []],
"threadState": { "flavor": 1, "x": [1, "two", { "value": null }, {}], "rosetta": { "tmp0": 1 } }
"threadState": { "flavor": 1, "x": [1, "two", { "value": null }, {}], "rosetta": { "tmp0": "one" } }
}
],
"usedImages": [null, { "base": 1.0 }]
@@ -567,9 +607,9 @@ it("records wrong-type rows, scalars and registers with their source path", () =
observed: "absent",
},
{
path: "body.threads[1].threadState.rosetta.value",
expected: "integer",
observed: "absent",
path: "body.threads[1].threadState.rosetta.tmp0",
expected: "integer or object with integer value",
observed: "string",
},
{
path: "body.threads[1].threadState.flavor",
+14
View File
@@ -175,6 +175,20 @@ const threadStateOf = (
});
for (const [key, entry] of Object.entries(members)) {
if (key === "x" || key === "flavor") continue;
if (key === "rosetta") {
// A translated process records Rosetta's registers as a nested state.
const rosetta = read.object(entry, `${path}.rosetta`);
for (const [name, nested] of Object.entries(rosetta?.members ?? {})) {
const register = registerValue(
`rosetta.${name}`,
nested,
`${path}.rosetta.${name}`,
read,
);
if (register !== null) registers.push(register);
}
continue;
}
const register = registerValue(key, entry, `${path}.${key}`, read);
if (register !== null) registers.push(register);
}