最近踩到這個地雷, 這是在我要將 kernel loader 改為使用
big real mode 以便載入超過 1M 的 kernel 時遇到的。在 x86 16bit 環境下使用 gas, gcc 產生的程式碼有點小差異。
16 111: e8 44 00 call 158 <init_bss_asm>
L16 這是 gas 組語產生的 machine code。
69 1ac: 66 e8 65 ff ff ff calll 117 <asm_4g_memcpy>
L69 這是 gcc c 語言產生的 machine code。多了一個 66 prefix, 改變 operand size 大小。在 16 bit 模式下, operand size 會改變成 32bit, 本來使用 call 指令後 (in 16 bit mode), %esp 是 -2, 但是加了這個 0x66 prefix %esp 會 -4, 所以要用 retl 才會讓 %esp +4 回來。L54, L71 的 ret 指令就差了一個 0x66 也是類似的道理。
結果是這樣的:
呼叫 init_bss_asm 前後, %esp 會加減 2; 呼叫 asm_4g_memcpy, %esp 卻會加減 4。
asm_4g_memcpy 是組語 function, 我在 c code 呼叫 asm_4g_memcpy, %esp 會減 4。而原本 asm_4g_memcpy 結尾寫 ret, 離開 asm_4g_memcpy 返回原 function 後, %esp 會加 2, 要寫成 retl 才會加 4。原本我是寫 ret, 結果造成 stack 在呼叫 asm_4g_memcpy 之後會出問題。
這問題花了我兩個下午才找到, 還得靠著 bochs 才能發現。除非直些看 machine code, 否則很難發現這問題。因為:
gcc -DIPC -std=c99 -fno-stack-protector -m32 -ffreestanding -fno-builtin -g -Iinclude -I../include -S kernel_loader.c
從 kernel_loader.s 只能看到
call asm_4g_memcpy, 而不是
calll asm_4g_memcpy, 看不到 66 這個 prefix。
call 和 calll 會翻成不同的 machine code (16bit mode):
call -> e8 XX
calll -> 66 e8 XX
測試 source code:
完整範例:
https://github.com/descent/progs/tree/master/gas_gcc_16bit
很開心的以為搞定, 結果遇上另外的
難題。
而若是在組合語言 call c function, 要用
calll c_func, 不能用
call c_func, 免得 %esp 又差了 2。
ref:
x86/x64 指令编码内幕(适用于 AMD/Intel)
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。