0

I'm using nasm on linux with x64 assembly.

I've written some working hello world type programs and began wondering if I can print a single character without having to have a data section. I thought something like this would work, but it doesn't and I don't understand why.

section .text
        global _start

_start:
        
        mov rdx, 1      ;length - 1 character
        mov rcx, 0x41   ;character 'A'
        mov rbx, 1      ;stdout
        mov rax, 4      ;write syscall
        int 0x80        ;kernel interrupt
        
        mov rax, 1      ;exit syscall
        int 0x80        ;kernel interrupt
nobutter
  • 1
  • 1
  • 2
    No, of course you don't need a `.data` section; or any static data at all. But `write` takes a *pointer*, not a value. You could `push 'A'` / `mov rsi, rsp`. But of course you'd need to use the 64-bit `syscall` ABI not the 32-bit `int 0x80` ABI for 64-bit pointers to work. – Peter Cordes Dec 23 '20 at 05:22
  • Excellent. Thank you! The stack seems like a much simpler way to handle a pointer in this case. – nobutter Dec 23 '20 at 06:23

1 Answers1

1

First of all, don't use int 0x80 in 64-bit code; use the syscall interface instead.

The write() system call requires a pointer to the data to be written. If you put 0x41 in that register (rcx in your code but should be rsi with syscall), it will attempt to write whatever data is at address 0x41, which of course will fail because that page is not mapped. If you check the return value from the system call, which is always a good idea, you'll see -EFAULT.

This might seem slightly inefficient for writing a single byte. You might wish there were a separate system call similar to C's putc which writes just a single character, passed in a register. But this is a rare enough case that it likely wouldn't be worth the trouble of implementing it; most applications will buffer their writes so as not to have to make a system call for each byte. Anyway, the overhead of using memory is negligible compared to the much greater overhead of making a system call in the first place.

So your letter A has to be in memory somewhere. But it doesn't necessarily have to be in the .data section. Any other section of static data would be fine: .rodata or .bss if you initialize it at runtime, or even in .text if you're careful not to execute it.

Another perfectly good option is to put it on the stack.

    push 0x41       ; actually pushes 8 bytes but that's okay,
                    ; `0x41` is the low byte because x86 is little-endian
    mov rsi, rsp    ; `rsp` points at what was most recently pushed
    mov edi, 1      ; file descriptor
                    ; it's ok to use edi instead of rdi because writes to 32-bit
                    ; registers are zero extended
    mov edx, 1      ; number of bytes to write
    mov eax, 1      ; system call number for write()
                    ; syscall uses different numbers than int 0x80
    syscall
    add rsp, 8      ; clean up the stack.  
                    ; pop rdi or any other unneeded register would work too, and be shorter
Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • Eventually found a good duplicate about passing a value instead of a pointer, mentions `strace` and everything: [How to print a character in Linux x86 NASM?](https://stackoverflow.com/q/21429355). I knew I'd seen (and improved an answer on) a good Q&A about that before, took me a few minutes to find. (It's for 32-bit code, but that's basically a separate problem; as you say, what the OP is trying wouldn't work in 32-bit code, or if using the 64-bit ABI.) – Peter Cordes Dec 23 '20 at 05:31
  • The stack pointer seems like a great way of doing this. I didn't realize that i was mixing 32 and 64 bit syntax. I was pulling my hair wondering why after making corrections I was getting segfault....but I had forgotten to add the exit syscall at the end. d'oh! – nobutter Dec 23 '20 at 06:49