425 lines
12 KiB
ArmAsm
425 lines
12 KiB
ArmAsm
|
.file "functions.c"
|
||
|
.version "01.01"
|
||
|
gcc2_compiled.:
|
||
|
.section .rodata
|
||
|
.LC0:
|
||
|
.string "%d, %d, %d\n"
|
||
|
.text
|
||
|
.align 4
|
||
|
.globl function3args
|
||
|
.type function3args,@function
|
||
|
function3args:
|
||
|
/* This push saves the ebp, and in combination with the move is called
|
||
|
* the function prolog. */
|
||
|
pushl %ebp /* at (%ebp) on the stack */
|
||
|
movl %esp,%ebp
|
||
|
|
||
|
/* This subl is used to allocate space for any local variables. In
|
||
|
* this case we have none, and we can see the fact that this
|
||
|
* instruction is useless because no stack references are negative
|
||
|
* offsets from the %ebp (visualize or draw the stack to see this).
|
||
|
* I'm not sure why GCC does this. */
|
||
|
subl $8,%esp
|
||
|
/* (%esp) == -8(%ebp) */
|
||
|
|
||
|
/* remember our comments. This instruction copies the last argument of
|
||
|
* the function to %eax*/
|
||
|
movl 16(%ebp),%eax
|
||
|
|
||
|
/* push this value as the last argument to the printf call.
|
||
|
* Note: This is why we have an %ebp register, because this push will
|
||
|
* affect the %esp, not the %ebp, and our references to local
|
||
|
* variables all remain the same still. */
|
||
|
pushl %eax
|
||
|
/* (%esp) == -12(%ebp) */
|
||
|
|
||
|
/* Now access the second argument of the function, and push it */
|
||
|
movl 12(%ebp),%eax
|
||
|
pushl %eax
|
||
|
/* (%esp) == -16(%ebp) */
|
||
|
|
||
|
/* Access the first argument of the function. Remember that the
|
||
|
* remaining two things below 8(%ebp) are the return address at
|
||
|
* 4(%ebp) and the old value of %ebp, which is at (%ebp) */
|
||
|
movl 8(%ebp),%eax
|
||
|
pushl %eax
|
||
|
/* (%esp) == -20(%ebp) */
|
||
|
|
||
|
/* Push the string onto the stack */
|
||
|
pushl $.LC0
|
||
|
/* (%esp) == -24(%ebp) */
|
||
|
call printf
|
||
|
/* (%esp) == -24(%ebp) because the stack is reset fixed after a call */
|
||
|
|
||
|
/* Again, "pop" all 16 bytes of arguments off the stack */
|
||
|
addl $16,%esp
|
||
|
|
||
|
/* (%esp) == -8(%ebp) */
|
||
|
|
||
|
.L2:
|
||
|
/* Leave copies the value of %ebp into %esp, effectively popping all
|
||
|
* extra local variables and junk off the stack. It then pops the top
|
||
|
* value off the stack (which is the saved %ebp) and stores it in %ebp
|
||
|
*
|
||
|
* So it is basically the reverse of the function
|
||
|
* prolog, and implicityly removes any local variables and junk that
|
||
|
* GCC may have thrown on the stack. This is key, because GCC loves to
|
||
|
* throw junk on the stack for no reason. It is all taken care of at
|
||
|
* function exit because of this instruction */
|
||
|
leave
|
||
|
|
||
|
/* (%esp) == (%ebp) == (old %ebp) just after call */
|
||
|
|
||
|
/* pops the return address saved on the stack into %eip, and thus
|
||
|
* execution transfers to just after the call */
|
||
|
ret
|
||
|
.Lfe1:
|
||
|
.size function3args,.Lfe1-function3args
|
||
|
.align 4
|
||
|
.globl function3argsRet
|
||
|
.type function3argsRet,@function
|
||
|
function3argsRet:
|
||
|
pushl %ebp
|
||
|
movl %esp,%ebp
|
||
|
|
||
|
/* Move the first argument to %edx */
|
||
|
/* The first argument is at 8 above the ebp. Ie it as at the lowest
|
||
|
* address of all arguments. The rest are at higher address */
|
||
|
movl 8(%ebp),%edx
|
||
|
|
||
|
/* multiply the second argument with %edx, store in %edx */
|
||
|
imull 12(%ebp),%edx
|
||
|
|
||
|
/* multiply the third argument with %edx, store in %edx */
|
||
|
imull 16(%ebp),%edx
|
||
|
|
||
|
/* Move %edx to %eax. %eax is the return value */
|
||
|
movl %edx,%eax
|
||
|
|
||
|
/* Alignment junk */
|
||
|
jmp .L3
|
||
|
.p2align 4,,7
|
||
|
.L3:
|
||
|
leave
|
||
|
ret
|
||
|
.Lfe2:
|
||
|
.size function3argsRet,.Lfe2-function3argsRet
|
||
|
.align 4
|
||
|
.globl functionPtrArg
|
||
|
.type functionPtrArg,@function
|
||
|
functionPtrArg:
|
||
|
pushl %ebp
|
||
|
movl %esp,%ebp
|
||
|
subl $8,%esp
|
||
|
|
||
|
/* move the third argument (the pointer) into eax */
|
||
|
movl 16(%ebp),%eax
|
||
|
|
||
|
/* derefrence it. Remember how I said that leal does not deref, but
|
||
|
* mov does? */
|
||
|
movl (%eax),%edx
|
||
|
|
||
|
/* push the rest of the args, and call printf */
|
||
|
pushl %edx
|
||
|
movl 12(%ebp),%eax
|
||
|
pushl %eax
|
||
|
movl 8(%ebp),%eax
|
||
|
pushl %eax
|
||
|
pushl $.LC0
|
||
|
call printf
|
||
|
addl $16,%esp
|
||
|
.L4:
|
||
|
leave
|
||
|
ret
|
||
|
.Lfe3:
|
||
|
.size functionPtrArg,.Lfe3-functionPtrArg
|
||
|
.align 4
|
||
|
.globl functionPtrRet
|
||
|
.type functionPtrRet,@function
|
||
|
functionPtrRet:
|
||
|
pushl %ebp
|
||
|
movl %esp,%ebp
|
||
|
|
||
|
/* Put the first argument of our function */
|
||
|
movl 8(%ebp),%eax
|
||
|
movl %eax,%edx
|
||
|
|
||
|
/* put the address made by 0 + %edx*4 into register %eax */
|
||
|
leal 0(,%edx,4),%eax
|
||
|
movl %eax,%edx
|
||
|
|
||
|
/* Add the third argument of our function (the pointer) to the result */
|
||
|
addl 16(%ebp),%edx
|
||
|
|
||
|
/* Put the second arg into eax */
|
||
|
movl 12(%ebp),%eax
|
||
|
movl %eax,%ecx
|
||
|
|
||
|
/* put the address 0 + %ecx*4 into %eax. */
|
||
|
leal 0(,%ecx,4),%eax
|
||
|
|
||
|
/* add %eax to %edx, store in %edx.
|
||
|
* If you were keeping track of the registers like you should have been,
|
||
|
* you should now realize that %edx contains pointer + second_arg*4 +
|
||
|
* third_ard*4. In other words, we know pointer is an integer pointer
|
||
|
* because the scale was 4 during all the pointer arithmetic */
|
||
|
addl %eax,%edx
|
||
|
|
||
|
/* Put the result into the return value register %eax */
|
||
|
movl %edx,%eax
|
||
|
jmp .L5
|
||
|
.p2align 4,,7
|
||
|
.L5:
|
||
|
leave
|
||
|
ret
|
||
|
.Lfe4:
|
||
|
.size functionPtrRet,.Lfe4-functionPtrRet
|
||
|
.align 4
|
||
|
.globl functionLocalVars
|
||
|
.type functionLocalVars,@function
|
||
|
functionLocalVars:
|
||
|
pushl %ebp
|
||
|
movl %esp, %ebp
|
||
|
/* so this is enough space for 4 integer variables, but sometimes GCC
|
||
|
* allocates more space than it needs, especially in recent versions.
|
||
|
* Note in this case, we have only THREE variables in our function.
|
||
|
* But we will actually get to see GCC use this magic local variable
|
||
|
* in a bit. Most times we aren't so lucky. */
|
||
|
subl $16, %esp
|
||
|
|
||
|
/* recall 12 from ebp is the second 4-byte function argument (note
|
||
|
* that if this function had non-integer arguments, 12(%ebp) might be
|
||
|
* like the 3rd or 5th argument. Just something to keep in mind) */
|
||
|
movl 12(%ebp), %eax
|
||
|
|
||
|
/* XOR the second function arg with the first function arg */
|
||
|
xorl 8(%ebp), %eax
|
||
|
|
||
|
/* Store it in the first local variable. So the first local variable
|
||
|
* now contains arg1 ^ arg2. This update of a local variable should
|
||
|
* clue you into the completetion of a C statement.
|
||
|
* In this case, we have determined that the statement was
|
||
|
* local1 = arg1 ^ arg2;
|
||
|
*/
|
||
|
movl %eax, -4(%ebp)
|
||
|
|
||
|
|
||
|
|
||
|
/* put the first arg into %edx */
|
||
|
movl 8(%ebp), %edx
|
||
|
|
||
|
/* Take the address of the second function arg.. */
|
||
|
leal 12(%ebp), %eax
|
||
|
|
||
|
/* put it into what appears to be the fourth local variable (again,
|
||
|
* it could be the the 9th, 17th, etc)
|
||
|
*
|
||
|
* HOWEVER, NOTE: We do NOT have 4 local variables in the
|
||
|
* corresponding C code. GCC has created a temporary here to do the
|
||
|
* calculation. This is further evidence of non-optimized code. */
|
||
|
movl %eax, -16(%ebp)
|
||
|
|
||
|
/* check your sheet for %edx */
|
||
|
movl %edx, %eax
|
||
|
|
||
|
/* Move the fourth local variable into %ecx. So, following your sheet,
|
||
|
* %ecx now contains the address of the second function arg. */
|
||
|
movl -16(%ebp), %ecx
|
||
|
|
||
|
/* FIXME: BUH? */
|
||
|
cltd
|
||
|
|
||
|
/* So here's an odd intruction. Basically, if you check the Intel
|
||
|
* Instruction set reference, you see that idiv takes a single
|
||
|
* argument of either a register %reg or an indirected register (ie a
|
||
|
* register containing a memory location, (%reg)) and then divides
|
||
|
* %eax by the value in %reg or at memory location (%reg). The result is
|
||
|
* stored in %eax, and the remainder is in %edx.
|
||
|
*/
|
||
|
|
||
|
/* Do: %eax = %eax/(%ecx); %edx = %eax MOD (%ecx);
|
||
|
* so from your sheet, %eax = arg1/arg2; %edx = arg1 MOD arg2 */
|
||
|
idivl (%ecx)
|
||
|
|
||
|
/* Move result to second local variable. So local2 = arg1 / arg2; */
|
||
|
movl %eax, -8(%ebp)
|
||
|
|
||
|
|
||
|
|
||
|
/* Move first arg to %edx */
|
||
|
movl 8(%ebp), %edx
|
||
|
|
||
|
/* Put the address of the second arg into %eax */
|
||
|
leal 12(%ebp), %eax
|
||
|
|
||
|
/* Use that temporary variable again */
|
||
|
movl %eax, -16(%ebp)
|
||
|
movl %edx, %eax
|
||
|
movl -16(%ebp), %ecx
|
||
|
|
||
|
cltd
|
||
|
|
||
|
/* %eax = %eax/(%ecx); %edx = %eax MOD (%ecx);
|
||
|
* So, %eax = arg1/arg2; %edx = arg1 MOD arg2;
|
||
|
*/
|
||
|
idivl (%ecx)
|
||
|
|
||
|
/* Store %edx into third local variable. So local3 = arg1 MOD arg2; */
|
||
|
movl %edx, -12(%ebp)
|
||
|
|
||
|
|
||
|
|
||
|
/* Put the local2 into %eax */
|
||
|
movl -8(%ebp), %eax
|
||
|
|
||
|
/* %eax = local1 | %eax */
|
||
|
orl -4(%ebp), %eax
|
||
|
|
||
|
/* local3 = local1 | local2 */
|
||
|
movl %eax, -12(%ebp)
|
||
|
|
||
|
/* Put local2 into eax */
|
||
|
movl -12(%ebp), %eax
|
||
|
|
||
|
/* %eax = local1 & local2 */
|
||
|
andl 8(%ebp), %eax
|
||
|
|
||
|
/* Junk instruction that says return %eax */
|
||
|
movl %eax, %eax
|
||
|
leave
|
||
|
ret
|
||
|
.Lfe5:
|
||
|
.size functionLocalVars,.Lfe5-functionLocalVars
|
||
|
.align 4
|
||
|
.globl main
|
||
|
.type main,@function
|
||
|
main:
|
||
|
/* save ebp */
|
||
|
pushl %ebp
|
||
|
|
||
|
/* move esp to ebp so we can access vars from ebp */
|
||
|
movl %esp,%ebp
|
||
|
|
||
|
/* allocate stack space.. Notice that gcc likes to allocate WAY more
|
||
|
* space than it needs in some cases.. why this is, I don't know.
|
||
|
* We really only need 4 bytes of space here for our int a, and a
|
||
|
* quick scroll through the function shows that -4(%ebp) is the only
|
||
|
* local variable we use */
|
||
|
subl $24,%esp
|
||
|
#APP
|
||
|
nop
|
||
|
#NO_APP
|
||
|
/* So here we see that GCC pushes some mystery arg onto the stack,
|
||
|
* and then the three arguments in reverse order, followed by the call
|
||
|
* to function3args. Remember that the call instruction places the
|
||
|
* address of the next instruction onto the stack. So at the entrance
|
||
|
* to function3args, esp points to the return address, and we have 20
|
||
|
* bytes above the esp, including ret and the mystery argument.
|
||
|
*
|
||
|
* However, since we are working on source generated without
|
||
|
* -fomit-frame-pointer, there will be a push of the ebp, and then the
|
||
|
* esp will be copied to ebp, and variables will be referenced from the
|
||
|
* ebp.
|
||
|
*/
|
||
|
addl $-4,%esp /* 20(%ebp) after prolog */
|
||
|
pushl $3 /* 16(ebp) */
|
||
|
pushl $2 /* 12(%ebp) */
|
||
|
pushl $1 /* 8(%ebp) */
|
||
|
call function3args /* 4(%ebp) */
|
||
|
|
||
|
/* Go to function3args and see the comments there to see these
|
||
|
* variables in action */
|
||
|
|
||
|
/* This stack ajustment is the same as popping all 4 arguments off the
|
||
|
* stack, ie the 3 integers and the mystery arg. */
|
||
|
addl $16,%esp
|
||
|
#APP
|
||
|
nop
|
||
|
#NO_APP
|
||
|
|
||
|
/* So this function is the same exact deal as the previous, except we
|
||
|
* have a return value. GCC uses the eax register to store the return
|
||
|
* value of a function.
|
||
|
* A good excercise would be to follow the stack along yourself with
|
||
|
* a sheet of paper for this example. */
|
||
|
addl $-4,%esp
|
||
|
pushl $3
|
||
|
pushl $2
|
||
|
pushl $1
|
||
|
call function3argsRet
|
||
|
addl $16,%esp
|
||
|
|
||
|
/* Junk instruction, unoptimized code */
|
||
|
movl %eax,%eax
|
||
|
|
||
|
/* Notice now that %eax is copied into the first local variable */
|
||
|
movl %eax,-4(%ebp)
|
||
|
#APP
|
||
|
nop
|
||
|
#NO_APP
|
||
|
|
||
|
/* This function exists as an example of what happens when you have a
|
||
|
* pointer as an argument. */
|
||
|
addl $-4,%esp
|
||
|
|
||
|
/* the lea instruction loads the effective address of its first
|
||
|
* argument and places it in the second. In other words, it simply
|
||
|
* adds the offset to the register being indexed, and then moves that
|
||
|
* into the destination.
|
||
|
*
|
||
|
* It is easy to become confused with this instruction, because it
|
||
|
* actually does NOT derefrence the first arg, where as a mov does.
|
||
|
*/
|
||
|
|
||
|
/* Load the address of the first local variable into %eax */
|
||
|
leal -4(%ebp),%eax
|
||
|
|
||
|
/* push it. Thus the pointer is the third argument */
|
||
|
pushl %eax
|
||
|
pushl $3
|
||
|
pushl $1
|
||
|
call functionPtrArg
|
||
|
addl $16,%esp
|
||
|
#APP
|
||
|
nop
|
||
|
#NO_APP
|
||
|
/* The example is the same as the previous, except we return a
|
||
|
* pointer */
|
||
|
addl $-4,%esp
|
||
|
|
||
|
leal -4(%ebp),%eax
|
||
|
pushl %eax
|
||
|
pushl $3
|
||
|
pushl $1
|
||
|
call functionPtrRet
|
||
|
addl $16,%esp
|
||
|
movl %eax,%eax
|
||
|
/* Put the value in %eax into the second local variable. So the second
|
||
|
* var must be an int pointer from out conclusions in functionPtrRet */
|
||
|
movl %eax,-8(%ebp)
|
||
|
#APP
|
||
|
nop
|
||
|
#NO_APP
|
||
|
/* This example is intended to show how a function handles local
|
||
|
* variables as always being negative offsets from the %ebp */
|
||
|
|
||
|
/* Here we see another mystery stack allocation.. */
|
||
|
subl $8, %esp
|
||
|
pushl $2
|
||
|
pushl $1
|
||
|
call functionLocalVars
|
||
|
addl $16, %esp
|
||
|
movl %eax, %eax
|
||
|
movl %eax, -4(%ebp)
|
||
|
#APP
|
||
|
nop
|
||
|
#NO_APP
|
||
|
|
||
|
.L6:
|
||
|
leave
|
||
|
ret
|
||
|
.Lfe6:
|
||
|
.size main,.Lfe6-main
|
||
|
.ident "GCC: (GNU) 2.95.4 (Debian prerelease)"
|
||
|
// vim:noet
|