调试工具
在日常开发需要使用gdb调试程序,如打印变量,查看内存以及汇编,生成core文件等,本文分享平时调试中常见的方法。
gdb的使用
gdb-server
x命令 如:gdb x/150x,gdb x/i
查看源码
ctrl-x-a 或者source layout,查看源码
list 显示代码
ulimit -c unlimit,cat/proc/sys/kernel/core_pattern 查看core文件保存位置
echo “/home/coresave/core.%e.%p.%t” > /proc/sys/kernel/core_pattern 设置core文件保存地址
info inferiors是查看当前的子进程
pstree -p
gdb调试程序方法论
首先编译程序,需要在可执行程序文件以及动态库中编入调试信息,设置编译选项,可以通过cmakelist文件设置,也可以直接设置gcc等参数,比如-g,-Wall等,设置程序的优化等级为-O0
Text Only set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g")
add_definitions(-std=c++11 -Wall -g -O0)
也可以编译的时候,cmake指定DEBUG,cmake .. -DCMAKE_BUILD_TYPE=Debug。
Text Only # set(CMAKE_CXX_VISIBILITY_PRESET default)
# set(CMAKE_VISIBILITY_INLINES_HIDDEN 0)
调试程序,远程调试使用gdbserver(如果是板子上cpu架构不同,使用开发机上交叉编译环境的gdb和gdbserver),开发机上运行的程序直接使用gdb
Text Only # 不带参数
gdb main
# 带参数
gdb --args main arg1 arg2
# attach 程序
gdb -p ${port}
断点,断点可以使用文件名加行号,也可以函数,用b断点(breakpoints的缩写),用disable禁用断点,delete删除断点
Text Only (gdb) b hello.cpp:12 # hello.cpp文件的12行
(gdb) b func1 # 在func1开始位置断点
执行程序,包括开始运行、继续到下一个断点、单步运行、进入函数
Text Only (gdb) r # 开始运行
(gdb) c # 继续到下一个断点
(gdb) n # 单步运行
(gdb) n 5 # 单步运行5步
(gdb) step # 进入函数
打印变量,查看内容,info、list、p命令、x命令
Text Only (gdb) p str1.size()
(gdb) p str1
(gdb) x/32bc str1.c_str() # 打印32个byte大小的char字符,从str1.c_sr()指针位置开始
(gdb) x/32bt str1.c_str() # 打印32个byte大小的二进制,从str1.c_sr()指针位置开始
# x命令还可以通过i打印寄存器内容、address打印地址、f打印float等。
Text Only (gdb) bt # 查看栈帧
(gdb) frame 1 # 查看1的函数栈
反汇编 disassemb
设置ulimit -c unlimit 生成core文件,gdb调试core文件
其它调试
还有其他一些常见的调试方法,在日常开发中可以帮助更加快速的发现问题。比如
c++程序使用了动态链接库.so做功能扩展,如何调试动态链接库,断点进入so源文件?
Python调用c语言动态链接库如何调试,这个在做ffi跨语言调用时很常见,比如ctypes load so文件?
python调试除了使用gdb,还可以使用pdb
python -m pdb main.py
在clion中调试环境很强大,很方便调试,但是在vscode中如何配置调试环境呢?
bin 文件查看方法
python3 compare_bin.py 1.bin 2.bin
Text Only import numpy as np
import argparse
parser = argparse.ArgumentParser(description="your script description")
#parser.add_argument('--verbose', '-v', action='store_true', help='verbose mode')
parser.add_argument("file1")
parser.add_argument("file2")
args = parser.parse_args()
print(args)
print(args.file1)
print(args.file2)
arr_file1 = np.fromfile(args.file1, dtype="float16")
arr_file2 = np.fromfile(args.file1, dtype="float16")
print(np.allclose(arr_file1, arr_file2))
Text Only od -t fF -N 32 xxx.bin
-t fF : 按照float32打印
-N 32 : 读取32个字节
-t fD : 按照float64打印
Text Only from PIL import Image
import numpy as np
height = 500
weight = 500
channel = 3
img_numpy = np.zeros((height, weight, channel), dtype=np.uint8)
img = Image.fromarray(img_numpy, "RGB")
# Display the Numpy array as Image
img.show()
# Save the Numpy array as Image
image_filename = "opengenus_image.jpeg"
img.save(image_filename)
设置gdb源码目录
GDB技巧
当再开发机上编译好调试程序后,scp到目标机上,使用gdb调试,但是因为可执行文件更改了目录。导致gdb找不到源代码路径。
可以通过:
Text Only (gdb)pwd # 查看当前路径
(gdb)show directories # 查看源码搜索路径
(gdb)dir /new_path/src # /new_path/src 是目标机上的源码路径,gdb就会去这个目录搜索源码
(gdb)info source # 可以查看当前源码信息,其中也包含编译信息
(gdb)set substitute-path from_path to_path # 可以让你修改源码搜索路径,把源码绝对路径里面的一个path映射到另一个path上去
交叉编译gdb和gdbserver
为了远程调试,开发机(x86)和目标机(arm64),需要编译开发机上运行的gdb,和目标机上运行的gdbserver,并且它们的版本需要一致。
首先为了,编译gdbserver,因为是在目标机上运行的,所以需要有交叉编译环境,比如:/proc/arm-linux/…/bin目录下,存在交叉编译使用的gcc等工具。
https://developer.aliyun.com/article/243857
下载地址:http://ftp.gnu.org/gnu/gdb/
Text Only wget http://ftp.gnu.org/gnu/gdb/gdb-8.1.1.tar.gz
Text Only #!/bin/bash
cd gdb-8.1.1/
# aarch64-mix210-linux 参考交叉编译工具命名
./configure --program-prefix=`aarch64-mix210-linux-` \
--target=aarch64-mix210-linux \
--prefix=`pwd`/out
make -j2
make install
编译gdbserver (使用交叉编译工具中的aarch64-mix210-linux-gcc编译)
Text Only #!/bin/bash
cd gdb-8.1.1/gdb/gdbserver
export CC=/opt/linux/x86-arm/aarch64-mix210-linux/bin/aarch64-mix210-linux-gcc
./configure \
--host=aarch64-mix210-linux \
--target=aarch64-mix210-linux \
--prefix=`pwd`/out
make -j2
make install
如果gdb和gdbserver文件size太大,可以使用strip工具,但注意strip gdbserver时要使用交叉编译工具中的aarch64-mix210-linux-strip
scp或者nfs将gdbserver传送到目标机上
addr2line
http://lazybing.github.io/blog/2016/12/22/addr2line-use/
拒绝超大coredump - 用backtrace和addr2line搞定异常函数栈
https://linux.die.net/man/3/backtrace_symbols
Text Only #include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <signal.h>
#include <execinfo.h>
// 0: GENERATE COREDUMP FILE
// 1: PRINT STACK BY SELF
int g_iTestFlag = 1;
#define ADDR_MAX_NUM 100
void CallbackSignal (int iSignalNo) {
printf ("CALLBACK: SIGNAL:\n", iSignalNo);
void *pBuf[ADDR_MAX_NUM] = {0};
int iAddrNum = backtrace(pBuf, ADDR_MAX_NUM);
printf("BACKTRACE: NUMBER OF ADDRESSES IS:%d\n\n", iAddrNum);
char ** strSymbols = backtrace_symbols(pBuf, iAddrNum);
if (strSymbols == NULL) {
printf("BACKTRACE: CANNOT GET BACKTRACE SYMBOLS\n");
return;
}
int ii = 0;
for (ii = 0; ii < iAddrNum; ii++) {
printf("%03d %s\n", iAddrNum-ii, strSymbols[ii]);
}
printf("\n");
free(strSymbols);
strSymbols = NULL;
exit(1); // QUIT PROCESS. IF NOT, MAYBE ENDLESS LOOP.
}
void FuncBadBoy() {
void* pBadThing = malloc(1024*1024*256);
free (pBadThing);
free (pBadThing);
}
void FuncBadFather() {
FuncBadBoy();
}
int main(int argc, char **argv){
if (g_iTestFlag) {
signal(SIGSEGV, CallbackSignal);
}
FuncBadFather();
return 0;
}
Text Only #include <stdio.h>
int div(int numerator, int denominator)
{
return numerator / denominator;
}
int main(int argc, char **argv)
{
int numerator = 10;
int denominator = 0;
return div(numerator, denominator);
}
使用addr2line 查看一个dmsg打印的地址看不到函数和行号,显示??,那么因为地址被重定位了,比如
dmesg打印:
Text Only [201297.280078] traps: div[41537] trap divide error ip:55e1e0c947f8 sp:7ffc8f750470 error:0 in div[55e1e0c94000+1000]
http://tangxinfa.github.io/article/linux-4e0b8c038bd5526553bb8c038bd54fe1606f76847a0b5e8f5d296e83.html
Text Only addr2line -e ./div -f 55e1e0c947f8
??
??:0
使用7f8就可以查看了:
Text Only addr2line -e ./div -f 7f8
_Z3divii
/home/xxx/div.c:5
这样我们通过在生产环境使用gdb提取崩溃调用栈以及地址映射空间,就可以在调试环境进行源代码级的问题定位,而不需要从生产环境取得 core 文件。
objdump
Text Only commond to output all the functions in a lib
objdump -Tt ./pavaro/baidu/vp-lab/pavaro/dependency/linux-arm/pavaro/lib/libpavaro.so | ag boost | awk '{print $7}' | c++filt
objdump -CTt ./pavaro/baidu/vp-lab/pavaro/dependency/linux-arm/pavaro/lib/libpavaro.so | ag boost | awk '{print $7}'
gprof
性能分析工具,通过gcc编译程序时,加入-pg选项
然后使用gprof工具分析
超级方便的Linux自带性能分析工具!gprof介绍、安装、使用及实践
uftrace
跟踪函数调用图
https://github.com/namhyung/uftrace/blob/master/doc/uftrace-live-demo.gif
Symbols
http://arthurchiao.art/blog/linux-tracing-basis-zh/
Perf火焰图的使用
https://www.cnblogs.com/happyliu/p/6142929.html
Text Only sudo perf record -F 99 -p PID -g -- sleep 60
-F 99: 每秒采样99次
-p PID: 指定进程id
-g: 记录调用栈
sleep 60: 持续60秒, 如果不加则一直采样到手动中断(CRTL+C)为止
嵌入式开发工具
嵌入式开发杂谈_Stoneshen1211的博客-CSDN博客
results.size() 是unsigned类型,这段代码,compare的时候会发生int到unsigned的转换,导致进入循环。
Text Only std::vector<int> results;
for (int index = 0; index < results.size() - 1; index++) {
std::cout <<"enter index is : " << index << "\n";
}
gdb交叉编译环境调试core文件
bt找不到函数和文件名,设置solib-search-path
core-file # 清空core文件
core-file ./corexxxx # 重新设置core文件