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.1.

Stack view inside the function with 6 arguments
Figure 1: 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 2 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.

Stack view from the perspective of a caller
Figure 2: 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 3 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.

Stack view from the perspective of a caller
Figure 3: 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.

en/multiasm/exercisesbook/pc/sut/scenarios/win5.txt · Last modified: by ktokarz
CC Attribution-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0