6 Replies - 1175 Views - Last Post: 15 May 2012 - 05:24 PM

#1 raspinudo  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 61
  • View blog
  • Posts: 232
  • Joined: 19-September 11

base pointer offset question

Posted 15 May 2012 - 01:10 AM

This problem came about when a friend of mine challenged me to swap two numbers without a temp variable. After writing it in C, I felt it was time to start really digging into assembly.

I finished writing this swap function earlier today, first in c, then in assembly, and I understand everything except lines 9 and 10 which access the parameters to the function. I figured out the offsets 32 and 28 by using the -S option with gcc and examining the .s file. This is solely for my own learning purpose, so I really want to take the time to understand why the offsets are what they are. I understand that parameters are passed in backwards in cdecl, and that they are ints, which is why the difference between them is 4. I am guessing they are in a new stack frame, which is why it is so high, but I am not sure.

#include <stdio.h>
char msg[] = "asm print test<-->\0";
char msg2[] = "ebx value is: %d\n\0";

//asm version
void asm_swap(int* a, int* b ){
	__asm__("\n\
		enter $0,$0\n\
		movl 32(%ebp), %ebx\n\
		movl 28(%ebp), %esi\n\
		xorl %ebx, %esi\n\
		xorl %esi, %ebx\n\
		xorl %ebx, %esi\n\
		movl %ebx, 32(%ebp)\n\
		movl %esi, 28(%ebp)\n\
		leave\n\
	");
}

void asm_loop(){
	__asm__("\n\
                xorl %ebx, %ebx\n\
                begin:\n\
                        addl $1, %ebx\n\
                        cmpl $3, %ebx\n\
                        jg end\n\
                        push $msg\n\
                        call printf\n\
                        addl $4, %esp\n\
                        pushl %ebx\n\
                        pushl $msg2\n\
                        call printf\n\
                        addl $8, %esp\n\
                        jmp begin\n\
                end:\n\
        ");
}
int main(){
	//testing asm_swap
	int a = 3232; int b = 7575;
	int* x = &a; int* y = &b;
	
	printf("\n::testing asm_swap::\nx: %d, y:%d\n",*x,*y);	
	asm_swap(x,y);
	printf("x: %d, y:%d\n\n",*x,*y);	

	return 0;
}



Edited:
Just for clarity's sake, the C function basically was:

void swap(int* x, int* y){
     *x ^= *y;
     *y ^= *x;
     *x ^= *y;
}

This post has been edited by raspinudo: 15 May 2012 - 01:18 AM


Is This A Good Question/Topic? 0
  • +

Replies To: base pointer offset question

#2 turboscrew  Icon User is offline

  • D.I.C Addict

Reputation: 100
  • View blog
  • Posts: 615
  • Joined: 03-April 12

Re: base pointer offset question

Posted 15 May 2012 - 07:26 AM

It looks like in x86 ABI (Application Binary Interface = "standard" compiler conventions) the ebp is used as stack. It also seems that for Intel, Windows and Linux have their own ABIs.

Didn't find the actual ABI, but this may be enough.
Was This Post Helpful? 0
  • +
  • -

#3 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,277
  • Joined: 28-March 11

Re: base pointer offset question

Posted 15 May 2012 - 10:16 AM

Your args should start at ebp+ 8, that not being the case means stack is not balanced properly. On my cell, the code looks all bunched up so will havr to wait till I get home from work later to get a better look. Not sure about inline asm, but c sets up a stack frame THEN YOU set up one on top of that with enter.
Was This Post Helpful? 0
  • +
  • -

#4 raspinudo  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 61
  • View blog
  • Posts: 232
  • Joined: 19-September 11

Re: base pointer offset question

Posted 15 May 2012 - 03:38 PM

View Postturboscrew, on 15 May 2012 - 07:26 AM, said:

It looks like in x86 ABI (Application Binary Interface = "standard" compiler conventions) the ebp is used as stack. It also seems that for Intel, Windows and Linux have their own ABIs.

Didn't find the actual ABI, but this may be enough.


Sorry for not mentioning, it is gnu inline assembly

View PostGunnerInc, on 15 May 2012 - 10:16 AM, said:

Your args should start at ebp+ 8, that not being the case means stack is not balanced properly. On my cell, the code looks all bunched up so will havr to wait till I get home from work later to get a better look. Not sure about inline asm, but c sets up a stack frame THEN YOU set up one on top of that with enter.


That is what I thought, which is why I was confused when +8 and +12 didn't work. To my understanding, the return will be at ebp +4, args at +8, +12 etc and locals at -4, -8....
Was This Post Helpful? 0
  • +
  • -

#5 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,277
  • Joined: 28-March 11

Re: base pointer offset question

Posted 15 May 2012 - 03:54 PM

First, if you are getting into Assembly don't use any of the High Level data types in your grammar, there is no such thing as an int. The CPU (and us Assembly programmers) only know the following data types:
BYTE - 8 bits
WORD - 16 bits
DWORD - 32 bits
QWORD - 64 bits
REAL
and a signed versions of those. The x86 "Natural Boundry" is a DWORD, so any offsets of pointers are 4 bytes. The stack is DWORD aligned and just about everything passed on the stack is a DWORD that is why the offsets are a multiple of 4.

The INTEL ABI says 4 registers have to be saved in each procedure (down in Assembly you don't have to if your not interacting with the OS or other software), C saved EBX, ESI, EDI, and EBP at the beginning of every proc.

So at the start of every proc it does this:
    push    ebx
    push    esi
    push    edi
    push    ebp
    mov     ebp, esp
and at the end of the proc it pop all that off the stack in reverse.

this is what the offsets look like:
1) Parm2 [ebp+24]
2) Parm1 [ebp+20]
3) RETurn address for CALL [ebp+16]
4) saved EBX contents [ebp+12]
5) saved ESI contents [ebp+8]
6) saved EDI contents [ebp+4]
7) saved EBP contents [ebp]

but, you added ENTER on top of all that so now there is an extra push in there - push ebp was added so your params should be: Param1 = [ebp + 24] Param2 [ebp + 28] but somehow somewhere there is a push that was never popped or the stack was not correctly cleaned up which is why your param1 starts at [ebp + 28]

When I said your args should start at [ebp + 8] that is when you don't have a normal prologue at the proc start.
Was This Post Helpful? 2
  • +
  • -

#6 raspinudo  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 61
  • View blog
  • Posts: 232
  • Joined: 19-September 11

Re: base pointer offset question

Posted 15 May 2012 - 04:50 PM

View PostGunnerInc, on 15 May 2012 - 03:54 PM, said:

First, if you are getting into Assembly don't use any of the High Level data types in your grammar, there is no such thing as an int. The CPU (and us Assembly programmers) only know the following data types:
BYTE - 8 bits
WORD - 16 bits
DWORD - 32 bits
QWORD - 64 bits
REAL
and a signed versions of those. The x86 "Natural Boundry" is a DWORD, so any offsets of pointers are 4 bytes. The stack is DWORD aligned and just about everything passed on the stack is a DWORD that is why the offsets are a multiple of 4.

The INTEL ABI says 4 registers have to be saved in each procedure (down in Assembly you don't have to if your not interacting with the OS or other software), C saved EBX, ESI, EDI, and EBP at the beginning of every proc.

So at the start of every proc it does this:
    push    ebx
    push    esi
    push    edi
    push    ebp
    mov     ebp, esp
and at the end of the proc it pop all that off the stack in reverse.

this is what the offsets look like:
1) Parm2 [ebp+24]
2) Parm1 [ebp+20]
3) RETurn address for CALL [ebp+16]
4) saved EBX contents [ebp+12]
5) saved ESI contents [ebp+8]
6) saved EDI contents [ebp+4]
7) saved EBP contents [ebp]

but, you added ENTER on top of all that so now there is an extra push in there - push ebp was added so your params should be: Param1 = [ebp + 24] Param2 [ebp + 28] but somehow somewhere there is a push that was never popped or the stack was not correctly cleaned up which is why your param1 starts at [ebp + 28]

When I said your args should start at [ebp + 8] that is when you don't have a normal prologue at the proc start.


So, without the unnecessary enter/leave, the offsets 24 and 28 work.

void asm_swap(int* a, int* B)/>{
	__asm__("\n\
		movl 24(%ebp), %ebx\n\
		movl 28(%ebp), %esi\n\
		xorl %ebx, %esi\n\
		xorl %esi, %ebx\n\
		xorl %ebx, %esi\n\
		movl %ebx, 24(%ebp)\n\
		movl %esi, 28(%ebp)\n\
	");
}


Let me make sure I am understanding this; ebx, esi, edi, and ebp were all automatically pushed by C. They each are one DWORD in size, which means 32 bits * 4 DWORDS => 16 bytes, which is why the normal ebp offset is for param1 is 16+8 => 24. Is this correct?
Was This Post Helpful? 0
  • +
  • -

#7 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,277
  • Joined: 28-March 11

Re: base pointer offset question

Posted 15 May 2012 - 05:24 PM

Yes.

Without saving EBX, ESI, and EDI then your params start at [ebp + 8]. I guess it is a bit confusing at first, but once you understand it all, it is simple really :D

So by saving them: (C already created a normal stack frame so we pushed ebp)
push ebx - param1 = [ebp + 12]
push esi - param1 = [ebp + 16]
push edi - param1 = [ebp + 20]
but... you added an extra push ebp with ENTER so:
push ebp - param1 = [ebp + 24]

Without setting up a stack frame and using esp directly, your args start at [esp + 4] since the return address is at [esp + 0]. But we really don't do it this way because you have to keep track of the offsets since they change with every push.

Quote

So, without the unnecessary enter/leave, the offsets 24 and 28 work.

You tell me :)

Here is a sample (Sorry, I don't do AT&T only INTEL syntax)
start:
    PrintDec esp
    push    3
    push    2
    push    1
    call    TestProc
    PrintDec esp ; make sure stack is same as before our call
    PrintLine

    PrintDec esp
    push    3
    push    2
    push    1
    call    TestProc2
    PrintDec esp 
    PrintLine

    PrintDec esp
    push    3
    push    2
    push    1
    call    YourProc
    PrintDec esp         
    
    push    0
    call    ExitProcess
    
TestProc:
    PrintText "Test Proc"
    push    ebp ; + 4
    mov     ebp, esp
    
    mov     eax, [ebp + 8] ; param1
    PrintDec eax
    mov     eax, [ebp + 12] ; param2
    PrintDec eax
    mov     eax, [ebp + 16] ; param3
    PrintDec eax
        
    pop     ebp
    ret     4 * 3
    
TestProc2:
    PrintText "Test Proc 2"

    push    esi ; + 4
    push    edi ; + 8
    push    ebx ; + 12
    push    ebp ; + 16
    mov     ebp, esp

    mov     eax, [ebp + 20] ; param1
    PrintDec eax
    mov     eax, [ebp + 24] ; param2
    PrintDec eax
    mov     eax, [ebp + 28] ; param3
    PrintDec eax   
    
    pop     ebp
    pop     ebx
    pop     edi
    pop     esi
    PrintLine
    ret     4 * 3
    
YourProc:
    PrintText "Your weird Proc"

    push    esi ; + 4
    push    edi ; + 8
    push    ebx ; + 12
    push    ebp ; + 16
    mov     ebp, esp    
    
    enter   0 , 0 ; + 20

    mov     eax, [ebp + 24] ; param1
    PrintDec eax
    mov     eax, [ebp + 28] ; param2
    PrintDec eax
    mov     eax, [ebp + 32] ; param3
    PrintDec eax     
    
    leave
    pop     ebp
    pop     ebx
    pop     edi
    pop     esi
    ret     4 * 3
end start


And the output:

Quote

esp = 1638252 (TestApp2.asm, 6)
Test Proc (TestApp2.asm, 32)
eax = 1 (TestApp2.asm, 37)
eax = 2 (TestApp2.asm, 39)
eax = 3 (TestApp2.asm, 41)
esp = 1638252 (TestApp2.asm, 11)
----------------------------------------
esp = 1638252 (TestApp2.asm, 14)
Test Proc 2 (TestApp2.asm, 47)
eax = 1 (TestApp2.asm, 56)
eax = 2 (TestApp2.asm, 58)
eax = 3 (TestApp2.asm, 60)
----------------------------------------
esp = 1638252 (TestApp2.asm, 19)
----------------------------------------
esp = 1638252 (TestApp2.asm, 22)
Your weird Proc (TestApp2.asm, 70)
eax = 1 (TestApp2.asm, 81)
eax = 2 (TestApp2.asm, 83)
eax = 3 (TestApp2.asm, 85)
esp = 1638252 (TestApp2.asm, 27)


You should learn to use a debugger or something that prints out values. The most important one is ESP, notice when I am playing around I print out the value of ESP before AND after a call to make sure the addresses are the same. If they are different, then something is screwed up somewhere.

This post has been edited by GunnerInc: 15 May 2012 - 05:27 PM

Was This Post Helpful? 1
  • +
  • -

Page 1 of 1