实验四
gdb
调试工具
gdb
调试工具
一、实验目的
- 正确表述
Linux
环境下C语言编译的过程; - 熟练运用
gdb
命令调试C语言程序。
二、实验要求
-
实验包括预习报告和实验报告;
-
实验预习报告应根据课程内容,查阅相关资料,列出与实验相关的背景知识;
-
实验报告应包括设计方案、详细步骤、结果分析等,关键过程和运行结果可配以截图说明。
三、实验原理
gcc
编译系统:c/c++
源文件——>预处理——>编译——>汇编——>连接——>执行。
gdb
程序调试工具:GNU发布的一个C/C++和汇编语言调试工具,用来检查错误,或验证输出,在程序运行中停止,并检查运行时数据。
四、实验预习内容
C语言编译过程
- 预处理阶段:读取C语言源文件,对其中以
#
开头的指令(伪指令)和特殊符号进行处理(伪指令:文件包含、宏定义和条件编译指令); - 编译阶段:对预处理后的输出文件进行词法分析和语法分析,找出所有不符合语法规则的部分;将源程序“翻译”为功能等价的中间代码表示或者汇编程序 ;
- 汇编阶段:把汇编代码翻译成为目标机器代码;目标文件由机器码构成,通常包括代码段和数据段两部分;
- 连接阶段:解决外部符号访问地址问题,将相关地目标文件练成一个整体,形成可执行文件;连接模式分为静态连接和动态连接
gdb程序调试
程序中的错误
**程序调试:**查找程序中的错误,诊断错误的位置,并予以改正.
(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
一、 实验目的
- 正确表述
Linux
环境下C语言编译的过程; - 熟练运用
gdb
命令调试C语言程序。
二、 实验要求
-
实验包括预习报告和实验报告;
-
实验预习报告应根据课程内容,查阅相关资料,列出与实验相关的背景知识;
-
实验报告应包括设计方案、详细步骤、结果分析等,关键过程和运行结果可配以截图说明。
三、 实验原理
gcc
编译系统:c/c++
源文件——>预处理——>编译——>汇编——>连接——>执行。
gdb
程序调试工具:GNU发布的一个C/C++和汇编语言调试工具,用来检查错误,或验证输出,在程序运行中停止,并检查运行时数据。
四、 实验内容
- 调试下面的程序,发现程序中的错误:
/* badprog.c 错误地访问内存 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
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 <stdio.h>
#include <stdlib.h>
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)在运行的过程中首先访问主函数,然后逐层访问各子函数,最后得到结果逐层返回至主函数并退出,顺序地进行相关操作。