Simple Functions The simplest type of function is the CALL... RETN sequence. It requires no manipulation of ESP or EBP directly. Normally with functions we have to PUSH one or more values before making the CALL. Then we need to do ADD ESP,(number) to fix the stack. With simple functions, we don't PUSH anything before the CALL. We also don't need the line that says, ADD ESP,(number) after the CALL. Remember that we've used these kinds of simple CALL... RETN sequences before. Let's try another example. Here I will make a simple function that performs a certain task. The function will set the 3 main registers EAX, ECX, and EDX to 0. Address Instruction 00493A01 MOV EAX,0 00493A06 MOV ECX,0 00493A0B MOV EDX,0 00493A10 RETNNow I can use CALL 493A01 at any point in the code (and as many times as I want) to store the number 0 into those 3 registers.
Complex Functions Simple functions are fast and easy to use (you only need CALL and RETN), but sometimes you want a more complex function because it provides more advantages. A bit of trivia: all of the original functions in Cave Story are complex functions. The only simple functions you will find are the ones made by ASM hackers. Complex assembly functions in Cave Story will start with 2 instructions: PUSH EBP MOV EBP, ESPThey will also end with the same 3 instructions: MOV ESP, EBP POP EBP RETNIf you've looked around at the Cave Story code, you'll see that pretty much everything has these "start" and "end" pieces of code, sometimes with minor variations: Counting by 4s This is how you count by fours in hexadecimal: 0, 4, 8, C, 10, 14, 18, 1C, 20, 24, 28, 2C, 30, 34, 38, 3C, 40 ... This'll be useful knowledge in the next section. Arguments of a Function Before designing a (complex) function, the first thing we have to talk about are those PUSHes that happen right before a function CALL. Those PUSHed numbers are known as the arguments of the function. Here is the random number function. It generates a random number between a max value and a minimum value and stores that random number to EAX. Random Number Function: PUSH (max) ;one of the arguments. PUSH (min) ;the other argument. CALL 40f350 ;A random number between min and max is stored into EAX. ADD ESP,8Pixel already created this function so you can use it directly - but let's pretend that the random number function doesn't exist yet and we're designing it. Since (min) and (max) were PUSHed, how are we going to access the values (min) and (max) if they're stuck in the middle of the stack? We can't use POP to retrieve the (min) and (max) values. POP will only work with the top of the stack. If we start to POP numbers off of the stack, then we're going to lose our return address and the RETN instruction at the end of our function won't work anymore. The solution is to dig into the middle of the stack directly without modifying the values near the top of the stack. We can use the register EBP to do this. The argument that was last PUSHed before the CALL will be held in [EBP+8]. In the case of the random number function, [EBP+8] holds the value (min). The second to last argument PUSHed will be held in [EBP+C]. In the case of the random number function, [EBP+C] holds the value (max). After CALLing the random number function, the stack will look something like this: The third to last argument will be inside [EBP+10], the fourth will be inside [EBP+14], and so on. All of these memory locations are DWORD-sized and each of those memory locations represents 1 slot somewhere inside the stack. Why is the last argument PUSHed before the CALL inside DWORD [EBP+8]? Why the +8 part? It's because [EBP] holds an important value that we're not going to worry about yet, and [EBP+4] holds the return address that the instruction RETN will use later. So we must start at [EBP+8] because that's the first slot available for the arguments. The second to last argument is [EBP+C] because EBP+8+4 is the same as EBP+C. Likewise, the third to last argument is [EBP+10] because EBP+C+4 is EBP+10. In each case, we add the number 4 to the address that points to the argument. We also do this using hex math, so that 8+4 = C, not 12. The reason we must add 4 each time because each DWORD is 4 bytes. The stack has slots that are each a DWORD in size. All that talk about arguments must have been extremely confusing. It's hard to explain. Hopefully the examples in later lessons will clear things up. Previous Lesson: Valentine Nemesis Next Lesson: Designing Functions Table of Contents |