News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Stack management and parameters

Started by mulu64, March 16, 2025, 05:09:26 AM

Previous topic - Next topic

mulu64

Hello,

For studying parameters passing, i'm using those C functions (found on a tutorial):

int foo(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8) {
    return arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8;
}

int bar() {
    return foo(1, 2, 3, 4, 5, 6, 7, 8);
}

When bar() is called, i expect to have arguments from 1 to 6 passed to EDI,ESI,EDX,R10,R9,R8 and arguments 7 and 8 go to the stack.
Check with godbolt:

bar:
        push    rbp
        mov     rbp, rsp
        push    8
        push    7
        mov     r9d, 6
        mov     r8d, 5
        mov     ecx, 4
        mov     edx, 3
        mov     esi, 2
        mov     edi, 1
        call    foo
        add     rsp, 16
        leave
        ret

First surprise: it's not R10 which is used as fourth argument, but ECX.
I had read here and there that ECX was not to be used (don't remember where, but chatgpt said the same thing, last time i trust you liar!). I've check later with AMD ABI documentation, and it seems that sometimes it's ECX and sometimes it's R10 (for kernel calls with syscall). I'm not sure of this, so i mention it.

For the « leave » instruction , it seems it's like:
mov rsp,rbp
pop rbp

I would have just push'ed and pop'ped RSP at beginning and end, but maybe it's a generical way to write for the compiler. Is it for restoring a good value, in case of RSP has been changed by inner function calls ?

But then, here goes foo()

foo:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     DWORD PTR [rbp-12], edx
        mov     DWORD PTR [rbp-16], ecx
        mov     DWORD PTR [rbp-20], r8d
        mov     DWORD PTR [rbp-24], r9d
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, DWORD PTR [rbp-8]
        add     edx, eax
        mov     eax, DWORD PTR [rbp-12]
        add     edx, eax
        mov     eax, DWORD PTR [rbp-16]
        add     edx, eax
        mov     eax, DWORD PTR [rbp-20]
        add     edx, eax
        mov     eax, DWORD PTR [rbp-24]
        add     edx, eax
        mov     eax, DWORD PTR [rbp+16]
        add     edx, eax
        mov     eax, DWORD PTR [rbp+24]
        add     eax, edx
        pop     rbp
        ret

So it saves RBP and store RSP in it.
And then it start to store parameters.

[RBP-4] is the first one.
So i believe it will write from RBP-4 to RBP , reaching top of allowed stack.

And then we continue until parameter 6, growing stack down.

I believer parameters 6 and 7 should be at RBP and RBP+8.

But i read in the addition, it accesses params 6 and 7 with [RBP+16] and [RBP+24].
What is stored in [RBP] and [RBP+8] ?
When the function ends, RSP is adjusted by 16 octets, not 32 (if param 7 begins at RBP+24, that means it ends at RBP+32)

I have a last question: we are using RBP, ans RSP stays at the top of the frame.
What if we push something of size x ? RSP will be used and destroys from RBP to RBP-x , won't it ?

Thank you.

PS: i'm sorry to have put all these questions in one post. Answer only to part you want, of course.

zedd151

I had seen your posting in the Linux Assembly board.
Is this for Linux or Windows?
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

mulu64

Quote from: zedd151 on March 16, 2025, 05:35:57 AMI had seen your posting in the Linux board.
Is this for Linux or Windows?

It is for Linux.

zedd151

I am not sure if it makes any difference for your example, but it might.
Thanks.
I myself don't have sufficient knowledge of 64 bit, or stack manipulations in 64 bit.

In Windows programming with the Masm64 SDK, when we use 'invoke' to call a procedure, all of the stack management is handled without needing to fiddle with the stack at all (as long as 4 arguments or less) the first 4 arguments are passed in rcx, rdx, r8, and r9. Also, 'shadow space' is taken care of as well.

For Linux, this may be (and probably is) different.
We have a couple of members that use Linux as their primary operating system and may be able to help you with this, mabdelouahab and I think jack.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

zedd151

Upon taking a quick second look at your code, you are indexing rbp at 4 byte increments. [rbp-4], [rbp-8], [rbp-12], etc.
That is wrong, this much I can tell you. 64 bit registers are 8 bytes, not 4. Beyond that I cannot offer much help otherwise.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

mulu64

Thank you, but it's the gcc compilation of the C code that was in first post. It seems to use 32 bits part of registers (optimisation of compiler ?).
I will use gdb tomorrow on this, i'm on my phone for now.

NoCforMe

There's a Linux forum here: perhaps that would be a better place for this topic? (Most of the discussions here are about x86/x64 code, so this is kind of an outlier.)
Assembly language programming should be fun. That's why I do it.

zedd151

Quote from: mulu64 on March 16, 2025, 07:38:53 AMThank you, but it's the gcc compilation of the C code that was in first post. It seems to use 32 bits part of registers (optimisation of compiler ?).
It just seemed odd to me. But I could be wrong. I have been known to be wrong once or twice.  :tongue:

@NoCforMe, I have requested already that this be moved, as it might be better served there.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

_japheth

Quote from: mulu64 on March 16, 2025, 05:09:26 AMFirst surprise: it's not R10 which is used as fourth argument, but ECX.
AFAIK the Linux x64 ABI uses indeed RDI, RSI, RDX, RCX, R8, R9 for the first 6 arguments. Since the arguments are of type "int", the compiler stores just the lower 32-bit part of the regs. Using "long" instead of "int" would make it store the 64-bit regs.

QuoteWhat is stored in [RBP] and [RBP+8] ?
in [RBP] is the old value of RBP (filled by the "push rbp"). In [RBP+8] is the return address (filled by the "call").

QuoteWhen the function ends, RSP is adjusted by 16 octets, not 32 (if param 7 begins at RBP+24, that means it ends at RBP+32)
Yes, since the "missing" 16 bytes are automatically "adjusted" by "pop rbp" and "ret".

QuoteWhat if we push something of size x ? RSP will be used and destroys from RBP to RBP-x , won't it ?

Yes. I guess the compiler is smart enough to realize that your function does nothing. If another function would be called inside, RSP would have to be adjusted first.

Dummheit, gepaart mit Dreistigkeit - eine furchtbare Macht.

zedd151

For myself, thanks japheth for clarification. The ABI difference had me a bit confused. I knew coding assembly for Linux was different than coding for Windows, I just didn't know how much different it was.
¯\_(ツ)_/¯   :azn:

'As we don't do "requests", show us your code first.'  -  hutch—

mulu64

Thank you for the time on this Zedd, and thank you Japheth for the complete explanation.
It's cristal clear. I should have think twice before asking.

I had posted this in the campus, because for me it was a newbie question not related to windows or linux (just stack manipulation). But yes, there was a question about ABI arguments for Linux. I should have parted this in two posts, one for the money campus, and two for the show linux. If it smells like penguin next time, i will post directly here.