So I'm learning x86 Linux assembly with NASM syntax (Oh god, not this again, you're all thinking). I'm trying to make a subroutine that will simply print the value in EAX to stdout. The code runs and exits without error, but nothing prints. I can't figure out why. First of all, here is the file I'm working in:
segment .bss
to_print: resd 1
segment .text
global print_eax_val
print_eax_val: ; (top)
push dword ebx ;Stack: edx
push dword ecx ; ecx
push dword edx ; ebx
; (bot)
mov ecx,eax ;ecx = eax
mov [to_print],ecx ;to_print = ecx
mov eax, 4 ;sys_write
mov ebx, 1 ;to stdout
add ecx, 47 ;add 47 for ASCII numbers
mov edx, 2 ;double word = 2 bytes
int 0x80
mov eax, [to_print] ;eax = original val
pop edx ;pop the registers back from the stack
pop ecx
pop ebx ;Stack: empty
ret
This is called from my main file, which looks like this (this is probably irrelevant, unless I'm missing something drastic).
segment .data
hello db "Hello world!", 0
newline db 0xA
len equ $ - hello
len2 equ $ - newline
segment .text
extern print_nl
extern print_eax_val
global main
main:
enter 0,0
call print_nl
mov eax, 1
call print_eax_val
mov ebx, 0 ;exit code = 0 (normal)
mov eax, 1 ;exit command
int 0x80 ;ask kernel to quit
print_nl
is just another subroutine that defines and prints a newline. This runs successfully and prints a new line as expected.
Does the problem have to do with the length parameter for my sys_write
call? I'm giving it 2, which is the size of a dword
, which is the size of both the EAX
register and my to_print
label, which I reserved with resd 1
. I tried changing the length to 1, 4, 8, 16, and 32 out of desperation... Nothing worked.
EDIT: For anyone who is wondering, here is how I fixed the code: (I will put asterisks on lines that I changed):
segment .bss
to_print: resd 1
segment .text
global print_eax_val
print_eax_val: ; (top)
push dword ebx ;Stack: edx
push dword ecx ; ecx
push dword edx ; ebx
; (bot)
mov ecx,eax ;ecx = eax
mov [to_print],ecx ;to_print = ecx
**** add dword [to_print], 48
mov eax, 4 ;sys_write
mov ebx, 1 ;to stdout
**** mov ecx, to_print
mov edx, 2
int 0x80
**** sub dword [to_print], 48
mov eax, [to_print] ;eax = original val
pop edx ;pop the registers back from the stack
pop ecx
pop ebx ;Stack: empty
ret
Basically, ecx
must contain the address of the block you want to print, NOT the value itself. As is pointed out in the selected answer, this will only work if eax is in the range 0-9.
EDIT 2: So I was a little bit confused about the 2nd parameter for sys_write (the one stored in edx
). I think it just refers to a number of bytes. So for a dword
, as I was using, it would be proper to use 4 there, because a double word is 4 bytes, or 32 bits. I'm guessing it worked because x86 is little-endian. So in memory, the hex value of to_print
would look like this:
90 00 00 00
And with a supplied length of two, sys_write gets:
90 00
So the value luckily doesn't get corrupted.
I later changed the code to store to_print
as a byte instead, using resb 1
and accessing it using byte
instead of dword
... A byte is fine here, because I know I'm not going to give to_print
a value above 9.