在 Assembly 中打印字符串,在 C 中调用该函数

Printing strings in Assembly, calling that function in C

全部。我正在尝试使用 NASM 进行编程,我还想学习如何在 C 中调用这些函数。我相当确定我目前拥有的代码是正确的,因为我需要设置堆栈帧,然后从例程中撤消 I return 之前的堆栈帧。我也知道我需要 return 一个零以确保没有错误。我也在使用 debian linux,以防我需要针对我的 OS.

进行调整

代码:

global hello

section .data
message:    db "Hello, world!",0 ; A C string needs the null terminator.

section .text
hello:
        push    rbp         ; C calling convention demands that a
        mov     rbp,rsp     ; stack frame be set up like so.

        ;THIS IS WHERE THE MAGIC (DOESN'T) HAPPEN

        pop     rbp         ; Restore the stack
        mov     rax,0       ; normal, no error, return value
        ret                 ; return

我觉得好像我应该指出我问这个是因为我发现的所有程序都对 printf 进行了外部调用。我不想这样做,我真的很想学习如何在装配中打印东西。所以我想我的问题是:NASM 中 C 函数的调用约定是什么?如何在 NASM 64 位程序集中打印字符串?

此外,为了确保我的这一部分是正确的,这是在 C 中调用汇编函数的正确方法吗?

#include <stdio.h>

int main() {
    hello();
    return 0;
}

编辑:好的,我能够解决这个问题。这是汇编代码。我使用 nasm -f elf64 -l hello.lst hello.asm && gcc -o hello hello.c hello.o

.asm 文件与 .c 文件组合在一起
section .text
global  hello

hello:
        push    rbp         ; C calling convention demands that a
        mov     rbp,rsp     ; stack frame be set up like so.
        mov     rdx,len     ; Message length
        mov     rcx,message ; Message to be written
        mov     rax,4       ; System call number (sys_write)
        int     0x80        ; Call kernel

        pop     rbp         ; Restore the stack
        mov     rax,0       ; normal, no error, return value
        ret             

section .data
message:    db "Hello, world!",0xa ; 0xa represents the newline char.
len:        equ $ - message

相关的 C 代码 (hello.c) 如下所示:

int main(int argc, char **argv) {
    hello();
    return 0;
}

一些解释包括缺少 #include,因为 I/O 在汇编文件中完成。另一件我需要看到才能相信的事情是,所有工作都不是在汇编中完成的,因为我没有 _start 标识符或任何所谓的标识符。绝对需要了解更多关于系统调用的知识。非常感谢所有为我指出正确方向的人。

如评论中所述,外部世界与您的代码之间的任何交互都是通过系统调用完成的。 C stdio 函数将文本格式化为输出缓冲区,然后用 write(2) 写入。或者 read(2) 进入输入缓冲区,然后扫描或读取行。

用 asm 编写并不意味着你应该避免 libc 有用的函数,例如printf/scanf。通常,为了提高速度,用 asm 编写程序的一小部分才有意义。例如在 asm 中编写一个具有热循环的函数,并从 C 或任何其他语言调用它。对 I/O 进行系统调用 return 值的所有必要错误检查在 asm 中不会很有趣。如果您对引擎盖下发生的事情感到好奇,请阅读编译器输出 and/or 单步执行 asm。有时您会从编译器中学到不错的技巧,有时您会发现它生成的代码效率低于您手工编写的代码。


这是个问题:

mov     rax,4       ; System call number (sys_write)
int     0x80        ; Call kernel

虽然64位进程可以使用i386 int 0x80系统调用ABI,但32位ABI,只有32位指针等等。一旦转到 write(2) 堆栈上的 char 数组,您就会遇到问题(因为 amd64 Linux 进程以设置了高位的堆栈指针开始。堆内存,并且 [=从可执行文件映射的 16=] 和 .rodata 内存映射到地址 space 的低 32b。)

原生 amd64 ABI 使用 syscall,系统调用编号与 i386 ABI 不同。我找到 this table of syscalls listing the number and which parameter goes in which register. sys/syscall.h eventually includes /usr/include/x86_64-linux-gnu/asm/unistd_64.h to get the actual #define __NR_write 1 macros, and so on. There are standard rules for mapping arguments in order to registers. (Given in the ABI doc, IIRC).