一道面試題重新拾起C語言
阿新 • • 發佈:2018-12-25
今天一個技術群裡有人發了一個脈脈上的面試題,如下:
這個題如果只用大學的C語言知識肯定無法解決的。還好我以前看過《深入理解計算機系統》這本書,知道一個程式其實就是一堆地址和一堆指令組成,這個提明顯需要在子函式裡面修改父函式的棧地址。我們知道棧地址是從高地址往的地址分配記憶體的,如下圖所示:
在做函式跳轉的時候,有兩個很關鍵的暫存器,ebp(64位是rbp)和esp(64位是rsp)。ebp是儲存的一個函式的棧基地址,esp是儲存的當前執行程式碼的地址。也就是所有函式裡面分配的臨時變數都會在ebp和esp記錄的地址之間。做函式跳轉的時候,首先會儲存父函式的當前棧幀地址,也就是ebp暫存器的值,然後把ebp切換到當前的esp,開始執行新的函式程式碼。
有了這些基礎知識,思路也就有了:我們需要在pass函式裡面先獲取到當前ebp的值,然後倒推上一個函式的esp的值,然後給減去分配臨時變數的偏移量,賦值為456即可。
下面開始解這個題:
先把如下C語言程式碼編譯後,生成彙編程式碼:
#include <stdio.h>
void pass()
{
}
int main()
{
int x = 123;
pass();
printf("%d\n", x);
return 0;
}
objdump -D a.out之後main函式和pass函式如下:
080483c4 <pass>:
80483c4: 55 push %ebp
80483c5: 89 e5 mov %esp,%ebp
80483c7: 5d pop %ebp
80483c8: c3 ret
080483c9 <main>:
80483c9: 55 push %ebp
80483ca: 89 e5 mov %esp,%ebp
80483cc: 83 e4 f0 and $0xfffffff0,%esp
80483cf: 83 ec 20 sub $0x20,%esp
80483d2: c7 44 24 1c 7b 00 00 movl $0x7b,0x1c(%esp)
80483d9: 00.
80483da: e8 e5 ff ff ff call 80483c4 <pass>
80483df: b8 c4 84 04 08 mov $0x80484c4,%eax
80483e4: 8b 54 24 1c mov 0x1c(%esp),%edx
80483e8: 89 54 24 04 mov %edx,0x4(%esp)
80483ec: 89 04 24 mov %eax,(%esp)
80483ef: e8 00 ff ff ff call 80482f4 < [email protected]>
80483f4: b8 00 00 00 00 mov $0x0,%eax
80483f9: c9 leave
80483fa: c3 ret
這裡可以通過“movl $0x7b,0x1c(%esp)”看到變數x的地址是esp地址加上0x1c。而由於產生了函式呼叫,並且在pass函式裡面需要儲存ebp的地址,所以這裡esp會往下偏移8個地址(原因是call指令和push指令都會做一次入棧操作)。我們通過彙編獲取到當前函式的ebp就可以計算出來上一個函式的esp暫存器的值了。所以我們最終的解題程式碼如下:
#include <stdio.h>
void pass()
{
int ebp = 0;
asm("movl %%ebp, %0 \n\t":"=r"(ebp));
int *px = (int*) (ebp +0x1c + 0x8);
*px = 456;
}
int main()
{
int x = 123;
pass();
printf("%d\n", x);
return 0;
}
需要注意的是以上的方法只能夠在32位系統上面正確執行,如果在64位系統上面,需要操作的是rbp,並且地址需要使用64位地址來存放。