6

I'm trying to print a single character or a number using NASM, targeting an x86 GNU/Linux architecture.

Here's the code I'm using:

section .text
    global _start

_start:

    ; Linux printing preparation
    mov eax,4            
    mov ebx,1       

    ; Print 'A' character 
    mov ecx,'A'     ; ecx should contain the value to print
    mov edx,1       ; edx should contain how many characters to print
    int 80h

    ; System exit
    mov eax,1            
    mov ebx,0            
    int 80h

Running this code, however, prints nothing. What am I doing wrong?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 2
    `ecx` is supposed to contain a pointer to a NUL-terminated string, not a char literal. – Michael Jan 29 '14 at 11:15
  • 5
    Doesn't have to be NUL-terminated, does have to be in memory. Could be `push`ed on the stack and `mov ecx, esp`. Don't forget to "remove" it afterwards. – Frank Kotler Jan 29 '14 at 11:55
  • Related: [How do I print an integer in Assembly Level Programming without printf from the c library?](https://stackoverflow.com/a/46301894) for numbers longer than a single digit – Peter Cordes Sep 20 '21 at 17:46

2 Answers2

12

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.

András Kovács
  • 29,931
  • 3
  • 53
  • 99
2

The system call you are executing expects ecx to contain an address in memory. This can be an arbitrary literal address (i.e. in your code, "A" translates to the address 041h), an address on the stack, or an address defined by a label in the program.

Here's an example of defining a byte in memory and writing it to the standard output stream of your terminal:

    section .rodata  ; This section contains read-only data
buffer:    db 'A'    ; Define our single character in memory

    section .text
    global start
_start:
    ; Prints the letter 'A', then exits

    mov eax, 4      ; sys_write()
    mov ebx, 1      ; ... to STDOUT
    mov ecx, buffer ; ... using the following memory address
    mov edx, 1      ; ... and only print one character
    int 80h         ; SYSCALL

    mov eax, 1      ; Return to system
    mov ebx, 0      ; Exit zero, success
    int 80h         ; SYSCALL
Joe
  • 99
  • 4
  • Your comments aren't quite right: eax=4 is just [`sys_write`](http://man7.org/linux/man-pages/man2/write.2.html). It's not necessarily to a TTY. You use the same system call whether the file descriptor is open on a TTY, a regular file, a network socket, a pipe, a block or char device, etc. Note that your program still works if you run it as `./a.out > A.txt`, in which case STDOUT is not a TTY. – Peter Cordes Aug 25 '18 at 13:38
  • Also, you could have put `buffer` in `.rodata`, because it's read-only. – Peter Cordes Aug 25 '18 at 13:38
  • Thanks @PeterCordes : Ok I agree. I assumed the person wanted to print to their terminal so I wrote my comment in the form of a story, rather than explaining exactly how `sys_write` works. – Joe Aug 26 '18 at 05:27
  • Right, but your story had plot holes. Fixed it for you. – Peter Cordes Aug 26 '18 at 05:32
  • Ok yeah, removing mention of the TTY works, too. If people know what a TTY is, they probably know what STDOUT is, and just saying STDOUT avoids saying anything that's not always true about TTYs. (I didn't think saying that STDOUT is normally "the TTY" was ambiguous though. It's normally true on Linux for an interactive process that `/dev/stdout` is open on the current process's controlling tty, `/dev/tty`. – Peter Cordes Aug 26 '18 at 05:36
  • I fixed it for me. You fixed it for me. We both fixed it for me! ;) – Joe Aug 26 '18 at 05:44
  • Oh sorry, we were editing at the same time. Your change was eaten by mine. Feel free to add the bit about the TTY back if you like. My edit/comments regarded the original code not your modifications. – Joe Aug 26 '18 at 05:59
  • No worries. I think it's fine now. Funny how close your edit came to mine; I assumed you must have based yours on mine. – Peter Cordes Aug 26 '18 at 06:35