I am having trouble with an access violation when using Microsoft Detours. I have made a dll which is loaded in to a thirdparty application. I am making a trampiline function using Detours to an undocumented function which Ida Pro displays as:
void __thiscall sub_6142E0(int a2, int a3)
My code looks like this:
#include "stdafx.h"
#include
#include
typedef void(__stdcall* pFunc)(int d1, int d2);
pFunc FuncToDetour = (pFunc)(0x6142EC);
void MyFunc(int d1, int d2)//Function does not mach call convension __thiscall. Possible problem?
{
printf("a2 %i, a1 %i);\n", d1, d2);
FuncToDetour(d1, d2);
}
void Init()
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)FuncToDetour, MyFunc);
DetourTransactionCommit();
}
The original assembly of the function I want to intercept is like this:
sub_6142E0 proc near
arg_0= dword ptr 8
arg_4= dword ptr 0Ch
push ebp
mov ebp, esp
mov eax, [ecx+8]
mov ecx, [ebp+arg_4]
mov edx, [ebp+arg_0]
The alterations Detours does results in this:
.text:006142EC jmp near ptr unk_F9C6802
...
d3d9.dll:0F9C6802 jmp near ptr unk_F9D5FE0 //jump to function in my dll
...
void MyFunc(int d1, int d2)//my function
{
printf("updateHealth(%i, %i);\n", d1, d2);
}
...
Stack[00004A8C]:0019FB4C sub ah, bh
Stack[00004A8C]:0019FB4E sbb [eax], eax //eax=0x491B -> access violation
Stack[00004A8C]:0019FB50 cmc
Stack[00004A8C]:0019FB51 inc si
Stack[00004A8C]:0019FB53 add [eax], dl
Stack[00004A8C]:0019FB55 add [eax], eax
Stack[00004A8C]:0019FB57 add [eax+80019FDh], cl
Stack[00004A8C]:0019FB5D add byte_19FC6415[eax], dh
Stack[00004A8C]:0019FB5D ; -------------------------------------------------
The error message I get is:
The instruction 0x19FB4E referenced memory at 0x491B. The memory could not be written -> 0000491B (exc.code c0000005, tid 19084)
I am going to try to answer my own questing.
This boils down to mismatch in calling convention between the two functions. The function I want to hook is using __thiscall and my function is using __cdecl(default calling convention). __thiscall is used as calling convention for member functions in a class where the "this pointer" is passed in the ecx register when calling a member function.
ecx in my case is written to when MyFunc is called in order to set up the stack frame(I think). The function I hook will then get a invalid this pointer when I call it from my trampoline function.
Check this link for some explanation and examples for how this can be done correctly.
Related
My question is very specific, i want force compiler to take the code of a funtion and copy it inside a another one, like inline or __forceinline keywords can do, but i want pass the function i want to copy in the other funtion, as an argument. Here is a simple example.
using pFunc = void(*)();
void func_1() { /*some code*/ }
void func_2(pFunc function) { /*some code*/ } //after compile i want this funtion takes no argument and copy the func_1 inside this.
int main()
{
func_2(func_1);
}
so with this example the compiler will pass the pointer of func_1 as argunent to func_2, as expected.
I tried add inline keyword for func_1 and also tried to pass the argument of func_2 as reference, but compiler didn't copied the func_1 inside func_2.
Any idea how can i do that?
I use the compiler of visual studio(msvc) with toolset 2017(v141).
My project platform is x64.
You can use a noinline template function to get the asm you want
So you want the compiler to do constant-propagation into a clone of void func_2(pFunc f){ f(); }? Like what GCC might do with __attribute__((noinline)) but not noclone?
For example,
using pFunc = void(*)();
int sink, sink2;
#ifdef _MSC_VER
#define NOINLINE _declspec(noinline)
#else
#define NOINLINE __attribute__((noinline)) // noclone and/or noipa
#endif
__attribute__((always_inline)) // without this, gcc chooses to clone .constprop.0 with just a jmp func_2
void func_1() { sink = 1; sink2 = 2; }
NOINLINE static void func_2(pFunc function) { function(); }
int main()
{
func_2(func_1);
}
produces, with GCC11.3 -O2 or higher, or -O1 -fipa-cp, on Godbolt. (Clang is similar):
# GCC11 -O3 with C++ name demangling
func_1():
mov DWORD PTR sink[rip], 1
mov DWORD PTR sink2[rip], 2
ret
func_2(void (*)()) [clone .constprop.0]:
mov DWORD PTR sink[rip], 1
mov DWORD PTR sink2[rip], 2
ret
main:
# note no arg passed, calling a special version of the function
# specialized for function = func_1
call func_2(void (*)()) [clone .constprop.0]
xor eax, eax
ret
Of course if we hadn't disabled inlining of func_2, main would just call func_1. Or inline that body of func_1 into main and not do any calls.
MSVC might not be willing to do that "optimization", instead preferring to just inline func_2 into main as call func_1.
If you want to force it to make clunky asm that duplicates func_1 unnecessarily, you could use a template to do the same thing as constprop, taking the function pointer as a template arg, so you can instantiate func_2<func1> as a stand-alone non-inline function if you really want. (Perhaps with _declspec(noinline)).
Your func_2 can accept func_1 as an unused argument if you want.
using pFunc = void(*)();
int sink, sink2;
#ifdef _MSC_VER
#define NOINLINE _declspec(noinline)
#define ALWAYS_INLINE /* */
#else
#define NOINLINE __attribute__((noinline)) // not noclone or noipa, we *want* those to happen
#define ALWAYS_INLINE __attribute__((always_inline))
#endif
//ALWAYS_INLINE // Seems not needed for this case, with the template version
void func_1() { sink = 1; sink2 = 2; }
template <pFunc f>
NOINLINE void func_2() { f(); }
int main()
{
func_2<func_1>();
}
Compiles as desired with MSVC -O2 (Godbolt), and GCC/clang
int sink DD 01H DUP (?) ; sink
int sink2 DD 01H DUP (?) ; sink2
void func_2<&void func_1(void)>(void) PROC ; func_2<&func_1>, COMDAT
mov DWORD PTR int sink, 1 ; sink
mov DWORD PTR int sink2, 2 ; sink2
ret 0
void func_2<&void func_1(void)>(void) ENDP ; func_2<&func_1>
void func_1(void) PROC ; func_1, COMDAT
mov DWORD PTR int sink, 1 ; sink
mov DWORD PTR int sink2, 2 ; sink2
ret 0
void func_1(void) ENDP ; func_1
main PROC ; COMDAT
$LN4:
sub rsp, 40 ; 00000028H
call void func_2<&void func_1(void)>(void) ; func_2<&func_1>
xor eax, eax
add rsp, 40 ; 00000028H
ret 0
main ENDP
Note the duplicated bodies of func_1 and func_2.
You should check (with a disassembler) that the linker doesn't do identical code folding and just attach the both symbol names to one block of machine code.
I don't think this looks like much of an obfuscation technique; IDK why having a 2nd copy of a function with identical machine code would be a problem to reverse engineer. I guess it would maybe create more overall work, and people wouldn't notice that two calls to different functions are actually doing the same thing.
I mostly answered as an exercise in making a compiler spit out the asm I wanted it to, whether or not that has value to anyone else.
Obviously it only works for compile-time-constant function pointers; commenters have been discussing self-modifying code and scripting languages. If you wanted this for non-const function pointer args to func_1, you're completely out of luck in a language like C++ that's designed for strictly ahead-of-time compilation.
Like the title says, I want to trace ALL functions calls in my application (from inside).
I tried using "_penter" but I get either a recursion limit reached error or an access violation when I try to prevent the recursion.
Is there any way to achieve this ?
Update
What I tried:
extern "C"
{
void __declspec(naked) _cdecl _penter()
{
_asm {
push eax
push ecx
push edx
mov ecx, [esp + 0Ch]
push ecx
mov ecx, offset Context::Instance
call Context::addFrame
pop edx
pop ecx
pop eax
ret
}
}
class Context
{
public:
__forceinline void addFrame(const void* addr) throw() {}
static thread_local Context Instance;
};
sadly this still gives a stack overflow due to recursion
Your approach is correct, /Gh and /GH compiler switches + _penter and _pexit functions is the way to go.
I think there’re errors in your implementation of these functions. That’s very low-level stuff, for 32 bit builds you have to use __declspec(naked), and for 64 bit builds you have to use assembler. Both are quite tricky to implement correctly.
Take a look at this repository for an example how to do it right:
https://github.com/tyoma/micro-profiler Specifically, to this source file: https://github.com/tyoma/micro-profiler/blob/master/micro-profiler/collector/hooks.asm As you see, they decided to use assembler for both platforms, and from that they call some C++ function to record call information. Also note how in C++ collector implementation they use __forceinline to avoid recursion.
but I get either a recursion limit reached error
this can be if inside Context::addFrame implementation compiler also insert call _penter which recursive call Context::addFrame.
but how __forceinline you can ask ? nothing. c/c++ compiler to insert a copy of the function body into each place the function is called from code which is generated by this compiler. c/c++ compiler can not insert a copy of the function body into code, which he not compile itself. so when we call function marked as __forceinline from assembler code - function will be called in usual way but not expanded in place. so your __forceinline simply have no effect and sense
you need implement Context::addFrame (and all functions which it call) in separate c++ file (let be context.cpp) compiled without /Gh option.
you can set /Gh for all files in project, except context.cpp
if exist too many cpp files in project - you can set /Gh for project, but how then remove it for single file context.cpp ? exist one original way - you can copy <cmdline> for this file and that set custom build tool for it
Command Line- CL.exe <cmdline> $(InputFileName) (not forget remove /Gh) and Outputs - $(IntDir)\$(InputName).obj. original by perfect work.
so in context.cpp you can have next code:
class Context
{
public:
void __fastcall addFrame(const void* addr);
int _n;
static thread_local Context Instance;
};
thread_local Context Context::Instance;
void __fastcall Context::addFrame(const void* addr)
{
#pragma message(__FUNCDNAME__)
DbgPrint("%p>%u\n", addr, _n++);
}
if Context::addFrame call some another internal function (explicit or implicit) - put it also in this file, which compile without /Gh
the _penter better implement in separate asm file, but not as inline asm (this not supported in x64 anyway)
so for x86 you can create code32.asm ( ml /c /Cp $(InputFileName) -> $(InputName).obj)
.686p
.MODEL flat
extern ?addFrame#Context##QAIXPBX#Z:proc
extern ?Instance#Context##2V12#A:byte
_TEXT segment 'CODE'
__penter proc
push edx
push ecx
mov edx,[esp+8]
lea ecx,?Instance#Context##2V12#A
call ?addFrame#Context##QAIXPBX#Z
pop ecx
pop edx
ret
__penter endp
_TEXT ends
end
note - you need save only rcx and rdx (if you use __fastcall , except context.cpp, functions)
for x64 - create code64.asm ( ml64 /c /Cp $(InputFileName) -> $(InputName).obj)
extern ?addFrame#Context##QEAAXPEBX#Z:proc
extern ?Instance#Context##2V12#A:byte
_TEXT segment 'CODE'
_penter proc
mov [rsp+8],rcx
mov [rsp+16],rdx
mov [rsp+24],r8
mov [rsp+32],r9
mov rdx,[rsp]
sub rsp,28h
lea rcx,?Instance#Context##2V12#A
call ?addFrame#Context##QEAAXPEBX#Z
add rsp,28h
mov r9,[rsp+32]
mov r8,[rsp+24]
mov rdx,[rsp+16]
mov rcx,[rsp+8]
ret
_penter endp
_TEXT ENDS
end
Here is what I use
Configuration Properties > C/C++ > Command Line
Add compiler option to Additional Options box
Like so
Add flag /Gh for _penter hook
Add flag /GH for _pexit hook
Code I use for tracing / logging
#include <intrin.h>
extern "C" void __declspec(naked) __cdecl _penter(void) {
__asm {
push ebp; // standard prolog
mov ebp, esp;
sub esp, __LOCAL_SIZE
pushad; // save registers
}
// _ReturnAddress always returns the address directly after the call, but that is not the start of the function!
PBYTE addr;
addr = (PBYTE)_ReturnAddress() - 5;
SYMBOL_INFO* mysymbol;
HANDLE process;
process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
mysymbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
mysymbol->MaxNameLen = 255;
mysymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
SymFromAddr(process, (DWORD64)((void*)addr), 0, mysymbol);
myprintf("Entered Function: %s [0x%X]\n", mysymbol->Name, addr);
_asm {
popad; // restore regs
mov esp, ebp; // standard epilog
pop ebp;
ret;
}
}
extern "C" void __declspec(naked) __cdecl _pexit(void) {
__asm {
push ebp; // standard prolog
mov ebp, esp;
sub esp, __LOCAL_SIZE
pushad; // save registers
}
// _ReturnAddress always returns the address directly after the call, but that is not the start of the function!
PBYTE addr;
addr = (PBYTE)_ReturnAddress() - 5;
SYMBOL_INFO* mysymbol;
HANDLE process;
process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
mysymbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
mysymbol->MaxNameLen = 255;
mysymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
SymFromAddr(process, (DWORD64)((void*)addr), 0, mysymbol);
myprintf("Exit Function: %s [0x%X]\n", mysymbol->Name, addr);
_asm {
popad; // restore regs
mov esp, ebp; // standard epilog
pop ebp;
ret;
}
}
I am working on an application with Visual Studio 2015 on Windows 7. The application has a C# frontend, a C++ CLR wrapper and C++ native code.
My application crashes with an access violation while initializing a static variable at function scope with C++ native code. But only on Windows Server 2003 Enterprise SP2 and not on Windows 7 or Windows Server 2012. I know Windows Server 2003 is out of support, but I have to target that platform for a few additional months and Visual Studio 2015 provides a platform toolset to target it.
I created a small reproducible example which you find at the end.
The crash only happens with all three parts involved (C#, C++ CLR, C++). If I remove any, the crash is gone.
The crash only happens with a custom constructor defined. If I remove the constructor, the crash is gone.
I am no assembly expert, but for me it looks like the crash is caused by the code which checks if the static initialization is required. The constructor is not even called.
My question is: Why does it crash on Windows Server 2003? Am I missing some important project setting?
The error message
Unhandled exception at 0x1000167E (Native.dll) in Managed.exe.dmp: 0xC0000005: Access violation reading location 0x00000000.
Visual C# Console Application "Managed.exe"
Program.cs
// Target framework: .NET Framework 4
// Platform target: x86
using System;
namespace Managed
{
class Program
{
static void Main(string[] args)
{
Console.Write("Press enter to start test...");
Console.ReadLine();
Native.Wrapper wrapper = new Native.Wrapper();
Console.WriteLine("Test was successful");
Console.Write("Press enter to exit...");
Console.ReadLine();
}
}
}
Visual C++ CLR Class Library "Native.dll"
Wrapper.hpp
#pragma once
namespace Native
{
public ref class Wrapper
{
public:
Wrapper();
}; // public ref class Wrapper
} // namespace Native
Wrapper.cpp
// Platform Toolset: Visual Studio 2015 - Windows XP (v140_xp)
// Common Language Runtime Support: Common Language Runtime Support (/clr)
// .NET Target Framework Version: v4.0
// Warning Level: Level4
// Treat Warnings As Errors: Yes (/WX)
// Precompiled Header: Not Using Precompiled Headers
// SubSystem: Console (/SUBSYSTEM:CONSOLE)
// Optimization: Disabled (/Od)
#pragma once
#include "Wrapper.hpp"
#include "Caller.hpp"
namespace Native
{
Wrapper::Wrapper()
{
Caller* caller = new Caller();
delete caller;
}
} // namespace Native
Caller.hpp
#pragma once
namespace Native
{
class Caller
{
public:
Caller();
}; // class Caller
} // namespace Native
Caller.cpp
// Platform Toolset: Visual Studio 2015 - Windows XP (v140_xp)
// Common Language Runtime Support: No Common Language RunTime Support
// Warning Level: Level4
// Treat Warnings As Errors: Yes (/WX)
// Precompiled Header: Not Using Precompiled Headers
// SubSystem: Console (/SUBSYSTEM:CONSOLE)
// Optimization: Disabled (/Od)
#include "Caller.hpp"
#include "Singleton.hpp"
namespace Native
{
Caller::Caller()
{
Singleton::GetInstance()->DoSomething();
}
} // namespace Native
Singleton.hpp
#pragma once
#include <iostream>
namespace Native
{
class Singleton
{
public:
Singleton() // !!! remove constructor to prevent crash !!!
{ }
static Singleton* GetInstance()
{
static Singleton Instance; // !!! crashes here !!!
return &Instance;
}
void DoSomething()
{
std::wcout << L"Doing something...\n";
}
}; // class Singleton
} // namespace Native
The disassembly
static Singleton* GetInstance()
{
10001650 push ebp
10001651 mov ebp,esp
10001653 push 0FFFFFFFFh
10001655 push 10006A8Ch
1000165A mov eax,dword ptr fs:[00000000h]
10001660 push eax
10001661 mov eax,dword ptr ds:[1001B334h]
10001666 xor eax,ebp
10001668 push eax
10001669 lea eax,[ebp-0Ch]
1000166C mov dword ptr fs:[00000000h],eax
static Singleton Instance;
10001672 mov eax,dword ptr ds:[1001B5D0h]
10001677 mov ecx,dword ptr fs:[2Ch]
1000167E mov edx,dword ptr [ecx+eax*4] // !!! access violation here !!!
10001681 mov eax,dword ptr ds:[1001B5A4h]
10001686 cmp eax,dword ptr [edx+4]
1000168C jle Native::Singleton::GetInstance+79h (100016C9h)
The registers
EAX = 00000000 EBX = 00000000 ECX = 00000000 EDX = 006A0003 ESI = 001647C8
EDI = 0012F3BC EIP = 1000167E ESP = 0012F394 EBP = 0012F3A4 EFL = 00010282
Edit 1
While debugging locally where the crash does not happen, a few more symbols are visible with the assembly:
static Singleton* GetInstance()
{
0FBD1650 push ebp
0FBD1651 mov ebp,esp
0FBD1653 push 0FFFFFFFFh
0FBD1655 push offset __ehhandler$?GetInstance#Singleton#Native##SAPAV12#XZ (0FBD86BCh)
0FBD165A mov eax,dword ptr fs:[00000000h]
0FBD1660 push eax
0FBD1661 mov eax,dword ptr [___security_cookie (0FBF03CCh)]
0FBD1666 xor eax,ebp
0FBD1668 push eax
0FBD1669 lea eax,[ebp-0Ch]
0FBD166C mov dword ptr fs:[00000000h],eax
static Singleton Instance;
0FBD1672 mov eax,dword ptr [__tls_index (0FBF0668h)]
0FBD1677 mov ecx,dword ptr fs:[2Ch]
0FBD167E mov edx,dword ptr [ecx+eax*4]
0FBD1681 mov eax,dword ptr [TSS0<`template-parameter-2',Native::Singleton::tInstance,Native::Singleton * * const volatile,void,int, ?? &> (0FBF063Ch)]
0FBD1686 cmp eax,dword ptr [edx+4]
0FBD168C jle Native::Singleton::GetInstance+79h (0FBD16C9h)
The symbol __tls_index seems to belong to some thread local store (guessed from the name). This matches with Magic statics which uses thread local store as a performance optimization in the reference implementation. When crashing, the thread local store returns 0.
Could this be a bug with the runtime environment on Windows Server 2003 which manages and initializes the thread local store?
Edit 2
Reported as bug through Microsoft Connect: Bug report
This is the answer of Microsoft as posted to my bug report at Microsoft Connect:
Windows Server 2003 and Windows XP have problems with dynamically loading a DLL (via LoadLibrary) that uses thread-local storage, which is what thread-safe statics use internally to provide efficient execution when the static local has already been initialized. As these systems are out of support, it is extremely unlikely for a patch to be created for those systems to add this support as is present in Vista and newer OSes, and we are reluctant to penalize the performance on in-support OSes to provide this functionality to the old out-of-support ones.
To work around the issue you can use /Zc:threadSafeInit- to disable the thread-safe initialization code and this will avoid the thread-local variable. Of course by doing so the initialization code reverts back to the VS2013 mode and is not thread-safe, so this option is only viable if you don't rely on the thread-safety of local statics.
your constructor is NOT removed since its public.
move it to the private section of your class declaration.
I am using Visual C++ 2010, and MASM as my x64-Assembler.
This is my C++ code:
// include directive
#include "stdafx.h"
// functions
extern "C" int Asm();
extern "C" int (convention) sum(int x, int y) { return x + y; }
// main function
int main()
{
// print asm
printf("Asm returned %d.\n", Asm());
// get char, return
_getch();
return EXIT_SUCCESS;
}
And my assembly code:
; external functions
extern sum : proc
; code segment
.code
Asm proc
; create shadow space
sub rsp, 20o
; setup parameters
mov ecx, 10
mov edx, 15
; call
call sum
; clean-up shadow space
add rsp, 20o
; return
ret
Asm endp
end
The reason I am doing this is so I can learn the different calling conventions.
I would make sum's calling convention stdcall, and modify the asm code so it would call sum the "stdcall" way. Once I got that working, I would make it, say, fastcall, and then call it in asm the "fastcall" way.
But look at my assembly code right now. When I use that code, no matter if sum is stdcall, fastcall or cdecl, it will compile, execute fine, and print 25 as my sum.
My question: How, and why can __cdecl, __stdcall and __fastcall all be called the exact same way?
The problem is that you're compiling for x64 targets. From MSDN
Given the expanded register set, x64 just uses the __fastcall calling
convention and a RISC-based exception-handling model. The __fastcall
model uses registers for the first four arguments and the stack frame
to pass the other parameters.
Switch over to compiling for x86 targets, and you should be able to see the various calling conventions in action.
As far as i know x64 only uses the __fastcall convention. __cdecl and stdcall will just be compiled as __fastcall.
I am getting the following error when trying to add a static variable to my struct:
Undefined Symbole s2::aa in module
file_name.cpp
s2 is the name of the structure and aa is the static variable.
The compiler I am using is Turbo C++ 3.0.
How do I fix this error?
I think you've probably forgotten to define the storage for the static variable:
int C::v = 0;
Static variable isn't allowed in structs in C because C requires the whole stucture elements to be placed together. To get an element value from a structure you count by the offset of the element from the beginning address of the structure.
However as far as I know you can have a static member in a C++ structure. Are you getting a specific error (which compiler?)
Why do you say this? Under g++ 4.1.2, this compiles:
#include <iostream>
struct Test
{
static int test; // declare (usually in header file)
};
int Test::test = 8; // define (usually in source file)
int
main()
{
std::cout << Test::test << std::endl;
return 0;
}
Static variables are allowed in C++ structs (as you say, they are just classes with a different default access specifier).
Static variables are not allowed in C structs, however.
This works...
typedef struct _X
{
static int x; // declare (usually in header file)
} X;
int X::x = 1; // define (usually in source file)
void _tmain(int argc, _TCHAR* argv[])
{
printf("%d", X::x);
}
effectively, you'll get a public symbol with the name [?test#Test##2HA] placed in a separate (globally accessible) segment/section in memory...
struct Test
{
static int test; // declare (usually in header file)
};
int Test::test = 8; // define (usually in source file)
int main()
{
int x = Test::test++;
return 0;
}
will translate in assembly to:
; Listing generated by Microsoft (R) Optimizing Compiler Version 19.16.27027.1
TITLE C:\WORK\C\Cpp\test.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC ?test#Test##2HA ; Test::test
_DATA SEGMENT
?test#Test##2HA DD 08H ; Test::test
_DATA ENDS
PUBLIC _main
; Function compile flags: /Odtp
_TEXT SEGMENT
_x$ = -4 ; size = 4
_main PROC
; File c:\work\c\cpp\test.cpp
; Line 9
push ebp
mov ebp, esp
push ecx
; Line 10
mov eax, DWORD PTR ?test#Test##2HA ; Test::test
mov DWORD PTR _x$[ebp], eax
mov ecx, DWORD PTR ?test#Test##2HA ; Test::test
add ecx, 1
mov DWORD PTR ?test#Test##2HA, ecx ; Test::test
; Line 11
xor eax, eax
; Line 12
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
Compile with:
cl /c test.cpp /TP /Fatest.asm /link /NODEFAULTLIB /entry:main
In C++ structure,you can use static variables same as class.
But you can't use static variables in C stuctures.
Because in c, we can't access static variable with stucture name. In c++ we can access static member variable with class name,like below.
ClassName::staticVariableName
'C' stucture don't provide such facility but c++ stucture does.