Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >
  1. 关注报错信息(搜索报错号
  2. -rdynamic -g 编译,直接gdb运行(-rdynamic可以方便监控可能的段错误)
  • 报错型bug调试建议:bt
    最靠近栈顶你写的函数l那个函数和上一层函数,看p info这个函数的参数对不对(如果不好p info又没有写打印代码的话,先改源码在这个函数里面刚入口的地方加打印,然后r
    不断向栈底看,直到参数没有问题的函数就是它调用接下来的函数参数不对,而它自己传入的参数又没有问题,所以问题必然出在它里面到调用下一次函数之前),首先检查这个函数里面调用参数出错函数的代码有没有问题(检查传入的参数以及它们的来源。比如用来调用的参数不小心写混名字,参数在前面赋值的时候不小心赋错了用混了函数名),如果源码没有问题的话在这个函数处设条件断点(条件为参数取值为此时bt显示的值),然后r,到断点后单步
    • 容器类型的参数要写打印代码(p info x都麻烦),注意把size也打印了(比如unorderedmap,没有分配的指针->size是奇怪值,但是for loop遍历打印内容可能却像空的一样)
  • 逻辑型bug:
    断点从后往前以功能block为单位地设:如果运行到断点处值都正常,那说明问题只出在断点之后,如果不正常则要向前设断点(前面有地方有问题)(同时当前断点之后也不能保证没有问题),问题至少出在接下来的新断点之后
    • 从后往前以功能为单位设断点的好处是,如果问题确实只出在最后一个功能block,那一下就定位问题所在了,前面不用都单步调试检查,
      不过这样要求你能够判断某个断点处各值是否正常,有时候这个是有难度的,而从头开始单步这个就比较简单
    • 小规模代码的话可以把断点设很前,然后单步直到发现值不对劲发现问题

颜色和内容配置

vim ~/.gdbinit

gdbinit/Gdbinit: Gdbinit for OS X, iOS and others - x86, x86_64 and ARM (github.com)

r(un)

在某处段错误后,打断点,然后可以r,这样程序会重新运行且你的断点还在(这样很好重新调试,一点点定位错误位置)

带参数调试

调试带参数的程序:–args

gdb --args ./main -d xxx -q xxx -f xxx

单步

finish就是但单步执行到子函数内时,用step out就可以执行完子函数余下部分,并返回到上一层函数。在其他调试器中相当于step-out,作用是在栈中前进到到下一层,并在调用函数的下一行停止

断点

info b 查看当前设的断点

设断点b

b test.c:9
b printNum
临时断点tb

仅生效一次

tbreak test.c:l0 
条件断点b
b <location> if <expression>

的语法和c一样

假设程序某处发生崩溃,而崩溃的原因怀疑是某个地方出现了非期望的值,那么你就可以在这里断点观察,当出现该非法值时,程序断住。这个时候我们可以借助gdb来设置条件断点,例如:

b test.c:23 if b==0

当在b等于0时,程序将会在第23行断住
它和condition有着类似的作用,假设上面的断点号为1,那么:

condition 1 b==0

会使得b等于0时,产生断点1。而实际上可以很方便地用来改变断点产生的条件,例如,之前设置b==0时产生该断点,那么使用condition可以修改断点产生的条件

正则设置函数断点rb
rbreak file:regex

eg:

rbreak printNum* #所有以printNum开头的函数都设置了断点
rbreak test.c:. #对test.c中的所有函数设置断点
rbreak test.c:^print #对以print开头的函数设置断点
watchpoint
watch a

变量值被改写时断住

rwatch a

当变量值被读时断住

awatch a

被读或者被改写时断住

断点操作

跳过断点ignore

跳过次数

ignore 1 30

1是你要忽略的断点号,可以通过前面的方式查找到,30是需要跳过的次数。

禁用或启动断点disable

有些断点暂时不想使用,但又不想删除,可以暂时禁用或启用。例如:

disable  #禁用所有断点
disable bnum #禁用标号为bnum的断点
enable  #启用所有断点
enable bnum #启用标号为bnum的断点
enable delete bnum  #启动标号为bnum的断点,并且在此之后删除该断点

保存读取断点

保存

(gdb) save breakpoint <文件名>.bp

加载:使用-x参数指定断点文件

gdb <可执行文件名> -x <bp文件名>.bp

断点清除

断点清除主要用到clear和delete命令。常见使用如下:

clear   #删除当前行所有breakpoints
clear function  #删除函数名为function处的断点
clear filename:function #删除文件filename中函数function处的断点
clear lineNum #删除行号为lineNum处的断点
clear f:lename:lineNum #删除文件filename中行号为lineNum处的断点
delete  #删除所有breakpoints,watchpoints和catchpoints
delete bnum #删除断点号为bnum的断点

查看变量值内存值

有些复杂的结构体想看的话写个打印

格式控制

格式控制字符如下:

  • x 按十六进制格式显示变量。
  • d 按十进制格式显示变量。
  • u 按十六进制格式显示无符号整型。
  • o 按八进制格式显示变量。
  • t 按二进制格式显示变量。
  • a 按十六进制格式显示变量。
  • c 按字符格式显示变量。
  • f 按浮点数格式显示变量。

p

指定变量所在文件/函数

p 'testGdb.h'::a #testGdb.h文件中
p 'main'::b #main函数中

打印指针所指多个值@

(gdb) p *d
$2 = 0
(gdb) p *d@10
$3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
(gdb) p *d@a #a是变量,当前值为10
$2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
(gdb) 

$可表示上一个变量

可使用下面方式不断打印链表内容:

(gdb) p *linkNode
(这里显示linkNode节点内容)
(gdb) p *$.next #$指上一个变量,即指刚刚p的那个linkNode
(这里显示linkNode节点下一个节点的内容)

定义一个类似UNIX环境变量,例如:

(gdb) set $index=0
(gdb) p b[$index++]
$11 = 1
(gdb) p b[$index++]
$12 = 2
(gdb) p b[$index++]
$13 = 3

打印格式

(gdb) p/x c
$19 = {0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x73, 0x68, 0x6f, 0x75, 0x77, 0x61, 
  0x6e, 0x67, 0x0}
(gdb)

用这种方式查看浮点数的二进制格式是怎样的是不行的,因为p/t首先会将其转换成整型再转换为二进制表示,因此最终会得到8:

(gdb) p e
$1 = 8.5
(gdb) p/t e
$2 = 1000
(gdb) 

info

info args

打印出当前函数的参数名及其值

info locals

打印出当前函数中所有局部变量及其值

info reg

查看寄存器值

x

x/[n][f][u] addr
  • n 表示要显示的内存单元数,默认值为1
  • f 表示要打印的格式,前面已经提到了格式控制字符
  • u 要打印的单元长度
  • addr 内存地址

单元类型常见有如下:

  • b 字节
  • h 半字,即双字节
  • w 字,即四字节
  • g 八字节

把float变量e按照二进制方式打印,并且打印单位是一字节:

(gdb) x/4tb &e
0x7fffffffdbd4:    00000000    00000000    00001000    01000001
(gdb) 

display

程序断住时,就显示某个变量的值

(gdb) display e
1: e = 8.5

查看哪些变量被设置了display info

(gdb)into display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  b
2:   y  e

delete

delete display num #num为前面变量前的编号,不带num时清除所有。

disable

disable display num  #num为前面变量前的编号,不带num时去使能所有

查看源码

l

(gdb) l test.c:1
(gdb) l test.c:printNum1

指定文件指定行之间:

(gdb) l test.c:1,test.c:3

查找段错误位置

段错误:

硬件设备MMU发现访问了一个非法的虚拟地址,通知操作系统内核给进程发送11号信号,进程收到了一个11号信号,导致进程异常终

方法一: -rdynamic编译

  • gcc -g -rdynamic编译

  • gdb 然后r

[root@localhost TEST]# gcc -g -rdynamic test.c 
[root@localhost TEST]# gdb  ./a.out 
...
(gdb) r
Starting program: /root/桌面/TEST/./a.out 

Program received signal SIGSEGV, Segmentation fault.
0x00000000004007d2 in main () at test.c:7
7		*ptr = 1;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-105.el7.x86_64
(gdb) 

不用一步步调试我们就找到了出错位置在test.c文件的第4行

从这里我们还发现进程是由于收到了SIGSEGV信号而结束的。通过进一步的查阅文档(man 7 signal),我们知道SIGSEGV默认handler的动作是打印”段错误”的出错信息,并产生Core文件

方法二:core文件

  • gcc -g 编译(加上-rdynamic也可以)

  • 设置coredump文件大小为无限制(在运行可执行文件之前)

    ulimit -c unlimit
    
  • ./a.out

  • 出现core.<数字>文件

  • gdb ./a.out core.15180(gdb 目标文件 核心转移文件

[root@localhost TEST]# ./a.out 
段错误(吐核)
[root@localhost TEST]# ls
a.out  core.15180  test  test.c
[root@localhost TEST]# gdb ./a.out core.15180 
...
[New LWP 15180]
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
##0  0x00000000004007d2 in main () at test.c:7
7		*ptr = 1;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-105.el7.x86_64
(gdb)

调试qemu

下文qemu-system-riscv64可以来自:

我们使用的计算机都是基于x86架构的。如何把程序编译到riscv64架构的汇编?这需要我们使用“目标语言为riscv64机器码的编译器”,在我们的电脑上进行交叉编译

使用现有的riscv-gcc编译器:两种方法

  • 源码编译

    https://github.com/riscv/riscv-gcc clone下来,然后在x86架构上编译riscv-gcc编译器为可执行的x86程序,就可以运行它,来把你的程序源代码编译成riscv架构的可执行文件了。这有点像绕口令,但只要有一点编译原理的基础就可以理解。不过,这个riscv-gcc仓库很大,而且自己编译工具链总是一件麻烦的事。

  • 使用别人已经编译好的编译器的可执行文件

    也就是所谓的预编译(prebuilt)工具链,下载下来解压,放在你喜欢的地方,配好路径(把编译器的位置加到系统的PATH环境变量里),就能在终端使用了。我们推荐使用sifive公司提供的预编译工具链,**下载“GNU Embedded Toolchain ”。**

配置好后,在终端输入riscv64-unknown-elf-gcc -v查看安装的gcc版本, 如果输出一大堆东西且最后一行有gcc version 某个数字.某个数字.某个数字,说明gcc配置成功

因为gdb和qemu是两个应用不能直接交流,比较常用的方法是以tcp进行通讯,也就是让qemu在localhost::1234端口上等待

编译:在lab0文件夹下打开终端,运行

$ qemu-system-riscv64 -S -s -hda ./bin/ucore.img 
WARNING: Image format was not specified for './bin/ucore.img' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
VNC server running on 127.0.0.1:5900

开始gdb:然后在该文件夹下重新打开一个终端,运行

$ riscv64-unknown-elf-gdb ./bin/kernel
GNU gdb (SiFive GDB 8.3.0-2020.04.1) 8.3
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=riscv64-unknown-elf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://github.com/sifive/freedom-tools/issues>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./bin/kernel...
(No debugging symbols found in ./bin/kernel)
(gdb) 

接着连接qemu:

(gdb) target remote :1234
Remote debugging using :1234
0x0000000000001000 in ?? ()

连接成功输入si就可以进行运行下一条指令

(gdb) si
0x0000000000001004 in ?? ()

CMake生成的可执行文件gdb调试

(14条消息) CMake生成的可执行文件能够gdb调试_漫游学海之旅-CSDN博客

1 首先在CMakeLists.txt中加入

  SET(CMAKE_BUILD_TYPE "Debug") 


   在下面加入:


  SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")


   SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")


    原因是CMake 中有一个变量 CMAKE_BUILD_TYPE ,可以的取值是 Debug Release RelWithDebInfo >和 MinSizeRel。

当这个变量值为 Debug 的时候,CMake 会使用变量 CMAKE_CXX_FLAGS_DEBUG 和 CMAKE_C_FLAGS_DEBUG 中的字符串作为编译选项生成 Makefile;

2 重新编译

   $  cmake  -DCMAKE_BUILD_TYPE=Debug    Path               

注: Path 为源码的文件夹路径(比如在项目文件夹下建build,在build里面cmake的话,则Path是..), 如果 需要 Release 版 也可以 -DCMAKE_BUILD_TYPE = Release
然后,

  $ cd  Path  #好像不用,比如在build目录下make就可以
  $ make 

3 可以调试

     $ gdb  sample


    注:sample 为该可执行文件

多线程调试

GDB多线程多进程调试 - 云+社区 - 腾讯云 (tencent.com)

gdb 多线程调试 - 阿笨猫 - 博客园 (cnblogs.com)

举例

-rdynamic -g编译后r之后报错,然后bt(已经bt过,现在是重新bt只显示我自己写的文件)

q_v:1 q_v_n:2
d_v:2 d_v_n:4
dmap:
sz:2
(1:2)
(0:1)

qmap:
sz:2
(1:1)
(0:1)

dmap:
sz:6491032
(2:1)
(1:1)

qmap:
sz:1
(3:1)

//看最栈顶我自己写的函数map_cover的参数,参数是map指针,内容不好确定是否正确,于是结束调试去在map_cover中刚进来的地方加打印代码(这样在crash时已经输出了出错调用的参数),同时结合自己的代码逻辑,为了确定map内容对不对还需确定是哪两个顶点的哪种类型邻居,因此在调用map_cover的check_nlf_cover中加打印dmap和qmap来自哪个顶点的代码
//然后重新编译和r,此时显示上文信息
//查看map_cover报错处的源码,看到报错在打印语句之后,所以最靠近报错处的dmap和qmap打印即出错调用map_cover传入的参数
//看到dmap的sz奇怪,另外注意到已经输出一次dmapqmap因此这是out_nlf的map_cover调用,因此定位是out_nlf的map_cover调用中dmap有问题(也注意到d_v_n:4,这个是在源代码检查无误之后去检查的,看d_v_n:4的out_nlf在check_nlf_cover中对不对)
//查看上一层的源码out_nlf的map_cover调用中dmap的代码,还有dmap的来源代码,发现错误
//继续看check_nlf_cover的参数,部分看出是对的,可以先改一个错误编译gdb运行看看
Program received signal SIGFPE, Arithmetic exception.
0x0000000000416a95 in std::__detail::_Mod_range_hashing::operator() (this=0x630c48, __num=3, __den=0) at /usr/include/c++/4.8.2/bits/hashtable_policy.h:345
345	    { return __num % __den; }
Missing separate debuginfos, use: debuginfo-install libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64
(gdb) bt -5 //bt
##5  0x000000000041a42a in FilterVertices::map_cover (dmap=0x630c48, qmap=0x630be8) at src/FilterVertices.cpp:777
##6  0x00000000004199c0 in FilterVertices::check_nlf_cover (d_v=2, j=0, q_v=1, in_count=2, d_in_count=2, q_in_neighbors=0x62e228, d_in_neighbors=0x62e82c, 
    query_graph=0x62e400, data_graph=0x62c300, candidates=0x631180) at src/FilterVertices.cpp:127
##7  0x00000000004196e8 in FilterVertices::NLFFilter_1step (data_graph=0x62c300, query_graph=0x62e400, candidates=@0x7fffffffdd08: 0x631180, 
    candidates_count=@0x7fffffffdd00: 0x631160) at src/FilterVertices.cpp:91
##8  0x000000000041b244 in bulkq_nlf_filterCandi (qfilename_prefix="/media/data/hnu2022/yuanzhiqiu/DFiso_example/query_graph/query_", 
    nlf_ave_candiScale=@0x7fffffffe0e8: 3.1974663790792123e-317, data_graph=0x62c300, qVScale=4, jb=1, je=2, one_step=true) at src/FilterQueryHelp.cpp:402
##9  0x0000000000420f33 in main (argc=9, argv=0x7fffffffe448) at main.cpp:327
(gdb) l src/FilterVertices.cpp:777 //看map_cover打印是否在出错之前
772	        return 0;
773	    }
774	    
775	    for (auto qit : *qmap)
776	    {
777	        auto it = dmap->find(qit.first);
778	        if (it == dmap->end() || (it->second) < qit.second)
779	        {
780	            return 0;
781	        }
(gdb) l src/FilterVertices.cpp:127 //看check_nlf_cover中out_nlf调用map_cover的源码(或者编辑器里面看也行)
122	                {
123	
124	                    const std::unordered_map<LabelID, ui> *d_in_map = data_graph->getVertexInNLF(d_v_n);
125	                    const std::unordered_map<LabelID, ui> *d_out_map = query_graph->getVertexOutNLF(d_v_n);//d_out_map的来源,可以看到data_graph写成了query_graph,也发现下面一行也写错了
126	                    const std::unordered_map<LabelID, ui> *d_bi_map = query_graph->getVertexBiNLF(d_v_n);
127	                    if (map_cover(d_in_map, q_in_map) && map_cover(d_out_map, q_out_map) && map_cover(d_bi_map, q_bi_map))//看这第二个Map_cover调用,两个参数传入代码没有问题
128	                    {
129	                        break; 
130	                    }
131	                }
(gdb) 

评论