Invoking Assembly Code in C#
Abstract
This article explains the techniques of inline Assembly programming by linking or invoking the CPU-dependent Native Assembly 32-bit code to C#.NET managed code. The .NET framework in fact doesn't support assembly code execution explicitly via the CLR compiler because it JITs the IL code to native code, and there is no such provision for assembly code in the .NET framework. But VC++ inline assembler can integrate any C or C++ methods in its scope due to its convenient nature.
Despite the complicated nature of this process, it is very useful in some scenarios where faster optimized code matters, because a larger calculation must be completed fast and C++ or C# code are relatively slow to process a complicated algorithm. Apart from program improvement, inline Assembly code also assists to control hardware and reduce memory leaks in a better way rather than .NET or VC++ language. We shall demonstrate assembly code invoking into a genuine C# code by presenting a GUI application which renders some math and CPU information gathering functionality by calling an ASM code library.
Prerequisite
The example scenario illustrated in the article requires comprehensive working experience for manipulating both managed and unmanaged DLL, and moreover the knowledge of MASM programming code syntax, because the DLL is going to contain methods definitions in the form of Assembly language. Our workstation must be configured with these software:
- Visual C++
- Visual C#.NET
- MASM (optional)
Fill out the form below to download the code associated with this article.
The Mechanics
Invoking hard core Assembly code into managed .NET code is a rather complicated task because CLR typically doesn't execute ASM instructions. But as we know, C# can consume the methods of an unmanaged code which could be located in any VC++ or C++ DLL and more interestingly, C++ or VC++ can execute or integrate Assembly code. So here is the hack, VC++ can become a mediator between Assembly code and C#.NET source code, and we can invoke the ASM code into C# code indirectly. However, we will first write some functionality for specific operations in the form of Assembly code and export them by marking _declspec in VC++ code, which produces an unmanaged DLL library which has the definitions for all methods. Finally, we will import these methods into a C#.NET code file to consume the in-built methods into the DLL as described in the following figure.
Inline ASM Code
Here, we are integrating the Assembly code routine which later will be called in the C#.NET program. This mechanism is performing some typical mathematical operations as well as calculating the CPU accuracy by writing logic in ASM code. This section is illustrating the process of Win32 VC++ DLL which has inline Assembly code. The following code segment is a header file which contains the exported routine definition as:
[c]
#include <windows.h>
extern "C" __declspec(dllexport) int __stdcall Addition(int a,int b);
extern "C" __declspec(dllexport) int __stdcall Substraction(int a,int b);
extern "C" __declspec(dllexport) int __stdcall Multiply(int a,int b);
extern "C" __declspec(dllexport) int __stdcall CPUSpeed();
extern "C" __declspec(dllexport) char* __stdcall CPUType();
extern "C" __declspec(dllexport) int __stdcall CPUFamily();
#endif
[/c]
Now let's come to one of the routines which adds two integers through Assembly code. This article doesn't intend to teach Assembly programming or there is no need to dive deeper into syntax. Just place simple opcodes which add two numbers and store results into an integer type variable through _asm block, which invokes the inline assembler and can appear in C or C++ statement anywhere as follows:
[c]
int Addition(int a, int b )
{
int result;
__asm
{
mov eax, a
add eax, b
mov result, eax
}
return result;
}
The method CPUSpeed is coded for calculating CPU actual speed through inline Assembly code instruction with VC++ as follows:
[c]
extern "C" __declspec(dllexport) int __stdcall CPUSpeed()
{
QueryPerformanceFrequency(&ulFreq);
QueryPerformanceCounter(&ulTicks);
// calculate one second interval
ulValue.QuadPart = ulTicks.QuadPart + ulFreq.QuadPart;
_asm {
rdtsc
mov ulEAX_EDX.LowPart, EAX
mov ulEAX_EDX.HighPart, EDX
ulStartCounter.QuadPart = ulEAX_EDX.QuadPart;
do {
QueryPerformanceCounter(&ulTicks);
_asm {
rdtsc
mov ulEAX_EDX.LowPart, EAX
mov ulEAX_EDX.HighPart, EDX
// calculate result
ulResult.QuadPart = ulEAX_EDX.QuadPart - ulStartCounter.QuadPart;
return (int)ulResult.QuadPart / 1000000;
}
Once we are done with the coding for all routines, we will debug the program. But before this, it is mandatory to perform some slight configuration. Since it is a DLL file, it doesn't have an entry point so we have to tell the compiler manually that it's a library project.
The second important setting is that all VC++ DLL a recompiled with the /clr option by default, which can cause a loader lock issue and deadlock situation to occur non-deterministically. The loader lock MDA typically identifies efforts to execute managed code on a thread that holds the Microsoft OS loader lock, and utilizing DLL before its initialization by OS is illegitimate and could lead to a deadlock situation. If this option is enabled, our DLL is compiled successfully, but the program fails because other Win32 functions also require the loader lock while programming inside the OS loader lock.
Hence we shall have to disable this option, and this can be done through solution properties. In the General section do these changes as:
Finally, we compile this project. AsmCodeLib.dll is created in the solution Debug folder, which will be referenced in the C#.NET GUI application in next section.
Note: the __asm block in a C/C++ program upsets optimization because the compiler doesn't try to optimize the __asm block itself and it also affects register variable storage.
Invoking ASM Code
Congratulations! Unmanaged DLL is finally done by inline assembly coding with VC++. This library contains a couple routine definitions to perform particular operations which will be going live through the C# Windows application. We can confirm with the methods definition details accompanied in the DLL through Dumpbin.exe utility as the following, which enumerate the routine and other metadata:
Perhaps you might be thinking that this is an unmanaged DLL and it would be referenced in the C# solution through Add Reference as we did earlier, but the important point to notice that it is neither a COM library nor a genuine VC++ unmanaged DLL. However, in this scenario we can't add its reference into the solution because it is not a managed Assembly, and Regsvr32.exe can't save us either because it is not a COM DLL. Hence, we shall proceed with another approach in which we just mention the full path of the DLL in the DllImport attribute where it is located as follows:
[c]
[DllImport(@”FULL-PATH AsmCodeLib.dll”)]
static extern int Addition(int x, int y);
[DllImport(@"FULL-PATH AsmCodeLib.dll")]
static extern int Substraction(int x, int y);
[DllImport(@"FULL-PATH AsmCodeLib.dll")]
static extern int Multiply(int x, int y);
[DllImport(@"FULL-PATH AsmCodeLib.dll")]
static extern string CPUType();
[DllImport(@"FULL-PATH AsmCodeLib.dll")]
static extern int CPUFamily();
[DllImport(@"FULL-PATH AsmCodeLib.dll")]
static extern int CPUModel();
After making the entry for each DLL method in the DllImport attribute in the C# file, invoke each corresponding routine related to the math operation and CPU by calling the method name directly and storing the return value on the designated Windows form text box as follows:
[c]
try
{
string CPU_Type = CPUType();
..
int CPU_Speed = CPUSpeed();
..
int res = Addition(Convert.ToInt32(txtX.Text), Convert.ToInt32(txtY.Text));
}
catch (DllNotFoundException err)
{
..
}
Finally, run the project and a Windows form will appear as follows, in which two sections will be highlighted, one for performing math operation and other is for retrieving CPU information. So, enter any two integer type values and perform any operation such as addition, division etc… and in the other box, we can obtain significant information about CPU such as speed, type and model by hitting the Retrieve button.
Final Note
Become a certified reverse engineer!
So we have discovered a special mechanism in which we can invoke the Assembly code into the C# source code file even if it is not supported by CLR. In this voyage, we have relied on Win32 VC++ DLL, which has inline definitions for Assembly code because VC++ supports or can execute ASM code, unlike .NET code, and VC++ unmanaged DLL can be invoked in C# code. So VC++ bridges the gap between Assembly language and C#.NET to achieve this functionality. Such implementation can happen through MASM ml.exe, VC++ cl.exe and C# csc.exe, in which each file is compiled and debugged in an isolated manner and later linked, but this process is considered to be more cumbersome than by opting through Visual Studio itself. Hopefully, it shall be explained in forthcoming articles soon.