【发布时间】:2020-06-21 22:11:44
【问题描述】:
我有一个简短的 sn-p 代码,带有一些内联程序集,可以在 O0 中正确打印 argv[0],但在 O2 中不打印任何内容(另一方面,使用 Clang 时,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-asynchronous-unwind-tables;-O2;-Wall;-nostdinc;-fno-builtin;-nostdlib; -nodefaultlibs;--no-standard-libraries;-nostartfiles;-nostdinc++。由于 Clang 支持上述 GCC 选项,您也可以将这些标志用于 Clang。
【问题讨论】:
-
参数可能没有通过堆栈传递 - 你调试了吗
-
另请注意,系统调用允许销毁
rcx和r11,因此您应该将它们添加到您的clobber 列表中。 -
gcc manual 表示您不应该将扩展 asm 用于“裸”功能,只能使用基本 asm,因此您需要相应地修改
_start。我很惊讶您的代码被编译器接受。使用i约束传递entry的地址应该是不必要的;就做jmp entry。破坏者也是不必要的。 -
您是否尝试过单步执行
_start函数并使用调试器检查它的堆栈,以查看实际存在的内容是否符合您的期望?这可能会帮助您了解正在发生的事情,并找出您的处理不正确的原因。 -
您似乎遗漏了定义
size_t和O_WRONLY的必要#include文件。你的真实代码到底是什么? (以及您使用哪些确切的 gcc 和 clang 命令行选项构建?)
标签: c linux assembly x86-64 inline-assembly