ntdll!NtTerminateProcess+0xc (FPO: [2,0,0])的FPO: [2,0,0]是什么?
經常在調試分析dmup時,會看到很多線程棧在函數的后面會帶上FPO,如下所示:
00 00eff818 777beb0d ffffffff 00000000 0107a2ec ntdll!NtTerminateProcess+0xc (FPO: [2,0,0])
01 00eff8f0 762c4f32 00000000 77e8f3b0 ffffffff ntdll!RtlExitUserProcess+0xbd (FPO: [Non-Fpo])
02 00eff904 71e74ebc 00000000 00eff958 71e7518e KERNEL32!ExitProcessImplementation+0x12 (FPO: [1,0,0])
03 00eff910 71e7518d 00000000 dd53d81d 00000000 MSVCR120!__crtExitProcess+0x15 (FPO: [Non-Fpo]) (CONV: cdecl) [f:ddvctoolscrtcrtw32startupcrt0dat.c @ 774]
04 00eff958 71e751b2 00000000 00000000 00000000 MSVCR120!doexit+0x115 (FPO: [Non-Fpo]) (CONV: cdecl) [f:ddvctoolscrtcrtw32startupcrt0dat.c @ 678]
*** WARNING: Unable to verify checksum for ConsoleApplication3.exe
05 00eff96c 01391a02 00000000 a2730342 01391a53 MSVCR120!exit+0xf (FPO: [Non-Fpo]) (CONV: cdecl) [f:ddvctoolscrtcrtw32startupcrt0dat.c @ 417]
06 00eff9a4 762c0419 00caa000 762c0400 00effa10 ConsoleApplication3!__tmainCRTStartup+0x114 (FPO: [Non-Fpo]) (CONV: cdecl) [f:ddvctoolscrtcrtw32dllstuffcrtexe.c @ 649]
07 00eff9b4 777c66dd 00caa000 65564319 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
08 00effa10 777c66ad ffffffff 777e53d5 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
09 00effa20 00000000 01391a53 00caa000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
由于不影響具體問題的分析,一直沒關注,今天又看到,且有點閑,就查了相關資料,補齊這塊知識。
什么是FPO
要知道答案,你必須回到史前時代。英特爾的8088處理器有一組非常有限的寄存器(我忽略了段寄存器),它們是:
| AX | BX | CX | DX | IP |
| SI | DI | BP | SP | FLAGS |
有了這樣一組有限的寄存器,這些寄存器都被指定了特定的用途。AX、BX、CX和DX是“通用”寄存器,SI和DI是“索引”寄存器,SP是“堆棧指針”,BP是“幀指針”,IP是“指令指針”,and FLAGS是一個只讀寄存器,它包含若干位,這些位表示處理器當前狀態的信息(例如,前一個算術或邏輯指令的結果是0)。
BX、SI、DI和BP寄存器是特殊的,因為它們可以用作“索引”寄存器。索引寄存器對編譯器至關重要,因為它們用于通過指針訪問內存。換句話說,如果您的結構位于內存中的偏移量0x1234處,則可以將索引寄存器設置為值0x1234并訪問相對于該位置的值。例如:
MOV BX, [Structure]
MOV AX, [BX]+4
將BX寄存器設置為[Structure]指向的內存值,并將AX的值設置為相對于該結構開頭的第4個字節處的字。
需要注意的一點是,SP寄存器不是索引寄存器。這意味著要訪問堆棧上的變量,需要使用不同的寄存器,這就是BP寄存器的來源-BP寄存器專門用于訪問堆棧上的值。
當386出現時,他們將各種寄存器擴展到32位,并且他們修正了只有BX、SI、DI和BP可以用作索引寄存器的限制。
| EAX | EBX | ECX | EDX | EIP |
| ESI | EDI | EBP | ESP | FLAGS |
這是件好事,突然之間,編譯器可以使用其中的6個,而不是被限制在3個索引寄存器中。
因為索引寄存器是用來訪問結構的,所以對于編譯器來說,它們就像黃金一樣——更多的索引寄存器是一件好事,而獲得更多索引寄存器幾乎是值得的。
一些非常聰明的人意識到,由于ESP現在是一個索引寄存器,EBP寄存器不再需要專門用于訪問堆棧上的變量。下面是使用EBP:
MyFunction:
PUSH EBP
MOV EBP, ESP
SUBESP, <LocalVariableStorage>
MOV EAX, [EBP+8]
:
:
MOVESP, EBP
POP EBP
RETD
要訪問堆棧上的第一個參數(EBP+0是EBP的舊值,EBP+4是返回地址),可以執行以下操作:
MyFunction:
SUB SP,<LocalVariableStorage>
MOV EAX, [ESP+4+<LocalVariableStorage>]
:
:
ADD SP, <LocalVariableStorage>
RETD
突然之間,EBP可以重新利用,并用作另一個通用寄存器!編譯人員稱這種優化為“幀指針省略(Frame Pointer Omission)”,它的首字母縮寫是FPO。FPO是一種優化,它壓縮或者省略了在棧上為該函數創建框架指針的過程。這個選項加速了函數調用,因為不需要建立和移除框架指針(ESP,EBP)了。同時,它還解放出了一個寄存器,用來存儲使用頻率較高的變量。只在IntelCPU的架構上才有這種優化。目前已經討論過的任何一種調用約定都保存了前一函數中棧的信息(壓棧ebp,然后讓ebp = esp,再移動esp來保存局部變量)。一個FPO的函數可能會保存前一函數的棧指針(ESP,EBP),但是并不為當前的函數調用設立EBP。相反,他使用EBP來存儲一些其他的變量。debugger 會計算棧指針,但是debugger必須得到一個使用FPO的提醒,該提醒是基于FPO類型的信息的來完成的。這項特性可以在MS Visual C++專業版和企業版中開啟。使用的是編譯器的/Oy選項。
FPO的數據結構可以在Microsoft的SDK中的winnt.h中找到,其中包含了描述??蚣軆热莸男畔ⅰ_@些信息被使用在debugger上,或者其他的需要在棧中尋找FPO函數的程序中。KV命令可以顯示出包括FPO信息在內的額外的運行時信息。
但FPO有一個小問題。如果您查看MyFunction的pre-FPO示例,您會注意到例程中的第一條指令是PUSH EBP,然后是MOV EBP,特別是它有一個有趣且非常有用的副作用。它實際上創建了一個單獨的鏈接列表,將每個調用者的幀指針鏈接到一個函數。從一個例程的EBP中,可以恢復一個函數的整個調用堆棧。這對于調試器來說是難以置信的有用——這意味著調用棧是相當可靠的,即使沒有被調試器的所有模塊的符號。不幸的是,當FPO被啟用時,堆棧幀的列表丟失了——信息根本沒有被跟蹤。
為了解決這個問題,編譯器人員將啟用FPO時丟失的信息放入二進制文件的PDB文件中。因此,當有模塊的符號時,可以恢復所有堆棧信息。
在NT 3.51中,所有的Windows二進制文件都啟用了FPO,但是在Vista中的Windows二進制文件卻被關閉了,因為它不再是必需的—機器從1995年開始變得足夠快,以至于FPO所實現的性能改進不足以抵消FPO在調試和分析中所帶來的痛苦。
FPO: [2,0,0]代表什么?
我們已經知道FPO是什么了,可是[2,0,0]是什么呢,看如下的表
| FPO數據表示形式 | (FPO: [ebp addr][x,y,z]) |
| x代表 | 作為參數壓棧了的DWORDS個數 |
| y代表 | 作為局部變量壓棧了的DWORDS個數 |
| z代表 | 在開場代碼中(prologue)壓棧了的寄存器個數 |
| ebp addr代表 | 僅在EBP在開場代碼中保存了的時候顯示 |
本文最開始的例子中,由于調試器有正確的symbol,所以調試器會計算出棧底(Frame Pointer)的位置,而不是在EBP之中保存它。比如說,第一個參數的位置是棧底+0x8,返回值的位置是棧底+0x4. 開啟了FPO之后,這些值就不能通過ebp + 0x8這樣拿到了,跟ebp等值的棧底(Frame Pointer)需要計算才能拿到。
總結
以上是生活随笔為你收集整理的ntdll!NtTerminateProcess+0xc (FPO: [2,0,0])的FPO: [2,0,0]是什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python 利用条件运算符:学习成绩
- 下一篇: Akka Netty 比较