完整的调试过程,跟踪堆栈变化,32位下。
注意64位和此不同。
a.c代码:
#include <stdio.h>
int main()
{
AFunc(5,6);
return 0;
}
int BFunc(int i,int j)
{
int m = 1;
int n = 2;
m = i;
n = j;
return m;
}
int AFunc(int i,int j)
{
int m = 3;
int n = 4;
m = i;
n = j;
BFunc(m,n);
return 8;
}
编译加上调试信息
#gcc -g -o a a.c
要调试C程序,在编译时,必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:
> cc -g hello.c -o hello
> g++ -g hello.cpp -o hello
如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。
启动gdb
#gdb a
增加断点
#break *main
运行
#run
步入
#s 进入的单步执行
如果已经进入了某函数,而想退出该函数返回到它的调用函数中,可使用命令finish
#finsh
#n 不进入的单步执行
查看数组的值
有时候,你需要查看一段连续的内存空间的值。比如数组的一段,或是动态分配的数据的大小。你可以使用GDB的“@”操作符,“@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度。例如,你的程序中有这样的语句:
int *array = (int *) malloc (len * sizeof (int));
于是,在GDB调试过程中,你可以以如下命令显示出这个动态数组的取值:
#p *array@len
如果是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。
whatis 命令可以显示某个变量的类型
(gdb) whatis p
type = int *
查看汇编
#disas
#bt 查看栈帧
#f 0查看第0帧
#f 1查看第1帧
#f N查看第N帧
之后查看寄存器也会查看对应的寄存器
#i r
之后也会查看对应寄存器内容
#x/40xw $esp
查看堆栈底
#x/40xw $ebp
--《深入理解计算机系统(原书第2版)》
开始
1.main函数中第1个s




ebp的内容为0
2.main函数中第2个s,开始调用A函数


很明显esp和ebp变化了,上一步的ebp地址被pusp到新的ebp的内容。
3.进入A函数

显示ebp入栈;
然后esp指向新的ebp
sub $0x18,%esp即esp减少24个地址;0xbffff618-18=0xbffff600
第1帧:

第0帧:


ebp依次保存了:
“上一个ebp的地址 0xbffff638;
“main函数中调用完A函数后的执行地址 0x080483b1”;
“上级函数传递的参数5保存在ebp的正向地址”;
“上级函数传递的参数6保存在ebp的正向地址”
将进入AFunc函数之前的EBP的值入栈保存,这时候的EBP相当于是AFunc上级函数; 的一个现场信息,所以需要保存起来,以便于AFunc返回后上级函数可以恢复EBP使其指向其调用; AFunc之前的堆栈位置(当然,这还需要靠恢复ESP来协助达到这一目的)
4.A函数中int n=4前

0x8(%ebp)的-8个位置存放3;
0x4(%ebp)的-4个位置存放4。

注意:这里如果调用f 0则后面的i r和x/40xw $ebp都是查看该栈帧

5.A函数m=j前


函数的局部变量放置在EBP的负偏移处(Negative; Offset)也就是向低地址方向。
esp在0xbffff618,3和4分别在0xbffff610和0xbffff614。
6.A函数n=j之前

0x8(ebp)获取ebp正向地址的值稍后mov到eax寄存器;
然后将eax寄存器中的5移到-0x8(ebp)即ebp的负地址8

0xbffff610处的3已经被替换为5
7.进入B函数之前

0xc(%ebp)从ebp高地址获取6并存储在ebp的低地址-4位置,然后放到eax寄存器

0xbffff614处的4已经被替换为6
8.进入B函数

按之前的第7步在进入B函数之前先把参数5和6从ebp的-4和-8地址上取出存在eax寄存器,然后存储到esp的正向地址上

esp和ebp存储新的内存地址位置

参数5和6依次保存在esp的正向地址中
8.B函数n=2之前

第2帧:

第1帧:

第一帧的ebp保存着:
main函数的ebp地址0xbffff638;
以及main函数需要继续执行的地址0x080483b1
第0帧:

这是当前B函数的帧以及对应的寄存器内容,其中1被存储在ebp的-8位置,和之前的A函数中的过程一样,没有新的差异。
9.B函数的m=i之前


10.B函数的n=j之前


11.B函数return之前


12.B函数返回值
leave 将ebp值赋给esp,
pop先前栈内的上级函数栈的基地址给ebp,
恢复原栈基址相当于:
movl %ebp,%esp
popl %ebp

返回值放在eax寄存器中

13.回到A函数

8赋给eax寄存器

14.A返回值,pop出main函数的ebp


15.回到main函数


16.退出main函数

17.进入__libc_start_main ()系统调用


18.结束

关于win32环境下的堆栈参考:Win32 环境下的堆栈
关于gdb查看栈参考: GDB查看栈信息
关于gdb调试可以参考:GDB调试--以汇编语言为例

本文详细介绍了如何在Linux环境下调试C程序,通过GDB工具设置断点、查看堆栈、跟踪变量及汇编代码。强调了在编译时添加-g选项以包含调试信息的重要性,并提供了查看数组、堆栈帧、寄存器状态等技巧。

690

被折叠的 条评论
为什么被折叠?



