青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

天秤座的唐風

總會有一個人需要你的分享~!- 唐風 -

  C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
  13 隨筆 :: 0 文章 :: 69 評論 :: 0 Trackbacks

前幾天和柯柯交流一個小問題,說是如何在一個函數內得到調用該函數的函數地址。有點拗口,就是說如果有一個函數A(當然我們在這個問題中并不知道它是哪個函數)調用了B函數,現在希望用個什么辦法得到A函數的地址。  

我首先聯想到的是,一般調試器都能給出嵌套的函數調用關系。那么肯定是有什么辦法解決這個問題。上網查了一通之后只找到一些debug用的API和一些開發環境提供的調整宏等等,感覺不是很適用。后來想想,函數調用都涉及到“函數調用棧”(call stack),也許這里可以得到些什么信息。隱約回想起以前匯編課里老師講過的一些函數調用時要“壓棧”、“要保存現場”等,但已經記得不太清楚了,于是就又上網找了些函數調用棧的知識,發現了一些有意思的信息(上網時看到ChinaUnix上的一篇,也是轉的,原地址和作者不詳,如果你知道請告訴我):

  1. 一個函數調用動作可分解為:零到多個PUSH指令(用于參數入棧),一個CALL指令。CALL指令內部其實還暗含了一個將返回地址(即CALL指令下一條指令的地址)壓棧的動作。
  2. 幾乎任何本地編譯器都會在每個函數體之前插入類似如下指令:PUSH EBP; MOV EBP ESP;即,在程式執行到一個函數的真正函數體時,已有以下數據順序入棧:參數,返回地址,EBP。 

這里我最關心的是:函數調用時,會在棧里壓入返回地址,和EBP。

因為函數調用的返回地址,正是調用指令Call的下一個指令的地址,那么,有了返回地址,就可以得到Call指令的位置了。有Call指令的位置又能干什么呢?幸好匯編課里的知識還記得一點點:Call指令就是一個跳轉指令,它可以讓IP(instruction point[Thanks to RednaxelaFX])指向要跳轉的指令的地址,從那里開始執行。對于函數調用來說,就是讓IP指向被調用的函數的地址。Call指令的操作數其實和被調用函數的地址有非常重要的關系。有了Call指令的操作數,就可以計算出被調用函數的地址。

但僅僅有這個還不夠,比如,A調用了B,那么在A函數中肯定有一個Call指令,但這個Call指令中的操作數是和B函數地址相關的,與A的函數地址直接關系不大(至少在沒有其它信息的情況下,不能計算出A的地址)。而我們要得到的卻是A函數的地址。所以,得向上再找一層,找到調用A函數的地方,那個地方的Call指令里的操作數才和A函數地址有關。也就是說,Z函數調用了A函數,A函數調用了B函數。現在要得到A函數的地址,我們得在Z函數里找Call指令的操作數。這時候EBP就派上用場了。本地編譯器在每個函數體之前插入的指令(PUSH EBP; MOV EBP ESP)構造了一個巧妙的結構,使得我們可以順著函數調用棧一層一層向上,找到所有調用關系。

如何向上查找呢?我們看看函數調用時棧、EBP的值的情況就知道了。

假設現在函數在正Z函數內執行,那么此時棧和EBP的值可能是像下圖這樣的:

我們先不管現在EBP指向的內存(0x000f)中的內容XXX是什么(要不然會是雞生蛋生雞的問題),總之目前在棧中的著色塊中的內容是屬于函數Z的參數,Z執行結束后應該返回的地址以及Z函數的局部變量值。

現在Z函數調用A函數,會先將傳給A的參數壓棧,然后將現在這個指令(就是"Call A"啦)的下一個指令的地址壓入棧中,以便A函數完后返回到Z中繼續執行。然后進入A函數的內存空間,首先就是調用PUSH EBP,也就是將Z的EPB的內容(地址0x000f)壓入棧中,然后再MOV EBP ESP,讓EBP有一個新的棧頂(此時棧頂中的內容不就是Z函數時EBP的內容么?),然后再將A函數的局部變量壓入棧中,開始執行A函數的代碼。這時,棧和EBP的情況就像如圖所示了: 

哈,這樣就很清楚了,原來現在的EBP中的內容,正是上一級函數的EBP中的內容。而每一個函數的EBP指向的位置,向棧頂可以得到該函數的局部變量,向棧底可以得到函數的返回地址和參數。于是我們就可以根據這個結構層層向上,找到任何一層我們想找的函數EBP,從而也就能得到相應的返回地址了。  

好,從B函數中得到Z函數對A函數調用點的返回地址的問題也就解決了。現在就是處理Call指令的問題了。

我在Visual Studio 2003的Debug版中進行反匯編調試,發現Call指令對應的機器指令都是5個byte,第一個byte(E8)是指令的器碼,猜想后面4個byte應該就是它的轉移的目標地址了。結果按這個地址去找,發現根本不對,想想匯編也忘得差不多了,于是又去找了教程看看,才記起原來Call的操作數并不是絕對地址,而是偏移地址(跳轉目標地址-Call指令地址-sizeof(Call指令)),這樣就好辦了,我有返回地址,于是就有了向上5個byte就是Call的地址,再從這個地址中取出Call指令機器碼的后四個字節,加上返回地址,就得到了目標地址。

原以為已經搞定了。不過還有一個小插曲,就是在VS的Debug版中,Call并不直接跳到一函數中去(不知道為什么),而是跳到一塊代碼區,這塊區域內排布了很多的Jmp指令用于各種跳轉(不知道為什么這么搞,也許是為調試的功能而設計的吧,誰知道?還請不吝賜教),不過沒關系,也就是多走一點路而已,Jmp指令的操作數和Call指令的意義是一樣的,最終Jmp是跳到函數代碼塊中去的。于是也就得到了想要的結果。

  

下面是代碼:

 1#include "stdafx.h"
 2
 3#include <string>
 4
 5unsigned int GetCallerAddress(void)
 6{
 7    unsigned int _ebp;
 8    __asm mov _ebp, ebp
 9
10    for (int i=2; i != 0--i) {
11        _ebp = *(unsigned int *)(_ebp);
12    }

13    unsigned int* ipAddress = (unsigned int*)(*(unsigned int *)(_ebp + 4));
14
15    ipAddress = (unsigned int*)((unsigned char *)ipAddress - 5);
16    unsigned int callInstructAddress = (unsigned int)ipAddress;
17    ipAddress = (unsigned int*)((unsigned char *)ipAddress + 1);
18    int funcAddrOffset = *ipAddress;
19    unsigned int *jumAddr = (unsigned int*)(callInstructAddress + funcAddrOffset + 5); 
20    callInstructAddress = (unsigned int)jumAddr;
21    jumAddr = (unsigned int*)((unsigned char *)jumAddr + 1);
22    funcAddrOffset = *jumAddr;
23    
24    return funcAddrOffset + callInstructAddress + 5;    
25}

26
27void fun1();
28
29void fun2()
30{
31    fun1();
32}

33
34void fun3()
35{
36    fun1();
37}

38
39
40void fun1()
41{
42    unsigned int _ebp;
43    __asm mov _ebp, ebp // 取當前EBP
44    unsigned int _preEbp = *(unsigned int *)(_ebp);   //得到上層函數的EBP
45    unsigned int* ipAddress = (unsigned int*)(*(unsigned int *)(_preEbp + 4)); // 取得返回地址
46    ipAddress = (unsigned int*)((unsigned char *)ipAddress - 5); // 得到Call指令地址  
47    unsigned int callInstructAddress = (unsigned int)ipAddress;     // 保存Call指令地址  
48    ipAddress = (unsigned int*)((unsigned char *)ipAddress + 1); 
49    int funcAddrOffset = *ipAddress; // 得到Call指令操作數
50    unsigned int *jumAddr = (unsigned int*)(callInstructAddress + funcAddrOffset + 5); // 找到Jmp指令
51    callInstructAddress = (unsigned int)jumAddr; // 保存jmp指令地址
52    jumAddr = (unsigned int*)((unsigned char *)jumAddr + 1); 
53    funcAddrOffset = *jumAddr; // 得到jmp指令操作數
54    unsigned int addr = funcAddrOffset + callInstructAddress + 5//得到函數地址
55
56    // 或者:unsigned int addr = GetCallerAddress();
57    printf("fun1 said : Caller Addres is 0x%08x\n", addr);
58}

59
60int _tmain(int argc, _TCHAR* argv[])
61{
62    fun1();
63    fun2();
64    fun3();
65
66    return 0;
67}

68
69


PS:后經柯柯驗證,只有VC6、2003、2008的Debug版里才有效。Release版中不行,具體原因未細查(沒時間,畢竟不是"正務",呵呵)。以后再遇到時再細究吧。至少,現在對函數調用棧有了一些新的認識。很開心,呵呵呵。  

 

后記: 

  這兩天翻看《Windows95編程大奧秘》(候捷譯)中,作者在分析PE格式的時候提到了,Call指令并不直接將程序控制轉到目標函數,而是轉入一個Jmp的代碼塊中,由Jmp來最終將控制權交給函數。為什么這么做呢?作者給出的結論是這樣做可以使得載入器的行為變得簡單。因為Jmp的操作數是存放在idata區的一個“變量”,載入器只需要將被調用的DLL的地址一次寫入這個“變量”中就可以了。如果不這么做,那么需要在每個Call指令中的位置對函數地址進行Fixup,這樣會有更多的工作量。 

  OK,你不要笑話我說還看Win95的書哦。是的,我承認我不知道上面這段話中內容在現在的XP或是Vista或是2000中是否依然有效(因為我沒有去驗證過),但我看到了解決的方向。另外,這本書真的像候捷先生所說,“仍然極具技術價值”。我很認同! 

  鑒于RednaxelaFX的提示和本書給的信息,我下一步將償試從PE文件來找這個問題的解決之道,并順帶學習一下PE格式。讀完《Win95》后,也可能會寫篇讀后感,敬請留意,嘿嘿。

  

posted on 2009-06-09 20:38 唐風 閱讀(4445) 評論(7)  編輯 收藏 引用 所屬分類: 調試技術與逆向工程

評論

# re: 函數調用棧初探 2009-11-15 03:33 OwnWaterloo
調試器需要符號表。

  回復  更多評論
  

# re: 函數調用棧初探 2009-11-21 17:39 唐風
@OwnWaterloo
調試器沒有符號表的情況下應該也能工作,但給出來的信息是“人類不可讀”的。

一直在找一種方法,希望能實現這樣的需求:
給出兩個斷點,當程序停在第二個斷點的時候,希望得到在之期間被調用過的函數的列表(按時間順序)。
當然,希望這個方法是非侵入性的。
棧信息顯然是不夠的,某一時刻的棧信息只有被斷點函數的上層函數信息,但可以肯定的是每個函數調用都會有壓棧出棧,能不能設置一個類似“勾子”的東西,在CPU進行壓棧的時候記錄下某些信息呢?

  回復  更多評論
  

# re: 函數調用棧初探 2009-11-21 17:55 OwnWaterloo
@唐風

但可以肯定的是每個函數調用都會有壓棧出棧

問題就在于這個前提是沒有保證的。
編譯器處理尾調用時可以使用jmp而不用call。
這種情況在msvc和gcc上都存在。

編譯器不一定會生成序言部分 —— push ebp mov ebp esp。
msvc肯定有這種情況,我見過。
gcc好像會嚴格生成這部分代碼。



vs2005 team suit好像有這個功能。 在性能測試中。 但沒這么靈活……
好像只能在程序跑完之后才能輸出分析的結果,不能任意兩個斷點之間。
輸出結果中記得是包含了調用樹的。
可以玩玩看,也許有輸出2個斷點之間的調用樹呢?

  回復  更多評論
  

# re: 函數調用棧初探 2009-11-21 17:56 OwnWaterloo
@唐風

能不能設置一個類似“勾子”的東西,在CPU進行壓棧的時候記錄下某些信息呢?

這個就不知道了……
查查vs的那個性能測試是怎么做到的? 也是非侵入的。
或者其他profile工具是怎么做的?

  回復  更多評論
  

# re: 函數調用棧初探 2009-11-22 12:54 唐風
>>問題就在于這個前提是沒有保證的。編譯器處理尾調用時可以使用jmp而不用call。這種情況在msvc和gcc上都存在。
哦哦,原來還有這種情況,呃,如果是在 debug 模式下,優化全關呢?嗯,找個時間我再也看看。

>>或者其他profile工具是怎么做的?
這倒是,以前沒想到這個可以做參考呢,嗯,謝謝提醒。


真是獨學而無友,則孤陋而寡聞。

PS:
一連兩天看到你搶占沙發長篇回復,果然是神人啊……
什么時候我夠格能和你“論戰”一番而后雙方受益,倒是可以“含笑九泉”了,:)

學習中……

  回復  更多評論
  

# re: 函數調用棧初探 2009-11-27 02:29 OwnWaterloo
你本來寫的是【quote】吧? cnblogs確實很偏心,cppblog確實是穿小鞋的……

說正題……
這里的評論里可能有你感興趣的內容:
http://www.cnblogs.com/JeffreyZhao/archive/2009/11/17/linker-loader-library-correction-about-call-stack.html#1704232

關于push ebp,frame pointer,call-stack,debugging等。

有點長哦,一直往下看。
哈哈,評論的主角就是這篇文章中提到的RednaxelaFX。
應該是同一個人吧?
  回復  更多評論
  

# re: 函數調用棧初探 2009-11-27 19:49 唐風
謝謝你的提醒啊~!
呵呵,這兩天跑去看 WinDbg 的擴展命令去了,企圖看能不能通過寫 WinDbg 的擴展來達到目的……有點頭大的說,哈哈。

RednaxelaFX 這個“家伙”也是個神人,我在 JavaEye 上第一次發表這篇文章的時候就有他的回復,相信是同一個人,嘿嘿。(他在 JavaEye 上有關于 JavaScript 運行機制的文章,看得出是很有幾把刷子的)

跑來跑去,發現世界挺大,圈子確挺小,哈哈哈哈 :P

嗯嗯,是夠長的,明天早起好好看,嘿嘿!!

PS:
>>你本來寫的是【quote】吧? cnblogs確實很偏心,cppblog確實是穿小鞋的……
確實是的,唉……
這段時間在通過 CSS 和 JS 腳本在配置 cnblogs 的博客,已經比較滿意了,嘿嘿。  回復  更多評論
  

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美在线观看一二区| 亚洲一区免费在线观看| 久久久久久黄| 亚洲第一主播视频| 91久久国产综合久久蜜月精品| 久久蜜桃资源一区二区老牛 | 久久国产直播| 亚洲国产精品国自产拍av秋霞| 亚洲激情视频在线观看| 欧美日韩国产限制| 老司机亚洲精品| 亚洲精品少妇30p| 欧美性猛交xxxx乱大交退制版| 亚洲欧美一区二区视频| 久久成人免费网| 亚洲另类在线视频| 先锋亚洲精品| 日韩小视频在线观看专区| 亚洲制服欧美中文字幕中文字幕| 国内成人精品视频| 亚洲免费精品| 黄色av成人| 亚洲视频在线免费观看| 亚洲福利视频一区二区| 中文国产一区| 亚洲国产天堂久久综合网| 亚洲素人一区二区| 亚洲人成人99网站| 午夜精品一区二区三区在线| 亚洲精品国产精品乱码不99按摩| 亚洲专区免费| 999亚洲国产精| 久久国产精品99精品国产| 中文精品一区二区三区| 久久亚洲不卡| 久久久久久夜| 国产精品视频精品| 亚洲精品三级| 91久久久国产精品| 久久精品最新地址| 欧美在线亚洲| 国产精品xxxxx| 亚洲欧洲午夜| 亚洲乱码国产乱码精品精| 久久国产精品99国产精| 欧美主播一区二区三区美女 久久精品人| 欧美国产一区二区三区激情无套| 久久综合给合| 国产在线欧美日韩| 欧美一区二区三区免费观看视频 | 在线成人h网| 欧美一区二区视频97| 亚洲欧洲av一区二区三区久久| 欧美激情一区二区三区蜜桃视频 | 日韩亚洲在线| 在线视频亚洲一区| 欧美日韩国产一级片| 亚洲免费av电影| 亚洲性夜色噜噜噜7777| 欧美精品一区二区三区一线天视频 | 久久蜜桃香蕉精品一区二区三区| 国产精品腿扒开做爽爽爽挤奶网站| 日韩一区二区免费看| 一区二区日韩免费看| 欧美日韩在线播放一区二区| 99re亚洲国产精品| 亚洲影院色在线观看免费| 欧美aaaaaaaa牛牛影院| 亚洲网站啪啪| 性色av一区二区三区在线观看| 欧美日韩一区三区| 亚洲欧美激情四射在线日| 亚洲欧美在线免费观看| 国产精品美女主播| 久久av二区| 欧美大片在线观看一区二区| 亚洲激情校园春色| 欧美色图首页| 亚洲女女女同性video| 久久久91精品国产一区二区三区 | 久久婷婷久久| 亚洲欧洲另类国产综合| 欧美国产精品一区| 亚洲图片欧美午夜| 久久野战av| 99精品国产在热久久婷婷| 国产精品理论片在线观看| 欧美一区二区视频观看视频| 免费在线观看精品| 一本色道久久精品| 国产一区二区日韩| 欧美sm重口味系列视频在线观看| 亚洲美女毛片| 久久久综合免费视频| 亚洲免费观看在线视频| 国产精品私房写真福利视频| 久久一区激情| 亚洲欧美bt| 亚洲国产毛片完整版 | 亚洲女性喷水在线观看一区| 国产一区99| 欧美日韩亚洲一区二区三区在线观看 | 久久天天狠狠| 亚洲网站视频福利| 亚洲高清视频的网址| 欧美在线地址| 一区二区电影免费观看| 好吊日精品视频| 欧美性生交xxxxx久久久| 狂野欧美激情性xxxx| 亚洲欧美成人网| 99re热精品| 亚洲国产精品电影| 麻豆精品网站| 欧美一区免费视频| 一区二区三区欧美视频| 亚洲激情黄色| 韩国成人理伦片免费播放| 欧美午夜不卡影院在线观看完整版免费 | 在线视频欧美一区| 亚洲精品一区二区三区婷婷月 | 亚洲一区亚洲| 久久亚洲私人国产精品va媚药| 麻豆av福利av久久av| 午夜精品久久久久久久白皮肤| 亚洲黄色三级| 欧美成人免费在线视频| 亚洲欧美日韩精品久久亚洲区 | 亚洲高清不卡一区| 欧美成人69av| 玖玖在线精品| 久久一区二区三区超碰国产精品| 亚洲综合色在线| 亚洲综合色激情五月| 亚洲永久免费av| 亚洲一区免费在线观看| 妖精视频成人观看www| 亚洲精品国产精品久久清纯直播| 在线视频国内自拍亚洲视频| 国语精品一区| 精品99一区二区三区| 国产一区二区三区四区| 狠狠色丁香婷婷综合久久片| 黄色成人av| 亚洲国产精品传媒在线观看 | 在线电影国产精品| 亚洲高清电影| 亚洲精品之草原avav久久| 亚洲欧洲精品一区二区三区不卡| 91久久久久久| 正在播放亚洲| 午夜精品一区二区三区电影天堂 | 一区二区免费在线视频| 亚洲一区二区三区免费视频 | 亚洲国产精品女人久久久| 最新成人在线| 亚洲尤物影院| 久久看片网站| 亚洲国产欧美一区二区三区久久| 亚洲美女av电影| 性欧美暴力猛交69hd| 久久久99久久精品女同性| 欧美成人午夜视频| 欧美视频国产精品| 国产日产精品一区二区三区四区的观看方式 | 久久免费偷拍视频| 欧美日韩免费区域视频在线观看| 国产精品mv在线观看| 国产综合av| 亚洲剧情一区二区| 欧美亚洲一区二区在线| 欧美成人中文| 亚洲一区二区视频| 美日韩在线观看| 国产精品永久免费| 亚洲清纯自拍| 欧美在线视频一区| 亚洲精品乱码久久久久久蜜桃麻豆 | 亚洲欧美视频一区二区三区| 久色婷婷小香蕉久久| 一区二区冒白浆视频| 久久精品亚洲精品国产欧美kt∨| 国产精品久久久久久久久免费樱桃 | 久久综合伊人77777麻豆| 欧美日韩一区二区高清| 一区国产精品| 性久久久久久久久久久久| 蜜臀91精品一区二区三区| 亚洲美女色禁图| 久久亚洲午夜电影| 国产伦一区二区三区色一情| 日韩一二三在线视频播| 狼狼综合久久久久综合网| 亚洲免费视频在线观看| 欧美日韩国产二区| 亚洲欧洲日本mm| 美女网站久久| 小处雏高清一区二区三区 | 欧美乱人伦中文字幕在线| 伊人久久噜噜噜躁狠狠躁 |