2017年10月18日 星期三

compiler [7] - code generator - funcall call, pass argument

倦怠期中, 無限取材休刊
這陣子有點懶懶的, 突然失去寫技術文章的熱情, 我花了一些時間重溫 c++ virtual function, 卻提不起勁把這些東西整理成一篇文章, 可能真有些倦怠感, 沒意外的話這篇應該會是倦怠期最後一篇的技術文章。不只這樣, 連正在進行的組譯器我也提不起勁繼續下去, 而好不容易理清的 elf section 我竟然也沒動力整理寫下來, 所以才跑去寫俄羅斯方塊。也許該暫停一下這些東西, 轉換一下學習方向。

倦怠期間沒什麼在學習, 看了 jojo 動畫、王牌大律師, 這樣的生活蠻開心的, 也很舒服, 但不知怎麼的, 就是覺得哪裡怪怪的 ... 「學如逆水行舟不進則退」如果一天下來都沒進步, 這樣的生活令我惶恐, 不過適度的放鬆也是必要的, 我懷疑我放鬆過了頭。

在四則運算告一個段後之後, 本來應該是 if/else, 不過 if/else 不是太難, 就先跳過, 來看看 c function 的呼叫應該怎麼產生對應的組合語言。本篇文章介紹怎麼產生 c function 參數傳遞的組合語言。也許有人知道用 stack 用來傳遞參數, 由右而左的順序放進 stack, 除了這些, 還需要其他的知識才能產生對應的組合語言。

把 char c, 傳給 fun1(int a) 時, c 需要做什麼特別的事情嗎?

依照慣例, 先來一個很簡短的 c 程式, 是簡單的 c 函式呼叫, 來看 gcc 會輸出什麼樣的組合語言?

f.c 是 source code, list 1 則是 gcc 輸出的 x86 32bit 組合語言。

fc.c
 1 
 2 char func678(char c)
 3 {
 4   return c;
 5 }
 6 int main(int argc, char *argv[])
 7 {
 8   char func678(char c);
 9   func678(5);
10   return 0;
11 }

list 1. gcc -m32 -S fc.c => fc.s
 1  .file "fc.c"
 2  .text
 3  .globl func678
 4  .type func678, @function
 5 func678:
 6  pushl %ebp
 7  movl %esp, %ebp
 8  subl $4, %esp
 9  movl 8(%ebp), %eax
10  movb %al, -4(%ebp)
11  movzbl -4(%ebp), %eax
12  leave
13  ret
14  .size func678, .-func678
15  .globl main
16  .type main, @function
17 main:
18  pushl %ebp
19  movl %esp, %ebp
20  pushl $5
21  call func678
22  addl $4, %esp
23  movl $0, %eax
24  leave
25  ret
26  .size main, .-main
27  .ident "GCC: (GNU) 5.4.0"
28  .section .note.GNU-stack,"",@progbits

function 參數的傳遞比想像中複雜, 當 function 沒有 prototype 時或是使用 K&R style 的宣告或是 ... 這種參數 - ex: printf(const char *format, ...), 會發動 integer promtion, 這很好理解, 可以參考《“对于那些没有原型的函数,传递给函数的实参将进行缺省参数提升”是什么意思?》, 請不要小看中文世界的知識量, 你的問題說不定並沒特別需要到英文世界找答案, 知乎上的回答很有水準, 有這樣的平台, 是中文使用者的福氣, 讓我們別輸懂英文的人太多, 但請不要把這些話理解成我覺得英文不重要, 英文的重要性是已經到不需要特別指出來了。

fc.s L20 那行是 integer promotion 嗎? 因為有 fc.c L8 那行 (有 function prototype), 所以上述規則並不是用在這個情況, 由於 push 4 byte 長度的 5 (list 1 L20), 應該可以輸出 pushb $5, 這樣只要 push 一個 byte 就好, 而 pushl $5, 看起來很像做了 integer promotion, 把 5 傳給 char c 提升到 int。

真相是怎麼樣呢?

為了找到答案, 我參閱了:

  1. c11 spec
  2. C 語言參考手冊
  3. C 編譯器剖析
  4. Linux C 编程一站式学习
  5. C 語言程序設計 - 現代方法
  6. 標準 C 語言指南: p166, p307。

並在
發問, 結合這些回答以及找到的資料再加上 c11 spec 6.5.2.2 function call 查到的

c11 spec 6.5.2.2
If the expression that denotes the called function has a type that does
include a prototype, the arguments are implicitly converted,
as if by assignment, to the types of the corresponding parameters,
taking the type of each parameter to be the unqualified version
of its declared type. The ellipsis notation in a function prototype
declarator causes argument type conversion to stop after
the last declared parameter. The default argument
promotions are performed on trailing arguments

我得出了結論:
把 5 傳給 char c, 相當於 char c=5, 這會用到 assign 那條轉換規則, 參考《第 15 章 数据类型详解/3. 类型转换 3.3. 由赋值产生的类型转换 (implicit conversion)》, 5 的 type 是 int (不是 short, 也不是 unsinged int), 所以會做 implicit conversion (所以若傳入 300, 就爆了, 翻出來的組合語言會傳入 44, 300 = 0x12c, 0x2c = 44), 而 function 的參數傳遞則是 facebook 討論區說的 ABI, 需要用 4 byte alignment 方式傳入。

節錄: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/LowLevelABI/130-IA-32_Function_Calling_Conventions/IA32.html

The caller places arguments in the parameter area in reverse order, in 4-byte chunks. That is, the rightmost argument has the highest address.

Figure 2  Argument assignment with arguments of the fundamental data types


由於這些巧合, 看起來就像 integer promotion。

知道了這些之後, 就知道該如何輸出函式參數傳遞的組合語言了。

我一開始並不知道這些規則, 而是在寫到這部份時, 自然就會有這些疑問, 我要根據哪些規則產生對應的程式碼呢? 才開始找尋問題的答案。比想像中難得多。

最後再回到沒有 prototype 時, 看看有什麼不同。

no_prototype.c
 1 
 6 int main(int argc, char *argv[])
 7 {
 9   func678(300);
10   return 0;
11 }

no_prototype.s
 1  .file "c.c"
 2  .text
 3  .globl main
 4  .type main, @function
 5 main:
 6  leal 4(%esp), %ecx
 7  andl $-16, %esp
 8  pushl -4(%ecx)
 9  pushl %ebp
10  movl %esp, %ebp
11  pushl %ebx
12  pushl %ecx
13  call __x86.get_pc_thunk.ax
14  addl $_GLOBAL_OFFSET_TABLE_, %eax
15  subl $12, %esp
16  pushl $300
17  movl %eax, %ebx
18  call func678@PLT
19  addl $16, %esp
20  movl $0, %eax
21  leal -8(%ebp), %esp
22  popl %ecx
23  popl %ebx
24  popl %ebp
25  leal -4(%ecx), %esp
26  ret
27  .size main, .-main
28  .section .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
29  .globl __x86.get_pc_thunk.ax
30  .hidden __x86.get_pc_thunk.ax
31  .type __x86.get_pc_thunk.ax, @function
32 __x86.get_pc_thunk.ax:
33  movl (%esp), %eax
34  ret
35  .ident "GCC: (Debian 7.2.0-8) 7.2.0"
36  .section .note.GNU-stack,"",@progbits

no_prototype.s L16 的 300 出現了, 不會被截斷為 44。

沒有留言:

張貼留言

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

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