彙編技術內幕(2)

2020-12-16 電子產品世界
問題:為什麼用EAX寄存器保存函數返回值?

實際上IA32並沒有規定用哪個寄存器來保存返回值。但如果反彙編Solaris/Linux的二進位文件,就會發現,都用EAX保存函數返回值。這不是偶然現象,是作業系統的ABI(Application Binary Interface)來決定的。Solaris/Linux作業系統的ABI就是Sytem V ABI。

本文引用地址:http://www.eepw.com.cn/article/201611/320808.htm


概念:SFP (Stack Frame Pointer) 棧框架指針
正確理解SFP必須了解:
IA32 的棧的概念
CPU 中32位寄存器ESP/EBP的作用
PUSH/POP 指令是如何影響棧的
CALL/RET/LEAVE 等指令是如何影響棧的


如我們所知:
1)IA32的棧是用來存放臨時數據,而且是LIFO,即後進先出的。棧的增長方向是從高地址向低地址增長,按字節為單位編址。
2) EBP是棧基址的指針,永遠指向棧底(高地址),ESP是棧指針,永遠指向棧頂(低地址)。
3) PUSH一個long型數據時,以字節為單位將數據壓入棧,從高到低按字節依次將數據存入ESP-1、ESP-2、ESP-3、ESP-4的地址單元。
4) POP一個long型數據,過程與PUSH相反,依次將ESP-4、ESP-3、ESP-2、ESP-1從棧內彈出,放入一個32位寄存器。
5) CALL指令用來調用一個函數或過程,此時,下一條指令地址會被壓入堆棧,以備返回時能恢復執行下條指令。
6) RET指令用來從一個函數或過程返回,之前CALL保存的下條指令地址會從棧內彈出到EIP寄存器中,程序轉到CALL之前下條指令處執行
7) ENTER是建立當前函數的棧框架,即相當於以下兩條指令:
pushl %ebp
movl %esp,%ebp
8) LEAVE是釋放當前函數或者過程的棧框架,即相當於以下兩條指令:
movl ebp esp
popl ebp


如果反彙編一個函數,很多時候會在函數進入和返回處,發現有類似如下形式的彙編語句:
pushl %ebp ; ebp寄存器內容壓棧,即保存main函數的上級調用函數的棧基地址
movl %esp,%ebp ; esp值賦給ebp,設置 main函數的棧基址
........... ; 以上兩條指令相當於 enter 0,0
...........
leave ; 將ebp值賦給esp,pop先前棧內的上級函數棧的基地址給ebp,恢復原棧基址
ret ; main函數返回,回到上級調用
這些語句就是用來創建和釋放一個函數或者過程的棧框架的。
原來編譯器會自動在函數入口和出口處插入創建和釋放棧框架的語句。


函數被調用時:
1) EIP/EBP成為新函數棧的邊界
函數被調用時,返回時的EIP首先被壓入堆棧;創建棧框架時,上級函數棧的EBP被壓入堆棧,與EIP一道行成新函數棧框架的邊界
2) EBP成為棧框架指針SFP,用來指示新函數棧的邊界
棧框架建立後,EBP指向的棧的內容就是上一級函數棧的EBP,可以想像,通過EBP就可以把層層調用函數的棧都回朔遍歷一遍,調試器就是利用這個特性實現 backtrace功能的
3) ESP總是作為棧指針指向棧頂,用來分配棧空間
棧分配空間給函數局部變量時的語句通常就是給ESP減去一個常數值,例如,分配一個整型數據就是 ESP-4
4) 函數的參數傳遞和局部變量訪問可以通過SFP即EBP來實現
由於棧框架指針永遠指向當前函數的棧基地址,參數和局部變量訪問通常為如下形式:
+8+xx(%ebp) ; 函數入口參數的的訪問
-xx(%ebp) ; 函數局部變量訪問


假如函數A調用函數B,函數B調用函數C ,則函數棧框架及調用關係如下圖所示:
+-------------------------+----> 高地址
| EIP (上級函數返回地址) |
+-------------------------+
+--> | EBP (上級函數的EBP) | --+ | +-------------------------+ +-->偏移量A
| | Local Variables | |
| | .......... | --+ | f +-------------------------+
| r | Arg n(函數B的第n個參數) |
| a +-------------------------+
| m | Arg .(函數B的第.個參數) |
| e +-------------------------+
| | Arg 1(函數B的第1個參數) |
| o +-------------------------+
| f | Arg 0(函數B的第0個參數) | --+ | +-------------------------+ +--> 偏移量B
| A | EIP (A函數的返回地址) | |
| +-------------------------+ --+
+--- | EBP (A函數的EBP) | +-------------------------+ |
| Local Variables | |
| .......... | | +-------------------------+ |
| Arg n(函數C的第n個參數) | |
+-------------------------+ |
| Arg .(函數C的第.個參數) | |
+-------------------------+ +--> frame of B
| Arg 1(函數C的第1個參數) | |
+-------------------------+ |
| Arg 0(函數C的第0個參數) | |
+-------------------------+ |
| EIP (B函數的返回地址) | |
+-------------------------+ |
+--> | EBP (B函數的EBP) | --+ | +-------------------------+
| | Local Variables |
| | .......... | | +-------------------------+----> 低地址
frame of C

圖 1-1

再分析test1反彙編結果中剩餘部分語句的含義:

# mdb test1
Loading modules: [ libc.so.1 ]
> main::dis ; 反彙編main函數
main: pushl %ebp
main+1: movl %esp,%ebp ; 創建Stack Frame(棧框架)
main+3: subl $8,%esp ; 通過ESP-8來分配8位元組堆棧空間
main+6: andl $0xf0,%esp ; 使棧地址16位元組對齊
main+9: movl $0,%eax ; 無意義
main+0xe: subl %eax,%esp ; 無意義
main+0x10: movl $0,%eax ; 設置main函數返回值
main+0x15: leave ; 撤銷Stack Frame(棧框架)
main+0x16: ret ; main 函數返回
>
以下兩句似乎是沒有意義的,果真是這樣嗎?
movl $0,%eax
subl %eax,%esp
用gcc的O2級優化來重新編譯test1.c:
# gcc -O2 test1.c -o test1
# mdb test1
> main::dis
main: pushl %ebp
main+1: movl %esp,%ebp
main+3: subl $8,%esp
main+6: andl $0xf0,%esp
main+9: xorl %eax,%eax ; 設置main返回值,使用xorl異或指令來使eax為0
main+0xb: leave
main+0xc: ret
>
新的反彙編結果比最初的結果要簡潔一些,果然之前被認為無用的語句被優化掉了,進一步驗證了之前的猜測。
提示:編譯器產生的某些語句可能在程序實際語義上沒有用處,可以用優化選項去掉這些語句。


問題:為什麼用xorl來設置eax的值?
注意到優化後的代碼中,eax返回值的設置由 movl $0,%eax 變為 xorl %eax,%eax ,這是因為IA32指令中,xorl比movl有更高的運行速度。


概念:Stack aligned 棧對齊
那麼,以下語句到底是和作用呢?
subl $8,%esp
andl $0xf0,%esp ; 通過andl使低4位為0,保證棧地址16位元組對齊

表面來看,這條語句最直接的後果是使ESP的地址後4位為0,即16位元組對齊,那麼為什麼這麼做呢?
原來,IA32 系列CPU的一些指令分別在4、8、16位元組對齊時會有更快的運行速度,因此gcc編譯器為提高生成代碼在IA32上的運行速度,默認對產生的代碼進行16位元組對齊
andl $0xf0,%esp 的意義很明顯,那麼 subl $8,%esp 呢,是必須的嗎?
這裡假設在進入main函數之前,棧是16位元組對齊的話,那麼,進入main函數後,EIP和EBP被壓入堆棧後,棧地址最末4位二進位位必定是1000,esp -8則恰好使後4位地址二進位位為0000。看來,這也是為保證棧16位元組對齊的。
如果查一下gcc的手冊,就會發現關於棧對齊的參數設置:
-mpreferred-stack-boundary=n ; 希望棧按照2的n次的字節邊界對齊, n的取值範圍是2-12
默認情況下,n是等於4的,也就是說,默認情況下,gcc是16位元組對齊,以適應IA32大多數指令的要求。
讓我們利用-mpreferred-stack-boundary=2來去除棧對齊指令:
# gcc -mpreferred-stack-boundary=2 test1.c -o test1
> main::dis
main: pushl %ebp
main+1: movl %esp,%ebp
main+3: movl $0,%eax
main+8: leave
main+9: ret
>
可以看到,棧對齊指令沒有了,因為,IA32的棧本身就是4位元組對齊的,不需要用額外指令進行對齊。
那麼,棧框架指針SFP是不是必須的呢?
# gcc -mpreferred-stack-boundary=2 -fomit-frame-pointer test1.c -o test
> main::dis
main: movl $0,%eax
main+5: ret
>
由此可知,-fomit-frame-pointer 可以去除SFP。
問題:去除SFP後有什麼缺點呢?
1)增加調式難度
由於SFP在調試器backtrace的指令中被使用到,因此沒有SFP該調試指令就無法使用。
2)降低彙編代碼可讀性
函數參數和局部變量的訪問,在沒有ebp的情況下,都只能通過+xx(esp)的方式訪問,而很難區分兩種方式,降低了程序的可讀性。


問題:去除SFP有什麼優點呢?
1)節省棧空間
2)減少建立和撤銷棧框架的指令後,簡化了代碼
3)使ebp空閒出來,使之作為通用寄存器使用,增加通用寄存器的數量
4)以上3點使得程序運行速度更快


概念:Calling Convention 調用約定和 ABI (Application Binary Interface) 應用程式二進位接口
函數如何找到它的參數?
函數如何返回結果?
函數在哪裡存放局部變量?
那一個硬體寄存器是起始空間?
那一個硬體寄存器必須預先保留?
Calling Convention 調用約定對以上問題作出了規定。Calling Convention也是ABI的一部分。
因此,遵守相同ABI規範的作業系統,使其相互間實現二進位代碼的互操作成為了可能。例如:由於Solaris、Linux都遵守System V的ABI,Solaris 10就提供了直接運行Linux二進位程序的功能。

相關焦點

  • 國內「首案」刑事案例彙編
    本期北大法寶彙編整理14例刑事案例國內首案,案例君轉載如下,以供參考。2.全國首例「花唄套現非法經營」案——杜某獅非法經營案【案號】(2017)渝0105刑初817號【案由】非法經營罪【審理法院】重慶市江北區人民法院
  • 國內「首案」刑事案例彙編_政務_澎湃新聞-The Paper
    本期北大法寶彙編整理14例刑事案例國內首案,案例君轉載如下,以供參考。,並將常見的對抗「反爬蟲」措施的技術行為認定為「侵入」計算機信息系統行為,這實際上等於創設數據流轉領域新的規則,為規範數據流轉行為起到了一定的積極意義。
  • 錦富技術總經理涉內幕交易被查 公司2年曆三任董事長
    來源:新京報網原標題:涉內幕交易 錦富技術總經理被查 公司2年曆3任董事長 上任不到一年,上市公司蘇州錦富技術股份有限公司(證券簡稱:錦富技術300128)總經理王文德被證監會立案調查,其在1個月前剛剛辭去公司董事會秘書一職。
  • 彙編程序基本原理知識筆記
    1.2 偽指令語句指示彙編程序在翻譯源程序的時候完成某些工作,比如給變量分配存儲單元、給某個符號賦值等。翻譯後不會產生機器代碼。偽指令語句是在源程序彙編的時候完成。1.3 宏指令語句允許用戶多次重複使用的程序代碼段稱為宏。
  • 各種開源彙編、反彙編引擎的非專業比較
    2. 解碼出來的結構不詳細,比如指令前綴支持不夠友好,這點從Ollydbg的反彙編窗口可以看出,除了movs/cmps等指令以外,repcc與其他指令組合時都是單獨分開的;  再比如寄存器無法表示ah\bh\ch\dh這種高8位寄存器。    3. 作者一次性開源後便不再維護開源版本,對於反彙編上的BUG很難即時修復。
  • ARM彙編特殊符號 彙編符號引用
    時編譯器將用該串變量的數值取代該串變量,如:GBLS STR1GBLS STR2STR1 SETS 「pen.」STR2 SETS 「This is a $STR1"編譯後的結果是STR2的值為This is a pen.如果$後是數字變量(與串變量區分),在彙編時編譯器將該數字變量的數值轉換成十六進位的串,然後用該十六進位的串取代$後的數字變量。如果$後是邏輯變量,在彙編時編譯器將該邏輯變量替換成它的取值(T或者F)。
  • 反彙編代碼還原之除數為非2的冪
    反彙編代碼還原之除數為2的冪反彙編代碼還原之加減乘* 點擊文字即可跳轉文章1.1 簡介除法在非2的冪優化上,採用的是除法轉變為乘法的形式,在學習這方面知識的時候我們先了解下簡單的數學知識除法非2的冪還原乍一看上面彙編代碼,除法怎麼變為了這樣,為什麼又有乘法又有除法,還有調整等,那麼這裡就著重講解一下。
  • 彙編語言入門
    雖然現在市面上關於彙編語言的書籍資料無窮多,卻無從下手?
  • *ST晨鑫2高管內幕交易遭罰 得知實控人遭拘捕避險賣出
    原標題:*ST晨鑫2高管內幕交易遭罰 得知實控人遭拘捕避險賣出
  • 重慶法院公開宣判兩起內幕交易、洩露內幕信息案
    9月17日,重慶市第一中級人民法院依法對被告人王國祥內幕交易、被告人王健忠洩露內幕信息案和被告人謝暄內幕交易、洩露內幕信息,被告人李文捷內幕交易案兩起案件進行公開宣判。在內幕信息敏感期內,謝暄夥同時任東方證券長沙勞動西路營業部總經理的被告人李文捷,購入博雲新材股票,獲利716 167.74元。被告人謝暄還將上述內幕信息洩露給李文捷,李文捷在內幕信息敏感期內,通過其實際控制他人證券交易帳戶買入博雲新材股票,獲利2 171 358.55元。
  • 蘇嘉鴻內幕交易威華股份案評析——兼議內幕交易認定中的證明標準
    在案件宣判後,學界的關注焦點多集中於內幕交易的調查規則和證明標準當中。[2]本文也將從蘇嘉鴻案的案情出發,簡要探討重組方案的變化對內幕信息認定的影響,重點分析證券行政調查流程中的基本要求以及內幕交易「推定」過程中的證明責任和證明標準問題。
  • 【在線答題】湖南2009--2018年十年學業水平考試題分類彙編(2)
    (2分) 【答案】26、文獻:①英國《權利法案》;②美國1787憲法; ③法蘭西第三共和國憲法 ;④德意志帝國憲法(任答兩點,4分)【答案】27、(1)限制國王權力,保障議會權力(或歸納具體內容亦可)。(2分)君主立憲制。(2分)(2)三權分立(分權與制衡)。(2分)《1787年憲法》。
  • DSP編程技巧之24---C/C++與彙編語言的交互之-(2)從C/C++代碼調用...
    2.使用內聯函數法調用彙編函數  這種方法一般用於引用單條的彙編語句,例如:  asm(";*** this is an assembly language comment");  上面例子並沒有影響任何的變量,它的作用只是在C/C++代碼編譯成彙編代碼之後,在相對應的位置插入了一端彙編代碼的注釋,對調試特別有幫助。
  • 混合使用C、C++和彙編語之:內聯彙編和嵌入型彙編的使用
    例如,在下面幾種情況中必須使用內聯彙編或嵌入型彙編。·程序中使用飽和算術運算(Saturatingarithmetic),如SSAT16和USAT16指令。·程序中需要對協處理器進行操作。·在C或C++程序中完成對程序狀態寄存器的操作。使用內聯彙編編寫的程序代碼效率也比較高。
  • 哪幾種情況中必須使用內聯彙編或嵌入型彙編?
    ARM系列文章,請點擊以下匯總連結:《從0學arm合集》一、gcc 內聯彙編內聯彙編即在C中直接使用彙編語句進行編程,使程序可以在C程序中實現C語言不能完成的一些工作,例如,在下面幾種情況中必須使用內聯彙編或嵌入型彙編。
  • 通過反彙編來理解restrict關鍵字
    在f中根據這兩個地址,將p和q分別設置為2和3,最後把p的值2當做函數返回值返回,並賦值給整形變量r。這個代碼很簡單,不過重點不是看它,而是它的反彙編代碼,下面我們通過objdump工具執行objdump -j .text -l -C -S test來生成彙編代碼。 首先來看下main函數的彙編代碼。
  • 易見股份夭折增資案「順藤摸瓜」 2人洩密5人內幕交易
    中國證券監督管理委員會上海監管局網站於近日公布的行政處罰決定書(滬〔2020〕19號)顯示,經查明,上述李聯鳳洩密之人張某1系本案內幕交易人張誼。李聯鳳與張誼相熟,二人見面較多,李聯鳳非法獲取內幕信息後,將有關信息告訴張誼。張誼與左某為好友關係,內幕信息公開前,二人於2017年5月2日、7日、14日通話聯絡。
  • 揭露除溼機什麼牌子好,行業內幕!
    揭露除溼機行業什麼牌子好,行業內幕真正的大品牌很少人知道,而一些假冒偽劣的品牌充斥市場。進入 2015 年以來,家用除溼機行業獲得了迅猛發展,隨著廣大市民對健康呼吸認識的提升,對空氣安全也格外重視,對除溼機也不再感到陌生,我國除溼機市場需求也不斷的拓展。
  • 彙編語言的基本知識
    一、彙編語言的語句格式     由彙編語言編寫的源程序是由許多語句(也可稱為彙編指令)組成的。
  • 內功修煉,彙編語言入門教程
    彙編語言就是低級語言,直接描述/控制 CPU 的運行。如果你想了解 CPU 到底幹了些什麼,以及代碼的運行步驟,就一定要學習彙編語言。彙編語言不容易學習,就連簡明扼要的介紹都很難找到。下面我嘗試寫一篇最好懂的彙編語言教程,解釋 CPU 如何執行代碼。