Welcome back, 1
we continue with x64 calling convention. 2
In this example, 3
we will write a simple program to calculate a sum. As you see, here we have two procedures. 4
The main function and addition. In main function, we call addition to do the calculation 5
and call c library function printf to print the result. 6
The addition and printf use Microsoft x64 calling convention. 7
So first we extern printf. The c language has a large set of function in c standard library. 8
All of the functions in c standard library are available to the program that is linked with the c standard library. 9
So we use extern to declare printf which is not defined in this program, but defined in other module. 10
You see 11
this is really simple. When we want to use functions in this library. 12
All we need to know is the name of the function and how to pass parameters to it. 13
Ok let’s first take a look at addition. The addition procedure has 8 parameters. 14
Suppose we want to add 1 through 8. The first four parameters are passed in registers. 15
We mov rcx,1 mov rdx,2 mov r8,3 16
and mov r9,4. The next four parameters are passed on the stack. 17
Generally, we don’t use push instruction to push parameters on the stack in 64-bit mode. 18
Instead, we calculate how much storage we need in the procedure and allocate the storage by adjusting rsp register only once. 19
So let’s see the state of the stack. In the main function, we call addition and printf. 20
Addition takes 8 parameters and printf takes 2 parameters in this example 21
. 22
So we pass parameters of printf in registers and push the last four parameters of addition on the stack 23
and we also allocate 32bytes of shadow space. 24
The storage we need to allocate is 64 bytes in total. 25
And don’t forget the fact that rsp register must be aligned on a 16-byte boundary 26
when you call a function which uses Microsoft x64 calling convention. 27
Since call instruction pushes 8-byte return address on the stack, we must subtract 8 from rsp to make it align on 16-byte boundary. 28
In this example, 29
we set aside 64 bytes on the stack which is already aligned on 16-byte value. 30
You can check it by dividing the value 64 in this case by 16. If it is divisible by 16, then it is aligned on a 16-byte value. 31
So all we need to do is subtract 72 bytes from rsp. 32
Let’s save the last four parameters on the stack. 33
As you see, the last parameter is on the high memory address which means the parameters are 34
pushed on the stack in reverse order. The last parameter is the first one pushed on the stack. 35
I have listed the offset related to rsp register. 36
The first 32bytes is shadow space, and the 5th parameter, the 6th parameter and so on. 37
So the 5th parameter is addressed by rsp+32 and we save 5 in it. Then save 6 in the location address 38
by rsp+40, the 7th one and the last one. 39
Alright, with all the parameters saved on the stack, we call addition. When we return from addition, 40
the result is in rax. Next thing we are going to do is print the value in rax. 41
We use printf function. 42
The first parameter it takes is the address of format string. The format string is null-terminated string. 43
The function will examine the format string to know how many parameters are passed to it. 44
Notice that the %d is specifier. When printf encounters a specifier, 45
it assumes that the corresponding parameter, which is the second parameter in this case, has been passed to it, and retrieve the parameter, 46
convert it according to the specified format and print it. %d means the parameter 47
is printed 48
as a signed decimal number. Other characters in the format string will be printed as they are. 49
Ok. 50
We save the address of the format string in rcx 51
and the value we want to print in rdx. 52
Then call printf. After we return from printf, we are at the end of the main function, we add rsp 72 53
to restore the state of the stack and we return. Ok what we are going to do next is write addition procedure. 54
In addition function, 55
the first thing we are going to do is push rbp, since we will use rbp as frame pointer 56
to access parameters and local variables. And also we will use r12 and r13 registers to save temporary result. 57
Remember r12 and r13 is nonvolatile registers, so we push them on the stack to save their value. 58
We mov rbp rsp and use rbp as frame pointer. Since addition is a simple procedure, we don’t need local variables. 59
But for demonstrating purpose, 60
suppose we use two local variables vara and varb which are quadword size variables. If we call other functions 61
, 62
we also set aside 32 bytes of shadow space. So we subtract rsp, 48. 63
Ok let’s check rsp register to see if it is aligned on 16-byte boundary. When we enter the procedure, 64
return address is pushed on the stack. Then we push rbp. Rsp register is aligned on 16-byte value at this point. 65
And we also save r12, r13 on the stack and allocate 48 bytes of storage for local variables 66
and shadow space. So 64 bytes of space is used in this case 67
which is already aligned on 16-byte value. And we can call other functions if we need to. The next thing we are going to do 68
is calculate the sum of the parameters. We mov r12,rcx and add them to r12. 69
Suppose we want to save the sum in local variable vara which is pointed to by rbp-8, 70
so we mov rbp-8, r12. 71
Calculating the sum of the next four parameters needs to access the stack. As you see, the 5th parameter is at rbp+64. 72
So we mov r13 rbp+64. The 6th one is at rbp+72 73
and so on. And we add them to r13 register. 74
We also save it to varb. So we mov rbp-16,r13. 75
Ok we have the sum of the first four parameters in r12 and other ones in r13 register. 76
We simply add r12,r13 and save the sum in rax 77
as return value 78
. 79
At the end of the procedure, we restore the old rsp value. The rsp points to the old r13 value at this point. 80
We pop r13, r12 81
and rbp. 82
Alright we have restored the nonvolatile registers and rsp points to the return address. 83
We write return and we are done. 84
Let's check it out in debug mode. 85
We allocate the stack at the beginning of the procedure. 86
We add rsp in memory window. 87
We save the first four parameters in registers. And also save the last four parameters of addition on the stack 88
. 89
How do you see the last four parameters. 90
As you see the last four parameters are saved on the stack. 91
We click step into and we enter the procedure. 92
Then we push nonvolatile registers on the stack and make rbp as frame pointer. 93
Next we allocate the storage for local variables and shadow space. Before we do the calculation, 94
we add rbp in memory window in order to see the value of parameters and local variables. 95
OK. 96
Let's run these instructions. Add the first four parameters to r12. 97
Save the sum in vara and calculate the sum of next four parameters. We add them to r13 register 98
and save it in varb. 99
Finally we add r12 r13 which produce the final result. Save it in rax. 100
You can see the sum is in rax. At the end of the procedure, 101
we restore rsp value by moving rsp rbp. And pop the original value of r13, r12 102
and rbp from stack. Ok the nonvolatile registers are restored. Rsp is pointing to the return address. 103
Execute return instruction and we return from addition. You can see the return value is in rax. 104
We save the address of format string in rcx and return value in rdx and we call printf. 105
Since printf is not the function we implement, we simple click step over to skip it. 106
When we return from printf, we deallocate the storage by adjusting rsp register and we are done. 107
As you see the result is printed in output window. 108
This is the end of the lesson.