作为一项统一任务,我为 write 系统调用编写了一个非常简单的包装器。这是针对 i386 的。代码编译为:
gcc -ffreestanding -fno-stack-protector -nostdlib -nostdinc -static -m32 -Wall -g -O2
我想知道为什么注释掉的代码只打印出 4 个字符,无论 num_bytes 是多少。
int my_write(int fd, void *buf, unsigned num_bytes){
int ret;
asm volatile (
"mov $4, %%eax;"
"mov %1, %%ebx;"
"mov %2, %%ecx;"
"mov %3, %%edx;"
"int $0x80;"
: "=r"(ret)
: "r"(fd), "r"(buf), "r"(num_bytes)
: "eax", "ebx", "ecx", "edx", "memory"
);
/* Does not work
asm volatile (
"mov $4, %%eax;"
"mov %1, %%ebx;"
"mov %2, %%ecx;"
"mov %3, %%edx;"
"int $0x80;"
"mov %%eax, %0;"
: "=a"(ret)
: "r"(fd), "r"(buf), "r"(num_bytes)
: "ebx", "ecx", "edx", "memory"
);
*/
return ret;
}
我知道我可以使用:
asm volatile (
"int $0x80;"
: "=a"(ret)
: "a"(4), "b"(fd), "c"(buf), "d"(num_bytes)
: "memory"
);
这也很好用。但是我想知道上述两种方法之间有什么不同。=a
我是否使用or并不重要,=r
因为 i386 的调用约定规定 syscall 的返回值位于eax
.
我尝试编写静态缓冲区并使用固定的 num_bytes,但问题仍然存在。我还尝试使用另一个优化标志进行编译-Og
。使用除 for 之外的任何内容-O2
都会导致代码在启动后立即以状态 1 退出,即使版本正常工作。但这可能是由代码中的某些其他函数引起的,因为my_write
使用-O2
和编译的部分的 objdump-Og
没有显示差异。但是我没有在任何其他功能中发现错误,所以我希望有人有一个想法。感谢您的任何意见。编辑:请原谅与格式的斗争
在我看来,您将寄存器的破坏列表视为保留寄存器的列表,您想为自己使用它,并认为编译器将远离它们。
但事实并非如此。破坏列表的作用是通知编译器这些寄存器在执行过程中可能已经更改了值
asm
,并且之后的值将被视为未定义。但是,通过在代码中按名称使用寄存器并使用
r
约束,您可能会面临编译器为您在代码中使用的约束选择相同寄存器的风险。这很可能会失败。为了调试此类问题,您应该反汇编生成的代码并将其与您的意图进行比较。例如,您注释的代码:
在我的机器中拆解为:
正如您所看到的,选择的寄存器为
%1
is ,但您在使用它之前eax
用 a 覆盖了它。$4
未注释的代码只是偶然起作用,因为
eax
没有选择寄存器来执行任何操作。您有两种选择来解决这个问题。最简单的方法是使用特定的寄存器来进行约束,正如您在问题末尾所示的那样。这样就不会发生冲突。
另一个解决方案是要格外小心,不要覆盖任何可能仍然需要的寄存器。像这样的东西:
这编译为:
对于所有这些堆栈舞蹈来说,这看起来有点傻,但它应该可行。
顺便说一句,如果你写
"=a"(ret)
为输出约束,那么它mov %%eax, %0
是多余的。mov %eax, %eax
这就是为什么你的代码最后会得到额外的结果。