- 編譯系統(tǒng)透視:圖解編譯原理
- 新設(shè)計(jì)團(tuán)隊(duì)
- 1325字
- 2019-01-04 03:30:30
1.2 更為復(fù)雜C程序的運(yùn)行時(shí)結(jié)構(gòu)
在實(shí)際編程過(guò)程中會(huì)遇到更為復(fù)雜的問(wèn)題。要解決這樣的問(wèn)題,更加依賴對(duì)運(yùn)行時(shí)結(jié)構(gòu)的了解。下面我們來(lái)看一個(gè)比較復(fù)雜的案例,案例的兩個(gè)程序分別如下:
#include <stdio.h> #include <stdio.h> #include <string.h> #include <string.h> void fun1() void fun1() { { int m =10; int m =10; char num[4]; char hum[4]; strcpy(num,"bbbbbbbbbbbbb\x0F\x10\x40\x00"); strcpy(num, "bbbb"); ) } void fun2() void fun2() ( { printf("You were attacked!!!\n"); printf("You were attacked!!!\n"); } } int main() int main() ( { fun1(); fun1 (); return 0; return 0; } }
這個(gè)案例中的兩個(gè)程序在代碼上只有微小的差別,但執(zhí)行結(jié)果卻不同,尤其是左邊的程序,執(zhí)行結(jié)果如下所示:
#include <stdio.h> #include <string.h> void fun1() { int m =10; char num[4]; strcpy(num,"bbbbbbbbbbbbb\x0F\x10\x40\x00"); ) void fun2() ( printf("You were attacked!!!\n"); } int main() ( fun1(); return 0; } You were attacked!!!
這些字符顯然是fun2函數(shù)被調(diào)用時(shí)才會(huì)輸出的,但fun2這個(gè)函數(shù)在本程序中沒(méi)有被調(diào)用過(guò),這樣的輸出結(jié)果顯得有些不可思議了,程序執(zhí)行時(shí)到底發(fā)生了什么呢?下面我們一步一步地對(duì)比分析這個(gè)案例。我們先來(lái)看main函數(shù)調(diào)用fun1函數(shù)時(shí)的情景,fun1函數(shù)執(zhí)行后的返回地址被壓入棧中,跳轉(zhuǎn)到fun1函數(shù)執(zhí)行,此時(shí)兩邊程序的執(zhí)行沒(méi)有差異,情景如圖1-29所示。

圖1-29 兩個(gè)程序都跳轉(zhuǎn)到fun1后的整體比較
之后保存了main函數(shù)棧底的地址值,ebp被騰出來(lái),指向fun1函數(shù)的棧底,此時(shí)兩邊也沒(méi)有差異。情景如圖1-30所示。

圖1-30 兩個(gè)程序都保存了main棧底地址值并準(zhǔn)備為fun1建棧
m入棧,初始化為10,為num數(shù)組開(kāi)辟了棧空間,此時(shí)仍然沒(méi)有差異,情景如圖1-31所示。

圖1-31 兩個(gè)程序都為fun1函數(shù)的局部變量開(kāi)辟了棧空間
下面差異產(chǎn)生了。調(diào)用strcpy函數(shù),執(zhí)行的目的是把指定的字符串拷貝到num數(shù)組中,指定多少,拷貝多少。我們先來(lái)看右邊的程序。該程序會(huì)把指定的字符串拷貝給num數(shù)組,其長(zhǎng)度剛好填滿num數(shù)組,情景如圖1-32所示。

圖1-32 右邊程序?qū)?shù)組初始化
再看左邊程序,指定的字符串長(zhǎng)度已經(jīng)超出了num數(shù)組的長(zhǎng)度,所以在拷貝的時(shí)候,會(huì)把棧中前面的數(shù)據(jù)覆蓋掉,包括num的數(shù)組、main函數(shù)棧底地址值直至fun1函數(shù)執(zhí)行后的返回地址,全部被覆蓋,情景如圖1-33所示。

圖1-33 左邊程序?qū)?shù)組初始化后覆蓋了其他數(shù)據(jù)
覆蓋的結(jié)果使得fun1函數(shù)在返回并恢復(fù)現(xiàn)場(chǎng)時(shí)出現(xiàn)了問(wèn)題。
我們先來(lái)看右邊的程序,跳轉(zhuǎn)回main函數(shù),正常恢復(fù),情景如圖1-34所示。

圖1-34 右邊程序返回到main函數(shù)繼續(xù)執(zhí)行
再看左邊的程序,棧底地址值被覆蓋了,ebp會(huì)得到一個(gè)亂值,不再指向main函數(shù)的棧底,另外,由于fun1函數(shù)執(zhí)行后返回地址已經(jīng)被覆蓋,而且覆蓋的數(shù)值正好是fun2函數(shù)的起始地址,將這個(gè)數(shù)據(jù)傳遞給eip,那么eip自然跳轉(zhuǎn)到fun2函數(shù)執(zhí)行,相當(dāng)于調(diào)用了fun2函數(shù),也就輸出了fun2函數(shù)的打印信息。同時(shí),ebp成了亂值,程序最終將產(chǎn)生段錯(cuò)誤,情景如圖1-35所示。

圖1-35 左邊程序棧中數(shù)據(jù)被覆蓋后導(dǎo)致的后果
在C語(yǔ)言中,棧的方向是從高地址向低地址延伸,而數(shù)組中數(shù)據(jù)在棧中的存儲(chǔ)方向與此正好相反。字符串拷貝等數(shù)組操作是不對(duì)數(shù)據(jù)長(zhǎng)度做審核的,如果實(shí)際的數(shù)據(jù)長(zhǎng)度超過(guò)了棧中預(yù)留的空間,就會(huì)將棧中其他數(shù)據(jù)覆蓋,這種現(xiàn)象被稱為“棧溢出”。棧溢出可能導(dǎo)致一個(gè)不可預(yù)期的錯(cuò)誤,也可能導(dǎo)致一個(gè)精心策劃的執(zhí)行流程發(fā)生改變。可見(jiàn),是否能夠?qū)ψ约核鶎?xiě)程序的運(yùn)行時(shí)狀態(tài)做到心中有數(shù),是能否寫(xiě)出高質(zhì)量、安全代碼的前提保證。
以上兩節(jié)介紹的運(yùn)行時(shí)結(jié)構(gòu)都是由C程序所對(duì)應(yīng)的指令,在內(nèi)存中執(zhí)行,驅(qū)動(dòng)數(shù)據(jù)變化而產(chǎn)生的。C程序只有經(jīng)過(guò)編譯,才能生成目標(biāo)代碼。目標(biāo)代碼將與指令和全局?jǐn)?shù)據(jù)一一對(duì)應(yīng)。編譯的最終目標(biāo)就是能讓C程序的設(shè)計(jì)意圖體現(xiàn)在運(yùn)行時(shí)結(jié)構(gòu)中,這也使得編譯的每個(gè)階段的中心任務(wù)都要為形成運(yùn)行時(shí)結(jié)構(gòu)著想。下一節(jié)我們將概述編譯的過(guò)程。
- MySQL數(shù)據(jù)庫(kù)管理實(shí)戰(zhàn)
- Go語(yǔ)言高效編程:原理、可觀測(cè)性與優(yōu)化
- Mastering QGIS
- Vue.js 3.x從入門(mén)到精通(視頻教學(xué)版)
- Dependency Injection in .NET Core 2.0
- Apache Spark 2 for Beginners
- Python爬蟲(chóng)開(kāi)發(fā)與項(xiàng)目實(shí)戰(zhàn)
- Python高級(jí)機(jī)器學(xué)習(xí)
- 移動(dòng)界面(Web/App)Photoshop UI設(shè)計(jì)十全大補(bǔ)
- Java高并發(fā)核心編程(卷1):NIO、Netty、Redis、ZooKeeper
- Mastering Python Design Patterns
- Machine Learning With Go
- Clean Code in C#
- Learning Nessus for Penetration Testing
- 遠(yuǎn)方:兩位持續(xù)創(chuàng)業(yè)者的點(diǎn)滴思考