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

Lance Pollard

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).

(Editor's note: the accepted answer only covers Linux. x86-64 MacOS uses a similar system-calling convention but different call numbers.)

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 (i.e. without syscall)? How is BareMetal OS doing this?

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. 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. 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?

David C. Rankin

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). 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:

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. Below the strprn function will write any string to stdout. It calls strsz to get the length while preserving the destination index on the stack. This reduces the task of writing a string to stdout and prevents a lot of repitition 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 NASM macros provide a convenient solution. Example strn (short for 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.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How to print signed integer in x86 assembly (NASM) on Mac

How to print a string in assembly intel x86 without changing the data

How to compare a char in a string with another char in NASM x86_64 Linux Assembly

How to change variable value in x86_64 assembly (nasm)

how to get address of variable and dereference it in nasm x86 assembly?

How to print a character in Linux x86 NASM?

Manipulate string in assembly x86 (mov and print to screen)

Calling read syscall from assembly (x86/64) yields segmentation fault (compiler construction)

How to print a number in assembly NASM?

NASM assembly, How to print the first 4 bytes of a string array

Input incorrect Assembly x86 NASM

Memory freeing: Assembly NASM x86

How do I print multiple variables in assembly x86?

How to pass structs to C function from x86-64 assembly on Mac (NASM)

What's the most concise way to reverse a string using x86 or x86_64 assembly?

x86_64 assembly execve *char[] syscall

How does xchg works with string for assembly x86?

How does this x86 Assembly code create a string?

x86 Nasm assembly - push'ing db vars on stack - how is the size known?

linux x86_64 nasm assembly syscalls

Assembly Nasm x86_64 Coredump on ErrCode:139

How to save a string temporary variable in assembly x86-64

Assembly x86 64 Linux AT&T: print routine segmentation error

x64 NASM Assembly extern printf doesn't print anything

x64 NASM Assembly print a single character returned from the function

How to use scanf to read a floating point value in nasm x86 64?

How to determine if a .NET assembly was built with platform target AnyCPU, AnyCPU Prefer32-bit, x86, x64 without using reflection and third party SW

Calling NASM float in x86 assembly from C

calling gets after malloc (assembly NASM x86)