From 20e32cb05c71479ba7d7f38fc2c32b52b68042ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Tue, 16 Jun 2026 09:30:09 +0200 Subject: [PATCH] Skip pseudo-libraries like [vdso] during symbolication Pseudo-modules such as [vdso], [vsyscall], [heap], [stack] and [anon:...] are kernel- or allocator-provided mappings that have no symbol files, so requesting symbols for them is always pointless. Worse, it is now actively harmful because the Mozilla symbolication server (Eliot) rejects the entire batched /symbolicate/v5 request with an HTTP 400 ("job N has invalid modules: module index 0 has an invalid debug_filename") when any job's module name contains brackets. Because we batch up to 10 libraries into a single request, that 400 has no `results` field and our response validation throws for the whole chunk, so every other library in it (most importantly libxul) fails to symbolicate too. The net effect is a profile with no symbols at all, even though the symbols are available on the server. This was previously harmless, Eliot (I think) used to tolerate [vdso], returning found_modules: false for that job while symbolicating the rest, which only produced a per-library warning in the console. --- src/profile-logic/symbol-store.ts | 18 ++++++++++++++++++ src/test/unit/symbol-store.test.ts | 17 ++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/profile-logic/symbol-store.ts b/src/profile-logic/symbol-store.ts index df39c76a30..39b77f5561 100644 --- a/src/profile-logic/symbol-store.ts +++ b/src/profile-logic/symbol-store.ts @@ -245,6 +245,24 @@ export class SymbolStore { ); return false; } + // Skip pseudo-modules such as [vdso], [vsyscall], [heap], [stack] and + // [anon:...]. These are kernel- or allocator-provided mappings with no + // symbol files, so requesting them is always pointless. Worse, the + // Mozilla symbolication server rejects the entire batched request with an + // HTTP 400 ("invalid debug_filename") when any job's module name contains + // brackets, which would prevent every other library in the same chunk + // (e.g. libxul) from being symbolicated. + if (debugName.startsWith('[')) { + errorCb( + request, + new SymbolsNotFoundError( + `Cannot symbolicate pseudo-library ${debugName}`, + request.lib, + new Error('Pseudo-library without symbols') + ) + ); + return false; + } return true; }); diff --git a/src/test/unit/symbol-store.test.ts b/src/test/unit/symbol-store.test.ts index 4b88f7aa7d..3a1c6842ff 100644 --- a/src/test/unit/symbol-store.test.ts +++ b/src/test/unit/symbol-store.test.ts @@ -270,6 +270,10 @@ describe('SymbolStore', function () { debugName: '', breakpadId: 'empty-debugname', }, + { + debugName: '[vdso]', + breakpadId: 'dont-care', + }, ]; const symbolTable = new Map([ @@ -291,6 +295,9 @@ describe('SymbolStore', function () { const { debugName, breakpadId } = request.lib; expect(debugName).not.toEqual(''); expect(breakpadId).not.toEqual(''); + // Pseudo-libraries must be filtered out before reaching the server, + // otherwise they cause the whole batched request to be rejected. + expect(debugName.startsWith('[')).toBe(false); await fakeSymbolStore.getSymbols( [request], (lib, results) => { @@ -367,7 +374,7 @@ describe('SymbolStore', function () { // Empty debugNames or breakpadIds should cause errors. And if symbols are // not available from any source, all errors along the way should be included // in the reported error. - expect([...failedLibs]).toBeArrayOfSize(3); + expect([...failedLibs]).toBeArrayOfSize(4); expect(failedLibs.get('empty-breakpadid')).toEqual( expect.objectContaining({ message: expect.stringContaining('Invalid debugName or breakpadId'), @@ -379,6 +386,14 @@ describe('SymbolStore', function () { }) ); + // Pseudo-libraries such as [vdso] should fail without being sent to any + // symbol source, so they can't take down the rest of the batch. + expect(failedLibs.get('[vdso]')).toEqual( + expect.objectContaining({ + message: expect.stringContaining('Cannot symbolicate pseudo-library'), + }) + ); + // For error objects, Jest's deep equality checks only check the message. expect(failedLibs.get('available-from-neither')).toEqual( new SymbolsNotFoundError(