GDB大法(四)

如何利用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/nfu

 

print/f

 

sp

 

it

分类: 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:
  1. #include <</span>stdlib.h>
  2. #include <</span>stdio.h>
  3. #define MACRO1(x) (++(x))
  4. #define MACRO2(x) (MACRO1(x)+100)
  5. #define MACRO3(x) (MACRO2(x)+200)
  6. int main(void)
  7. {
  8. int a = 0;
  9. int b = 0;
  10.     b = MACRO3(a);
  11.     printf("%d\n", b);
  12.     return 0;
  13. }

这里的MACRO3嵌套调用了MACRO2,MACRO1。使用gcc -E test.c > test.e,得到预编译后的代码:

  1. /*
  2. 前面是1800+行的头文件代码,此处省略 
  3. */
  4. int main(void)
  5. {
  6. int a = 0;
  7. int b = 0;
  8.     b = (((++(a))+100)+200);
  9.     printf("%d\n", b);
  10.     return 0;
  11. }

这里可以清晰的看到b = (((++(a))+100)+200);这个就比刚才的宏定义要清楚的多。但是从这个例子也可以看到这个方法的局限性。

1. 由于预编译处理会执行所有的预处理代码,包括头文件的插入,这导致最后的代码行数太多

2. 得到的了一个新的代码文件。这样的话,在大型工程中,如果需要调试多个文件中的宏定义,需要我们一个一个的预编译,太麻烦了。

 
2:使用gcc的-g3选项。-g是为了调试程序,它将调试信息加入到最后的二进制可执行文件中。当不指定级别的时候,level为2,为了调试宏定义,使用level 3,即g3。然后gdb调试的过程中使用macro expand/exp 来展开宏定义。比如:
  1. (gdb) macro exp MACRO3(3)
  2. 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)

转载

标签: 

返回值

 

eax

 

rax

 

struct

 

 

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. //示例1:返回值为char  
  2.  
  3. char f()
    {
            char a = 'a';
            return a;
    }
    int main()
    {
            char b = f();
            return 0;
    }
     
  4.  
  5. .file "char.c"
  6. .text
  7. .globl f
  8. f:
  9. pushl �p
  10. movl %esp, �p
  11. subl $16, %esp
  12. movb $97, -1(�p)  //我的显示的为 movb $0x61,-0x1(%rbp) with objdump or the same with gcc -S
  13. movsbl -1(�p),�x //符号扩展,我自个的显示为movzbl -0x1(%rbp),�x
  14. leave
  15. ret
  16. .globl main
  17. main:
  18. leal 4(%esp), �x
  19. andl $-16, %esp  
  20. pushl -4(�x)
  21. pushl �p
  22. movl %esp, �p
  23. pushl �x
  24. subl $16, %esp // sub $0x10,%rsp or subq $16,%rsp
  25. call f
  26. movb %al, -5(�p) //movb %al, -1(%rbp)
  27. movl $0, �x
  28. addl $16, %esp
  29. popl �x
  30. popl �p
  31. leal -4(�x), %esp
  32. ret

从汇编代码中可以看出,调用完f后,main函数从al中找返回值

同样,对于short,int,分别把返回值存放到ax,eax,假如在64位系统里,那么long long 返回值是存到rax的,它的长度为64位,在32位系统里是怎么存的呢?

在32位系统里返回64位数,是通过edx和eax联合实现的,edx存高32位,eax存低32位。

 

  1.  
  2.  
  3. long long f()  
  4. {  
  5.         long long a = 5;  
  6.         return a;  
  7. }  
  8. int main()  
  9. {  
  10.         long long b;  
  11.          b=f();  
  12.         return 0;  
  13. }  
  14.  
  15.        .file   "longint.c" 
  16.         .text  
  17. .globl f  
  18. f:  
  19.         pushl   �p  
  20.         movl    %esp, �p  
  21.         subl    $16, %esp  
  22.         movl    $5, -8(�p)  
  23.         movl    $0, -4(�p)  
  24.         movl    -8(�p), �x  
  25.         movl    -4(�p), �x  
  26.         leave  
  27.         ret  
  28. .globl main  
  29. main:  
  30.         leal    4(%esp), �x  
  31.         andl    $-16, %esp  
  32.         pushl   -4(�x)  
  33.         pushl   �p  
  34.         movl    %esp, �p  
  35.         pushl   �x  
  36.         subl    $20, %esp  
  37.         call    f  
  38.         movl    �x, -16(�p)  
  39.         movl    �x, -12(�p)  
  40.         movl    $0, �x  
  41.         addl    $20, %esp  
  42.         popl    �x  
  43.         popl    �p  
  44.         leal    -4(�x), %esp  
  45.         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中,也要再压到浮点数寄存器堆栈中,在主调函数中,会认为返回结果存到浮点数寄存器了,当然,如果你要手动优化汇编代码也是没问题的。

下面是示例。

 
  1.  
  2. float f()  
  3. {  
  4.         return 0.1;  
  5. }  
  6. int main()  
  7. {  
  8.         float a = f();  
  9.         return 0;  
  10. }  
  11.  
  12.         .file   "float.c"  
  13.         .text  
  14. .globl f  
  15. f:  
  16.         pushl   �p  
  17.         movl    %esp, �p  
  18.         subl    $4, %esp  
  19.         movl    $0x3dcccccd, �x  
  20.         movl    �x, -4(�p)  
  21.         flds    -4(�p)  //把结果压到浮点寄存器栈顶  //我的使用的是movss -0x4(%rbp),%xmm0
  22.         leave  
  23.         ret  
  24. .globl main  
  25. main:  
  26.         leal    4(%esp), �x  
  27.         andl    $-16, %esp  
  28.         pushl   -4(�x)  
  29.         pushl   �p  
  30.         movl    %esp, �p  
  31.         pushl   �x  
  32.         subl    $16, %esp  
  33.         call    f  
  34.         fstps   -8(�p) //从浮点寄存器栈顶取数  //我的使用的是movss %xmm0,-0x4(%rbp)
  35.         movl    $0, �x  
  36.         addl    $16, %esp  
  37.         popl    �x  
  38.         popl    �p  
  39.         leal    -4(�x), %esp  
  40.         ret 

关于浮点寄存器及浮点运算指令,可参考:http://www.diybl.com/course/3_program/hb/hbjs/2007124/89946.html

如果返回值为指针?那肯定是用eax(32bit)或者rax(64bit)了。不管是什么类型的指针,都一样,我们来看一个奇怪的程序。

 
  1.  
  2.  
  3. int f()  
  4. {  
  5.         return 5;  
  6. }  
  7. int (*whatisthis()) ()  //这个函数的返回类型是函数指针
  8. {  
  9.         return f;  
  10. }  
  11. int main()  
  12. {  
  13.         int (*a) ();  
  14.         int b;  
  15.         a = whatisthis();  
  16.         b = a();  
  17.         printf("%d\n",b);  
  18.         return 0;  
  19. }  
  20.  
  21.         .file   "ret_fun.c" 
  22.         .text  
  23. .globl f  
  24. f:  
  25.         pushl   �p  
  26.         movl    %esp, �p  
  27.         movl    $5, �x  
  28.         popl    �p  
  29.         ret  
  30.  
  31. .globl whatisthis  
  32. whatisthis:  
  33.         pushl   �p  
  34.         movl    %esp, �p  
  35.         movl    $f, �x  
  36.         popl    �p  
  37.         ret  
  38.  
  39. .LC0:  
  40.         .string "%d\n" 
  41.         .text  
  42.  
  43. .globl main  
  44. main:  
  45.         leal    4(%esp), �x  
  46.         andl    $-16, %esp  
  47.         pushl   -4(�x)  
  48.         pushl   �p  
  49.         movl    %esp, �p  
  50.         pushl   �x  
  51.         subl    $36, %esp  
  52.         call    whatisthis  
  53.         movl    �x, -12(�p)   //我的显示为movq %rax,-16(%rbp)
  54.         movl    -12(�p), �x  //我的显示movq -16(%rbp),%rdx
  55.         call    *�x            //我的显示 call *�x
  56.         movl    �x, -8(�p)   //把返回结果5 放入�x,从而main函数从�x读取返回值5到-8(%rbp)
  57.         movl    -8(�p), �x  
  58.         movl    �x, 4(%esp)  
  59.         movl    $.LC0, (%esp)  
  60.         call    printf  
  61.         movl    $0, �x  
  62.         addl    $36, %esp  
  63.         popl    �x  
  64.         popl    �p  
  65.         leal    -4(�x), %esp  
  66.         ret  

一个函数的返回值可以是函数指针,定义一个这样的函数如下:

函数1   int f(int,char)

函数2   返回值为上面函数的类型的指针,假如函数名为g,参数为float

那么g的定义为     int   (* g(float x)  )    (int,char)

基本类型讨论完了,那么struct类型呢?struct可大可小,怎么存到寄存器里呢?

答案是:主调函数会把被赋值对象的地址传给被调用函数。你可能会说这不是传引用吗,其实传引用传值什么的都是浮云。

还有一个问题就是,对于struct xxx { char a; };这样的结构也要传地址吗?答案是肯定的,gcc是这样做的,其它编译器可能不这样,当然也可以手动修改汇编代码。

 
  1.  
  2.  
  3. struct xxx{  
  4.         char a;  
  5. };  
  6. struct xxx  f()  
  7. {  
  8.         struct xxx x;  
  9.         x.a = '9';  
  10.         return x;  
  11. }  
  12. int main()  
  13. {  
  14.         struct xxx y = f();  
  15.         return 0;  
  16. }  
  17.  
  18.         .file   "struct_char.c" 
  19.         .text  
  20. .globl f  
  21. f:  
  22.         pushl   �p  
  23.         movl    %esp, �p  
  24.         subl    $16, %esp  
  25.         movl    8(�p), �x //取出地址,放入edx  
  26.         movb    $57, -1(�p)     // movb $0x39, -0x1(%rbp)
  27.         movzbl  -1(�p), �x //'9'放到 al  
  28.         movb    %al, (�x) //将al内容写到edx指向的地址 我的无此步骤
  29.         movl    �x, �x  
  30.         leave  
  31.         ret     $4  
  32.  
  33. .globl main  
  34. main:  
  35.         leal    4(%esp), �x  
  36.         andl    $-16, %esp  
  37.         pushl   -4(�x)  
  38.         pushl   �p  
  39.         movl    %esp, �p  
  40.         pushl   �x  
  41.         subl    $24, %esp  
  42.         leal    -21(�p), �x //地址放到eax  
  43.         movl    �x, (%esp) //地址压入栈中  
  44.         call    f   
  45.         subl    $4, %esp    //没有取返回值的指令了  
  46.         movzbl  -21(�p), �x//因为已经写到目的地址了  我的main中使用mov %al, -0x1(%rbp)来取得地址
  47.         movb    %al, -5(�p)  
  48.         movl    $0, �x  
  49.         movl    -4(�p), �x  
  50.         leave  
  51.         leal    -4(�x), %esp  
  52.         ret  

我们再来看个复杂点的例子

 
  1.  
  2.  
  3. struct xxx {  
  4.         char a[10];  
  5. };  
  6. struct xxx f(int a)  
  7. {  
  8.         struct xxx t;  
  9.         t.a[9] = 1;  
  10.         return t;  
  11. }  
  12. int main()  
  13. {  
  14.         struct xxx m=f(1);  
  15.         return 0;  
  16. }  
  17.  
  18.         .file   "struct.c" 
  19.         .text  
  20. .globl f  
  21. f:  
  22.         pushl   �p  
  23.         movl    %esp, �p  
  24.         subl    $16, %esp  
  25.         movl    8(�p), �x   //取地址 我的显示的是是movl �i, -36(%rbp) 读取传入参数,并复制到栈内
  26.         movb    $1, -1(�p)  
  27.         movl    -10(�p), �x  
  28.         movl    �x, (�x)  
  29.         movl    -6(�p), �x  
  30.         movl    �x, 4(�x)  
  31.         movzwl  -2(�p), �x  
  32.         movw    %ax, 8(�x)  
  33.         movl    �x, �x  
  34.         leave  
  35.         ret     $4  
  36.  
  37. .globl main  
  38. main:  
  39.         leal    4(%esp), �x  
  40.         andl    $-16, %esp  
  41.         pushl   -4(�x)  
  42.         pushl   �p  
  43.         movl    %esp, �p  
  44.         pushl   �x  
  45.         subl    $24, %esp  
  46.         leal    -14(�p), �x  
  47.         movl    $1, 4(%esp)      //先压入参数  我的显示 movl $1,�i
  48.         movl    �x, (%esp)     //再压入返回值地址  我的显示没有此项 
  49.         call    f  
  50.         subl    $4, %esp  
  51.         movl    $0, �x  
  52.         movl    -4(�p), �x  
  53.         leave  
  54.         leal    -4(�x), %esp  
  55.         ret  

进入被调用函数后的堆栈情况

参数2

参数1

返回值地址

旧EIP

旧EBP

它会到假定8(�p)处存放着返回值的地址。这也是为什么main的返回值为struct时会引起段错误,main函数认为这个地方存着返回值的地址,实际上这个地方是操作系统写入的特定值,把这个当作返回值的地址乱写,肯定会引起段错误。

下面这个程序

假如对于struct,有返回值的函数却不赋值怎么办?

比如

 
  1. struct xxx {  
  2.         char a[10];  
  3. };  
  4. struct xxx f(int a)  
  5. {  
  6.         struct xxx t;  
  7.         t.a[9] = 1;  
  8.         return t;  
  9. }  
  10. int main()  
  11. {  
  12.         f(1);  
  13.         return 0;  
  14. }  

对于上述程序,主调用函数需要开辟垃圾空间作为返回值空间,感兴趣的可以验证下看看。

如下贴出的是我机器上上述代码的汇编部分

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强制转换成浮点数。

下面代码可正常运行:

 
  1. int main()  
  2. {  
  3.         int a;  
  4.         long long a1;  
  5.         double a2;  
  6.         a  = {int b = 5; printf("xxx\n");;};  
  7.         a1  = {int b = 5;int c = 2; 3-4;b-c;};  
  8.         a2  = {int b = 5;int c = 2; 10-8;};  
  9.         printf("%d\n",a);  
  10.         printf("%ld\n",a1);  
  11.         printf("%lf\n",a2);  
  12.         return 0;  
  13. }  

这个有点商榷,目前我在本机上无法编译成功。三行的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值