问题描述
基于此教程,我试图将Hello World
写入控制台在64位Linux上.编译不会引起任何错误,但控制台上也不会显示任何文本.我不知道怎么了.
Based on this tutorial, I am trying to write Hello World
to the console on 64 bit Linux. Compilation raises no errors, but I get no text on console either. I don't know what is wrong.
写入.s:
.data
SYSREAD = 0
SYSWRITE = 1
SYSEXIT = 60
STDOUT = 1
STDIN = 0
EXIT_SUCCESS = 0
message: .ascii "Hello, world!\n"
message_len = .-message
.text
.globl _write
_write:
pushq %rbp
movq %rsp, %rbp
movq $SYSWRITE, %rax
movq $STDOUT, %rdi
movq $message, %rsi
movq $message_len, %rdx
syscall
popq %rbp
ret
main.c:
extern void write(void);
int main (int argc, char **argv)
{
write();
return 0;
}
编译:
as write.s -o write.o
gcc main.c -c -o main.o
gcc main.o write.o -o program
./program
推荐答案
您正在阅读的教程不太正确. ELF(可执行和可链接格式)可执行文件中的全局符号有两种不同的约定.一种约定表示所有全局C符号应以_
为前缀,另一种约定则不以C符号为前缀. 在GNU/Linux中,尤其是在x86-64 ABI中,全局符号不带_
前缀.但是,您链接的教程可能适用于某些不使用GNU libc的 other Linux/ELF编译器.
The tutorial you're reading is not quite right. There has been two differing conventions for global symbols in the ELF (Executable and Linkable Format) executables. One convention says that all global C symbols should be prefixed with _
, the other convention does not prefix the C symbols. In GNU/Linux, especially in x86-64 ABI, the global symbols are not prefixed with _
. However the tutorial that you linked might be right for some other compiler for Linux/ELF that didn't use the GNU libc.
现在,在原始代码中发生的事情是,汇编函数在C代码中将显示为_write
,而不是write
.而是在libc
(用于write(2)
系统调用的包装器)中找到write
符号:
Now, what happens in your original code is that your assembler function would be visible as _write
in C code, not write
. Instead, the write
symbol is found in the libc
(the wrapper for write(2)
system call):
ssize_t write(int fd, const void *buf, size_t count);
现在,您将 this write
声明为函数void write(void);
,当您调用它时,会导致未定义的行为.您可以使用strace ./program
找出它产生的系统调用:
Now you declared this write
as a function void write(void);
, which leads to undefined behaviour as such when you call it. You can use strace ./program
to find out what system calls it makes:
% strace ./program
...
write(1, "\246^P\313\374\177\0\0\0\0\0\0\0\0"..., 140723719521144) = -1 EFAULT (Bad address)
...
因此,它调用的write
系统调用不是使用您想要的参数,而是使用提供给glibc write
包装器的寄存器中的任何垃圾. (实际上,这里已知垃圾"-第一个参数是argc
,第二个参数是argv
的值,而第三个参数是char **environ
的值).而且,由于内核注意到以(void*)argv
开始且长度为140723719521144字节的缓冲区没有完全包含在映射的地址空间中,因此它从该系统调用中返回了EFAULT
.结果:没有崩溃,没有消息.
So it called the write
system call not with your intended arguments, but with whatever garbage there was in the registers provided to glibc write
wrapper. (actually the "garbage" is known here - first argument is the argc
, and the second argument is the value of argv
and the 3rd argument is the value of char **environ
). And as the kernel noticed that a buffer starting at (void*)argv
and 140723719521144 bytes long wasn't completely contained within the mapped address space, it returned EFAULT
from that system call. Result: no crash, no message.
write
不是C中的保留字.它是一个函数,在POSIX中可能是宏.您可以 覆盖它,链接顺序很重要-如果您编程定义write
,则将根据该定义链接其他代码,而不是glibc中的代码.但是,这意味着其他调用write
的代码最终将改为调用您的不兼容函数.
write
is not a reserved word as such in C. It is a function and possibly a macro in POSIX. You could overwrite it, the linking order matters - if you program defines write
, other code would be linked against this definition instead of the one found in glibc. However this would mean that other code calling write
would end up calling your incompatible function instead.
因此,解决方案是不要使用GNU libc或链接到的任何其他库中的函数名称.因此,在汇编程序中,您可以使用:
Thus the solution is to not use a name that is a function in the GNU libc or in any other libraries that you've linked against. Thus in assembler you can use:
.global writehello
writehello:
然后
extern void writehello(void);
当您自己发现时.
这篇关于混合C和汇编. 64位Linux上的"Hello World"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!