-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Export Stalls #2
Comments
I took a quick look through the code, and I saw an issue that could be causing stalls: I also noticed that the managed delegate for The zombie process could probably be dealt with by terminating the Unity process from the main console app when it closes. I'm not sure about how other issues could be debugged though. |
Thanks for the help with Produce and GenerateTypeTree. Debugging with logging to console had narrowed it down to something to do with object produce, I wasn't aware I had screwed up GenerateTypeTree too. The question was mostly about debugging, I'm not very experienced with C# interop, so I expect to make a bunch of dumb mistakes like that, and debugging is a bit harder without the stacktraces unity was giving when it crashed. I'll look into it a bit more and see if I can come up with a good solution. |
I did some cursory searching, and apparently adding Edit: According to Unity documentation, the parameter is |
Ah, I had assumed that it was automatically writing logs to After fixing the log path and the Object::Produce signature, the log shows:
and stalls while calling DestroyImmediate. It seems it doesn't like creating and destroying objects from outside the main thread. Also passing ObjectCreationMode.FromNonMainThread to Object::Produce changes the log to this.
|
Yeah, you need to create an instance ID yourself if you want to create an object outside of the main thread. I think there's a function for that somewhere in the code, I just don't know the signature. (Edit: It's For destroying objects outside of the main thread, we might be able to make use of the dummy project. Unity has an Edit: Alternatively, we could hook the update loop with EasyHook, which would place us on the main thread. It looks like |
I was going to suggest trying to use -executeMethod to trigger the main thread to somehow call into |
The Edit: I tried printing the result of |
Something interesting to note is that using Edit: It seems DIA does not like being used from multiple threads. If I create a completely new DIA session on the main thread, there are no hangs. So it might be worth turning the symbol resolver and DIA-related fields into |
Hmm, I wonder if dbghelp.dll has the same issue. The thread issue probably isn't big enough problem to switch over even if it doesn't. |
I just pushed two commits that fix cross-thread usage of |
I tried calling Moving the dumping logic to the AfterEverythingLoaded callback fixes the stall. The output is not valid, but i'm still looking into that |
This is caused by an incorrect P/Invoke signature. The default behaviour for [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.U1)]
delegate bool IsMainThreadDelegate(); I think I've actually made this mistake on the |
Regarding the export stall with Unity 5.6.7, I created a c++ dumper to help with debugging https://github.com/spacehamster/NativeTypeTreeDumper. TypeTreeDumper Editor.log The stacktrace looks like this
and some testing shows that setting MemLabelId.Identifier to 0x32 stops the stall in TypeTreeDumper and stops the crash in the c++ dumper. An identifier of 0x32 is equivalent to |
I wonder if there's a way for us to still have a native stack trace in managed code. I can't imagine it's easy though, and probably requires a bunch of managed<->unmanaged hopping around. |
I'm looking at how to handle STL strings for versions 5.4 and lower. basic_string::c_str has a slightly different signature in each version.
5.3:
5.2:
Etcetera, so I think symbol resolver needs to be extended to support finding symbols that match a prefix (starting with |
public partial class SymbolResolver
{
public abstract string[] FindSymbolsWithPrefix(string prefix);
} I think an API like this would be fine. There can also be a few helper methods such as: public partial class SymbolResolver
{
public T* ResolveFirstWithPrefix<T>(string prefix) where T : unmanaged;
public T ResolveFirstFunctionWithPrefix<T>(string prefix) where T : Delegate;
} So then we could just do: resolver.ResolveFirstFunctionWithPrefix<CStrDelegate>("?c_str@?$basic_string@") |
Just pushed support for this on master. Since DIA supports Regex, I changed the API design a little bit to reflect that.
resolver.ResolveFirstFunctionMatching<CStrDelegate>(new Regex(@"\?c_str@\?\$basic_string@*")); |
This line doesn't print out the exception
but if you change it to |
That's strange, I wonder if it's because it's being set to |
Hm, I still see exceptions printed out even with |
I just add void ExecuteDumper()
{
throw new UnresolvedSymbolException("Missing Symbol Name Here");
var GetUnityVersion = resolver.ResolveFunction<GetUnityVersionDelegate>("?GameEngineVersion@PlatformWrapper@UnityEngine@@SAPEBDXZ");
var ParseUnityVersion = resolver.ResolveFunction<UnityVersionDelegate>("??0UnityVersion@@QEAA@PEBD@Z");
ParseUnityVersion(out UnityVersion version, Marshal.PtrToStringAnsi(GetUnityVersion()));
Dumper.Execute(new UnityEngine(version, resolver), server.OutputDirectory);
} It also appears that if I change UnresolvedSymbolException to System.Exception, it prints like normal. |
I can confirm that a wrapper TextWriter fixes the issue. Fixed in 81d2e56. |
Do you know why it effects UnresolvedSymbolException but not System.Exception? |
That's because |
Remote hooking Unity 4.7 doesn't work, a blank console pops up and nothing happens. I'm guessing it is because it is a 32 bit app? I believe 5.0 onwards are 64 bit. |
That's probably why, yeah. It would probably work if you enable |
The most recent commit on master should now work |
WIth Unity 4.7, ClassIDToRTTI always returns null. I've not been able to figure out why. Also, side note, AfterEverythingLoaded was changed to a __thiscall in 4.7. |
It might require some investigation in Ghidra.
Thanks for the heads up, I haven't verified most of the calling conventions since it only applies to x86 and not x64. |
The function seems really simple /* public: static struct Object::RTTI * __cdecl Object::ClassIDToRTTI(int) */
RTTI * __cdecl ClassIDToRTTI(int param_1)
{
_Tree<class_std::_Tmap_traits<int,struct_Object::RTTI,struct_std::less<int>,class_stl_allocator<struct_std::pair<int_const_,struct_Object::RTTI>,1,4>,0>_>
*p_Var1;
_Tree_iterator<std::_Tree_val<std::_Tmap_traits<int,Object::RTTI,std::less<int>,stl_allocator<std::pair<intconst,Object::RTTI>,1,4>,0>>>
i;
p_Var1 = gRTTI;
find(gRTTI,(int *)&i);
if (i == *(
_Tree_iterator<std::_Tree_val<std::_Tmap_traits<int,Object::RTTI,std::less<int>,stl_allocator<std::pair<intconst,Object::RTTI>,1,4>,0>>>
*)(p_Var1 + 4)) {
return (RTTI *)0x0;
}
return (RTTI *)((int)i + 0x10);
} (BTW i renamed gRTTI, it doesn't have a symbol associated with it) My first guess was that gRTTI was not being initialized, so I tried calling RegisterAllClasses and InitializeAllClasses, but that does not help (it warns that it can't register classes multiple times). Changing the entry point to InitializeEngineNoGraphics also didn't help |
I wanted to use some of the code in other projects, is it possible to add a license? |
Sure, I'll add the MIT license to the repo |
Done a7657a1 |
Here's a JSON file that maps Unity version and build numbers to directory names under Interestingly, this reveals that it only contains PDBs for Unity 2018.1 through to Unity 2019.1. Generating these JSON files is a long process, so I'm still working on generating one for |
That matches up with what I experienced. I'm looking at adding dumping support for versions older then 3.4-4.7, but i'm having a bit of trouble. The TypeTree format has changed so there are no TypeTreeNodes any more. The main issue is that TypeTree children are stored in an std::list, but it seems that all of the std::list functions are inlined, so there are no suitable functions to call. I had planned to use a TypeTreeIterator to access children instead, but it appears that was only introduced in 4.7, so the 3.4-4.6 would still be unsolved. I suspect even if you used c++ to access it with the std::list functions like this does https://gist.github.com/robert-nix/7db0145e809b692b63f2#file-unitystructgen-cpp-L72, it would not work unless you used the same version of the standard library runtime that unity compiled against. |
Yeah, I've been working on a refactoring of some of the engine interop code and noticed the usage of |
Another idea I had considered was to try and reimplment the std::list standard library functions in C#, but I did not have much success when I tried that idea for std::string.c_str, so i'm not especially optimistic about that approach. |
I considered doing that, but I think for now it's very much a last resort approach. It'd be nice to have a managed implementation at some point, but I'd like to exhaust the simpler options first. |
There is a function |
Since the API exposed by the generated Dia2Lib is messy and doesn't expose some things properly, I've been working on a replacement for it: https://github.com/DaZombieKiller/Dia2. It has XML documentation comments for the entire public API surface based on the DIA SDK Reference. I haven't finished porting everything across though, so it still depends on Dia2Lib for now. I'm currently using this in a GUI program for viewing the contents of PDB files (and for generating C++ and C# structures from them) which I'll also put up on GitHub once it's ready. Ideally we should be able to turn the I also noticed that Ghidra has support for importing |
I've been playing around with the older versions of unity, and have had some success accessing TypeTreeLists using Visual Studio 2019 (v142) like this: #define _ITERATOR_DEBUG_LEVEL 0
#include <string>
#include <list>
#define EXPORT extern "C" __declspec(dllexport)
struct TypeTree;
template <class T = char>
class stl_allocator : public std::allocator<T>
{
public:
template<class _Other>
struct rebind
{
typedef stl_allocator_new<_Other> other;
};
stl_allocator_new()
{
}
stl_allocator_new(const std::allocator<T>&)
{
}
};
typedef std::list<TypeTree, stl_allocator<TypeTree>> TypeTreeList;
typedef std::basic_string<char, std::char_traits<char>, stl_allocator<char>> TypeTreeString;
struct TypeTree
{
TypeTreeList m_Children;
int _dummy1;
int _dummy2;
TypeTree* m_Father;
TypeTreeString m_Type;
int _dummy3;
TypeTreeString m_Name;
int _dummy4;
int m_ByteSize;
int m_Index;
int m_IsArray;
int m_Version;
int m_MetaFlag;
int m_ByteOffset;
void* m_DirectPtr;
};
EXPORT const char* StringToCStr(std::string* str) {
return str->c_str();
}
EXPORT unsigned int ListSize(TypeTreeList* list) {
return list->size();
}
EXPORT TypeTreeNew& ListGet(TypeTreeList* list, int index) {
int i = 0;
for (auto it = list->begin(); it != list->end(); it++)
{
if (i == index) {
return *it;
}
i++;
}
TypeTreeNew result;
return result;
} csharp [DllImport("StlHelper", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr StringToCStr(void* str);
[DllImport("StlHelper", CallingConvention = CallingConvention.Cdecl)]
internal static extern int ListSize(void* list);
[DllImport("StlHelper", CallingConvention = CallingConvention.Cdecl)]
internal static extern TypeTree* ListGet(void* list, int index); It doesn't work when i add TypeTreeList seems to be implemented as a doubly linked list, and i think it would be cleaner to access it purely from c#, like this unsafe struct TypeTreeList
{
public TypeTreeListNode* Head;
public uint Size;
public int Padding1;
public int Padding2;
}
unsafe struct TypeTreeListNode
{
public TypeTreeNode* Next;
public TypeTreeNode* Prev;
public TypeTree Value;
} |
Did some more testing, and was able to successfully walk the tree without the c++ helpers like this public void CreateNodes(ManagedTypeTree owner, ref List<TypeTreeNode> nodes, ref TypeTree tree, int level = 0)
{
var typeIndex = GetOrCreateStringIndex(tree.m_Type);
var nameIndex = GetOrCreateStringIndex(tree.m_Name);
var nodeImpl = new TypeTreeNode.V1(
version: (short)tree.m_Version,
level: (byte)level,
typeFlags: (TypeFlags)tree.m_IsArray,
typeStrOffset: typeIndex,
nameStrOffset: nameIndex,
byteSize: tree.m_ByteSize,
index: tree.m_Index,
metaFlag: tree.m_MetaFlag);
nodes.Add(new TypeTreeNode(nodeImpl, owner));
TypeTreeListNode* node = tree.m_Children.Head;
for(int i = 0; i < tree.m_Children.Size; i++)
{
node = node->Next;
TypeTree child = node->Value;
CreateNodes(owner, ref nodes, ref child, level + 1);
}
} I think accessing it from c# is the cleanest solution |
Nice! So is this for Unity 3.4? Or is there an even earlier version available on Windows?
I agree, it also keeps the build system simple since we don't need to include a C++ project. |
I've only tested this in 4.5, but i'm hoping the TypeTreeList layout will be the same with 3.4 and the same method will work there too. I haven't been able to find any older versions of unity then 3.4 available. |
Here's part of the code for the 3.4 TypeTree from the refactor I've been working on: using System;
namespace Unity.V3_4
{
public unsafe partial struct TypeTree
{
public CppList Children;
public TypeTree* Father;
public CppString Type;
public CppString Name;
public int ByteSize;
public int Index;
public int IsArray;
public int Version;
public TransferMetaFlags MetaFlag;
public int ByteOffset;
public IntPtr DirectPtr;
}
} Does that look similar? |
yes internal struct TypeTreeString
{
fixed byte Data[28];
};
internal unsafe struct TypeTreeList
{
public TypeTreeListNode* Head;
public uint Size;
public int Padding1;
public int Padding2;
};
internal unsafe struct TypeTreeListNode
{
public TypeTreeListNode* Next;
public TypeTreeListNode* Prev;
public TypeTree Value;
}
internal struct TypeTree
{
public TypeTreeList m_Children;
public TypeTree* m_Father;
public TypeTreeString m_Type;
public TypeTreeString m_Name;
public int m_ByteSize;
public int m_Index;
public int m_IsArray;
public int m_Version;
public TransferMetaFlags m_MetaFlag;
public int m_ByteOffset;
public void* m_DirectPtr;
} |
Also TypeTreeString's implementation seems to be identical to this https://stackoverflow.com/questions/40393350/why-does-microsofts-implementation-of-stdstring-require-40-bytes-on-the-stack (except for the 4 bytes of padding at the end that appears unused) |
How are you running TypeTreeDumper on the 3.4 player? Are you building a custom .exe or can you run |
I just run it on |
Ah, yeah. You need to build a small dummy game so that the player will boot. |
I got a proof of concept for hooking to work in 3.5 like this [SuppressMessage("Style", "IDE0060", Justification = "Required by EasyHook")]
public void Run(RemoteHooking.IContext context, EntryPointArgs args){
...snip...
OnEngineInitialized += ExecuteDumper;
RemoteHooking.WakeUpProcess();
if (VersionInfo.FileMajorPart == 3)
{
HookAfterEverythingLoadLegacy();
}
Thread.Sleep(Timeout.Infinite);
...snip...
}
unsafe void HookAfterEverythingLoadLegacy()
{
Console.WriteLine("Trying to hook AfterEverythingLoaded");
resolver.TryResolveFirstMatching(new Regex(Regex.Escape("?AfterEverythingLoaded@Application@") + "*"), out var address);
byte* buffer = (byte*)address;
byte[] matchingCodes = new byte[] {
0x55, 0x8B, 0xEC, 0x6A, 0xFF, 0x68, 0xF8, 0xED
};
//Wait for target to be decrypted
bool validCodes = false;
while (!validCodes)
{
bool foundMatch = true;
for(int i = 0; i < matchingCodes.Length; i++)
{
if (buffer[i] != matchingCodes[i]) foundMatch = false;
}
validCodes = foundMatch;
}
Console.WriteLine("Hooking AfterEverythingLoaded");
AfterEverythingLoadedHook = LocalHook.Create(address, new AfterEverythingLoadedDelegate(AfterEverythingLoaded), null);
AfterEverythingLoadedHook.ThreadACL.SetExclusiveACL(Array.Empty<int>());
Console.WriteLine("Hooked AfterEverythingLoaded");
} This method requires you to know what the valid function bytes are in advanced, but suspect you might be able to just watch for changed bytes instead of valid bytes. For reverse engineering, you can dump the process from memory and feed that into IDA. I tried with ghidra, but it ghidra refuses to load the associated .pdb if its not an exact match to the .exe. |
I'm wondering which approach you prefer, dumping the 3.5 editor or dumping the 3.5 player? |
Dumping from the editor is more consistent, so in general I think it's nice to support it. Ideally we can support both, so I think it's a good idea to implement your solution. |
For initializing the AfterEverythingLoadCallback, do you prefer the commandline I believe that by the time AfterEverythingLoad is called, it should be safe to hook functions like normal |
Yeah, the |
I did a bit of thinking and figured out that we can pass data from the dumper to the editor by using process environment variables. I've replaced the template string approach with this in ddec84f. |
Just wanted to mention that this experimental build of Unity 2020.2 contains a completely unstripped PDB: https://forum.unity.com/threads/experimental-contacts-modification-api.924809/#post-6427604 |
I've started to implement exporting here https://github.com/spacehamster/TypeTreeDumper/tree/working, and issues with code that I would have expected to cause a hard crash cause the process to stall. Closing the terminal leaves a zombie unity process alive. There are no stack traces logged in Editor.log either. I wonder if there are good ways to debug issues like that?
The text was updated successfully, but these errors were encountered: