UNPKG

minidump

Version:
202 lines (184 loc) 7.42 kB
// Just enough of the minidump format to extract module names + debug // identifiers so we can download pdbs const headerMagic = Buffer.from('MDMP').readUInt32LE(0); if (!Buffer.prototype.readBigUInt64LE) { Buffer.prototype.readBigUInt64LE = function (offset) { // ESLint doesn't support BigInt yet // eslint-disable-next-line return ( BigInt(this.readUInt32LE(offset)) + (BigInt(this.readUInt32LE(offset + 4)) << BigInt(32)) ); }; } function assertRange(buf, offset, length, label) { if (!Number.isInteger(offset) || offset < 0 || offset + length > buf.length) { throw new Error( `malformed minidump: ${label} at offset ${offset} (len ${length}) is out of bounds`, ); } } // MDRawHeader // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/google_breakpad/common/minidump_format.h#252 function readHeader(buf) { return { signature: buf.readUInt32LE(0), version: buf.readUInt32LE(4), stream_count: buf.readUInt32LE(8), stream_directory_rva: buf.readUInt32LE(12), checksum: buf.readUInt32LE(16), time_date_stamp: buf.readUInt32LE(20), flags: buf.readBigUInt64LE(24), }; } // MDRawDirectory // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/google_breakpad/common/minidump_format.h#305 function readDirectory(buf, rva) { assertRange(buf, rva, 12, 'stream directory entry'); return { type: buf.readUInt32LE(rva), location: readLocationDescriptor(buf, rva + 4), }; } // MDRawModule // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/google_breakpad/common/minidump_format.h#386 function readRawModule(buf, rva) { assertRange(buf, rva, 24 + 13 * 4 + 8 + 8, 'module record'); const module = { base_of_image: buf.readBigUInt64LE(rva), size_of_image: buf.readUInt32LE(rva + 8), checksum: buf.readUInt32LE(rva + 12), time_date_stamp: buf.readUInt32LE(rva + 16), module_name_rva: buf.readUInt32LE(rva + 20), version_info: readVersionInfo(buf, rva + 24), cv_record: readCVRecord(buf, readLocationDescriptor(buf, rva + 24 + 13 * 4)), misc_record: readLocationDescriptor(buf, rva + 24 + 13 * 4 + 8), }; // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/processor/minidump.cc#2255 module.version = [ module.version_info.file_version_hi >> 16, module.version_info.file_version_hi & 0xffff, module.version_info.file_version_lo >> 16, module.version_info.file_version_lo & 0xffff, ].join('.'); module.name = readString(buf, module.module_name_rva); return module; } // MDVSFixedFileInfo // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/google_breakpad/common/minidump_format.h#129 function readVersionInfo(buf, base) { assertRange(buf, base, 40, 'version info'); return { signature: buf.readUInt32LE(base), struct_version: buf.readUInt32LE(base + 4), file_version_hi: buf.readUInt32LE(base + 8), file_version_lo: buf.readUInt32LE(base + 12), product_version_hi: buf.readUInt32LE(base + 16), product_version_lo: buf.readUInt32LE(base + 20), file_flags_mask: buf.readUInt32LE(base + 24), file_flags: buf.readUInt32LE(base + 28), file_os: buf.readUInt32LE(base + 32), file_type: buf.readUInt32LE(base + 24), file_subtype: buf.readUInt32LE(base + 28), file_date_hi: buf.readUInt32LE(base + 32), file_date_lo: buf.readUInt32LE(base + 36), }; } // MDLocationDescriptor // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/google_breakpad/common/minidump_format.h#237 function readLocationDescriptor(buf, base) { assertRange(buf, base, 8, 'location descriptor'); return { data_size: buf.readUInt32LE(base), rva: buf.readUInt32LE(base + 4), }; } // MDGUID // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/google_breakpad/common/minidump_format.h#81 function readGUID(buf) { return { data1: buf.readUInt32LE(0), data2: buf.readUInt16LE(4), data3: buf.readUInt16LE(6), data4: [...buf.subarray(8)], }; } // guid_and_age_to_debug_id // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/processor/minidump.cc#2153 function debugIdFromGuidAndAge(guid, age) { return [ guid.data1.toString(16).padStart(8, '0'), guid.data2.toString(16).padStart(4, '0'), guid.data3.toString(16).padStart(4, '0'), ...guid.data4.map((x) => x.toString(16).padStart(2, '0')), age.toString(16), ] .join('') .toUpperCase(); } // MDCVInfo{PDB70,ELF} // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/google_breakpad/common/minidump_format.h#426 function readCVRecord(buf, { rva, data_size: dataSize }) { if (rva === 0) return; assertRange(buf, rva, 4, 'cv record signature'); const cvSignature = buf.readUInt32LE(rva); if (cvSignature !== 0x53445352 /* SDSR */) { assertRange(buf, rva + 4, 16 + 4, 'cv record guid/age'); const age = buf.readUInt32LE(rva + 4 + 16); const guid = readGUID(buf.subarray(rva + 4, rva + 4 + 16)); return { cv_signature: cvSignature, guid, age, pdb_file_name: buf.subarray(rva + 4 + 16 + 4, rva + dataSize - 1).toString('utf8'), debug_file_id: debugIdFromGuidAndAge(guid, age), }; } else { return { cv_signature: cvSignature }; } } // MDString // https://chromium.googlesource.com/breakpad/breakpad/+/c46151db0ffd1a8dae914e45f1212ef427f61ed3/src/google_breakpad/common/minidump_format.h#357 function readString(buf, rva) { if (rva === 0) return null; assertRange(buf, rva, 4, 'string length'); const bytes = buf.readUInt32LE(rva); return buf.subarray(rva + 4, rva + 4 + bytes).toString('utf16le'); } // MDStreamType // https://chromium.googlesource.com/breakpad/breakpad/+/refs/heads/master/src/google_breakpad/common/minidump_format.h#310 export const streamTypes = { MD_MODULE_LIST_STREAM: 4, }; const streamTypeProcessors = { [streamTypes.MD_MODULE_LIST_STREAM]: (stream, buf) => { assertRange(buf, stream.location.rva, 4, 'module list count'); const numModules = buf.readUInt32LE(stream.location.rva); const modules = []; const size = 8 + 4 + 4 + 4 + 4 + 13 * 4 + 8 + 8 + 8 + 8; const base = stream.location.rva + 4; assertRange(buf, base, numModules * size, 'module list'); for (let i = 0; i < numModules; i++) { modules.push(readRawModule(buf, base + i * size)); } stream.modules = modules; return stream; }, }; export function readMinidump(buf) { if (!Buffer.isBuffer(buf) || buf.length < 32) { throw new Error('minidump too small'); } const header = readHeader(buf); if (header.signature !== headerMagic) { throw new Error('not a minidump file'); } assertRange(buf, header.stream_directory_rva, header.stream_count * 12, 'stream directory'); const streams = []; for (let i = 0; i < header.stream_count; i++) { const stream = readDirectory(buf, header.stream_directory_rva + i * 12); if (stream.type !== 0) { streams.push((streamTypeProcessors[stream.type] || ((s) => s))(stream, buf)); } } return { header, streams }; }