【问题标题】:Mixing C and Assembly. `Hello World` on 64-bit Linux混合 C 和汇编。 64 位 Linux 上的“Hello World”
【发布时间】:2016-04-13 18:28:41
【问题描述】:

基于此tutorial,我正在尝试将Hello World 写入64 位Linux 上的控制台。编译不会引发任何错误,但我在控制台上也没有收到任何文本。不知道怎么回事。

write.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

ma​​in.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 

【问题讨论】:

  • 正如我在您之前提交的内容中提到的,write 是“C”中的保留字。重命名您的函数,然后重试。
  • 更正:它不是保留字,但它是“C”程序常用的函数,可能在调用本地函数之前与您的程序链接。我的建议成立。
  • 避免冲突 - 将其命名为 _writeStr 而不是 _write
  • 重命名为'writehello'。我现在有:未定义的对“writehello”的引用。看起来,就像从 C 中看不到汇编函数一样:/
  • 好的,我明白了!在我的情况下,函数名中不能有下划线......你是对的命名!我会“+”你的 cmets 并添加一个答案!谢谢!

标签: c assembly x86-64 gnu-assembler


【解决方案1】:

好的,所以我的代码有两个错误:

1) 我将我的 as 函数命名为“write”,这是常见的 c 名称,我需要重命名它。

2) 在函数名中,我不应该加下划线。

正确的代码:

writehello.s

.data
SYSREAD = 0
SYSWRITE = 1
SYSEXIT = 60
STDOUT = 1
STDIN = 0
EXIT_SUCCESS = 0

message: .ascii "Hello, world!\n"
message_len =  .-message

.text
#.global main
#main:
#call write
#movq $SYSEXIT, %rax
#movq $EXIT_SUCCESS, %rdi
#syscall

#********
.global writehello
writehello:
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 writehello(void);
int main (int argc, char **argv)
{
    writehello();
    return 0;
}

编译保持原样:) 感谢所有提供帮助的人!

【讨论】:

    【解决方案2】:

    您正在阅读的教程不太正确。 ELF(可执行和可链接格式)可执行文件中的全局符号有两种不同的约定。一种约定说所有全局 C 符号都应以 _ 为前缀,另一种约定不为 C 符号添加前缀。 在 GNU/Linux 中,尤其是在 x86-64 ABI 中,全局符号 不以 _ 为前缀。但是,您链接的教程可能适用于某些不使用 GNU libc 的 other Linux/ELF 编译器。


    现在,在您的原始代码中发生的情况是,您的汇编函数在 C 代码中将显示为 _write,而不是 write。相反,write 符号位于 libcwrite(2) 系统调用的包装器)中:

    ssize_t write(int fd, const void *buf, size_t count);
    

    现在您将 this write 声明为函数 void write(void);,这会在您调用它时导致 未定义的行为。您可以使用strace ./program 找出它发出的系统调用:

    % 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。结果:没有崩溃,没有消息。


    write 在 C 中不是保留字。它是一个函数,在 POSIX 中可能是一个宏。你可以覆盖它,链接顺序很重要——如果你的程序定义了write,其他代码将链接到这个定义而不是glibc中的那个。但是,这意味着调用 write 的其他代码最终会改为调用您的不兼容函数。

    因此,解决方案是不要使用 GNU libc 或您链接的任何其他库中的函数名称。因此在汇编器中你可以使用:

    .global writehello
    writehello:
    

    然后

    extern void writehello(void);
    

    你自己已经发现了。

    【讨论】:

      猜你喜欢
      • 2017-06-10
      • 1970-01-01
      • 2013-03-10
      • 1970-01-01
      • 2013-01-29
      • 1970-01-01
      • 2013-10-29
      • 1970-01-01
      • 2023-03-16
      相关资源
      最近更新 更多