Better processing of minidump files

This commit is contained in:
Fedor Indutny 2024-08-07 09:17:28 -07:00 committed by GitHub
parent de33410be1
commit 8719b296cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -10,24 +10,58 @@ import z from 'zod';
import type { LoggerType } from '../ts/types/Logging';
import * as Errors from '../ts/types/errors';
import { isProduction } from '../ts/util/version';
import { isNotNil } from '../ts/util/isNotNil';
import OS from '../ts/util/os/osMain';
const dumpSchema = z
.object({
crashing_thread: z
.object({
// See https://github.com/rust-minidump/rust-minidump/blob/main/minidump-processor/json-schema.md
const dumpString = z.string().or(z.null()).optional();
const dumpNumber = z.number().or(z.null()).optional();
const threadSchema = z.object({
thread_name: dumpString,
frames: z
.object({
registers: z.unknown(),
offset: dumpString,
module: dumpString,
module_offset: dumpString,
})
.passthrough()
.array()
.or(z.null())
.optional(),
});
const dumpSchema = z.object({
crash_info: z
.object({
type: dumpString,
crashing_thread: dumpNumber,
address: dumpString,
})
.passthrough()
.optional()
.or(z.null()),
crashing_thread: threadSchema.or(z.null()).optional(),
threads: threadSchema.array().or(z.null()).optional(),
modules: z
.object({
filename: dumpString,
debug_file: dumpString,
debug_id: dumpString,
base_addr: dumpString,
end_addr: dumpString,
version: dumpString,
})
.array()
.or(z.null())
.optional(),
system_info: z
.object({
cpu_arch: dumpString,
os: dumpString,
os_ver: dumpString,
})
.passthrough();
.or(z.null())
.optional(),
});
async function getPendingDumps(): Promise<ReadonlyArray<string>> {
const crashDumpsPath = await realpath(app.getPath('crashDumps'));
@ -81,12 +115,43 @@ export function setup(
}
const pendingDumps = await getPendingDumps();
if (pendingDumps.length !== 0) {
getLogger().warn(
`crashReports: ${pendingDumps.length} pending dumps found`
const filteredDumps = (
await Promise.all(
pendingDumps.map(async fullPath => {
const content = await readFile(fullPath);
try {
const dump = dumpSchema.parse(
JSON.parse(dumpToJSONString(content))
);
if (dump.crash_info?.type !== 'Simulated Exception') {
return fullPath;
}
} catch (error) {
getLogger().error(
`crashReports: failed to read crash report ${fullPath} due to error`,
Errors.toLogFormat(error)
);
}
return pendingDumps.length;
try {
await unlink(fullPath);
} catch (error) {
getLogger().error(
`crashReports: failed to unlink crash report ${fullPath}`,
Errors.toLogFormat(error)
);
}
return undefined;
})
)
).filter(isNotNil);
if (filteredDumps.length !== 0) {
getLogger().warn(
`crashReports: ${filteredDumps.length} pending dumps found`
);
}
return filteredDumps.length;
});
ipc.handle('crash-reports:write-to-log', async () => {
@ -109,10 +174,30 @@ export function setup(
const { mtime } = await stat(fullPath);
const dump = dumpSchema.parse(JSON.parse(dumpToJSONString(content)));
for (const frame of dump.crashing_thread?.frames ?? []) {
delete frame.registers;
if (dump.crash_info?.type === 'Simulated Exception') {
return undefined;
}
dump.modules = dump.modules?.filter(({ filename }) => {
if (filename == null) {
return false;
}
// Node.js Addons are useful
if (/\.node$/.test(filename)) {
return true;
}
// So is Electron
if (/electron|signal/i.test(filename)) {
return true;
}
// Rest are not relevant
return false;
});
logger.warn(
`crashReports: dump=${basename(fullPath)} ` +
`mtime=${JSON.stringify(mtime)}`,