簡體   English   中英

Clang 11 和 GCC 8 O2 中斷內聯組裝

[英]Clang 11 and GCC 8 O2 Breaks Inline Assembly

我有一小段代碼,其中有一些內聯程序集可以在 O0 中正確打印 argv[0],但不會在 O2 中打印任何內容(使用 Clang 時。另一方面,GCC 打印存儲在 envp[0] 中的字符串打印 argv[0] 時)。 這個問題也僅限於 argv(其他兩個函數參數可以在啟用或不啟用優化的情況下按預期使用)。 我用 GCC 和 Clang 對此進行了測試,兩個編譯器都有這個問題。

這是代碼:

void exit(unsigned long long status) {
    asm volatile("movq $60, %%rax;" //system call 60 is exit
        "movq %0, %%rdi;" //return code 0
        "syscall"
        : //no outputs
        :"r"(status)
        :"rax", "rdi");
}

int open(const char *pathname, unsigned long long flags) {
    asm volatile("movq $2, %%rax;" //system call 2 is open
        "movq %0, %%rdi;"
        "movq %1, %%rsi;"
        "syscall"
        : //no outputs
        :"r"(pathname), "r"(flags)
        :"rax", "rdi", "rsi");
        return 1;
}

int write(unsigned long long fd, const void *buf, size_t count) {
    asm volatile("movq $1, %%rax;" //system call 1 is write
        "movq %0, %%rdi;"
        "movq %1, %%rsi;"
        "movq %2, %%rdx;"
        "syscall"
        : //no outputs
        :"r"(fd), "r"(buf), "r"(count)
        :"rax", "rdi", "rsi", "rdx");
        return 1;
}

static void entry(unsigned long long argc, char** argv, char** envp);

/*https://www.systutorials.com/x86-64-calling-convention-by-gcc/: "The calling convention of the System V AMD64 ABI is followed on GNU/Linux. The registers RDI, RSI, RDX, RCX, R8, and R9 are used for integer and memory address arguments
and XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point arguments.
For system calls, R10 is used instead of RCX. Additional arguments are passed on the stack and the return value is stored in RAX."*/

//__attribute__((naked)) defines a pure-assembly function
__attribute__((naked)) void _start() {
    asm volatile("xor %%rbp,%%rbp;" //http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html: "%ebp,%ebp sets %ebp to zero. This is suggested by the ABI (Application Binary Interface specification), to mark the outermost frame."
    "pop %%rdi;" //rdi: arg1: argc -- can be popped off the stack because it is copied onto register
    "mov %%rsp, %%rsi;" //rsi: arg2: argv
    "mov %%rdi, %%rdx;"
    "shl $3, %%rdx;" //each argv pointer takes up 8 bytes (so multiply argc by 8)
    "add $8, %%rdx;" //add size of null word at end of argv-pointer array (8 bytes)
    "add %%rsp, %%rdx;" //rdx: arg3: envp
    "andq $-16, %%rsp;" //align stack to 16-bits (which is required on x86-64)
    "jmp %P0" //https://stackoverflow.com/questions/3467180/direct-c-function-call-using-gccs-inline-assembly: "After looking at the GCC source code, it's not exactly clear what the code P in front of a constraint means. But, among other things, it prevents GCC from putting a $ in front of constant values. Which is exactly what I need in this case."
    :
    :"i"(entry)
    :"rdi", "rsp", "rsi", "rdx", "rbp", "memory");
}

//Function cannot be optimized-away, since it is passed-in as an argument to asm-block above
//Compiler Options: -fno-asynchronous-unwind-tables;-O2;-Wall;-nostdlibinc;-nobuiltininc;-fno-builtin;-nostdlib; -nodefaultlibs;--no-standard-libraries;-nostartfiles;-nostdinc++
//Linker Options: -nostdlib; -nodefaultlibs
static void entry(unsigned long long argc, char** argv, char** envp) {
    int ttyfd = open("/dev/tty", O_WRONLY);

    write(ttyfd, argv[0], 9);
    write(ttyfd, "\n", 1);

    exit(0);
}

編輯:添加了系統調用定義。

編輯:將 rcx 和 r11 添加到系統調用的 clobber 列表中修復了 clang 的問題,但 gcc 出現錯誤。

編輯:GCC 實際上沒有錯誤,但是我的構建系統 (CodeLite) 中出現了某種奇怪的錯誤,因此該程序運行了某種部分構建的程序,盡管 GCC 報告了有關它無法識別其中兩個的錯誤傳入的編譯器標志。 對於 GCC,請改用這些標志:-fomit-frame-pointer;-fno-asynchronous-unwind-tables;-O2;-Wall;-nostdinc;-fno-builtin;-nostdlib; -nodefaultlibs;--no-standard-libraries;-nostartfiles;-nostdinc++。 由於 Clang 支持上述 GCC 選項,您也可以將這些標志用於 Clang。

  1. 根據gcc 手冊,您不能在naked函數中使用擴展 asm,只能使用基本 asm。 您不需要將損壞的寄存器通知編譯器(因為它無論如何都不會對它們做任何事情;在naked函數中,您負責所有寄存器管理)。 並且不需要在擴展操作數中傳遞entry地址; 只需執行jmp entry

    (在我的測試中,您的代碼根本無法編譯,所以我假設您沒有向我們展示您的確切代碼 - 下次請這樣做,以免浪費人們的時間。)

  2. Linux x86-64 syscall系統調用允許破壞rcxr11寄存器,因此您需要將它們添加到系統調用的破壞列表中。

  3. 在跳轉到entry之前,您將堆棧與 16 字節邊界對齊。 然而,16字節對齊規則是基於這樣的假設,你將調用與函數call ,這將推動一個額外的8個字節到堆棧中。 因此,被調用的函數實際上期望堆棧最初不是 16 的倍數,而是比 16 的倍數多或少 8。因此您實際上是在錯誤地對齊堆棧,這可能是導致各種神秘的麻煩。

    因此,要么將您的jmp替換為call ,要么從rsp再減去 8 個字節(或者只是push您選擇的一些 64 位寄存器)。

  4. 樣式說明: unsigned long在 Linux x86-64 上已經是 64 位,因此在任何地方使用它代替unsigned long long會更慣用。

  5. 一般提示:了解擴展 asm 中的寄存器約束。 你可以讓編譯器為你加載你想要的寄存器,而不是在你的 asm 中編寫指令來自己做。 所以你的exit函數可能看起來像:

    void exit(unsigned long status) {
        asm volatile("syscall"
            : //no outputs
            :"a"(60), "D" (status)
            :"rcx", "r11");
    }

這特別為您節省了一些指令,因為status已經在函數入口的%rdi寄存器中。 對於您的原始代碼,編譯器必須將它移到其他地方,以便您可以自己將其加載到%rdi

  1. 您的open函數始終返回 1,這通常不是實際打開的 fd。 因此,如果您的程序在標准輸出重定向的情況下運行,您的程序將寫入重定向的標准輸出,而不是像它似乎想要做的那樣寫入 tty。 事實上,這使得open系統調用完全沒有意義,因為你從不使用你打開的文件。

    您應該安排open返回系統調用實際返回的值,當syscall返回時,該值將保留在%rax寄存器中。 您可以使用輸出操作數將其存儲在臨時變量中(編譯器可能會對其進行優化),然后返回該變量。 您需要使用數字約束,因為它與輸入操作數位於同一寄存器中。 我把它留作你的練習。 如果您的write函數實際上返回了寫入的字節數,那同樣會很好。

暫無
暫無

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

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