2013年1月31日 星期四

c function 參數傳遞, 如何存取傳進來的參數

test env: x86/linux

func.c
 1 #include <stdio.h>
 2 
 3 typedef signed int s32;
 4 typedef unsigned int u32;
 5 
 6 static inline u32 asm_get_esp(void)
 7 {
 8   u32 v=0;
 9   __asm__ __volatile__
10     (
11       "movl %%esp, %%eax\n"
12       : "=a"(v)// output
13     );
14   return v;
15 }
16 
17 
18 void func(char c, int i, const char *ptr)
19 {
20   c+=2;
21   i+=3;
22   ++ptr;
23   printf("%c %d %p\n", c, i, ptr);
24 }
25 
26 void asm_func(char c, int i, const char *ptr);
27 
28 
29 
30 int main(int argc, const char *argv[])
31 {
32   u32 esp = asm_get_esp();
33 
34   func('a', 9, (char*)(0x1234));        
35   #if 0
36   printf("esp: %x\n", esp);
37   asm_func('a', 9, (char*)(0x1234));        
38   printf("esp: %x\n", esp);
39   #endif
40   return 0;
41 }


這是 func.c 的某部份反組譯, 透過反組譯來查看 c 如何使用 stack 來傳遞參數, 如何存取傳進來的參數。

objdump
 1 0804843a <func>:
 2  804843a:       55                      push   %ebp
 3  804843b:       89 e5                   mov    %esp,%ebp
 4  804843d:       83 ec 28                sub    $0x28,%esp
 5  8048440:       8b 45 08                mov    0x8(%ebp),%eax   // 'a'
 6  8048443:       88 45 f4                mov    %al,-0xc(%ebp)
 7  8048446:       0f b6 45 f4             movzbl -0xc(%ebp),%eax
 8  804844a:       83 c0 02                add    $0x2,%eax
 9  804844d:       88 45 f4                mov    %al,-0xc(%ebp)
10  8048450:       83 45 0c 03             addl   $0x3,0xc(%ebp)   // 9 + 3
11  8048454:       83 45 10 01             addl   $0x1,0x10(%ebp)  // 0x1234 + 1
12  8048458:       0f be 45 f4             movsbl -0xc(%ebp),%eax
13  804845c:       8b 55 10                mov    0x10(%ebp),%edx
14  804845f:       89 54 24 0c             mov    %edx,0xc(%esp)
15  8048463:       8b 55 0c                mov    0xc(%ebp),%edx
16  8048466:       89 54 24 08             mov    %edx,0x8(%esp)
17  804846a:       89 44 24 04             mov    %eax,0x4(%esp)
18  804846e:       c7 04 24 90 85 04 08    movl   $0x8048590,(%esp)
19  8048475:       e8 86 fe ff ff          call   8048300 <printf@plt>
20  804847a:       c9                      leave  
21  804847b:       c3                      ret    
22 
23 0804847c <main>:
24  804847c:       55                      push   %ebp
25  804847d:       89 e5                   mov    %esp,%ebp
26  804847f:       83 e4 f0                and    $0xfffffff0,%esp
27  8048482:       83 ec 20                sub    $0x20,%esp
28  8048485:       e8 92 ff ff ff          call   804841c <asm_get_esp>
29  804848a:       89 44 24 1c             mov    %eax,0x1c(%esp)
30  804848e:       c7 44 24 08 34 12 00    movl   $0x1234,0x8(%esp)
31  8048495:       00 
32  8048496:       c7 44 24 04 09 00 00    movl   $0x9,0x4(%esp)
33  804849d:       00 
34  804849e:       c7 04 24 61 00 00 00    movl   $0x61,(%esp)
35  80484a5:       e8 90 ff ff ff          call   804843a <func>
36  80484aa:       b8 00 00 00 00          mov    $0x0,%eax
37  80484af:       c9                      leave  
38  80484b0:       c3                      ret   




34   func('a', 9, (char*)(0x1234));        

從 L5, 10, 11 可以看到, 用 %epb 存取 func 傳入的 'a', 9, (char*)0x1234,
%epb + 8 -> 'a'
%epb + 0xc -> 9
%epb + 0x10 -> 0x1234

參考以下的 stack 位址 (這是我用 gdb 印出的位址, 每個系統可能會不同) 可以更清楚。
綠色是 main(), 藍色是 func()。

執行  func('a', 9, (char*)(0x1234));
'a', 9,  (char*)(0x1234) 分別被複製到 stack 位址 (這就是所謂的 call by value)
0xffffd6e0 + 8
0xffffd6e0 + 4
0xffffd6e0

func('a', 9, (char*)(0x1234)); 的下一個指令會被 push 到 stack, 就是圖中的 ret addr, 而 main() 的 %epb 會被 push, func() epb 會被指定為 0xffffd6d8, 所以這就是
%epb + 8 -> 'a'
%epb + 0xc -> 9
%epb + 0x10 -> 0x1234
可以存取到傳進來的參數的方法。

20   c+=2;
21   i+=3;
22   ++ptr;
 
 5  8048440:       8b 45 08                mov    0x8(%ebp),%eax   // 'a'
 6  8048443:       88 45 f4                mov    %al,-0xc(%ebp)
 7  8048446:       0f b6 45 f4             movzbl -0xc(%ebp),%eax
 8  804844a:       83 c0 02                add    $0x2,%eax 
10  8048450:       83 45 0c 03             addl   $0x3,0xc(%ebp)   // 9 + 3
11  8048454:       83 45 10 01             addl   $0x1,0x10(%ebp)  // 0x1234 + 1
 
不過 'a' /* c+=2 */ 的處理方式比較複雜, 不像
%epb + 0xc 直接加 3 (9+3) /* i+=3) */
%epb + 0x10 直接加 1 (0x1234+1)  /* ++ptr */
而是很複雜的方式, 也許是長度為 1byte 的關係。

我也不知道為什麼 %esp 要減掉 0x28
 4  804843d:       83 ec 28                sub    $0x28,%esp

下圖是我自己得到的結果可能有些錯誤, 我還不是很懂組合語言, 不過和主題無關, 是我自己想觀察的部份。

我要描述的主題應該是正確的。這不是很好懂, 得花腦力看一下組合語言, 也要思考一下。因為我被這部份困惑很久了, 決心用自己的方法來理解, 我已經儘量將東西簡化了, 一次只討論一個小部份。

stack frame
(main esp) 0xffffd6e0 + 8 0x1234
(main esp) 0xffffd6e0 + 4 9
(main esp) 0xffffd6e0 'a'
0xffffd6dc calll func ret addr
0xffffd6d8 push %ebp; mov %esp,%ebp caller ebp
0xffffd6d4
0xffffd6d0
0xffffd6cc 'a'+2
0xffffd6c8
0xffffd6c4
0xffffd6c0
0xffffd6bc 0x1234 + 1
0xffffd6b8 9+3
0xffffd6b4 'a'+2
(func esp)0xffffd6b0 "%c %d %p\n"

callee (func) epb = 0xffffd6d8

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。