public void TestDetoursExt() { lock (TestObject.Lock) { // The following use cases are not meant to be usage examples. // Please take a look at DetourTest and HookTest instead. using (NativeDetour d = new NativeDetour( // .GetNativeStart() to enforce a native detour. typeof(TestObject).GetMethod("TestStaticMethod").Pin().GetNativeStart(), typeof(DetourExtTest).GetMethod("TestStaticMethod_A") )) { int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(6, staticResult); staticResult = TestObject.TestStaticMethod(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(12, staticResult); } // We can't create a backup for this. MethodBase dm; using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(TestObject).GetMethod("TestStaticMethod"))) { dm = dmd.Generate(); } using (NativeDetour d = new NativeDetour( dm, typeof(DetourExtTest).GetMethod("TestStaticMethod_A") )) { int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(6, staticResult); // FIXME: dm.Invoke can fail with a release build in mono 5.X! // staticResult = (int) dm.Invoke(null, new object[] { 2, 3 }); staticResult = ((Func <int, int, int>)dm.CreateDelegate <Func <int, int, int> >())(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(12, staticResult); } // This was provided by Chicken Bones (tModLoader). // GetEncoding behaves differently on .NET Core and even between .NET Framework versions, // which is why this test only applies to Mono, preferably on Linux to verify if flagging // regions of code as read-writable and then read-executable works for AOT'd code. #if false using (Hook h = new Hook( typeof(Encoding).GetMethod("GetEncoding", new Type[] { typeof(string) }), new Func <Func <string, Encoding>, string, Encoding>((orig, name) => { if (name == "IBM437") { return(null); } return(orig(name)); }) )) { Assert.Null(Encoding.GetEncoding("IBM437")); } #endif } }
public void TestDetours() { // The following use cases are not meant to be usage examples. // Please take a look at DetourTest and HookTest instead. using (NativeDetour d = new NativeDetour( // .GetNativeStart() to enforce a native detour. typeof(TestObject).GetMethod("TestStaticMethod").GetNativeStart(), typeof(DetourExtTest).GetMethod("TestStaticMethod_A") )) { int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(6, staticResult); staticResult = TestObject.TestStaticMethod(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(12, staticResult); } // We can't create a backup for this. MethodBase dm; using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(TestObject).GetMethod("TestStaticMethod"))) { dm = dmd.Generate(); } using (NativeDetour d = new NativeDetour( dm, typeof(DetourExtTest).GetMethod("TestStaticMethod_A") )) { int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(6, staticResult); staticResult = (int)dm.Invoke(null, new object[] { 2, 3 }); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(12, staticResult); } // This was provided by Chicken Bones (tModLoader). using (Hook h = new Hook( typeof(Encoding).GetMethod("GetEncoding", new Type[] { typeof(string) }), new Func <Func <string, Encoding>, string, Encoding>((orig, name) => { if (name == "IBM437") { return(null); } return(orig(name)); }) )) { Assert.Null(Encoding.GetEncoding("IBM437")); } }
public void TestDetoursRedo() { lock (TestObject.Lock) { // The following use cases are not meant to be usage examples. // Please take a look at DetourTest and HookTest instead. Step(new NativeDetour( typeof(TestObject).GetMethod("TestStaticMethod"), typeof(DetourRedoTest).GetMethod("TestStaticMethod_A") )); Step(new Detour( typeof(TestObject).GetMethod("TestStaticMethod"), typeof(DetourRedoTest).GetMethod("TestStaticMethod_A") )); Step(new Hook( typeof(TestObject).GetMethod("TestStaticMethod"), typeof(DetourRedoTest).GetMethod("TestStaticMethod_A") )); void Step(IDetour d) { using (d) { Assert.True(d.IsValid); Assert.True(d.IsApplied); int staticResult = TestObject.TestStaticMethod(2, 3); Assert.Equal(12, staticResult); d.Undo(); Assert.True(d.IsValid); Assert.False(d.IsApplied); staticResult = TestObject.TestStaticMethod(2, 3); Assert.Equal(6, staticResult); d.Apply(); Assert.True(d.IsValid); Assert.True(d.IsApplied); staticResult = TestObject.TestStaticMethod(2, 3); Assert.Equal(12, staticResult); } Assert.False(d.IsValid); Assert.False(d.IsApplied); } } }
public void TestDetoursRedo() { lock (TestObject.Lock) { // The following use cases are not meant to be usage examples. // Please take a look at DetourTest and HookTest instead. // Uncomment the following line when you want to run this test isolated and make sure that pins aren't being leaked. DetourRuntimeILPlatform runtimeIL = null; // DetourHelper.Runtime as DetourRuntimeILPlatform; DetourRuntimeILPlatform.MethodPinInfo[] pinnedPrev = null; if (runtimeIL != null) { pinnedPrev = runtimeIL.GetPins(); } Step(new NativeDetour( typeof(TestObject).GetMethod("TestStaticMethod"), typeof(DetourRedoTest).GetMethod("TestStaticMethod_A") )); Step(new Detour( typeof(TestObject).GetMethod("TestStaticMethod"), typeof(DetourRedoTest).GetMethod("TestStaticMethod_A") )); Step(new Hook( typeof(TestObject).GetMethod("TestStaticMethod"), typeof(DetourRedoTest).GetMethod("TestStaticMethod_A") )); if (runtimeIL != null) { DetourRuntimeILPlatform.MethodPinInfo[] pinned = runtimeIL.GetPins(); Assert.Equal(pinnedPrev.Length, pinned.Length); for (int i = 0; i < pinned.Length; i++) { DetourRuntimeILPlatform.MethodPinInfo pinPrev = pinnedPrev[i]; DetourRuntimeILPlatform.MethodPinInfo pin = pinned[i]; Assert.Equal(pinPrev.Handle.Value, pin.Handle.Value); Assert.Equal(pinPrev.Count, pin.Count); } } void Step(IDetour d) { using (d) { Assert.True(d.IsValid); Assert.True(d.IsApplied); int staticResult = TestObject.TestStaticMethod(2, 3); Assert.Equal(12, staticResult); d.Undo(); Assert.True(d.IsValid); Assert.False(d.IsApplied); staticResult = TestObject.TestStaticMethod(2, 3); Assert.Equal(6, staticResult); d.Apply(); Assert.True(d.IsValid); Assert.True(d.IsApplied); staticResult = TestObject.TestStaticMethod(2, 3); Assert.Equal(12, staticResult); } Assert.False(d.IsValid); Assert.False(d.IsApplied); } } }
public void TestDetours() { Console.WriteLine("Detours: none"); TestObject.TestStep(5, 6, 8); Console.WriteLine(); // Three examples of using Detour. // Note that if you need non-layered, low-level hooks, you can use NativeDetour instead. // This is also why the variable type is IDetour. // System.Linq.Expressions, thanks to leo (HookedMethod) for telling me about how to (ab)use MethodCallExpression! IDetour detourTestMethodA = new Detour( () => default(TestObject).TestMethod(default(int), default(int)), () => TestMethod_A(default(TestObject), default(int), default(int)) ); // Method references as delegates. IDetour detourTestStaticMethodA = new Detour <Func <int, int, int> >( TestObject.TestStaticMethod, TestStaticMethod_A ); // MethodBase, old syntax. IDetour detourTestVoidMethodA = new Detour( typeof(TestObject).GetMethod("TestVoidMethod", BindingFlags.Static | BindingFlags.Public), typeof(DetourTest).GetMethod("TestVoidMethod_A", BindingFlags.Static | BindingFlags.Public) ); Console.WriteLine("Detours: A"); TestObject.TestStep(42, 12, 1); Console.WriteLine("Testing trampoline, should invoke orig, TestVoidMethod(2, 3)"); detourTestVoidMethodA.GenerateTrampoline <Action <int, int> >()(2, 3); Console.WriteLine(); IDetour detourTestMethodB = new Detour( () => default(TestObject).TestMethod(default(int), default(int)), () => TestMethod_B(default(TestObject), default(int), default(int)) ); IDetour detourTestStaticMethodB = new Detour( () => TestObject.TestStaticMethod(default(int), default(int)), () => TestStaticMethod_B(default(int), default(int)) ); IDetour detourTestVoidMethodB = new Detour( () => TestObject.TestVoidMethod(default(int), default(int)), () => TestVoidMethod_B(default(int), default(int)) ); Console.WriteLine("Detours: A + B"); TestObject.TestStep(120, 8, 2); Console.WriteLine("Testing trampoline, should invoke hook A, TestVoidMethod(2, 3)"); Action <int, int> trampolineTestVoidMethodB = detourTestVoidMethodB.GenerateTrampoline <Action <int, int> >(); trampolineTestVoidMethodB(2, 3); Console.WriteLine(); detourTestMethodA.Undo(); detourTestStaticMethodA.Undo(); detourTestVoidMethodA.Undo(); Console.WriteLine("Detours: B"); TestObject.TestStep(120, 8, 2); Console.WriteLine("Testing trampoline, should invoke orig, TestVoidMethod(2, 3)"); trampolineTestVoidMethodB(2, 3); Console.WriteLine(); detourTestMethodB.Undo(); detourTestStaticMethodB.Undo(); detourTestVoidMethodB.Undo(); Console.WriteLine("Detours: none"); TestObject.TestStep(5, 6, 8); Console.WriteLine(); }
public void TestDetoursExt() { lock (TestObject.Lock) { // The following use cases are not meant to be usage examples. // Please take a look at DetourTest and HookTest instead. // Just to verify that having a first chance exception handler doesn't introduce any conflicts. AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException; #if true using (NativeDetour d = new NativeDetour( // .GetNativeStart() to enforce a native detour. typeof(TestObject).GetMethod("TestStaticMethod").Pin().GetNativeStart(), typeof(DetourExtTest).GetMethod("TestStaticMethod_A") )) { int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(6, staticResult); staticResult = TestObject.TestStaticMethod(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(12, staticResult); } // We can't create a backup for this. MethodBase dm; using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(TestObject).GetMethod("TestStaticMethod"))) { dm = dmd.Generate(); } using (NativeDetour d = new NativeDetour( dm, typeof(DetourExtTest).GetMethod("TestStaticMethod_A") )) { int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(6, staticResult); // FIXME: dm.Invoke can fail with a release build in mono 5.X! // staticResult = (int) dm.Invoke(null, new object[] { 2, 3 }); staticResult = ((Func <int, int, int>)dm.CreateDelegate <Func <int, int, int> >())(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(12, staticResult); } #endif // This wasn't provided by anyone and instead is just an internal test. #if true MethodInfo dummyA = typeof(DetourExtTest).GetMethod("DummyA").Pin(); MethodInfo dummyB = typeof(DetourExtTest).GetMethod("DummyB").Pin(); MethodInfo dummyC = (MethodInfo)dm; IntPtr dummyAPtr = dummyA.GetNativeStart(); Assert.True(DetourHelper.Runtime.TryMemAllocScratchCloseTo(dummyAPtr, out IntPtr allocAPtr, -1) != 0); Assert.NotEqual(IntPtr.Zero, allocAPtr); IntPtr dummyBPtr = dummyB.GetNativeStart(); Assert.True(DetourHelper.Runtime.TryMemAllocScratchCloseTo(dummyBPtr, out IntPtr allocBPtr, -1) != 0); Assert.NotEqual(IntPtr.Zero, allocBPtr); IntPtr dummyCPtr = dummyC.GetNativeStart(); Assert.True(DetourHelper.Runtime.TryMemAllocScratchCloseTo(dummyCPtr, out IntPtr allocCPtr, -1) != 0); Assert.NotEqual(IntPtr.Zero, allocCPtr); Console.WriteLine($"dummyAPtr: 0x{(long) dummyAPtr:X16}"); Console.WriteLine($"allocAPtr: 0x{(long) allocAPtr:X16}"); Console.WriteLine($"dummyBPtr: 0x{(long) dummyBPtr:X16}"); Console.WriteLine($"allocBPtr: 0x{(long) allocBPtr:X16}"); Console.WriteLine($"dummyCPtr: 0x{(long) dummyCPtr:X16}"); Console.WriteLine($"allocCPtr: 0x{(long) allocCPtr:X16}"); // Close scratch allocs should ideally be within a 1 GiB range of the original method. Assert.True(Math.Abs((long)dummyAPtr - (long)allocAPtr) < 1024 * 1024 * 1024, "dummyAPtr and allocAPtr are too far apart."); Assert.True(Math.Abs((long)dummyBPtr - (long)allocBPtr) < 1024 * 1024 * 1024, "dummyBPtr and allocBPtr are too far apart."); Assert.True(Math.Abs((long)dummyCPtr - (long)allocCPtr) < 1024 * 1024 * 1024, "dummyCPtr and allocCPtr are too far apart."); #endif // This was provided by Chicken Bones (tModLoader). // GetEncoding behaves differently on .NET Core and even between .NET Framework versions, // which is why this test only applies to Mono, preferably on Linux to verify if flagging // regions of code as read-writable and then read-executable works for AOT'd code. #if false using (Hook h = new Hook( typeof(Encoding).GetMethod("GetEncoding", new Type[] { typeof(string) }), new Func <Func <string, Encoding>, string, Encoding>((orig, name) => { if (name == "IBM437") { return(null); } return(orig(name)); }) )) { Assert.Null(Encoding.GetEncoding("IBM437")); } #endif // This was provided by a Harmony user. // TextWriter's methods (including all overrides) were unable to be hooked on some runtimes. // FIXME: .NET 5 introduces similar behavior for macOS and Linux, but RD isn't ready for that. See DetourRuntimeNETPlatform for more info. #if true using (MemoryStream ms = new MemoryStream()) { using (StreamWriter writer = new StreamWriter(ms, Encoding.UTF8, 1024, true)) { // In case anyone needs to debug this mess anytime in the future ever again: /*/ * MethodBase m = typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) }); * Console.WriteLine($"meth: 0x{(long) m?.MethodHandle.Value:X16}"); * Console.WriteLine($"getf: 0x{(long) m?.MethodHandle.GetFunctionPointer():X16}"); * Console.WriteLine($"fptr: 0x{(long) m?.GetLdftnPointer():X16}"); * Console.WriteLine($"nats: 0x{(long) m?.GetNativeStart():X16}"); * /**/ // Debugger.Break(); writer.Write("A"); using (Hook h = new Hook( typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) }), new Action <Action <StreamWriter, string>, StreamWriter, string>((orig, self, value) => { orig(self, "-"); }) )) { // Debugger.Break(); writer.Write("B"); } writer.Write("C"); } ms.Seek(0, SeekOrigin.Begin); using (StreamReader reader = new StreamReader(ms, Encoding.UTF8, false, 1024, true)) { Assert.Equal("A-C", reader.ReadToEnd()); } } #endif #if NETFRAMEWORK && true Assert.Equal("A", new SqlCommand("A").CommandText); using (Hook h = new Hook( typeof(SqlCommand).GetConstructor(new Type[] { typeof(string) }), new Action <Action <SqlCommand, string>, SqlCommand, string>((orig, self, value) => { orig(self, "-"); }) )) { Assert.Equal("-", new SqlCommand("B").CommandText); } Assert.Equal("C", new SqlCommand("C").CommandText); #endif // This was provided by tModLoader. // The .NET Framework codepath failed on making the method writable the for a single user. #if NETFRAMEWORK && true try { throw new Exception(); } catch (Exception e) { Assert.NotEqual("", e.StackTrace.Trim()); } using (Hook h = Type.GetType("Mono.Runtime") != null ? // Mono new Hook( typeof(Exception).GetMethod("GetStackTrace", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance), new Func <Func <Exception, bool, string>, Exception, bool, string>((orig, self, fNeedFileInfo) => { return(""); }) ) : // .NET new Hook( typeof(StackTrace).GetConstructor(new[] { typeof(Exception), typeof(bool) }), new Action <Action <StackTrace, Exception, bool>, StackTrace, Exception, bool>((orig, self, e, fNeedFileInfo) => { orig(self, e, fNeedFileInfo); DynamicData.Set(self, new { frames = new StackFrame[0], m_iNumOfFrames = 0, m_iMethodsToSkip = 0 }); }) )) { try { throw new Exception(); } catch (Exception e) { Assert.Equal("", e.StackTrace.Trim()); } } try { throw new Exception(); } catch (Exception e) { Assert.NotEqual("", e.StackTrace.Trim()); } #endif // This was provided by a Harmony user. // Theoretically this should be a DynamicMethodDefinition test but who knows what else this will unearth. #if true try { new Thrower(1); } catch (Exception e) { Assert.Equal("1", e.Message); } using (Hook h = new Hook( typeof(Thrower).GetConstructor(new Type[] { typeof(int) }), new Action <Action <Thrower, int>, Thrower, int>((orig, self, a) => { try { orig(self, a + 2); } catch (Exception e) { throw new Exception($"{a} + 2 = {e.Message}"); } }) )) { try { new Thrower(1); } catch (Exception e) { Assert.Equal("1 + 2 = 3", e.Message); } } try { new Thrower(1); } catch (Exception e) { Assert.Equal("1", e.Message); } #endif // This was provided by tModLoader. #if true using (Hook h = new Hook( typeof(Process).GetMethod("Start", BindingFlags.Public | BindingFlags.Instance), new Func <Func <Process, bool>, Process, bool>((orig, self) => { return(orig(self)); }) )) { } #endif // This was provided by WEGFan from the Everest team. // It should be preferably tested on x86, as it's where the struct size caused problems. #if true Assert.Equal(new TwoInts() { A = 11111111, B = 22222222 }, DummyTwoInts()); using (Hook h = new Hook( typeof(DetourExtTest).GetMethod("DummyTwoInts", BindingFlags.NonPublic | BindingFlags.Instance), new Func <Func <DetourExtTest, TwoInts>, DetourExtTest, TwoInts>((orig, self) => { TwoInts rv = orig(self); rv.A *= 2; rv.B *= 3; return(rv); }) )) { Assert.Equal(new TwoInts() { A = 11111111 * 2, B = 22222222 * 3 }, DummyTwoInts()); } Assert.Equal(new TwoInts() { A = 11111111, B = 22222222 }, DummyTwoInts()); #endif // This was provided by a Harmony user. // The "struct virtual override" edge case fix itself has got a weird edge case with "struct interface implementations". // Note that .NET Framework differs too heavily and .NET Core 2.1 and older inline the getter. #if NET5_0_OR_GREATER Assert.Equal( new KeyValuePair <int, int>(default, default),
public void TestDetoursExt() { lock (TestObject.Lock) { // The following use cases are not meant to be usage examples. // Please take a look at DetourTest and HookTest instead. #if true using (NativeDetour d = new NativeDetour( // .GetNativeStart() to enforce a native detour. typeof(TestObject).GetMethod("TestStaticMethod").Pin().GetNativeStart(), typeof(DetourExtTest).GetMethod("TestStaticMethod_A") )) { int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(6, staticResult); staticResult = TestObject.TestStaticMethod(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(12, staticResult); } // We can't create a backup for this. MethodBase dm; using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(TestObject).GetMethod("TestStaticMethod"))) { dm = dmd.Generate(); } using (NativeDetour d = new NativeDetour( dm, typeof(DetourExtTest).GetMethod("TestStaticMethod_A") )) { int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(6, staticResult); // FIXME: dm.Invoke can fail with a release build in mono 5.X! // staticResult = (int) dm.Invoke(null, new object[] { 2, 3 }); staticResult = ((Func <int, int, int>)dm.CreateDelegate <Func <int, int, int> >())(2, 3); Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}"); Assert.Equal(12, staticResult); } #endif // This was provided by Chicken Bones (tModLoader). // GetEncoding behaves differently on .NET Core and even between .NET Framework versions, // which is why this test only applies to Mono, preferably on Linux to verify if flagging // regions of code as read-writable and then read-executable works for AOT'd code. #if false using (Hook h = new Hook( typeof(Encoding).GetMethod("GetEncoding", new Type[] { typeof(string) }), new Func <Func <string, Encoding>, string, Encoding>((orig, name) => { if (name == "IBM437") { return(null); } return(orig(name)); }) )) { Assert.Null(Encoding.GetEncoding("IBM437")); } #endif // This was provided by a Harmony user. // TextWriter's methods (including all overrides) were unable to be hooked on some runtimes. // FIXME: .NET 5 introduces similar behavior for macOS and Linux, but RD isn't ready for that. See DetourRuntimeNETPlatform for more info. #if true using (MemoryStream ms = new MemoryStream()) { using (StreamWriter writer = new StreamWriter(ms, Encoding.UTF8, 1024, true)) { // In case anyone needs to debug this mess anytime in the future ever again: /*/ * MethodBase m = typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) }); * Console.WriteLine($"meth: 0x{(long) m?.MethodHandle.Value:X16}"); * Console.WriteLine($"getf: 0x{(long) m?.MethodHandle.GetFunctionPointer():X16}"); * Console.WriteLine($"fptr: 0x{(long) m?.GetLdftnPointer():X16}"); * Console.WriteLine($"nats: 0x{(long) m?.GetNativeStart():X16}"); * /**/ // Debugger.Break(); writer.Write("A"); using (Hook h = new Hook( typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) }), new Action <Action <StreamWriter, string>, StreamWriter, string>((orig, self, value) => { orig(self, "-"); }) )) { // Debugger.Break(); writer.Write("B"); } writer.Write("C"); } ms.Seek(0, SeekOrigin.Begin); using (StreamReader reader = new StreamReader(ms, Encoding.UTF8, false, 1024, true)) { Assert.Equal("A-C", reader.ReadToEnd()); } } #endif #if NETFRAMEWORK && true Assert.Equal("A", new SqlCommand("A").CommandText); using (Hook h = new Hook( typeof(SqlCommand).GetConstructor(new Type[] { typeof(string) }), new Action <Action <SqlCommand, string>, SqlCommand, string>((orig, self, value) => { orig(self, "-"); }) )) { Assert.Equal("-", new SqlCommand("B").CommandText); } Assert.Equal("C", new SqlCommand("C").CommandText); #endif // This was provided by tModLoader. // The .NET Framework codepath failed on making the method writable the for a single user. #if NETFRAMEWORK && true try { throw new Exception(); } catch (Exception e) { Assert.NotEqual("", e.StackTrace.Trim()); } using (Hook h = Type.GetType("Mono.Runtime") != null ? // Mono new Hook( typeof(Exception).GetMethod("GetStackTrace", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance), new Func <Func <Exception, bool, string>, Exception, bool, string>((orig, self, fNeedFileInfo) => { return(""); }) ) : // .NET new Hook( typeof(StackTrace).GetConstructor(new[] { typeof(Exception), typeof(bool) }), new Action <Action <StackTrace, Exception, bool>, StackTrace, Exception, bool>((orig, self, e, fNeedFileInfo) => { orig(self, e, fNeedFileInfo); DynamicData.Set(self, new { frames = new StackFrame[0], m_iNumOfFrames = 0, m_iMethodsToSkip = 0 }); }) )) { try { throw new Exception(); } catch (Exception e) { Assert.Equal("", e.StackTrace.Trim()); } } try { throw new Exception(); } catch (Exception e) { Assert.NotEqual("", e.StackTrace.Trim()); } #endif } }