日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

gdb相关(栈和寄存器)

發(fā)布時間:2023/11/27 生活经验 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 gdb相关(栈和寄存器) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

GDB的常用調試命令大家可以查閱gdb手冊就可以快速的上手了,在這兒就不給大家分享了,需要的可以到GDB的官網去下載手冊。這里重點分享下GDB調試中的一些寄存器和棧的相關知識用于解決下列gdb調試時的問題:

  1. 優(yōu)化的代碼在printf或其它glibc函數處core
  2. 沒有檢查返回值的函數調用異常導致的異常
  3. 優(yōu)化的代碼的計算異常的中間過程分析
  4. 棧溢出導致的core
  5. 局部變量越界導致棧異常的core

寄存器

通常調試的代碼基本上都是在未開啟優(yōu)化的情況下,各個變量都可以直接查看,因此造成很多人調試時基本上不會看寄存器,但是對于線上的生產環(huán)境,可能會因為性能的因素,需要打開代碼優(yōu)化,此時出現異常需要調試時就通常需要查看寄存器了,下列是gdb調試中需要了解的寄存器。

  • $rip 指令寄存器,指向當前執(zhí)行的代碼位置
  • $rsp 棧指針寄存器,指向當前棧頂
  • $rax,$rbx,$rcx,$rdx,$rsi,$rdi,$rbp,$r8,$r9,$r10,$r11,$r12,$r13,$r14,$r15 通用寄存器
  • 函數入參

    一般linux下會優(yōu)先將參數壓到寄存器中,只有當寄存器不夠所有的參數時,才會將入參壓到棧上,一般入參的壓棧順序為$rdi、$rsi、$rdx、$rcx、$r8、$r9,如下
     
    1. int arg_int(int a, int b, int c,

    2. int d, int e, int f,

    3. int g, int h, int i)

    4. {

    5. return (a + b + c + d + e + f + g + h + i);

    6. }

    arg_int入參
     
    1. Breakpoint 1, arg_int (a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9) at ./reg.cpp:7

    2. ......

    3. (gdb) i r

    4. rax 0x7ffff7639f60 140737343889248

    5. rbx 0x14 20

    6. rcx 0x4 4

    7. rdx 0x3 3

    8. rsi 0x2 2

    9. rdi 0x1 1

    10. rbp 0xa 0xa

    11. rsp 0x7fffffffe4e8 0x7fffffffe4e8

    12. r8 0x5 5

    13. r9 0x6 6

    14. r10 0x7fffffffe3b0 140737488348080

    15. r11 0x7ffff72cbbe0 140737340292064

    16. r12 0x1e 30

    17. r13 0x28 40

    18. r14 0x32 50

    19. r15 0x3c 60

    20. rip 0x4005e4 0x4005e4 <arg_int(int, int, int, int, int, int, int, int, int)>

    第7、8、9個入參將會被壓到棧上,具體的棧上的位置信息大家可以自己研究。
    了解了入參時的寄存器知識,也該學以致用了,最典型的需要使用寄存器查看函數入參的場景是glibc的函數,在此以printf函數作為示例。printf最一類典型的core場景,通常是因為指定的格式和實際的類型不符合造成的,但是如果沒有源碼的話,即使斷點抓住了也不知道具體是哪個參數的格式不對,但是通過查看寄存器就可以調試此類問題了
     
    1. int along[9] = { 10, 20, 30, 40, 50, 60, 70, 80, 90 };

    2. printf("arg_long = %ld\n",

    3. arg_long(along[0], along[1], along[2],

    4. along[3], along[4], along[5],

    5. along[6], along[7], along[8]));

    斷點printf時寄存器信息
     
    1. Breakpoint 3, 0x00007ffff72fb990 in printf () from /lib64/libc.so.6

    2. (gdb) i r

    3. rax 0x0 0

    4. rbx 0x7fffffffe648 140737488348744

    5. rcx 0x4 4

    6. rdx 0x15 21

    7. rsi 0x1ef 495

    8. rdi 0x400858 4196440

    9. rbp 0x0 0x0

    10. rsp 0x7fffffffe538 0x7fffffffe538

    11. r8 0x5 5

    12. r9 0x6 6

    13. r10 0x7fffffffe2c0 140737488347840

    14. r11 0x7ffff72fb990 140737340488080

    15. r12 0x400500 4195584

    16. r13 0x7fffffffe640 140737488348736

    17. r14 0x0 0

    18. r15 0x0 0

    19. rip 0x7ffff72fb990 0x7ffff72fb990 <printf>

    大家可以根據上面的知識來查看寄存器知道具體的輸出信息,$rdi是format,先查看format后就可以按順序來查看后面的參數是否正確了
     
    1. (gdb) p/s (char*)$rdi

    2. $1 = 0x400858 "arg_long = %ld\n"

    3. (gdb) p/d $rsi

    4. $2 = 495


  • 函數返回值

    函數返回值由$rax保存返回,單步執(zhí)行上面的用例,printf的返回值如下:
     
    1. arg_long = 495

    2. ......

    3. (gdb) p/d $rax

    4. $1 = 15

    打印的內容為”arg_long = 495\n”,一共15個字節(jié)
    返回值查看的具體使用場景比較廣,例如glibc、外部庫等各種看不到源碼的函數調用的結果都可以通過$rax查看返回值,這兒就不舉例了,大家可以自己驗證下

  • 函數運行中

    對于開啟了優(yōu)化的場景下,局部變量往往會僅在寄存器中存儲,如果需要查看被優(yōu)化了的局部變量的值,如下:
     
    1. for (int i = 0 ; i < end ; ++i)

    2. {

    3. total += i;

    4. }

    執(zhí)行N次后的斷點信息如下:
     
    1. (gdb)

    2. 34 total += i;

    3. (gdb) p i

    4. $3 = <value optimized out>

    5. (gdb) p total

    6. $4 = <value optimized out>

    直接打印i、total都顯示被優(yōu)化了,因此無法直接打印其值,通過打印當前寄存器信息得到了寄存器中的值,然后通過查看匯編和源碼對比
     
    1. 0x0000000000400735 <+120>: jle 0x400740 <main(int, char**)+131>

    2. => 0x0000000000400737 <+122>: add %edx,%esi

    3. 0x0000000000400739 <+124>: add $0x1,%edx

    4. 0x000000000040073c <+127>: cmp %eax,%edx

    5. 0x000000000040073e <+129>: jl 0x400737 <main(int, char**)+122>

    通過匯編可以知道,i對應為寄存器$rdx,total對應為寄存器$rsi,end對應為寄存器$rax
     
    1. (gdb) i r

    2. rax 0x5 5

    3. rbx 0x7fffffffe648 140737488348744

    4. rcx 0x5 5

    5. rdx 0x2 2

    6. rsi 0x1 1

    7. rdi 0x7fffffffe8a6 140737488349350

    8. rbp 0x0 0x0

    9. rsp 0x7fffffffe540 0x7fffffffe540

    10. r8 0x7ffff7637580 140737343878528

    11. r9 0x7ffff73eb9e0 140737341471200

    12. r10 0x5 5

    13. r11 0x1999999999999999 1844674407370955161

    14. r12 0x400500 4195584

    15. r13 0x7fffffffe640 140737488348736

    16. r14 0x0 0

    17. r15 0x0 0

    18. rip 0x400737 0x400737 <main(int, char**)+122>

    也就是此時i為2、total為1,total += i;執(zhí)行完后$rsi應該會變?yōu)?,這點大家可以嘗試驗證下。

在gdb調試棧錯誤前,你需要了解下列的棧知識

  • 函數調用跳轉時在新幀的棧首8Bytes存放上一幀的指令地址
  • 通常函數的起始操作為push $rbp,將上幀的棧底地址8Bytes壓入棧中
  • 在保存完指令地址和棧底地址后,會進行一次sub xxx,$rsp,為當前函數內所有在棧上的局部變量都申請好需要的??臻g
  • 函數調用前將需要保存的寄存器值和超過6個的參數都壓入棧中

棧異常導致的core是線上最常見的core原因之一,常見原因有:

  • 遞歸調用或大變量消耗??臻g,導致棧溢出

    這類問題往往通過gdb查看?;拘畔⒕涂梢远ㄎ唤鉀Q,話不多說,直接上實戰(zhàn)
     
    1. (gdb) bt

    2. #0 0x00007ffff72fb990 in printf () from /lib64/libc.so.6

    3. #1 0x000000000040069f in f1 (ac1=0x7fffffffe360, ac2=0x7fffffffe230) at ./stack.cpp:9

    4. #2 0x00000000004006f9 in f2 (ac=0x7fffffffe360) at ./stack.cpp:18

    5. #3 0x000000000040074a in main (argc=1, argv=0x7fffffffe658) at ./stack.cpp:27

    6. (gdb) info frame 0

    7. Stack frame at 0x7fffffffe1d0:

    8. Locals at 0x7fffffffe1c0, Previous frame's sp is 0x7fffffffe1d0

    9. (gdb) info frame 1

    10. Stack frame at 0x7fffffffe220:

    11. (gdb) info frame 2

    12. Stack frame at 0x7fffffffe350:

    13. (gdb) info frame 3

    14. Stack frame at 0x7fffffffe580:

    Stack frame at xxxx的意義為當前幀的用戶棧起始地址
    frame 0的當前棧使用為:0x7fffffffe1d0 - 0x7fffffffe1c0 = 16 Bytes
    frame 1的當前棧使用為:0x7fffffffe220 - 0x7fffffffe1d0 = 80 Bytes
    frame 2的當前棧使用為:0x7fffffffe350 - 0x7fffffffe220 = 304 Bytes
    frame 3的當前棧使用為:0x7fffffffe580 - 0x7fffffffe350 = 560 Bytes
    當前棧使用為:0x7fffffffe580 - 0x7fffffffe1c0
    由此類推,在遇到懷疑棧溢出的時候就可以根據整體棧使用和各幀的棧使用來快速定位出具體是哪層棧造成的溢出。

  • 棧上變量的越界訪問導致棧內的數據被改寫

    這類問題往往gdb查看時會得到一個錯亂的棧信息
     
    1. Program received signal SIGSEGV, Segmentation fault.

    2. 0x00000000000b0000 in ?? ()

    3. (gdb) bt

    4. #0 0x00000000000b0000 in ?? ()

    5. #1 0x00000000000c0000 in ?? ()

    6. #2 0x00000000000d0000 in ?? ()

    7. #3 0x00000000000e0000 in ?? ()

    8. #4 0x00000000000f0000 in ?? ()

    9. #5 0x0000000000000002 in ?? ()

    10. #6 0x0000000000000003 in ?? ()

    11. ......

    這種情況下通常是棧上前面幀的指令地址被修改導致的,可能剛好被修改的是上一幀的指令地址,也可能修改的是幾幀前的指令地址。通常這類問題定位起來非常麻煩,根據$rsp不一定能還原棧信息,定位起來時往往需要大量的猜測和驗證。
    下面的示例是在踩了棧后沒有其它的函數調用重新寫棧信息
     
    1. ### gdb 查找宏

    2. define find

    3. set $ptr = $arg0

    4. set $cnt = 0

    5. while ( ($ptr<=$arg1) && ($cnt<$arg3) )

    6. if ( *(unsigned long *)$ptr == $arg2 )

    7. x/gx $ptr

    8. set $cnt = $cnt + 1

    9. end

    10. set $ptr = $ptr + 8

    11. end

    12. end

     
    1. (gdb) p $rsp

    2. $2 = (void *) 0x7fffffffe4d0

    3. (gdb) find 0x7fffffffe000 0x7fffffffe4d0 0x7fffffffe4c0 3

    4. 0x7fffffffe370: 0x00007fffffffe4c0

    5. 0x7fffffffe450: 0x00007fffffffe4c0

    6. (gdb) x/2gx 0x7fffffffe370

    7. 0x7fffffffe370: 0x00007fffffffe4c0 0x00007ffff72fba2a

    8. (gdb) x/2gx 0x7fffffffe450

    9. 0x7fffffffe450: 0x00007fffffffe4c0 0x000000000040066c

     
    1. addr2line -e ./stack 0x000000000040066c

    2. /data/lambygao/test/./stack.cpp:20

    示例中的find為定義的gdb宏,用于在棧上查找指定的地址值,$rsp為0x7fffffffe4d0,則該幀對應的$rpb為$rsp-16=0x7fffffffe4c0,在棧上查找該值后得到2個地址,分別查看按該地址為幀頭來測試,找到了懷疑的附近地址/data/lambygao/test/./stack.cpp:20
    當然如果是必現的core的話,也可以提前抓好棧的信息,然后設置好watch,這點大家可以自己嘗試下。

補充:

find 0x7fffffffe000 0x7fffffffe4d0 0x7fffffffe4c0 3
其中查找范圍的起始地址,和查找的值這2個值可能不太理解為什么是這2個值,因此補充下面的這張解釋圖說明下:

因為是在f1內越界訪問寫的f2內的數組,破壞的是f2幀頭的main幀的返回后的執(zhí)行代碼地址和棧底。因此只有在f2函數執(zhí)行完后出棧后才會core。剛好這個例子是f2調用了f1后只調用了printf,f1幀的棧的面貌沒有被其它的調用清理掉,所以嘗試找f1幀的棧頭信息時剛好可以找到,否則的話找到的會是f2中最后一個調用函數調用入棧寫入的$rbp值。

  • $rsp - 16是f1 frame中保存的f2 frame的棧底地址;

  • 按頁對齊0x7fffffffe4d0的頁起始值是0x7fffffffe000,先搜索的本頁地址也就是從0x7fffffffe000到0x7fffffffe4d0,之所以按頁來查找也是因為前面的地址頁是否存在,實際中可能需要逐漸搜索多頁。

總結

以上是生活随笔為你收集整理的gdb相关(栈和寄存器)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。