====== Scenario x64_5: Passing parameters to procedures. Implementation of the integer calculation function. ======
In this scenario, we will create a library with a function performing simple calculations on integers. Our function will add six integer values. This example will present argument passing through the registers and also through the stack, showing the order and addresses of stack-allocated arguments.
** Prerequisites **\\
Read the section “Creating static libraries”. You should have the main program and library with conversion functions ready to be able to print results in a console. They are presented in previous scenarios.
** Scenario **\\
Write the code of a library with an addition function to calculate the sum of six integer arguments.
** Result **\\
The sum is properly calculated and displayed by programs prepared in the previous scenarios.
** Start **\\
Create a new assembly source file.
** Step 1**\\
The simplest version of a function adds six integers without advanced stack manipulation. Let's present the code of a function first. It takes the first four arguments from registers, and the latter two from the stack. Please note that for each argument, there is an 8-byte space reserved on the stack.
.code
; ---------------------------------
; sum of six integer arguments
; this is a leaf function
; does not need to reserve shadow space
; arguments as passed by MSVC
; a = RCX
; b = RDX
; c = R8
; d = R9
; e = [RSP + 28h]
; f = [RSP + 30h]
; ---------------------------------
sum_6_int proc
mov rax, rcx
add rax, rdx ; a + b
mov rcx, r8
add rax, rcx ; + c
mov rcx, r9
add rax, rcx ; + d
mov rcx, QWORD PTR [rsp + 28h]
add rax, rcx ; + e
mov rcx, QWORD PTR [rsp + 30h]
add rax, rcx ; + f
ret
sum_6_int endp
The stack from a function perspective looks like in a fig.{{ref>ex_stack_simple}}.
{{ :en:multiasm:exercisebook:pc:ex_stack_simple.png?400 |Stack view inside the function with 6 arguments}}
Stack view inside the function with 6 arguments
The function is called from a main program. The caller passes arguments according to the Windows x64 ABI. First four arguments through RCX, RDX, R8 and R9 registers. Further arguments are placed onto the stack. The caller is also responsible for reserving the shadow space for all arguments before the call, even those passed through registers.
** Step 2 **\\
The first approach of calling the function will place arguments onto the stack with the **push** instruction. Additionally, it will reserve 32 bytes of shadow space before the return address is automatically placed on the stack by the call instruction.
Please note the order of arguments. It is assumed that they are placed onto the stack in reverse order. The last argument is placed on the stack first. That's why the 6th argument is at the higher address, next is the 5th argument and next there is a shadow space for arguments 1 - 4. From the perspective of a function, the first argument (or rather its shadow) is just after the return address. As the return address consumes 8 bytes, the shadow space for the first argument is at address SP+8.
Putting the first four parameters into registers is quite simple. To place remaining arguments onto the stack, we use the **push** instruction.
;call sum of 6 integers function
mov rcx, 1 ; 1st argmument
mov rdx, 2 ; 2nd argmument
mov r8, 3 ; 3rd argmument
mov r9, 4 ; 4th argmument
mov r11, 6
push r11 ; 6th argument
mov r10, 5
push r10 ; 5th argument
sub rsp, 20h ; 32 bytes of the shadow space
call sum_6_int ; function call
add rsp, 30h ; stack cleanup
mov rcx, rax ; result in rax
The figure {{ref>ex_stack_caller_push}} shows the stack organisation from the caller's perspective. First, the 6th argument is pushed onto the stack. Next, the 5th argument is pushed. Next, the 32 bytes of the shadow space are reserved with the subtraction instruction **sub rsp, 20h**. Finally, the return address is pushed by the **call** instruction. The green arrows point to the addresses where RSP points after executing the specified instructions. Note that the order of both **push** and **sub rsp, 20h** instructions is important and determines the order of placement of elements on the stack.
{{ :en:multiasm:exercisebook:pc:ex_stack_caller_push.png?400 |Stack view from the perspective of a caller}}
Stack view from the perspective of a calling code
** Step 3 **\\
It is possible to reserve the shadow space first for all six arguments and later, put the last two of them using indirect **mov** instructions. To do it, we need to calculate the proper offset from the actual stack pointer value. Note that the shadow space size reserved with the **sub rsp, 30h** instruction is 16 bytes larger than in the previous example, where an additional 16 bytes were reserved by **push** instructions. Note also that in this approach, the order of **mov [rsp+20h], r10**, and **mov [rsp+28h], r11** does not matter because these instructions do not modify the stack pointer, and the order of arguments is determined by offsets (20h, 28h) encoded in instructions. Certainly, they must be executed after the stack pointer is modified with **sub rsp, 30h**.
;call sum of 6 integers function
sub rsp, 30h ; 48 bytes of the shadow space
mov rcx, 1 ; 1st argmument
mov rdx, 2 ; 2nd argmument
mov r8, 3 ; 3rd argmument
mov r9, 4 ; 4th argmument
mov r10, 5
mov [rsp+20h], r10 ; 5th argument
mov r11, 6
mov [rsp+28h], r11 ; 6th argument
call sum_6_int ; function call
add rsp, 30h ; stack cleanup
mov rcx, rax ; result in rax
The figure {{ref>ex_stack_caller_mov}} shows the stack organisation from the caller's perspective. First, the 48 bytes of the shadow space are reserved with the subtraction instruction **sub rsp, 30h**. Next, the 5th argument is copied onto the stack at offset 20h, and the 6th argument is copied at offset 28h. Finally, the return address is pushed by the **call** instruction. The green arrows point to the addresses where RSP points after executing the specified instructions. Orange and purple arrows show where arguments are stored. The order of boxes with instructions (from top to bottom) indicates the order of execution of instructions.
{{ :en:multiasm:exercisebook:pc:ex_stack_caller_mov.png?500 |Stack view from the perspective of a caller}}
Stack view from the perspective of a calling code
** Result validation **\\
The code of a function should be called from the main program. After obtaining the result of an addition, you should call the conversion function and later display the integer value in a console.
** If it does not print the proper value? **\\
Please remember the order and placement of the arguments and result. Please keep the Windows ABI calling convention and preserve any non-volatile register before use. Observe the stack; every modification you make before a function call or inside the function should be reversed afterwards.