2019年6月6日 星期四

gcc 輸出的組合語言蓋掉 stack

返無
在開發 uefi os loader 時, 遇到了一個詭異問題, crt0_x86_64_efi.S _start 會 call _relocate() 做 relocation, 這時候只要在 gcc 8/5 用 -O0 就會發生錯誤, 我用上了 gdb + qemu 在執行時期追組合語言, 查閱 stack/register, 終於讓我找到為什麼?

crt0_x86_64_efi.S
11  .text
12  .align 4
13 
14  .globl _start
15 _start:
16  subq $8, %rsp
17 
23  pushq %rcx
24  pushq %rdx
25 
26  lea image_base(%rip), %rcx
27  lea _DYNAMIC(%rip), %rdx
28 
29  call _relocate
30 
31  popq %rdx
32  popq %rcx

問題出在 crt0_x86_64_efi.S L29, 只要呼叫了 _relocate 之後, crt0_x86_64_efi.S L31, L32 pop 回來的值不會是之前 push 的, 我一直搞不懂為什麼, 起先還以為是爆 stack, 不過看起來不是, rsp 在 _relocate 之前之後, 會回到原來的值。

後來簡化到 _relocate 是空的也會發生, 真是奇怪了, 最後查看反組譯之後的組合語言, 終於理解了。-O0 會生成 (我不太喜歡這辭, 詞語不太順口) 輸出 list 1 的組合語言; -Os, -O1 就不會。list 1 L1, L2 %rcx, %rdx 的值直接填在 stack, 這個位址剛好是 crt0_x86_64_efi.S L23, L24 push 進去的值, 所以就 pop 不回原來的值, 而 %rdx 是 system table, 所以只要用到 system table 的程式碼, 一律掛掉。後來用了 list 2 的方法, push/pop 4 次, 避開這樣的問題, 如此一來 -O0 也可以正常執行。

這問題其實蠻棘手的, 如果一開始就可以看到反組譯的結果, 那就很順利, 可惜運氣不再這邊, 花了許多時間, 才找到這問題。期間也讓我對於 gdb + qemu 的除錯方式更加理解, 讓我得到 ice 等級的除錯利器。

list 1
efi_status_t EFIAPI _relocate(long ldbase, Elf64_Dyn *dyn)
{  
  return 0;
}

0000000000003f76 _relocate:
1    3f76:       48 89 4c 24 08          mov    %rcx,0x8(%rsp)
2    3f7b:       48 89 54 24 10          mov    %rdx,0x10(%rsp)
3    3f80:       b8 00 00 00 00          mov    $0x0,%eax
4    3f85:       c3                      retq

list 2
 1 +       pushq %rcx
 2 +       pushq %rdx
 3         pushq %rcx
 4         pushq %rdx
 5
 6 @@ -25,6 +30,8 @@ _start:
 7
 8         popq %rdx
 9         popq %rcx
10 +       popq %rdx
11 +       popq %rcx

沒有留言:

張貼留言

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

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