.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