簡體   English   中英

GCC 優化器在 nostdlib 代碼中生成錯誤

[英]GCC optimizer generating error in nostdlib code

我有以下代碼:

void cp(void *a, const void *b, int n) {
    for (int i = 0; i < n; ++i) {
        ((char *) a)[i] = ((const char *) b)[i];
    }
}

void _start(void) {
    char buf[20];

    const char m[] = "123456789012345";
    cp(buf, m, 15);

    register int rax __asm__ ("rax") = 60; // exit
    register int rdi __asm__ ("rdi") = 0; // status

    __asm__ volatile (
        "syscall" :: "r" (rax), "r" (rdi) : "cc", "rcx", "r11"
    );

    __builtin_unreachable();
}

如果我用gcc -nostdlib -O1 "./ac" -o "./a"編譯它,我得到一個正常運行的程序,但如果我用-O2編譯它,我得到一個產生分段錯誤的程序。

這是使用-O1生成的代碼:

0000000000001000 <cp>:
    1000:   b8 00 00 00 00          mov    $0x0,%eax
    1005:   0f b6 14 06             movzbl (%rsi,%rax,1),%edx
    1009:   88 14 07                mov    %dl,(%rdi,%rax,1)
    100c:   48 83 c0 01             add    $0x1,%rax
    1010:   48 83 f8 0f             cmp    $0xf,%rax
    1014:   75 ef                   jne    1005 <cp+0x5>
    1016:   c3                      retq   

0000000000001017 <_start>:
    1017:   48 83 ec 30             sub    $0x30,%rsp
    101b:   48 b8 31 32 33 34 35    movabs $0x3837363534333231,%rax
    1022:   36 37 38 
    1025:   48 ba 39 30 31 32 33    movabs $0x35343332313039,%rdx
    102c:   34 35 00 
    102f:   48 89 04 24             mov    %rax,(%rsp)
    1033:   48 89 54 24 08          mov    %rdx,0x8(%rsp)
    1038:   48 89 e6                mov    %rsp,%rsi
    103b:   48 8d 7c 24 10          lea    0x10(%rsp),%rdi
    1040:   ba 0f 00 00 00          mov    $0xf,%edx
    1045:   e8 b6 ff ff ff          callq  1000 <cp>
    104a:   b8 3c 00 00 00          mov    $0x3c,%eax
    104f:   bf 00 00 00 00          mov    $0x0,%edi
    1054:   0f 05                   syscall 

這是使用-O2生成的代碼:

0000000000001000 <cp>:
    1000:   31 c0                   xor    %eax,%eax
    1002:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
    1008:   0f b6 14 06             movzbl (%rsi,%rax,1),%edx
    100c:   88 14 07                mov    %dl,(%rdi,%rax,1)
    100f:   48 83 c0 01             add    $0x1,%rax
    1013:   48 83 f8 0f             cmp    $0xf,%rax
    1017:   75 ef                   jne    1008 <cp+0x8>
    1019:   c3                      retq   
    101a:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

0000000000001020 <_start>:
    1020:   48 8d 44 24 d8          lea    -0x28(%rsp),%rax
    1025:   48 8d 54 24 c9          lea    -0x37(%rsp),%rdx
    102a:   b9 31 00 00 00          mov    $0x31,%ecx
    102f:   66 0f 6f 05 c9 0f 00    movdqa 0xfc9(%rip),%xmm0        # 2000 <_start+0xfe0>
    1036:   00 
    1037:   48 8d 70 0f             lea    0xf(%rax),%rsi
    103b:   0f 29 44 24 c8          movaps %xmm0,-0x38(%rsp)
    1040:   eb 0d                   jmp    104f <_start+0x2f>
    1042:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
    1048:   0f b6 0a                movzbl (%rdx),%ecx
    104b:   48 83 c2 01             add    $0x1,%rdx
    104f:   88 08                   mov    %cl,(%rax)
    1051:   48 83 c0 01             add    $0x1,%rax
    1055:   48 39 f0                cmp    %rsi,%rax
    1058:   75 ee                   jne    1048 <_start+0x28>
    105a:   b8 3c 00 00 00          mov    $0x3c,%eax
    105f:   31 ff                   xor    %edi,%edi
    1061:   0f 05                   syscall 

崩潰發生在103b ,指令movaps %xmm0,-0x38(%rsp)

我注意到如果m包含少於 15 個字符,那么生成的代碼會有所不同,並且不會發生崩潰。

我究竟做錯了什么?

_start不是 function。 它沒有被任何東西調用,並且在入口處堆棧是 16 字節對齊的而不是(按照 ABI 的要求)距離 16 字節 alignment 8 個字節。

(ABI在call之前需要16字節的alignment,並且call推送一個8字節的返回地址。所以在function條目上,RSP-8和RSP+8是16字節對齊的。)


-O2 GCC 使用需要對齊的 16 字節指令來實現由cp()完成的復制,將"123456789012345"從 static 存儲復制到堆棧。

在 -O1 , -O1只使用兩個mov r64, imm64指令將字節獲取到 integer regs 用於 8 字節存儲。 這些不需要 alignment。


解決方法

如果您希望一切正常,只需像普通人一樣在 C 中編寫一個main

或者,如果你想在 asm 中對輕量級的東西進行微基准測試,你可以使用gcc -nostdlib -O3 -mincoming-stack-boundary=3 ( docs ) 告訴 GCC 函數不能假設它們被調用超過8 字節 alignment。 -mpreferred-stack-boundary=3不同,在進行進一步調用之前,它仍將對齊 16。 因此,如果您有其他非葉函數,您可能只想在您的 hacky C _start()上使用一個屬性,而不是影響整個文件。


更糟糕,更hacky的方法是嘗試放置
asm("push %rax"); _start的最頂部將 RSP 修改為 8,其中 GCC 希望在對堆棧執行任何其他操作之前運行它。 GNU C 基本的 asm 語句是隱式的volatile ,所以你不需要asm volatile ,盡管這不會有壞處。

您是 100% 靠自己的,並且負責通過使用適用於您正在使用的任何優化級別的內聯匯編來正確欺騙編譯器。


另一種更安全的方法是編寫自己的輕量級_start調用 main:

// at global scope:
asm(
   ".globl _start \n"
   "_start:       \n"
   "    mov   (%rsp), %rdi  \n"     // argc
   "    lea   8(%rsp), %rsi  \n"    // argv
   "    lea   8(%rsi, %rdi, 8), %rdx \n"   // envp
   "    call  main \n"
          // NOT DONE: stdio cleanup or other atexit stuff
          // DO NOT USE WITH GLIBC; use libc's CRT code if you use libc
   "    mov   %eax, %edi \n"
   "    mov   $231, %eax \n"
   "    syscall"               // exit_group( main() )
);

int main(int argc, char**argv, char**envp) {
   ... your code here
   return 0;
}

如果你不想main返回,你可以pop %rdi mov %rsp, %rsi ; jmp main給它 argc 和 argv 沒有返回地址。

然后main可以通過內聯 asm 退出,或者如果鏈接 libc,則通過調用exit()_exit()退出。 (但如果你鏈接 libc,你通常應該使用它的_start 。)

另請參閱: 如何在沒有 Glibc 的情況下使用 C 中的內聯匯編獲取 arguments 值? 對於其他手卷_start版本; 這很像@zwol's there。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM