简体   繁体   English

如何在没有系统调用的情况下在 x86-64 程序集 (NASM) 中将字符串打印到终端?

[英]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 ).示例strnstring_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM