diff --git a/sample/profiles/Instruments/8.3.3/integer-uniquer-index.grammar b/sample/profiles/Instruments/8.3.3/integer-uniquer-index.grammar index c942de2..e0c029f 100644 --- a/sample/profiles/Instruments/8.3.3/integer-uniquer-index.grammar +++ b/sample/profiles/Instruments/8.3.3/integer-uniquer-index.grammar @@ -1,12 +1,12 @@ - + Grammar for INDEX files - - - - - + + + + + diff --git a/sample/profiles/Instruments/8.3.3/time-sample-bulkstore.grammar b/sample/profiles/Instruments/8.3.3/time-sample-bulkstore.grammar index b890d35..d9392de 100644 --- a/sample/profiles/Instruments/8.3.3/time-sample-bulkstore.grammar +++ b/sample/profiles/Instruments/8.3.3/time-sample-bulkstore.grammar @@ -1,21 +1,22 @@ - + Grammar for BIN files - - - - - - - - - + + + + + + + + + - - - - + + + + + diff --git a/src/import/instruments.ts b/src/import/instruments.ts index 31f3344..d5b9827 100644 --- a/src/import/instruments.ts +++ b/src/import/instruments.ts @@ -286,13 +286,21 @@ async function getRawSampleList(core: TraceDirectoryTree): Promise { while (true) { // Schema as of Instruments 8.3.3 is a 6 byte timestamp, followed by a bunch // of stuff we don't care about, followed by a 4 byte backtrace ID + const timestampBytes = 48 / 8 const timestamp = bulkstore.readUint48() if (timestamp === 0) break + const threadIDBytes = 32 / 8 const threadID = bulkstore.readUint32() - bulkstore.skip(bytesPerEntry - 6 - 4 - 4) + // Skip the stuff we don't care about. We can do this because we know how + // many bytes there shuold be per entry from the header of the file, and + // we know how many bytes we're reading for each of the fields we do care + // about. + const backtraceIDBytes = 32 / 8 + bulkstore.skip(bytesPerEntry - timestampBytes - threadIDBytes - backtraceIDBytes) const backtraceID = bulkstore.readUint32() + samples.push({timestamp, threadID, backtraceID}) } return samples @@ -516,22 +524,44 @@ export function importThreadFromInstrumentsTrace(args: { const profile = new StackListProfileBuilder(lastOf(samples)!.timestamp) profile.setName(`${fileName} - thread ${threadID}`) + // Each sample's stack is identified by a single number. This number might be + // a an address, or an index into the integer array list. If it's an index + // into the integer arrays list, then the integer array it corresponds to + // might itself contain either addresses or indices into the integer array. function appendRecursive(k: number, stack: FrameInfo[]) { - const frame = addressToFrameMap.get(k) + // First try: let's see if the number is an address and we have + // an associated stack frame for the address. + const address = k + const frame = addressToFrameMap.get(address) if (frame) { stack.push(frame) - } else if (k in arrays) { - for (let addr of arrays[k]) { + return + } + + // Second try: let's see if the number is an index into the integer array + // list. We'll re-interpret the index as a 32 bit integer here by ignoring + // the upper 32 bits of the integer. + // + // TODO(jlfwong): This seems pretty dubious. Is there a more correct wait to + // differentiate between an address and a number? I wonder if there's either + // some metadata somewhere that we need or if there's something in the byte + // sequence that indicates what kind of number it is. + const arrayIndex = k & 0xffffffff + if (arrayIndex in arrays) { + for (let addr of arrays[arrayIndex]) { appendRecursive(addr, stack) } - } else { - const rawAddressFrame: FrameInfo = { - key: k, - name: `0x${zeroPad(k.toString(16), 16)}`, - } - addressToFrameMap.set(k, rawAddressFrame) - stack.push(rawAddressFrame) + return } + + // Fallback case: we'll say we can't find the address, and just + // display the address instead of frame information. + const rawAddressFrame: FrameInfo = { + key: k, + name: `0x${zeroPad(k.toString(16), 16)}`, + } + addressToFrameMap.set(k, rawAddressFrame) + stack.push(rawAddressFrame) } let lastTimestamp: null | number = null