[英]How to print a string to the terminal in x86-64 assembly (NASM) without syscall?
I am new to assembly, and want to first try to get an intuitive feel for how printing a string to the terminal would work, without going through the operating system abstraction (Linux or OSX).我是汇编新手,想首先尝试直观地了解将字符串打印到终端的工作方式,而无需通过操作系统抽象(Linux 或 OSX)。
(Editor's note: the accepted answer only covers Linux. x86-64 MacOS uses a similar system-calling convention but different call numbers.) (编者注:接受的答案仅涵盖 Linux。x86-64 MacOS 使用类似的系统调用约定,但调用号不同。)
tl;dr How do you write to stdout (print to the terminal) in x86-64 assembly with NASM on OSX, at the lowest level possible (ie without syscall)? tl;dr如何在 OSX 上使用 NASM 在 x86-64 汇编中以尽可能低的级别(即没有系统调用)写入标准输出(打印到终端)? How is BareMetal OS doing this? BareMetal OS 是如何做到这一点的?
Most examples show something like this :大多数示例显示类似这样:
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
In there, they are using syscall
to print the string, which is relying on the operating system .在那里,他们使用syscall
来打印依赖于操作系统的字符串。 I am not looking for that, but for how to write a string to stdout directly, at the lowest level possible.我不是在寻找那个,而是寻找如何在可能的最低级别直接将字符串写入标准输出。
There is this exokernel project, BareMetal OS that I think is doing this.我认为有一个外部内核项目BareMetal OS正在这样做。 Though since I am new to assembly, I don't know enough yet to figure out how they accomplish this.虽然因为我是组装新手,我还没有足够的知识来弄清楚他们是如何做到这一点的。 From what it seems though, the two important files are:但从表面上看,这两个重要文件是:
It seems the relevant code to print is this (extracted from those two files):似乎要打印的相关代码是这样的(从这两个文件中提取):
;
; 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
But that doesn't look complete to me (though I wouldn't know yet since I'm new).但这对我来说并不完整(虽然我还不知道,因为我是新人)。
So my question is, along the lines of that BareMetal OS snippet, how do you write to stdout (print to the terminal) in x86-64 assembly with NASM on OSX?所以我的问题是,按照 BareMetal OS 片段的思路,您如何在 OSX 上使用 NASM 在 x86-64 程序集中写入标准输出(打印到终端)?
This is a good exercise.这是一个很好的练习。 You will use syscall
(you cannot access stdout
otherwise), but you can do a "bare-metal" write without any external library providing the output routine (like calling printf
).您将使用syscall
(否则您无法访问stdout
),但您可以在没有任何提供输出例程的外部库(如调用printf
)的情况下进行“裸机”写入。 As an example of the basic "bare-metal" write to stdout
in x86_64, I put together a example without any internal or system function calls:作为在 x86_64 中写入stdout
的基本“裸机”示例,我整理了一个没有任何内部或系统函数调用的示例:
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
output:输出:
$ ./hello-stack_64
Hello StackOverflow!!!
For general use, I split the process into two parts (1) getting the length and (2) writing to stdout
.对于一般用途,我将过程分为两部分(1)获取长度和(2)写入stdout
。 Below the strprn
function will write any string to stdout
.在strprn
函数下方将任何字符串写入stdout
。 It calls strsz
to get the length while preserving the destination index on the stack.它调用strsz
来获取长度,同时保留堆栈上的目标索引。 This reduces the task of writing a string to stdout
and prevents a lot of repitition in your code.这减少了将字符串写入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
To further automate general output to stdout
NASM macros provide a convenient solution.为了进一步自动输出到stdout
输出,NASM 宏提供了一个方便的解决方案。 Example strn
(short for string_n
).示例strn
( string_n
缩写)。 It takes two arguments, the addresses of the string, and the number of characters to write:它需要两个参数,字符串的地址和要写入的字符数:
%macro strn 2
mov rax, 1
mov rdi, 1
mov rsi, %1
mov rdx, %2
syscall
%endmacro
Useful for indents, newlines or writing complete strings.用于缩进、换行或编写完整的字符串。 You could generalize further by passing 3 arguments including the destination for rdi
.您可以通过传递 3 个参数(包括rdi
的目的地)来进一步概括。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.