3F / Conari

🧬 One-touch unmanaged memory, runtime dynamic use of the unmanaged native C/C++ in .NET world, related P/Invoke features, and …
MIT License
252 stars 28 forks source link

ExVar.getVar returning wrong value #17

Closed SapientGuardian closed 4 years ago

SapientGuardian commented 4 years ago

Hello,

I have a C# function that is getting invoked from a C++ executable. In this function, I am attempting to do the literal equivalent of

int number;
std::cin >> number;

Obviously I could do something like number = int.Parse(Console.ReadLine()), but in this case I really need to do exactly what C++ is doing (and besides, what fun would that be?). Leveraging a decompiler to get the exact mangled names, I came up with this code:

            int number= 0;
            using (var l = new ConariL(@"c:\windows\system32\MSVCP140.dll"))
            {                
                var cin = l.ExVar.getVar<IntPtr>("?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A");

                l.bind<FuncOut2<IntPtr, int, IntPtr>>("??5?$basic_istream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@AEAH@Z").Invoke(cin, out number);
            }

The function binding works correctly, but it throws an access violation inside of the function because the address of cin is incorrect. The intptr returned by getVar is 0x00007ffe2a0cc110, which the Visual Studio debugger tells me is the address of std::basic_istream<char,std::char_traits<char>>:: vbtable', not of std::basic_istream<char,std::char_traits<char>> std::cin. Indeed, if I put std::cout << &(std::cin); in the C++ app, it outputs 00007FFE2A0F7200, which Visual Studio agrees is correct.

If I force 00007FFE2A0F7200 into my cin variable instead of getting it with getVar, the function executes correctly, prompting for user input and storing it in number.

3F commented 4 years ago

Hello @SapientGuardian

std::cin is not actually a variable (at least for ExVar context) and in general assumes working with streams. ExVar was just designed for other purposes such as final data as variables or some other const data in memory.

That is,

00007FF844A06070 -> 00007ff8449db0d0  -> data
                    ^^^^^^^^^^^^^^^^
                    final value at this pointer

See details in #7 and https://github.com/3F/DllExport/issues/24

Logic of the getVar above was based on getField + our Conari 'native' extension:

-> getField
v
-> -> provider
        .Svc
        .native(lpProcName)
        .t(type)
        .Raw.Type.FirstField;

Therefore it will return an wrong pointer because ExVar feature already calculated this as final data by jumping "forward".

I don't remember exactly but more like we don't have special logic for any streams and similar cases in 1.4. Although for 1.4 you can try at least this:

var cin = l.Svc.getProcAddr(l.Svc.procName("?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A", false));
// now a correct pointer to std::cin stream

I think we need a more elegant ~"Conari way".

But I still can't talk anything specific about some changes/new features/or some releases at least from me due to my health problems and more; However I can try to coordinate some work around my projects and their releases if someone be ready for something.

Thanks for using. Yours, -- Denis Kuzmin

3F commented 4 years ago

FYI don't forget about aliases which are super useful for C++ (the case of #3). For example:

l.Aliases.Add("cin", "?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A");

then it could be like

_v1 = l.ExVar.DLR.cin<IntPtr>();
_v2 = l.ExVar.get<IntPtr>("cin");
// ...
l.ExVar.get<IntPtr>("class std::basic_istream<char,struct std::char_traits<char>> std::cin");
etc.
SapientGuardian commented 4 years ago

Thank you for your insights; the getProcAddr method you suggested works perfectly!

3F commented 3 years ago

Conari 1.5 now provides IProvider.addr()

Use it easier,

l.addr("...")

Also note the following,

l.Svc.getProcAddr("...")
l.Svc.native("...")