Skip to content
This repository was archived by the owner on Jul 15, 2023. It is now read-only.

Commit c74b73d

Browse files
committed
Add stacktrace dump and better error messages on EXC_BAD_ACCESS panics
Usability workaround for #1903
1 parent 295cb87 commit c74b73d

File tree

1 file changed

+101
-20
lines changed

1 file changed

+101
-20
lines changed

src/debugAdapter/goDebug.ts

Lines changed: 101 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ interface DebugThread {
132132
id: number;
133133
line: number;
134134
pc: number;
135+
goroutineID: number;
135136
function?: DebugFunction;
136137
}
137138

@@ -153,6 +154,7 @@ interface DebugFunction {
153154
goType: number;
154155
args: DebugVariable[];
155156
locals: DebugVariable[];
157+
optimized: boolean;
156158
}
157159

158160
interface ListVarsOut {
@@ -693,7 +695,7 @@ class Delve {
693695
await this.callPromise('Detach', [this.isApiV1 ? true : { Kill: isLocalDebugging }]);
694696
} catch (err) {
695697
log('DetachResponse');
696-
logError(`Failed to detach - ${err.toString() || ''}`);
698+
logError(err, 'Failed to detach');
697699
shouldForceClean = isLocalDebugging;
698700
}
699701
}
@@ -872,7 +874,7 @@ class GoDebugSession extends LoggingDebugSession {
872874
// We use NonBlocking so the call would return immediately.
873875
this.debugState = await this.delve.getDebugState();
874876
} catch (error) {
875-
logError(`Failed to get state ${String(error)}`);
877+
this.logDelveError(error, 'Failed to get state');
876878
}
877879

878880
if (!this.debugState.Running && !this.continueRequestRunning) {
@@ -883,15 +885,13 @@ class GoDebugSession extends LoggingDebugSession {
883885
() => {
884886
return this.setBreakPoints(response, args).then(() => {
885887
return this.continue(true).then(null, (err) => {
886-
logError(
887-
`Failed to continue delve after halting it to set breakpoints: "${err.toString()}"`
888-
);
888+
this.logDelveError(err, 'Failed to continue delve after halting it to set breakpoints');
889889
});
890890
});
891891
},
892892
(err) => {
893893
this.skipStopEventOnce = false;
894-
logError(err);
894+
this.logDelveError(err, 'Failed to halt delve before attempting to set breakpoint');
895895
return this.sendErrorResponse(
896896
response,
897897
2008,
@@ -919,7 +919,7 @@ class GoDebugSession extends LoggingDebugSession {
919919
}
920920

921921
if (err) {
922-
logError('Failed to get threads - ' + err.toString());
922+
this.logDelveError(err, 'Failed to get threads');
923923
return this.sendErrorResponse(response, 2003, 'Unable to display threads: "{e}"', {
924924
e: err.toString()
925925
});
@@ -961,7 +961,7 @@ class GoDebugSession extends LoggingDebugSession {
961961
[stackTraceIn],
962962
(err, out) => {
963963
if (err) {
964-
logError('Failed to produce stack trace!');
964+
this.logDelveError(err, 'Failed to produce stacktrace');
965965
return this.sendErrorResponse(response, 2004, 'Unable to produce stack trace: "{e}"', {
966966
e: err.toString()
967967
});
@@ -1002,7 +1002,7 @@ class GoDebugSession extends LoggingDebugSession {
10021002
this.delve.isApiV1 ? [listLocalVarsIn] : [{ scope: listLocalVarsIn, cfg: this.delve.loadConfig }],
10031003
(err, out) => {
10041004
if (err) {
1005-
logError('Failed to list local variables - ' + err.toString());
1005+
this.logDelveError(err, 'Failed to get list local variables');
10061006
return this.sendErrorResponse(response, 2005, 'Unable to list locals: "{e}"', {
10071007
e: err.toString()
10081008
});
@@ -1018,7 +1018,7 @@ class GoDebugSession extends LoggingDebugSession {
10181018
: [{ scope: listLocalFunctionArgsIn, cfg: this.delve.loadConfig }],
10191019
(listFunctionErr, outArgs) => {
10201020
if (listFunctionErr) {
1021-
logError('Failed to list function args - ' + listFunctionErr.toString());
1021+
this.logDelveError(listFunctionErr, 'Failed to list function args');
10221022
return this.sendErrorResponse(response, 2006, 'Unable to list args: "{e}"', {
10231023
e: listFunctionErr.toString()
10241024
});
@@ -1098,7 +1098,7 @@ class GoDebugSession extends LoggingDebugSession {
10981098
this.delve.isApiV1 ? [filter] : [{ filter, cfg: this.delve.loadConfig }],
10991099
(listPkgVarsErr, listPkgVarsOut) => {
11001100
if (listPkgVarsErr) {
1101-
logError('Failed to list global vars - ' + listPkgVarsErr.toString());
1101+
this.logDelveError(listPkgVarsErr, 'Failed to list global vars');
11021102
return this.sendErrorResponse(
11031103
response,
11041104
2007,
@@ -1170,7 +1170,7 @@ class GoDebugSession extends LoggingDebugSession {
11701170
const variable = this.delve.isApiV1 ? <DebugVariable>result : (<EvalOut>result).Variable;
11711171
v.children = variable.children;
11721172
},
1173-
(err) => logError('Failed to evaluate expression - ' + err.toString())
1173+
(err) => this.logDelveError(err, 'Failed to evaluate expression')
11741174
);
11751175
}
11761176
};
@@ -1249,7 +1249,7 @@ class GoDebugSession extends LoggingDebugSession {
12491249
log('NextRequest');
12501250
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'next' }], (err, out) => {
12511251
if (err) {
1252-
logError('Failed to next - ' + err.toString());
1252+
this.logDelveError(err, 'Failed to next');
12531253
}
12541254
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
12551255
log('next state', state);
@@ -1264,7 +1264,7 @@ class GoDebugSession extends LoggingDebugSession {
12641264
log('StepInRequest');
12651265
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'step' }], (err, out) => {
12661266
if (err) {
1267-
logError('Failed to step - ' + err.toString());
1267+
this.logDelveError(err, 'Failed to step in');
12681268
}
12691269
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
12701270
log('stop state', state);
@@ -1279,7 +1279,7 @@ class GoDebugSession extends LoggingDebugSession {
12791279
log('StepOutRequest');
12801280
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'stepOut' }], (err, out) => {
12811281
if (err) {
1282-
logError('Failed to stepout - ' + err.toString());
1282+
this.logDelveError(err, 'Failed to step out');
12831283
}
12841284
const state = this.delve.isApiV1 ? <DebuggerState>out : (<CommandOut>out).State;
12851285
log('stepout state', state);
@@ -1294,7 +1294,7 @@ class GoDebugSession extends LoggingDebugSession {
12941294
log('PauseRequest');
12951295
this.delve.call<DebuggerState | CommandOut>('Command', [{ name: 'halt' }], (err, out) => {
12961296
if (err) {
1297-
logError('Failed to halt - ' + err.toString());
1297+
this.logDelveError(err, 'Failed to halt');
12981298
return this.sendErrorResponse(response, 2010, 'Unable to halt execution: "{e}"', {
12991299
e: err.toString()
13001300
});
@@ -1343,7 +1343,7 @@ class GoDebugSession extends LoggingDebugSession {
13431343
this.delve.call(this.delve.isApiV1 ? 'SetSymbol' : 'Set', [setSymbolArgs], (err) => {
13441344
if (err) {
13451345
const errMessage = `Failed to set variable: ${err.toString()}`;
1346-
logError(errMessage);
1346+
this.logDelveError(err, 'Failed to set variable');
13471347
return this.sendErrorResponse(response, 2010, errMessage);
13481348
}
13491349
response.body = { value: args.value };
@@ -1741,7 +1741,7 @@ class GoDebugSession extends LoggingDebugSession {
17411741
// [TODO] Can we avoid doing this? https://github.com/Microsoft/vscode/issues/40#issuecomment-161999881
17421742
this.delve.call<DebugGoroutine[] | ListGoroutinesOut>('ListGoroutines', [], (err, out) => {
17431743
if (err) {
1744-
logError('Failed to get threads - ' + err.toString());
1744+
this.logDelveError(err, 'Failed to get threads');
17451745
}
17461746
const goroutines = this.delve.isApiV1 ? <DebugGoroutine[]>out : (<ListGoroutinesOut>out).Goroutines;
17471747
this.updateGoroutinesList(goroutines);
@@ -1781,7 +1781,7 @@ class GoDebugSession extends LoggingDebugSession {
17811781
if (!calledWhenSettingBreakpoint) {
17821782
errorCallback = (err: any) => {
17831783
if (err) {
1784-
logError('Failed to continue - ' + err.toString());
1784+
this.logDelveError(err, 'Failed to continue');
17851785
}
17861786
this.handleReenterDebug('breakpoint');
17871787
throw err;
@@ -1838,6 +1838,87 @@ class GoDebugSession extends LoggingDebugSession {
18381838
});
18391839
});
18401840
}
1841+
1842+
private logDelveError(err: any, message: string) {
1843+
if (err === undefined) {
1844+
return;
1845+
}
1846+
1847+
let errorMessage = err.toString();
1848+
// Handle unpropagated fatalpanic errors with a more user friendly message:
1849+
// https://github.com/microsoft/vscode-go/issues/1903#issuecomment-460126884
1850+
// https://github.com/go-delve/delve/issues/852
1851+
// This affects macOS only although we're agnostic of the OS at this stage, only handle the error
1852+
if (errorMessage === 'bad access') {
1853+
errorMessage = 'unpropagated fatalpanic: signal SIGSEGV (EXC_BAD_ACCESS). This fatalpanic is not traceable on macOS, see https://github.com/go-delve/delve/issues/852';
1854+
}
1855+
1856+
logError(message + ' - ' + errorMessage);
1857+
1858+
if (errorMessage === 'bad access') {
1859+
logError('WARNING: this stack might not be from the expected active goroutine');
1860+
}
1861+
1862+
this.dumpStacktrace();
1863+
}
1864+
1865+
private async dumpStacktrace() {
1866+
// Get current goroutine
1867+
// Debugger may be stopped at this point but we still can (and need) to obtain state and stacktrace
1868+
let goroutineId = 0;
1869+
try {
1870+
const stateCallResult = await this.delve.getDebugState();
1871+
// In some fault scenarios there may not be a currentGoroutine available from the debugger state
1872+
// Use the current thread
1873+
if (!stateCallResult.currentGoroutine) {
1874+
goroutineId = stateCallResult.currentThread.goroutineID;
1875+
} else {
1876+
goroutineId = stateCallResult.currentGoroutine.id;
1877+
}
1878+
} catch (error) {
1879+
logError('dumpStacktrace - Failed to get debugger state ' + error);
1880+
}
1881+
1882+
// Get goroutine stacktrace
1883+
const stackTraceIn = { id: goroutineId, depth: this.delve.stackTraceDepth };
1884+
if (!this.delve.isApiV1) {
1885+
Object.assign(stackTraceIn, { full: false, cfg: this.delve.loadConfig });
1886+
}
1887+
this.delve.call<DebugLocation[] | StacktraceOut>(
1888+
this.delve.isApiV1 ?
1889+
'StacktraceGoroutine' : 'Stacktrace', [stackTraceIn], (err, out) => {
1890+
if (err) {
1891+
logError('dumpStacktrace: Failed to produce stack trace' + err);
1892+
return;
1893+
}
1894+
const locations = this.delve.isApiV1 ? <DebugLocation[]>out : (<StacktraceOut>out).Locations;
1895+
log('locations', locations);
1896+
const stackFrames = locations.map((location, frameId) => {
1897+
const uniqueStackFrameId = this.stackFrameHandles.create([goroutineId, frameId]);
1898+
return new StackFrame(
1899+
uniqueStackFrameId,
1900+
location.function ? location.function.name : '<unknown>',
1901+
location.file === '<autogenerated>' ? null : new Source(
1902+
path.basename(location.file),
1903+
this.toLocalPath(location.file)
1904+
),
1905+
location.line,
1906+
0
1907+
);
1908+
});
1909+
1910+
// Dump stacktrace into error logger
1911+
logError(`Last known immediate stacktrace (goroutine id ${goroutineId}):`);
1912+
let output = '';
1913+
stackFrames.forEach((stackFrame) => {
1914+
output = output.concat(`\t${stackFrame.source.path}:${stackFrame.line}\n`);
1915+
if (stackFrame.name) {
1916+
output = output.concat(`\t\t${stackFrame.name}\n`);
1917+
}
1918+
});
1919+
logError(output);
1920+
});
1921+
}
18411922
}
18421923

18431924
function random(low: number, high: number): number {
@@ -1879,4 +1960,4 @@ async function removeFile(filePath: string): Promise<void> {
18791960
}
18801961
}
18811962

1882-
DebugSession.run(GoDebugSession);
1963+
DebugSession.run(GoDebugSession);

0 commit comments

Comments
 (0)