How to print a number in assembly NASM?
Solution 1
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.
Solution 2
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.
Solution 3
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
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
: plaincall 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 traditionalcall printf
call [rel printf wrt ..got]
to not use a PLT at all, likegcc -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 acall 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.
Solution 4
It depends on the architecture/environment you are using.
For instance, if I want to display a number on linux, the ASM code will be different from the one I would use on windows.
Edit:
You can refer to THIS for an example of conversion.
Related videos on Youtube
AR89
Software Engineer with mobile professional experience. Constantly learning new languages and tools, passionate about solid code and challenging UI.
Updated on June 16, 2020Comments
-
AR89 almost 4 years
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.
-
Alexey Frunze over 12 yearsPlease specify the OS where the program will run.
-
Peter Cordes over 6 yearsRelated: convert an integer to an ASCII decimal string in a buffer on the stack and print it with Linux
write
system call, not usingprintf
or any other functions. With comments and explanation.
-
-
AR89 over 12 yearsA Linux example would be fine.
-
moongoal over 12 years@AR89 it's a bad job.. You have to convert the number to ASCII first. Take a look at the edited question.
-
AR89 over 12 yearsThanks, it seems to be the what I was looking for. Do you know if it works also on Mac os X?
-
Figen Güngör over 11 yearsHow to compile it on 64-bit?
-
Andrei Bârsan over 11 yearsYou don't need AT&T syntax to run this on linux.
-
BlackBear over 11 years@AndreiBârsan: You're right, fixed that.. It's such an old answer :)
-
Andrei Bârsan over 11 yearsIMHO, this answer is better since you don't need the C runtime (which a call to
printf(...)
requires. -
BlackBear over 11 years@AndreiBârsan yes, and it's kind of pointless using the C runtime in assembly
-
Peter Cordes almost 7 yearsYou can
add '0'
and store your digits in a buffer as you produce them. Usedec
do move the pointer downwards. When you're done, you have a pointer to the last digit you stored, so you can pass that tosys_write()
(along with the digit count). This is much more efficient than making a separate system call for every byte, and doesn't really take more code. It's easy to allocate a buffer long enough to hold the longest possible string of digits, and start at the end, because you know how many decimal digits 2^32 has. -
Peter Cordes almost 7 yearsrelated: I wrote an integer->string loop as part of this extended-precision Fibonacci code-golf answer. See the
.toascii_digit:
loop. Of course, that's optimized for size, so it uses a slowdiv
instead of a multiply trick. -
baz almost 7 yearsThank you this is definitely preferable than calling sys_write for every digit:)
-
Peter Cordes over 6 yearsI posted my int->string +
sys_write
code as a stand-alone function on another question, with comments. -
Peter Cordes over 5 yearsYou're using space below ESP. That's only safe in cases like this where you know there are no signal handlers installed, and shouldn't be used in functions that could be called in other contexts. 32-bit Linux doesn't guarantee a red-zone. Also, use
xor edx,edx
/div
orcdq
/idiv
so the zero or sign-extension of the dividend matches the signedness of the division. In this case you wantxor
/div
so you always have a positive remainder. If you want to treat your input as signed, you'll want to test/js and print the unsigned absolute value (with a leading-
if needed). -
TigerTV.ru over 5 years@PeterCordes, Hi, Peter! You're right about safety. It's a partial solution and I didn't think about signed numbers.
-
Peter Cordes over 5 yearsYou should still change
idiv
todiv
so it works for the full range of unsigned numbers. Hmm, actually this might be safe anyway, because 2^32-1 / 10 doesn't overflow EAX. zero-extending into edx:eax gives you a signed non-negative dividend from 0..2^32-1. -
TigerTV.ru over 5 years@PeterCordes, The
idiv
has been replaced. Also I added base for the number. What do you think about it? And else I reserved a buffer on stack for number string with size 32. -
Peter Cordes over 5 years
add esp, 32
should besub
to reserve space. You're stepping on the caller's stack space.mov byte [ecx], 10
would be more efficient than setting a register first. Or evenpush 10
/mov ecx, esp
/sub esp, 32
. (For your current version, a large number with base=2 will use 32 digits, but you use up one of your 32 with a newline.) -
TigerTV.ru over 5 yearsSure, I'm wrong. :) It didn't even break stack in the context. Fixed.
-
Peter Cordes about 5 yearsPlease don't recommend
mov
for putting static addresses in registers in 64-bit mode. Use RIP-relative LEA, unless you're optimizing for position-dependent code where you can usemov r32, imm32
. -
Ciro Santilli OurBigBook.com about 5 yearsHi @PeterCordes thanks for the edit. Yes, I think I didn't know what PIE was at the time + many other details :-) If you feel like making it work with
-pie
as well, that would be cool ;-) I'm lazy to research that now. -
Peter Cordes about 5 yearsI already included
call printf wrt ..plt
in my first edit. I put it back in in a more appropriate place now that you made a bullet point for it. I had to look up the NASM equivalent of GAScall *printf@GOTPCREL(%rip)
, for no-plt style code that does early binding of dynamic symbols instead of lazy linking via the PLT. (But with the upside of just indirect call instead of call +jmp
for lazy dynamic linking with a PLT.) -
Ciro Santilli OurBigBook.com about 5 years@PeterCordes ah OK, I thought that was just a pseudo notation, what a weird syntax!
-
Peter Cordes about 5 yearsAgreed.
.plt
is the section name, and I guess there's an extra.
in there maybe to go with the with-respect-to abbreviation? -
Peter Cordes almost 5 yearsCan't call C standard library function on 64-bit Linux from assembly (yasm) code has a bit more detail about
call [rel printf wrt ..got]
andcall printf wrt ..plt
. I just updated it to work for NASM as well as YASM. -
Peter Cordes over 3 years2021 update: you may need
gcc -m32 -no-pie
, or at least it's a good idea if you're going to docall printf
instead ofcall printf@plt
, and also to use absolute addresses as immediates, not position-independent. In practice for 32-bit code you can usually get away with it. -
Peter Cordes over 3 years32-bit code: How to convert a binary integer number to a hex string?. 32-bit / 64-bit conversion to decimal: How do I print an integer in Assembly Level Programming without printf from the c library? with 64-bit Linux
syscall
to write to stdout.