How to Print a String to the Terminal in X86-64 Assembly (Nasm) Without Syscall

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

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.

How to print a character in Linux x86 NASM?

ecx should contain a pointer to the start of your char buffer. So you have to have your buffer in memory. You can do the following:

; Print 'A' character 
mov eax, 4 ; __NR_write from asm/unistd_32.h (32-bit int 0x80 ABI)
mov ebx, 1 ; stdout fileno

push 'A'
mov ecx, esp ; esp now points to your char
mov edx, 1 ; edx should contain how many characters to print
int 80h ; sys_write(1, "A", 1)

; return value in EAX = 1 (byte written), or error (-errno)

add esp, 4 ; restore esp if necessary

You can mov byte [esp], 'A' or whatever other address if it's OK to overwrite whatever is on the stack.

Or you can have a character array in section .rodata instead of storing on the fly.


Making a write() system call with the const void *buf arg being some small number (like 'A') will make it return -EFAULT without printing anything. The kernel has to check the pointer anyway, and system calls return an error instead of raising SIGSEGV on bad pointers.

Use strace ./my_program to trace the system calls you actually made, including decoding the return values.

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

My answer on How do I print an integer in Assembly Level Programming without printf from the c library? which you already linked shows that serializing an integer into memory as ASCII decimal gives you a length, so you have no use for (a custom version of) strlen here.

(Your msg has an assemble-time constant length, so it's silly not to use that.)

To print a signed integer, implement this logic:

if (x < 0) {
print('-'); // or just was_negative = 1
x = -x;
}
unsigned_intprint(x);

Unsigned covers the abs(most_negative_integer) case, e.g. in 8-bit - (-128) overflows to -128 signed. But if you treat the result of that conditional neg as unsigned, it's correct with no overflow for all inputs.

Instead of actually printing a - by itself, just save the fact that the starting number was negative and stick the - in front of the other digits after generating the last one. For bases that aren't powers of 2, the normal algorithm can only generate digits in reverse order of printing,

My x86-64 print integer with syscall answer treats the input as unsigned, so you should simply use that with some sign-handling code around it. It was written for Linux, but replacing the write system call number will make it work on Mac. They have the same calling convention and ABI.


And BTW, xor al,al is strictly worse than xor eax,eax unless you specifically want to preserve the upper 7 bytes of RAX. Only xor-zeroing of full registers is handled efficiently as a zeroing idiom.

Also, repnz scasb is not fast; about 1 compare per clock for large strings.

For strings up to 16 bytes, you can use a single XMM vector with pcmpeqb / pmovmskb / bsf to find the first zero byte, with no loop. (SSE2 is baseline for x86-64).

Printing a character to standard output in Assembly x86

Sure, you can use any normal C function. Here's a NASM example that uses printf to print some output:

;
; assemble and link with:
; nasm -f elf test.asm && gcc -m32 -o test test.o
;
section .text

extern printf ; If you need other functions, list them in a similar way

global main

main:

mov eax, 0x21 ; The '!' character
push eax
push message
call printf
add esp, 8 ; Restore stack - 4 bytes for eax, and 4 bytes for 'message'
ret

message db 'The character is: %c', 10, 0

If you only want to print a single character, you could use putchar:

push eax
call putchar

If you want to print out a number, you could do it like this:

mov ebx, 8
push ebx
push message
call printf
...
message db 'The number is: %d', 10, 0

How to print a number in assembly NASM?

If you're already on Linux, there's no need to do the conversion yourself. Just use printf instead:

;
; assemble and link with:
; nasm -f elf printf-test.asm && gcc -m32 -o printf-test printf-test.o
;
section .text
global main
extern printf

main:

mov eax, 0xDEADBEEF
push eax
push message
call printf
add esp, 8
ret

message db "Register = %08X", 10, 0

Note that printf uses the cdecl calling convention so we need to restore the stack pointer afterwards, i.e. add 4 bytes per parameter passed to the function.



Related Topics



Leave a reply



Submit