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

 昀尧约_146 发布于 2022-12-04 03:38

我是装配新手,并希望首先尝试直观地了解如何将字符串打印到终端上,而无需通过操作系统抽象(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(打印到终端)?

1 个回答
  • 这是一个很好的练习.您将使用syscall(stdout否则无法访问),但您可以执行"裸机"写入,而无需任何外部库提供输出例程(如调用printf).作为stdoutx86_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
    

    为了进一步自动化通用输出到stdoutNASM宏,提供了一个方便的解决方案.示例strn(简称string_n).它需要两个参数,字符串的地址和要写入的字符数:

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

    用于缩进,换行或编写完整的字符串.你可以通过传递3个参数来进一步概括,包括目的地rdi.

    2022-12-11 02:09 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有