Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to print a number in assembly NASM?

Suppose that I have an integer number in a register, how can I print it? Can you show a simple example code?

I already know how to print a string such as "hello, world".

I'm developing on Linux.

like image 572
AR89 Avatar asked Nov 19 '11 12:11

AR89


People also ask

Can you print in assembly?

Assembly language has no direct means of printing anything. Your assembler may or may not come with a library that supplies such a facility, otherwise you have to write it yourself, and it will be quite a complex function.

How do I print a character in assembly language?

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.


3 Answers

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.

like image 120
Martin Avatar answered Oct 27 '22 06:10

Martin


You have to convert it in a string; if you're talking about hex numbers it's pretty easy. Any number can be represented this way:

0xa31f = 0xf * 16^0 + 0x1 * 16^1 + 3 * 16^2 + 0xa * 16^3

So when you have this number you have to split it like I've shown then convert every "section" to its ASCII equivalent.
Getting the four parts is easily done with some bit magic, in particular with a right shift to move the part we're interested in in the first four bits then AND the result with 0xf to isolate it from the rest. Here's what I mean (soppose we want to take the 3):

0xa31f -> shift right by 8 = 0x00a3 -> AND with 0xf = 0x0003

Now that we have a single number we have to convert it into its ASCII value. If the number is smaller or equal than 9 we can just add 0's ASCII value (0x30), if it's greater than 9 we have to use a's ASCII value (0x61).
Here it is, now we just have to code it:

    mov si, ???         ; si points to the target buffer
    mov ax, 0a31fh      ; ax contains the number we want to convert
    mov bx, ax          ; store a copy in bx
    xor dx, dx          ; dx will contain the result
    mov cx, 3           ; cx's our counter

convert_loop:
    mov ax, bx          ; load the number into ax
    and ax, 0fh         ; we want the first 4 bits
    cmp ax, 9h          ; check what we should add
    ja  greater_than_9
    add ax, 30h         ; 0x30 ('0')
    jmp converted

greater_than_9:
    add ax, 61h         ; or 0x61 ('a')

converted:
    xchg    al, ah      ; put a null terminator after it
    mov [si], ax        ; (will be overwritten unless this
    inc si              ; is the last one)

    shr bx, 4           ; get the next part
    dec cx              ; one less to do
    jnz convert_loop

    sub di, 4           ; di still points to the target buffer

PS: I know this is 16 bit code but I still use the old TASM :P

PPS: this is Intel syntax, converting to AT&T syntax isn't difficult though, look here.

like image 44
BlackBear Avatar answered Oct 27 '22 05:10

BlackBear


Linux x86-64 with printf

main.asm

default rel            ; make [rel format] the default, you always want this.
extern printf, exit    ; NASM requires declarations of external symbols, unlike GAS
section .rodata
    format db "%#x", 10, 0   ; C 0-terminated string: "%#x\n" 
section .text
global main
main:
    sub   rsp, 8             ; re-align the stack to 16 before calling another function

    ; Call printf.
    mov   esi, 0x12345678    ; "%x" takes a 32-bit unsigned int
    lea   rdi, [rel format]
    xor   eax, eax           ; AL=0  no FP args in XMM regs
    call  printf

    ; Return from main.
    xor   eax, eax
    add   rsp, 8
    ret

GitHub upstream.

Then:

nasm -f elf64 -o main.o main.asm
gcc -no-pie -o main.out main.o
./main.out

Output:

0x12345678

Notes:

  • sub rsp, 8: How to write assembly language hello world program for 64 bit Mac OS X using printf?
  • xor eax, eax: Why is %eax zeroed before a call to printf?
  • -no-pie: plain call printf doesn't work in a PIE executable (-pie), the linker only automatically generates a PLT stub for old-style executables. Your options are:

    • call printf wrt ..plt to call through the PLT like traditional call printf

    • call [rel printf wrt ..got] to not use a PLT at all, like gcc -fno-plt.

    Like GAS syntax call *printf@GOTPCREL(%rip).

    Either of these are fine in a non-PIE executable as well, and don't cause any inefficiency unless you're statically linking libc. In which case call printf can resolve to a call rel32 directly to libc, because the offset from your code to the libc function would be known at static linking time.

    See also: Can't call C standard library function on 64-bit Linux from assembly (yasm) code

If you want hex without the C library: Printing Hexadecimal Digits with Assembly

Tested on Ubuntu 18.10, NASM 2.13.03.