1. 程式人生 > >一道面試題重新拾起C語言

一道面試題重新拾起C語言

今天一個技術群裡有人發了一個脈脈上的面試題,如下:
在這裡插入圖片描述

這個題如果只用大學的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位地址來存放。