我目前正在学习汇编,我需要一些关于这个简单程序的帮助。
say_something() 函数是由 x86_64 程序集(在 64 位 Linux 上使用 NASM)创建的,只是用来打印出短语和换行符。
主程序是用 C 语言编写的,如下所示:
extern void say_something(char *);
int main(void) {
say_something("First line.");
say_something("Second sentence on another line.");
return 0;
}
...*.asm 文件的内容是这样的:
section .text
global say_something
say_something:
push rbp
mov rbp, rsp
mov rax, 4 ; syscall for write
mov rbx, 1
mov rcx, rdi
int 0x80
pop rbp
ret
但显然我的 ASM 函数有问题,因为当我编译程序并运行它时,输出只是一些垃圾,而它应该打印这个:
First line.
Second sentence on another line.
我应该对 assembly 部件做哪些更改才能使其正常工作?
最佳答案
你这里有一个主要问题,另外两个主要问题可能对这个特定的调用者不可见:
- 您忘记将
edx
设置为字符串长度。您正在使用的系统调用是
sys_write(int fd, const char *buf, size_t len);
,所以你传递的len
是你的调用者在edx
中留下的任何垃圾>.
如果这不能使 sys_write
返回 -EFAULT
而不打印任何内容,那么可能在它检测到问题之前您正在打印一堆二进制垃圾,如果您以非常大的 len
结束?嗯,write
可能会在写入任何内容之前检查整个范围,所以也许您最终在 edx
中得到了一个中等大小的值,并且只是在字符串之后打印了一些二进制垃圾。不过,您的字符串应该在输出的开头。
在调用前使用strace ./my_program
查找,或者GDB 查看edx
。 (对于来自 64 位代码的 int 0x80
,strace
实际上不会正常工作;它的解码就像您使用的是 native 64 位 syscall
ABI)。
您破坏了调用者的
rbx
而没有保存/恢复它,这违反了调用约定(x86-64 System V ABI),其中rbx
是一个调用-保留寄存器。参见 http://agner.org/optimize/有关调用约定的好文档。这可能没有效果;您的main
可能不会编译为使用rbx
的代码,因此您要销毁的rbx
由 CRT“拥有”启动调用main
的函数。奇怪的是,你保存/恢复了
rbp
,即使你根本不使用它。
最重要的是,您是 using the 32-bit int 0x80
ABI from 64-bit code !!这就是为什么我一直说 write
的 length
在 edx
而不是 rdx
中,因为32 位 int 0x80
ABI 与 32 位用户空间可以使用的 ABI 完全相同,它只查看寄存器的低 32 位。
您的代码将因指针超出虚拟地址空间的低 32 位而中断。例如如果您使用 gcc -fPIE -pie
进行编译,即使是 .rodata
部分中的那些字符串文字也会超出低 32 位,并且 sys_write
可能会在不执行任何操作的情况下返回 -EFAULT
。
-pie
is the default for gcc on many modern Linux distros ,所以你很“幸运”(?)你的程序打印了任何东西,而不是仅仅让 write
失败并返回 -EFAULT
。您没有检查错误返回值,并且它不会像您尝试从用户空间中的坏点读取/写入时那样对您的程序进行段错误,因此它会默默地失败。
正如@fuz 指出的那样,Why cant i sys_write from a register?显示了一些使用 64 位 syscall
进行 sys_write
的示例代码,但它对长度进行了硬编码。您将需要使用 libc 函数或循环来strlen
您的字符串。 (或者一个缓慢但紧凑的 repe scasb
)。
关于linux - 从仅采用 char* 参数的 x86_64 函数中使用 sys_write?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49728516/