OpenAsar/core/src/app/discord_native/renderer/minidump.js

458 lines
19 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.readMinidump = readMinidump;
var _fs = _interopRequireDefault(require("fs"));
var _util = _interopRequireDefault(require("util"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/naming-convention */
const exceptionTypes = {
C0000005: 'EXCEPTION_ACCESS_VIOLATION',
'80000002': 'EXCEPTION_DATATYPE_MISALIGNMENT',
'80000003': 'EXCEPTION_BREAKPOINT',
'80000004': 'EXCEPTION_SINGLE_STEP',
C000008C: 'EXCEPTION_ARRAY_BOUNDS_EXCEEDED',
C000008D: 'EXCEPTION_FLT_DENORMAL_OPERAND',
C000008E: 'EXCEPTION_FLT_DIVIDE_BY_ZERO',
C000008F: 'EXCEPTION_FLT_INEXACT_RESULT',
C0000090: 'EXCEPTION_FLT_INVALID_OPERATION',
C0000091: 'EXCEPTION_FLT_OVERFLOW',
C0000092: 'EXCEPTION_FLT_STACK_CHECK',
C0000093: 'EXCEPTION_FLT_UNDERFLOW',
C0000094: 'EXCEPTION_INT_DIVIDE_BY_ZERO',
C0000095: 'EXCEPTION_INT_OVERFLOW',
C0000096: 'EXCEPTION_PRIV_INSTRUCTION',
C0000006: 'EXCEPTION_IN_PAGE_ERROR',
C000001D: 'EXCEPTION_ILLEGAL_INSTRUCTION',
C0000025: 'EXCEPTION_NONCONTINUABLE_EXCEPTION',
C00000FD: 'EXCEPTION_STACK_OVERFLOW',
C0000026: 'EXCEPTION_INVALID_DISPOSITION',
'80000001': 'EXCEPTION_GUARD_PAGE',
C0000008: 'EXCEPTION_INVALID_HANDLE'
};
class FileReader {
utf16Decoder = new _util.default.TextDecoder('utf-16');
static promiseFs = Object.freeze({
open: _util.default.promisify(_fs.default.open),
read: _util.default.promisify(_fs.default.read),
close: _util.default.promisify(_fs.default.close)
});
constructor(path, bufferSize = 2048) {
this.handle = FileReader.promiseFs.open(path, 'r');
this.buffer = new Uint8Array(bufferSize);
}
async read(u32toReadCount, position = null) {
const byteSize = u32toReadCount * 4;
await this.readCore(byteSize, position);
return new ReadResult(this.buffer.buffer.slice(0, byteSize));
}
async readMinidumpString(rva) {
if (rva === 0) {
return '';
}
// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_string
await this.readCore(4, rva);
const length = this.buffer[0] | this.buffer[1] << 8 | this.buffer[2] << 16 | this.buffer[3] << 24;
await this.readCore(Math.min(length, this.buffer.byteLength), rva + 4);
return this.utf16Decoder.decode(this.buffer.slice(0, length));
}
async readCore(byteLength, position = null) {
if (byteLength > this.buffer.byteLength) {
throw new Error(`Requested nuber of bytes ${byteLength} exceeds buffer size ${this.buffer.byteLength}.`);
}
if (this.handle == null) {
throw new Error('Cannot use FileReader once closed.');
}
const handle = await this.handle;
const readResult = await FileReader.promiseFs.read(handle, this.buffer, 0, byteLength, position);
if (readResult.bytesRead < byteLength) {
throw new Error(`FileReader failed to read enough bytes: 0x${readResult.bytesRead}`);
}
}
async close() {
if (this.handle == null) {
return;
}
const handle = await this.handle;
this.handle = null;
await FileReader.promiseFs.close(handle);
}
}
class ReadResult {
index = 0;
// irl, this should likely take the uint8 and work from there, but this is fine for us.
// Maybe just steal my other impl here https://github.com/jlennox/WebWad/blob/main/wad.ts#L8
// but we likely don't want to load the entire file at once. But who knows, maybe the system
// call reduction is better and/or it irl doesn't matter either way.
constructor(buffer) {
this.u8 = new Uint8Array(buffer);
this.u16 = new Uint16Array(buffer);
this.u32 = new Uint32Array(buffer);
}
seek(index) {
this.index = index;
}
readuint32() {
const val = this.u32[this.index / 4];
this.index += 4;
return val;
}
readuint16() {
const val = this.u16[this.index / 2];
this.index += 2;
return val;
}
readByteArray(count) {
const val = Array.from(this.u8.slice(this.index, this.index + count));
this.index += count;
return val;
}
readuint64() {
const u32Index = this.index / 4;
const val = BigInt(this.u32[u32Index]) | BigInt(this.u32[u32Index + 1]) << BigInt(32);
this.index += 8;
return val;
}
}
function isMinidumpFilename(filename) {
return /\.dmp$/i.test(filename);
}
var MinidumpStreamType; // https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_header
(function (MinidumpStreamType) {
MinidumpStreamType[MinidumpStreamType["UnusedStream"] = 0] = "UnusedStream";
MinidumpStreamType[MinidumpStreamType["ReservedStream0"] = 1] = "ReservedStream0";
MinidumpStreamType[MinidumpStreamType["ReservedStream1"] = 2] = "ReservedStream1";
MinidumpStreamType[MinidumpStreamType["ThreadListStream"] = 3] = "ThreadListStream";
MinidumpStreamType[MinidumpStreamType["ModuleListStream"] = 4] = "ModuleListStream";
MinidumpStreamType[MinidumpStreamType["MemoryListStream"] = 5] = "MemoryListStream";
MinidumpStreamType[MinidumpStreamType["ExceptionStream"] = 6] = "ExceptionStream";
MinidumpStreamType[MinidumpStreamType["SystemInfoStream"] = 7] = "SystemInfoStream";
MinidumpStreamType[MinidumpStreamType["ThreadExListStream"] = 8] = "ThreadExListStream";
MinidumpStreamType[MinidumpStreamType["Memory64ListStream"] = 9] = "Memory64ListStream";
MinidumpStreamType[MinidumpStreamType["CommentStreamA"] = 10] = "CommentStreamA";
MinidumpStreamType[MinidumpStreamType["CommentStreamW"] = 11] = "CommentStreamW";
MinidumpStreamType[MinidumpStreamType["HandleDataStream"] = 12] = "HandleDataStream";
MinidumpStreamType[MinidumpStreamType["FunctionTableStream"] = 13] = "FunctionTableStream";
MinidumpStreamType[MinidumpStreamType["UnloadedModuleListStream"] = 14] = "UnloadedModuleListStream";
MinidumpStreamType[MinidumpStreamType["MiscInfoStream"] = 15] = "MiscInfoStream";
MinidumpStreamType[MinidumpStreamType["MemoryInfoListStream"] = 16] = "MemoryInfoListStream";
MinidumpStreamType[MinidumpStreamType["ThreadInfoListStream"] = 17] = "ThreadInfoListStream";
MinidumpStreamType[MinidumpStreamType["HandleOperationListStream"] = 18] = "HandleOperationListStream";
MinidumpStreamType[MinidumpStreamType["TokenStream"] = 19] = "TokenStream";
MinidumpStreamType[MinidumpStreamType["JavaScriptDataStream"] = 20] = "JavaScriptDataStream";
MinidumpStreamType[MinidumpStreamType["SystemMemoryInfoStream"] = 21] = "SystemMemoryInfoStream";
MinidumpStreamType[MinidumpStreamType["ProcessVmCountersStream"] = 22] = "ProcessVmCountersStream";
MinidumpStreamType[MinidumpStreamType["IptTraceStream"] = 23] = "IptTraceStream";
MinidumpStreamType[MinidumpStreamType["ThreadNamesStream"] = 24] = "ThreadNamesStream";
MinidumpStreamType[MinidumpStreamType["ceStreamNull"] = 32768] = "ceStreamNull";
MinidumpStreamType[MinidumpStreamType["ceStreamSystemInfo"] = 32769] = "ceStreamSystemInfo";
MinidumpStreamType[MinidumpStreamType["ceStreamException"] = 32770] = "ceStreamException";
MinidumpStreamType[MinidumpStreamType["ceStreamModuleList"] = 32771] = "ceStreamModuleList";
MinidumpStreamType[MinidumpStreamType["ceStreamProcessList"] = 32772] = "ceStreamProcessList";
MinidumpStreamType[MinidumpStreamType["ceStreamThreadList"] = 32773] = "ceStreamThreadList";
MinidumpStreamType[MinidumpStreamType["ceStreamThreadContextList"] = 32774] = "ceStreamThreadContextList";
MinidumpStreamType[MinidumpStreamType["ceStreamThreadCallStackList"] = 32775] = "ceStreamThreadCallStackList";
MinidumpStreamType[MinidumpStreamType["ceStreamMemoryVirtualList"] = 32776] = "ceStreamMemoryVirtualList";
MinidumpStreamType[MinidumpStreamType["ceStreamMemoryPhysicalList"] = 32777] = "ceStreamMemoryPhysicalList";
MinidumpStreamType[MinidumpStreamType["ceStreamBucketParameters"] = 32778] = "ceStreamBucketParameters";
MinidumpStreamType[MinidumpStreamType["ceStreamProcessModuleMap"] = 32779] = "ceStreamProcessModuleMap";
MinidumpStreamType[MinidumpStreamType["ceStreamDiagnosisList"] = 32780] = "ceStreamDiagnosisList";
MinidumpStreamType[MinidumpStreamType["LastReservedStream"] = 65535] = "LastReservedStream";
})(MinidumpStreamType || (MinidumpStreamType = {}));
class MINIDUMP_HEADER {
static U32_SIZE = 4;
constructor(reader) {
this.signature = reader.readuint32();
this.version = reader.readuint32();
this.numberOfStreams = reader.readuint32();
this.streamDirectoryOffset = reader.readuint32();
}
static async read(reader, position) {
return new MINIDUMP_HEADER(await reader.read(MINIDUMP_HEADER.U32_SIZE, position));
}
}
// https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_directory
class MINIDUMP_DIRECTORY {
static U32_SIZE = 3;
constructor(reader) {
this.streamType = reader.readuint32();
this.dataSize = reader.readuint32();
this.dataOffset = reader.readuint32();
}
static async read(reader, position) {
return new MINIDUMP_DIRECTORY(await reader.read(MINIDUMP_DIRECTORY.U32_SIZE, position));
}
}
// https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_exception_stream
class MINIDUMP_EXCEPTION_STREAM {
static U32_SIZE = 8;
constructor(reader) {
this.threadId = reader.readuint32();
this.alignment = reader.readuint32();
this.exceptionCode = reader.readuint32();
this.exceptionFlags = reader.readuint32();
this.exceptionRecord = reader.readuint64();
this.exceptionAddress = reader.readuint64();
}
static async read(reader, position) {
return new MINIDUMP_EXCEPTION_STREAM(await reader.read(MINIDUMP_EXCEPTION_STREAM.U32_SIZE, position));
}
getExceptionCodeString() {
const exceptionCode = this.exceptionCode.toString(16).toUpperCase().padStart(8, '0');
const exceptionString = exceptionTypes[exceptionCode] ?? exceptionCode;
return exceptionString;
}
}
// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_location_descriptor
class MINIDUMP_LOCATION_DESCRIPTOR {
constructor(reader) {
this.dataSize = reader.readuint32();
this.rva = reader.readuint32();
}
}
// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_module_list
class MINIDUMP_MODULE_LIST {
static U32_SIZE = 1;
constructor(reader) {
this.numberOfModules = reader.readuint32();
}
static async read(reader, position) {
return new MINIDUMP_MODULE_LIST(await reader.read(MINIDUMP_MODULE_LIST.U32_SIZE, position));
}
}
// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_module
class MINIDUMP_MODULE {
// sizeof(MINIDUMP_MODULE) 108
// sizeof(VS_FIXEDFILEINFO) 52
// sizeof(MINIDUMP_LOCATION_DESCRIPTOR) 8
// sizeof(RVA) 4
static U32_SIZE = 108 / 4;
constructor(reader) {
this.baseOfImage = reader.readuint64();
this.sizeOfImage = reader.readuint32();
this.checkSum = reader.readuint32();
this.timeDateStamp = reader.readuint32();
this.moduleNameRva = reader.readuint32();
this.versionInfo = new VS_FIXEDFILEINFO(reader);
this.cvRecord = new MINIDUMP_LOCATION_DESCRIPTOR(reader);
this.miscRecord = new MINIDUMP_LOCATION_DESCRIPTOR(reader);
this.reserved0 = reader.readuint64();
this.reserved1 = reader.readuint64();
}
static async read(reader, position) {
return new MINIDUMP_MODULE(await reader.read(MINIDUMP_MODULE.U32_SIZE, position));
}
containsAddress(address) {
const endAddress = this.baseOfImage + BigInt(this.sizeOfImage);
return this.baseOfImage <= address && endAddress > address;
}
async getModuleFileName(reader) {
const moduleName = await reader.readMinidumpString(this.moduleNameRva);
let dirPos = moduleName.lastIndexOf('\\');
if (dirPos === -1) {
dirPos = moduleName.lastIndexOf('/');
}
dirPos = dirPos === -1 ? 0 : dirPos + 1;
return moduleName.slice(dirPos);
}
async getCVInfoIdString(reader) {
return (await CV_INFO.read(reader, this.cvRecord.rva)).getIdString();
}
getCodeIdString() {
return (this.timeDateStamp.toString(16).padStart(8, '0') + this.sizeOfImage.toString(16)).toUpperCase();
}
}
// https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
class VS_FIXEDFILEINFO {
constructor(reader) {
this.dwSignature = reader.readuint32();
this.dwStrucVersion = reader.readuint32();
this.dwFileVersionMS = reader.readuint32();
this.dwFileVersionLS = reader.readuint32();
this.dwProductVersionMS = reader.readuint32();
this.dwProductVersionLS = reader.readuint32();
this.dwFileFlagsMask = reader.readuint32();
this.dwFileFlags = reader.readuint32();
this.dwFileOS = reader.readuint32();
this.dwFileType = reader.readuint32();
this.dwFileSubtype = reader.readuint32();
this.dwFileDateMS = reader.readuint32();
this.dwFileDateLS = reader.readuint32();
}
getVersionString() {
const first = this.dwProductVersionMS >> 16 & 0xffff;
const second = this.dwProductVersionMS & 0xffff;
const third = this.dwProductVersionLS >> 16 & 0xffff;
const fourth = this.dwProductVersionLS & 0xffff;
return `${first}.${second}.${third}.${fourth}`;
}
}
// This isn't spec'ed on the minidump page (from what I could tell):
// https://github.com/rust-minidump/rust-minidump/blob/main/minidump/src/minidump.rs#L267
// This returns the ID string that is used by the Mozilla tools for PDB lookup.
class CV_INFO {
static async read(reader, position) {
if (position === 0) {
return new CV_INFO_UNKNOWN(0);
}
// 6 is the largest.
const readResult = await reader.read(6, position);
const cvSignature = readResult.readuint32();
switch (cvSignature) {
case CV_INFO_PDB20.SIGNATURE:
return new CV_INFO_PDB20(readResult);
case CV_INFO_PDB70.SIGNATURE:
return new CV_INFO_PDB70(readResult);
case CV_INFO_ELF.SIGNATURE:
return new CV_INFO_ELF();
default:
return new CV_INFO_UNKNOWN(cvSignature);
}
}
}
class CV_INFO_PDB20 {
static SIGNATURE = 0x3031424e;
constructor(reader) {
this.cvOffset = reader.readuint32();
this.signature = reader.readuint32();
this.age = reader.readuint32();
}
getIdString() {
// TODO: Probably uncommon at this point.
return 'CV_INFO_PDB20';
}
}
class GUID {
// fixed 8 length.
constructor(reader) {
this.data1 = reader.readuint32();
this.data2 = reader.readuint16();
this.data3 = reader.readuint16();
this.data4 = reader.readByteArray(8);
}
toString() {
if (this.data4.length !== 8) {
return 'Invalid';
}
return this.data1.toString(16).padStart(4, '0') + this.data2.toString(16).padStart(2, '0') + this.data3.toString(16).padStart(2, '0') + this.data4.map(b => b.toString(16).padStart(2, '0')).join('');
}
}
class CV_INFO_PDB70 {
static SIGNATURE = 0x53445352;
constructor(reader) {
this.signature = new GUID(reader);
this.age = reader.readuint32();
}
getIdString() {
// https://randomascii.wordpress.com/2013/03/09/symbols-the-microsoft-way/
return (this.signature.toString() + this.age.toString(16)).toUpperCase();
}
}
class CV_INFO_ELF {
static SIGNATURE = 0x4270454c;
getIdString() {
// TODO: Would be needed for macOS/linux support presumably?
return 'CV_INFO_ELF';
}
}
class CV_INFO_UNKNOWN {
constructor(cvSignature) {
this.cvSignature = cvSignature;
}
getIdString() {
return 'CV_INFO_UNKNOWN:' + this.cvSignature.toString(16).padStart(4, '0');
}
}
async function readMinidump(file) {
if (file == null || !isMinidumpFilename(file)) return null;
let reader = null;
const info = {};
try {
reader = new FileReader(file);
const header = await MINIDUMP_HEADER.read(reader, 0);
if (header.signature !== 0x504d444d) {
console.log(`readMinidump Bad signature: 0x${header.signature.toString(16)}`);
return null;
}
// Arbitrary number. Just a sanity check.
if (header.numberOfStreams > 0x100) {
console.log(`readMinidump Bad numberOfStreams: 0x${header.numberOfStreams.toString(16)}`);
return null;
}
const streamLookup = {};
// First create a lookup because we want to process the entries in a specific order.
for (let i = 0; i < header.numberOfStreams; ++i) {
const streamOffset = header.streamDirectoryOffset + i * 12;
const entry = await MINIDUMP_DIRECTORY.read(reader, streamOffset);
// We only care about a limited amount of stream types, so lets avoid some of the overhead.
switch (entry.streamType) {
case MinidumpStreamType.ExceptionStream:
case MinidumpStreamType.ModuleListStream:
break;
default:
continue;
}
streamLookup[entry.streamType] = entry;
}
const exceptionStreamEntry = streamLookup[MinidumpStreamType.ExceptionStream];
if (exceptionStreamEntry == null) {
console.log(`readMinidump: No ExceptionStream found.`);
return null;
}
const exceptionStream = await MINIDUMP_EXCEPTION_STREAM.read(reader, exceptionStreamEntry.dataOffset);
info.exceptionString = exceptionStream.getExceptionCodeString();
const exceptionAddrString = exceptionStream.exceptionAddress.toString(16);
console.log(`readMinidump exceptionCode: ${info.exceptionString}, exceptionAddress ${exceptionAddrString}`);
const moduleStreamEntry = streamLookup[MinidumpStreamType.ModuleListStream];
// Skip if `exceptionAddress` is 0 since there will be no crashing module.
if (moduleStreamEntry != null && exceptionStream.exceptionAddress !== BigInt(0)) {
// https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_module_list
// Do not read them all at once. This 1) allows us to exit early, 2) would require a variable sized buffer
// instead of our smaller fixed size buffer.
const moduleList = await MINIDUMP_MODULE_LIST.read(reader, moduleStreamEntry.dataOffset);
// Sanity check, this number is arbitrary.
if (moduleList.numberOfModules > 0x200) {
console.log(`readMinidump ModuleListstream Bad numberOfModules: 0x${moduleList.numberOfModules.toString(16)}`);
return info;
}
let moduleEntryOffset = moduleStreamEntry.dataOffset + 4;
for (let i = 0; i < moduleList.numberOfModules; ++i) {
const module = await MINIDUMP_MODULE.read(reader, moduleEntryOffset);
moduleEntryOffset += MINIDUMP_MODULE.U32_SIZE * 4;
if (module.containsAddress(exceptionStream.exceptionAddress)) {
info.exceptionModuleName = await module.getModuleFileName(reader);
info.exceptionModuleVersion = module.versionInfo.getVersionString();
info.relativeCrashAddress = (exceptionStream.exceptionAddress - module.baseOfImage).toString(16);
info.exceptionModuleCodeId = module.getCodeIdString();
break;
}
}
}
} catch (e) {
console.log(`readMinidump exception: ${e} ${e === null || e === void 0 ? void 0 : e.stack}`);
return null;
} finally {
var _reader;
(_reader = reader) === null || _reader === void 0 ? void 0 : _reader.close();
}
console.log(`readMinidump result ${JSON.stringify(info)}`);
return info;
}