7

I'm a little confused about how to print a character to the screen using Assembly. The architecture is x86 (linux). Is it possible to call one of the C functions or is there a simpler way? The character I want to output is stored in a register.

Thanks!

user973758
  • 707
  • 1
  • 9
  • 22

3 Answers3

10

In the interest of completeness, here is how to do it without C.

Using DOS interrupts

AH = 02h -WRITE CHARACTER TO STANDARD OUTPUT

Writes character in DL. (I tested this with the DOS emulator emu2)

mov dl, 21h ; char '!'
mov ah, 02h ; char output service
int 21h     ; call DOS services

x86-32 Linux write syscall

write requires the address of your string. For a single character you can push your character on the stack.

push    '!'         ; push dword
mov     eax, 4      ; write call number, __NR_write from unistd_32.h
mov     ebx, 1      ; write to stdout (fd=1)
mov     ecx, esp    ; use char on stack
mov     edx, 1      ; write 1 char
int     0x80        ; call into the kernel
add     esp, 4      ; restore sp 

Info on register order

x86-64 Linux write syscall

Similar to above, but the call number is now 1, syscall instead of int 0x80, and the calling convention registers are different.

push    '!'         ; push qword 0x21
mov     rax, 1      ; write call number, __NR_write from unistd_64.h
mov     edi, 1      ; write to stdout (int fd=1)
mov     rsi, rsp    ; use char on stack
mov     rdx, 1      ; size_t len = 1 char to write.
syscall            ; call the kernel, it looks at registers to decide what to do
add     rsp, 8      ; restore stack pointer

mov edx, 1 does exactly the same thing as mov rdx, 1, but NASM will optimize it for you. This code uses operand-sizes that match the C types, not optimizing mov of small non-negative 64-bit integers.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
qwr
  • 9,525
  • 5
  • 58
  • 102
  • 1
    For x86-64 Linux, you can `lea -1(%rsp), %rsi` / `movb $0x21, (%rsi)` to use the red-zone, and not have to pop the stack. – Peter Cordes Jun 20 '18 at 04:41
  • and what about for X86-64 Windows? (21h calls are blocked.) – BrainSlugs83 Aug 06 '20 at 18:58
  • 1
    @BrainSlugs83 I don't know about Windows, sorry. Maybe Peter Cordes can shed some light. – qwr Aug 06 '20 at 19:21
  • @BrainSlugs83: Windows has a system-call ABI you can in theory use from user-space, but it's undocumented and not stable across versions. Still, see https://github.com/j00ru/windows-syscalls for reverse-engineering details. I don't know which one(s) you'd actually want to use. – Peter Cordes May 28 '21 at 00:55
  • @BrainSlugs83: In general for Windows you *don't* want to use a raw system-call directly, you want to call a DLL function. [How to write hello world in assembly under Windows?](https://stackoverflow.com/q/1023593) shows how for 32 and 64-bit executables with a variety of APIs and assemblers. – Peter Cordes Jan 30 '23 at 04:03
10

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
Martin
  • 37,119
  • 15
  • 73
  • 82
  • Thanks. But what if register ebx holds the value 8, and I want to print the character '8'. If I try and push ebx as the parameter it doesn't work so is there a way around this? Cheers – user973758 Nov 20 '11 at 13:26
  • 1
    that would be equivalent to `printf("%d", 8);` I added that to the answer – Martin Nov 20 '11 at 13:31
-3

Calling putchar(3) is the simplest way. Just move the char's value into the rdi register (for x86-64, or edi for x86), and call putchar.

E.g. (for x86-64):

asm("movl $120, %rdi\n\t"
    "call putchar\n\t");

will print an x to stdout.

Bailey Parker
  • 15,599
  • 5
  • 53
  • 91
Anthony Blake
  • 5,328
  • 2
  • 25
  • 24
  • Wrong ABI! (x86-64 OS X, at a guess, given the register -- although I think you want `%rdi` not `%edi` -- and the leading `_` on the library function?) – Matthew Slattery Nov 22 '11 at 01:17
  • You changed it in the code, but your description still says "the edi register." – porglezomp Sep 08 '14 at 22:36
  • The i386 System V calling convention passes args on the stack, and EDI is a call-preserved register. Plus doing this from GNU C "basic" inline asm (with no clobbers) isn't safe: you step on the compilers registers. And for x86-64, you step on the compiler's red-zone, too. This *will* call `putchar` on x86-64 Linux, but that's all, what happens after that is unpredictable. – Peter Cordes Jun 20 '18 at 04:39
  • @MatthewSlattery: `movl` to `%edi` is a more efficient way to zero-extend a value into RDI. As it stands now, this won't even assemble (mismatch between `movl` dword operand size suffix vs. qword register). But anyway, without any clobbers this is totally broken. See my previous comment. In stand-alone asm, `mov $'x', %edi` ; `call putchar` this would be fine. – Peter Cordes Feb 16 '20 at 22:12