天天看點

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大法(四)

轉載▼

标簽: 

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. int main(void)
  2. {
  3. int a = 0;
  4. int b = 0;
  5.     b = (((++(a))+100)+200);
  6.     printf("%d\n", b);
  7.     return 0;
  8. }

這裡可以清晰的看到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)

GDB大法(四)

轉載▼

标簽: 

傳回值

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 $?

可以看到,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. char f()

    {

            char a = 'a';

            return a;

    }

    int main()

    {

            char b = f();

            return 0;

    }

  3. .file "char.c"
  4. .text
  5. .globl f
  6. f:
  7. pushl �p
  8. movl %esp, �p
  9. subl $16, %esp
  10. movb $97, -1(�p)  //我的顯示的為 movb $0x61,-0x1(%rbp) with objdump or the same with gcc -S
  11. movsbl -1(�p),�x //符号擴充,我自個的顯示為movzbl -0x1(%rbp),�x
  12. leave
  13. ret
  14. .globl main
  15. main:
  16. leal 4(%esp), �x
  17. andl $-16, %esp  
  18. pushl -4(�x)
  19. pushl �p
  20. movl %esp, �p
  21. pushl �x
  22. subl $16, %esp // sub $0x10,%rsp or subq $16,%rsp
  23. call f
  24. movb %al, -5(�p) //movb %al, -1(%rbp)
  25. movl $0, �x
  26. addl $16, %esp
  27. popl �x
  28. popl �p
  29. leal -4(�x), %esp
  30. ret

從彙編代碼中可以看出,調用完f後,main函數從al中找傳回值。

同樣,對于short,int,分别把傳回值存放到ax,eax,假如在64位系統裡,那麼long long 傳回值是存到rax的,它的長度為64位,在32位系統裡是怎麼存的呢?

在32位系統裡傳回64位數,是通過edx和eax聯合實作的,edx存高32位,eax存低32位。

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

關于浮點寄存器及浮點運算指令,可參考:http://www.diybl.com/course/3_program/hb/hbjs/2007124/89946.html

如果傳回值為指針?那肯定是用eax(32bit)或者rax(64bit)了。不管是什麼類型的指針,都一樣,我們來看一個奇怪的程式。

  1. int f()  
  2. {  
  3.         return 5;  
  4. }  
  5. int (*whatisthis()) ()  //這個函數的傳回類型是函數指針
  6. {  
  7.         return f;  
  8. }  
  9. int main()  
  10. {  
  11.         int (*a) ();  
  12.         int b;  
  13.         a = whatisthis();  
  14.         b = a();  
  15.         printf("%d\n",b);  
  16.         return 0;  
  17. }  
  18.         .file   "ret_fun.c" 
  19.         .text  
  20. .globl f  
  21. f:  
  22.         pushl   �p  
  23.         movl    %esp, �p  
  24.         movl    $5, �x  
  25.         popl    �p  
  26.         ret  
  27. .globl whatisthis  
  28. whatisthis:  
  29.         pushl   �p  
  30.         movl    %esp, �p  
  31.         movl    $f, �x  
  32.         popl    �p  
  33.         ret  
  34. .LC0:  
  35.         .string "%d\n" 
  36.         .text  
  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    $36, %esp  
  46.         call    whatisthis  
  47.         movl    �x, -12(�p)   //我的顯示為movq %rax,-16(%rbp)
  48.         movl    -12(�p), �x  //我的顯示movq -16(%rbp),%rdx
  49.         call    *�x            //我的顯示 call *�x
  50.         movl    �x, -8(�p)   //把傳回結果5 放入�x,進而main函數從�x讀取傳回值5到-8(%rbp)
  51.         movl    -8(�p), �x  
  52.         movl    �x, 4(%esp)  
  53.         movl    $.LC0, (%esp)  
  54.         call    printf  
  55.         movl    $0, �x  
  56.         addl    $36, %esp  
  57.         popl    �x  
  58.         popl    �p  
  59.         leal    -4(�x), %esp  
  60.         ret  

一個函數的傳回值可以是函數指針,定義一個這樣的函數如下:

函數1   int f(int,char)

函數2   傳回值為上面函數的類型的指針,假如函數名為g,參數為float

那麼g的定義為     int   (* g(float x)  )    (int,char)

基本類型讨論完了,那麼struct類型呢?struct可大可小,怎麼存到寄存器裡呢?

答案是:主調函數會把被指派對象的位址傳給被調用函數。你可能會說這不是傳引用嗎,其實傳引用傳值什麼的都是浮雲。

還有一個問題就是,對于struct xxx { char a; };這樣的結構也要傳位址嗎?答案是肯定的,gcc是這樣做的,其它編譯器可能不這樣,當然也可以手動修改彙編代碼。

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

我們再來看個複雜點的例子

  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.         struct xxx m=f(1);  
  13.         return 0;  
  14. }  
  15.         .file   "struct.c" 
  16.         .text  
  17. .globl f  
  18. f:  
  19.         pushl   �p  
  20.         movl    %esp, �p  
  21.         subl    $16, %esp  
  22.         movl    8(�p), �x   //取位址 我的顯示的是是movl �i, -36(%rbp) 讀取傳入參數,并複制到棧内
  23.         movb    $1, -1(�p)  
  24.         movl    -10(�p), �x  
  25.         movl    �x, (�x)  
  26.         movl    -6(�p), �x  
  27.         movl    �x, 4(�x)  
  28.         movzwl  -2(�p), �x  
  29.         movw    %ax, 8(�x)  
  30.         movl    �x, �x  
  31.         leave  
  32.         ret     $4  
  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    -14(�p), �x  
  43.         movl    $1, 4(%esp)      //先壓入參數  我的顯示 movl $1,�i
  44.         movl    �x, (%esp)     //再壓入傳回值位址  我的顯示沒有此項 
  45.         call    f  
  46.         subl    $4, %esp  
  47.         movl    $0, �x  
  48.         movl    -4(�p), �x  
  49.         leave  
  50.         leal    -4(�x), %esp  
  51.         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

gdb

繼續閱讀