【go/方法记录】cgo静态库编译以及使用dlv定位cgo崩溃问题
目录
- 说在前面
- 文件树
- 静态库编译
- cgo使用
- 崩溃模拟
- 使用dlv定位崩溃
- 参考
说在前面
- 测试环境:WSL2
- go版本:go version go1.23.1 linux/amd64
- gcc版本:gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
- cmake版本:3.22.1
文件树
├── buffer # go package
│ ├── buffer.go
│ ├── buffer_go.h
│ └── libbuffer.a
├── c # c/c++源代码
│ ├── CMakeLists.txt # cmake
│ ├── buffer # c/c++源代码
│ │ ├── buffer.h
│ │ ├── buffer_go.cpp
│ │ └── buffer_go.h
│ ├── lib # 静态库目录
│ │ └── libbuffer.a
│ └── main.cpp
└── main.go
静态库编译
- 这部分和
go
无关,按照正常的静态库编译走就行,这里我使用cmake
进行编译 buffer.h
#ifndef __BUFFER_H__ #define __BUFFER_H__ #include <string> struct Buffer { std::string* s_; Buffer(int size) { this->s_ = new std::string(size, char('\0')); } ~Buffer() { delete this->s_; } int Size() const { return this->s_->size(); } char* Data() { return (char*)this->s_->data(); } }; #endif
- 然后我们需要对其进行封装,这部分可以参考这里
buffer_go.h
#define DLLIMPORT #ifdef __cplusplus extern "C" { #endif typedef struct Buffer_T Buffer_T; Buffer_T* NewBuffer(int size); void DeleteBuffer(Buffer_T* p); int BufferSize(Buffer_T* p); #ifdef __cplusplus } #endif
buffer_go.cpp
#include "buffer.h" #include "buffer_go.h" #ifdef __cplusplus extern "C" { #endif struct Buffer_T: Buffer { Buffer_T(int size): Buffer(size) {} ~Buffer_T() {} }; DLLIMPORT Buffer_T* NewBuffer(int size) { auto p = new Buffer_T(size); return p; } DLLIMPORT void DeleteBuffer(Buffer_T* p) { delete p; } DLLIMPORT int BufferSize(Buffer_T* p) { return p->Size(); } #ifdef __cplusplus } #endif
- 然后就是
cmake
cmake_minimum_required(VERSION 3.2) project(Buffer) set(CMAKE_BUILD_TYPE "RELEASE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") # 设置编译后库文件目录 set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) include_directories(${PROJECT_SOURCE_DIR}/buffer) # 添加编译可执行文件 aux_source_directory(${PROJECT_SOURCE_DIR}/buffer SRC) add_library(buffer STATIC ${SRC}) add_executable(buffer_exe main.cpp ) # 链接主程序 target_link_libraries(buffer_exe PRIVATE buffer )
- 执行编译
动态库将生成在目录cd ./c cmake . make
./c/lib
cgo使用
- 首先将
buffer_go.h
、libbuffer.a
拷贝到./buffer
目录下,并创建buffer.go
文件。注意,以下注释中的#include "buffer_go.h"
和import "C"
不要有空行 buffer.go
package buffer // #cgo CFLAGS: -I. // #cgo CXXFLAGS: -std=c++11 // #cgo LDFLAGS: -L./ -lbuffer -lstdc++ // // #include "buffer_go.h" import "C" type BufferT C.Buffer_T func NewBuffer(size int) *BufferT { p := C.NewBuffer(C.int(size)) return (*BufferT)(p) } func DeleteBuffer(p *BufferT) { C.DeleteBuffer((*C.Buffer_T)(p)) } func BufferSize(p *BufferT) C.int { return C.BufferSize((*C.Buffer_T)(p)) }
- 然后是
main.go
package main import ( "fmt" ) func main() { b := buffer.NewBuffer(2) fmt.Println(buffer.BufferSize(b)) buffer.DeleteBuffer(b) }
- 执行程序输出
~/cgotest$ go run main.go 2
崩溃模拟
-
正常
go
中的崩溃,例如空指针调用等是可以通过recover
处理的,例如func main() { go func() { defer func() { if err := recover(); err != nil { fmt.Println("err: ", string(debug.Stack())) } }() time.Sleep(5 * time.Second) var b uint32 var a uint32 _ = b / a }() var i int for { time.Sleep(time.Second) i++ fmt.Println(i) } }
可以看到进程可以继续运行
~/cgotest$ go run main.go 1 2 3 4 err: goroutine 18 [running]: runtime/debug.Stack() /usr/local/go/src/runtime/debug/stack.go:26 +0x5e main.main.func1.1() /home/xx/cgotest/main.go:14 +0x2a panic({0x49cb60?, 0x54d840?}) /usr/local/go/src/runtime/panic.go:785 +0x132 main.main.func1() /home/xx/cgotest/main.go:23 +0x3f created by main.main in goroutine 1 /home/xx/cgotest/main.go:11 +0x1a 5 6
-
而对于
cgo
中的崩溃,目前go
是没法进行recover
的,会导致进程直接崩溃package main import ( "cgotest/cgotest/buffer" "fmt" "runtime/debug" "time" ) func main() { go func() { defer func() { if err := recover(); err != nil { fmt.Println("err: ", string(debug.Stack())) } }() time.Sleep(5 * time.Second) b := buffer.NewBuffer(2) buffer.DeleteBuffer(b) b = nil fmt.Println(buffer.BufferSize(b)) }() var i int for { time.Sleep(time.Second) i++ fmt.Println(i) } }
~/cgotest$ go run main.go 1 2 3 4 SIGSEGV: segmentation violation PC=0x4930b4 m=5 sigcode=1 addr=0x0 signal arrived during cgo execution goroutine 6 gp=0xc000007dc0 m=5 mp=0xc000100008 [syscall]: runtime.cgocall(0x492550, 0xc000076750) goroutine 6 gp=0xc000007dc0 m=5 mp=0xc000100008 [syscall]: runtime.cgocall(0x492550, 0xc000076750) /usr/local/go/src/runtime/cgocall.go:167 +0x4b fp=0xc000076728 sp=0xc0000766f0 pc=0x462f8b cgotest/cgotest/buffer._Cfunc_BufferSize(0x0) _cgo_gotypes.go:52 +0x47 fp=0xc000076750 sp=0xc000076728 pc=0x475b87 main.main.func1.BufferSize.3(0x0) /home/xx/cgotest/buffer/buffer.go:22 +0x3b fp=0xc000076788 sp=0xc000076750 pc=0x4923fb cgotest/cgotest/buffer.BufferSize(...) /home/xx/cgotest/buffer/buffer.go:22 main.main.func1() /home/xx/cgotest/main.go:29 +0x6c fp=0xc0000767e0 sp=0xc000076788 pc=0x49234c runtime.goexit({}) /usr/local/go/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc0000767e8 sp=0xc0000767e0 pc=0x46e601 created by main.main in goroutine 1 /home/xx/cgotest/main.go:12 +0x1a
-
可以看到进程退出并输出了堆栈信息
使用dlv定位崩溃
- 虽然崩溃时的堆栈信息有标准输出,但是有时标准输出中的堆栈信息可能会被覆盖掉,少了最前面的信息,这个时候可以开启coredump并使用dlv来定位
- 启用
coredump
在进程崩溃后会生成ulimit -c unlimited GOTRACEBACK=crash go run main.go
coredump
文件 - 查看
coredump
文件目录
可以看到文件生成在~/cgotest$ cat /proc/sys/kernel/core_pattern /mnt/wslg/dumps/core.%e
/mnt/wslg/dumps/
~/cgotest$ ll /mnt/wslg/dumps/ total 6684 -rw------- 1 le le 83562496 Sep 21 16:04 core.main
- 安装
dlv
可以在gopath文件夹下找到go install github.com/go-delve/delve/cmd/dlv@latest
~/cgotest$ /home/xx/go/bin/ dlv gopls staticcheck
- 使用
结果如下# dlv core 二进制程序 coredump文件 ~/go/bin/dlv core cgotest /mnt/wslg/dumps/core.main
~/cgotest$ ~/go/bin/dlv core cgotest /mnt/wslg/dumps/core.main Type 'help' for list of commands. (dlv) bt 0 0x000000000046fde1 in runtime.raise at /usr/local/go/src/runtime/sys_linux_amd64.s:154 1 0x000000000044b625 in runtime.dieFromSignal at /usr/local/go/src/runtime/signal_unix.go:942 2 0x000000000044bc86 in runtime.sigfwdgo at /usr/local/go/src/runtime/signal_unix.go:1154 3 0x000000000044a625 in runtime.sigtrampgo at /usr/local/go/src/runtime/signal_unix.go:432 4 0x000000000046fde1 in runtime.raise at /usr/local/go/src/runtime/sys_linux_amd64.s:153 5 0x000000000044b625 in runtime.dieFromSignal at /usr/local/go/src/runtime/signal_unix.go:942 6 0x000000000044b1a6 in runtime.crash at /usr/local/go/src/runtime/signal_unix.go:1031 Sending output to pager... 0 0x000000000046fde1 in runtime.raise at /usr/local/go/src/runtime/sys_linux_amd64.s:154 1 0x000000000044b625 in runtime.dieFromSignal at /usr/local/go/src/runtime/signal_unix.go:942 2 0x000000000044bc86 in runtime.sigfwdgo at /usr/local/go/src/runtime/signal_unix.go:1154 3 0x000000000044a625 in runtime.sigtrampgo at /usr/local/go/src/runtime/signal_unix.go:432 4 0x000000000046fde1 in runtime.raise at /usr/local/go/src/runtime/sys_linux_amd64.s:153 5 0x000000000044b625 in runtime.dieFromSignal at /usr/local/go/src/runtime/signal_unix.go:942 6 0x000000000044b1a6 in runtime.crash at /usr/local/go/src/runtime/signal_unix.go:1031 7 0x000000000044b1a6 in runtime.sighandler at /usr/local/go/src/runtime/signal_unix.go:806 8 0x000000000044a726 in runtime.sigtrampgo at /usr/local/go/src/runtime/signal_unix.go:490 9 0x00000000004930b4 in ??? at ?:-1 10 0x000000000049256b in C._cgo_a4c0d8419d5e_Cfunc_BufferSize at /tmp/go-build/buffer.cgo2.c:55 11 0x000000000046e284 in runtime.asmcgocall at /usr/local/go/src/runtime/asm_amd64.s:923 12 0x0000000000000000 in ??? at ?:-1 13 0x0000000000462fb5 in runtime.cgocall at /usr/local/go/src/runtime/cgocall.go:185 14 0x0000000000475b87 in cgotest/cgotest/buffer._Cfunc_BufferSize at _cgo_gotypes.go:52 15 0x00000000004923fb in main.main.func1.BufferSize.3 at ./buffer/buffer.go:22 16 0x0000000000492345 in cgotest/cgotest/buffer.BufferSize at ./buffer/buffer.go:22 17 0x0000000000492345 in main.main.func1 at ./main.go:29 18 0x000000000046e601 in runtime.goexit at /usr/local/go/src/runtime/asm_amd64.s:1700
参考
- dlv安装