gdb
调试工具
Linux
环境下C语言编译的过程;gdb
命令调试C语言程序。实验包括预习报告和实验报告;
实验预习报告应根据课程内容,查阅相关资料,列出与实验相关的背景知识;
实验报告应包括设计方案、详细步骤、结果分析等,关键过程和运行结果可配以截图说明。
gcc
编译系统:c/c++
源文件——>预处理——>编译——>汇编——>连接——>执行。
gdb
程序调试工具:GNU发布的一个C/C++和汇编语言调试工具,用来检查错误,或验证输出,在程序运行中停止,并检查运行时数据。
#
开头的指令(伪指令)和特殊符号进行处理(伪指令:文件包含、宏定义和条件编译指令);静态连接 vs. 动态连接
静态连接:将静态链接库(通常以.a结尾)或归档库文件中复制到可执行文件中
动态连接:在程序执行时,才将动态链接库中连入可执行文件
gcc优先使用动态链接库gcc基本用法
gcc test.c #无选项时,gcc对test.c进行预处理、汇编、编译和连接,最终生成可执行文件,默认为 a.out gcc test.c -o test # -o指定输出文件 #gcc生成地文件具有可执行权限,可实现源程序地功能 man gcc #查看手册中的gcc的使用 # 预处理相关选项 gcc -I temp -D DOPTION=’”abc”’ -C -E hello.c -o hello.i #-I dir选项:指定指定搜索头文件的路径dir。若找不到,则在标准路径(/usr/include,/usr/lib及当前工作目录)中搜索 # -D name=value选项:预定义一个宏name,并指定其值为value所指定的值,若未指定值,默认值为1(等价于在源文件中使用宏定义指令#define name value 若宏值为字符串需要用双引号进行转义,默认值1常用于条件判断) # -U name选项:取消对宏name的定义(-D -U选项优先级最低) # -E选项:只对指定的源文件进行预处理,不做编译,生成的结果送到标准输出(若保存预处理后的源文件,需进行重定向或使用-o选项) # -C选项:在预处理后的输出中保留源文件中的注释 # -o file选项:输出预处理文件,扩展名应为.i或.ii # 编译程序选项 # -c选项:只生成目标文件,不进行连接,用于对源文件的分别编译 # -S选项:只进行编译,不做汇编,生成汇编代码文件格式,其名与源文件相同,扩展名为.s # -g选项:指示编译程序在目标代码中加入供调试程序gdb使用的附加信息 # -v选项:在标准出错输出上显示编译阶段所执行的命令,即编译驱动程序及预处理程序的版本号 # 多文件编译 # 多文件编译:gcc可以将多个源文件或中间文件一起编译,得到最终的可执行文件 # 连接程序选项 # 使用-c,-S,-E选项不会运行连接程序,不能使用目标文件作为参数 # -l name选项:连接时搜索由name命名的库(库名是libname.a或libname.so,若同时存在动态库和静态库,优先连接动态库) # -static选项:强制连接静态库 # -L dir:指定连接库的路径,把指定的目录dir加到连接程序搜索库文件的路径表中(若在-L指定的目录找不到库文件,再到标准位置下搜索) # 连接静态库 (将sq.c和cu.c创建为静态库,并在编译cal.c时使用该静态库) gcc -c sq.c cu.c #分别编译两个源文件 ar -rcs libcf.a sq.o cu.o #将目标文件制作为静态库 mkdir lib; mv libcf.a lib #创建静态库目录 gcc -o cal cal.c -lcf -Llib #在编译时连接静态库(-l指定连接库名cf,-L指定连接的目录为lib) # 连接动态库(将sq.c和cu.c创建为动态库,并在编译cal.c和执行cal时使用该动态库) gcc -c -fpic sq.c cu.c #分别编译两个源文件, 在编译时生成位置独立代码 gcc -shared -o libcf.so sq.o cu.o #将目标文件制作为动态库 sudo mv libcf.so /usr/lib #移动至系统指定的动态库目录 gcc -o cal cal.c -lcf #编译时连接动态库 ./cal #运行时自动连接动态库
选项 对应的C语言编译过程 输出文件 扩展名 -E 预处理 预处理后的C源文件 .i -S 预处理、编译 汇编程序文件 .s -c 预处理、编译、汇编 目标文件 .o 无选项 预处理、编译、汇编、连接 可执行文件 (.out)
程序中的错误
编译错误(语法错误):不符合语言规则
运行错误:运行过程中发生的错误,如除数为0
逻辑错误:对问题的求解发生错误
**程序调试:**查找程序中的错误,诊断错误的位置,并予以改正.
(gdb)
:提示符在编译源程序时使用-g选项,gcc -g -o dbme dbme.c
;将可执行文件作为gdb的参数或在gdb环境中使用file命令启动调试help
:查看gdb
的内部命令,(gdb) file dbme
quit, q
:退出gdb
环境
run,r
:运行调试(生成一个inferior调试进程,由于未设置断点,调试将运行到程序结束,或出错处)
kill,k
:终止调试
shell
:在gdb
环境中执行shell
命令
list, l
:显示源程序
l #显示10行源程序
l - #显示之前的10行源程序
l dbme.c:13 # 显示dbme.c源文件中13行前后的10行程序
l dbme.c:10,15 #显示dbme.c中第10到15行的程序
l index_m #显示index_m函数前后的10行程序
show listsize
,默认显示源程序行数
search, forward-search, reverse-search main
,在l命令列出的源码中进行搜索main
print, p, inspect
:调试停止时,查看运行时的数据,p [/输出格式] 表达式
,设置断点并运行调试,break 21,r
gdb中的运算符
&
获取变量的内存地址(p &i) 查看数组元素的内存地址(p &ary[0],p ary)
{type}adrexp
:查看地址中的数据(p {float}0x7fffffffdcac)。
@
查看多个数组元素(p ary[0]@10),查看数组元素的内存地址(p ary@10,p *ary@10)
file::var
与function::var:
查看文件与函数中的变量
whatis
:查看变量的数据类型[whatis i]
display, disp
:跟踪预先设置的变量或表达式,每次停止都会显示它的值 [disp i]
break,b
:设置断点
-g #选项编译的可执行文件的调试才支持断点
b 19 #在第19行设置断点,调试将停在该行开头
b 19 if i=3 #在19行设置断点,当i=3时停止
b index_m #在函数index_m入口设置断点
b dbme.c:19#在文件dbme.c第19行设置断点
b dbme.c:index_m#在文件dbme.c函数index_m入口处设置断点
b #在下一条语句处设置断点
info breakpoints, info break, i b
:显示断点
Num:断点号,类型为断点或观察点
Disp:断点被命中后的处理方式,保留(keep)、 删除(del)或关闭(dis)
Enb:断点的状态,激活(y)、关闭(n)
Address:断点的内存地址
What:断点在源文件中的信息
delete, d:
删除断点[d 、d 1]
disable与enable
:关闭与激活断点
watch:
设置观察点
断点在调试启动前预先设置,观察点在调试过程设置
语法:watch 表达式
watch i 对变量i设置观察点,当i值发生变化时停止
rwatch f 对变量f设置读观察点,当f被读取时停止
awatch f 对变量f设置读写观察点,当f被读取或写 入时停止
观察点也通过i b,d,disable和enable操作
run,r
运行调试,(r
不带参数运行,r argv1 argv2
带参数运行,参数将传递给main函数)
start:
运行调试,并停止第一条语句前(start
不带参数运行,start argv1 argv2
带参数运行)
单步追踪
step, s:执行下一条语句,若为函数调用则进入,相当于step into
next, n:执行下一条语句,若为函数调用不进入,相当于step out
finish, fin:继续运行函数内部的剩余代码,并退出当前函数,相当于step over
连续运行
continue, c:从当前行开始继续运行到下一处断点,或到达程序结束
其他命令
return:强行退出当前函数,并返回指定的值 return 3
p或set var:在调试过程中设置变量的值 p i=9 set var i=9
jump:跳转到指定的行或地址 jump 19
调试样例
调试过程
b index_m 设置断点
r 启动调试
disp i \disp ary[i] \disp &ary[i] \disp fary[i] \disp &fary[i]追踪变量
n 单步执行
发现错误
p ary[0]@20 \p fary[0]@20循环次数超出ary和fary数组申请的内存
make:程序维护工具
用来对大型软件进行维护,建立软件各部分的依赖关系
根据软件中各部分修改时间对软件进行维护,保持目标文件是最新的
makefile文件
make依赖makefile文件执行,makefile是一个文本形式的数据库文件,保存着文件之间的依赖关系及在这种依赖关系基础上应执行的命令序列
make依次查找名为GNUmakefile、makefile和Makefile的描述文件,通常使用Makefile
某个正在开发的软件包含以下文件:
三个C语言源文件:x.c,y.c和z.c,其中x.c和y.c使 用了defs.h头文件
汇编语言源文件assmb.s
动态链接库/home/me/lib/libm.so
软件最终生成可执行文件prog
makefile
文件编写
prog:x.o y.o z.o assmb.o
gcc x.o y.o z.o assmb.o -L/home/me/lib -lm -o prog
x.o:x.c defs.h
gcc -c x.c
y.o:y.c defs.h
gcc -c y.c
z.o:z.c
gcc -c z.c
assmb.o:assmb.s
as -o assmb.o assmbs.s
clean:
rm prog *.o
依赖关系图 一些高级用法
双冒号规则:常用于同一个目标文件由不同源文件生成的情况
make变量
定义:<变量名>=<字符串>,引用:$(变量名)
自动变量:如$<,$^,\$@
预定义变量:如CC,AR,AS
隐式规则:省略某些自动推导的规则
C源文件生成对应的目标文件
C++源文件生成对应的目标文件
汇编程序生成对应的目标文件
Linux
环境下C语言编译的过程;gdb
命令调试C语言程序。实验包括预习报告和实验报告;
实验预习报告应根据课程内容,查阅相关资料,列出与实验相关的背景知识;
实验报告应包括设计方案、详细步骤、结果分析等,关键过程和运行结果可配以截图说明。
gcc
编译系统:c/c++
源文件——>预处理——>编译——>汇编——>连接——>执行。
gdb
程序调试工具:GNU发布的一个C/C++和汇编语言调试工具,用来检查错误,或验证输出,在程序运行中停止,并检查运行时数据。
/* badprog.c 错误地访问内存 */
#include
#include
#include
int main(int argc, char **argv){
char *p;int i;
p = malloc(30);
strcpy(p, "not 30 bytes");
printf("p=<%s>\n", p);
if (argc == 2) {
if (strcmp(argv[1], "-b") == 0)
p[50] = 'a';
else if (strcmp(argv[1], "-f") == 0) {
free(p);
p[0] = 'b';}
}
free(p);
return 0;}
调试结果
(gdb) l
1 /* badprog.c 错误地访问内存 */
2 #include
3 #include
4 #include
5 int main(int argc, char **argv){
6 char *p;int i;
7 p = malloc(30);
8 strcpy(p, "not 30 bytes");
9 printf("p=<%s>\n", p);
10 if (argc == 2) {
(gdb) b 7
Breakpoint 1 at 0x40061c: file nc.c, line 7.
(gdb) r -b
Starting program: /root/a.out -b
Breakpoint 1, main (argc=2, argv=0x7fffffffe4f8) at nc.c:7
7 p = malloc(30);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-325.el7_9.x86_64
(gdb) n
8 strcpy(p, "not 30 bytes");
(gdb) n
9 printf("p=<%s>\n", p);
(gdb) n
p=<not 30 bytes>
10 if (argc == 2) {
(gdb) n
11 if (strcmp(argv[1], "-b") == 0)
(gdb) n
12 p[50] = 'a';
(gdb) p &p[50]
$1 = 0x602042 ""
(gdb) p &p[29]
$2 = 0x60202d ""
##错误的访问了未初始化的p[50]
(gdb) b 7
Breakpoint 1 at 0x40061c: file nc.c, line 7.
(gdb) r -f
Starting program: /root/a.out -f
Breakpoint 1, main (argc=2, argv=0x7fffffffe4f8) at nc.c:7
7 p = malloc(30);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-325.el7_9.x86_64
(gdb) n
8 strcpy(p, "not 30 bytes");
(gdb) n
9 printf("p=<%s>\n", p);
(gdb) n
p=<not 30 bytes>
10 if (argc == 2) {
(gdb) n
11 if (strcmp(argv[1], "-b") == 0)
(gdb) n
13 else if (strcmp(argv[1], "-f") == 0) {
(gdb) n
14 free(p);
(gdb) n
15 p[0] = 'b';}
(gdb) p &p[0]
$1 = 0x602010 ""
##且在free数组后仍访问该片内存
/* callstk.c 有三个函数调用深度的调用链 */
#include
#include
int make_key(void);
int get_key_num(void);
int number(void);
int main(void){
int ret = make_key();
printf("make_key returns %d\n", ret);
exit(EXIT_SUCCESS);}
int make_key(void){
int ret = get_key_num();
return ret;}
int get_key_num(void){
int ret = number();
return ret;}
int number(void){
return 10;}
调试结果
#使用断点调试
(gdb) b main
Breakpoint 2 at 0x55555555514d: file c.c, line 8.
(gdb) b make_key
Breakpoint 3 at 0x55555555517d: file c.c, line 12.
(gdb) b number
Note: breakpoint 1 also set at pc 0x5555555551a3.
Breakpoint 4 at 0x5555555551a3: file c.c, line 17.
(gdb) r
Starting program: /mnt/d/Document/local/linuxTestEnv/a.out
Breakpoint 2, main () at c.c:8
8 int ret = make_key();
(gdb) n
Breakpoint 3, make_key () at c.c:12
12 int ret = get_key_num();
(gdb) n
Breakpoint 1, number () at c.c:17
17 int number(void){return 10;}
(gdb) n
get_key_num () at c.c:16
16 return ret;}
(gdb) n
make_key () at c.c:13
13 return ret;}
(gdb) n
main () at c.c:9
9 printf("make_key returns %d\n", ret);
(gdb) n
make_key returns 10
10 exit(EXIT_SUCCESS);}
(gdb) n
[Inferior 1 (process 4873) exited normally]
(gdb) n
The program is not being run.
#使用单步调试
(gdb) start
Temporary breakpoint 2 at 0x400585: file hh.c, line 8.
Starting program: /root/a.out a.out
Temporary breakpoint 2, main () at hh.c:8
8 int ret = make_key();
(gdb) s
make_key () at hh.c:12
12 int ret = get_key_num();
(gdb) s
get_key_num () at hh.c:15
15 int ret = number();
(gdb) s
number () at hh.c:18
18 return 10;}(gdb) s
get_key_num () at hh.c:16
16 return ret;}
(gdb) s
make_key () at hh.c:13
13 return ret;}
(gdb) s
main () at hh.c:9
9 printf("make_key returns %d\n", ret);
(gdb) s
make_key returns 10
10 exit(EXIT_SUCCESS);}
(gdb) s
[Inferior 1 (process 2108) exited normally]
(gdb) s
通过本次实验学习并掌握了使用gdb
进行C语言程序代码调试以及gdb
常用调试方法与命令,并通过调试过程了解了C语言的编译过程。实验内容(1)程序错误的访问了未在指定范围内内存p[50]
,实验(2)在运行的过程中首先访问主函数,然后逐层访问各子函数,最后得到结果逐层返回至主函数并退出,顺序地进行相关操作。