CallStack調(diào)用棧:用調(diào)試器腳本查看調(diào)用棧信息

字號:

我們先來看一個例子,下面的程序并不是一個優(yōu)美的程序片段,但是它能夠幫助我們說明問題。程序使用了一個簡單的遞歸,把1到參數(shù)d的和累加到sum之上。在main中,我們把d設(shè)為10,這樣,在斷點處,我們就能獲得一個深度為11的調(diào)用棧。
    #include
    int SumToOne(int d, int sum)
    {
    sum += d;
    if (d != 1)
    sum = SumToOne(d-1, sum);
    else
    sum = sum; // 這條語句方便設(shè)置斷點
    return sum;
    }
    void main()
    {
    int sum = SumToOne(10, 0);
    printf("sum=%d", sum);
    }
    然后,在當(dāng)前文件夾下,編輯調(diào)試器腳本文件DumpStack.txt,內(nèi)容如下
    .printf "Dump %d framesn", ${$arg1}
    r $t1=@ebp;
    .for (r $t0=1; $t0<=${$arg1}; r $t0=$t0+1)
    {
    .printf "frame %d, d=%d sum=%dn", $t0, poi($t1+8), poi($t1+c)
    r $t1=poi($t1)
    }
    在windbg中,運行程序,當(dāng)程序停止在斷點處時,執(zhí)行腳本
    $$>a< “dumpStack.txt”a
    我們看到了10個frame以及它的參數(shù)信息。
    現(xiàn)在,對這個調(diào)試腳本稍加解釋,稍顯來看看腳本的語法:
    調(diào)試腳本的調(diào)用方法,windbg的語法是$$>a< “腳本文件名”參數(shù)。其中$$>a<中的a示意運行腳本的時候傳入?yún)?shù)(argument)
    調(diào)試腳本的參數(shù):在調(diào)試腳本中,用${$argi}來引用第i個參數(shù)。由于windbg默認16進制數(shù),所以我們在調(diào)用這個參數(shù)的時候,用了a($$>a< "dumpStack.txt" a)
    腳本變量的賦值和引用:這里使用了windbg別名(alias)的語法,大家可以把別名類比成c中的宏。在賦值的時候,用r $別名= 的格式,引用的時候,使用$別名
    取值操作:c中的*p操作在windbg中,要用poi(p),原因是因為windbg默認支持MSAM語法。
    控制語句:.for語句的使用和任何一種語言的for語句思想一樣,不再多述
    輸出語句:.printf和c中的printf也基本相似,這里也不多述
    了解了語法之后,來看看算法:
    腳本通過poi($t1+8), poi($t1+c)來顯示每個frame中d和sum的值,這里$t1代表了每個frame中ebp的值,所以簡單的說,就是把每個frame中ebp+8,ebp+c的值輸出。在介紹調(diào)用約定的博客中,我講述了這個偏移量的由來,在這里重溫一下。由于函數(shù)SumToOne是stdcall,壓棧順序從右往左,如下表所示
    前一個ebp
    eip
    d
    sum
    ebp指向存儲前一個ebo的位置,所以d的位置在ebp+8,sum在ebp+c
    前往下一個frame,只需要把棧上ebp位置的值取出,作為新的ebp就可以了。因為基本上每一個程序在進行棧操作之前都會備份老的ebp(push ebp),然后把當(dāng)前的esp作為新的ebp(mov ebp, esp)
    總結(jié)一下,今天這篇博文作為這個系列的結(jié)束,通過一個調(diào)式器腳本,復(fù)習(xí)了之前講述的調(diào)用棧的相關(guān)概念。同時也展示了調(diào)試器腳本的相關(guān)語法。