本文演示了python通过ctypes调用C/C++ SDK,当SDK异常时,同时打印C/C++/Python的栈信息.基于traceback、addr2line、PyErr_SetString、backtrace_symbols
一.复现步骤
cat > print_backtrace.h <<-'EOF'
#ifndef __PRINT_BACKTRACE_H__
#define __PRINT_BACKTRACE_H__
#ifdef __cplusplus
extern "C" {
#endif
void print_backtrace();
#ifdef __cplusplus
}
#endif
#endif // __PRINT_BACKTRACE_H__
EOF
cat > print_backtrace.cpp <<-'EOF'
#include "print_backtrace.h"
#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <stdexcept>
#include <Python.h>
#define MAX_FRAMES 128
#define BUFFER_SIZE 512
void print_backtrace()
{
// 1.打印调用栈
void *buff[MAX_FRAMES] = { 0 };
unsigned int depth = backtrace(buff, MAX_FRAMES);
char **strings = backtrace_symbols(buff, depth);
printf("-----------------------print_backtrace(c/c++)--------------------\n");
for (int i = 0; i < MAX_FRAMES; i++)
{
if (i >= depth)
{
break;
}
// 1.1 将地址转换为代码路径和行号
{
char cmdline[BUFFER_SIZE] = {0};
snprintf(cmdline, BUFFER_SIZE, "bash ./parser.sh \"%s\"", strings[i]);
FILE *pFd = popen(cmdline, "r");
if ( pFd == nullptr )
{
printf("popen failed\n");
return ;
}
char buf[BUFFER_SIZE] = {0};
fread(buf, sizeof(buf)/sizeof (buf[0]), 1, pFd);
printf("%s", buf);
pclose(pFd);
}
}
free(strings);
// 2.触发python异常
if (!Py_IsInitialized())
{
Py_Initialize();
}
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyErr_SetString(PyExc_RuntimeError, "print_backtrace trigger_error");
PyGILState_Release(gstate);
}
EOF
cat > api.c <<-'EOF'
#include <stdio.h>
#include <stdlib.h>
#include "print_backtrace.h"
int function_a() {
printf("%s %d\n",__FILE__,__LINE__);
print_backtrace();
return 0;
}
int function_b() {
printf("%s %d\n",__FILE__,__LINE__);
function_a();
return 0;
}
int function_c() {
printf("%s %d\n",__FILE__,__LINE__);
function_b();
return 0;
}
int function_d() {
printf("%s %d\n",__FILE__,__LINE__);
function_c();
return 0;
}
int function_e() {
printf("%s %d\n",__FILE__,__LINE__);
function_d();
return 0;
}
EOF
cat > parser.sh <<-'EOF'
#!/bin/bash
line=`echo $1 | awk '{print $1}' | sed 's/[()]/ /g'`
so_path=`echo $line | awk '{print $1}'`
if [ ! -f $so_path ];then
exit 0
fi
addr_path=`echo $line | awk '{print $2}'`
function_name=`echo $addr_path | awk -F+ '{print $1}'`
offset=`echo $addr_path | awk -F+ '{print $2}'`
if [ $function_name ];then
function_base=`nm $so_path | grep -w "$function_name" | awk '{print $1}'`
abs_addr=`echo "$function_base $offset" | gawk --non-decimal-data '{A="0x"$1;B=$2;printf "0x%x\n",A+B}'`
debug_info=`addr2line -f $abs_addr -e $so_path`
else
debug_info=`addr2line -f $offset -e $so_path`
fi
echo $debug_info
EOF
cat > main.py <<-'EOF'
import ctypes
import traceback
def function_a():
print("function_a")
lib = ctypes.CDLL('./libapi.so')
function_e = lib.function_e
function_e()
def function_b():
print("function_b")
function_a()
def function_c():
print("function_c")
function_b()
def function_d():
print("function_d")
function_c()
try:
function_d()
except:
print("-----------------------print_backtrace(python)--------------------")
traceback.print_exc()
EOF
g++ -fPIC -g -shared -o libprint_backtrace.so print_backtrace.cpp \
-I /home/anaconda3/envs/demo/include/python3.10 \
/home/anaconda3/envs/demo/lib/libpython3.10.so \
-Wl,-rpath=/home/anaconda3/envs/demo/lib/
gcc -fPIC -g -shared -o libapi.so api.c -L . -lprint_backtrace -Wl,-rpath=.
python3 main.py
二.输出
function_d
function_c
function_b
function_a
api.c 30
api.c 24
api.c 18
api.c 12
api.c 6
-----------------------print_backtrace(c/c++)--------------------
print_backtrace /home/print_backtrace.cpp:18
function_a /home/api.c:8
function_b /home/api.c:14
function_c /home/api.c:20
function_d /home/api.c:26
function_e /home/api.c:32
ffi_call_unix64 :?
ffi_call_int ffi64.c:?
ffi_call ??:?
addr2line: DWARF error: can't find .debug_ranges section.
_ctypes_callproc.cold :?
addr2line: DWARF error: can't find .debug_ranges section.
PyCFuncPtr_call.cold :?
nm: /lib/x86_64-linux-gnu/libc.so.6: no symbols
?? ??:0
-----------------------print_backtrace(python)--------------------
RuntimeError: print_backtrace trigger_error
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/main.py", line 39, in <module>
function_d()
File "/home/main.py", line 36, in function_d
function_c()
File "/home/main.py", line 32, in function_c
function_b()
File "/home/main.py", line 28, in function_b
function_a()
File "/home/main.py", line 24, in function_a
function_e()
SystemError: <_FuncPtr object at 0x7f10c6d4cdc0> returned a result with an exception set