Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/59.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/linux/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Clang 11和GCC 8 O2断开直列组件_C_Linux_Assembly_X86 64_Inline Assembly - Fatal编程技术网

Clang 11和GCC 8 O2断开直列组件

Clang 11和GCC 8 O2断开直列组件,c,linux,assembly,x86-64,inline-assembly,C,Linux,Assembly,X86 64,Inline Assembly,我有一个简短的代码片段,其中包含一些内联程序集,可以在O0中正确打印argv[0],但在使用Clang时不会在O2中打印任何内容。另一方面,GCC在打印argv[0]时打印存储在envp[0]中的字符串。此问题也仅限于argv。在启用或不启用优化的情况下,其他两个函数参数都可以按预期使用。我用GCC和Clang测试了这个问题,两个编译器都有这个问题 代码如下: void exit(unsigned long long status) { asm volatile("movq $60, %

我有一个简短的代码片段,其中包含一些内联程序集,可以在O0中正确打印argv[0],但在使用Clang时不会在O2中打印任何内容。另一方面,GCC在打印argv[0]时打印存储在envp[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异步展开表-氧气-墙-诺斯蒂尼-fno内置-诺斯特利布-nodefaultlibs-没有标准的图书馆-诺斯塔特档案-nostinc++。由于Clang支持上述GCC选项,您还可以将这些标志用于Clang

您不能在裸函数中使用扩展asm,只能使用基本asm。您不需要通知编译器被破坏的寄存器,因为它无论如何都不会对它们做任何事情;在裸体功能中,您负责所有注册管理。在扩展操作数中传递条目地址是不必要的;只需执行jmp条目

在我的测试中,您的代码根本不可编译,所以我假设您没有向我们展示您的确切代码-下次请这样做,以避免浪费人们的时间

Linux x86-64系统调用允许关闭rcx和r11寄存器,因此需要将它们添加到系统调用的关闭列表中

在跳转到条目之前,将堆栈与16字节边界对齐。但是,16字节对齐规则基于这样一种假设,即您将使用call调用函数,这将在堆栈上增加8个字节。因此,被调用函数实际上期望堆栈最初不是16的倍数,而是比16的倍数多8或少8。因此,实际上,您正在错误地对齐堆栈,这可能会导致各种各样的神秘问题

所以,要么用call替换jmp,要么从rsp中再减去8个字节,或者只推送一些您选择的64位寄存器

样式说明:在Linux x86-64上,unsigned long已经是64位了,因此使用它来代替unsigned long-where更为惯用

一般提示:了解扩展asm中的寄存器约束。您可以让编译器为您加载所需的寄存器,而不是自己在asm中编写指令。因此,您的退出函数可以改为如下所示:

这尤其为您节省了一些指令,因为状态已在函数项的%rdi寄存器中。对于原始代码,编译器必须将其移动到其他地方,以便您可以自己将其加载到%rdi中

open函数始终返回1,这通常不是实际打开的fd。因此,如果您的程序在标准输出重定向的情况下运行,那么您的程序将写入重定向的标准输出,而不是像它看起来想要的那样写入tty。事实上,这使得open系统调用完全没有意义,因为您从未使用您打开的文件

您应该安排open返回系统调用实际返回的值,当syscall返回时,该值将保留在%rax寄存器中。您可以使用输出操作数将其存储在编译器可能会优化的临时变量中,然后返回该变量。您需要使用数字约束,因为它与输入操作数位于同一寄存器中。我把这个留给你做练习。同样,如果您的write函数实际返回写入的字节数也会很好

您不能在裸函数中使用扩展asm,只能使用基本asm。您不需要通知编译器被破坏的寄存器,因为它无论如何都不会对它们做任何事情;在裸体功能中,您负责所有注册管理。在扩展操作数中传递条目地址是不必要的;只需执行jmp条目

在我的测试中,您的代码根本不可编译,所以我假设您没有向我们展示您的确切代码-下次请这样做,以避免浪费人们的时间

Linux x86-64系统调用允许关闭rcx和r11寄存器,因此需要将它们添加到系统调用的关闭列表中

在跳转到条目之前,将堆栈与16字节边界对齐。但是,16字节对齐规则是基于这样一种假设,即您将使用call调用函数,这将额外增加8个字节 到堆栈。因此,被调用函数实际上期望堆栈最初不是16的倍数,而是比16的倍数多8或少8。因此,实际上,您正在错误地对齐堆栈,这可能会导致各种各样的神秘问题

所以,要么用call替换jmp,要么从rsp中再减去8个字节,或者只推送一些您选择的64位寄存器

样式说明:在Linux x86-64上,unsigned long已经是64位了,因此使用它来代替unsigned long-where更为惯用

一般提示:了解扩展asm中的寄存器约束。您可以让编译器为您加载所需的寄存器,而不是自己在asm中编写指令。因此,您的退出函数可以改为如下所示:

这尤其为您节省了一些指令,因为状态已在函数项的%rdi寄存器中。对于原始代码,编译器必须将其移动到其他地方,以便您可以自己将其加载到%rdi中

open函数始终返回1,这通常不是实际打开的fd。因此,如果您的程序在标准输出重定向的情况下运行,那么您的程序将写入重定向的标准输出,而不是像它看起来想要的那样写入tty。事实上,这使得open系统调用完全没有意义,因为您从未使用您打开的文件

您应该安排open返回系统调用实际返回的值,当syscall返回时,该值将保留在%rax寄存器中。您可以使用输出操作数将其存储在编译器可能会优化的临时变量中,然后返回该变量。您需要使用数字约束,因为它与输入操作数位于同一寄存器中。我把这个留给你做练习。同样,如果您的write函数实际返回写入的字节数也会很好


参数可能不会通过堆栈传递-您调试过它吗?还请注意,允许系统调用销毁rcx和r11,因此您应该将它们添加到clobber列表中。声明不允许对裸函数使用扩展asm,只允许使用基本asm,因此您需要相应地修改_start。我很惊讶你的代码被编译器接受了。使用i约束传递条目地址应该是不必要的;只需执行jmp条目。你有没有尝试过单步执行_start函数,并用调试器检查它的堆栈,看看它是否符合你的期望?这可能有助于您了解正在发生的事情,并找出您的处理不正确的原因。您似乎遗漏了必要的包含文件,这些文件仅定义了大小和大小。你真正的代码是什么?您构建的gcc和clang命令行选项是什么?参数可能不会通过堆栈传递-您调试过它吗?请注意,允许系统调用销毁rcx和r11,因此您应该将它们添加到clobber列表中。声明不应将扩展asm用于裸函数,而应仅使用基本asm,因此,您需要相应地修改_start。我很惊讶你的代码被编译器接受了。使用i约束传递条目地址应该是不必要的;只需执行jmp条目。你有没有尝试过单步执行_start函数,并用调试器检查它的堆栈,看看它是否符合你的期望?这可能有助于您了解正在发生的事情,并找出您的处理不正确的原因。您似乎遗漏了必要的包含文件,这些文件仅定义了大小和大小。你真正的代码是什么?您构建的gcc和clang命令行选项是什么。或者更简单地说,如果您的entry函数不是真正的函数并且无法返回,请删除and并保留jmp。在进入_start时,ABI保证堆栈指针是16字节对齐的,因此一个pop设置您进入noreturn函数的函数。或者只是mov load而不是pop,然后您可以正常呼叫。显示了一些手写的裸体启动实现,它们使用argc、argv.5调用C函数。include可从asm/unistd.h获取呼叫号码的宏,如SYS\u exit或Linux\uu NR\u exit宏名称。这些头文件甚至可以安全地包含在纯asm中。您可以使用unsigned long rax=\uuuu NR\u write并使用+arax约束来更简单地表示包装函数中该寄存器的双输入/输出使用,而不必麻烦使用匹配约束来为不同的输入和输出C变量使用相同的寄存器。或者只使用一个_NR_write and=aretval-特定的寄存器约束使匹配约束变得不必要,因为您知道它们将选择哪个寄存器,所以您可以手动硬编码匹配。OP代码的附加代码检查:它们的C包装类型与基础系统调用不匹配。E
Gfd参数是32位整数,而不是64位长。如果包含为这些函数提供原型的普通C头,则会产生不匹配。您应该能够做到这一点,并且仍然可以定义自己的内联版本,但OP不能,因为它们将类型弄得一团糟。它导致了一个额外的MOVSLQ,将扩展int int tffd从打开到未签名的长FD,用于在编译器生成的ASM编译中写入,因为C++允许重载函数名,因此,有两个不同版本的OPEN打开,让这个混乱编译不是错误的。因为asm使用了;作为指令分隔符而不是\n\t,每个asm块都会变成一条长线,这是一个巨大的混乱。无论如何,我认为这个代码评审不值得单独回答,所以就把它留在这里。或者更简单地说,如果您的entry函数不是真正的函数并且无法返回,请删除and并保留jmp。在进入_start时,ABI保证堆栈指针是16字节对齐的,因此一个pop设置您进入noreturn函数的函数。或者只是mov load而不是pop,然后您可以正常呼叫。显示了一些手写的裸体启动实现,它们使用argc、argv.5调用C函数。include可从asm/unistd.h获取呼叫号码的宏,如SYS\u exit或Linux\uu NR\u exit宏名称。这些头文件甚至可以安全地包含在纯asm中。您可以使用unsigned long rax=\uuuu NR\u write并使用+arax约束来更简单地表示包装函数中该寄存器的双输入/输出使用,而不必麻烦使用匹配约束来为不同的输入和输出C变量使用相同的寄存器。或者只使用一个_NR_write and=aretval-特定的寄存器约束使匹配约束变得不必要,因为您知道它们将选择哪个寄存器,所以您可以手动硬编码匹配。OP代码的附加代码检查:它们的C包装类型与基础系统调用不匹配。e、 g.fd参数是32位整数,而不是64位长。如果包含为这些函数提供原型的普通C头,则会产生不匹配。您应该能够做到这一点,并且仍然可以定义自己的内联版本,但OP不能,因为它们将类型弄得一团糟。它导致了一个额外的MOVSLQ,将扩展int int tffd从打开到未签名的长FD,用于在编译器生成的ASM编译中写入,因为C++允许重载函数名,因此,有两个不同版本的OPEN打开,让这个混乱编译不是错误的。因为asm使用了;作为指令分隔符而不是\n\t,每个asm块都会变成一条长线,这是一个巨大的混乱。无论如何,我认为这个代码审查不值得单独回答,所以把它留在这里。
    void exit(unsigned long status) {
        asm volatile("syscall"
            : //no outputs
            :"a"(60), "D" (status)
            :"rcx", "r11");
    }