Hi,
How to call a C++ method from C - https://stackoverflow.com/questions/14815274/how-to-call-a-c-method-from-c
Tutorial: HowTo integrate a C++ library/class into a C programm - https://www.teddy.ch/c++_library_in_c/
I want to implement a universal translator for auto-generation of an interface, that describes a C++ class. The main goal is to be able to link C++ classes in an assembly program. But from the very beginning I have many problems. I have compiled object modules in visual studio, that do not exceed the size of 1 kb. Without the initial creation of a wrapper, I took the desired main function and formalized it as a main procedure. Then I tried to link all this using the linker from the visual studio, to my surprise, the 3 kb code increased itself to 400 kb, for the reason that there are two functions in the code responsible for creating an object and deleting it. Only two functions contributed so much shit to the code :dazzled: :sad:
It seems that there is no other way out. It is necessary to change the libraries that support object-oriented code. Maybe then something will come out. So my idea failed before it even started :greensml:
https://www.youtube.com/watch?v=iItfIP4PabU :tongue:
Part 1: Using a simple class - http://masm32.com/board/index.php?topic=9317.msg102309#msg102309 (http://masm32.com/board/index.php?topic=9317.msg102309#msg102309)
Part 2: Using a class template - http://masm32.com/board/index.php?topic=9317.msg102551#msg102551 (http://masm32.com/board/index.php?topic=9317.msg102551#msg102551)
Part 3: Advanced techniques for addressing functions in C ++ from an assembler program
Part 4: Using dynamic variables and class initialization from assembler
Part 5: Defining a class in another class
Part 6: I haven't thought about that yet :tongue:
All this looks unnecessarily complicated. Any chance to provide a tiny C++ "class" for testing how "this" etc are passed to C++?
Quote from: jj2007 on April 23, 2021, 05:04:02 AM
All this looks unnecessarily complicated. Any chance to provide a tiny C++ "class" for testing how "this" etc are passed to C++?
Hi jj2007,
in the example, there is already an object module and source, CPP_All_in_one - here everything is collected in one object file without a wrapper. But the goal was to make executables small in size without rewriting everything.
Oops, doesn't work...
C:\Windows\ccut1p6K.o:Tmp.cpp:(.text.startup+0x16): undefined reference to `operator new(unsigned int)'
C:\Windows\ccut1p6K.o:Tmp.cpp:(.text.startup+0x24): undefined reference to `operator delete(void*)'
collect2.exe: error: ld returned 1 exit status
Quote from: jj2007 on April 23, 2021, 05:04:02 AM
All this looks unnecessarily complicated. Any chance to provide a tiny C++ "class" for testing how "this" etc are passed to C++?
That hidden
this comes in ecx / rcx ?
?int_set@MyClass@@QEAAXH@Z:
00000000 89542410 mov dword ptr [rsp+10h], edx
00000004 48894C2408 mov qword ptr [rsp+8h], rcx
00000009 488B442408 mov rax, qword ptr [rsp+8h]
0000000E 8B4C2410 mov ecx, dword ptr [rsp+10h]
00000012 8908 mov dword ptr [rax], ecx
00000014 C3 ret
?int_get@MyClass@@QEAAHXZ:
00000020 48894C2408 mov qword ptr [rsp+8h], rcx
00000025 488B442408 mov rax, qword ptr [rsp+8h]
0000002A 8B00 mov eax, dword ptr [rax]
0000002C C3 ret
Quote from: TimoVJL on April 23, 2021, 04:39:52 PMThat hidden this comes in ecx / rcx ?
Yes indeed, at least in 64-bit code. Unfortunately, I haven't been able to make GetProcAddress work. Example:
mov ebx, rv(LoadLibrary, "ureg")
invoke GetProcAddress, ebx, Chr$("?SaveKeyToFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z")
The string is correct, and it
should work (according to some sites), but it always returns zero. Plus, all that stuff is badly documented. Here is the output of podump.exe:
Dump of C:\Windows\System32\ureg.dll
File type: DLL
Exported symbols for UREG.dll
0 characteristics
4A5BA291 time date stamp (Mon Jul 13 23:09:37 2009)
0.00 version
1 ordinal base
19 number of functions
19 number of names
ordinal hint address name
1 0 000007FF701119A0 ??0REGISTRY@@QEAA@XZ
2 1 000007FF701115A0 ??0REGISTRY_KEY_INFO@@QEAA@XZ
3 2 000007FF70111348 ??0REGISTRY_VALUE_ENTRY@@QEAA@XZ
4 3 000007FF70111A40 ??1REGISTRY@@UEAA@XZ
5 4 000007FF70111E38 ?AddValueEntry@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVREGISTRY_VALUE_ENTRY@@EPEAK@Z
6 5 000007FF701123D8 ?CreateKey@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@1PEAKE@Z
7 6 000007FF7011266C ?DeleteKey@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z
8 7 000007FF7011272C ?DeleteValueEntry@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z
9 8 000007FF701128A4 ?DoesKeyExist@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@1PEAK@Z
A 9 000007FF70112900 ?DoesValueExist@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@11PEAK@Z
B A 000007FF70114498 ?EnableRootNotification@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAXKE@Z
C B 000007FF70111AC4 ?Initialize@REGISTRY@@QEAAEPEBVWSTRING@@PEAK@Z
D C 000007FF70111740 ?Initialize@REGISTRY_KEY_INFO@@QEAAEPEBVWSTRING@@0K0PEAU_SECURITY_ATTRIBUTES@@@Z
E D 000007FF7011146C ?Initialize@REGISTRY_VALUE_ENTRY@@QEAAEPEBVWSTRING@@KW4_REG_TYPE@@PEBEK@Z
F E 000007FF70114DC4 ?IsAccessAllowed@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@KPEAK@Z
10 F 000007FF701148D0 ?LoadHive@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z
11 10 000007FF70112A38 ?QueryKeyInfo@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@1PEAVREGISTRY_KEY_INFO@@PEAK@Z
12 11 000007FF70112E88 ?QueryKeySecurity@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVREGISTRY_KEY_INFO@@KPEAPEAXPEAK@Z
13 12 000007FF70113018 ?QuerySubKeysInfo@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@1PEAVARRAY@@PEAK@Z
14 13 000007FF7011350C ?QueryValues@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEBVWSTRING@@1PEAVARRAY@@PEAK@Z
15 14 000007FF70114C84 ?RestoreKeyFromFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@EPEAK@Z
16 15 000007FF70114B60 ?SaveKeyToFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z
17 16 000007FF70113B88 ?SetKeySecurity@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@KPEAXPEAKE@Z
18 17 000007FF70114A50 ?UnLoadHive@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEAK@Z
19 18 000007FF70113CC4 ?UpdateKeyInfo@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEAK@Z
Hi LiaoMi,
The main problem is the complicated internals of C++ and the libraries associated with the compiler suite. Disassembling your object module with Agner Fog's objconv :
extern ??3@YAXPEAX_K@Z: near
extern ??2@YAPEAX_K@Z: near
main LABEL NEAR
mov qword ptr [rsp+10H], rdx
mov dword ptr [rsp+8H], ecx
push rdi
sub rsp, 64
mov ecx, 4
call ??2@YAPEAX_K@Z
.
.
.
.
mov edx, 4
mov rcx, qword ptr [rsp+38H]
call ??3@YAXPEAX_K@Z
xor eax, eax
add rsp, 64
pop rdi
ret
??3@YAXPEAX_K@Z and ??2@YAPEAX_K@Z are the two externals, the references to new and delete in your code :
#include "MyClass.h"
using namespace std;
int main(int argc, char* argv[]) {
MyClass *c = new MyClass();
c->int_set(3);
c->int_get();
delete c;
}
To get a smaller executable, the trick is to reduce the dependencies like new \ delete as much as possible. In our case, we are depending on the VC++ libraries LIBCMT.lib and OLDNAMES.lib This is why we get a bloated C++ executable for the obvious reasons.
To avoid the problematic C++ dependencies, I tried to replace the new & delete couple with pointers in my example :
#include <stdio.h>
class volume {
public:
int a;
int b;
int c;
int d;
int calc(void);
void getvol(int);
};
int volume::calc(void)
{
return a*b*c;
}
void volume::getvol(int v)
{
printf("The volume is %u cm3\n",v);
}
int main()
{
volume Box;
volume *PtrBox;
PtrBox = &Box;
PtrBox->a = 2;
PtrBox->b = 4;
PtrBox->c = 6;
PtrBox->d = PtrBox->calc();
PtrBox->getvol(PtrBox->d);
return 0;
}
I am using VC++ Python to compile the code. It's and old compiler but very good for practical programming. Plus, you can make a portable package from the compiler components.
C:\Tools\VCForPython27>cl ClassBox.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.30729.01 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
ClassBox.cpp
Microsoft (R) Incremental Linker Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
/out:ClassBox.exe
ClassBox.obj
The executable's size is 51712 bytes.
Building the same project with a small C run-time library :
cl -c -Zl ClassSample.cpp
\masm32\bin64\link /SUBSYSTEM:CONSOLE /LARGEADDRESSAWARE /LIBPATH:\masm32\lib64 ClassSample.obj kernel32.lib msvcrt.lib crt0\crt0.lib
The result is 3072 bytes.
My method may not be suitable for complicated \ sophisticated C++ projects but it could be interesting to use it for small examples.
Quote from: jj2007 on April 23, 2021, 07:06:11 AM
Oops, doesn't work...
C:\Windows\ccut1p6K.o:Tmp.cpp:(.text.startup+0x16): undefined reference to `operator new(unsigned int)'
C:\Windows\ccut1p6K.o:Tmp.cpp:(.text.startup+0x24): undefined reference to `operator delete(void*)'
collect2.exe: error: ld returned 1 exit status
Hi jj2007,
that's right, these are two functions that create and delete an object, they are in the visual studio libraries. These functions pull the entire 400kb of bloating code with them.
https://ghidra-sre.org/ghidra_9.2.3_PUBLIC_20210325.zip
https://download.java.net/java/early_access/jdk17/18/GPL/openjdk-17-ea+18_windows-x64_bin.zip
Windows: Extract the JDK distribution (.zip file) to your desired location and add the JDK's bin directory to your PATH:
Extract the JDK:
Right-click on the zip file and click Extract All...
Click Extract
Open Environment Variables window:
Windows 10: Right-click on Windows start button, and click System
Windows 7: Click Windows start button, right-click on Computer, and click Properties
Click
Advanced system settingsClick Environment variables...
Add the JDK bin directory
to the PATH variable:
Under System variables, highlight Path and click Edit...
At the end of the the Variable value field, add a semicolon followed by <path of extracted JDK dir>\bin
Click OK
Click OK
Click OK
Restart any open Command Prompt windows for changes to take effect
https://i.ibb.co/7Y8WGMz/2021-04-23-12-01-49-Code-Browser-Class-My-Wrapper-obj.png (https://i.ibb.co/7Y8WGMz/2021-04-23-12-01-49-Code-Browser-Class-My-Wrapper-obj.png)
Quote from: jj2007 on April 23, 2021, 06:07:38 PM
Yes indeed, at least in 64-bit code. Unfortunately, I haven't been able to make GetProcAddress work. Example:
mov ebx, rv(LoadLibrary, "ureg")
invoke GetProcAddress, ebx, Chr$("?SaveKeyToFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z")
Hi Jochen,
It works. You need to try it with 64-bit code :
include \masm32\include64\masm64rt.inc
.data
func db '?SaveKeyToFile@REGISTRY@@QEAAEW4_PREDEFINED_KEY@@PEAVREGISTRY_KEY_INFO@@PEBVWSTRING@@PEAK@Z',0
.code
start PROC
LOCAL hDLL:QWORD
invoke LoadLibrary,chr$("ureg.dll")
mov hDLL,rax
invoke GetProcAddress,rax,ADDR func
invoke vc_printf,\
chr$("The address of the function = %llX"),\
rax
invoke FreeLibrary,hDLL
invoke ExitProcess,0
start ENDP
END
Quote from: Vortex on April 23, 2021, 09:21:27 PM
It works. You need to try it with 64-bit code
Hi Erol,
You are right! So by accident the 32-bit version of the DLL does not have that proc :cool:
So, now that we can access the proc with its address, can we identify "
this", put it into rcx, and then use the proc?
We would need a crispy example of a C++ function that is
- available in System32\*.dll,
- does something useful and
- is documented :badgrin:
Hi Vortex,
thanks for the great example :thup: :thup: :thup:
From the technical point of view, we need to evaluate the library itself, how big it is in terms of dependencies, this is the main problem, even if I implement the function wrapper.
If we apply this logic to this example - Microsoft Cognitive Toolkit (CNTK) with ASMC - http://masm32.com/board/index.php?topic=9260.0, in this case there is a difference of 300 kb,
; 221 : ::operator delete(_Ptr, _Bytes);
call ??3@YAXPEAX_K@Z ; operator delete
; 221 : ::operator delete(_Ptr, _Bytes);
call ??3@YAXPEAX_K@Z ; operator delete
; File C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29910\include\xhash
; 322 : _Mypair._Myval2._Myfirst = nullptr;
xor eax, eax
mov QWORD PTR [rbx], rax
; 323 : _Mypair._Myval2._Mylast = nullptr;
mov QWORD PTR [rbx+8], rax
; 324 : _Mypair._Myval2._Myend = nullptr;
mov QWORD PTR [rbx+16], rax
that based on all this, I concluded that it is impossible to do this. With a wrapper, the code can be called from assembly language programs, but its size will be the same as before :tongue:
Hi LiaoMi,
You are welcome. Here is another attempt :
#include <stdio.h>
class formula {
public:
int calc(int, int, int, int, int);
void GetResult(int);
};
int formula::calc(int a, int b, int c, int d, int e)
{
return a * b * c * d * e;
}
void formula::GetResult(int t)
{
printf("The result is %u\n",t);
}
QuoteSo, now that we can access the proc with its address, can we identify "this", put it into rcx, and then use the proc?
Hi Jochen,
Identifiying this as rcx and calling the functions from Masm :
include \masm32\include64\masm64rt.inc
EXTERN ?calc@formula@@QEAAHHHHHH@Z:PROC
calc TEXTEQU <?calc@formula@@QEAAHHHHHH@Z>
EXTERN ?GetResult@formula@@QEAAXH@Z:PROC
GetResult TEXTEQU <?GetResult@formula@@QEAAXH@Z>
dummy TEXTEQU <0>
.code
start PROC
; rcx -> this
invoke calc,dummy,2,4,6,8,10
invoke GetResult,dummy,rax
invoke ExitProcess,0
start ENDP
END
Of course, my example is very simple. C++ offers constructors, destructors, friend functions, overloading etc. All those features needs to be studied to see how they should be combined with asm.
Here is the UASM version :
Option Stackbase:rsp
Option Frame:auto
Option Win64:11
?calc@formula@@QEAAHHHHHH@Z PROTO :QWORD,:QWORD,:QWORD,:QWORD,:QWORD,:QWORD
calc TEXTEQU <?calc@formula@@QEAAHHHHHH@Z>
?GetResult@formula@@QEAAXH@Z PROTO :QWORD,:QWORD
GetResult TEXTEQU <?GetResult@formula@@QEAAXH@Z>
ExitProcess PROTO :QWORD
dummy TEXTEQU <0>
.code
start PROC
; rcx -> this
invoke calc,dummy,2,4,6,8,10
invoke GetResult,dummy,rax
invoke ExitProcess,0
start ENDP
END
Undecorating the C++ symbols with objconv :
\Tools\objconv\objconv.exe -nr:?calc@formula@@QEAAHHHHHH@Z:calc Class.obj Class2.obj
\Tools\objconv\objconv.exe -nr:?GetResult@formula@@QEAAXH@Z:GetResult Class2.obj Class.obj
Option Stackbase:rsp
Option Frame:auto
Option Win64:11
calc PROTO :QWORD,:QWORD,:QWORD,:QWORD,:QWORD,:QWORD
GetResult PROTO :QWORD,:QWORD
ExitProcess PROTO :QWORD
dummy TEXTEQU <0>
.code
start PROC
; rcx -> this
invoke calc,dummy,2,4,6,8,10
invoke GetResult,dummy,rax
invoke ExitProcess,0
start ENDP
END
Hi Vortex and jj2007,
thanks for Your tips and help, I've finally managed to build a fully working version as planned in the first post. In the first steps, I wanted to replace all the functions that generate program bloat, so I parsed the
libcmt.lib library, I took out object files from there and then pulled out the missing functions for integration in my code. It was planned to make a kind of stub function. Since I can make my own libraries that do not have any checks from the visual studio. After some experimentation, it turned out that both of these functions are exported in the msvcrt.lib library. The problem now is that each of these libraries that I have contain completely different functions with different types of checks, some of these libraries do not have exception checks and things like that.
From the library
libcmt.lib library
; void *__fastcall operator new(size_t Size)
;public ??2@YAPEAX_K@Z
??2@YAPEAX_K@Z proc near
;??3@YAXPEAX_K@Z proc near ; DATA XREF: .pdata:$pdata$??2@YAPEAX_K@Z\u2193o
push rbx ; $LN21
sub rsp, 20h
mov rbx, rcx
jmp short loc_1A
; ---------------------------------------------------------------------------
loc_B: ; CODE XREF: operator new(unsigned __int64)+22\u2193j
mov rcx, rbx ; Size
call _callnewh
test eax, eax
jz short loc_2A
mov rcx, rbx ; Size
loc_1A: ; CODE XREF: operator new(unsigned __int64)+9\u2191j
call malloc
test rax, rax
jz short loc_B
add rsp, 20h
pop rbx
retn
; ---------------------------------------------------------------------------
loc_2A: ; CODE XREF: operator new(unsigned __int64)+15\u2191j
cmp rbx, 0FFFFFFFFFFFFFFFFh
jz short loc_36
;call ?__scrt_throw_std_bad_alloc@@YAXXZ ; __scrt_throw_std_bad_alloc(void)
int 3 ; Trap to Debugger
; ---------------------------------------------------------------------------
loc_36: ; CODE XREF: operator new(unsigned __int64)+2E\u2191j
;call ?__scrt_throw_std_bad_array_new_length@@YAXXZ ; __scrt_throw_std_bad_array_new_length(void)
int 3 ; Trap to Debugger
??2@YAPEAX_K@Z endp
;??3@YAXPEAX_K@Z endp
; void __cdecl operator delete(void *Block)
;public ??3@YAXPEAX@Z
??3@YAXPEAX@Z proc near
jmp free
??3@YAXPEAX@Z endp
It will be interesting to see the assembly log in the archive .. look at the executable file in the debugger, it's extremely interesting :eusa_dance:
UASM v2.52, Apr 2 2021, Masm-compatible assembler.
Portions Copyright (c) 1992-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.
Fatal error A1106: Cannot open file: "C:\masm64\bin" [13]
main_c.asm(8) : Warning A4305: Stackbase automatically changed to RSP to support WIN64 options
Translated Windows SDK 10.0 64 bits
main_c.asm: 102 lines, 2 passes, 976 ms, 1 warnings, 0 errors
Microsoft (R) Incremental Linker Version 14.28.29337.0
Copyright (C) Microsoft Corporation. All rights reserved.
Processed /NODEFAULTLIB:libcmt.lib
LINK : warning LNK4010: invalid subsystem version number 5.0; default subsystem version assumed
Starting pass 1
Processed /DEFAULTLIB:C:\masm64\lib64\msvcrt.lib
Processed /DEFAULTLIB:msvcrt.lib
Processed /DEFAULTLIB:D:\masm64\14.28.29910\lib\x64\msvcrt.lib
Processed /DEFAULTLIB:C:\masm64\m64lib\m64lib.lib
Processed /DEFAULTLIB:OLDNAMES
Searching libraries
Searching C:\masm64\lib64\msvcrt.lib:
Found printf
Referenced in main_c.obj
Loaded msvcrt.lib(msvcrt.dll)
Found "void * __cdecl operator new(unsigned __int64)" (??2@YAPEAX_K@Z)
Referenced in MyWrapper.obj
Loaded msvcrt.lib(msvcrt.dll)
Found __IMPORT_DESCRIPTOR_msvcrt
Referenced in msvcrt.lib(msvcrt.dll)
Referenced in msvcrt.lib(msvcrt.dll)
Loaded msvcrt.lib(msvcrt.dll)
Found __NULL_IMPORT_DESCRIPTOR
Referenced in msvcrt.lib(msvcrt.dll)
Loaded msvcrt.lib(msvcrt.dll)
Found msvcrt_NULL_THUNK_DATA
Referenced in msvcrt.lib(msvcrt.dll)
Loaded msvcrt.lib(msvcrt.dll)
Searching D:\masm64\14.28.29910\lib\x64\msvcrt.lib:
Found "void __cdecl operator delete(void *,unsigned __int64)" (??3@YAXPEAX_K@Z)
Referenced in MyWrapper.obj
Loaded msvcrt.lib(delete_scalar_size.obj)
Found "void __cdecl operator delete(void *)" (??3@YAXPEAX@Z)
Referenced in msvcrt.lib(delete_scalar_size.obj)
Loaded msvcrt.lib(delete_scalar.obj)
Searching C:\masm64\m64lib\m64lib.lib:
Searching D:\masm64\14.28.29910\lib\onecore\x64\OLDNAMES.lib:
Searching C:\masm64\lib64\msvcrt.lib:
Found free
Referenced in msvcrt.lib(delete_scalar.obj)
Loaded msvcrt.lib(msvcrt.dll)
Finished searching libraries
Finished pass 1
Unused libraries:
C:\masm64\m64lib\m64lib.lib
D:\masm64\14.28.29910\lib\onecore\x64\OLDNAMES.lib
Starting pass 2
MyClass.obj
MyWrapper.obj
main_c.obj
msvcrt.lib(msvcrt.dll)
msvcrt.lib(msvcrt.dll)
msvcrt.lib(msvcrt.dll)
msvcrt.lib(msvcrt.dll)
msvcrt.lib(msvcrt.dll)
msvcrt.lib(msvcrt.dll)
msvcrt.lib(delete_scalar.obj)
msvcrt.lib(delete_scalar_size.obj)
Finished pass 2
From the log you can see that both functions were found in different libraries :biggrin: What does it mean?!
At the moment we have a scheme -
C++ Object Modules <- C Language Wrapper <- Assembler program -> Wrapper generator to automate interface creation* (subsequent challenge)
As you said earlier, there are two ways out, a direct function call or the ability to create your own library with the necessary functions by examining libcmt.lib
Quote from: Vortex on April 24, 2021, 12:28:47 AM
Of course, my example is very simple. C++ offers constructors, destructors, friend functions, overloading etc. All those features needs to be studied to see how they should be combined with asm.
Quote from: jj2007 on April 23, 2021, 05:04:02 AM
Any chance to provide a tiny C++ "class" for testing how "this" etc are passed to C++?
The last question I would like to clarify, if all functions are exported from Microsoft libraries, then I don't understand why they inline functions that can be found in standard libraries ?! It will be very interesting to hear your opinion :skrewy:
Hi LiaoMi,
Nice work. Inline functions are useful to avoid function calling overhead. It's an optimization method to build faster code.
> then I don't understand why they inline functions that can be found in standard libraries
This one is simple, inlining some functions can be more efficient than calling external library modules.
Hello,
Code modified to use
new and
delete :
#include <stdio.h>
class formula {
public:
int calc(int, int, int, int, int);
void GetResult(int);
};
int formula::calc(int a, int b, int c, int d, int e)
{
return a * b * c * d * e;
}
void formula::GetResult(int t)
{
printf("The result is %u\n",t);
}
int main()
{
int result=0;
formula *f = new formula();
result = f->calc(2,4,6,8,10);
f->GetResult(result);
delete f;
return 0;
}
An alternative :
Quote/MD link with MSVCRT.LIB
cl /MD ClassSample.cpp
ClassSample.exe : 5632 bytes
\PellesC\bin\podump.exe /imports ClassSample.exe | find ".dll"
MSVCR90.dll
KERNEL32.dll
The attached example imports
new and
delete from msvcrt.dll , not msvcr90.dll The size of the executable is 3072 bytes.
deleted
There was little time to answer on time, sorry :undecided:
@VortexHi Vortex,
another great example, thank you! :thup:
@HutchHi Hutch,
perfect strategy until 400kb of useless code is added :biggrin: Thanks!
@nidudHi Nidud,
the reverse technique is also quite powerful, thanks! :thumbsup:
@NewI made an example on templates, but it does not work for float numbers, maybe I missed something, or there is an error in the compiler - https://developercommunity.visualstudio.com/t/c-compiler-bug-when-using-fpfast-and-o2/155625
(https://i.ibb.co/vjDFkGX/main-c-20210502-170537-exe.png)
I tried different methods, the code is not generated correctly :dazzled:
0000000140001390 <main_c_20210502_173122.public: void __cdecl MyClass<float>::template_set(float) __ptr64> | F3:0F1109 | MOVSS DWORD PTR DS:[RCX], XMM1 |
0000000140001350 <main_c_20210502_173122.public: float __cdecl MyClass<float>::template_get(void) __ptr64> | F3:0F1001 | MOVSS XMM0, DWORD PTR DS:[RCX] |
DWORD != QWORD ->
Float INIT00000001400010D9 | F2:0F100D 871F0000 | MOVSD XMM1, [b]QWORD[/b] PTR DS:[<__real@404e000000000000>] |
C Language
__real@404e000000000000 dq 404E000000000000h
Asm
__real@404e000000000000 dq -60.0; Float
QuoteMove Scalar Single-Precision Floating-Point Values
Opcode Mnemonic Description
F3 0F 10 /r MOVSS xmm1, xmm2/m32 Move scalar single-precision floating-point value from xmm2/m32 to xmm1 register.
F3 0F 11 /r MOVSS xmm2/m32, xmm1 Move scalar single-precision floating-point value from xmm1 register to xmm2/m32.
Description
Moves a scalar single-precision floating-point value from the source operand (second operand) to the destination operand (first operand). The source and destination operands can be XMM registers or 32-bit memory locations. This instruction can be used to move a single-precision floating-point value to and from the low doubleword of an XMM register and a 32-bit memory location, or to move a single-precision floating-point value between the low doublewords of two XMM registers. The instruction cannot be used to transfer data between memory locations.
When the source and destination operands are XMM registers, the three high-order doublewords of the destination operand remain unchanged. When the source operand is a memory location and destination operand is an XMM registers, the three high-order doublewords of the destination operand are cleared to all 0s.
Operation
//MOVSS instruction when source and destination operands are XMM registers:
if(IsXMM(Source) && IsXMM(Destination)) Destination[0..31] = Source[0..31];
//Destination[32..127] remains unchanged
//MOVSS instruction when source operand is XMM register and destination operand is memory location:
else if(IsXMM(Source) && IsMemory(Destination)) Destination = Source[0..31];
//MOVSS instruction when source operand is memory location and destination operand is XMM register:
else {
Destination[0..31] = Source;
Destination[32..127] = 0;
}
int In = 56;
float flo = 60.0f; <- QWORD
double doub = 3.14159265358979;
long long ll = 3.14159265358971;
struct float_attrstr* f = newMyClassFloat();
MyClass_float_set(f, 60.0f);
//using %f format specifier
printf("Value of float = %f\n", MyClass_float_get(f)); <- DWORD
printf("Value of float = %f\n", flo); <- QWORD
deleteMyClassFloat(f);
The main procedure for tests is not correct, because I think the float should be
DWORD (32 bits).
Quote from: LiaoMi on May 03, 2021, 01:40:51 AM
DWORD != QWORD -> Float INIT
00000001400010D9 | F2:0F100D 871F0000 | MOVSD XMM1, [b]QWORD[/b] PTR DS:[<__real@404e000000000000>] |
C Language
__real@404e000000000000 dq 404E000000000000h
int In = 56;
float flo = 60.0f; <- QWORD
double doub = 3.14159265358979;
long long ll = 3.14159265358971;
struct float_attrstr* f = newMyClassFloat();
MyClass_float_set(f, 60.0f);
//using %f format specifier
printf("Value of float = %f\n", MyClass_float_get(f)); <- DWORD
printf("Value of float = %f\n", flo); <- QWORD
deleteMyClassFloat(f);
The main procedure for tests is not correct, because I think the float should be DWORD (32 bits).
Hi LiaoMi,
The code is correct. printf() has varargs. Also, %f handles only QWORDs. flo is converted to double (REAL8/QWORD) because printf() value argument is untyped. I think you have optimization enabled, so compiler eliminated flo, and loaded QWORD 60.0 directly from constant memory.
Hi tenkey,
to see the error, you need to run the file and look at the template. At this point, there may be no error, but in the class itself, the float is treated as a dword, i.e. value is truncated, the supplied qword does not retain its value, as you can see in the picture above. The value was read in the same way, this value is also not correct. I tried both with and without optimization, there are both versions in the archive above. I tried converting a variable from qword to dword, but nothing good came out.
MyClass_float_set(f, 60.0f); <- 60.0f =QWORD IN - here we need a DWORD, but the input is QWORD
printf("Value of float = %f\n", MyClass_float_get(f)); <- DWORD out
Value of int = 56
Value of int = 56
Value of double = 3.141593
Value of double = 3.141593
Value of float = 0.000000 <- Bug
Value of float = 60.000000
Value of long long = 1431655765
Value of long long = 1431655765
Hi LiaoMi,
The following code compiles correctly under MSVC 19.28.29914.0:
#include <stdio.h>
extern struct float_attrstr* newMyClassFloat();
extern void deleteMyClassFloat(struct float_attrstr *f);
extern void MyClass_float_set(struct float_attrstr* f, float v);
extern float MyClass_float_get(struct float_attrstr* f);
int main(int argc, char** argv)
{
int In = 56;
float flo = 60.0f;
double doub = 3.14159265358979;
long long ll = 3.14159265358971;
struct float_attrstr* f = newMyClassFloat();
MyClass_float_set(f, 60.0f); // DWORD constant
//using %f format specifier
printf("Value of float = %f\n", MyClass_float_get(f)); // returned DWORD, converted to QWORD
printf("Value of float = %f\n", flo); // QWORD constant
deleteMyClassFloat(f);
}
Important differences between main_c.asm and the MSVC 19.28.29914.0 .asm output:
call newMyClassFloat
movsd xmm1, __real@404e000000000000 ; not VC - does not load a QWORD constant
movss xmm1, DWORD PTR __real@42700000 ; VC - loads a DWORD constant
movsxd rbx, eax ; not VC - newMyClassFloat() does not return a 32-bit pointer
mov rbx, rax ; VC - newMyClassFloat() returns a 64-bit pointer
movq rdx, xmm1
mov rcx, rbx
call MyClass_float_set
mov rcx, rbx
call MyClass_float_get
PrologueRsp40
mov edx, eax ; not VC - 32-bit float is not returned in eax
cvtss2sd xmm1, xmm0 ; VC - 32-bit float is returned in xmm0, converted to 64-bit float
movq rdx, xmm1 ; VC - printf() has varargs, all floats must be at least 64-bit
lea rcx, Format3 ; "Value of float = %lf\n"
call printf
EpilogueRsp40
Quote from: tenkey on May 05, 2021, 10:51:27 AM
Hi LiaoMi,
The following code compiles correctly under MSVC 19.28.29914.0:
#include <stdio.h>
extern struct float_attrstr* newMyClassFloat();
extern void deleteMyClassFloat(struct float_attrstr *f);
extern void MyClass_float_set(struct float_attrstr* f, float v);
extern float MyClass_float_get(struct float_attrstr* f);
int main(int argc, char** argv)
{
int In = 56;
float flo = 60.0f;
double doub = 3.14159265358979;
long long ll = 3.14159265358971;
struct float_attrstr* f = newMyClassFloat();
MyClass_float_set(f, 60.0f); // DWORD constant
//using %f format specifier
printf("Value of float = %f\n", MyClass_float_get(f)); // returned DWORD, converted to QWORD
printf("Value of float = %f\n", flo); // QWORD constant
deleteMyClassFloat(f);
}
Important differences between main_c.asm and the MSVC 19.28.29914.0 .asm output:
call newMyClassFloat
movsd xmm1, __real@404e000000000000 ; not VC - does not load a QWORD constant
movss xmm1, DWORD PTR __real@42700000 ; VC - loads a DWORD constant
movsxd rbx, eax ; not VC - newMyClassFloat() does not return a 32-bit pointer
mov rbx, rax ; VC - newMyClassFloat() returns a 64-bit pointer
movq rdx, xmm1
mov rcx, rbx
call MyClass_float_set
mov rcx, rbx
call MyClass_float_get
PrologueRsp40
mov edx, eax ; not VC - 32-bit float is not returned in eax
cvtss2sd xmm1, xmm0 ; VC - 32-bit float is returned in xmm0, converted to 64-bit float
movq rdx, xmm1 ; VC - printf() has varargs, all floats must be at least 64-bit
lea rcx, Format3 ; "Value of float = %lf\n"
call printf
EpilogueRsp40
Hi tenkey,
thanks a lot :thup: :thup: :thup: :thumbsup:, I completely forgot that I had to describe the prototypes in the C example, I didn't even notice it :biggrin:. Now the results are all correct
Value of int = 56
Value of int = 56
Value of double = 3.141593
Value of double = 3.141593
Value of float = 60.000000
Value of float = 60.000000
Value of long long = 1431655765
Value of long long = 1431655765
The update can be downloaded below :arrow_down:
Assembly stages1. cl -c /D /x64 /doc /O2 MyWrapper.cc
2. cl -c /D /x64 /doc /O2 MyClass.cc
3. Build_uasm64_Link_x64_Debug.bat - link /OPT:NOREF /FIXED /MAP:%appname%_%stamp%.map /entry:%entryOEP% /DEBUG /debugtype:cv /VERBOSE /NODEFAULTLIB:libcmt.lib /pdb:%appname%_%stamp%.pdb /SUBSYSTEM:Console,5.0 /VERSION:4.0 /MACHINE:X64 /RELEASE %appname%.obj MyWrapper.obj MyClass.obj >> %appname%_%stamp%.assemblylog.txt
Hi,
I updated the first post with this content:
Part 1: Using a simple class - http://masm32.com/board/index.php?topic=9317.msg102309#msg102309 (http://masm32.com/board/index.php?topic=9317.msg102309#msg102309)
Part 2: Using a class template - http://masm32.com/board/index.php?topic=9317.msg102551#msg102551 (http://masm32.com/board/index.php?topic=9317.msg102551#msg102551)
Part 3: Advanced techniques for addressing functions in C++ from an assembler program
Part 4: Using dynamic variables and class initialization from assembler
Part 5: Defining a class in another class
Part 6: I haven't thought about that yet :tongue:
Now we need to think about how to convert the classes into a ready-made wrapper object in assembler, so that this is not the same as I did by compiling an example in the C language. Each type in the template will need its own wrapper function. To do this, we will take classes and templates that are already familiar to us.
class MyClass {
private:
int m_i;
public:
void int_set(int i);
int int_get();
};
void MyClass::int_set(int i) {
m_i = i;
}
int MyClass::int_get() {
return m_i;
}
template<typename T>
class MyClass {
private:
T m_i;
public:
void template_set(T i);
T template_get();
};
template <typename T>
void MyClass<T>::template_set(T i)
{
m_i = i;
}
template <typename T>
T MyClass<T>::template_get()
{
return m_i;
}
template class MyClass<int>;
template class MyClass<double>;
template class MyClass<float>;
template class MyClass<long long>;
The structure of the wrapper remains the same for us, only the types will change.
#ifdef __cplusplus
extern "C" {
#endif
typedef struct MyClass MyClass;
MyClass* newMyClass();
void MyClass_int_set(MyClass* v, int i);
int MyClass_int_get(MyClass* v);
void deleteMyClass(MyClass* v);
#ifdef __cplusplus
}
#endif
It should be something like this (this is an incomplete conversion, partly pseudocode).
MyClass TYPEDEF MyClass
newMyClass PROTO
MyClass_int_set PROTO v:XMASM ,i:DWORD
MyClass_int_get PROTO v:XMASM
deleteMyClass PROTO v:XMASM
MyClass STRUCT DEFALIGNMASM
m_i DWORD ?
void int_set(int i)
int int_get()
MyClass ENDS
or
MyClass STRUCT DEFALIGNMASM
m_i DWORD ?
int_set QWORD ?
int_get QWORD ?
MyClass ENDS
For those who want to dive deeper into the topic:
(Addison-Wesley professional computing series) David R. Hanson - C Interfaces and Implementations Techniques for Creating Reusable Software-Addison-Wesley Professional (1996) - https://archive.org/details/cinterfacesimple0000hans
Source Code - https://github.com/drh/cii or https://github.com/drh/cii/archive/refs/heads/master.zip