@@ -1165,13 +1165,10 @@ export function watchForCrashes(crashDirectory: string): void {
11651165let previousCrashData : string ;
11661166let previousCrashCount : number = 0 ;
11671167
1168- function logCrashTelemetry ( data : string , type : string , offsetData ?: string , crashLog ?: string ) : void {
1168+ function logCrashTelemetry ( data : string , type : string , crashLog ?: string ) : void {
11691169 const crashObject : Record < string , string > = { } ;
11701170 const crashCountObject : Record < string , number > = { } ;
11711171 crashObject . CrashingThreadCallStack = data ;
1172- if ( offsetData !== undefined ) {
1173- crashObject . CrashingThreadCallStackOffsets = offsetData ;
1174- }
11751172 if ( crashLog !== undefined ) {
11761173 crashObject . CrashLog = crashLog ;
11771174 }
@@ -1185,8 +1182,8 @@ function logMacCrashTelemetry(data: string): void {
11851182 logCrashTelemetry ( data , "MacCrash" ) ;
11861183}
11871184
1188- function logCppCrashTelemetry ( data : string , offsetData ?: string , crashLog ?: string ) : void {
1189- logCrashTelemetry ( data , "CppCrash" , offsetData , crashLog ) ;
1185+ function logCppCrashTelemetry ( data : string , crashLog ?: string ) : void {
1186+ logCrashTelemetry ( data , "CppCrash" , crashLog ) ;
11901187}
11911188
11921189function handleMacCrashFileRead ( err : NodeJS . ErrnoException | undefined | null , data : string ) : void {
@@ -1292,6 +1289,39 @@ function containsFilteredTelemetryData(str: string): boolean {
12921289 return regex . test ( str ) ;
12931290}
12941291
1292+ // Non-null fault addresses are randomized by ASLR (and use-after-free/wild pointers vary run to
1293+ // run), so embedding the raw value in CrashingThreadCallStack would fragment crash buckets and
1294+ // make CrashCount meaningless. Preserve near-null addresses (typical null-pointer dereferences,
1295+ // which are stable and useful for bucketing), but replace arbitrary addresses with a stable
1296+ // placeholder so identical crashes still de-duplicate.
1297+ function bucketSignalAddress ( address : string ) : string {
1298+ let value : bigint ;
1299+ try {
1300+ value = BigInt ( address . trim ( ) ) ;
1301+ } catch {
1302+ return address ; // Not a parseable address; leave it untouched.
1303+ }
1304+ // 0x10000 (64 KB) covers null plus small member/array offsets off a null pointer.
1305+ return value < 0x10000n ? address : "<non-null>" ;
1306+ }
1307+
1308+ // An unsymbolized frame is reported as a raw runtime address. Addresses in the fixed-base main
1309+ // executable (non-PIE on Linux) stay constant across runs and are useful for bucketing, but
1310+ // addresses in the ASLR-randomized shared-library/mmap region (Linux 0x7f..., and on macOS the
1311+ // PIE main image and dyld shared cache) shift every launch and would fragment crash buckets. Keep
1312+ // the low, fixed addresses but replace high (relocated) ones with a stable placeholder. 4 GB is a
1313+ // safe cut: a non-PIE executable's own code loads well below it, while the relocated region is far
1314+ // above it.
1315+ function bucketFrameAddress ( address : string ) : string {
1316+ let value : bigint ;
1317+ try {
1318+ value = BigInt ( address . trim ( ) ) ;
1319+ } catch {
1320+ return address ; // Not a parseable address; leave it untouched.
1321+ }
1322+ return value < 0x100000000n ? address : "<relocated>" ;
1323+ }
1324+
12951325async function handleCrashFileRead ( crashDirectory : string , crashFile : string , crashDate : Date , err : NodeJS . ErrnoException | undefined | null , data : string ) : Promise < void > {
12961326 if ( err ) {
12971327 if ( err . code === "ENOENT" ) {
@@ -1301,16 +1331,15 @@ async function handleCrashFileRead(crashDirectory: string, crashFile: string, cr
13011331 }
13021332
13031333 const lines : string [ ] = data . split ( "\n" ) ;
1304- let addressData : string ;
1305- const isCppToolsSrv2 : boolean = crashFile . startsWith ( "cpptools-srv2" ) ;
1306- const isCppToolsSrv : boolean = crashFile . startsWith ( "cpptools-srv" ) ;
1307- const telemetryHeader : string = ( isCppToolsSrv2 ? "cpptools-srv2.txt" : isCppToolsSrv ? "cpptools-srv.txt " : crashFile ) + "\n" ;
1334+ let signalInfo : string ;
1335+ const processName : string = ( crashFile . startsWith ( "cpptools-srv2" ) ? "cpptools-srv2 process" :
1336+ crashFile . startsWith ( "cpptools-srv" ) ? "cpptools-srv process" :
1337+ crashFile . startsWith ( "cpptools-wordexp" ) ? "cpptools-wordexp process " : "cpptools process" ) + "\n" ;
13081338 const filtPath : string | null = which . sync ( "c++filt" , { nothrow : true } ) ;
13091339 const isMac : boolean = process . platform === "darwin" ;
13101340 const startStr : string = isMac ? " _" : "<" ;
13111341 const offsetStr : string = isMac ? " + " : "+" ;
13121342 const endOffsetStr : string = isMac ? " " : " <" ;
1313- const dotStr : string = "…\n" ;
13141343 let signalType : string ;
13151344 let crashLog : string = "" ;
13161345 let crashStackStartLine : number = 0 ;
@@ -1333,40 +1362,38 @@ async function handleCrashFileRead(crashDirectory: string, crashFile: string, cr
13331362 }
13341363 if ( lines [ crashStackStartLine ] . startsWith ( "SIG" ) ) {
13351364 signalType = `${ lines [ crashStackStartLine ] } \n` ;
1336- addressData = `${ lines [ crashStackStartLine + 1 ] } : ${ lines [ crashStackStartLine + 2 ] } \n` ; // signalCode:signalAddr
1365+ signalInfo = `si_code= ${ lines [ crashStackStartLine + 1 ] } , si_addr= ${ bucketSignalAddress ( lines [ crashStackStartLine + 2 ] ) } \n` ;
13371366 crashStackStartLine += 3 ;
13381367 } else {
13391368 // The signal type may fail to be written.
13401369 // Intentionally different from SIGUNKNOWN from cpptools,
13411370 // and not SIG-? to avoid matching the regex in containsFilteredTelemetryData.
13421371 signalType = "SIGMISSING\n" ;
1343- addressData = ".\n " ;
1372+ signalInfo = "" ;
13441373 }
1345- data = telemetryHeader + signalType ;
1374+ data = processName + signalType + signalInfo ;
13461375 let crashCallStack : string = "" ;
13471376 let validFrameFound : boolean = false ;
13481377 for ( let lineNum : number = crashStackStartLine ; lineNum < lines . length - 3 ; ++ lineNum ) { // skip last lines
13491378 const line : string = lines [ lineNum ] ;
13501379 const startPos : number = line . indexOf ( startStr ) ;
13511380 let pendingCallStack : string = "" ;
13521381 if ( startPos === - 1 || line [ startPos + ( isMac ? 1 : 4 ) ] === "+" ) {
1353- pendingCallStack = dotStr ;
13541382 const startAddressPos : number = line . indexOf ( "0x" ) ;
13551383 const endAddressPos : number = line . indexOf ( endOffsetStr , startAddressPos + 2 ) ;
13561384 if ( startAddressPos === - 1 || endAddressPos === - 1 || startAddressPos >= endAddressPos ) {
1357- addressData + = "Unexpected offset\n" ;
1385+ pendingCallStack = "Unexpected offset\n" ;
13581386 } else {
1359- let pendingAddressData : string = line . substring ( startAddressPos , endAddressPos ) + "\n" ;
1387+ let pendingAddressData : string = bucketFrameAddress ( line . substring ( startAddressPos , endAddressPos ) ) + "\n" ;
13601388 if ( containsFilteredTelemetryData ( pendingAddressData ) ) {
13611389 pendingAddressData = "?\n" ;
13621390 }
1363- addressData + = pendingAddressData ;
1391+ pendingCallStack = pendingAddressData ;
13641392 }
13651393 } else {
13661394 const offsetPos : number = line . indexOf ( offsetStr , startPos + startStr . length ) ;
13671395 if ( offsetPos === - 1 ) {
13681396 pendingCallStack = "Missing offsetStr\n" ;
1369- addressData += "\n" ;
13701397 } else {
13711398 const startPos2 : number = startPos + 1 ;
13721399 let funcStr : string = line . substring ( startPos2 , offsetPos ) ;
@@ -1397,27 +1424,13 @@ async function handleCrashFileRead(crashDirectory: string, crashFile: string, cr
13971424 // Compute pendingOffset.
13981425 if ( isMac ) {
13991426 pendingOffset += line . substring ( offsetPos2 ) ;
1400- const startAddressPos : number = line . indexOf ( "0x" ) ;
1401- if ( startAddressPos === - 1 || startAddressPos >= startPos ) {
1402- // unexpected
1403- pendingOffset += "<Missing 0x>" ;
1404- addressData += "\n" ;
1405- } else {
1406- let pendingAddressData : string = line . substring ( startAddressPos , startPos ) + "\n" ;
1407- if ( containsFilteredTelemetryData ( pendingAddressData ) ) {
1408- pendingAddressData = "?\n" ;
1409- }
1410- addressData += pendingAddressData ;
1411- }
14121427 } else {
14131428 const endPos : number = line . indexOf ( ">" , offsetPos2 ) ;
14141429 if ( endPos === - 1 ) {
14151430 pendingOffset += "<Missing > >" ; // unexpected
14161431 } else {
14171432 pendingOffset += line . substring ( offsetPos2 , endPos ) ;
14181433 }
1419- addressData += "\n" ;
1420- // TODO: It seems like addressData should be obtained on Linux in case the function is filtered.
14211434 }
14221435 pendingOffset += "\n" ;
14231436 pendingCallStack = funcStr + pendingOffset ;
@@ -1447,19 +1460,18 @@ async function handleCrashFileRead(crashDirectory: string, crashFile: string, cr
14471460 }
14481461
14491462 crashCallStack = crashCallStack . trimEnd ( ) ;
1450- addressData = addressData . trimEnd ( ) ;
14511463
14521464 if ( crashCallStack !== prevCppCrashCallStackData ) {
14531465 prevCppCrashCallStackData = crashCallStack ;
14541466
14551467 if ( lines . length >= 6 && util . getLoggingLevel ( ) >= 1 ) {
1456- getCrashCallStacksChannel ( ) . appendLine ( `\n${ isCppToolsSrv2 ? "cpptools-srv2" : isCppToolsSrv ? "cpptools-srv" : "cpptools" } \n ${ crashDate . toLocaleString ( ) } \n${ signalType } ${ crashCallStack } ${ crashLog . length > 0 ? "\n\n" + crashLog : "" } ` ) ;
1468+ getCrashCallStacksChannel ( ) . appendLine ( `\n${ processName } ${ crashDate . toLocaleString ( ) } \n${ signalType } ${ signalInfo } ${ crashCallStack } ${ crashLog . length > 0 ? "\n\n" + crashLog : "" } ` ) ;
14571469 }
14581470 }
14591471
14601472 data += crashCallStack ;
14611473
1462- logCppCrashTelemetry ( data , addressData , crashLog ) ;
1474+ logCppCrashTelemetry ( data , crashLog ) ;
14631475
14641476 await util . deleteFile ( path . resolve ( crashDirectory , crashFile ) ) . catch ( logAndReturn . undefined ) ;
14651477 if ( crashFile === "cpptools.txt" ) {
0 commit comments