====== Scenario x64_8: Calling assembler procedures from C++. Integer arguments passing in C++. ====== In this scenario, we will try to call the library we prepared in scenario 5 from a C++ program written in Visual Studio Community Edition. Our function still calculates the sum of six integers. ** Prerequisites **\\ Read the section “Visual Studio Community solution with static assembly library”. You should have the library with the template of the sum function ready. They are presented in previous scenarios. ** Scenario **\\ Write the code of a C++ main program to call an assembler library function to calculate the sum of six integer arguments. Use various integer types, including **int**, **long**, and **long long**. ** Result **\\ The sum is properly calculated and displayed by a C++ program. ** Start **\\ Create a new Visual Studio solution with both C++ and assembly source files. Configure the solution for the static assembler library used in the C++ main program. ** Step 1**\\ We will write a library with a function that adds six integers (C++ int type). The code of a function is similar to that in scenario x64_5. It takes the first four arguments from registers, and the latter two from the stack. In C++, the **int** type is a signed 32-bit value, so while the assembler function is called from a C++ program, only the lower halves of registers are used. The fifth and sixth arguments are passed through the stack. Please note that for each argument, an 8-byte space is reserved on the stack. It results in the fifth argument stored at address **rsp+20h** and the sixth argument at address **rsp+28h**. .code ; --------------------------------- ; sum of six signed int arguments ; arguments as passed by MSVC ; a = ECX ; b = EDX ; c = R8D ; d = R9D ; e = [RSP + 28h] ; f = [RSP + 30h] ; --------------------------------- sum_6_int proc mov eax, ecx add eax, edx ; a + b mov ecx, r8d add eax, ecx ; + c mov ecx, r9d add eax, ecx ; + d mov ecx, DWORD PTR [rsp + 28h] add eax, ecx ; + e mov ecx, DWORD PTR [rsp + 30h] add eax, ecx ; + f ret sum_6_int endp The function is intended for calling from a main C++ 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. Just after the 8-byte return address, there is a shadow space reserved for four 64-bit registers RCX, RDX, R8, and R9. At address RSP+28h, there is a 32-bit fifth parameter placed; at address RSP+30h, there is a 32-bit sixth parameter placed. Even while parameters are 32-bit long, there are 8 bytes reserved for each of them. The stack from a function perspective looks like in a fig.{{ref>ex_stack_int}}.
{{ :en:multiasm:exercisebook:pc:ex_stack_int.png?400 |Stack view inside the function with 6 integer arguments. Details are described in a text.}} Stack view inside the function with 6 integer arguments
** Step 2 **\\ Write a C++ main program to call our sum function. The main cpp file can look as follows: #include #include "add.h" int main() { std::cout << "The sum of 6 integers: "; std::cout << sum_6_int(-1, 2, 3, 4, 5, 6); std::cout << "\n"; } We need a header file "add.h" with the prototype of a function. #pragma once extern "C" int sum_6_int(int a, int b, int c, int d, int e, int f); After successful compilation, we should observe the message appended with the sum of our six values. ** Step 3 **\\ Using the Visual Studio built-in debugger, we can observe how arguments are passed. The generated code can be seen in a disassembly window. You can open a disassembly window after stopping your program execution by choosing the menu option "Debug" -> "Windows" -> "Disassembly" or by pressing "Alt+Ctr+D". 00007FF6501B6566 C7 44 24 28 06 00 00 00 mov dword ptr [rsp+28h],6 00007FF6501B656E C7 44 24 20 05 00 00 00 mov dword ptr [rsp+20h],5 00007FF6501B6576 41 B9 04 00 00 00 mov r9d,4 00007FF6501B657C 41 B8 03 00 00 00 mov r8d,3 00007FF6501B6582 BA 02 00 00 00 mov edx,2 00007FF6501B6587 B9 FF FF FF FF mov ecx,0FFFFFFFFh 00007FF6501B658C E8 B1 AE FF FF call sum_6_int (07FF6501B1442h) 00007FF6501B6591 89 85 C0 00 00 00 mov dword ptr [rbp+0C0h],eax You can see that integer arguments are passed in halves of registers and as doublewords through the stack. If you change the types of the arguments and results to the **long long int** type, the compiler will use 64-bit registers and instructions where required. Note that if the value fits 32 bits, the mov instruction is still encoded using a 32-bit constant. This is because **mov** with a constant in 64-bit mode automatically sign-extends the argument. 00007FF6501B65BF 48 C7 44 24 28 06 00 00 00 mov qword ptr [rsp+28h],6 00007FF6501B65C8 48 C7 44 24 20 05 00 00 00 mov qword ptr [rsp+20h],5 00007FF6501B65D1 41 B9 04 00 00 00 mov r9d,4 00007FF6501B65D7 41 B8 03 00 00 00 mov r8d,3 00007FF6501B65DD BA 02 00 00 00 mov edx,2 00007FF6501B65E2 48 C7 C1 FF FF FF FF mov rcx,0FFFFFFFFFFFFFFFFh 00007FF6501B65E9 E8 5E AE FF FF call sum_6_longlong (07FF6501B144Ch) 00007FF6501B65EE 48 89 85 C0 00 00 00 mov qword ptr [rbp+0C0h],rax ** Step 4 **\\ The task for your own implementation is to write the function that calculates the sum of three int and three long long int arguments, returning long long int result. Please note that the types are not checked between the C++ main program and the assembler library. The C++ program relies on the prototype declared in the header file. Make experiments with types of arguments, for example, check the behaviour if you use the **int** type in C++ and a 64-bit register in assembler, and the **long long int** type in C++ and a 32-bit register used in assembler. ** Result validation **\\ The code of a function should be called from the main program. After obtaining the result of an addition, you should observe a proper integer value in the console. Please check the positive and negative input values. ** If it does not print the proper value? **\\ If you make calculations on 64-bit values in assembler and use a 32-bit int type in C++, you may have the upper half of the register or variable (eg, on the stack) set with some previous value. In such a situation, the compiler does not use instructions that perform sign-extension or clear the upper half of the destination argument. 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.