From 8719b296cf3833b9a0f9bda58286bafba985bd14 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Wed, 7 Aug 2024 09:17:28 -0700 Subject: [PATCH] Better processing of minidump files --- app/crashReports.ts | 127 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 21 deletions(-) diff --git a/app/crashReports.ts b/app/crashReports.ts index 0a8d211f0ddb..643cf582bab9 100644 --- a/app/crashReports.ts +++ b/app/crashReports.ts @@ -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({ - frames: z - .object({ - registers: z.unknown(), - }) - .passthrough() - .array() - .optional(), - }) - .passthrough() - .optional(), - }) - .passthrough(); +// 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({ + offset: dumpString, + module: dumpString, + module_offset: dumpString, + }) + .array() + .or(z.null()) + .optional(), +}); + +const dumpSchema = z.object({ + crash_info: z + .object({ + type: dumpString, + crashing_thread: dumpNumber, + address: dumpString, + }) + .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, + }) + .or(z.null()) + .optional(), +}); async function getPendingDumps(): Promise> { const crashDumpsPath = await realpath(app.getPath('crashDumps')); @@ -81,12 +115,43 @@ export function setup( } const pendingDumps = await getPendingDumps(); - if (pendingDumps.length !== 0) { + 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) + ); + } + + 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: ${pendingDumps.length} pending dumps found` + `crashReports: ${filteredDumps.length} pending dumps found` ); } - return pendingDumps.length; + 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)}`,