New code injection method

Started by Micktu, August 05, 2016, 02:59:26 AM

Previous topic - Next topic

Micktu

So after a day and some of some hardcore ass-in-chair action I've managed to produce another solution that is dumb and brilliant at the same time.

Basically, it acts as an override: if the method was called from some distant galaxy, it calls your hook/detour/whateveryoucallit. However, if it's called from YOUR method, it calls the base method. It still relies on guessing the boundaries of your method once, but now I have a pretty good idea how to make it reliable.

It's really really hacky, I'll publish it once I tidy it up a little bit.

1000101

Quote from: biship on August 06, 2016, 06:09:49 AM
I don't have 1/10th the coding knowledge you guys have... so posting this here as it might be semi-relevant.
For most moddable games, someone eventually comes out with a performance meter of some kind. Is there one for Rimworld?
I'm looking for, or willing to learn how to make, a dll to monitor the frequency (to start with) mod methods fire.
To paint a picture - a mod is supposed to fire when a pawn's mood changes, yet fires needlessly on every pawn item interaction?
The extension of this would be to determine how long each time each mod activation consumes.

From the code changes I follow on github, I know you guys are aware of the need to optimize your own code. Was just wondering if there is a way to determine the impact of other peoples code ingame. Thanks for any replies.

This is not really relevant to the current discussion, please create a new thread for it.  :)
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

biship

Then, I don't have a 1/10th the clue what this thread is about. :)

Micktu

So I'll drop this monstrosity here: https://github.com/micktu/RimWorld-BuildProductive/blob/injection/Source/HookInjector.cs

There's still a lot of refactoring and testing to do, but the idea is there.

It lets me do this:



To get this:



I'll also make sure that it still works correctly even if the proc was already rerouted by other mods using CCL.

Was it worth the effort? Not sure. Also thing like this risks hella lot of maintenance even if it's stable.

RawCode

method is good.

there is no reason to reject again and again things you don't like for some personal reasons.

Micktu

After browsing Mono sources for a while I've found that the correct method to find generated code size is to hook into Mono JIT's mono_destroy_compile (MonoCompile *cfg). The MonoCompile struct is supposed to contain code length then, it will be freed afterwards, and the info forever lost. Not doing it at the moment though.

Micktu

Yep, that was correct.



Highlighted is the function pointer, followed by code_size and code_len fields, containing correct values (I don't know why both fields are needed, the compiler seems to care about code_len in the end).

Micktu

A cute small function injected before mono_empty_compile() keeps track of last 256 functions compiled with their sizes.


Micktu

Aaand it worked. Prototype code here: https://github.com/micktu/RimWorld-BuildProductive/blob/injection/Source/MethodSizeHelper.cs

This thing is getting reliable. Let's make it work on x64 again.

RawCode

this is bloat, proper way for calculation method size is:

[MethodImpl(MethodImplOptions.NoInlining)]
static public void MethodFastPrint(RuntimeMethodHandle hx)
{
void* fpraw = hx.GetFunctionPointer().ToPointer();
void* jitinforaw = mono_jit_info_table_find (mono_domain_get (), fpraw);
u_MonoJitInfo jitinfo = *(u_MonoJitInfo*)jitinforaw;
int size = jitinfo.code_size;
}


where

[DllImport("__Internal")]
static extern private unsafe void* mono_jit_info_table_find(void* MonoDomain, void* ptr2function);

[DllImport("__Internal")]
static extern private unsafe void* mono_domain_get ();


and memory map is

[StructLayout(LayoutKind.Explicit)] public unsafe struct u_MonoJitInfo
{
[FieldOffset(0) ] public int  *dmethod;
[FieldOffset(8) ] public int  *code_start;
[FieldOffset(16)] public int   code_size;
}


linked table contains ALL methods ever compiled for domain, you don't need to inject anything to keep track of them, runtime already do it.

Micktu

#25
Thanks, I'll verify one I'll get to my PC.

Edit: Yes, that seems just about right! Let's implement.

Actually it wasn't bloat, it was only the beginning of bloat, considering I can't get away with a static address on *nix and have to dlinfo/dlsym modules etc. So it sucked.

This is so much better.

Longwelwind

#26
I believe you can use Mono.Cecil to do that but more easily.
Back when I was modding Planetbase, we did something similar to add event hooks to the game. I began to convert what I did for Planetbase to Rimworld, but since CCL already exists, I didn't continue.
The repo is available here. Basically, what it does is injecting MSIL code into the Assembly-CSharp.dll assembly to call the library that will load mods and offer them event hooks. It reads a XML file to know what kind of code to inject in the assembly. For example:

<Class Name="PlayDataLoader" Location="0">
    <Method Name="LoadAllPlayData">
      <Instruction OpCode="Call" Assembly="PhiScript" Type="PhiScript.Phi" Method="StaticLaunch" />
    </Method>
</Class>

This configuration would inject a static call to a static method called "Phi" in a class called "PhiScript" in an assembly called "PhiScript" in the PlayDataLoader (one of the earliest called method in Rimworld), allowing me to catch this event and add behaviour for this.
The advantage of using Mono.Cecil is that it takes care of pretty much everything: calculating addresses, offsets, adding assemblies dependencies into the modified dependencies and such.

Micktu

#27
Cecil cannot patch assemblies at runtime. You can only get away with Cecil (and it works perfectly well) if you patch the assembly before loading it.

Anyway, we're beyond patching assemblies, we're patching native code here.

RawCode

Quote from: Longwelwind on August 08, 2016, 08:39:23 AM
I believe you can use Mono.Cecil to do that but more easily.
Back when I was modding Planetbase, we did something similar to add event hooks to the game. I began to convert what I did for Planetbase to Rimworld, but since CCL already exists, I didn't continue.
The repo is available here. Basically, what it does is injecting MSIL code into the Assembly-CSharp.dll assembly to call the library that will load mods and offer them event hooks. It reads a XML file to know what kind of code to inject in the assembly. For example:

<Class Name="PlayDataLoader" Location="0">
    <Method Name="LoadAllPlayData">
      <Instruction OpCode="Call" Assembly="PhiScript" Type="PhiScript.Phi" Method="StaticLaunch" />
    </Method>
</Class>

This configuration would inject a static call to a static method called "Phi" in a class called "PhiScript" in an assembly called "PhiScript" in the PlayDataLoader (one of the earliest called method in Rimworld), allowing me to catch this event and add behaviour for this.
The advantage of using Mono.Cecil is that it takes care of pretty much everything: calculating addresses, offsets, adding assemblies dependencies into the modified dependencies and such.

base files modifications is not allowed, read manual first.

Micktu

#29
So, thanks again, RawCode-sensei! It does work as expected.

Implemented like this so it should work on x64 out of the box (maybe will have to go with libmono instead of __Internal on *nix though):


        [DllImport("__Internal")]
        public static extern IntPtr mono_jit_info_table_find(IntPtr domain, IntPtr addr);

        [DllImport("__Internal")]
        public static extern IntPtr mono_domain_get();

        [StructLayout(LayoutKind.Sequential)]
        public struct MonoJitInfo
        {
            public IntPtr d;
            public IntPtr n;
            public IntPtr code_start;
            public uint unwind_info;
            public int code_size;
            // The rest is omitted
        }



            var infoPtr = Platform.mono_jit_info_table_find(Platform.mono_domain_get(), targetPtr);
            var ji = (Platform.MonoJitInfo)Marshal.PtrToStructure(infoPtr, typeof(Platform.MonoJitInfo));


Now I can finally get rid of terrible, terrible size scan code.