如何利用gdb调试程序之细节(info reg命令以及寄存器地址) //经典;
https://blog.csdn.net/u010535088/article/details/12191401
gdb查看内存地址和栈中的值
http://blog.sina.com.cn/s/blog_605f5b4f0101ey1q.html
gdb查看内存地址和栈中的值
(2014-05-30 22:17:42)
标签: gdb内存地址单元x/nfuprint/fspit | 分类: C |
gdb查看指定地址的内存地址的值:examine 简写 x-----使用gdb> help x 来查看使用方式
x/ (n,f,u为可选参数)
n: 需要显示的内存单元个数,也就是从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义
f:显示格式
x(hex) 按十六进制格式显示变量。
d(decimal) 按十进制格式显示变量。
u(unsigned decimal) 按十进制格式显示无符号整型。
o(octal) 按八进制格式显示变量。
t(binary) 按二进制格式显示变量。
a(address) 按十六进制格式显示变量。
c(char) 按字符格式显示变量。
f(float) 按浮点数格式显示变量
u:每个单元的大小,按字节数来计算。默认是4 bytes。GDB会从指定内存地址开始读取指定字节,并把其当作一个值取出来,并使用格式f来显示
b:1 byte h:2 bytes w:4 bytes g:8 bytes
比如x/3uh 0x54320表示从内存地址0x54320读取内容,h表示以双字节为单位,3表示输出3个单位,u表示按照十六进制显示。
from http://www.cnblogs.com/super119/archive/2011/03/26/1996125.html
gdb打印表达式的值:print/f 表达式
f是输出的格式,x/d/u/o/t/a/c/f
表达式可以是当前程序的const常量,变量,函数等内容,但是GDB不能使用程序中所定义的宏
查看当前程序栈的内容: x/10x $sp-->打印stack的前10个元素
查看当前程序栈的信息: info frame----list general info about the frame
查看当前程序栈的参数: info args---lists arguments to the function
查看当前程序栈的局部变量: info locals---list variables stored in the frame
查看当前寄存器的值:info registers(不包括浮点寄存器) info all-registers(包括浮点寄存器)
查看当前栈帧中的异常处理器:info catch(exception handlers)
from http://blog.chinaunix.net/uid-29062294-id-4255572.html
调试宏定义
http://blog.sina.com.cn/s/blog_605f5b4f0101ey1p.html
调试宏定义的方法:
1:通过gcc -E 产生预编译后的源代码,所有的预编译动作都已完成,如头文件的插入,宏定义的展开
example:
- #include <</span>stdlib.h>
- #include <</span>stdio.h>
- #define MACRO1(x) (++(x))
- #define MACRO2(x) (MACRO1(x)+100)
- #define MACRO3(x) (MACRO2(x)+200)
- int main(void)
- {
- int a = 0;
- int b = 0;
- b = MACRO3(a);
- printf("%d\n", b);
- return 0;
- }
这里的MACRO3嵌套调用了MACRO2,MACRO1。使用gcc -E test.c > test.e,得到预编译后的代码:
- /*
- 前面是1800+行的头文件代码,此处省略
- */
- int main(void)
- {
- int a = 0;
- int b = 0;
- b = (((++(a))+100)+200);
- printf("%d\n", b);
- return 0;
- }
这里可以清晰的看到b = (((++(a))+100)+200);这个就比刚才的宏定义要清楚的多。但是从这个例子也可以看到这个方法的局限性。
1. 由于预编译处理会执行所有的预处理代码,包括头文件的插入,这导致最后的代码行数太多。
2. 得到的了一个新的代码文件。这样的话,在大型工程中,如果需要调试多个文件中的宏定义,需要我们一个一个的预编译,太麻烦了。
2:使用gcc的-g3选项。-g是为了调试程序,它将调试信息加入到最后的二进制可执行文件中。当不指定级别的时候,level为2,为了调试宏定义,使用level 3,即g3。然后gdb调试的过程中使用macro expand/exp 来展开宏定义。比如:
- (gdb) macro exp MACRO3(3)
- expands to: (((++(3))+100)+200)
from http://blog.chinaunix.net/uid-29062294-id-4255572.html
函数返回值的存放地址
http://blog.sina.com.cn/s/blog_605f5b4f0101ey3a.html
(2014-05-30 22:21:13)
标签: 返回值eaxraxstruct栈it | 分类: C |
from http://nxlhero.blog.51cto.com/962631/703953
在操作系统中(以linux为例),每个程序都需要有一个返回值,返回给操作系统.
在shell中,可以利用echo $?查看程序的返回值
比如:
$ ls not_exist
ls: not_exist: No such file or directory
$ echo $?
2
$ ls main.c
main.c
$ echo $?
0
可以看到,not_exist不存在,返回2,main.c存在,返回0,一般返回0表示成功,而返回非0表示失败或者其他意义。
其实这个返回值是存放在eax中的,c规范要求main必须返回int,而int和eax长度是一致的(32位系统)。
$ cat ret_test.s
.globl main
main:
pushq %rbp
movl $4,�x
popq %rbp
ret
$ make ret_test
cc ret_test.s -o ret_test
$ ./ret_test
$ echo $
4
这个汇编程序只有一条指令,将4存到eax,检测返回值发现是4。
如果你的程序用void main(),有的编译器会报错,有的会警告,如果编译过了,运行时一般没问题。
$ cat test.c
int f()
{
return 100;
}
void main()
{
f();
}
$ make test
cc test.c -o test
$ ./test
$ echo $?
100
函数f把返回值放到eax了,main函数什么都没做,所以返回值还是100。
但是我们来看另外一个例子cat haha.c
struct xxx{
int a[50];
};
struct xxx main()
{
struct xxx haha;
return haha;
}
$make haha
cc haha.c -o haha
$ ./haha
Segmentation fault (core dumped)
$ echo $?
139
为什么会出现段错误?我们后面会研究它。
我们先把返回值进行分类:
首先是基本类型,void,char,short,long,long long,float,double,指针
然后是结构类型struct。
对于void类型,没有返回值,不做讨论。
char只有1个字节,eax有4个字节,怎么存?只用低8位al就可以了。下面是示例
- //示例1:返回值为char
- char f()
{
char a = 'a';
return a;
}
int main()
{
char b = f();
return 0;
}
- .file "char.c"
- .text
- .globl f
- f:
- pushl �p
- movl %esp, �p
- subl $16, %esp
- movb $97, -1(�p) //我的显示的为 movb $0x61,-0x1(%rbp) with objdump or the same with gcc -S
- movsbl -1(�p),�x //符号扩展,我自个的显示为movzbl -0x1(%rbp),�x
- leave
- ret
- .globl main
- main:
- leal 4(%esp), �x
- andl $-16, %esp
- pushl -4(�x)
- pushl �p
- movl %esp, �p
- pushl �x
- subl $16, %esp // sub $0x10,%rsp or subq $16,%rsp
- call f
- movb %al, -5(�p) //movb %al, -1(%rbp)
- movl $0, �x
- addl $16, %esp
- popl �x
- popl �p
- leal -4(�x), %esp
- ret
从汇编代码中可以看出,调用完f后,main函数从al中找返回值。
同样,对于short,int,分别把返回值存放到ax,eax,假如在64位系统里,那么long long 返回值是存到rax的,它的长度为64位,在32位系统里是怎么存的呢?
在32位系统里返回64位数,是通过edx和eax联合实现的,edx存高32位,eax存低32位。
- long long f()
- {
- long long a = 5;
- return a;
- }
- int main()
- {
- long long b;
- b=f();
- return 0;
- }
- .file "longint.c"
- .text
- .globl f
- f:
- pushl �p
- movl %esp, �p
- subl $16, %esp
- movl $5, -8(�p)
- movl $0, -4(�p)
- movl -8(�p), �x
- movl -4(�p), �x
- leave
- ret
- .globl main
- main:
- leal 4(%esp), �x
- andl $-16, %esp
- pushl -4(�x)
- pushl �p
- movl %esp, �p
- pushl �x
- subl $20, %esp
- call f
- movl �x, -16(�p)
- movl �x, -12(�p)
- movl $0, �x
- addl $20, %esp
- popl �x
- popl �p
- leal -4(�x), %esp
- ret
对于64位的机器来说:
f函数中 movl -8(�p),�x movl -4(�p),�x 变成了mov -0x8(%rbp),%rax
而main函数取返回值的 movl �x,-16(�p) movl �x,-12(�p) 变成了mov %rax -0x8(%rbp)
对于浮点类型,虽然运算过程中会存放在eax等普通寄存器中,但是作为返回值时,不会用eax,edx等,即使运算结果已经存到了eax中,也要再压到浮点数寄存器堆栈中,在主调函数中,会认为返回结果存到浮点数寄存器了,当然,如果你要手动优化汇编代码也是没问题的。
下面是示例。
- float f()
- {
- return 0.1;
- }
- int main()
- {
- float a = f();
- return 0;
- }
- .file "float.c"
- .text
- .globl f
- f:
- pushl �p
- movl %esp, �p
- subl $4, %esp
- movl $0x3dcccccd, �x
- movl �x, -4(�p)
- flds -4(�p) //把结果压到浮点寄存器栈顶 //我的使用的是movss -0x4(%rbp),%xmm0
- leave
- ret
- .globl main
- main:
- leal 4(%esp), �x
- andl $-16, %esp
- pushl -4(�x)
- pushl �p
- movl %esp, �p
- pushl �x
- subl $16, %esp
- call f
- fstps -8(�p) //从浮点寄存器栈顶取数 //我的使用的是movss %xmm0,-0x4(%rbp)
- movl $0, �x
- addl $16, %esp
- popl �x
- popl �p
- leal -4(�x), %esp
- ret
关于浮点寄存器及浮点运算指令,可参考:http://www.diybl.com/course/3_program/hb/hbjs/2007124/89946.html
如果返回值为指针?那肯定是用eax(32bit)或者rax(64bit)了。不管是什么类型的指针,都一样,我们来看一个奇怪的程序。
- int f()
- {
- return 5;
- }
- int (*whatisthis()) () //这个函数的返回类型是函数指针
- {
- return f;
- }
- int main()
- {
- int (*a) ();
- int b;
- a = whatisthis();
- b = a();
- printf("%d\n",b);
- return 0;
- }
- .file "ret_fun.c"
- .text
- .globl f
- f:
- pushl �p
- movl %esp, �p
- movl $5, �x
- popl �p
- ret
- .globl whatisthis
- whatisthis:
- pushl �p
- movl %esp, �p
- movl $f, �x
- popl �p
- ret
- .LC0:
- .string "%d\n"
- .text
- .globl main
- main:
- leal 4(%esp), �x
- andl $-16, %esp
- pushl -4(�x)
- pushl �p
- movl %esp, �p
- pushl �x
- subl $36, %esp
- call whatisthis
- movl �x, -12(�p) //我的显示为movq %rax,-16(%rbp)
- movl -12(�p), �x //我的显示movq -16(%rbp),%rdx
- call *�x //我的显示 call *�x
- movl �x, -8(�p) //把返回结果5 放入�x,从而main函数从�x读取返回值5到-8(%rbp)
- movl -8(�p), �x
- movl �x, 4(%esp)
- movl $.LC0, (%esp)
- call printf
- movl $0, �x
- addl $36, %esp
- popl �x
- popl �p
- leal -4(�x), %esp
- ret
一个函数的返回值可以是函数指针,定义一个这样的函数如下:
函数1 int f(int,char)
函数2 返回值为上面函数的类型的指针,假如函数名为g,参数为float
那么g的定义为 int (* g(float x) ) (int,char)
基本类型讨论完了,那么struct类型呢?struct可大可小,怎么存到寄存器里呢?
答案是:主调函数会把被赋值对象的地址传给被调用函数。你可能会说这不是传引用吗,其实传引用传值什么的都是浮云。
还有一个问题就是,对于struct xxx { char a; };这样的结构也要传地址吗?答案是肯定的,gcc是这样做的,其它编译器可能不这样,当然也可以手动修改汇编代码。
- struct xxx{
- char a;
- };
- struct xxx f()
- {
- struct xxx x;
- x.a = '9';
- return x;
- }
- int main()
- {
- struct xxx y = f();
- return 0;
- }
- .file "struct_char.c"
- .text
- .globl f
- f:
- pushl �p
- movl %esp, �p
- subl $16, %esp
- movl 8(�p), �x //取出地址,放入edx
- movb $57, -1(�p) // movb $0x39, -0x1(%rbp)
- movzbl -1(�p), �x //'9'放到 al
- movb %al, (�x) //将al内容写到edx指向的地址 我的无此步骤
- movl �x, �x
- leave
- ret $4
- .globl main
- main:
- leal 4(%esp), �x
- andl $-16, %esp
- pushl -4(�x)
- pushl �p
- movl %esp, �p
- pushl �x
- subl $24, %esp
- leal -21(�p), �x //地址放到eax
- movl �x, (%esp) //地址压入栈中
- call f
- subl $4, %esp //没有取返回值的指令了
- movzbl -21(�p), �x//因为已经写到目的地址了 我的main中使用mov %al, -0x1(%rbp)来取得地址
- movb %al, -5(�p)
- movl $0, �x
- movl -4(�p), �x
- leave
- leal -4(�x), %esp
- ret
我们再来看个复杂点的例子
- struct xxx {
- char a[10];
- };
- struct xxx f(int a)
- {
- struct xxx t;
- t.a[9] = 1;
- return t;
- }
- int main()
- {
- struct xxx m=f(1);
- return 0;
- }
- .file "struct.c"
- .text
- .globl f
- f:
- pushl �p
- movl %esp, �p
- subl $16, %esp
- movl 8(�p), �x //取地址 我的显示的是是movl �i, -36(%rbp) 读取传入参数,并复制到栈内
- movb $1, -1(�p)
- movl -10(�p), �x
- movl �x, (�x)
- movl -6(�p), �x
- movl �x, 4(�x)
- movzwl -2(�p), �x
- movw %ax, 8(�x)
- movl �x, �x
- leave
- ret $4
- .globl main
- main:
- leal 4(%esp), �x
- andl $-16, %esp
- pushl -4(�x)
- pushl �p
- movl %esp, �p
- pushl �x
- subl $24, %esp
- leal -14(�p), �x
- movl $1, 4(%esp) //先压入参数 我的显示 movl $1,�i
- movl �x, (%esp) //再压入返回值地址 我的显示没有此项
- call f
- subl $4, %esp
- movl $0, �x
- movl -4(�p), �x
- leave
- leal -4(�x), %esp
- ret
进入被调用函数后的堆栈情况
参数2
参数1
返回值地址
旧EIP
旧EBP
它会到假定8(�p)处存放着返回值的地址。这也是为什么main的返回值为struct时会引起段错误,main函数认为这个地方存着返回值的地址,实际上这个地方是操作系统写入的特定值,把这个当作返回值的地址乱写,肯定会引起段错误。
下面这个程序
假如对于struct,有返回值的函数却不赋值怎么办?
比如
- struct xxx {
- char a[10];
- };
- struct xxx f(int a)
- {
- struct xxx t;
- t.a[9] = 1;
- return t;
- }
- int main()
- {
- f(1);
- return 0;
- }
对于上述程序,主调用函数需要开辟垃圾空间作为返回值空间,感兴趣的可以验证下看看。
如下贴出的是我机器上上述代码的汇编部分
f:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl �i, -36(%rbp)
movb $1, -23(%rbp)
movq -32(%rbp), %rax
movq %rax, -16(%rbp)
movzwl -24(%rbp), �x
movw %ax, -8(%rbp)
movq -16(%rbp), %rax //f函数依旧返回地址给%rax
movzwl -8(%rbp), �x
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size f, .-f
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $1, �i
call f
movl $0, �x //main 函数对于抛弃的返回值不做处理而直接将�x清零。
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",@progbits
补充:
gcc支持代码块有返回值
比如a = { int b = 2; int c = 3; c-b;} 最终a = 1;
根据我的测试:代码块里必须有除了变量声明的其他语句,否则不对,不能有return;
另外,只能对基本类型赋值,struct类型不能赋值。
最后的结果是:代码块执行结束后,取出eax的值,检查要赋值的变量类型,如果是char,取al,如果是int,取eax,如果是long long,符号扩展,如果是float或者double,将eax强制转换成浮点数。
下面代码可正常运行:
- int main()
- {
- int a;
- long long a1;
- double a2;
- a = {int b = 5; printf("xxx\n");;};
- a1 = {int b = 5;int c = 2; 3-4;b-c;};
- a2 = {int b = 5;int c = 2; 10-8;};
- printf("%d\n",a);
- printf("%ld\n",a1);
- printf("%lf\n",a2);
- return 0;
- }
这个有点商榷,目前我在本机上无法编译成功。三行的block赋值语句都报错:
retblock.c:9:4: error: expected expression before ‘{’ token
retblock.c:10:5: error: expected expression before ‘{’ token
retblock.c:11:5: error: expected expression before ‘{’ token