From the course: Learning Assembly Language

Calling subprograms - Python Tutorial

From the course: Learning Assembly Language

Start my 1-month free trial

Calling subprograms

- [Instructor] As we develop larger programs, we'll need to structure code into manageable modules and then call them with arguments as required. We have the option of doing this with the Call or the Invoke commands. Let's have a look at how we do this by coding a simple adder using each approach. In the Data section, we'll declare three quad words, value1 dq 18, value2 dq 24, and value3, which will be the result of the addition dq, and we'll just put 0 there. We'll code up the first adder in a way that we can use it through the Invoke command, and we do this. We'll call it adder1 and declare it as a Frame with three arguments, pvalue1, pvalue2, and pvalue3, which are pointers to the three variables. Now, we automatically get a Ret and an EndF put in for us. We'll declare a uses rax and rdx. We'll be using those registers, and the user's command will automatically save and restore those registers at the beginning and the end of the function call. We'll then move into edx the pointer to our first value, and we'll use that to move into rax the value itself. Similarly, we'll move into edx the pointer to our second value, and we'll add into rax that value, and we'll move into edx the pointer to our third value, and we'll move into that location our rax result. Okay, so that's all we need to do for the first adder routine that we'll invoke. The second routine is one which runs in the existing stack frame and can be called, and we'll take a more manual approach to protecting registers. We'll call it adder2, and we only need to declare a label, not a frame. We'll push ebp onto the stack. We're saving the current value in our ebp register, and we're going to use the ebp register, move into ebp the current stack pointer, and this will give us a stack pointer to point to the values that we're sending in the stack for the addition routine. We're going to be using rax, so we'll save its current value, and we're going to be using edx, so we'll save its current value as well on the stack. Okay, so for getting to the addition, we move into rax our first value, and the value is on the stack, and we can access it by using ebp+10 hexadecimal, and that points to the location on the stack where the first value was pushed. We can add to that, add to rax the second value which is on the stack, which is ebp+18, and then we'll move into edx ebp+20, which is the address of the result, and we will then move into that address rax, which is the result itself. Okay, we've done our addition. We now need to wind back the stack, pulling off the registers and recovering them, so we pop edx. We do this the opposite way around to the way we put it on. We pop edx, pop rax. We move our ebp pointer that we stored the value of the stack back into the stack pointer, and we pop ebp to recover the original ebp value. We then ret 24, and that returns, taking 24 bytes off the stack, which is three eight quad word values that we put on the stack. Okay, let's see what our calls look like if we go down to the Main Frame function. For the invoke, we need to set up three arguments. They're all addresses: address of value3, arg address value2, arg address value1, and then invoke adder1. Before we go into the call routine, we'll clear our results so it's a little bit easier to see what's going on when we debug, so we'll clear rax, and we'll move that into our value3, so we're ready to go into the adder routine. We'll push the address of value3 onto the stack. We'll push the content of value2 onto the stack. We'll push the content of value1 onto the stack, and we'll call adder2. Okay, let's build this and debug it. We'll run to user code, and we'll go down into our adder code, so let's step down to 4010A9. Here we see that we're not pushing arguments onto the stack before the invoke, but we're loading registers. We can see that R8, RDX, and RCX are being used to pass parameters. This is important to know because anything we may have held in those registers prior to the invoke will be overwritten, and this can cause problems when invoking a function, for example, within a loop because we use ecx as the loop counter, so let's load those three addresses and call starter, and note we're doing a sub rsp20 because we're setting up a new frame. When we get into starter, we push the three registers that we've got parameters in onto the stack as though they'd have been pushed on before we came into the routine. We then push rbp, and rax, and rdx. We move our stack pointer into rbp. All this has been automatically generated for us by the frame call. Okay, move into edx the address of our first value, and move that into rax, 12 hexadecimal. Move the address of our second value and add that to rax to give 2A, and we move the address of value3 into edx, and if we have a look at the memory map, at the next instruction, we'll see the result being stored at 402018. We then recover the stack pointer, pop the registers that we've used in rbp, and return. Okay, down at 4010CD, we can see that we clear rax, and we store that over the result, value3, so we have in memory a 0 result. At 4010D7 and 4010DE, we're loading the address of value3 and pushing it on the stack, and the assembler is using R11 to do that for us. We then push the values of value2 and value1 onto the stack directly, and we can see at the lower right the stack pointer and the stack itself. We then go into adder2, and we have our manual code to save rbp, move the current esp, current stack pointer, into ebp, push the two registers that we're going to be using. We load rax with the value of value1. We add value2 to it. We move the address of value3 into edx, and then, at the lower left, we'll see our memory being changed as we store the result. We then pop the registers, restore the stack pointer, pop rbp, and return.

Contents