在 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).
全部。我正在尝试使用 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).