Toybrick

【调试技巧】使用GDB跟踪应用程序错误,协助开发人员调试

jefferyzhang

版主

积分
13093
发表于 2023-5-6 11:04:13    查看: 2646|回复: 0 | [复制链接]    打印 | 显示全部楼层
本帖最后由 jefferyzhang 于 2023-5-6 11:20 编辑

前言

我们发现很多同学和入门工程师不会用GDB,Linux开发不会GDB是不行的,本文针对入门学员,快速讲解如何使用GDB在设备端跟踪代码。

GDB是GNU Debug 工具,类似的工具还有LLDB,用法基本相同。
GDB支持本地调试和远程调试,其中Android原生只支持远程调试,也可以自己编译一个本地gdb程序推送到固件上进行本地调试,
本文讲解本地调试方法,远程调试可以上网搜下,情况类似,但远程调试一定要注意本地和设备端gdb版本要一致,否则会出现行号或者堆栈偏移情况。

在Toybrick发布的Debian固件中,我们建议本地调试比较方便。


准备工作

1. 登录板子,或直接在板子的终端界面操作
2. 安装gdb

  1. sudo apt install gdb
复制代码



编译程序

1. 如果是调试别人的程序,请忽略这步
2. 如果是调试自己的程序,为了在gdb中精确显示行号,需要在编译选项加入 -g

  1. # 例如:
  2. gcc a.c -o test -g    # Toybrick开发板本地编译
复制代码


3. 如果没有加-g, 也仅只是不显示行号,但是堆栈信息的符号表还是可以正常显示,但如果程序被strip过,符号表就被删除了,这时候调试需要有原始未被strip过的并且是同一次编译的带符号链接的bin来搜索符号表,根据堆栈指针,找到代码所在行号,例如:(如果不会请网络搜索更详细教程)
  1. addr2line xxxxxx.bin -fe 0x0FFAABBCC    # Toybrick本地调试,交叉编译环境用aarch64-linux-gnu-addr2line
复制代码


PS: Android源码在编译时候out目录都会有两个路径,一个是烧录固件时候用的,为了减小固件大小,里头所有二进制文件全都strip过,不带符号表,但是在out的symbols目录里有一一对应的带符号表的二进制文件,调试时候可以gdb load进这些文件就可以直接定位到行号,或使用交叉编译工具的-addr2line定位行号(同上),或者更傻的方法就是把需要调试的二进制文件直接推送到固件里替代被strip的文件,就可以在本地调试时候省去load这步。


调试程序

1-1. 直接使用gdb加载程序,注意,这里仅是加载程序入口代码,不会运行程序,也不是在这里输入参数。例如:
  1. gdb test
复制代码


1-2. 如果调试的是python3程序,需加载python3,例如:
  1. gdb python3
复制代码


2. 在gdb启动提示符下运行程序并带入参数

如果没有参数,直接执行r
  1. (gdb) r
复制代码


如果带参数,直接将参数带在r后面,如果是python程序,也是作为参数传进去
  1. (gdb) r test.py
复制代码


3. 然后就会开始运行程序,如果程序遇到段错误、中断或者断点时候,就会停住,这时候在gdb提示符下执行bt就可以查看堆栈信息.

  1. (gdb) bt

  2. #0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
  3. #1  0x0000007ff228eaa0 in __GI_abort () at abort.c:79
  4. #2  0x00000055555a447c in V4L2CaptureUnit::fGetstream(std::stop_token const&) ()
  5. #3  0x00000055555a4514 in _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZNSt7jthreadC4IZN15V4L2CaptureUnit11setStreamOnEvEUlRKSt10stop_tokenE_JEvEEOT_DpOT0_EUlS6_SB_SE_E_S6_S9_EEEEE6_M_runEv ()
  6. #4  0x0000007ff257acac in ?? () from /lib/aarch64-linux-gnu/libstdc++.so.6
  7. #5  0x0000007ff26df648 in start_thread (arg=0x7fcf2029c0) at pthread_create.c:477
  8. #6  0x0000007ff233ffdc in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78
复制代码


4. 这时候就可以根据堆栈信息找到对应代码行号进行调试,如果是Toybrick或者Rockchip官方问题,也请提供该堆栈信息,便于工程师快速调试。
5. 如果堆栈信息没有符号表或者行号,请参看本文第二部(编译程序)里头的提示。


小技巧:C++符号表怎么看

认真的同学就要问了,像上面那个堆栈:
_ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZNSt7jthreadC4IZN15V4L2CaptureUnit11setStreamOnEvEUlRKSt10stop_tokenE_JEvEEOT_DpOT0_EUlS6_SB_SE_E_S6_S9_EEEEE6_M_runEv
这么长是什么玩意儿。他其实只是c++类的符号表,可以用c++filt命令转换成人类可阅读格式:

  1. c++filt  _ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZNSt7jthreadC4IZN15V4L2CaptureUnit11setStreamOnEvEUlRKSt10stop_tokenE_JEvEEOT_DpOT0_EUlS6_SB_SE_E_S6_S9_EEEEE6_M_runEv
复制代码


这行就会得到一个c++的lambda表达式可阅读格式:

  1. std::thread::_State_impl<std::thread::_Invoker<std::tuple<std::jthread::jthread<V4L2CaptureUnit::setStreamOn()::{lambda(std::stop_token const&)#1}, , void>(V4L2CaptureUnit::setStreamOn()::{lambda(std::stop_token const&)#1}&&)::{lambda(std::stop_token, auto:1, auto:2&&)#1}, std::stop_token, {lambda(std::stop_token const&)#1}> > >::_M_run()
复制代码



其他调试

当然gdb/lldb的功能不仅只有这些,它还可以下断点,单步跟踪,多线程调试、甚至可以直接修改程序等,可以进一步网上搜索更详细教程,或者直接问gpt寻求答案。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

产品中心 购买渠道 开源社区 Wiki教程 资料下载 关于Toybrick


快速回复 返回顶部 返回列表