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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

C/C++中手动获取调用堆栈【转】

發(fā)布時(shí)間:2024/1/17 c/c++ 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C/C++中手动获取调用堆栈【转】 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)自:http://blog.csdn.net/kevinlynx/article/details/39269507

當(dāng)我們的程序core掉之后,如果能獲取到core時(shí)的函數(shù)調(diào)用堆棧將非常有利于定位問(wèn)題。在Windows下可以使用SEH機(jī)制;在Linux下通過(guò)gdb使用coredump文件即可。

但有時(shí)候由于某些錯(cuò)誤導(dǎo)致堆棧被破壞,發(fā)生拿不到調(diào)用堆棧的情況。

一些基礎(chǔ)預(yù)備知識(shí)本文不再詳述,可以參考以下文章:

  • 函數(shù)調(diào)用棧的獲取原理分析
  • 寄存器、函數(shù)調(diào)用與棧幀

需要知道的信息:

  • 函數(shù)調(diào)用對(duì)應(yīng)的call指令本質(zhì)上是先壓入下一條指令的地址到堆棧,然后跳轉(zhuǎn)到目標(biāo)函數(shù)地址
  • 函數(shù)返回指令ret則是從堆棧取出一個(gè)地址,然后跳轉(zhuǎn)到該地址
  • EBP寄存器始終指向當(dāng)前執(zhí)行函數(shù)相關(guān)信息(局部變量)所在棧中的位置,ESP則始終指向棧頂
  • 每一個(gè)函數(shù)入口都會(huì)保存調(diào)用者的EBP值,在出口處都會(huì)重設(shè)EBP值,從而實(shí)現(xiàn)函數(shù)調(diào)用的現(xiàn)場(chǎng)保存及現(xiàn)場(chǎng)恢復(fù)
  • 64位機(jī)器增加了不少寄存器,從而使得函數(shù)調(diào)用的參數(shù)大部分時(shí)候可以通過(guò)寄存器傳遞;同時(shí)寄存器名字發(fā)生改變,例如EBP變?yōu)镽BP

在函數(shù)調(diào)用中堆棧的情況可用下圖說(shuō)明:

將代碼對(duì)應(yīng)起來(lái):

[cpp] view plaincopy
  • void?g()?{??
  • ????int?*p?=?0;??
  • ????long?a?=?0x1234;??
  • ????printf("%p?%x\n",?&a,?a);??
  • ????printf("%p?%x\n",?&p,?p);??
  • ????f();??
  • ????*p?=?1;??
  • }??
  • ??
  • void?b(int?argc,?char?**argv)?{??
  • ????printf("%p?%p\n",?&argc,?&argv);??
  • ????g();??
  • }??
  • ??
  • int?main(int?argc,?char?**argv)?{??
  • ????b(argc,?argv);??
  • ????return?0;??
  • }??
  • 在函數(shù)g()中斷點(diǎn),看看堆棧中的內(nèi)容(64位機(jī)器):

    [plain] view plaincopy
  • (gdb)?p?$rbp??
  • $2?=?(void?*)?0x7fffffffe370??
  • (gdb)?p?&p??
  • $3?=?(int?**)?0x7fffffffe368??
  • (gdb)?p?$rsp??
  • $4?=?(void?*)?0x7fffffffe360??
  • (gdb)?x/8ag?$rbp-16??
  • 0x7fffffffe360:?0x1234??0x0??
  • 0x7fffffffe370:?0x7fffffffe390??0x400631?<b(int,?char**)+43>??
  • 0x7fffffffe380:?0x7fffffffe498??0x1a561cbc0??
  • 0x7fffffffe390:?0x7fffffffe3b0??0x40064f?<main(int,?char**)+27>??
  • 對(duì)應(yīng)的堆棧圖:

    可以看看例子中0x400631 <b(int, char**)+43>和0x40064f <main(int, char**)+27>中的代碼:

    [plain] view plaincopy
  • (gdb)?disassemble?0x400631??
  • ...??
  • 0x0000000000400627?<b(int,?char**)+33>:?callq??0x400468?<printf@plt>??
  • 0x000000000040062c?<b(int,?char**)+38>:?callq??0x4005ae?<g()>??
  • 0x0000000000400631?<b(int,?char**)+43>:?leaveq???????????????????????????#?call的下一條指令??
  • ...??
  • ??
  • (gdb)?disassemble?0x40064f??
  • ...???
  • 0x000000000040063f?<main(int,?char**)+11>:??????mov????%rsi,-0x10(%rbp)??
  • 0x0000000000400643?<main(int,?char**)+15>:??????mov????-0x10(%rbp),%rsi??
  • 0x0000000000400647?<main(int,?char**)+19>:??????mov????-0x4(%rbp),%edi??
  • 0x000000000040064a?<main(int,?char**)+22>:??????callq??0x400606?<b(int,?char**)>??
  • 0x000000000040064f?<main(int,?char**)+27>:??????mov????$0x0,%eax?????????#?call的下一條指令??
  • ...??
  • 順帶一提,每個(gè)函數(shù)入口和出口,對(duì)應(yīng)的設(shè)置RBP代碼為:

    [plain] view plaincopy
  • (gdb)?disassemble?g??
  • ...??
  • 0x00000000004005ae?<g()+0>:?????push???%rbp???????????????#?保存調(diào)用者的RBP到堆棧??
  • 0x00000000004005af?<g()+1>:?????mov????%rsp,%rbp??????????#?設(shè)置自己的RBP??
  • ...??
  • 0x0000000000400603?<g()+85>:????leaveq????????????????????#?等同于:movq?%rbp,?%rsp??
  • ??????????????????????????????????????????????????????????#?????????popq?%rbp??
  • 0x0000000000400604?<g()+86>:????retq????????????????????????
  • 由以上可見(jiàn),通過(guò)當(dāng)前的RSP或RBP就可以找到調(diào)用堆棧中所有函數(shù)的RBP;找到了RBP就可以找到函數(shù)地址。因?yàn)?#xff0c;任何時(shí)候的RBP指向的堆棧位置就是上一個(gè)函數(shù)的RBP;而任何時(shí)候RBP所在堆棧中的前一個(gè)位置就是函數(shù)返回地址。

    由此我們可以自己構(gòu)建一個(gè)導(dǎo)致gdb無(wú)法取得調(diào)用堆棧的例子:

    [cpp] view plaincopy
  • void?f()?{??
  • ????long?*p?=?0;??
  • ????p?=?(long*)?(&p?+?1);?//?取得g()的RBP??
  • ????*p?=?0;??//?破壞g()的RBP??
  • }??
  • ??
  • void?g()?{??
  • ????int?*p?=?0;??
  • ????long?a?=?0x1234;??
  • ????printf("%p?%x\n",?&a,?a);??
  • ????printf("%p?%x\n",?&p,?p);??
  • ????f();??
  • ????*p?=?1;?//?寫(xiě)0地址導(dǎo)致一次core??
  • }??
  • ??
  • void?b(int?argc,?char?**argv)?{??
  • ????printf("%p?%p\n",?&argc,?&argv);??
  • ????g();??
  • }??
  • ??
  • int?main(int?argc,?char?**argv)?{??
  • ????b(argc,?argv);??
  • ????return?0;??
  • }??
  • 使用gdb運(yùn)行該程序:

    [plain] view plaincopy
  • Program?received?signal?SIGSEGV,?Segmentation?fault.??
  • g?()?at?ebp.c:37??
  • 37??????????*p?=?1;??
  • (gdb)?bt??
  • Cannot?access?memory?at?address?0x8??
  • (gdb)?p?$rbp??
  • $1?=?(void?*)?0x0??
  • bt無(wú)法獲取堆棧,在函數(shù)g()中RBP被改寫(xiě)為0,gdb從0偏移一個(gè)地址長(zhǎng)度即0x8,嘗試從0x8內(nèi)存位置獲取函數(shù)地址,然后提示Cannot access memory at address 0x8。

    RBP出現(xiàn)了問(wèn)題,我們就可以通過(guò)RSP來(lái)手動(dòng)獲取調(diào)用堆棧。因?yàn)镽SP是不會(huì)被破壞的,要通過(guò)RSP獲取調(diào)用堆棧則需要偏移一些局部變量所占的空間:

    [plain] view plaincopy
  • (gdb)?p?$rsp??
  • $2?=?(void?*)?0x7fffffffe360??
  • (gdb)?x/8ag?$rsp+16?????????????#?g()中局部變量占16字節(jié)??
  • 0x7fffffffe370:?0x7fffffffe390??0x400631?<b(int,?char**)+43>??
  • 0x7fffffffe380:?0x7fffffffe498??0x1a561cbc0??
  • 0x7fffffffe390:?0x7fffffffe3b0??0x40064f?<main(int,?char**)+27>??
  • 0x7fffffffe3a0:?0x7fffffffe498??0x100000000??
  • 基于以上就可以手工找到調(diào)用堆棧:

    [plain] view plaincopy
  • g()??
  • 0x400631?<b(int,?char**)+43>??
  • 0x40064f?<main(int,?char**)+27>??
  • 上面的例子本質(zhì)上也是破壞堆棧,并且僅僅破壞了保存了的RBP。在實(shí)際情況中,堆棧可能會(huì)被破壞得更多,則可能導(dǎo)致手動(dòng)定位也較困難。

    堆棧被破壞還可能導(dǎo)致更多的問(wèn)題,例如覆蓋了函數(shù)返回地址,則會(huì)導(dǎo)致RIP錯(cuò)誤;例如堆棧的不平衡。導(dǎo)致堆棧被破壞的原因也有很多,例如局部數(shù)組越界;delete/free棧上對(duì)象等。

    omit-frame-pointer

    使用RBP獲取調(diào)用堆棧相對(duì)比較容易。但現(xiàn)在編譯器都可以設(shè)置不使用RBP(gcc使用-fomit-frame-pointer,msvc使用/Oy),對(duì)于函數(shù)而言不設(shè)置其RBP意味著可以節(jié)省若干條指令。在函數(shù)內(nèi)部則完全使用RSP的偏移來(lái)定位局部變量,包括嵌套作用域里的局部變量,即使程序?qū)嶋H運(yùn)行時(shí)不會(huì)進(jìn)入這個(gè)作用域。

    例如:

    [cpp] view plaincopy
  • void?f2()?{??
  • ????int?a?=?0x1234;??
  • ????if?(a?>?0)?{??
  • ????????int?b?=?0xff;??
  • ????????b?=?a;??
  • ????}??
  • }??
  • gcc中使用-fomit-frame-pointer生成的代碼為:

    [plain] view plaincopy
  • (gdb)?disassemble?f2??
  • Dump?of?assembler?code?for?function?f2:??
  • 0x00000000004004a5?<f2+0>:??????movl???$0x1234,-0x8(%rsp)????#?int?a?=?0x1234??
  • 0x00000000004004ad?<f2+8>:??????cmpl???$0x0,-0x8(%rsp)?????????
  • 0x00000000004004b2?<f2+13>:?????jle????0x4004c4?<f2+31>????????
  • 0x00000000004004b4?<f2+15>:?????movl???$0xff,-0x4(%rsp)??????#?int?b?=?0xff??
  • 0x00000000004004bc?<f2+23>:?????mov????-0x8(%rsp),%eax??
  • 0x00000000004004c0?<f2+27>:?????mov????%eax,-0x4(%rsp)??
  • 0x00000000004004c4?<f2+31>:?????retq??
  • 可以發(fā)現(xiàn)f2()沒(méi)有操作RBP之類的指令了。

    總結(jié)

    以上是生活随笔為你收集整理的C/C++中手动获取调用堆栈【转】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。