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:
div ecx
divides the 64-bit valueedx:eax
byecx
, so you need to ensure that you setedx
to 0 before the division.The second argument to the
write
system call (inecx
) 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
Aslr Bits of Entropy of Mmap()
How to Use Global Arrays in Bash
Cuda Compiler Not Working with Gcc 4.5 +
Compile/Run Assembler in Linux
Execute a Shell Script Everyday at Specific Time
Replace Only If String Exists in Current Line
Explanation of Convertor of Cidr to Netmask in Linux Shell Netmask2Cdir and Cdir2Netmask
Detecting a Chroot Jail from Within
Isolate Kernel Module to a Specific Core Using Cpuset
Best Way Elevate the Privileges Programmatically Under Different Versions of Linux
Bash Script to Find and Display Oldest File
Why Didn't I Get Segmentation Fault When Storing Past the End of the Bss
Modify Http Headers on System Level (Linux)
When to Use Pipes VS When to Use Shared Memory