How do I print a string to the terminal in x86-64 assembly (NASM) without syscall?

I'm new to assembly and want to first try to get an intuitive idea of ​​how printing a line on the terminal will work, without going through the operating system (Linux or OSX) abstraction.

tl; dr How do you write to stdout (print to terminal) in x86-64 assembly with NASM on OSX, at the lowest level (no syscall)? How does BareMetal OS work?

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

      

There they use a syscall

string to print, which relies on the operating system . I'm not looking for this, but for how to write a string to stdout directly, at the lowest level.

There is an exokernel project, BareMetal OS , which I think does this. Although, since I'm new to assembly, I don't know enough yet to understand how they do it. From what appears to be two important files:

It seems that the relevant code for printing is this (extracted from these 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 (although I don't know yet, since I'm a beginner).

So my question is, according to this BareMetal OS snippet, how do you write to stdout (print to terminal) in x86-64 build with NASM on OSX?

+3


source to share


1 answer


This is a good exercise. You will use syscall

(you cannot stdout

otherwise access ), but you can write bare metal without an external library providing an inference routine (like a call printf

). As an example of basic "bare metal" write in stdout

x86_64, I put together an example without any internal or system function calls:

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 in stdout

. strprn

Any string will be written below the function stdout

. It calls strsz

to get the length while keeping the destination index on the stack. This reduces the task of writing the string to stdout

and prevents a lot of repetition in your code.



; 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

macros, NASM provides a convenient solution. Example strn

(short for string_n

). It takes two arguments, the address 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 indenting, newlines, or writing full lines. You can generalize it by passing 3 arguments including the destination for rdi

.

+7


source







All Articles