Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
scripting: enhanced stack trace support
  • Loading branch information
blattersturm committed Jul 15, 2019
1 parent 82f1b83 commit 98d7281
Show file tree
Hide file tree
Showing 27 changed files with 1,304 additions and 88 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Expand Up @@ -139,3 +139,6 @@
[submodule "vendor/thread-pool-cpp"]
path = vendor/thread-pool-cpp
url = https://github.com/inkooboo/thread-pool-cpp.git
[submodule "vendor/ben-demystifier"]
path = vendor/ben-demystifier
url = https://github.com/citizenfx/Ben.Demystifier.git
5 changes: 5 additions & 0 deletions code/client/clrcore/BaseScript.cs
Expand Up @@ -151,6 +151,11 @@ public static void TriggerClientEvent(string eventName, params object[] args)
[SecurityCritical]
private static void TriggerEventInternal(string eventName, byte[] argsSerialized, bool isRemote)
{
if (GameInterface.SnapshotStackBoundary(out var b))
{
InternalManager.ScriptHost.SubmitBoundaryEnd(b, b.Length);
}

var nativeHash = Hash.TRIGGER_EVENT_INTERNAL;

#if !IS_FXSERVER
Expand Down
8 changes: 8 additions & 0 deletions code/client/clrcore/GameInterface.cs
Expand Up @@ -19,6 +19,14 @@ internal static class GameInterface
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern ulong GetMemoryUsage();

[SecurityCritical]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern bool SnapshotStackBoundary(out byte[] data);

[SecurityCritical]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern bool WalkStackBoundary(string resourceName, byte[] start, byte[] end, out byte[] blob);

[SecurityCritical]
[DllImport("CoreRT", EntryPoint = "CoreFxCreateObjectInstance")]
public static extern int CreateObjectInstance([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid, out IntPtr objectPtr);
Expand Down
6 changes: 6 additions & 0 deletions code/client/clrcore/IScriptHost.cs
Expand Up @@ -26,6 +26,12 @@ internal interface IScriptHost
[MethodImpl(MethodImplOptions.InternalCall)]
[PreserveSig]
void ScriptTrace([MarshalAs(UnmanagedType.LPStr)] string message);

[MethodImpl(MethodImplOptions.InternalCall)]
void SubmitBoundaryStart([In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] boundaryData, int boundarySize);

[MethodImpl(MethodImplOptions.InternalCall)]
void SubmitBoundaryEnd([In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] boundaryData, int boundarySize);
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
Expand Down
22 changes: 21 additions & 1 deletion code/client/clrcore/IScriptTickRuntime.cs
Expand Up @@ -8,4 +8,24 @@ public interface IScriptTickRuntime
{
void Tick();
}
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("182CAAF3-E33D-474B-A6AF-33D59FF0E9ED")]
[ComImport]
public interface IScriptStackWalkVisitor
{
void SubmitStackFrame([In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] frameBlob, int frameBlobSize);
}

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("567D2FDA-610C-4FA0-AE3E-4F700AE5CE56")]
public interface IScriptStackWalkingRuntime
{
void WalkStack(
[In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] boundaryStart,
[In] int boundaryStartLength,
[In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] byte[] boundaryEnd,
[In] int boundaryEndLength,
[In] IScriptStackWalkVisitor visitor);
}
}
141 changes: 125 additions & 16 deletions code/client/clrcore/InternalManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
Expand All @@ -19,6 +20,8 @@ class InternalManager : MarshalByRefObject, InternalManagerInterface
private static readonly List<Tuple<DateTime, AsyncCallback>> ms_delays = new List<Tuple<DateTime, AsyncCallback>>();
private static int ms_instanceId;

private string m_resourceName;

public static IScriptHost ScriptHost { get; internal set; }

// actually, domain-global
Expand All @@ -43,6 +46,11 @@ public InternalManager()
#endif
}

public void SetResourceName(string resourceName)
{
m_resourceName = resourceName;
}

[SecuritySafeCritical]
public void SetScriptHost(IScriptHost host, int instanceId)
{
Expand Down Expand Up @@ -207,6 +215,14 @@ static Assembly LoadAssemblyInternal(string baseName, bool useSearchPaths = fals
return null;
}

[SecuritySafeCritical]
public byte[] WalkStack(byte[] boundaryStart, byte[] boundaryEnd)
{
var success = GameInterface.WalkStackBoundary(m_resourceName, boundaryStart, boundaryEnd, out var blob);

return (success) ? blob : null;
}

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return LoadAssemblyInternal(args.Name.Split(',')[0], useSearchPaths: true);
Expand All @@ -222,8 +238,14 @@ public static void TickGlobal()
GlobalManager.Tick();
}

[SecuritySafeCritical]
public void Tick()
{
if (GameInterface.SnapshotStackBoundary(out var b))
{
ScriptHost.SubmitBoundaryStart(b, b.Length);
}

try
{
ScriptContext.GlobalCleanUp();
Expand Down Expand Up @@ -251,12 +273,18 @@ public void Tick()
}
catch (Exception e)
{
Debug.WriteLine("Error during Tick: {0}", e.ToString());
PrintError("tick", e);
}
}

[SecuritySafeCritical]
public void TriggerEvent(string eventName, byte[] argsSerialized, string sourceString)
{
if (GameInterface.SnapshotStackBoundary(out var bo))
{
ScriptHost.SubmitBoundaryStart(bo, bo.Length);
}

try
{
var obj = MsgPackDeserializer.Deserialize(argsSerialized, netSource: (sourceString.StartsWith("net") ? sourceString : null)) as List<object> ?? (IEnumerable<object>)new object[0];
Expand Down Expand Up @@ -285,7 +313,7 @@ public void TriggerEvent(string eventName, byte[] argsSerialized, string sourceS
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
PrintError($"event ({eventName})", e);
}
}

Expand All @@ -306,30 +334,45 @@ public static string CanonicalizeRef(int refId)
[SecuritySafeCritical]
public void CallRef(int refIndex, byte[] argsSerialized, out IntPtr retvalSerialized, out int retvalSize)
{
var retvalData = FunctionReference.Invoke(refIndex, argsSerialized);
if (GameInterface.SnapshotStackBoundary(out var b))
{
ScriptHost.SubmitBoundaryStart(b, b.Length);
}

if (retvalData != null)
try
{
if (m_retvalBuffer == IntPtr.Zero)
{
m_retvalBuffer = Marshal.AllocHGlobal(32768);
m_retvalBufferSize = 32768;
}
var retvalData = FunctionReference.Invoke(refIndex, argsSerialized);

if (m_retvalBufferSize < retvalData.Length)
if (retvalData != null)
{
m_retvalBuffer = Marshal.ReAllocHGlobal(m_retvalBuffer, new IntPtr(retvalData.Length));
}
if (m_retvalBuffer == IntPtr.Zero)
{
m_retvalBuffer = Marshal.AllocHGlobal(32768);
m_retvalBufferSize = 32768;
}

if (m_retvalBufferSize < retvalData.Length)
{
m_retvalBuffer = Marshal.ReAllocHGlobal(m_retvalBuffer, new IntPtr(retvalData.Length));
}

Marshal.Copy(retvalData, 0, m_retvalBuffer, retvalData.Length);
Marshal.Copy(retvalData, 0, m_retvalBuffer, retvalData.Length);

retvalSerialized = m_retvalBuffer;
retvalSize = retvalData.Length;
retvalSerialized = m_retvalBuffer;
retvalSize = retvalData.Length;
}
else
{
retvalSerialized = IntPtr.Zero;
retvalSize = 0;
}
}
else
catch (Exception e)
{
retvalSerialized = IntPtr.Zero;
retvalSize = 0;

PrintError($"reference call", e.InnerException ?? e);
}
}

Expand Down Expand Up @@ -371,6 +414,47 @@ public override object InitializeLifetimeService()
return null;
}

[SecuritySafeCritical]
private void PrintError(string where, Exception what)
{
ScriptHost.SubmitBoundaryEnd(null, 0);

var stackTrace = new StackTrace(what, true);
var frames = stackTrace.GetFrames()
.Select(a => new
{
Frame = a,
Method = a.GetMethod(),
Type = a.GetMethod()?.DeclaringType
})
.Where(a => a.Method != null && (!a.Type.Assembly.GetName().Name.Contains("mscorlib") && !a.Type.Assembly.GetName().Name.Contains("CitizenFX.Core") && !a.Type.Assembly.GetName().Name.StartsWith("System")))
.Select(a => new
{
name = EnhancedStackTrace.GetMethodDisplayString(a.Method).ToString(),
sourcefile = a.Frame.GetFileName() ?? "",
line = a.Frame.GetFileLineNumber(),
file = $"@{m_resourceName}/{a.Type?.Assembly.GetName().Name ?? "UNK"}.dll"
});

var serializedFrames = MsgPackSerializer.Serialize(frames);
var formattedStackTrace = FormatStackTrace(serializedFrames);

if (formattedStackTrace != null)
{
Debug.WriteLine($"^1SCRIPT ERROR in {where}: {what.GetType().FullName}: {what.Message}^7");
Debug.WriteLine("{0}", formattedStackTrace);
}
}

[SecurityCritical]
private unsafe string FormatStackTrace(byte[] serializedFrames)
{
fixed (byte* ptr = serializedFrames)
{
return Native.Function.Call<string>((Native.Hash)0xd70c3bca, ptr, serializedFrames.Length);
}
}

private class DirectScriptHost : IScriptHost
{
private IntPtr hostPtr;
Expand All @@ -380,6 +464,8 @@ private class DirectScriptHost : IScriptHost
private FastMethod<Func<IntPtr, IntPtr, IntPtr, int>> openHostFileMethod;
private FastMethod<Func<IntPtr, int, int, IntPtr, int>> canonicalizeRefMethod;
private FastMethod<Action<IntPtr, IntPtr>> scriptTraceMethod;
private FastMethod<Action<IntPtr, IntPtr, int>> submitBoundaryStartMethod;
private FastMethod<Action<IntPtr, IntPtr, int>> submitBoundaryEndMethod;

[SecuritySafeCritical]
public DirectScriptHost(IntPtr hostPtr)
Expand All @@ -391,6 +477,8 @@ public DirectScriptHost(IntPtr hostPtr)
openHostFileMethod = new FastMethod<Func<IntPtr, IntPtr, IntPtr, int>>(nameof(openHostFileMethod), hostPtr, 2);
canonicalizeRefMethod = new FastMethod<Func<IntPtr, int, int, IntPtr, int>>(nameof(canonicalizeRefMethod), hostPtr, 3);
scriptTraceMethod = new FastMethod<Action<IntPtr, IntPtr>>(nameof(scriptTraceMethod), hostPtr, 4);
submitBoundaryStartMethod = new FastMethod<Action<IntPtr, IntPtr, int>>(nameof(submitBoundaryStartMethod), hostPtr, 5);
submitBoundaryEndMethod = new FastMethod<Action<IntPtr, IntPtr, int>>(nameof(submitBoundaryEndMethod), hostPtr, 6);
}

[SecuritySafeCritical]
Expand Down Expand Up @@ -495,6 +583,27 @@ private unsafe void ScriptTraceInternal(string message)
scriptTraceMethod.method(hostPtr, new IntPtr(p));
}
}

[SecuritySafeCritical]
public void SubmitBoundaryStart(byte[] boundaryData, int boundarySize)
{
SubmitBoundaryInternal(submitBoundaryStartMethod, boundaryData, boundarySize);
}

[SecuritySafeCritical]
public void SubmitBoundaryEnd(byte[] boundaryData, int boundarySize)
{
SubmitBoundaryInternal(submitBoundaryEndMethod, boundaryData, boundarySize);
}

[SecurityCritical]
private unsafe void SubmitBoundaryInternal(FastMethod<Action<IntPtr, IntPtr, int>> method, byte[] boundaryData, int boundarySize)
{
fixed (byte* p = boundaryData)
{
method.method(hostPtr, new IntPtr(p), boundarySize);
}
}
}
}
}
4 changes: 4 additions & 0 deletions code/client/clrcore/InternalManagerInterface.cs
Expand Up @@ -4,6 +4,8 @@ namespace CitizenFX.Core
{
interface InternalManagerInterface
{
void SetResourceName(string resourceName);

void SetScriptHost(IScriptHost host, int instanceId);

void CreateAssembly(string name, byte[] assemblyData, byte[] symbolData);
Expand All @@ -21,5 +23,7 @@ interface InternalManagerInterface
void RemoveRef(int refIndex);

ulong GetMemoryUsage();

byte[] WalkStack(byte[] boundaryStart, byte[] boundaryEnd);
}
}

0 comments on commit 98d7281

Please sign in to comment.