Skip to content

Commit ea306ae

Browse files
runtime: support symbolic backtrace of C code in a cgo crash
The new function runtime.SetCgoTraceback may be used to register stack traceback and symbolizer functions, written in C, to do a stack traceback from cgo code. There is a sample implementation of runtime.SetCgoSymbolizer at github.com/ianlancetaylor/cgosymbolizer. Just importing that package is sufficient to get symbolic C backtraces. Currently only supported on linux/amd64. Change-Id: If96ee2eb41c6c7379d407b9561b87557bfe47341 Reviewed-on: https://go-review.googlesource.com/17761 Reviewed-by: Austin Clements <[email protected]>
1 parent b64f549 commit ea306ae

15 files changed

+489
-5
lines changed

src/runtime/cgo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import "unsafe"
1616
//go:linkname _cgo_thread_start _cgo_thread_start
1717
//go:linkname _cgo_sys_thread_create _cgo_sys_thread_create
1818
//go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done
19+
//go:linkname _cgo_callers _cgo_callers
1920

2021
var (
2122
_cgo_init unsafe.Pointer
@@ -24,6 +25,7 @@ var (
2425
_cgo_thread_start unsafe.Pointer
2526
_cgo_sys_thread_create unsafe.Pointer
2627
_cgo_notify_runtime_init_done unsafe.Pointer
28+
_cgo_callers unsafe.Pointer
2729
)
2830

2931
// iscgo is set to true by the runtime/cgo package

src/runtime/cgo/callbacks.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,13 @@ var _cgo_sys_thread_create = &x_cgo_sys_thread_create
9292
var x_cgo_notify_runtime_init_done byte
9393
var _cgo_notify_runtime_init_done = &x_cgo_notify_runtime_init_done
9494

95+
// Calls the traceback function passed to SetCgoTraceback.
96+
97+
//go:cgo_import_static x_cgo_callers
98+
//go:linkname x_cgo_callers x_cgo_callers
99+
//go:linkname _cgo_callers _cgo_callers
100+
var x_cgo_callers byte
101+
var _cgo_callers = &x_cgo_callers
102+
95103
//go:cgo_export_static _cgo_topofstack
96104
//go:cgo_export_dynamic _cgo_topofstack

src/runtime/cgo/gcc_traceback.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build cgo
6+
// +build linux
7+
8+
#include <stdint.h>
9+
10+
struct cgoTracebackArg {
11+
uintptr_t Context;
12+
uintptr_t* Buf;
13+
uintptr_t Max;
14+
};
15+
16+
// Call the user's traceback function and then call sigtramp.
17+
// The runtime signal handler will jump to this code.
18+
// We do it this way so that the user's traceback function will be called
19+
// by a C function with proper unwind info.
20+
void
21+
x_cgo_callers(uintptr_t sig, void *info, void *context, void (*cgoTraceback)(struct cgoTracebackArg*), uintptr_t* cgoCallers, void (*sigtramp)(uintptr_t, void*, void*)) {
22+
struct cgoTracebackArg arg;
23+
24+
arg.Context = 0;
25+
arg.Buf = cgoCallers;
26+
arg.Max = 32; // must match len(runtime.cgoCallers)
27+
(*cgoTraceback)(&arg);
28+
sigtramp(sig, info, context);
29+
}

src/runtime/cgocall.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ import (
8484
"unsafe"
8585
)
8686

87+
// Addresses collected in a cgo backtrace when crashing.
88+
// Length must match arg.Max in x_cgo_callers in runtime/cgo/gcc_traceback.c.
89+
type cgoCallers [32]uintptr
90+
8791
// Call from Go to C.
8892
//go:nosplit
8993
func cgocall(fn, arg unsafe.Pointer) int32 {
@@ -109,6 +113,14 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
109113
mp.ncgo++
110114
defer endcgo(mp)
111115

116+
// Allocate memory to hold a cgo traceback if the cgo call crashes.
117+
if mp.cgoCallers == nil {
118+
mp.cgoCallers = new(cgoCallers)
119+
}
120+
121+
// Reset traceback.
122+
mp.cgoCallers[0] = 0
123+
112124
/*
113125
* Announce we are entering a system call
114126
* so that the scheduler knows to create another

src/runtime/crash_cgo_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,15 @@ func TestCgoCCodeSIGPROF(t *testing.T) {
209209
t.Errorf("expected %q got %v", want, got)
210210
}
211211
}
212+
213+
func TestCgoCrashTraceback(t *testing.T) {
214+
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
215+
t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
216+
}
217+
got := runTestProg(t, "testprogcgo", "CrashTraceback")
218+
for i := 1; i <= 3; i++ {
219+
if !strings.Contains(got, fmt.Sprintf("cgo symbolizer:%d", i)) {
220+
t.Errorf("missing cgo symbolizer:%d", i)
221+
}
222+
}
223+
}

src/runtime/os1_linux.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ func memlimit() uintptr {
305305

306306
func sigreturn()
307307
func sigtramp()
308+
func cgoSigtramp()
308309

309310
//go:nosplit
310311
//go:nowritebarrierrec
@@ -323,7 +324,11 @@ func setsig(i int32, fn uintptr, restart bool) {
323324
sa.sa_restorer = funcPC(sigreturn)
324325
}
325326
if fn == funcPC(sighandler) {
326-
fn = funcPC(sigtramp)
327+
if iscgo {
328+
fn = funcPC(cgoSigtramp)
329+
} else {
330+
fn = funcPC(sigtramp)
331+
}
327332
}
328333
sa.sa_handler = fn
329334
rt_sigaction(uintptr(i), &sa, nil, unsafe.Sizeof(sa.sa_mask))
@@ -354,7 +359,7 @@ func getsig(i int32) uintptr {
354359
if rt_sigaction(uintptr(i), nil, &sa, unsafe.Sizeof(sa.sa_mask)) != 0 {
355360
throw("rt_sigaction read failure")
356361
}
357-
if sa.sa_handler == funcPC(sigtramp) {
362+
if sa.sa_handler == funcPC(sigtramp) || sa.sa_handler == funcPC(cgoSigtramp) {
358363
return funcPC(sighandler)
359364
}
360365
return sa.sa_handler

src/runtime/runtime2.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,10 @@ type m struct {
373373
newSigstack bool // minit on C thread called sigaltstack
374374
printlock int8
375375
fastrand uint32
376-
ncgocall uint64 // number of cgo calls in total
377-
ncgo int32 // number of cgo calls currently in progress
376+
ncgocall uint64 // number of cgo calls in total
377+
ncgo int32 // number of cgo calls currently in progress
378+
cgoCallersUse uint32 // if non-zero, cgoCallers in use temporarily
379+
cgoCallers *cgoCallers // cgo traceback if crashing in cgo call
378380
park note
379381
alllink *m // on allm
380382
schedlink muintptr

src/runtime/sys_linux_386.s

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$12
232232
CALL runtime·sigtrampgo(SB)
233233
RET
234234

235+
TEXT runtime·cgoSigtramp(SB),NOSPLIT,$0
236+
JMP runtime·sigtramp(SB)
237+
235238
TEXT runtime·sigreturn(SB),NOSPLIT,$0
236239
MOVL $173, AX // rt_sigreturn
237240
// Sigreturn expects same SP as signal handler,

src/runtime/sys_linux_amd64.s

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,65 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$24
234234
CALL AX
235235
RET
236236

237+
// Used instead of sigtramp in programs that use cgo.
238+
// Arguments from kernel are in DI, SI, DX.
239+
TEXT runtime·cgoSigtramp(SB),NOSPLIT,$0
240+
// If no traceback function, do usual sigtramp.
241+
MOVQ runtime·cgoTraceback(SB), AX
242+
TESTQ AX, AX
243+
JZ sigtramp
244+
245+
// If no traceback support function, which means that
246+
// runtime/cgo was not linked in, do usual sigtramp.
247+
MOVQ _cgo_callers(SB), AX
248+
TESTQ AX, AX
249+
JZ sigtramp
250+
251+
// Figure out if we are currently in a cgo call.
252+
// If not, just do usual sigtramp.
253+
get_tls(CX)
254+
MOVQ g(CX),AX
255+
TESTQ AX, AX
256+
JZ sigtramp // g == nil
257+
MOVQ g_m(AX), AX
258+
TESTQ AX, AX
259+
JZ sigtramp // g.m == nil
260+
MOVL m_ncgo(AX), CX
261+
TESTL CX, CX
262+
JZ sigtramp // g.m.ncgo == 0
263+
MOVQ m_curg(AX), CX
264+
TESTQ CX, CX
265+
JZ sigtramp // g.m.curg == nil
266+
MOVQ g_syscallsp(CX), CX
267+
TESTQ CX, CX
268+
JZ sigtramp // g.m.curg.syscallsp == 0
269+
MOVQ m_cgoCallers(AX), R8
270+
TESTQ R8, R8
271+
JZ sigtramp // g.m.cgoCallers == nil
272+
MOVL m_cgoCallersUse(AX), CX
273+
TESTL CX, CX
274+
JNZ sigtramp // g.m.cgoCallersUse != 0
275+
276+
// Jump to a function in runtime/cgo.
277+
// That function, written in C, will call the user's traceback
278+
// function with proper unwind info, and will then call back here.
279+
// The first three arguments are already in registers.
280+
// Set the last three arguments now.
281+
MOVQ runtime·cgoTraceback(SB), CX
282+
MOVQ $runtime·sigtramp(SB), R9
283+
MOVQ _cgo_callers(SB), AX
284+
JMP AX
285+
286+
sigtramp:
287+
JMP runtime·sigtramp(SB)
288+
289+
// For cgo unwinding to work, this function must look precisely like
290+
// the one in glibc. The glibc source code is:
291+
// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/x86_64/sigaction.c
292+
// The code that cares about the precise instructions used is:
293+
// https://gcc.gnu.org/viewcvs/gcc/trunk/libgcc/config/i386/linux-unwind.h?revision=219188&view=markup
237294
TEXT runtime·sigreturn(SB),NOSPLIT,$0
238-
MOVL $15, AX // rt_sigreturn
295+
MOVQ $15, AX // rt_sigreturn
239296
SYSCALL
240297
INT $3 // not reached
241298

src/runtime/sys_linux_arm.s

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,10 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$12
361361
BL (R11)
362362
RET
363363

364+
TEXT runtime·cgoSigtramp(SB),NOSPLIT,$0
365+
MOVW $runtime·sigtramp(SB), R11
366+
B (R11)
367+
364368
TEXT runtime·rtsigprocmask(SB),NOSPLIT,$0
365369
MOVW sig+0(FP), R0
366370
MOVW new+4(FP), R1

0 commit comments

Comments
 (0)