一段程序,認識棧幀

1、認識棧幀linux

先來看一段神奇的代碼:windows

wKiom1dioATxGyU3AACN-rpzxJg163.png

 

(windows下,代碼以下)ide

#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
void fun()
{
 printf("You Are Done\n");
 Sleep(2000);
 printf("Suppose The Computer Will Shut Down~~~~\n");  
 //上面這行若是換成system("reboot")之類的呢?
 Sleep(2000);
 exit(1);
}
int fun1(int a, int b)
{
 int *p = &a;
 p--;
 *p = fun;
 int c = 0xcccc;
 return c;
}
int main()
{
 printf("begin run...\n");
 int a = 0;
 int b = 1;
 fun1(a, b);
 printf("you should run here\n");
 return 0;
}

執行結果爲:函數

    linux下:工具

wKioL1dio5ygvWH0AAA2LLJsos8997.png

 

    win下:spa

wKioL1dio8ei0JdJAAAHh3YMUQk709.png

 

結果彷佛都和咱們預期的不同啊3d

按理說,程序從main開始執行指針

中間調用fun1函數調試

調用完畢後應該繼續執行下面的printform

而後輸出:

    you should run here

 

而實際上,程序最終卻進入了fun函數

之因此這樣,是由於棧幀的緣故。

若是你對上面發生的事情感到好奇,能夠接着往下看

 

2、原理解釋

關於棧幀,百度百科是這樣解釋的:

C語言中,每一個棧幀對應着一個未運行完的函數。棧幀中保存了該函數的返回地址和局部變量。

 

也就是說,上面的代碼,在內存方面能夠這樣理解:

wKioL1diqh_ivWi8AABFlbFXhPw385.png

 

簡單解釋一下,

咱們知道C語言中函數中定義的變量是在棧上開闢的,這張圖片就表示棧內存,

其地址從上往下表示從大到小

 

main函數中,前後將a b 入棧,

而後調用fun1(a, b)

圖片中的這個fun1() 其實不許確,它應該是 返回地址

這個 返回地址 就是表示:執行fun1(a, b)完畢後,應該返回到這個地方接着執行main函數中剩下的代碼。

此外,在fun1() 和 b 之間,還應該存放一個東西:棧指針ebp(圖上沒有表示出來)

 

而後參數 a b 是局部變量,也分別入棧,

藉助調試工具,能夠看到,a b 的地址分別爲圖中所示

而後定義一個指針p指向a

接下來p--,這時p指向的地址爲0x0018fc20,也就是 剛剛說的 返回地址

 

這時候,應該能發現,返回地址已經變了, 變成了fun的地址

也就是說,當執行完fun1()後,程序並不會返回到 main函數中調用它的地方,而是接着調用fun函數

wKiom1diq8ix2p3tAABJXz8Vme0189.png

 

這就致使程序難以想象地進入了咱們沒有預想到的地方,調用了咱們本不想調用的函數,

並且因爲這個返回地址的丟失,在調用完畢fun後,程序也會由於找不到返回地址而掛掉。

(我在代碼中執行了  exit(1);  這句話強行終止了程序)

 

以上就是代碼的原理解釋了。

接下來,利用剛剛所get到的棧幀方面的知識,能夠作一個事情:

 

 

3、修改b的值

要求:不要直接修改a、b變量,而經過棧幀,實現修改a、b變量的值

 

代碼:

void fun1(int a, int b)
{
 int *p = &a;
 p -= 2;
 int ReturnAddr = *p;   //返回值
 //修改main中的a
 p = ReturnAddr - 4 * 2;
 *p = 11111;
 //修改main中的b
 p = ReturnAddr - 4 * 5;
 *p = 12345;
}
int main()
{
 printf("begin run...\n");
 int a = 0;
 int b = 1;
 fun1(a, b);
 printf("you should run here\n");
 printf("%d\n", a);
 printf("%d\n", b);
 return 0;
}

以前畫的內存圖比較簡單,要想實現這個要求,必須進一步瞭解棧幀是怎麼存放的了,下面是比較詳細的棧內存圖:

wKioL1diu7WA992LAAA_PuUQFy0463.png

 

 

linux代碼:

wKiom1dizxajNDYRAAB2f4Y7YMw939.png

運行結果

wKiom1dizzaQHhPPAAAP5GW3DOg271.png

(gcc 和 vs編譯的程序的棧幀存放規則不一樣)

相關文章
相關標籤/搜索