Reputation: 71
section .data
array dw 1,2,3,4,5,6,7,8,9,10 ; array of integers
msg db " numbers are : %d %d ",10,0
section .text
global main
extern printf ; for c printf
main:
push ebp
mov ebp,esp ;intialise stack
mov ax,11
push ax ;push ax with value 11
mov ax,22
push ax ;push ax with value 12
push msg
call printf ; calling printf function
add esp ,12
mov esp,ebp ;restore stack
pop ebp
When I pushed immediate values instead of pushing via AX it works fine. Why is that?
Upvotes: 2
Views: 888
Reputation: 155
You can push a 16-bit register onto a 32-bit stack but printf will later pick it up as full 32-bit item from the stack anyway. That's where you get the garbage value from - the higher part of the item being picked up by printf. That's exactly your problem. So to prevent future problem, always push 32-bit item on 32-bit CPU, 64-bit item onto 64-bit CPU and so on. For an immediate, your compiler will default to 32-bit immediate so that's why your program works with immediates.
Upvotes: 0
Reputation: 47613
As Jester pointed out: don't PUSH 16-bit values onto the stack with 32-bit code unless you know what you are doing. AX is a 16-bit register (lower half of the 32-bit EAX register). When you do:
push ax
16-bits are pushed onto the stack because AX is a 16-bit register. This will prevent printf
from accessing the value properly because the data wasn't 32-bits wide. If you were to do:
push 11
you'd find this works. When NASM generates 32-bit code it assumes that immediate values are 32-bits wide when pushed onto the stack. That is why this scenario worked for you.
If you were to PUSH a 32-bit register, then a full 32-bits of data would be placed on the top of the stack. As an example:
push eax
It appears that your intent may be to access or traverse a WORD array (WORD = 16-bit values) and print them with the %d
conversion specifier used by printf
. %d
prints 32-bit DWORDS as signed values. You will have to load them into memory as WORDs and convert them to DWORDS before you push them on the stack.
Assembly language doesn't have the concept of variables in the conventional sense of a higher level programming language. You give meaning to a memory location holding a WORD (16-bit value). Whether it is signed or unsigned is determined by the code you use to interact with that data.
The 386 has two instructions to help. MOVSX is used to sign extend a smaller operand to larger operand. This is used when you want the SIGN (positive or negative) to be preserved. MOVZX is used to zero extend a smaller operand to a larger one. This instruction is for unsigned values and during the conversion is simply sets all the upper bits of the destination operand to zero.
As an example of this I wrote some code centered around an array of words:
section .data
array dw -1,0,1,2,3,4,5,6,7,8,9,10,-32768,32767,32768
; array of integers
arraylen equ ($-array)/2 ; number of word elements in array
msg db " numbers are : %d %d ",10,0
section .text
global main
extern printf ; for c printf
main:
push ebp
mov ebp,esp ; intialise stack
push ebx ; ebx is caller saved register. We destroy it so
; we must restore it before our function exits
xor ebx, ebx ; index = 0
; Make the equivalent of a for loop to traverse array
.loop1:
cmp ebx, arraylen ; We'll process all the elements of the array
je .endloop ; End when our index = arraylen
movzx eax, word [array + ebx * 2] ; Use EBX as index into WORD array
; zero extend 16-bit array value into 32-bit register
push eax ; parameter 3 = unsigned DWORD
movsx eax, word [array + ebx * 2] ; Use EBX as index into WORD array
; sign extend 16-bit array value into 32-bit register
; movsx eax, ax ; The line above would have also worked this way
push eax ; parameter 2 = signed DWORD onto stack
push msg ; parameter 1 = pointer to format string
call printf ; calling printf function
add esp, 12
inc ebx ; index += 1
jmp .loop1 ; continue for loop
.endloop:
pop ebx ; Restore ebx
mov esp,ebp ; restore stack
pop ebp
The code ensures that any of the registers (EBX in the code above) that are callee-saved per the CDECL calling convention are saved and restored at the beginning and end of the function. More on an explanation of that can be found in a recent StackOverflow answer I wrote.
I coded the equivalent of a for loop (you could have coded it as a do-while) or any other loop construct to traverse the array. I use both MOVZX and MOVSX and display the results with your printf
format string.
Note: : MOVZX can also be done by zeroing out the destination operand and moving the source operand into the destination after. As an example:
movzx eax, word [array + ebx * 2]
Could have been coded as:
xor eax, eax ; eax = 0
mov ax, word [array + ebx * 2]
One should be able to assemble and link with:
nasm -f elf32 testmov.asm
gcc -m32 -o testmov testmov.o
When run as ./testmov
the results should look like:
numbers are : -1 65535 numbers are : 0 0 numbers are : 1 1 numbers are : 2 2 numbers are : 3 3 numbers are : 4 4 numbers are : 5 5 numbers are : 6 6 numbers are : 7 7 numbers are : 8 8 numbers are : 9 9 numbers are : 10 10 numbers are : -32768 32768 numbers are : 32767 32767 numbers are : -32768 32768
If you want to print unsigned WORDs (16-bit values) with printf you can use %hu
(unsigned short), and for signed WORDs you can use %hd
(signed short). Although you still have to pass in a DWORD for the parameters you don't need to worry about zero extending (or sign extending) since printf
will only look at the lower 2 bytes of the DWORDs you pass as parameters.
Upvotes: 5