【问题标题】:How to print a string to the terminal in x86-64 assembly (NASM) without syscall?如何在没有系统调用的情况下在 x86-64 程序集(NASM)中将字符串打印到终端?
【发布时间】:2015-02-20 01:22:03
【问题描述】:

我是汇编新手,我想先尝试直观地了解将字符串打印到终端的工作原理,而无需通过操作系统抽象(Linux 或 OSX)。

(编者注:接受的答案仅涵盖 Linux。x86-64 MacOS 使用类似的系统调用约定,但调用号不同。)

tl;dr 如何在 OSX 上使用 NASM 在 x86-64 程序集中以尽可能低的级别(即没有系统调用)写入标准输出(打印到终端)? BareMetal OS 是如何做到这一点的?

大多数示例显示类似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

在那里,他们使用syscall 打印字符串,即relying on the operating system。我不是在寻找那个,而是在寻找如何以尽可能低的级别直接将字符串写入标准输出。

我认为有这个 exokernel 项目 BareMetal OS 正在这样做。虽然由于我是组装新手,但我还没有足够的知识来弄清楚他们是如何做到这一点的。看起来,两个重要的文件是:

似乎要打印的相关代码是这样的(从这两个文件中提取):

;
; 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 OS sn-p 的思路,您如何在 OSX 上使用 NASM 的 x86-64 程序集中写入标准输出(打印到终端)?

【问题讨论】:

  • 所以您认为syscalls 中的代码不涉及系统调用?您希望如何绕过操作系统(管理操作系统是其工作之一)来执行任何类型的 I/O?
  • 嗯,在整个 repo 中根本没有提到 syscallint 0x80,这就是为什么我想知道他们是如何做到的。
  • stdout 一种操作系统抽象。我不明白你在做什么。
  • Bare Metal OS 通过将字节写入视频内存来做到这一点。 Linux 不会让你这样做。
  • @FrankKotler:仅在与 IBM 兼容的计算机上,在某些视频模式下,使用某些(不是旧的)显卡...

标签: linux assembly x86-64 nasm


【解决方案1】:

这是一个很好的练习。您将使用syscall(否则您无法访问stdout),但您可以在没有任何外部库提供输出例程的情况下进行“裸机”写入(例如调用printf)。作为在 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

输出:

$ ./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 宏,提供了一个方便的解决方案。示例 strnstring_n 的缩写)。它需要两个参数,字符串的地址和要写入的字符数:

%macro  strn    2
        mov     rax, 1
        mov     rdi, 1
        mov     rsi, %1
        mov     rdx, %2
        syscall
%endmacro

对于缩进、换行或编写完整的字符串很有用。您可以通过传递 3 个参数来进一步概括,包括 rdi 的目标。

【讨论】:

  • 几乎没有裸机,但在真正的保护模式操作系统(如 Linux)上可能是最低级别。
猜你喜欢
  • 2020-12-17
  • 1970-01-01
  • 2020-09-13
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多