Daniel Carvalho
Daniel Carvalho

Reputation: 483

How can one access first parameter of a function using stack frame?

There's a series of problems in SPOJ about creating a function in a single line with some constraints. I've already solved the easy, medium and hard ones, but for the impossible one I keep getting Wrong Answer.

To sum it up, the problem requests to fill in the code of the return statement such that if x is 1, the return value should be 2. For other x values, it should return 3. The constraint is that the letter 'x' can't be used, and no more code can be added; one can only code that return statement. Clearly, to solve this, one must create a hack.

So I've used gcc's built in way to get the stack frame, and then decreased the pointer to get a pointer to the first parameter. Other than that, the statement is just a normal comparison.

On my machine it works fine, but for the cluster (Intel Pentinum G860) used by the online judge, it doesn't work, probably due to a different calling convention. I'm not sure I understood the processor's ABI (I'm not sure if the stack frame pointer is saved on the stack or only on a register), or even if I'm reading the correct ABI.

The question is: what would be the correct way to get the first parameter of a function using the stack?

My code is (it must be formatted this way, otherwise it's not accepted):

#include <stdio.h>

int count(int x){

 return (*(((int*)__builtin_frame_address(0))-1) == 1) ? 2 : 3;

}

int main(i){
    for(i=1;i%1000001;i++)
        printf("%d %d\n",i,count(i));
    return 0;
}

Upvotes: 2

Views: 646

Answers (2)

Jason Hu
Jason Hu

Reputation: 6333

C standard does NOT require a stack in any implementation, so really your problem doesn't make any sense.

in the context of gcc, the behavior is different in x86 and x86-64(and any others).

in x86, parameters reside in stack, but in x86-64, the first 6 parameters(including the implicit ones) reside in registers. so basically you can't do the hacking as you say.

if you want to hack the code, you need to specify the platform you want to run on, otherwise, there is no point to answer your question.

Upvotes: 0

Grzegorz Szpetkowski
Grzegorz Szpetkowski

Reputation: 37954

The question is: what would be the correct way to get the first parameter of a function using the stack?

There is no way in portable manner. You must assume specific compiler, its settings and ABI, along with calling conventions.

The gcc compiler is likely to "lay down" an int local variable with -0x4 offset (assuming that sizeof(int) == 4). You might observe with most basic definition of count:

4   {
   0x00000000004004c4 <+0>: push   %rbp
   0x00000000004004c5 <+1>: mov    %rsp,%rbp
   0x00000000004004c8 <+4>: mov    %edi,-0x4(%rbp)

5       return x == 1 ? 2 : 3;
   0x00000000004004cb <+7>: cmpl   $0x1,-0x4(%rbp)
   0x00000000004004cf <+11>:    jne    0x4004d8 <count+20>
   0x00000000004004d1 <+13>:    mov    $0x2,%eax
   0x00000000004004d6 <+18>:    jmp    0x4004dd <count+25>
   0x00000000004004d8 <+20>:    mov    $0x3,%eax

6   }
   0x00000000004004dd <+25>:    leaveq 
   0x00000000004004de <+26>:    retq

You may also see that %edi register holds first parameter. This is the case for AMD64 ABI (%edi is also not preserved between calls).

Now, with that knowledge, you might write something like:

int count(int x)
{
    return *((int*)(__builtin_frame_address(0) - sizeof(int))) == 1 ? 2 : 3;
}

which can be obfuscated as:

return *((int*)(__builtin_frame_address(0)-sizeof(int)))==1?2:3;

However, trick is that such optimizing compiler may enthusiastically assume that since x is not referenced in count, it could simply skip moving into stack. For example it produces following assembly with -O flag:

4   {
   0x00000000004004c4 <+0>: push   %rbp
   0x00000000004004c5 <+1>: mov    %rsp,%rbp

5       return *((int*)(__builtin_frame_address(0)-sizeof(int)))==1?2:3;
   0x00000000004004c8 <+4>: cmpl   $0x1,-0x4(%rbp)
   0x00000000004004cc <+8>: setne  %al
   0x00000000004004cf <+11>:    movzbl %al,%eax
   0x00000000004004d2 <+14>:    add    $0x2,%eax

6   }
   0x00000000004004d5 <+17>:    leaveq 
   0x00000000004004d6 <+18>:    retq

As you can see mov %edi,-0x4(%rbp) instruction is now missing, thus the only way1 would be to access value of x from %edi register:

int count(int x)
{
    return ({register int edi asm("edi");edi==1?2:3;});
}

but this method lacks of ability to "obfuscate", as whitespaces are needed for variable declaration, that holds value of %edi.

1) Not necessarily. Even if compiler decides to skip mov operation from register to stack, there is still a possibility to "force" it to do so, by asm("mov %edi,-0x4(%rbp)"); inline assembly. Beware though, compiler may have its revenge, sooner or later.

Upvotes: 2

Related Questions