1

I am trying to use the write syscall in order to reproduce the putchar function behavior which prints a single character. My code is as follows,

asm_putchar:
  push    rbp
  mov     rbp, rsp

  mov     r8, rdi

call:
  mov     rax, 1
  mov     rdi, 1
  mov     rsi, r8
  mov     rdx, 1
  syscall

return:
  mov     rsp, rbp
  pop     rbp
  ret
Evan Carroll
  • 71,692
  • 44
  • 234
  • 400
Lucas
  • 33
  • 6
  • Have you verified that `rdi` contains a valid string address? Also, you're running this on a 64-bit Linux system, right? – Michael Jun 04 '18 at 13:08
  • 1
    Can you write it in C? Note that `putchar` accepts a character but `write` expects a pointer. – Jester Jun 04 '18 at 13:11
  • Yes I'm running on a 64-bit Linux, I know that write takes a pointer but most of people using this syscall use a data section to store strings – Lucas Jun 04 '18 at 13:25
  • Can't you use the stack instead? – Michael Jun 04 '18 at 13:28
  • It consists to push rax and use the syscall again ? – Lucas Jun 04 '18 at 13:31
  • 2
    @Lucas Push the desired character on the stack, then pass a pointer to that character to the system call. – fuz Jun 04 '18 at 13:35
  • thanks, i'll try that – Lucas Jun 04 '18 at 13:38
  • Just to be sure, can I use the rbp pointer register (pushed at begining) and move rbp, rdi. And so use rbp instead of r8 in my previous exemple ? – Lucas Jun 04 '18 at 13:47
  • 1
    you don't need additional register. You need memory. Also you don't need to touch `rbp` at all (including push/pop). You can just `push rdi` at the beginning to store the character into the stack (= memory), then `mov rsi,rsp` will get the memory address of stored char into `rsi`. And remember to release the stored character after `sys_write`, like `add rsp,8` (or `pop` into some register which you don't need any more, `pop rcx` is probably good candidate, as `rcx` was modified by `syscall` already). – Ped7g Jun 04 '18 at 14:28
  • Related: [GCC: putchar(char) in inline assembly](https://stackoverflow.com/a/50691215). See the compiler asm output in my answer for a complete asm function that implements `return sys_write(1, &my_char, 1);`. On the Godbolt link, you can click the "intel" button to get Intel syntax instead of AT&T. – Peter Cordes Jun 05 '18 at 02:42
  • Strangely I couldn't find an exact duplicate of this very simple question. It's been asked and answered many times for the `int 0x80` 32-bit ABI, but possibly not for x86-64. – Peter Cordes Jun 05 '18 at 02:51

1 Answers1

6

From man 2 write, you can see the signature of write is,

ssize_t write(int fd, const void *buf, size_t count);

It takes a pointer (const void *buf) to a buffer in memory. You can't pass it a char by value, so you have to store it to memory and pass a pointer.

(Don't print one char at a time unless you only have one to print, that's really inefficient. Construct a buffer in memory and print that. e.g. this x86-64 Linux NASM function: How do I print an integer in Assembly Level Programming without printf from the c library?)

A NASM version of GCC: putchar(char) in inline assembly:

; x86-64 System V calling convention: input = byte in DIL
; clobbers: RDI, RSI, RDX,  RCX, R11 (last 2 by syscall itself)
; returns:  RAX = write return value: 1 for success, -1..-4095 for error
writechar:
    mov    byte [rsp-4], dil      ; store the char from RDI

    mov     edi, 1                ; EDI = fd=1 = stdout
    lea     rsi, [rsp-4]          ; RSI = buf
    mov     edx, edi              ; RDX = len = 1
    syscall                    ; rax = write(1, buf, 1)
    ret

If you do pass an invalid pointer in RSI, such as '2' (integer 50), the system call will return -EFAULT (-14) in RAX. (The kernel returns error codes on bad pointers to system calls, instead of delivering a SIGSEGV like it would if you deref in user-space).

See also What are the return values of system calls in Assembly?

Instead of writing code to check return values, in toy programs / experiments you should just run them under strace ./a.out, especially if you're writing your own _start without libc there won't be any other system calls during startup that you don't make yourself, so it's very easy to read the output. How should strace be used?

Peter Cordes
  • 286,368
  • 41
  • 520
  • 731