程序调试--GDB

无论是多么优秀的程序员,都难以保证自己在编写代码时不会出现任何错误,因此调试是软件开发过程中的一个必不可少的 组成部分。当程序完成编译之后,它很可能无法正常运行,或者会彻底崩溃,或者不能实现预期的功能。此时如何通过调试找到问题的症结所在,就变成了摆在开发 人员面前最严峻的问题。通常说来,软件项目的规模越大,调试起来就会越困难,越需要一个强大而高效的调试器作为后盾。对于Linux程序员来讲,目前可供 使用的调试器非常多,GDB(GNU DeBugger)就是其中较为优秀的。 

    初识GDB 

    GDB 是自由软件基金会(Free Software Foundation,FSF)的软件工具之一。它的作用是协助程序员找到代码中的错误。如果没有GDB的帮助,程序员要想跟踪代码的执行流程,唯一的办 法就是添加大量的语句来产生特定的输出。但这一手段本身就可能会引入新的错误,从而也就无法对那些导致程序崩溃的错误代码进行分析。GDB的出现减轻了开 发人员的负担,他们可以在程序运行的时候单步跟踪自己的代码,或者通过断点暂时中止程序的执行。此外,他们还能够随时察看变量和内存的当前状态,并监视关 键的数据结构是如何影响代码运行的。 

    调试方法 

    如果想对程序进行调试,必须先在用GCC编译源代码时加上-g选项,以便产生GDB所需要的调试符号信息。例如,debugme.c是一个存在错误程序,可以使用如下的命令对其进行编译,同时产生调试符号:
    # gcc -g debugme.c -o debugme 

    如 果愿意的话,还可以在编译时使用“-ggdb”选项来生成更多的调试信息。由于这些调试信息中的相当一部分是GDB所特有的,所以生成的代码将无法在其它 调试器中正常调试。对于大多数情况来说,普通的-g选项就足够了。需要注意的是,GCC虽然允许同时使用-g(调试)和-o(优化)选项,但优化会影响最 终生成的代码,导致程序源代码和二进制代码之间的关系变得复杂起来如果不想为调试制造障碍,建议不要将-g和-o选项一同使用,并且只在程序彻底调试完 后才开始进行代码优化。这样调试过程将变得相对轻松和愉快。 

    基本应用 

    现在可以启动GDB来调试已经生成的可执行程序debugme,命令如下: 

# gdb debugme GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb)


    如果一切正常,GDB将被启动并在屏幕上输出版权信息,但如果使用了-q或--quiet选项则不会显示它们。启动GDB时另外一个有用的命令行选项是“-d dirname”,其中dirname是一个目录名。该目录名告诉GDB应该到哪里去寻找源代码。 

    一 旦出现GDB的命令提示符(gdb),就表明GDB已经准备好接收来自用户的各种调试命令了。如果想在调试环境下运行这个程序,可以使用GDB提供的 “run”命令,而程序在正常运行时所需的各种参数可以作为“run”命令的参数传入,或者使用单独的“set args”命令进行设置。如果在执行“run”命令时没有给出任何参数,GDB将使用上一次“run”或“set args”命令指定的参数。如果想取消上次设置的参数,可以执行不带任何参数的“set args”命令。下面尝试在调试器中运行这个程序: 

(gdb) run …… Program received signal SIGSEGV, Segmentation fault. 0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2


    最后一行输出表明程序在调用动态链接库/lib/ld-linux.so.2中的_dl_fini() 函数时出现了错误,地址是0x4000c6ac。这些对调试是非常重要的线索。另外还有一种信息对调试也很重要,就是错误发生时的函数调用层级关系,可以 通过执行“backtrace”命令来获得。在使用GDB调试命令时,用户可以不必输入完整的命令名称,使用任何惟一的缩写都可以。例如 “backtrace”命令就可以缩写成“back”甚至“bt”。GDB还支持很多常用的Shell命令编辑特征,比如可以像在bash或tcsh中那 样按Tab键补齐命令。如果相关命令不惟一的话,则列出所有可能的匹配项。此外键盘上的方向键可用来翻动历史命令。 

    GDB是一个源代码级的调试器,使用“list”命令可以查看当前调试对象的源代码。该命令的通用格式为“list [m,n]”,表示显示从m行开始到n行结束的代码段,而不带任何参数的“list”命令将显示最近10行源代码。 

    设置断点 
    在 调试有问题的代码时,在某一点停止运行往往很管用。这样程序运行到此外时会暂时挂起,等待用户的进一步输入。GDB允许在几种不同的代码结构上设置断点, 包括行号和函数名等,并且还允许设置条件断点,让程序只有在满足一定的条件时才停止执行。要根据行号设置断点,可以使用“ break linenum”命令要根据函数名设置断点,则应该使用“break funcname”命令。 

    在以上两种情况中,GDB将 在执行指定的行号或进入指定的函数之前停止执行程序此时可以使用“print”显示变量的值,或者使用“list”查看将要执行的代码。对于由多个源文 件组成的项目,如果想在执行到非当前源文件的某行或某个函数时停止执行,可以使用如下形式的命令: 

# break 20041126110727.htm:linenum # break 20041126110727.htm:funcname


    条件断点允许当一定条件满足时暂时停止程序的执行。它对于调试来讲非常有用。设置条件断点的正确语法如下: 

break linenum if expr break funcname if expr


    其中expr是一个逻辑表达式。当该表达式的值为真时,程序将在该断点处暂时挂起。例如,下面的命令将在debugme程序的第38行设置一个条件断点。当程序运行到该行时,如果count的值等于3,就将暂时停止执行:
    (gdb) break 38 if count==3 

    设置断点是调试程序时最常用到的一种手段。它可以中断程序的运行,给程序员一个单步跟踪的机会。使用命令“ break main”在main函数上设置断点可以在程序启动时就开始进行跟踪。 

    接 下去使用“continue”命令继续执行程序,直到遇到下一个断点。如果在调试时设置了很多断点,可以随时使用“info breakpoints”命令来查看设置的断点。此外,开发人员还可以使用“delete”命令删除断点,或者使用“disable”命令来使设置的断点 暂时无效。被设置为无效的断点在需要的时候可以用“enable”命令使其重新生效。 

    观察变量 
    GDB 最有用的特性之一是能够显示被调试程序中几乎任何表达式、变量或数组的类型和值,并且能够用编写程序所用的语言打印出任何合法表达式的值。查看数据最简单 的办法是使用“print”命令,只需在“print”命令后面加上变量表达式,就可以打印出此变量表达式的当前值,示例如下: 

(gdb) print str $1 = 0x40015360 "Happy new year!/n"


    从输出信息中可以看出,输入字符串被正确地存储在了字符指针str所指向的内存缓冲区中。除了给出变量 表达式的值外,“print”命令的输出信息中还包含变量标号($1)和对应的内存地址(0x40015360)。变量标号保存着被检查数值的历史记录, 如果此后还想访问这些值,就可以直接使用别名而不用重新输入变量表达式。 

    如果想知道变量的类型,可以使用“whatis”命令,示例如下: 

(gdb) whatis str          


    对于第一次调试别人的代码,或者面对的是一个异常复杂的系统时,“whatis”命令的作用不容忽视。 

    单步执行 
    为了单步跟踪代码,可以使用单步跟踪命令“step”,它每次执行源代码中的一行。 

    在GDB中可以使用许多方法来简化操作,除了可以将“step”命令简化为“s”之外,还可以直接输入回车键来重复执行前面一条命令。 

    除了可以用“step”命令来单步运行程序之外,GDB还提供了另外一条单步调试命令“next”。两者功能非常相似,差别在于如果将要被执行的代码行中包含函数调用,使用step命令将跟踪进入函数体内,而使用next命令则不进入函数体内。 

    在进入下一部分之前,使用下面的命令退出GDB:
    (gdb) quit 

    分析核心(core)文件 

    在 程序发生崩溃时,有时可能无法直接运行GDB来进行调试。比如程序可能是在另外一台机器上运行的,或者因为程序对时间比较敏感,所以手动跟踪调试会产生无 法接受的延迟等。遇到这些情况,就只能等到程序运行结束后才能判断崩溃的原因了。这时需要用到Linux提供的core dump机制。当程序中出现内存操作错误时,会发生崩溃并产生核心文件。使用GDB可以对产生的核心文件进行分析,找出程序是在什么时候崩溃的和在崩溃之 前程序都做了些什么。当然,如果要用GDB来分析核心文件,也必须在编译时加上-g选项来产生调试符号表。 

    在分析核心文件之前必须确认系统是否允许生成核心文件,很多Linux发行版在默认时禁止生成核心文件。为了生成核心文件,首先必须执行下面的命令:
    # ulimit -c unlimited 

    然后就可以生成核心文件了。这里仍以前面的debugme程序为例,再次执行下面命令将产生核心文件: 

# ./debugme Enter a string to count words:Happy new year! The number of words is 3. Segmentation fault (core dumped)


    生成的核心文件名根据系统配置的不同会有所差异。要在GDB中分析核心文件,除了要给出核心文件的文件名外,还必须给出生成该核心文件的可执行程序的名称,示例如下: 

#gdb debugme core.547 …… Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. ……


    从GDB的输出信息中可以看出,产生这个核心文件的原因是因为程序收到了序号为11的信号。如果想知道程序在崩溃之前运行到了哪里,可以使用“backtrace”或“info stack”命令查看一下堆栈的历史记录。示例如下: 

(gdb) info stack #0 0x4000c6ac in _dl_fini () from /lib/ld-linux.so.2 #1 0x40057940 in exit () from /lib/libc.so.6 #2 0x4004291f in _libc_start_main () from /lib/libc.so.6


    由上可知,程序崩溃时正处于_dl_fini()函数之中。但很多时候程序员感兴趣的可能并不是这个, 而是exit()或_libc_start_main()函数,因为它们才可能是问题真正的症结所在。GDB提供的“frame”命令可以用来在不同的调 用上下文中切换。例如下面的命令可以查看exit()函数在执行时的状况: 

(gdb) frame 1 #1 0x40057940 in exit () from /lib/libc.so.6


    此外还可以用“up”或“down”命令在不同的函数调用上下文中切换。开发人员使用这三条命令可以很轻松地实现调用栈的遍历。在分析核心文件时,通过将遍历栈的命令和检查变量值的“print”命令结合起来,就能够复原程序运行时的全部景象。 

    调试其它进程 

    有 时会遇到一种很特殊的调试需求,对当前正在运行的其它进程进行调试。这种情况有可能发生在那些无法直接在调试器中运行的进程身上,例如有的进程只能在系统 启动时运行。另外如果需要对进程产生的子进程进行调试的话,也只能采用这种方式。GDB可以对正在执行的程序进行调度,它允许开发人员中断程序并查看其状 态,之后还能让这个程序正常地继续执行。 

    GDB提供了两种方式来调试正在运行的进程:一种是在GDB命令行上指定进程的PID,另一种是在GDB中使用“attach”命令。例如,开发人员可以先启动debugme程序,让其开始等待用户的输入。示例如下: 

#./debugme Enter a string to count words:


    接下去在另一个虚拟控制台中用下面的命令查出该进程对应的进程号: 

# ps -ax | grep debugme 555 pts/1 S 0:00 ./debugme


    得到进程的PID后,就可以使用GDB对其进行调试了: 

# gdb debugme 555 GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) Attaching to program: /home/xiaowp/debugme, process 555 Reading symbols from /lib/libc.so.6...done. ……


    在上面的输出信息中,以Attaching to program开始的行表明GDB已经成功地附加在PID为555的进程上了。另外一种连接到其它进程的方法是先用file命令加载调试时所需的符号表,然后再通过“attaché”命令进行连接: 

(gdb) file /home/xiaowp/debugme Reading symbols from /home/xiaowp/debugme...done. (gdb) attach 555 ……


    如果想知道程序现在运行到了哪里,同样可以使用“backtrace”命令。当然也可以使用“step”命令对程序进行单步调试。 

    在完成调试之后,不要忘记用detach命令断开连接,让被调试的进程可以继续正常运行: 

    GDB是Linux下一个最基本的调试器,其功能非常丰富。完整地介绍GDB的功能可能需要几百页,本文只涵盖了GDB的一些最常见的用法。作为一个合格的Linux程序员,花在GDB上的功夫和时间越多,从调试中获得的益处就越多。




Linux 包含了一个叫 gdb 的 GNU 调试程序. gdb 是一个用来调试 C 和 C++ 程序的强力调试器. 它使你能在程序运行时观察程序的内部结构和内存的使用情况. 以下是 gdb 所提供的一些功能: 
它使你能监视你程序中变量的值. 
它使你能设置断点以使程序在指定的代码行上停止执行. 
它使你能一行行的执行你的代码. 

在命令行上键入 gdb 并按回车键就可以运行 gdb 了, 如果一切正常的话, gdb 将被启动并且你将在屏幕上看到类似的内容: 
GDB is free software and you are welcome to distribute copies of it 
under certain conditions; type "show copying" to see the conditions. 
There is absolutely no warranty for GDB; type "show warranty" for details. 
GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc. 
(gdb) 
当你启动 gdb 后, 你能在命令行上指定很多的选项. 你也可以以下面的方式来运行 gdb : 
gdb <fname> 
当你用这种方式运行 gdb , 你能直接指定想要调试的程序. 这将告诉gdb 装入名为 fname 的可执行文件. 你也可以用 gdb 去检查一个因程序异常终止而产生的 core 文件, 或者与一个正在运行的程序相连. 你可以参考 gdb 指南页或在命令行上键入 gdb -h 得到一个有关这些选项的说明的简单列表. 

为调试编译代码(Compiling Code for Debugging) 
为了使 gdb 正常工作, 你必须使你的程序在编译时包含调试信息. 调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号. gdb 利用这些信息使源代码和机器码相关联. 
在编译时用 -g 选项打开调试选项. 

gdb 基本命令 
gdb 支持很多的命令使你能实现不同的功能. 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令, 表27.1列出了你在用 gdb 调试时会用到的一些命令. 想了解 gdb 的详细使用请参考 gdb 的指南页. 
表 27.1. 基本 gdb 命令. 

命 令 描 述 
file 装入想要调试的可执行文件. 
kill 终止正在调试的程序. 
list 列出产生执行文件的源代码的一部分. 
next 执行一行源代码但不进入函数内部. 
step 执行一行源代码而且进入函数内部. 
run 执行当前被调试的程序 
quit 终止 gdb 
watch 使你能监视一个变量的值而不管它何时被改变. 
break 在代码里设置断点, 这将使程序执行到这里时被挂起. 
make 使你能不退出 gdb 就可以重新产生可执行文件. 
shell 使你能不离开 gdb 就执行 UNIX shell 命令. 

gdb 支持很多与 UNIX shell 程序一样的命令编辑特征. 你能象在 bash 或 tcsh里那样按 Tab 键让 gdb 帮你补齐一个唯一的命令, 如果不唯一的话 gdb 会列出所有匹配的命令. 你也能用光标键上下翻动历史命令. 

gdb 应用举例 
本节用一个实例教你一步步的用 gdb 调试程序. 被调试的程序相当的简单, 但它展示了 gdb 的典型应用. 

下面列出了将被调试的程序. 这个程序被称为 greeting , 它显示一个简单的问候, 再用反序将它列出.
#include <stdio.h> 
main () 

char my_string[] = "hello there"; 
my_print (my_string); 
my_print2 (my_string); 


void my_print (char *string) 

printf ("The string is %s/n", string); 


void my_print2 (char *string) 

char *string2; 
int size, i; 

size = strlen (string); 
string2 = (char *) malloc (size + 1); 
for (i = 0; i < size; i++) 
string2[size - i] = string[i]; 
string2[size+1] = `/0'; 
printf ("The string printed backward is %s/n", string2); 

用下面的命令编译它: 

gcc -o test test.c 
这个程序执行时显示如下结果: 
The string is hello there 

The string printed backward is 
输出的第一行是正确的, 但第二行打印出的东西并不是我们所期望的. 我们所设想的输出应该是: 
The string printed backward is ereht olleh 
由于某些原因, my_print2 函数没有正常工作. 让我们用 gdb 看看问题究竟出在哪儿, 先键入如下命令: 

gdb greeting 

-------------------------------------------------------------------------------- 
注意: 记得在编译 greeting 程序时把调试选项打开. 
-------------------------------------------------------------------------------- 

如果你在输入命令时忘了把要调试的程序作为参数传给 gdb , 你可以在 gdb 提示符下用 file 命令来载入它: 

(gdb) file greeting 
这个命令将载入 greeting 可执行文件就象你在 gdb 命令行里装入它一样. 
这时你能用 gdb 的 run 命令来运行 greeting 了. 当它在 gdb 里被运行后结果大约会象这样: 

(gdb) run 
Starting program: /root/greeting 
The string is hello there 
The string printed backward is 
Program exited with code 041 
这个输出和在 gdb 外面运行的结果一样. 问题是, 为什么反序打印没有工作? 为了找出症结所在, 我们可以在 my_print2 函数的 for 语句后设一个断点, 具体的做法是在 gdb 提示符下键入 list 命令三次, 列出源代码: 
(gdb) list 

(gdb) list 

(gdb) list 
-------------------------------------------------------------------------------- 
技巧: 在 gdb 提示符下按回车健将重复上一个命令. 
-------------------------------------------------------------------------------- 

第一次键入 list 命令的输出如下: 
1 #include <stdio.h> 

3 main () 
4 { 
5 char my_string[] = "hello there"; 

7 my_print (my_string); 
8 my_print2 (my_string); 
9 } 
10 
如果按下回车, gdb 将再执行一次 list 命令, 给出下列输出: 
11 my_print (char *string) 
12 { 
13 printf ("The string is %s/n", string); 
14 } 
15 
16 my_print2 (char *string) 
17 { 
18 char *string2; 
19 int size, i; 
20 
再按一次回车将列出 greeting 程序的剩余部分: 
21 size = strlen (string); 
22 string2 = (char *) malloc (size + 1); 
23 for (i = 0; i < size; i++) 
24 string2[size - i] = string[i]; 
25 string2[size+1] = `/0'; 
26 printf ("The string printed backward is %s/n", string2); 
27 } 
根据列出的源程序, 你能看到要设断点的地方在第24行, 在 gdb 命令行提示符下键入如下命令设置断点: 
(gdb) break 24 
gdb 将作出如下的响应: 
Breakpoint 1 at 0x139: file greeting.c, line 24 
(gdb) 
现在再键入 run 命令, 将产生如下的输出: 
Starting program: /root/greeting 
The string is hello there 
Breakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :24 
24 string2[size-i]=string[i] 
你能通过设置一个观察 string2[size - i] 变量的值的观察点来看出错误是怎样产生的, 做法是键入: 
(gdb) watch string2[size - i] 
gdb 将作出如下回应: 
Watchpoint 2: string2[size - i] 
现在可以用 next 命令来一步步的执行 for 循环了: 
(gdb) next 
经过第一次循环后, gdb 告诉我们 string2[size - i] 的值是 `h`. gdb 用如下的显示来告诉你这个信息: 
Watchpoint 2, string2[size - i] 
Old value = 0 `/000' 
New value = 104 `h' 
my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23 
23 for (i=0; i<size; i++) 
这个值正是期望的. 后来的数次循环的结果都是正确的. 当 i=10 时, 表达式 string2[size - i] 的值等于 `e`, size - i 的值等于 1, 最后一个字符已经拷到新串里了. 
如果你再把循环执行下去, 你会看到已经没有值分配给 string2[0] 了, 而它是新串的第一个字符, 因为 malloc 函数在分配内存时把它们初始化为空(null)字符. 所以 string2 的第一个字符是空字符. 这解释了为什么在打印 string2 时没有任何输出了. 

现在找出了问题出在哪里, 修正这个错误是很容易的. 你得把代码里写入 string2 的第一个字符的的偏移量改为 size - 1 而不是 size. 这是因为 string2 的大小为 12, 但起始偏移量是 0, 串内的字符从偏移量 0 到 偏移量 10, 偏移量 11 为空字符保留. 

为了使代码正常工作有很多种修改办法. 一种是另设一个比串的实际大小小 1 的变量. 这是这种解决办法的代码: 
#include <stdio.h> 
main () 

char my_string[] = "hello there"; 
my_print (my_string); 
my_print2 (my_string); 


my_print (char *string) 

printf ("The string is %s/n", string); 


my_print2 (char *string) 

char *string2; 
int size, size2, i; 

size = strlen (string); 
size2 = size -1; 
string2 = (char *) malloc (size + 1); 
for (i = 0; i < size; i++) 
string2[size2 - i] = string[i]; 
string2[size] = `/0'; 
printf ("The string printed backward is %s/n", string2); 


  

在Linux下调试程序一般用GDB来执行。
  这里简要介绍一下是否gdb调试程序的方法:
  (1)进入gdb调试:
  gdb + 已经编译通过的可执行程序 -》 就进入调试模式。例如:gdb MiddlePublisher
  (2)r + 运行时的参数 -》 开始运行可执行程序。例如 r -lxml2 -f refile
   (3)b + 断点 -》设置调试的断点。两种:一种是:b CMSTask.cpp:200 表示在CMSTask.cpp文件的第200行设置断点。另一种:b TaskManager::buildPubWinTask 表示在执行buildPubWinTask这个函数的时候停止。
  (4)取消断点:
  dis 1 表示取消第一个断点
  dis 2 表示取消第二个断点
  (5)查看设置断点信息: info b
  (6)在断点停止处查看所在代码的详细信息:l
  (7)可以在gdb中直接编译,然后再重新运行时,gdb会直接执行新编译好的可执行程序。例如:直接在gdb下执行make后再重庆运行。
  (8)跟进一个函数:s
  如果设置的断点是在一个函数入口。到达该断点时,键入s就可以进入该函数内部进行调试。如果有多个函数就多次键入S来进入内部的函数。
  PS:
  1、在SecureCRT远程登录界面上开启多个窗口。在窗口之间切换时用:Alt+1,Alt+2.....表示切换到第1个,第2个窗口。
  2、同样在在SecureCRT远程登录界面上要粘贴复制好的内容用:Shift+Insert。


  

检查一切 
memcpy, strcpy, strcat sprintf 动态数组下标。 
这种问题多半世内存访问错误或者缓冲区溢出覆盖堆栈造成的。 
调试方法: 
gdb 调试程序或者gdb调试core文件  
  

编译时加入-g调试选项,去掉-Ox选项 
使用gdb运行,如果中断退出,使用bt命令查看调用堆栈,如果不是可以 
通过thr n (n表示线程号,用 info thr查看)切换,然后bt看堆栈 
以上方法在kernel 2.6+gdb 6中有问题 

  

一:列文件清单     
  1.   List     
  (gdb)   list   line1,line2     
    
  二:执行程序     
  要想运行准备调试的程序,可使用run命令,在它后面可以跟随发给该程序的任何参数,包括标准输入和标准输出说明符(<和>)和外壳通配符(*、?、[、])在内。     
  如果你使用不带参数的run命令,gdb就再次使用你给予前一条run命令的参数,这是很有用的。     
  利用set   args   命令就可以修改发送给程序的参数,而使用show   args   命令就可以查看其缺省参数的列表。     
  (gdb)set   args   –b   –x     
  (gdb)   show   args     
  backtrace命令为堆栈提供向后跟踪功能。     
  Backtrace   命令产生一张列表,包含着从最近的过程开始的所以有效过程和调用这些过程的参数。     
    
  三:显示数据     
  利用print   命令可以检查各个变量的值。     
  (gdb)   print   p   (p为变量名)     
  whatis   命令可以显示某个变量的类型     
  (gdb)   whatis   p     
  type   =   int   *     
    
  print   是gdb的一个功能很强的命令,利用它可以显示被调试的语言中任何有效的表达式。表达式除了包含你程序中的变量外,还可以包含以下内容:     
  l   对程序中函数的调用     
  (gdb)   print   find_entry(1,0)     
  l   数据结构和其他复杂对象     
  (gdb)   print   *table_start     
  $8={e=reference=’/000’,location=0x0,next=0x0}     
  l   值的历史成分     
  (gdb)print   $1   ($1为历史记录变量,在以后可以直接引用   $1   的值)     
  l   人为数组     
  人为数组提供了一种去显示存储器块(数组节或动态分配的存储区)内容的方法。早期的调试程序没有很好的方法将任意的指针换成一个数组。就像对待参数一样,让我们查看内存中在变量h后面的10个整数,一个动态数组的语法如下所示:     
  base@length      
  因此,要想显示在h后面的10个元素,可以使用h@10 :     
  (gdb)print   h@10      
  $13=(-1,345,23,-234,0,0,0,98,345,10)     
    
  四:断点(breakpoint)     
  break命令(可以简写为b)可以用来在调试的程序中设置断点,该命令有如下四种形式:     
  l   break   line-number   使程序恰好在执行给定行之前停止。     
  l   break   function-name   使程序恰好在进入指定的函数之前停止。     
  l   break   line-or-function   if   condition   如果condition(条件)是真,程序到达指定行或函数时停止。     
  l   break   routine-name   在指定例程的入口处设置断点     
    
  如果该程序是由很多原文件构成的,你可以在各个原文件中设置断点,而不是在当前的原文件中设置断点,其方法如下:     
  (gdb)   break   filename:line-number     
  (gdb)   break   filename:function-name     
    
  要想设置一个条件断点,可以利用break   if命令,如下所示:     
  (gdb)   break   line-or-function   if   expr     
  例:     
  (gdb)   break   46   if   testsize==100     
    
  从断点继续运行:countinue   命令     
  五.断点的管理     
    
  1.   显示当前gdb的断点信息:     
  (gdb)   info   break     
  他会以如下的形式显示所有的断点信息:     
  Num   Type   Disp   Enb   Address   What     
  1   breakpoint   keep   y   0x000028bc   in   init_random   at   qsort2.c:155     
  2   breakpoint   keep   y   0x0000291c   in   init_organ   at   qsort2.c:168     
  (gdb)     
  2.删除指定的某个断点:     
  (gdb)   delete   breakpoint   1     
  该命令将会删除编号为1的断点,如果不带编号参数,将删除所有的断点     
  (gdb)   delete   breakpoint     
  3.禁止使用某个断点     
  (gdb)   disable   breakpoint   1     
  该命令将禁止断点   1,同时断点信息的   (Enb)域将变为   n     
  4.允许使用某个断点     
  (gdb)   enable   breakpoint   1     
  该命令将允许断点   1,同时断点信息的   (Enb)域将变为   y     
  5.清除原文件中某一代码行上的所有断点     
  (gdb)clean   number     
  注:number   为原文件的某个代码行的行号     
  六.变量的检查和赋值     
  l   whatis:识别数组或变量的类型     
  l   ptype:比whatis的功能更强,他可以提供一个结构的定义     
  l   set   variable:将值赋予变量     
  l   print   除了显示一个变量的值外,还可以用来赋值     
七.单步执行     
  l   next     
  不进入的单步执行     
  l   step     
  进入的单步执行     
  如果已经进入了某函数,而想退出该函数返回到它的调用函数中,可使用命令finish     
  八.函数的调用     
  l   call   name   调用和执行一个函数     
  (gdb)   call   gen_and_sork(   1234,1,0   )     
  (gdb)   call   printf(“abcd”)     
  $1=4     
  l   finish   结束执行当前函数,显示其返回值(如果有的话)     
    
  九.机器语言工具     
  有一组专用的gdb变量可以用来检查和修改计算机的通用寄存器,gdb提供了目前每一台计算机中实际使用的4个寄存器的标准名字:     
  l   $pc   :   程序计数器     
  l   $fp   :   帧指针(当前堆栈帧)     
  l   $sp   :   栈指针     
  l   $ps   :   处理器状态     
    
  十.信号     
  gdb通常可以捕捉到发送给它的大多数信号,通过捕捉信号,它就可决定对于正在运行的进程要做些什么工作。例如,按CTRL-C将中断信号发送给gdb, 通常就会终止gdb。但是你或许不想中断gdb,真正的目的是要中断gdb正在运行的程序,因此,gdb要抓住该信号并停止它正在运行的程序,这样就可以 执行某些调试操作。     
    
  Handle命令可控制信号的处理,他有两个参数,一个是信号名,另一个是接受到信号时该作什么。几种可能的参数是:     
  l   nostop   接收到信号时,不要将它发送给程序,也不要停止程序。     
  l   stop   接受到信号时停止程序的执行,从而允许程序调试;显示一条表示已接受到信号的消息(禁止使用消息除外)     
  l   print   接受到信号时显示一条消息     
  l   noprint   接受到信号时不要显示消息(而且隐含着不停止程序运行)     
  l   pass   将信号发送给程序,从而允许你的程序去处理它、停止运行或采取别的动作。     
  l   nopass   停止程序运行,但不要将信号发送给程序。     
  例如,假定你截获SIGPIPE信号,以防止正在调试的程序接受到该信号,而且只要该信号一到达,就要求该程序停止,并通知你。要完成这一任务,可利用如下命令:     
  (gdb)   handle   SIGPIPE   stop   print     
  请注意,UNIX的信号名总是采用大写字母!你可以用信号编号替代信号名     
  如果你的程序要执行任何信号处理操作,就需要能够测试其信号处理程序,为此,就需要一种能将信号发送给程序的简便方法,这就是signal命令的任务。 该   命令的参数是一个数字或者一个名字,如SIGINT。假定你的程序已将一个专用的SIGINT(键盘输入,或CTRL-C;信号2)信号处理程序设置成 采   取某个清理动作,要想测试该信号处理程序,你可以设置一个断点并使用如下命令:     
  (gdb)   signal   2     
  continuing   with   signal   SIGINT(2)     
  该程序继续执行,但是立即传输该信号,而且处理程序开始运行.     
    
  十一.   原文件的搜索     
  search   text:该命令可显示在当前文件中包含text串的下一行。     
  Reverse-search   text:该命令可以显示包含text   的前一行。     
    
  十二.UNIX接口     
  shell   命令可启动UNIX外壳,CTRL-D退出外壳,返回到   gdb.     
    
  十三.命令的历史     
  为了允许使用历史命令,可使用   set   history   expansion   on   命令     
  (gdb)   set   history   expansion   on     
    
  小结:常用的gdb命令     
  backtrace   显示程序中的当前位置和表示如何到达当前位置的栈跟踪(同义词:where)     
  breakpoint   在程序中设置一个断点     
  cd   改变当前工作目录     
  clear   删除刚才停止处的断点     
  commands   命中断点时,列出将要执行的命令     
  continue   从断点开始继续执行     
  delete   删除一个断点或监测点;也可与其他命令一起使用     
  display   程序停止时显示变量和表达时     
  down   下移栈帧,使得另一个函数成为当前函数     
  frame   选择下一条continue命令的帧     
  info   显示与该程序有关的各种信息     
  jump   在源程序中的另一点开始运行     
  kill   异常终止在gdb   控制下运行的程序     
  list   列出相应于正在执行的程序的原文件内容     
  next   执行下一个源程序行,从而执行其整体中的一个函数     
  print   显示变量或表达式的值     
  pwd   显示当前工作目录     
  pype   显示一个数据结构(如一个结构或C++类)的内容     
  quit   退出gdb     
  reverse-search   在源文件中反向搜索正规表达式     
  run   执行该程序     
  search   在源文件中搜索正规表达式     
  set   variable   给变量赋值     
  signal   将一个信号发送到正在运行的进程     
  step   执行下一个源程序行,必要时进入下一个函数     
  undisplay   display命令的反命令,不要显示表达式     
  until   结束当前循环     
  up   上移栈帧,使另一函数成为当前函数     
  watch   在程序中设置一个监测点(即数据断点)     
  whatis   显示变量或函数类型     
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 RISC-V 平台上使用 GDB 进行调试,需要先安装 RISC-V 版本的 GDB。可以通过以下命令安装: ``` sudo apt-get install gdb-multiarch ``` 安装完成后,可以使用以下命令启动 GDB: ``` riscv64-unknown-elf-gdb <executable> ``` 其中 `<executable>` 是要调试的可执行文件的路径。接下来,可以使用 GDB 的各种命令进行调试。 以下是一些常用的 GDB 命令: - `break <function>`:在指定函数处设置断点。 - `break <line>`:在指定行处设置断点。 - `run <args>`:运行程序并传入参数。 - `step`:单步执行程序。 - `next`:执行下一条语句,不进入函数内部。 - `continue`:继续执行程序,直到遇到下一个断点。 - `print <variable>`:打印变量的值。 - `backtrace`:查看函数调用栈。 此外,还可以使用 `-g` 选项编译程序时生成调试信息,以便在 GDB 中进行调试。例如: ``` riscv64-unknown-elf-gcc -g -o <executable> <source files> ``` ### 回答2: gdb是一个功能强大的调试工具,适用于多种不同的处理器架构,其中包括risc-v。使用gdb调试risc-v程序可以帮助我们定位并解决程序中的bug。 具体步骤如下: 1. 安装gdb:首先需要确保在计算机上安装了gdb。可以通过包管理器或者从gdb的官方网站上下载并安装。 2. 编译程序:在使用gdb之前,需要将C/C++程序编译为可执行文件。例如,我们可以使用risc-v的交叉编译工具链来编译程序。 3. 运行gdb:在命令行中输入"gdb"命令来启动gdb调试器。 4. 加载可执行文件:在gdb中输入"file <可执行文件路径>"命令来加载要调试的可执行文件。 5. 设置断点:可以使用"break <行号或函数名>"命令在程序中设置断点。这将使程序暂停在指定的位置,以便进行调试。 6. 启动程序:在gdb中输入"run"命令来启动程序的执行。当程序到达设置的断点时,它将暂停执行。 7. 检查程序状态:在程序暂停时,可以使用各种gdb命令来检查当前变量的值、堆栈跟踪等信息,以了解程序的当前状态。 8. 单步执行:可以使用"next"或"step"命令逐行执行程序。这将允许我们逐步跟踪程序的执行,以查找错误。 9. 查看变量值:使用"gdb"命令可以查看当前变量的值。可以为每个变量设置监视点来观察其变化。 10. 修复错误:一旦发现了bug,可以在gdb中进行相应的更改或修复。然后可以重新编译程序并再次使用gdb进行调试,以确保问题已经解决。 使用gdb调试risc-v程序可以更加高效地定位问题并进行调试。通过将gdb与其他调试技术,如断言和日志输出,结合使用,可以帮助开发人员更轻松地编写和维护risc-v程序。 ### 回答3: gdb是一款强大的调试器,可用于调试RISC-V程序。以下是使用gdb调试RISC-V程序的步骤: 首先,确保已经安装了RISC-V架构的gdb工具。如果没有安装,可以通过下载源代码并手动编译安装来获取。 1. 将需要调试的RISC-V可执行文件加载到gdb中。可以使用以下命令: ``` gdb <可执行文件名> ``` 2. 设置断点来在程序的特定位置中断执行。使用`break`命令来设置断点。例如,要在第30行设置断点,可以使用以下命令: ``` break main.c:30 ``` 3. 启动调试会话。可以直接使用`run`命令启动程序,或者使用`start`命令以暂停的方式启动程序(这样可以在设置完断点后再启动)。 4. 当程序在断点处中断时,可以使用`next`命令逐过程执行,或者使用`step`命令逐语句执行。还可以使用`continue`命令使程序继续执行直到下一个断点或程序结束。 5. 在程序执行过程中,可以使用`print`命令打印变量或表达式的值。例如,要打印变量`x`的值,可以使用以下命令: ``` print x ``` 6. 若要查看当前执行的源代码行,可以使用`list`命令。 7. 将gdb作为交互式调试器使用时,还可以使用一些其他命令来查看寄存器的值、改变变量的值等。 在调试RISC-V程序时,gdb是一个非常有用的工具。它提供了一系列命令和功能,帮助我们理解程序执行的过程,查找错误并进行调试。以上是使用gdb调试RISC-V程序的基本步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值