Printing an Integer with X86 32-Bit Linux Sys_Write (Nasm)

Printing an integer with x86 32-bit Linux sys_write (NASM)

Your numbers will quickly grow larger than just a single digit. What you ought to be doing is have an integer in num rather than a character, and then convert that integer into a string that you can print with sys_write.

Here's one way of doing the conversion: repeated division by 10, getting the lowest digit first as the remainder:

; Input:
; eax = integer value to convert
; esi = pointer to buffer to store the string in (must have room for at least 10 bytes)
; Output:
; eax = pointer to the first character of the generated string
; ecx = length of the generated string
int_to_string:
add esi,9
mov byte [esi],0 ; String terminator

mov ebx,10
.next_digit:
xor edx,edx ; Clear edx prior to dividing edx:eax by ebx
div ebx ; eax /= 10
add dl,'0' ; Convert the remainder to ASCII
dec esi ; store characters in reverse order
mov [esi],dl
test eax,eax
jnz .next_digit ; Repeat until eax==0

; return a pointer to the first digit (not necessarily the start of the provided buffer)
mov eax,esi
ret

Which you can use like this:

    mov    dword [num],1
...
mov eax,[num] ; function args using our own private calling convention
mov esi,buffer
call int_to_string
; eax now holds the address that you pass to sys_write
...

section .bss
num resd 1
buffer resb 10

Your number-doubling can be simplified to shl dword [num],1. Or better, double it at some point while it's still in a register with add eax,eax.

How to print a number in assembly NASM?

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.

How to print a character in Linux x86 NASM?

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.

Printing floats with printf in x86 nasm 32-bit

As Michael pointed, %f in printf expects a double, so your number must be converted into a double just before pushing it on the stack for printf:

global main
extern printf, scanf

section .data
scan_format: db "%f",0
print_format: db "Result: %f",0xA,0

section .bss
result_num: resb 4

section .text
main:
push result_num
push scan_format
call scanf
add esp, 8

sub esp,8 ;reserve stack for a double in stack
mov ebx,result_num
fld dword [ebx] ;load float
fstp qword [esp] ;store double (8087 does the conversion internally)
push print_format
call printf
add esp, 12
ret

push qword [result_num_dub] ;ASSEMBLER ERROR HERE

You cannot do 64 bit operations, like pushing 64 bits at a time, in 32 bit mode. This is why I used the sub esp method to reserve stack space. Your second program just needs this:

section .text
main:
push result_num
push scan_format
call scanf
add esp, 8

fld dword [result_num]
fstp qword [result_num_dub]
push dword [result_num_dub+4] ;pushes 32 bits (MSB)
push dword [result_num_dub] ;pushes 32 bits (LSB)
push print_format
call printf
add esp, 12 ;<-- 12 bytes, not 8.
ret

Scan an integer and print the interval (1, integer) in NASM

There are several problems:

1. The parameters to printf, as discussed in the comments. In x86-64, the first few parameters are passed in registers.

2. printf does not preserve the value of eax.

3. The stack is misaligned.

4. rbx is used without saving the caller's value.

5. The address of integer is being loaded instead of its value.

6. Since printf is a varargs function, eax needs to be set to 0 before the call.

7. Spurious int 80h after the call to scanf.

I'll repeat the entire function in order to show the necessary changes in context.

main:
push rbx ; This fixes problems 3 and 4.

mov eax, 4
mov ebx, 1
mov ecx, message1
mov edx, message1Len
int 80h

mov rdi, formatin
mov rsi, integer
mov al, 0
call scanf

mov ebx, [integer] ; fix problems 2 and 5
loop:
mov rdi, formatout ; fix problem 1
mov esi, ebx
xor eax, eax ; fix problem 6
call printf
dec ebx
jnz loop

pop rbx ; restore caller's value
mov rax,0

ret

P.S. To make it count up instead of down, change the loop like this:

    mov ebx, 1
loop:
<call printf>
inc ebx
cmp ebx, [integer]
jle loop

Outputting integers in assembly on Linux

There are at least two more problems. beyond the corruption of ecx that @sarnold mentioned:

  1. div ecx divides the 64-bit value edx:eax by ecx, so you need to ensure that you set edx to 0 before the division.

  2. The second argument to the write system call (in ecx) should be a pointer to a buffer containing the character you want to print, not the character itself.

One way to solve the second problem is to push the register containing the character you want to print on the stack, and then assign the stack pointer esp to ecx (the stack pointer points at the most recently pushed item, and x86 stores values little-endian, so the first byte is the low 8 bits). e.g.

push edx         ; save value on stack
mov eax, 4 ; sys_write
mov ebx, 1 ; to STDOUT
mov ecx, esp ; first byte on stack
mov edx, 1 ; length = one byte
int 0x80
pop edx ; remove what we pushed (or "add esp, 4" would do just as well here;
; we don't need the actual value again)

That should be enough to get some output...

(But at that point, you might notice a "feature" of your algorithm, and want to re-think how you store the digits that are produced by the division!)

Why in following assembly code the sys_write print out twice?

Jester is correct. To elaborate, the $ is the current location. The way that you have lenmsg and lensum declared is equivalent to:

lenmsg: equ lenmsg-sum_msg

lensum: equ lensum-num1

I would have expected the second sys_write to print from num1 to the end of sum plus lenmsg interpreted as characters (i.e. the last line of the output that you provided.)



Related Topics



Leave a reply



Submit