Post

Invoking assembly functions from MSVC-compiled x64 C++ program

Introduction

Since this is the first article of my blog, I start with relatively very simple subject.

So in this post I’ll be talking about how you can call functions defined in independent assembly file from C++.

Table of Contents

Background and expected environment

Why do we have to use independent asm file rather than inline assembly in the first place? You can simply write inline assembly like this, right?

1
2
3
4
5
__asm {
  push eax
  xor eax, eax
  // ... whatever
}

Well I initially thought of it too.

However, lord MSVC compiler doesn’t allow us to levarage inline assembly with x64 target architecture. And one of the work arounds of it is using .asm file while I guess you can pull it off with shell code too yet it might get more complicated.

so the expected environment is:

  • Windows 10
  • MSVC compiler
  • x64 target architecture

How to do it?

I have suitable repository for this topic so I’ll pick up code from there as a reference. vxcall/call_cpuid_asm

C++ side

Let’s implement C++ code first to grasp the overview.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <string>

extern "C" void get_cpu_type(char* sys_type);

std::string get_cpu_type_string()
{
  char sys_type[13];
  get_cpu_type(sys_type);
  sys_type[12] = '\0';
  return std::string(sys_type);
}

int main()
{
  std::string cpu_type = get_cpu_type_string();
  std::cout << "CPU TYPE: " << cpu_type << std::endl;

  std::cin.get();

  return 0;
}

Here’s the line by line explanation.

1
extern "C" void get_cpu_type(char* sys_type);

Firstly you need the forward declaration of get_cpu_type function which we’ll define later in asm file.

1
2
3
4
5
6
7
std::string get_cpu_type_string()
{
  char sys_type[13];
  get_cpu_type(sys_type);
  sys_type[12] = '\0';
  return std::string(sys_type);
}

Secondly setting up wrapper function. Since the asm function expects buffer as param in our case, so we create a wrapper not to mess up the main function.

The buffer size is supposed to be 12 chars + null terminator = 13. The get_cpu_type function will populate the buffer with string and we manually adding null terminator at last and returning it as std::string.

Don’t worry once you see the asm function and come back here, you’ll get it.

Lastly you’ve got main function which I’ll omit because it’s self-explanatory.

Assembly side

Let’s move onto asm side. First create get_cpu_type.asm file.

Here I’ll explain how to write simple asm in intel syntax for MSVC using this sample code.

1
2
3
4
5
6
7
8
9
10
11
12
public get_cpu_type
.code _text
get_cpu_type proc public
    mov     rax, 0
    mov     rdi, rcx
    cpuid
    mov     dword ptr [rdi], ebx
    mov     dword ptr [rdi+4], edx
    mov     dword ptr [rdi+8], ecx
    ret
get_cpu_type endp
end

I’ll describe the code line by line.

1
public get_cpu_type

This statement marks the get_cpu_type function global making other files can access this function.

However, in terms of MASM (Microsoft Macro Assembler) which we gonna use later to compile assembly marks every functions as public by default so technically you dont have to explicitly mark them as public tbh.

1
.code _text

This line serves as a directive that specifies the beginning of a code section named _text. The part “_text” could be whatever else, even removing it entirely works as well.

1
2
3
get_cpu_type proc public
; body
get_cpu_type endp

This is the function definition. It should start with FUNCTION_NAME proc public and end with FUNCTION_NAME endp

This public statement is also optional because of the reason I mentioned prior.

1
2
3
4
5
6
7
mov     rax, 0
mov     rdi, rcx
cpuid
mov     dword ptr [rdi], ebx
mov     dword ptr [rdi+4], edx
mov     dword ptr [rdi+8], ecx
ret

This is the function body. It’s basically loading buffer address to rdi and populating the buffer. Each ebx, edx, ecx registers holds byte representations of ascii characters.

Importantly, you must end the function with ret instruction of course to get back to return address.

That’s it! Simple.

Visual Studio side

We’ve done both cpp and asm coding but there is one last thing to do.

By default all .asm files are not included in the visual studio project when it comes to user mode application i believe.

  1. First right click project in solution explorer and click Build Customizations. menu

  2. Check masm(.targets,.props) and click OK. masm

  3. Right click your get_cpu_type.asm file and goto Properties. ap

  4. Change Item Type to Microsoft Macro Assembler. asm_property

Now your asm file is included in compile process and good to go.

Conclusion

footer

As you saw, it’s actually not that overwhelming to use asm with C++.

I believe using asm in conjunction with C++ has your code more flexible. Moreover, it makes you feel like you’re a gigachad despite of how actually you are.

Thanks for reading my article. :)

This post is licensed under CC BY 4.0 by the author.

© vxcall. Some rights reserved.