【问题标题】:Printing strings in Assembly, calling that function in C [duplicate]在汇编中打印字符串,在 C 中调用该函数 [重复]
【发布时间】:2015-09-29 00:07:18
【问题描述】:

全部。我正在尝试使用 NASM 进行编程,并且我还想学习如何使这些函数在 C 中可调用。我相当确定到目前为止我所拥有的代码是正确的,因为我需要设置一个堆栈框架,然后在我从例程返回之前撤消该堆栈帧。我也知道我需要返回一个零以确保没有错误。我也在使用 debian linux,以防我需要针对我的操作系统进行调整。

代码:

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 &amp;&amp; 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 标识符,或者任何所谓的标识符。肯定需要更多地了解系统调用。非常感谢所有为我指明正确方向的人。

【问题讨论】:

  • 网上有很多讨论x86-64 calling conventions 的信息,尤其是来自 C 的。您的示例代码不是 64 位的,尽管您表示您希望使用 64 位。如何在汇编程序中打印字符串是通过对您正在使用的操作系统进行系统调用来完成的。所以这取决于操作系统。
  • 64位寄存器以r开头,对吧?比如raxrdi等?我会编辑帖子来解决这个问题。
  • 是的,他们有。但是,当您进入 64 位汇编程序时,除了在寄存器名称前面放置一个 r 之外,还有更多的事情要做。调用约定不同于 x86。
  • 更好的方法可能是先用 C 编写你的东西,然后让编译器输出汇编程序(例如 gcc -Wall -fverbose-asm -S -O code.c,然后查看 code.s),以获得一些初步灵感跨度>
  • @lurker,你是对的,我可能应该认为这不会那么容易。我正在使用 debian linux,如果这可以澄清任何事情。

标签: c assembly nasm x86-64


【解决方案1】:

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

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


这是个问题

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

虽然 64 位进程可以使用 i386 int 0x80 系统调用 ABI,但它 32 位 ABI,只有 32 位指针等等。一旦你转到write(2) 堆栈上的一个字符数组,你就会遇到问题(因为 amd64 Linux 进程从一个设置了高位的堆栈指针开始。堆内存,以及.data.rodata 内存从可执行文件映射到地址空间的低 32b。)

原生 amd64 ABI 使用syscall,系统调用号与 i386 ABI 不同。我发现this table of syscalls 列出了数字以及哪个参数进入哪个寄存器。 sys/syscall.h 最终包含 /usr/include/x86_64-linux-gnu/asm/unistd_64.h 以获取实际的 #define __NR_write 1 宏,依此类推。为了寄存器映射参数有标准规则。 (Given in the ABI doc, IIRC)。

【讨论】:

  • 啊,你是对的!我最初认为我必须将系统调用号放在%rdi 寄存器中。然后我注意到返回值应该放在%rax,一旦我这样做了,连同将消息放在%rsi%rdx 中的字符串长度,返回的输出和一切都是正确的世界。非常感谢。我会给你一个赞成票,但似乎我没有这方面的声誉。哦,我理解并接受,感谢您的解释,使用 libc 并不是魔鬼,但这是一个了解这些东西在汇编中如何工作的练习
  • @Azhraam:是的,我认为这只是一个测试你的知识的练习。 IIRC,当我了解事情是如何在幕后工作时,我将系统调用与 asm 分开。 C 是一种很好的处理系统调用的语言。为了确保我的 asm 做我认为它做的事情,我通常从 C 调用它,或者只是查看调试器中的值而不是实际打印任何东西。 strace 也是在尝试系统调用时节省日志打印的好工具。 :P strace -o /dev/pts/XX 效果很好,其中 pty 是来自 xterm 的 tty,以避免将 strace 与正常输出混合
猜你喜欢
  • 2013-04-30
  • 2020-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多