No, this is wrong. When you enter a function, the first item that will be popped is the return address, then the parameters follow. In most functions, it is customary to access the parameters by moving SP into BP and accessing them at an offset from BP. For example, like this:
Code: Select all
push bp ; Save BP
mov bp,sp
mov ax,[bp+4] ; gets parameter 1, skipping old BP and return address
mov bx,[bp+6] ; gets parameter 2
mov cx,[bp+8] ; gets parameter 3
mov dx,[bp+10] ; gets parameter 4
pop bp ; Restore BP
ret 8 ; return and remove 8 bytes of parameters, assuming the function uses the __stdcall convention
This approach is used in nearly every case, especially when there are many parameters or you need to access them randomly.
The return value is in AL if it's a byte, in AX if it's a word, and with 16 bit programs, the high half is in DX and the low half in AX if it's a dword, whereas with 32 bit programs, a dword is returned in EAX. Structures are returned in different ways depending on the compiler.
For example, if a 16-bit function takes one parameter which is a word, and returns the value of the parameter plus 100, it would look like one of these:
Code: Select all
Add100_Variant1:
push bp ; Save BP
mov bp,sp
mov ax,[bp+4] ; Get parameter
add ax,100
pop bp ; Restore BP
ret 2 ; Return and remove the 2 bytes making up the parameter
Add100_Variant2:
pop cx ; Remove return address and put it in CX
pop ax ; Get parameter and remove it
add ax,100
jmp cx ; Jump to return address
Here is the same example where the function operates on a dword and returns a dword:
Code: Select all
Add100_Variant1_L:
push bp
mov bp,sp
mov ax,[bp+4] ; Get low part of parameter
mov dx,[bp+6] ; Get high part of parameter
add ax,100 ; Add 100 to low part
adc dx,0 ; Add 0 to high part with carry
pop bp
ret 4 ; Return and remove the 4 bytes making up the parameter
Add100_Variant2_L:
pop cx ; Remove return address and put it in CX
pop ax ; Get low part of parameter and remove it
pop dx ; Get high part of parameter and remove it
add ax,100
adc dx,0
jmp cx ; Jump to return address
A 32 bit function doing the same to a dword could look like this:
Code: Select all
mov eax,[esp+4] ; Here, we can use ESP and don't need to put it in EBP. A 16-bit function could also use ESP provided that the upper half of ESP was zero, but it could not use SP.
add eax,100
ret 4
And a 32 bit function adding 5 dword parameters together would look like this:
Code: Select all
push ebp
mov ebp,esp
mov eax,[ebp+8] ; Parameters now start at 8, since the saved EBP takes 4 bytes and the return address takes 4 bytes.
add eax,[ebp+12]
add eax,[ebp+16]
add eax,[ebp+20]
add eax,[ebp+24]
pop ebp
ret 20
It should be noted that instructions that use addresses relative to ESP are one byte longer than instructions using addresses relative to EBP, and that's why the last example uses EBP.
Note that the C statement printf( "Hi this is " + i ) ; would add i to the address of the string "Hi this is " and pass the result to printf. So, for example, if i was 4, it would print "his is ".