一道有趣的面試題

.html

.面試

.編程

.函數

.spa

同事問了我一道有意思的面試題,通過一番琢磨,解出了答案,遂把原題和個人答案記錄以下:指針

問題:void f(void) 如何實現,能夠打印出 x 是任何一個值?code

1 int main(int argc, char** argv)
2 {
3     int x = 10;
4     f();
5     printf("x = %d\n", x);
6     return 0;
7 }

 

我提供了兩種解題思路,須要的前置知識以下:htm

思路1:Unix 文件IO。對此前置知識感興趣的同窗能夠參考我以前的文章《 一塊兒學 Unix 環境高級編程 (APUE) 之 文件 IO 》blog

思路2:x86 彙編。ip


思路1:
在 f() 函數裏面隨意打印一個值,而後把標準輸出(stdout)重定向到 /dev/null,讓後面的代碼沒法 printf(3) 到 console 上。

 1 #include <fcntl.h>
 2 #include <stdio.h>
 3 #include <unistd.h>
 4 
 5 #include <sys/stat.h>
 6 #include <sys/types.h>
 7 
 8 void f(void)
 9 {
10     int fd = -1;
11     printf("3\n");
12     if ((fd = open("/dev/null", O_WRONLY)) < 0)
13     {
14         perror("Open /dev/null failed!");
15         return ;
16     }
17     dup2(fd, 1); // stdout 的文件描述符是1。
18     close(fd);
19 }


解釋:因爲 printf(3) 會將參數輸出到標準輸出(stdout)流,經過 dup2(2) 函數將 stdout 關閉,並將 /dev/null 的文件描述符拷貝到 1 號文件描述符(stdout 的文件描述符是1),就可使 printf(3) 向 1 號文件描述符的輸出全都重定向到 /dev/null 文件中。/dev/null 是一個像黑洞同樣的特殊文件,全部寫入 /dev/null 的內容都會消失,所以 dup2(2) 以後全部 printf(3) 的內容都將不可見。

 

同事說這種實現方式有點詭異,也許不是題意的目的,題目是但願可以經過 f() 函數修改 main() 函數的局部變量 x 的值,那麼接下來咱們來看看第二種方案。

 


思路2:
取出 main() 的棧指針地址 rbp,而後給 rbp-4 的地址從新賦值就能夠了。

1 void f(void)
2 {
3     asm volatile(
4     "popq %%rbp;\n\t"
5     "movl $3, -4(%%rbp);\n\t"
6     "ret;\n\t"
7     :::
8     );
9 }


先看下彙編代碼是如何存儲 main() 函數的局部變量 x 的。

>$ gcc -Wall -S 2.c
>$ cat -n 2.s

如下是摘錄的 main() 函數代碼:

    34    main:
    35    .LFB1:
    36        .cfi_startproc
    37        pushq    %rbp
    38        .cfi_def_cfa_offset 16
    39        .cfi_offset 6, -16
    40        movq    %rsp, %rbp          ; 記錄 main() 函數棧幀開始的地址
    41        .cfi_def_cfa_register 6
    42        subq    $32, %rsp           ; 分配 32byte 的棧空間
    43        movl    %edi, -20(%rbp)
    44        movq    %rsi, -32(%rbp)
    45        movl    $10, -4(%rbp)       ; 將變量 x 的值入棧
    46        call    f                   ; 調用 f() 函數
    47        movl    -4(%rbp), %eax
    48        movl    %eax, %esi
    49        leaq    .LC0(%rip), %rdi
    50        movl    $0, %eax
    51        call    printf@PLT
    52        movl    $0, %eax
    53        leave
    54        .cfi_def_cfa 7, 8
    55        ret
    56        .cfi_endproc

說明:
1. 第40行將棧幀開始的地址記錄到 rbp 寄存器;
2. 第42行移動棧指針(rsp),爲 main() 函數分配了 32byte 的棧空間用於存儲局部變量;
3. 第45行將常數 10 存放到 rbp-4 的位置。

接下來再來看下 f() 函數所作的處理:

     5    f:
     6    .LFB0:
     7        .cfi_startproc
     8        pushq    %rbp
     9        .cfi_def_cfa_offset 16
    10        .cfi_offset 6, -16
    11        movq    %rsp, %rbp          ; 記錄 f() 函數棧幀開始的地址
    12        .cfi_def_cfa_register 6
    13    #APP
    14    # 5 "2.c" 1                     ; APP 到 NO_APP 之間是咱們本身寫的指令
    15        popq %rbp;                  ; 彈棧,獲得 main() 函數棧幀的起始地址
    16        movl $3, -4(%rbp);          ; 修改 main() 函數局部變量 x 的值。
                                          ; 還記得上面爲 x 賦值的時候用的是哪一個地址嗎?
; 就是 main() 函數的棧幀裏 rbp
-4 的位置。 17 ret; ; 直接返回,不要再執行編譯器生成的彈棧操做了。 18 19 # 0 "" 2 20 #NO_APP 21 nop 22 popq %rbp 23 .cfi_def_cfa 7, 8 24 ret 25 .cfi_endproc

註釋中已經解釋得很清楚了,原理就是先找到 main() 函數的棧幀的位置,再找到變量 x 所在的位置,最後經過指針修改變量 x 所在位置的值就能夠了。

相關文章
相關標籤/搜索