Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
benesch committed Mar 27, 2018
0 parents commit 9f05ade
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 0 deletions.
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# cgosymbolizer

[![GoDoc](https://godoc.org/github.com/benesch/cgosymbolizer?status.svg)](https://godoc.org/github.com/benesch/cgosymbolizer)

cgosymbolizer teaches the Go runtime to include cgo frames in backtraces.

## Usage

Just import the package for its side effects:

```go
import _ github.com/benesch/cgosymbolizer
```

## Example output

When you get a backtrace from a Go function that's called from a C function,
you'll see the C frames in the backtrace:

```
./example
goroutine 1 [running, locked to thread]:
runtime/debug.Stack(0x405ac8d, 0x8, 0xc000000180)
/Users/benesch/Sites/go/src/runtime/debug/stack.go:24 +0xa7
runtime/debug.PrintStack()
/Users/benesch/Sites/go/src/runtime/debug/stack.go:16 +0x22
main.goPrintStack()
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/example.go:32 +0x20
main._cgoexpwrap_0981d921ddb7_goPrintStack()
_cgo_gotypes.go:57 +0x20
goPrintStack
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/./example:0 pc=0x407f4e5
c3
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/./example:0 pc=0x407f519
c2
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/./example:0 pc=0x407f529
c1
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/./example:0 pc=0x407f539
main._Cfunc_c1()
_cgo_gotypes.go:41 +0x41
main.go2()
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/example.go:27 +0x20
main.go1()
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/example.go:23 +0x20
main.main()
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/example.go:19 +0x20
```

Without cgosymbolizer, c1, c2, and c3 would be missing.

You'll also see C frames in backtraces generated due to fatal signals. Here's an
example:

```
goroutine 1 [syscall]:
x_cgo_callers
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/./example:0 pc=0x407fb83
abort
/usr/lib/system/libsystem_c.dylib:0 pc=0x7fffb9b2d420
c3
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/./example:0 pc=0x407f51e
c2
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/./example:0 pc=0x407f529
c1
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/./example:0 pc=0x407f539
runtime.cgocall(0x407f500, 0xc000067f58, 0xc00001e118)
/Users/benesch/Sites/go/src/runtime/cgocall.go:128 +0x65 fp=0xc000067f28 sp=0xc000067ef0 pc=0x40043f5
main._Cfunc_c1()
_cgo_gotypes.go:41 +0x41 fp=0xc000067f58 sp=0xc000067f28 pc=0x407f2e1
main.go2()
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/example.go:27 +0x20 fp=0xc000067f68 sp=0xc000067f58 pc=0x407f410
main.go1()
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/example.go:23 +0x20 fp=0xc000067f78 sp=0xc000067f68 pc=0x407f3e0
main.main()
/Users/benesch/go/src/github.com/benesch/cgosymbolizer/example/example.go:19 +0x20 fp=0xc000067f88 sp=0xc000067f78 pc=0x407f3b0
runtime.main()
/Users/benesch/Sites/go/src/runtime/proc.go:198 +0x207 fp=0xc000067fe0 sp=0xc000067f88 pc=0x402a417
runtime.goexit()
/Users/benesch/Sites/go/src/runtime/asm_amd64.s:1385 +0x1 fp=0xc000067fe8 sp=0xc000067fe0 pc=0x40501d1
```

# Platform support

Only Linux and macOS are supported.

Collecting C stack traces when C code receives a signal—the second example
above—requires additional support from the Go runtime. Go 1.10 only provides
this support for linux/amd64 and linux/ppc64le. Go 1.11 will add support for
freebsd/amd64, and possibly darwin/amd64. For details, follow [golang/go#24518].

[golang/go#24518]: https://github.com/golang/go/issues/24518
87 changes: 87 additions & 0 deletions cgosymbolizer_darwin.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include <dlfcn.h>
#include <libunwind.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "cgosymbolizer_darwin.h"

struct cgo_context {
unw_context_t unw_ctx;
unw_cursor_t unw_cursor;
};

void cgo_traceback(void* p) {
struct {
uintptr_t ctx;
uintptr_t sig_ctx;
uintptr_t* buf;
uintptr_t max;
}* arg = p;

struct cgo_context ctx;
if (arg->ctx)
ctx = *(struct cgo_context*) arg->ctx;
else {
if (unw_getcontext(&ctx.unw_ctx) != 0)
return;
if (unw_init_local(&ctx.unw_cursor, &ctx.unw_ctx) != 0)
return;
}

int i = 0;
while (i < arg->max && unw_step(&ctx.unw_cursor) > 0) {
unw_word_t pc;
unw_get_reg(&ctx.unw_cursor, UNW_REG_IP, &pc);
if (pc == 0)
break;
arg->buf[i++] = pc;
}
if (i < arg->max)
arg->buf[i] = 0;
}

void cgo_context(void* p) {
struct {
uintptr_t ctx;
}* arg = p;

if (arg->ctx != 0) {
free((struct cgo_context*) arg->ctx);
return;
}

struct cgo_context* ctx = malloc(sizeof(cgo_context));
if (ctx == NULL)
return;
if (unw_getcontext(&ctx->unw_ctx) != 0)
return;
if (unw_init_local(&ctx->unw_cursor, &ctx->unw_ctx) != 0)
return;
// Step the saved state past this frame. It will cease to exist momentarily.
if (unw_step(&ctx->unw_cursor) <= 0)
return;
arg->ctx = (uintptr_t) ctx;
}

void cgo_symbolizer(void* p) {
struct {
uintptr_t pc;
const char* file;
uintptr_t lineno;
const char* func;
uintptr_t entry;
uintptr_t more;
uintptr_t data;
}* arg = p;

Dl_info dlinfo;
if (dladdr((void *) arg->pc, &dlinfo) == 0) {
arg->file = "?";
arg->func = "?";
arg->entry = 0;
return;
}
arg->file = dlinfo.dli_fname;
arg->func = dlinfo.dli_sname;
arg->entry = (uintptr_t) dlinfo.dli_saddr;
}
13 changes: 13 additions & 0 deletions cgosymbolizer_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cgosymbolizer

import (
"runtime"
)

// #include "cgosymbolizer_darwin.h"
import "C"

func init() {
const structVersion = 0
runtime.SetCgoTraceback(structVersion, C.cgo_traceback, C.cgo_context, C.cgo_symbolizer)
}
3 changes: 3 additions & 0 deletions cgosymbolizer_darwin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
void cgo_traceback(void* p);
void cgo_context(void* p);
void cgo_symbolizer(void* p);
3 changes: 3 additions & 0 deletions cgosymbolizer_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package cgosymbolizer

import _ "github.com/ianlancetaylor/cgosymbolizer"
21 changes: 21 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Package cgosymbolizer teaches the Go runtime to include cgo frames in
// backtraces.
//
// To use the package, import it for its side effects:
//
// import _ github.com/benesch/cgosymbolizer
//
// cgosymbolizer only supports Linux and macOS. Importing it on another
// operating system will have no effect.
//
// Support is further limited by Go's implementation of runtime.SetCgoTraceback.
// On at least linux/amd64 and linux/ppc64le, Go can collect a C traceback, with
// cgosymbolizer's help, if the C code receives a signal. This permits
// visibility into C code in CPU profiles and fatal signals. Otherwise
// cgosymbolizer can only collect a C stack trace when a C function calls a Go
// function. For details, follow https://github.com/golang/go/issues/24518.
//
// Note that the Linux implementation is provided by
// github.com/ianlancetaylor/cgosymbolizer. The macOS implementation is original
// work.
package cgosymbolizer
16 changes: 16 additions & 0 deletions example/example.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <stdlib.h>

void goPrintStack(void);

void c3(void) {
goPrintStack();
abort();
}

void c2(void) {
c3();
}

void c1(void) {
c2();
}
33 changes: 33 additions & 0 deletions example/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Example demonstrates the use of github.com/benesch/cgosymbolizer.
//
// When invoked it prints two backtraces that include C functions. The first is
// generated by a C function that calls a Go function which dumps the stack. The
// second is generated by raising SIGABRT from C.
package main

import (
"runtime/debug"

_ "github.com/benesch/cgosymbolizer"
)

// #cgo CFLAGS: -g
// void c1(void);
import "C"

func main() {
go1()
}

func go1() {
go2()
}

func go2() {
C.c1()
}

//export goPrintStack
func goPrintStack() {
debug.PrintStack()
}

0 comments on commit 9f05ade

Please sign in to comment.