我是装配新手,并希望首先尝试直观地了解如何将字符串打印到终端上,而无需通过操作系统抽象(Linux或OSX).
tl; dr如何在OSX上使用NASM在x86-64程序集中写入stdout(打印到终端),尽可能最低级别(即没有系统调用)?BareMetal OS如何做到这一点?
大多数示例显示类似这样:
global start section .text start: mov rax, 1 mov rdi, 1 mov rsi, message mov rdx, 13 syscall mov eax, 60 xor rdi, rdi syscall message: db "Hello world", 10
在那里,他们syscall
用来打印依赖于操作系统的字符串.我不是在寻找那个,而是为了如何在最低级别直接将字符串写入stdout.
有这个exokernel项目,BareMetal OS,我认为这样做.虽然因为我不熟悉集会,但我还不知道他们是如何做到这一点的.从它看来,两个重要的文件是:
系统调用/ screen.asm
系统调用/ string.asm
打印的相关代码似乎是这个(从这两个文件中提取):
; ; Display text in terminal. ; ; IN: RSI = message location (zero-terminated string) ; OUT: All registers preserved ; os_output: push rcx call os_string_length call os_output_chars pop rcx ret ; ; Displays text. ; ; IN: RSI = message location (an ASCII string, not zero-terminated) ; RCX = number of chars to print ; OUT: All registers preserved ; os_output_chars: push rdi push rsi push rcx push rax cld ; Clear the direction flag.. we want to increment through the string mov ah, 0x07 ; Store the attribute into AH so STOSW can be used later on ; ; Return length of a string. ; ; IN: RSI = string location ; OUT: RCX = length (not including the NULL terminator) ; ; All other registers preserved ; os_string_length: push rdi push rax xor ecx, ecx xor eax, eax mov rdi, rsi not rcx cld repne scasb ; compare byte at RDI to value in AL not rcx dec rcx pop rax pop rdi ret
但这对我来说并不完整(虽然我不知道,因为我是新手).
所以我的问题是,就像BareMetal操作系统片段一样,你如何在OSX上用NASM写入x86-64程序集中的stdout(打印到终端)?
这是一个很好的练习.您将使用syscall
(stdout
否则无法访问),但您可以执行"裸机"写入,而无需任何外部库提供输出例程(如调用printf
).作为stdout
x86_64中基本的"裸机"写入的示例,我将一个示例放在一起,没有任何内部或系统函数调用:
section .data string1 db 0xa, " Hello StackOverflow!!!", 0xa, 0xa, 0 section .text global _start _start: ; calculate the length of string mov rdi, string1 ; string1 to destination index xor rcx, rcx ; zero rcx not rcx ; set rcx = -1 xor al,al ; zero the al register (initialize to NUL) cld ; clear the direction flag repnz scasb ; get the string length (dec rcx through NUL) not rcx ; rev all bits of negative results in absolute value dec rcx ; -1 to skip the null-terminator, rcx contains length mov rdx, rcx ; put length in rdx ; write string to stdout mov rsi, string1 ; string1 to source index mov rax, 1 ; set write to command mov rdi,rax ; set destination index to rax (stdout) syscall ; call kernel ; exit xor rdi,rdi ; zero rdi (rdi hold return value) mov rax, 0x3c ; set syscall number to 60 (0x3c hex) syscall ; call kernel ; Compile/Link ; ; nasm -f elf64 -o hello-stack_64.o hello-stack_64.asm ; ld -o hello-stack_64 hello-stack_64.o
输出:
$ ./hello-stack_64 Hello StackOverflow!!!
对于一般用途,我将过程分为两部分(1)获取长度和(2)写入stdout
.strprn
函数下面会写入任何字符串stdout
.它调用strsz
获取长度,同时保留堆栈上的目标索引.这减少了编写字符串的任务,stdout
并防止代码中的大量重复.
; szstr computes the lenght of a string. ; rdi - string address ; rdx - contains string length (returned) section .text strsz: xor rcx, rcx ; zero rcx not rcx ; set rcx = -1 (uses bitwise id: ~x = -x-1) xor al,al ; zero the al register (initialize to NUL) cld ; clear the direction flag repnz scasb ; get the string length (dec rcx through NUL) not rcx ; rev all bits of negative -> absolute value dec rcx ; -1 to skip the null-term, rcx contains length mov rdx, rcx ; size returned in rdx, ready to call write ret ; strprn writes a string to the file descriptor. ; rdi - string address ; rdx - contains string length section .text strprn: push rdi ; push string address onto stack call strsz ; call strsz to get length pop rsi ; pop string to rsi (source index) mov rax, 0x1 ; put write/stdout number in rax (both 1) mov rdi, rax ; set destination index to rax (stdout) syscall ; call kernel ret
为了进一步自动化通用输出到stdout
NASM宏,提供了一个方便的解决方案.示例strn
(简称string_n
).它需要两个参数,字符串的地址和要写入的字符数:
%macro strn 2 mov rax, 1 mov rdi, 1 mov rsi, %1 mov rdx, %2 syscall %endmacro
用于缩进,换行或编写完整的字符串.你可以通过传递3个参数来进一步概括,包括目的地rdi
.