From 18e7d1baa36f85e8a1eeecb83ab92f0ef8007c55 Mon Sep 17 00:00:00 2001 From: ElPsyCongree <345036769@qq.com> Date: Fri, 16 Nov 2018 18:43:36 +0800 Subject: [PATCH 1/9] lua profiler --- .gitignore | 2 + Assets/Slua/LuaProfiler.meta | 8 + Assets/Slua/LuaProfiler/Editor.meta | 8 + Assets/Slua/LuaProfiler/Editor/TreeView.meta | 8 + .../Editor/TreeView/LuaProfilerTreeView.cs | 495 ++++++++++ .../TreeView/LuaProfilerTreeView.cs.meta | 11 + .../Editor/TreeView/LuaProfilerWindow.cs | 131 +++ .../Editor/TreeView/LuaProfilerWindow.cs.meta | 12 + Assets/Slua/LuaProfiler/Parse.meta | 8 + Assets/Slua/LuaProfiler/Parse/Common.cs | 128 +++ Assets/Slua/LuaProfiler/Parse/Common.cs.meta | 8 + Assets/Slua/LuaProfiler/Parse/LLex.cs | 923 ++++++++++++++++++ Assets/Slua/LuaProfiler/Parse/LLex.cs.meta | 8 + Assets/Slua/LuaProfiler/Parse/Parse.cs | 191 ++++ Assets/Slua/LuaProfiler/Parse/Parse.cs.meta | 11 + Assets/Slua/LuaProfiler/Parse/Util.cs | 225 +++++ Assets/Slua/LuaProfiler/Parse/Util.cs.meta | 8 + Assets/Slua/LuaProfiler/UnityHook.meta | 8 + .../Slua/LuaProfiler/UnityHook/HookerPool.cs | 31 + .../LuaProfiler/UnityHook/HookerPool.cs.meta | 11 + Assets/Slua/LuaProfiler/UnityHook/LDasm.cs | 877 +++++++++++++++++ .../Slua/LuaProfiler/UnityHook/LDasm.cs.meta | 11 + .../LuaProfiler/UnityHook/MethodHooker.cs | 268 +++++ .../UnityHook/MethodHooker.cs.meta | 11 + Assets/Slua/LuaProfiler/UnityHook/StarUp.cs | 54 + .../Slua/LuaProfiler/UnityHook/StarUp.cs.meta | 13 + Assets/Slua/LuaProfiler/Utils.meta | 8 + .../Utils/LuaDeepProfilerSetting.cs | 85 ++ .../Utils/LuaDeepProfilerSetting.cs.meta | 13 + Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs | 868 ++++++++++++++++ .../LuaProfiler/Utils/LuaProfiler.cs.meta | 13 + Assets/Slua/LuaProfiler/Utils/ObjectPool.cs | 170 ++++ .../Slua/LuaProfiler/Utils/ObjectPool.cs.meta | 12 + 33 files changed, 4638 insertions(+) create mode 100644 Assets/Slua/LuaProfiler.meta create mode 100644 Assets/Slua/LuaProfiler/Editor.meta create mode 100644 Assets/Slua/LuaProfiler/Editor/TreeView.meta create mode 100644 Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs create mode 100644 Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs.meta create mode 100644 Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs create mode 100644 Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs.meta create mode 100644 Assets/Slua/LuaProfiler/Parse.meta create mode 100644 Assets/Slua/LuaProfiler/Parse/Common.cs create mode 100644 Assets/Slua/LuaProfiler/Parse/Common.cs.meta create mode 100644 Assets/Slua/LuaProfiler/Parse/LLex.cs create mode 100644 Assets/Slua/LuaProfiler/Parse/LLex.cs.meta create mode 100644 Assets/Slua/LuaProfiler/Parse/Parse.cs create mode 100644 Assets/Slua/LuaProfiler/Parse/Parse.cs.meta create mode 100644 Assets/Slua/LuaProfiler/Parse/Util.cs create mode 100644 Assets/Slua/LuaProfiler/Parse/Util.cs.meta create mode 100644 Assets/Slua/LuaProfiler/UnityHook.meta create mode 100644 Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs create mode 100644 Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs.meta create mode 100644 Assets/Slua/LuaProfiler/UnityHook/LDasm.cs create mode 100644 Assets/Slua/LuaProfiler/UnityHook/LDasm.cs.meta create mode 100644 Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs create mode 100644 Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs.meta create mode 100644 Assets/Slua/LuaProfiler/UnityHook/StarUp.cs create mode 100644 Assets/Slua/LuaProfiler/UnityHook/StarUp.cs.meta create mode 100644 Assets/Slua/LuaProfiler/Utils.meta create mode 100644 Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs create mode 100644 Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs.meta create mode 100644 Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs create mode 100644 Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs.meta create mode 100644 Assets/Slua/LuaProfiler/Utils/ObjectPool.cs create mode 100644 Assets/Slua/LuaProfiler/Utils/ObjectPool.cs.meta diff --git a/.gitignore b/.gitignore index c2d01bd3..7869bf9f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ Assets/Slua/Meta/Resources/lua3rdmeta.asset Assets/Slua/Meta/Resources/lua3rdmeta.asset.meta /standalone/NUnit.Runners.2.6.4 +/Assets/LuaDeepProfilerSettings.asset +/Assets/LuaDeepProfilerSettings.asset.meta diff --git a/Assets/Slua/LuaProfiler.meta b/Assets/Slua/LuaProfiler.meta new file mode 100644 index 00000000..8bfd6dfa --- /dev/null +++ b/Assets/Slua/LuaProfiler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 029d8948c60ac254d801540d2ba0770f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Editor.meta b/Assets/Slua/LuaProfiler/Editor.meta new file mode 100644 index 00000000..bb665425 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 830ddb418ab883c4f84dc1f5824f969e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView.meta b/Assets/Slua/LuaProfiler/Editor/TreeView.meta new file mode 100644 index 00000000..65ada809 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Editor/TreeView.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ee04b2b4ef5a40c4b8300302016a18e4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs new file mode 100644 index 00000000..fa084757 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs @@ -0,0 +1,495 @@ +/* +* ============================================================================== +* Filename: LuaExport +* Created: 2018/7/13 14:29:22 +* Author: エル・プサイ・コングルゥ +* Purpose: +* ============================================================================== +*/ +using System; +using System.Collections.Generic; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace MikuLuaProfiler +{ +#if UNITY_5_6_OR_NEWER + #region item + [Serializable] + //The TreeElement data class is extended to hold extra data, which you can show and edit in the front-end TreeView. + public class LuaProfilerTreeViewItem : TreeViewItem + { + private static ObjectPool objectPool = new ObjectPool(30); + public static LuaProfilerTreeViewItem Create(LuaProfiler.Sample sample, int depth, LuaProfilerTreeViewItem father) + { + var dict = LuaProfilerTreeView.m_nodeDict; + + LuaProfilerTreeViewItem mt; + mt = objectPool.GetObject(); + mt.ResetBySample(sample, depth, father); + dict.Add(mt.fullName, mt); + + return mt; + } + public void Restore() + { + objectPool.Store(this); + } + + public int frameCalls { private set; get; } + private long[] _gc = new long[] { 0, 0, 0, 0 }; + //没什么意义,因为Lua 执行代码的同时 异步GC,所以导致GC的数字一直闪烁,用上这个去闪烁 + private long _showGC = 0; + public long showGC + { + private set + { + _showGC = value; + } + get + { + if (!UnityEditor.EditorApplication.isPlaying) + { + return _showGC; + } + + if (Time.frameCount == _frameCount) + { + return _showGC; + } + else { return 0; } + } + } + public long totalMemory { private set; get; } + public long totalTime { private set; get; } + public long averageTime { private set; get; } + public float currentTime { private set; get; } + public int totalCallTime { private set; get; } + + public string fullName + { + get; + private set; + } + public readonly List childs = new List(); + public LuaProfilerTreeViewItem father { private set; get; } + private int _frameCount; + public LuaProfilerTreeViewItem() + { + } + + public void ResetBySample(LuaProfiler.Sample sample, int depth, LuaProfilerTreeViewItem father) + { + if (sample != null) + { + totalMemory = sample.costGC; + totalTime = (long)(sample.costTime * 1000000); + displayName = sample.name; + fullName = sample.fullName; + } + else + { + totalMemory = 0; + totalTime = 0; + displayName = "root"; + fullName = "root"; + } + totalCallTime = 1; + averageTime = totalTime / totalCallTime; + + this.id = LuaProfilerTreeView.GetUniqueId(); + this.depth = depth; + + + childs.Clear(); + if (sample != null) + { + for (int i = 0, imax = sample.childs.Count; i < imax; i++) + { + var dict = LuaProfilerTreeView.m_nodeDict; + + LuaProfilerTreeViewItem mt; + var childSample = sample.childs[i]; + if (dict.TryGetValue(childSample.fullName, out mt)) + { + mt.AddSample(childSample); + } + else + { + var item = Create(sample.childs[i], depth + 1, this); + childs.Add(item); + } + } + } + this.father = father; + + _frameCount = Time.frameCount; + } + public void AddSample(LuaProfiler.Sample sample) + { + if (_frameCount == Time.frameCount) + { + _gc[3] += sample.costGC; + frameCalls += sample.oneFrameCall; + currentTime += sample.costTime; + } + else + { + _gc[0] = _gc[1]; + _gc[1] = _gc[2]; + _gc[2] = _gc[3]; + _gc[3] = sample.costGC; + frameCalls = sample.oneFrameCall; + currentTime = sample.costTime; + } + totalMemory += sample.costGC; + + totalTime += (long)(sample.costTime * 1000000); + totalCallTime += sample.oneFrameCall; + averageTime = totalTime / totalCallTime; + for (int i = 0, imax = sample.childs.Count; i < imax; i++) + { + LuaProfilerTreeViewItem childItem = null; + var sampleChild = sample.childs[i]; + if (LuaProfilerTreeView.m_nodeDict.TryGetValue(sampleChild.fullName, out childItem)) + { + childItem.AddSample(sampleChild); + } + else + { + var treeItem = Create(sampleChild, depth + 1, this); + childs.Add(treeItem); + } + } + //以下代码只不过为了 gc的显示数值不闪烁 + if (_gc[0] == _gc[1] || _gc[0] == _gc[2] || _gc[0] == _gc[3]) + { + showGC = _gc[0]; + } + else if (_gc[1] == _gc[2] || _gc[1] == _gc[3]) + { + showGC = _gc[1]; + } + else if (_gc[2] == _gc[3]) + { + showGC = _gc[2]; + } + else + { + showGC = _gc[3]; + } + _frameCount = Time.frameCount; + } + } + #endregion + + public class LuaProfilerTreeView : TreeView + { + #region pool + private static int _uniqueId = 0; + public static int GetUniqueId() + { + return _uniqueId++; + } + + private readonly List roots = new List(); + #endregion + + #region field + private readonly LuaProfilerTreeViewItem root; + private readonly List treeViewItems = new List(); + #endregion + public LuaProfilerTreeView(TreeViewState treeViewState, float width) + : base(treeViewState, CreateDefaultMultiColumnHeaderState(width)) + { + LuaProfiler.SetSampleEnd(LoadRootSample); + root = LuaProfilerTreeViewItem.Create(null, -1, null); + Reload(); + } + + private static MultiColumnHeader CreateDefaultMultiColumnHeaderState(float treeViewWidth) + { + var columns = new[] + { + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("Overview"), + contextMenuText = "Overview", + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + sortingArrowAlignment = TextAlignment.Right, + width = 500, + minWidth = 500, + maxWidth = 500, + autoResize = true, + canSort = false, + allowToggleVisibility = true + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("totalMemory"), + contextMenuText = "totalMemory", + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + sortingArrowAlignment = TextAlignment.Right, + width = 80, + minWidth = 80, + maxWidth = 80, + autoResize = true, + canSort = true, + allowToggleVisibility = false + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("currentTime"), + contextMenuText = "currentTime", + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + sortingArrowAlignment = TextAlignment.Right, + width = 120, + minWidth = 120, + maxWidth = 120, + autoResize = true, + canSort = true, + allowToggleVisibility = false + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("averageTime"), + contextMenuText = "averageTime", + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + sortingArrowAlignment = TextAlignment.Right, + width = 120, + minWidth = 120, + maxWidth = 120, + autoResize = true, + canSort = true, + allowToggleVisibility = false + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("totalTime"), + contextMenuText = "totalTime", + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + sortingArrowAlignment = TextAlignment.Right, + width = 120, + minWidth = 120, + maxWidth = 120, + autoResize = true, + canSort = true, + allowToggleVisibility = false + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("Now GC"), + contextMenuText = "Now GC", + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + sortingArrowAlignment = TextAlignment.Right, + width = 60, + minWidth = 60, + maxWidth = 60, + autoResize = true, + canSort = true, + allowToggleVisibility = false + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("totalCalls"), + contextMenuText = "totalCalls", + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + sortingArrowAlignment = TextAlignment.Right, + width = 120, + minWidth = 120, + maxWidth = 120, + autoResize = true, + canSort = true, + allowToggleVisibility = false + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("Now Calls"), + contextMenuText = "Now Calls", + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + sortingArrowAlignment = TextAlignment.Right, + width = 80, + minWidth = 80, + maxWidth = 80, + autoResize = true, + canSort = true, + allowToggleVisibility = false + }, + }; + + var state = new MultiColumnHeaderState(columns); + return new MultiColumnHeader(state); + } + + public void Clear() + { + roots.Clear(); + m_nodeDict.Clear(); + treeViewItems.Clear(); + } + + protected override void DoubleClickedItem(int id) + { + /* + base.DoubleClickedItem(id); + var selectItem = FindItem(id, BuildRoot()); + string fileName = "/" + selectItem.displayName.Split(new char[] { ',' }, 2)[0].Replace(".", "/").Replace("/lua", ".lua").Trim(); + try + { + int line = 0; + int.TryParse(Regex.Match(selectItem.displayName, @"(?<=(line:))\d*(?=( ))").Value, out line); + //LocalToLuaIDE.OnOpenAsset(fileName, line); + } + catch + { + }*/ + } + + public static Dictionary m_nodeDict = new Dictionary(); + + private void LoadRootSample(LuaProfiler.Sample sample) + { + LuaProfilerTreeViewItem item; + string f = sample.fullName; + string f1 = sample.fullName; + if (m_nodeDict.TryGetValue(sample.fullName, out item)) + { + item.AddSample(sample); + } + else + { + item = LuaProfilerTreeViewItem.Create(sample, 0, null); + roots.Add(item); + } + } + + private void ReLoadTreeItems() + { + treeViewItems.Clear(); + List rootList = new List(roots); + int sortIndex = multiColumnHeader.sortedColumnIndex; + int sign = 0; + if (sortIndex > 0) + { + sign = multiColumnHeader.IsSortedAscending(sortIndex) ? -1 : 1; + } + switch (sortIndex) + { + case 1: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalMemory - b.totalMemory); }); break; + case 2: rootList.Sort((a, b) => { return sign * Math.Sign(a.currentTime - b.currentTime); }); break; + case 3: rootList.Sort((a, b) => { return sign * Math.Sign(a.averageTime - b.averageTime); }); break; + case 4: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalTime - b.totalTime); }); break; + case 5: rootList.Sort((a, b) => { return sign * Math.Sign(a.showGC - b.showGC); }); break; + case 6: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalCallTime - b.totalCallTime); }); break; + case 7: rootList.Sort((a, b) => { return sign * Math.Sign(a.frameCalls - b.frameCalls); }); break; + } + foreach (var item in rootList) + { + AddOneNode(item); + SortChildren(sortIndex, sign, item); + } + } + + private void SortChildren(int sortIndex, int sign, LuaProfilerTreeViewItem item) + { + if (item.childs != null && item.childs.Count > 0) + { + List rootList = item.childs; + switch (sortIndex) + { + case 1: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalMemory - b.totalMemory); }); break; + case 2: rootList.Sort((a, b) => { return sign * Math.Sign(a.currentTime - b.currentTime); }); break; + case 3: rootList.Sort((a, b) => { return sign * Math.Sign(a.averageTime - b.averageTime); }); break; + case 4: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalTime - b.totalTime); }); break; + case 5: rootList.Sort((a, b) => { return sign * Math.Sign(a.showGC - b.showGC); }); break; + case 6: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalCallTime - b.totalCallTime); }); break; + case 7: rootList.Sort((a, b) => { return sign * Math.Sign(a.frameCalls - b.frameCalls); }); break; + } + foreach (var t in rootList) + { + SortChildren(sortIndex, sign, t); + } + } + } + + private void AddOneNode(LuaProfilerTreeViewItem root) + { + treeViewItems.Add(root); + + m_nodeDict[root.fullName] = root; + if (root.children != null) + { + root.children.Clear(); + } + foreach (var item in root.childs) + { + AddOneNode(item); + } + } + + protected override TreeViewItem BuildRoot() + { + ReLoadTreeItems(); + + // Utility method that initializes the TreeViewItem.children and -parent for all items. + SetupParentsAndChildrenFromDepths(root, treeViewItems); + + // Return root of the tree + return root; + } + + protected override void RowGUI(RowGUIArgs args) + { + GUIStyle gs = new GUIStyle(); + gs.normal.textColor = Color.green; //new Color32(0, 200, 0, 255); + gs.alignment = TextAnchor.MiddleCenter; + + var item = (LuaProfilerTreeViewItem)args.item; + Rect r = args.rowRect; + + base.RowGUI(args); + + //r.x = r.x + 30; + //GUI.Label(r, item.displayName); + + r.x = r.x + 500; + r.width = 80; + GUI.Label(r, LuaProfiler.GetMemoryString(item.totalMemory), gs); + + r.x = r.x + 80; + r.width = 120; + GUI.Label(r, item.currentTime.ToString("f6") + "s", gs); + + r.x = r.x + 120; + r.width = 120; + GUI.Label(r, ((float)item.averageTime / 1000000).ToString("f6") + "s", gs); + + r.x = r.x + 120; + r.width = 120; + GUI.Label(r, ((float)item.totalTime / 1000000).ToString("f6") + "s", gs); + + r.x = r.x + 120; + r.width = 60; + GUI.Label(r, LuaProfiler.GetMemoryString(item.showGC), gs); + + r.x = r.x + 60; + r.width = 120; + GUI.Label(r, LuaProfiler.GetMemoryString(item.totalCallTime, ""), gs); + + r.x = r.x + 120; + r.width = 80; + GUI.Label(r, item.frameCalls.ToString(), gs); + + } + + } +#endif +} diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs.meta b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs.meta new file mode 100644 index 00000000..f95c182d --- /dev/null +++ b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d4ee9033653a2184da71af94f390339a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs new file mode 100644 index 00000000..a6f21ed7 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs @@ -0,0 +1,131 @@ +/* +* ============================================================================== +* Filename: LuaExport +* Created: 2018/7/13 14:29:22 +* Author: エル・プサイ・コングルゥ +* Purpose: +* ============================================================================== +*/ +using UnityEngine; +using UnityEditor.IMGUI.Controls; +using UnityEditor; + +namespace MikuLuaProfiler +{ +#if UNITY_5_6_OR_NEWER + public class LuaProfilerWindow : EditorWindow + { + [SerializeField] TreeViewState m_TreeViewState; + + LuaProfilerTreeView m_TreeView; + SearchField m_SearchField; + + void OnEnable() + { + if (m_TreeViewState == null) + m_TreeViewState = new TreeViewState(); + + m_TreeView = new LuaProfilerTreeView(m_TreeViewState, position.width - 40); + m_SearchField = new SearchField(); + m_SearchField.downOrUpArrowKeyPressed += m_TreeView.SetFocusAndEnsureSelectedItem; + } + void OnGUI() + { + DoToolbar(); + DoTreeView(); + } + + private bool m_isStop = false; + void DoToolbar() + { + GUILayout.BeginHorizontal(EditorStyles.toolbar); + + #region clear + bool isClear = GUILayout.Button("Clear", EditorStyles.toolbarButton, GUILayout.Height(30)); + if (isClear) + { + m_TreeView.Clear(); + } + GUILayout.Space(5); + #endregion + + #region deep + bool flag = GUILayout.Toggle(LuaDeepProfilerSetting.Instance.isDeepProfiler, + "Deep Profiler", EditorStyles.toolbarButton, GUILayout.Height(30)); + if (flag != LuaDeepProfilerSetting.Instance.isDeepProfiler) + { + LuaDeepProfilerSetting.Instance.isDeepProfiler = flag; + } + GUILayout.Space(5); + #endregion + + #region stop + bool isStop = GUILayout.Toggle(m_isStop, "Stop GC", EditorStyles.toolbarButton, GUILayout.Height(30)); + + if (m_isStop != isStop) + { + if (isStop) + { + var env = LuaProfiler.mainEnv; + if (env != null) + { + SLua.LuaDLL.lua_gc(env.L, SLua.LuaGCOptions.LUA_GCSTOP, 0); + } + m_isStop = true; + } + else + { + var env = LuaProfiler.mainEnv; + if (env != null) + { + SLua.LuaDLL.lua_gc(env.L, SLua.LuaGCOptions.LUA_GCRESTART, 0); + } + m_isStop = false; + } + } + GUILayout.Space(5); + #endregion + + #region run gc + bool isRunGC = GUILayout.Button("Run GC", EditorStyles.toolbarButton, GUILayout.Height(30)); + if (isRunGC) + { + var env = LuaProfiler.mainEnv; + if (env != null) + { + SLua.LuaDLL.lua_gc(env.L, SLua.LuaGCOptions.LUA_GCCOLLECT, 0); + } + } + GUILayout.Space(20); + GUILayout.FlexibleSpace(); + #endregion + + #region gc value + GUILayout.Label(string.Format("Lua Total:{0}", LuaProfiler.GetLuaMemory()), EditorStyles.toolbarButton, GUILayout.Height(30)); + #endregion + + GUILayout.Space(100); + GUILayout.FlexibleSpace(); + m_TreeView.searchString = m_SearchField.OnToolbarGUI(m_TreeView.searchString); + GUILayout.EndHorizontal(); + } + + void DoTreeView() + { + Rect rect = GUILayoutUtility.GetRect(0, 100000, 0, 100000); + m_TreeView.Reload(); + m_TreeView.OnGUI(rect); + } + + // Add menu named "My Window" to the Window menu + [MenuItem("Window/Lua Profiler Window")] + static public void ShowWindow() + { + // Get existing open window or if none, make a new one: + var window = GetWindow(); + window.titleContent = new GUIContent("Lua Profiler"); + window.Show(); + } + } +#endif +} diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs.meta b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs.meta new file mode 100644 index 00000000..7d56260c --- /dev/null +++ b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 55ce904866ab5b74f98c6fbff125c69b +timeCreated: 1471510735 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Parse.meta b/Assets/Slua/LuaProfiler/Parse.meta new file mode 100644 index 00000000..a7d6a01f --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c728c77c691363e45a9b18e716754e99 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Parse/Common.cs b/Assets/Slua/LuaProfiler/Parse/Common.cs new file mode 100644 index 00000000..d7f73684 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse/Common.cs @@ -0,0 +1,128 @@ +#if UNITY_EDITOR +namespace UniLua +{ + public static class LuaConf + { + public const int LUAI_BITSINT = 32; + +#pragma warning disable 0429 + public const int LUAI_MAXSTACK = (LUAI_BITSINT >= 32) + ? 1000000 + : 15000 + ; +#pragma warning restore 0429 + + // reserve some space for error handling + public const int LUAI_FIRSTPSEUDOIDX = (-LUAI_MAXSTACK-1000); + + public const string LUA_SIGNATURE = "\u001bLua"; + public static string LUA_DIRSEP { + get { return System.IO.Path.DirectorySeparatorChar.ToString(); } + } + } + + public static class LuaLimits + { + public const int MAX_INT = System.Int32.MaxValue - 2; + public const int MAXUPVAL = System.Byte.MaxValue; + public const int LUAI_MAXCCALLS = 200; + public const int MAXSTACK = 250; + } + + public static class LuaDef + { + public const int LUA_MINSTACK = 20; + public const int BASIC_STACK_SIZE = LUA_MINSTACK * 2; + public const int EXTRA_STACK = 5; + + public const int LUA_RIDX_MAINTHREAD = 1; + public const int LUA_RIDX_GLOBALS = 2; + public const int LUA_RIDX_LAST = LUA_RIDX_GLOBALS; + + public const int LUA_MULTRET = -1; + + public const int LUA_REGISTRYINDEX = LuaConf.LUAI_FIRSTPSEUDOIDX; + + // number of list items accumulate before a SETLIST instruction + public const int LFIELDS_PER_FLUSH = 50; + + public const int LUA_IDSIZE = 60; + + public const string LUA_VERSION_MAJOR = "5"; + public const string LUA_VERSION_MINOR = "2"; + public const string LUA_VERSION = "Lua " + LUA_VERSION_MAJOR + "." + LUA_VERSION_MINOR; + + public const string LUA_ENV = "_ENV"; + + public const int BASE_CI_SIZE = 8; + } + + public static class LuaConstants + { + public const int LUA_NOREF = -2; + public const int LUA_REFNIL = -1; + } + + public enum LuaType + { + LUA_TNONE = -1, + LUA_TNIL = 0, + LUA_TBOOLEAN = 1, + LUA_TLIGHTUSERDATA = 2, + LUA_TNUMBER = 3, + LUA_TSTRING = 4, + LUA_TTABLE = 5, + LUA_TFUNCTION = 6, + LUA_TUSERDATA = 7, + LUA_TTHREAD = 8, + + LUA_TUINT64 = 9, + + LUA_NUMTAGS = 10, + + LUA_TPROTO, + LUA_TUPVAL, + LUA_TDEADKEY, + } + + public enum ClosureType + { + LUA, + CSHARP, + } + + public enum ThreadStatus + { + LUA_RESUME_ERROR = -1, + LUA_OK = 0, + LUA_YIELD = 1, + LUA_ERRRUN = 2, + LUA_ERRSYNTAX = 3, + LUA_ERRMEM = 4, + LUA_ERRGCMM = 5, + LUA_ERRERR = 6, + + LUA_ERRFILE = 7, + } + + /* ORDER TM */ + internal enum LuaOp + { + LUA_OPADD = 0, + LUA_OPSUB = 1, + LUA_OPMUL = 2, + LUA_OPDIV = 3, + LUA_OPMOD = 4, + LUA_OPPOW = 5, + LUA_OPUNM = 6, + } + + public enum LuaEq + { + LUA_OPEQ = 0, + LUA_OPLT = 1, + LUA_OPLE = 2, + } + +} +#endif diff --git a/Assets/Slua/LuaProfiler/Parse/Common.cs.meta b/Assets/Slua/LuaProfiler/Parse/Common.cs.meta new file mode 100644 index 00000000..666ce914 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse/Common.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db0ce5a0e23232d4eacc46d0cf7f2390 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Slua/LuaProfiler/Parse/LLex.cs b/Assets/Slua/LuaProfiler/Parse/LLex.cs new file mode 100644 index 00000000..622a7f51 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse/LLex.cs @@ -0,0 +1,923 @@ + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; + +using NumberStyles = System.Globalization.NumberStyles; + +namespace UniLua +{ + public class LLexException : Exception + { + public LLexException( string info ) : base( info ) { } + } + + public enum TK + { + // reserved words + AND = 257, + BREAK, + DO, + ELSE, + ELSEIF, + END, + FALSE, + FOR, + FUNCTION, + GOTO, + IF, + IN, + LOCAL, + NIL, + NOT, + OR, + REPEAT, + RETURN, + THEN, + TRUE, + UNTIL, + WHILE, + // other terminal symbols + CONCAT, + DOTS, + EQ, + GE, + LE, + NE, + DBCOLON, + NUMBER, + STRING, + NAME, + EOS, + } + + public class StringLoadInfo + { + public StringLoadInfo(string s) + { + Str = new StringBuilder(s); + Pos = 0; + } + + public int ReadByte() + { + if (Pos >= Str.Length) + return -1; + else + return Str[Pos++]; + } + + public int PosChar + { + get + { + if (Pos >= Str.Length) + return (int)TK.EOS; + else + return Str[Pos]; + } + } + + public int PeekByte() + { + if (Pos >= Str.Length) + return -1; + else + return Str[Pos]; + } + + public string Replace(int start, int len, string value) + { + string result = Str.ToString().Substring(start, len); + Str = Str.Remove(start, len); + Str = Str.Insert(start, value); + if ((start + len) <= Pos) + { + Pos = Pos - (len - value.Length); + } + return result; + } + public string ReadString(int startPos, int len) + { + char[] chars = new char[len]; + for (int i = 0, imax = len; i < imax; i++) + { + chars[i] = Str[i + startPos]; + } + + return new string(chars); + } + + public void InsertString(int startPos, string value) + { + Str = Str.Insert(startPos, value); + Pos += value.Length; + } + public string code + { + get + { + return Str.ToString(); + } + } + private StringBuilder Str; + public int Pos; + } + + public class BytesLoadInfo + { + public BytesLoadInfo(byte[] bytes) + { + Bytes = bytes; + Pos = 0; + } + + public int ReadByte() + { + if (Pos >= Bytes.Length) + return -1; + else + return Bytes[Pos++]; + } + + public int PeekByte() + { + if (Pos >= Bytes.Length) + return -1; + else + return Bytes[Pos]; + } + + private byte[] Bytes; + private int Pos; + } + + public abstract class Token + { + public abstract int TokenType{ get; } + + public bool EqualsToToken( Token other ) { + return TokenType == other.TokenType; + } + + public bool EqualsToToken( int other ) { + return TokenType == other; + } + + public bool EqualsToToken( TK other ) { + return TokenType == (int)other; + } + } + + public class CommentToken : Token + { + public CommentToken() + { + } + + public override int TokenType + { + get { return -1; } + } + + public override string ToString() + { + return "CommentToken"; + } + } + + public class LiteralToken : Token + { + private int _Literal; + + public LiteralToken( int literal ) + { + _Literal = literal; + } + + public override int TokenType + { + get { return _Literal; } + } + + public override string ToString() + { + return string.Format( "LiteralToken: {0}", (char)_Literal ); + } + } + + public class TypedToken : Token + { + private TK _Type; + + public TypedToken( TK type ) + { + _Type = type; + } + + public override int TokenType + { + get { return (int)_Type; } + } + + public override string ToString() + { + return string.Format( "TypedToken: {0}", _Type ); + } + } + + public class StringToken : TypedToken + { + public string SemInfo; + + public StringToken( string seminfo ) : base( TK.STRING ) + { + SemInfo = seminfo; + } + + public override string ToString() + { + return string.Format( "StringToken: {0}", SemInfo ); + } + } + + public class NameToken : TypedToken + { + public string SemInfo; + + public NameToken( string seminfo ) : base( TK.NAME ) + { + SemInfo = seminfo; + } + + public override string ToString() + { + return string.Format( "NameToken: {0}", SemInfo ); + } + } + + public class NumberToken : TypedToken + { + public double SemInfo; + + public NumberToken( double seminfo ) : base( TK.NUMBER ) + { + SemInfo = seminfo; + } + + public override string ToString() + { + return string.Format( "NumberToken: {0}", SemInfo ); + } + } + + public class LLex + { + public const char EOZ = Char.MaxValue; + + public int pos + { + get + { + return LoadInfo.Pos; + } + } + private int Current; + public int LineNumber; + public int LastLine; + private StringLoadInfo LoadInfo; + public string Source; + + public Token Token; + private Token LookAhead; + + private StringBuilder _Saved; + private StringBuilder Saved + { + get { + if( _Saved == null ) { _Saved = new StringBuilder(); } + return _Saved; + } + } + + private static Dictionary ReservedWordDict; + static LLex() + { + ReservedWordDict = new Dictionary(); + ReservedWordDict.Add("and", TK.AND); + ReservedWordDict.Add("break", TK.BREAK); + ReservedWordDict.Add("do", TK.DO); + ReservedWordDict.Add("else", TK.ELSE); + ReservedWordDict.Add("elseif", TK.ELSEIF); + ReservedWordDict.Add("end", TK.END); + ReservedWordDict.Add("false", TK.FALSE); + ReservedWordDict.Add("for", TK.FOR); + ReservedWordDict.Add("function", TK.FUNCTION); + ReservedWordDict.Add("goto", TK.GOTO); + ReservedWordDict.Add("if", TK.IF); + ReservedWordDict.Add("in", TK.IN); + ReservedWordDict.Add("local", TK.LOCAL); + ReservedWordDict.Add("nil", TK.NIL); + ReservedWordDict.Add("not", TK.NOT); + ReservedWordDict.Add("or", TK.OR); + ReservedWordDict.Add("repeat", TK.REPEAT); + ReservedWordDict.Add("return", TK.RETURN); + ReservedWordDict.Add("then", TK.THEN); + ReservedWordDict.Add("true", TK.TRUE); + ReservedWordDict.Add("until", TK.UNTIL); + ReservedWordDict.Add("while", TK.WHILE); + } + + public LLex(StringLoadInfo loadinfo, string name ) + { + LoadInfo = loadinfo; + LineNumber = 1; + LastLine = 1; + Token = null; + LookAhead = null; + _Saved = null; + Source = name; + + _Next(); + } + + public string code + { + get + { + return LoadInfo.code; + } + } + + public void InsertString(int startPos, string value) + { + LoadInfo.InsertString(startPos, value); + } + + public void Next() + { + LastLine = LineNumber; + if( LookAhead != null ) + { + Token = LookAhead; + LookAhead = null; + } + else + { + Token = _Lex(); + } + } + + public Token GetLookAhead() + { + Utl.Assert( LookAhead == null ); + LookAhead = _Lex(); + return LookAhead; + } + + public string Replace(int start, int end, string value) + { + return LoadInfo.Replace(start, end + 1 - start, value); + } + + public string ReadString(int start, int end) + { + int len = end - start + 1; + return LoadInfo.ReadString(start, len); + } + + private void _Next() + { + var c = LoadInfo.ReadByte(); + Current = (c == -1) ? EOZ : c; + } + + private void _SaveAndNext() + { + Saved.Append( (char)Current ); + _Next(); + } + + private void _Save( char c ) + { + Saved.Append( c ); + } + + private string _GetSavedString() + { + return Saved.ToString(); + } + + private void _ClearSaved() + { + _Saved = null; + } + + private bool _CurrentIsNewLine() + { + return Current == '\n' || Current == '\r'; + } + + private bool _CurrentIsDigit() + { + return Char.IsDigit( (char)Current ); + } + + private bool _CurrentIsXDigit() + { + return _CurrentIsDigit() || + ('A' <= Current && Current <= 'F') || + ('a' <= Current && Current <= 'f'); + } + + private bool _CurrentIsSpace() + { + return Char.IsWhiteSpace( (char)Current ); + } + + private bool _CurrentIsAlpha() + { + return Char.IsLetter( (char)Current ); + } + + private bool _IsReserved( string identifier, out TK type ) + { + return ReservedWordDict.TryGetValue( identifier, out type ); + } + + public bool IsReservedWord( string name ) + { + return ReservedWordDict.ContainsKey( name ); + } + + private void _IncLineNumber() + { + var old = Current; + _Next(); + if( _CurrentIsNewLine() && Current != old ) + _Next(); + if( ++LineNumber >= Int32.MaxValue ) + _Error( "chunk has too many lines" ); + } + + private string _ReadLongString( int sep ) + { + _SaveAndNext(); + + if( _CurrentIsNewLine() ) + _IncLineNumber(); + + while( true ) + { + switch( Current ) + { + case EOZ: + _LexError( _GetSavedString(), + "unfinished long string/comment", + (int)TK.EOS ); + break; + + case '[': + { + if( _SkipSep() == sep ) + { + _SaveAndNext(); + if( sep == 0 ) + { + _LexError( _GetSavedString(), + "nesting of [[...]] is deprecated", + (int)TK.EOS ); + } + } + break; + } + + case ']': + { + if( _SkipSep() == sep ) + { + _SaveAndNext(); + goto endloop; + } + break; + } + + case '\n': + case '\r': + { + _Save('\n'); + _IncLineNumber(); + break; + } + + default: + { + _SaveAndNext(); + break; + } + } + } + endloop: + var r = _GetSavedString(); + return r.Substring( 2+sep, r.Length - 2*(2+sep) ); + } + + private void _EscapeError( string info, string msg ) + { + _LexError( "\\"+info, msg, (int)TK.STRING ); + } + + private byte _ReadHexEscape() + { + int r = 0; + var c = new char[3] { 'x', (char)0, (char)0 }; + // read two hex digits + for( int i=1; i<3; ++i ) + { + _Next(); + c[i] = (char)Current; + if( !_CurrentIsXDigit() ) + { + _EscapeError( new String(c, 0, i+1), + "hexadecimal digit expected" ); + // error + } + r = (r << 4) + Int32.Parse( Current.ToString(), + NumberStyles.HexNumber ); + } + return (byte)r; + } + + private byte _ReadDecEscape() + { + int r = 0; + var c = new char[3]; + // read up to 3 digits + int i = 0; + for( i=0; i<3 && _CurrentIsDigit(); ++i ) + { + c[i] = (char)Current; + r = r*10 + Current - '0'; + _Next(); + } + if( r > Byte.MaxValue ) + _EscapeError( new String(c, 0, i), + "decimal escape too large" ); + return (byte)r; + } + + private string _ReadString() + { + var del = Current; + _Next(); + while( Current != del ) + { + switch( Current ) + { + case EOZ: + _Error( "unfinished string" ); + continue; + + case '\n': + case '\r': + _Error( "unfinished string" ); + continue; + + case '\\': + { + byte c; + _Next(); + switch( Current ) + { + case 'a': c=(byte)'\a'; break; + case 'b': c=(byte)'\b'; break; + case 'f': c=(byte)'\f'; break; + case 'n': c=(byte)'\n'; break; + case 'r': c=(byte)'\r'; break; + case 't': c=(byte)'\t'; break; + case 'v': c=(byte)'\v'; break; + case 'x': c=_ReadHexEscape(); break; + + case '\n': + case '\r': _Save('\n'); _IncLineNumber(); continue; + + case '\\': + case '\"': + case '\'': c=(byte)Current; break; + + case EOZ: continue; + + // zap following span of spaces + case 'z': { + _Next(); // skip `z' + while( _CurrentIsSpace() ) + { + if( _CurrentIsNewLine() ) + _IncLineNumber(); + else + _Next(); + } + continue; + } + + default: + { + if( !_CurrentIsDigit() ) + _EscapeError( Current.ToString(), + "invalid escape sequence" ); + + // digital escape \ddd + c = _ReadDecEscape(); + _Save( (char)c ); + continue; + // { + // c = (char)0; + // for(int i=0; i<3 && _CurrentIsDigit(); ++i) + // { + // c = (char)(c*10 + Current - '0'); + // _Next(); + // } + // _Save( c ); + // } + // continue; + } + } + _Save( (char)c ); + _Next(); + continue; + } + + default: + _SaveAndNext(); + continue; + } + } + _Next(); + return _GetSavedString(); + } + + private double _ReadNumber() + { + var expo = new char[] { 'E', 'e' }; + Utl.Assert( _CurrentIsDigit() ); + var first = Current; + _SaveAndNext(); + if( first == '0' && (Current == 'X' || Current == 'x')) + { + expo = new char[] { 'P', 'p' }; + _SaveAndNext(); + } + for(;;) + { + if( Current == expo[0] || Current == expo[1] ) + { + _SaveAndNext(); + if( Current == '+' || Current == '-' ) + _SaveAndNext(); + } + if( _CurrentIsXDigit() || Current == '.' ) + _SaveAndNext(); + else + break; + } + + double ret; + var str = _GetSavedString(); + if(O_Str2Decimal( str, out ret ) ) + { + return ret; + } + else + { + _Error( "malformed number: " + str ); + return 0.0; + } + } + + public static bool O_Str2Decimal(string s, out double result) + { + result = 0.0; + + if (s.Contains("n") || s.Contains("N")) // reject `inf' and `nan' + return false; + + int pos = 0; + if (s.Contains("x") || s.Contains("X")) + result = Utl.StrX2Number(s, ref pos); + else + result = Utl.Str2Number(s, ref pos); + + if (pos == 0) + return false; // nothing recognized + + while (pos < s.Length && Char.IsWhiteSpace(s[pos])) ++pos; + return pos == s.Length; // OK if no trailing characters + } + + // private float _ReadNumber() + // { + // do + // { + // _SaveAndNext(); + // } while( _CurrentIsDigit() || Current == '.' ); + // if( Current == 'E' || Current == 'e' ) + // { + // _SaveAndNext(); + // if( Current == '+' || Current == '-' ) + // _SaveAndNext(); + // } + // while( _CurrentIsAlpha() || _CurrentIsDigit() || Current == '_' ) + // _SaveAndNext(); + // float ret; + // if( !Single.TryParse( _GetSavedString(), out ret ) ) + // _Error( "malformed number" ); + // return ret; + // } + + private void _Error( string error ) + { + throw new Exception(string.Format("{0}:{1}: {2}",Source, LineNumber, error)); + } + + private void _LexError( string info, string msg, int tokenType ) + { + // TODO + _Error( msg + ":" + info ); + } + + public void SyntaxError( string msg ) + { + // TODO + _Error( msg ); + } + + private int _SkipSep() + { + int count = 0; + var boundary = Current; + _SaveAndNext(); + while( Current == '=' ) { + _SaveAndNext(); + count++; + } + return ( Current == boundary ? count : (-count)-1 ); + } + + private Token _Lex() + { + _ClearSaved(); + while( true ) + { + switch( Current ) + { + case '\n': + case '\r': { + _IncLineNumber(); + continue; + } + + case '-': { + _Next(); + if( Current != '-' ) return new LiteralToken('-'); + + // else is a long comment + _Next(); + if( Current == '[' ) + { + int sep = _SkipSep(); + _ClearSaved(); + if( sep >= 0 ) + { + _ReadLongString( sep ); + _ClearSaved(); + return new CommentToken(); + } + } + + // else is a short comment + while( !_CurrentIsNewLine() && Current != EOZ ) + _Next(); + return new CommentToken(); + } + + case '[': { + int sep = _SkipSep(); + if( sep >= 0 ) { + string seminfo = _ReadLongString( sep ); + return new StringToken( seminfo ); + } + else if( sep == -1 ) return new LiteralToken('['); + else _Error("invalid long string delimiter"); + continue; + } + + case '=': { + _Next(); + if( Current != '=' ) return new LiteralToken('='); + _Next(); + return new TypedToken( TK.EQ ); + } + + case '<': { + _Next(); + if( Current != '=' ) return new LiteralToken('<'); + _Next(); + return new TypedToken( TK.LE ); + } + + case '>': { + _Next(); + if( Current != '=' ) return new LiteralToken('>'); + _Next(); + return new TypedToken( TK.GE ); + } + + case '~': { + _Next(); + if( Current != '=' ) return new LiteralToken('~'); + _Next(); + return new TypedToken( TK.NE ); + } + + case ':': { + _Next(); + if( Current != ':' ) return new LiteralToken(':'); + _Next(); + return new TypedToken( TK.DBCOLON ); // new in 5.2 ? + } + + case '"': + case '\'': { + return new StringToken( _ReadString() ); + } + + case '.': { + _SaveAndNext(); + if( Current == '.' ) + { + _SaveAndNext(); + if( Current == '.' ) + { + _SaveAndNext(); + return new TypedToken( TK.DOTS ); + } + else + { + return new TypedToken( TK.CONCAT ); + } + } + else if( !_CurrentIsDigit() ) + return new LiteralToken('.'); + else + return new NumberToken( _ReadNumber() ); + } + + case EOZ: { + return new TypedToken( TK.EOS ); + } + + default: { + if( _CurrentIsSpace() ) + { + _Next(); + continue; + } + else if( _CurrentIsDigit() ) + { + return new NumberToken( _ReadNumber() ); + } + else if( _CurrentIsAlpha() || Current == '_' ) + { + do { + _SaveAndNext(); + } while( _CurrentIsAlpha() || + _CurrentIsDigit() || + Current == '_' ); + + string identifier = _GetSavedString(); + TK type; + if( _IsReserved( identifier, out type ) ) + { + return new TypedToken( type ); + } + else + { + return new NameToken( identifier ); + } + } + else + { + var c = Current; + _Next(); + return new LiteralToken(c); + } + } + } + } + } + + } + +} + diff --git a/Assets/Slua/LuaProfiler/Parse/LLex.cs.meta b/Assets/Slua/LuaProfiler/Parse/LLex.cs.meta new file mode 100644 index 00000000..e12b4185 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse/LLex.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45c6de50fce82bc4fb1287ae03999b43 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Slua/LuaProfiler/Parse/Parse.cs b/Assets/Slua/LuaProfiler/Parse/Parse.cs new file mode 100644 index 00000000..ced06047 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse/Parse.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using UniLua; + +namespace MikuLuaProfiler +{ + public static class Parse + { + #region parse + public static string InsertSample(string value, string name) + { + LLex l = new LLex(new StringLoadInfo(value), name); + l.InsertString(0, "BeginMikuSample(\"" + name + ", line:1 require file\")\r\n"); + int lastPos = 0; + int nextPos = l.pos; + l.Next(); + int tokenType = l.Token.TokenType; + + lastPos = nextPos; + nextPos = l.pos; + + InsertSample(l, ref lastPos, ref nextPos, tokenType, false); + + return l.code; + } + + static void InsertSample(LLex l, ref int lastPos, ref int nextPos, int tokenType, bool onlyFun) + { + Stack tokens = new Stack(); + + bool needLastSample = true; + bool hasReturn = false; + int lastStackToken = -1; + while (tokenType != (int)TK.EOS) + { + switch (tokenType) + { + case (int)TK.FUNCTION: + hasReturn = false; + tokens.Push(tokenType); + lastStackToken = tokenType; + string funName = ""; + bool isLeft = false; + + while (tokenType != (int)TK.EOS) + { + l.Next(); + tokenType = l.Token.TokenType; + + lastPos = nextPos; + nextPos = l.pos; + if (!isLeft) + { + if (l.Token is NameToken) + { + funName += ((NameToken)l.Token).SemInfo; + } + else if ((l.Token.TokenType == (int)':')) + { + funName += ':'; + } + else if ((l.Token.TokenType == (int)'.')) + { + funName += '.'; + } + } + + + if (tokenType == (int)'(') + { + isLeft = true; + } + + if (tokenType == (int)')') + { + l.InsertString(nextPos, "\r\nBeginMikuSample(\"" + l.Source + + ",line:" + l.LineNumber + " funName:" + funName + "\")\r\n"); + nextPos = l.pos; + break; + } + } + break; + case (int)TK.IF: + case (int)TK.FOR: + case (int)TK.WHILE: + if (tokens.Count > 0) + { + tokens.Push(tokenType); + lastStackToken = tokenType; + } + break; + case (int)TK.RETURN: + int insertPos = lastPos - 1; + + if (tokens.Count == 0) + { + needLastSample = false; + } + + while (tokenType != (int)TK.EOS) + { + l.Next(); + + tokenType = l.Token.TokenType; + + lastPos = nextPos; + nextPos = l.pos; + + if (tokenType == (int)TK.FUNCTION) + { + InsertSample(l, ref lastPos, ref nextPos, tokenType, true); + tokenType = l.Token.TokenType; + } + + if (tokenType == (int)TK.END + || tokenType == (int)TK.ELSEIF + || tokenType == (int)TK.ELSE + || tokenType == (int)TK.EOS + || (l.Token is CommentToken)) + { + string returnStr = l.ReadString(insertPos, lastPos - 1); ; + + returnStr = returnStr.Trim(); + returnStr = "\r\nreturn miku_unpack_return_value(" + returnStr.Substring(6, returnStr.Length - 6).Trim() + ")\r\n"; + + l.Replace(insertPos, lastPos - 1, returnStr); + nextPos = l.pos; + if (tokenType == (int)TK.END) + { + if (tokens.Count > 0) + tokens.Pop(); + if (onlyFun && tokens.Count <= 0) + { + l.Next(); + lastPos = nextPos; + nextPos = l.pos; + return; + } + } + break; + } + } + + if (lastStackToken != (int)TK.IF) + { + hasReturn = true; + } + break; + case (int)TK.END: + if (tokens.Count > 0) + { + int token = tokens.Pop(); + if (token == (int)TK.FUNCTION) + { + if (!hasReturn) + { + l.InsertString(lastPos, "\r\nEndMikuSample()\r\n"); + lastPos = nextPos; + nextPos = l.pos; + } + if (onlyFun && tokens.Count <= 0) + { + l.Next(); + lastPos = nextPos; + nextPos = l.pos; + return; + } + } + if (tokens.Count > 0) + { + var tA = tokens.ToArray(); + lastStackToken = tA[tA.Length - 1]; + } + hasReturn = false; + } + break; + } + l.Next(); + tokenType = l.Token.TokenType; + lastPos = nextPos; + nextPos = l.pos; + } + + if (needLastSample) + { + l.InsertString(nextPos, "\r\nEndMikuSample()"); + } + } + #endregion + } +} diff --git a/Assets/Slua/LuaProfiler/Parse/Parse.cs.meta b/Assets/Slua/LuaProfiler/Parse/Parse.cs.meta new file mode 100644 index 00000000..dec4f822 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse/Parse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a52e2cc637b97674e95c3febdf48dc15 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Parse/Util.cs b/Assets/Slua/LuaProfiler/Parse/Util.cs new file mode 100644 index 00000000..f8b15381 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse/Util.cs @@ -0,0 +1,225 @@ + +#define API_CHECK +#define UNILUA_ASSERT + +using System; + +namespace UniLua +{ + using DebugS = System.Diagnostics.Debug; + using NumberStyles = System.Globalization.NumberStyles; + + internal static class Utl + { + private static void Throw( params string[] msgs ) + { + throw new Exception(String.Join("", msgs)); + } + + public static void Assert( bool condition ) + { +#if UNILUA_ASSERT + if( !condition ) + Throw("assert failed!" ); + DebugS.Assert( condition ); +#endif + } + + public static void Assert( bool condition, string message ) + { +#if UNILUA_ASSERT + if( !condition ) + Throw( "assert failed! ", message ); + DebugS.Assert( condition, message ); +#endif + } + + public static void Assert( bool condition, string message, string detailMessage ) + { +#if UNILUA_ASSERT + if( !condition ) + Throw( "assert failed! ", message, "\n", detailMessage ); + DebugS.Assert( condition, message, detailMessage ); +#endif + } + + public static void ApiCheck( bool condition, string message ) + { +#if UNILUA_ASSERT +#if API_CHECK + Assert( condition, message ); +#endif +#endif + } + + private static bool IsNegative( string s, ref int pos ) + { + if( pos >= s.Length ) + return false; + + char c = s[pos]; + if( c == '-' ) + { + ++pos; + return true; + } + else if( c == '+' ) + { + ++pos; + } + return false; + } + + private static bool IsXDigit( char c ) + { + if( Char.IsDigit( c ) ) + return true; + + if( 'a' <= c && c <= 'f' ) + return true; + + if( 'A' <= c && c <= 'F' ) + return true; + + return false; + } + + private static double ReadHexa( string s, ref int pos, double r, out int count ) + { + count = 0; + while( pos < s.Length && IsXDigit( s[pos] ) ) + { + r = (r * 16.0) + Int32.Parse( s[pos].ToString(), NumberStyles.HexNumber ); + ++pos; + ++count; + } + return r; + } + + private static double ReadDecimal( string s, ref int pos, double r, out int count ) + { + count = 0; + while( pos < s.Length && Char.IsDigit( s[pos] ) ) + { + r = (r * 10.0) + Int32.Parse( s[pos].ToString() ); + ++pos; + ++count; + } + return r; + } + + // following C99 specification for 'strtod' + public static double StrX2Number( string s, ref int curpos ) + { + int pos = curpos; + while( pos < s.Length && Char.IsWhiteSpace( s[pos] )) ++pos; + bool negative = IsNegative( s, ref pos ); + + // check `0x' + if( pos >= s.Length || !(s[pos] == '0' && (s[pos+1] == 'x' || s[pos+1] == 'X')) ) + return 0.0; + + pos += 2; // skip `0x' + + double r = 0.0; + int i = 0; + int e = 0; + r = ReadHexa( s, ref pos, r, out i ); + if( pos < s.Length && s[pos] == '.' ) + { + ++pos; // skip `.' + r = ReadHexa( s, ref pos, r, out e ); + } + if( i == 0 && e == 0 ) + return 0.0; // invalid format (no digit) + + // each fractional digit divides value by 2^-4 + e *= -4; + curpos = pos; + + // exponent part + if( pos < s.Length && (s[pos] == 'p' || s[pos] == 'P') ) + { + ++pos; // skip `p' + bool expNegative = IsNegative( s, ref pos ); + if( pos >= s.Length || !Char.IsDigit( s[pos] ) ) + goto ret; + + int exp1 = 0; + while( pos < s.Length && Char.IsDigit( s[pos] ) ) + { + exp1 = exp1 * 10 + Int32.Parse( s[pos].ToString() ); + ++pos; + } + if( expNegative ) + exp1 = -exp1; + e += exp1; + } + curpos = pos; + +ret: + if( negative ) r = -r; + + return r * Math.Pow(2.0, e); + } + + public static double Str2Number( string s, ref int curpos ) + { + int pos = curpos; + while( pos < s.Length && Char.IsWhiteSpace( s[pos] )) ++pos; + bool negative = IsNegative( s, ref pos ); + + double r = 0.0; + int i = 0; + int f = 0; + r = ReadDecimal( s, ref pos, r, out i ); + if( pos < s.Length && s[pos] == '.' ) + { + ++pos; + r = ReadDecimal( s, ref pos, r, out f ); + } + if( i == 0 && f == 0 ) + return 0.0; + + f = -f; + curpos = pos; + + // exponent part + double e = 0.0; + if( pos < s.Length && (s[pos] == 'e' || s[pos] == 'E') ) + { + ++pos; + bool expNegative = IsNegative( s, ref pos ); + if( pos >= s.Length || !Char.IsDigit( s[pos] ) ) + goto ret; + + int n; + e = ReadDecimal( s, ref pos, e, out n ); + if( expNegative ) + e = -e; + f += (int)e; + } + curpos = pos; + +ret: + if( negative ) r = -r; + + return r * Math.Pow(10, f); + } + + public static string TrimWhiteSpace( string str ) + { + int s = 0; + int e = str.Length; + + while( s < str.Length && Char.IsWhiteSpace( str[s] ) ) ++s; + if( s >= e ) + return ""; + + while( e >= 0 && Char.IsWhiteSpace( str[e-1] ) ) --e; + return str.Substring( s, e-s ); + } + } + +} + diff --git a/Assets/Slua/LuaProfiler/Parse/Util.cs.meta b/Assets/Slua/LuaProfiler/Parse/Util.cs.meta new file mode 100644 index 00000000..77be9fe8 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Parse/Util.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e8e03e8ba75219a4b9ed5f669f6bc86b +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Slua/LuaProfiler/UnityHook.meta b/Assets/Slua/LuaProfiler/UnityHook.meta new file mode 100644 index 00000000..d2a29dc6 --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 635b0b64fcd9b8b4aa20083ddfe769f2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs b/Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs new file mode 100644 index 00000000..88b6c1b9 --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs @@ -0,0 +1,31 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +/// +/// Hooker 池,防止重复 Hook +/// +public static class HookerPool +{ + private static Dictionary _hookers = new Dictionary(); + + public static void AddHooker(MethodBase method, MethodHooker hooker) + { + MethodHooker preHooker; + if (_hookers.TryGetValue(method, out preHooker)) + { + preHooker.Uninstall(); + _hookers[method] = hooker; + } + else + _hookers.Add(method, hooker); + } + + public static void RemoveHooker(MethodBase method) + { + _hookers.Remove(method); + } +} +#endif diff --git a/Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs.meta b/Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs.meta new file mode 100644 index 00000000..75038591 --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b7421e47f0ae1e4ebb72bf18d1d7d48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/UnityHook/LDasm.cs b/Assets/Slua/LuaProfiler/UnityHook/LDasm.cs new file mode 100644 index 00000000..1de7eb25 --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook/LDasm.cs @@ -0,0 +1,877 @@ +#if UNITY_EDITOR +using System; + +namespace DotNetDetour +{ + /// + /// 用于计算汇编指令长度,使用的是BlackBone的LDasm.c中的算法,我把他翻译成C#了 + /// + public unsafe class LDasm + { + const int F_INVALID = 0x01; + const int F_PREFIX = 0x02; + const int F_REX = 0x04; + const int F_MODRM = 0x08; + const int F_SIB = 0x10; + const int F_DISP = 0x20; + const int F_IMM = 0x40; + const int F_RELATIVE = 0x80; + + const int OP_NONE = 0x00; + const int OP_INVALID = 0x80; + + const int OP_DATA_I8 = 0x01; + const int OP_DATA_I16 = 0x02; + const int OP_DATA_I16_I32 = 0x04; + const int OP_DATA_I16_I32_I64 = 0x08; + const int OP_EXTENDED = 0x10; + const int OP_RELATIVE = 0x20; + const int OP_MODRM = 0x40; + const int OP_PREFIX = 0x80; + + struct ldasm_data + { + public byte flags; + public byte rex; + public byte modrm; + public byte sib; + public byte opcd_offset; + public byte opcd_size; + public byte disp_offset; + public byte disp_size; + public byte imm_offset; + public byte imm_size; + } + + static byte[] flags_table = + { + /* 00 */ OP_MODRM, + /* 01 */ OP_MODRM, + /* 02 */ OP_MODRM, + /* 03 */ OP_MODRM, + /* 04 */ OP_DATA_I8, + /* 05 */ OP_DATA_I16_I32, + /* 06 */ OP_NONE, + /* 07 */ OP_NONE, + /* 08 */ OP_MODRM, + /* 09 */ OP_MODRM, + /* 0A */ OP_MODRM, + /* 0B */ OP_MODRM, + /* 0C */ OP_DATA_I8, + /* 0D */ OP_DATA_I16_I32, + /* 0E */ OP_NONE, + /* 0F */ OP_NONE, + + /* 10 */ OP_MODRM, + /* 11 */ OP_MODRM, + /* 12 */ OP_MODRM, + /* 13 */ OP_MODRM, + /* 14 */ OP_DATA_I8, + /* 15 */ OP_DATA_I16_I32, + /* 16 */ OP_NONE, + /* 17 */ OP_NONE, + /* 18 */ OP_MODRM, + /* 19 */ OP_MODRM, + /* 1A */ OP_MODRM, + /* 1B */ OP_MODRM, + /* 1C */ OP_DATA_I8, + /* 1D */ OP_DATA_I16_I32, + /* 1E */ OP_NONE, + /* 1F */ OP_NONE, + + /* 20 */ OP_MODRM, + /* 21 */ OP_MODRM, + /* 22 */ OP_MODRM, + /* 23 */ OP_MODRM, + /* 24 */ OP_DATA_I8, + /* 25 */ OP_DATA_I16_I32, + /* 26 */ OP_PREFIX, + /* 27 */ OP_NONE, + /* 28 */ OP_MODRM, + /* 29 */ OP_MODRM, + /* 2A */ OP_MODRM, + /* 2B */ OP_MODRM, + /* 2C */ OP_DATA_I8, + /* 2D */ OP_DATA_I16_I32, + /* 2E */ OP_PREFIX, + /* 2F */ OP_NONE, + + /* 30 */ OP_MODRM, + /* 31 */ OP_MODRM, + /* 32 */ OP_MODRM, + /* 33 */ OP_MODRM, + /* 34 */ OP_DATA_I8, + /* 35 */ OP_DATA_I16_I32, + /* 36 */ OP_PREFIX, + /* 37 */ OP_NONE, + /* 38 */ OP_MODRM, + /* 39 */ OP_MODRM, + /* 3A */ OP_MODRM, + /* 3B */ OP_MODRM, + /* 3C */ OP_DATA_I8, + /* 3D */ OP_DATA_I16_I32, + /* 3E */ OP_PREFIX, + /* 3F */ OP_NONE, + + /* 40 */ OP_NONE, + /* 41 */ OP_NONE, + /* 42 */ OP_NONE, + /* 43 */ OP_NONE, + /* 44 */ OP_NONE, + /* 45 */ OP_NONE, + /* 46 */ OP_NONE, + /* 47 */ OP_NONE, + /* 48 */ OP_NONE, + /* 49 */ OP_NONE, + /* 4A */ OP_NONE, + /* 4B */ OP_NONE, + /* 4C */ OP_NONE, + /* 4D */ OP_NONE, + /* 4E */ OP_NONE, + /* 4F */ OP_NONE, + + /* 50 */ OP_NONE, + /* 51 */ OP_NONE, + /* 52 */ OP_NONE, + /* 53 */ OP_NONE, + /* 54 */ OP_NONE, + /* 55 */ OP_NONE, + /* 56 */ OP_NONE, + /* 57 */ OP_NONE, + /* 58 */ OP_NONE, + /* 59 */ OP_NONE, + /* 5A */ OP_NONE, + /* 5B */ OP_NONE, + /* 5C */ OP_NONE, + /* 5D */ OP_NONE, + /* 5E */ OP_NONE, + /* 5F */ OP_NONE, + /* 60 */ OP_NONE, + + /* 61 */ OP_NONE, + /* 62 */ OP_MODRM, + /* 63 */ OP_MODRM, + /* 64 */ OP_PREFIX, + /* 65 */ OP_PREFIX, + /* 66 */ OP_PREFIX, + /* 67 */ OP_PREFIX, + /* 68 */ OP_DATA_I16_I32, + /* 69 */ OP_MODRM | OP_DATA_I16_I32, + /* 6A */ OP_DATA_I8, + /* 6B */ OP_MODRM | OP_DATA_I8, + /* 6C */ OP_NONE, + /* 6D */ OP_NONE, + /* 6E */ OP_NONE, + /* 6F */ OP_NONE, + + /* 70 */ OP_RELATIVE | OP_DATA_I8, + /* 71 */ OP_RELATIVE | OP_DATA_I8, + /* 72 */ OP_RELATIVE | OP_DATA_I8, + /* 73 */ OP_RELATIVE | OP_DATA_I8, + /* 74 */ OP_RELATIVE | OP_DATA_I8, + /* 75 */ OP_RELATIVE | OP_DATA_I8, + /* 76 */ OP_RELATIVE | OP_DATA_I8, + /* 77 */ OP_RELATIVE | OP_DATA_I8, + /* 78 */ OP_RELATIVE | OP_DATA_I8, + /* 79 */ OP_RELATIVE | OP_DATA_I8, + /* 7A */ OP_RELATIVE | OP_DATA_I8, + /* 7B */ OP_RELATIVE | OP_DATA_I8, + /* 7C */ OP_RELATIVE | OP_DATA_I8, + /* 7D */ OP_RELATIVE | OP_DATA_I8, + /* 7E */ OP_RELATIVE | OP_DATA_I8, + /* 7F */ OP_RELATIVE | OP_DATA_I8, + + /* 80 */ OP_MODRM | OP_DATA_I8, + /* 81 */ OP_MODRM | OP_DATA_I16_I32, + /* 82 */ OP_MODRM | OP_DATA_I8, + /* 83 */ OP_MODRM | OP_DATA_I8, + /* 84 */ OP_MODRM, + /* 85 */ OP_MODRM, + /* 86 */ OP_MODRM, + /* 87 */ OP_MODRM, + /* 88 */ OP_MODRM, + /* 89 */ OP_MODRM, + /* 8A */ OP_MODRM, + /* 8B */ OP_MODRM, + /* 8C */ OP_MODRM, + /* 8D */ OP_MODRM, + /* 8E */ OP_MODRM, + /* 8F */ OP_MODRM, + + /* 90 */ OP_NONE, + /* 91 */ OP_NONE, + /* 92 */ OP_NONE, + /* 93 */ OP_NONE, + /* 94 */ OP_NONE, + /* 95 */ OP_NONE, + /* 96 */ OP_NONE, + /* 97 */ OP_NONE, + /* 98 */ OP_NONE, + /* 99 */ OP_NONE, + /* 9A */ OP_DATA_I16 | OP_DATA_I16_I32, + /* 9B */ OP_NONE, + /* 9C */ OP_NONE, + /* 9D */ OP_NONE, + /* 9E */ OP_NONE, + /* 9F */ OP_NONE, + + /* A0 */ OP_DATA_I8, + /* A1 */ OP_DATA_I16_I32_I64, + /* A2 */ OP_DATA_I8, + /* A3 */ OP_DATA_I16_I32_I64, + /* A4 */ OP_NONE, + /* A5 */ OP_NONE, + /* A6 */ OP_NONE, + /* A7 */ OP_NONE, + /* A8 */ OP_DATA_I8, + /* A9 */ OP_DATA_I16_I32, + /* AA */ OP_NONE, + /* AB */ OP_NONE, + /* AC */ OP_NONE, + /* AD */ OP_NONE, + /* AE */ OP_NONE, + /* AF */ OP_NONE, + + /* B0 */ OP_DATA_I8, + /* B1 */ OP_DATA_I8, + /* B2 */ OP_DATA_I8, + /* B3 */ OP_DATA_I8, + /* B4 */ OP_DATA_I8, + /* B5 */ OP_DATA_I8, + /* B6 */ OP_DATA_I8, + /* B7 */ OP_DATA_I8, + /* B8 */ OP_DATA_I16_I32_I64, + /* B9 */ OP_DATA_I16_I32_I64, + /* BA */ OP_DATA_I16_I32_I64, + /* BB */ OP_DATA_I16_I32_I64, + /* BC */ OP_DATA_I16_I32_I64, + /* BD */ OP_DATA_I16_I32_I64, + /* BE */ OP_DATA_I16_I32_I64, + /* BF */ OP_DATA_I16_I32_I64, + + /* C0 */ OP_MODRM | OP_DATA_I8, + /* C1 */ OP_MODRM | OP_DATA_I8, + /* C2 */ OP_DATA_I16, + /* C3 */ OP_NONE, + /* C4 */ OP_MODRM, + /* C5 */ OP_MODRM, + /* C6 */ OP_MODRM | OP_DATA_I8, + /* C7 */ OP_MODRM | OP_DATA_I16_I32, + /* C8 */ OP_DATA_I8 | OP_DATA_I16, + /* C9 */ OP_NONE, + /* CA */ OP_DATA_I16, + /* CB */ OP_NONE, + /* CC */ OP_NONE, + /* CD */ OP_DATA_I8, + /* CE */ OP_NONE, + /* CF */ OP_NONE, + + /* D0 */ OP_MODRM, + /* D1 */ OP_MODRM, + /* D2 */ OP_MODRM, + /* D3 */ OP_MODRM, + /* D4 */ OP_DATA_I8, + /* D5 */ OP_DATA_I8, + /* D6 */ OP_NONE, + /* D7 */ OP_NONE, + /* D8 */ OP_MODRM, + /* D9 */ OP_MODRM, + /* DA */ OP_MODRM, + /* DB */ OP_MODRM, + /* DC */ OP_MODRM, + /* DD */ OP_MODRM, + /* DE */ OP_MODRM, + /* DF */ OP_MODRM, + + /* E0 */ OP_RELATIVE | OP_DATA_I8, + /* E1 */ OP_RELATIVE | OP_DATA_I8, + /* E2 */ OP_RELATIVE | OP_DATA_I8, + /* E3 */ OP_RELATIVE | OP_DATA_I8, + /* E4 */ OP_DATA_I8, + /* E5 */ OP_DATA_I8, + /* E6 */ OP_DATA_I8, + /* E7 */ OP_DATA_I8, + /* E8 */ OP_RELATIVE | OP_DATA_I16_I32, + /* E9 */ OP_RELATIVE | OP_DATA_I16_I32, + /* EA */ OP_DATA_I16 | OP_DATA_I16_I32, + /* EB */ OP_RELATIVE | OP_DATA_I8, + /* EC */ OP_NONE, + /* ED */ OP_NONE, + /* EE */ OP_NONE, + /* EF */ OP_NONE, + + /* F0 */ OP_PREFIX, + /* F1 */ OP_NONE, + /* F2 */ OP_PREFIX, + /* F3 */ OP_PREFIX, + /* F4 */ OP_NONE, + /* F5 */ OP_NONE, + /* F6 */ OP_MODRM, + /* F7 */ OP_MODRM, + /* F8 */ OP_NONE, + /* F9 */ OP_NONE, + /* FA */ OP_NONE, + /* FB */ OP_NONE, + /* FC */ OP_NONE, + /* FD */ OP_NONE, + /* FE */ OP_MODRM, + /* FF */ OP_MODRM + }; + + static byte[] flags_table_ex = + { + /* 0F00 */ OP_MODRM, + /* 0F01 */ OP_MODRM, + /* 0F02 */ OP_MODRM, + /* 0F03 */ OP_MODRM, + /* 0F04 */ OP_INVALID, + /* 0F05 */ OP_NONE, + /* 0F06 */ OP_NONE, + /* 0F07 */ OP_NONE, + /* 0F08 */ OP_NONE, + /* 0F09 */ OP_NONE, + /* 0F0A */ OP_INVALID, + /* 0F0B */ OP_NONE, + /* 0F0C */ OP_INVALID, + /* 0F0D */ OP_MODRM, + /* 0F0E */ OP_INVALID, + /* 0F0F */ OP_MODRM | OP_DATA_I8, //3Dnow + + /* 0F10 */ OP_MODRM, + /* 0F11 */ OP_MODRM, + /* 0F12 */ OP_MODRM, + /* 0F13 */ OP_MODRM, + /* 0F14 */ OP_MODRM, + /* 0F15 */ OP_MODRM, + /* 0F16 */ OP_MODRM, + /* 0F17 */ OP_MODRM, + /* 0F18 */ OP_MODRM, + /* 0F19 */ OP_INVALID, + /* 0F1A */ OP_INVALID, + /* 0F1B */ OP_INVALID, + /* 0F1C */ OP_INVALID, + /* 0F1D */ OP_INVALID, + /* 0F1E */ OP_INVALID, + /* 0F1F */ OP_NONE, + + /* 0F20 */ OP_MODRM, + /* 0F21 */ OP_MODRM, + /* 0F22 */ OP_MODRM, + /* 0F23 */ OP_MODRM, + /* 0F24 */ OP_MODRM | OP_EXTENDED, //SSE5 + /* 0F25 */ OP_INVALID, + /* 0F26 */ OP_MODRM, + /* 0F27 */ OP_INVALID, + /* 0F28 */ OP_MODRM, + /* 0F29 */ OP_MODRM, + /* 0F2A */ OP_MODRM, + /* 0F2B */ OP_MODRM, + /* 0F2C */ OP_MODRM, + /* 0F2D */ OP_MODRM, + /* 0F2E */ OP_MODRM, + /* 0F2F */ OP_MODRM, + + /* 0F30 */ OP_NONE, + /* 0F31 */ OP_NONE, + /* 0F32 */ OP_NONE, + /* 0F33 */ OP_NONE, + /* 0F34 */ OP_NONE, + /* 0F35 */ OP_NONE, + /* 0F36 */ OP_INVALID, + /* 0F37 */ OP_NONE, + /* 0F38 */ OP_MODRM | OP_EXTENDED, + /* 0F39 */ OP_INVALID, + /* 0F3A */ OP_MODRM | OP_EXTENDED | OP_DATA_I8, + /* 0F3B */ OP_INVALID, + /* 0F3C */ OP_INVALID, + /* 0F3D */ OP_INVALID, + /* 0F3E */ OP_INVALID, + /* 0F3F */ OP_INVALID, + + /* 0F40 */ OP_MODRM, + /* 0F41 */ OP_MODRM, + /* 0F42 */ OP_MODRM, + /* 0F43 */ OP_MODRM, + /* 0F44 */ OP_MODRM, + /* 0F45 */ OP_MODRM, + /* 0F46 */ OP_MODRM, + /* 0F47 */ OP_MODRM, + /* 0F48 */ OP_MODRM, + /* 0F49 */ OP_MODRM, + /* 0F4A */ OP_MODRM, + /* 0F4B */ OP_MODRM, + /* 0F4C */ OP_MODRM, + /* 0F4D */ OP_MODRM, + /* 0F4E */ OP_MODRM, + /* 0F4F */ OP_MODRM, + + /* 0F50 */ OP_MODRM, + /* 0F51 */ OP_MODRM, + /* 0F52 */ OP_MODRM, + /* 0F53 */ OP_MODRM, + /* 0F54 */ OP_MODRM, + /* 0F55 */ OP_MODRM, + /* 0F56 */ OP_MODRM, + /* 0F57 */ OP_MODRM, + /* 0F58 */ OP_MODRM, + /* 0F59 */ OP_MODRM, + /* 0F5A */ OP_MODRM, + /* 0F5B */ OP_MODRM, + /* 0F5C */ OP_MODRM, + /* 0F5D */ OP_MODRM, + /* 0F5E */ OP_MODRM, + /* 0F5F */ OP_MODRM, + + /* 0F60 */ OP_MODRM, + /* 0F61 */ OP_MODRM, + /* 0F62 */ OP_MODRM, + /* 0F63 */ OP_MODRM, + /* 0F64 */ OP_MODRM, + /* 0F65 */ OP_MODRM, + /* 0F66 */ OP_MODRM, + /* 0F67 */ OP_MODRM, + /* 0F68 */ OP_MODRM, + /* 0F69 */ OP_MODRM, + /* 0F6A */ OP_MODRM, + /* 0F6B */ OP_MODRM, + /* 0F6C */ OP_MODRM, + /* 0F6D */ OP_MODRM, + /* 0F6E */ OP_MODRM, + /* 0F6F */ OP_MODRM, + + /* 0F70 */ OP_MODRM | OP_DATA_I8, + /* 0F71 */ OP_MODRM | OP_DATA_I8, + /* 0F72 */ OP_MODRM | OP_DATA_I8, + /* 0F73 */ OP_MODRM | OP_DATA_I8, + /* 0F74 */ OP_MODRM, + /* 0F75 */ OP_MODRM, + /* 0F76 */ OP_MODRM, + /* 0F77 */ OP_NONE, + /* 0F78 */ OP_MODRM, + /* 0F79 */ OP_MODRM, + /* 0F7A */ OP_INVALID, + /* 0F7B */ OP_INVALID, + /* 0F7C */ OP_MODRM, + /* 0F7D */ OP_MODRM, + /* 0F7E */ OP_MODRM, + /* 0F7F */ OP_MODRM, + + /* 0F80 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F81 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F82 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F83 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F84 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F85 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F86 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F87 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F88 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F89 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8A */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8B */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8C */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8D */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8E */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8F */ OP_RELATIVE | OP_DATA_I16_I32, + + /* 0F90 */ OP_MODRM, + /* 0F91 */ OP_MODRM, + /* 0F92 */ OP_MODRM, + /* 0F93 */ OP_MODRM, + /* 0F94 */ OP_MODRM, + /* 0F95 */ OP_MODRM, + /* 0F96 */ OP_MODRM, + /* 0F97 */ OP_MODRM, + /* 0F98 */ OP_MODRM, + /* 0F99 */ OP_MODRM, + /* 0F9A */ OP_MODRM, + /* 0F9B */ OP_MODRM, + /* 0F9C */ OP_MODRM, + /* 0F9D */ OP_MODRM, + /* 0F9E */ OP_MODRM, + /* 0F9F */ OP_MODRM, + + /* 0FA0 */ OP_NONE, + /* 0FA1 */ OP_NONE, + /* 0FA2 */ OP_NONE, + /* 0FA3 */ OP_MODRM, + /* 0FA4 */ OP_MODRM | OP_DATA_I8, + /* 0FA5 */ OP_MODRM, + /* 0FA6 */ OP_INVALID, + /* 0FA7 */ OP_INVALID, + /* 0FA8 */ OP_NONE, + /* 0FA9 */ OP_NONE, + /* 0FAA */ OP_NONE, + /* 0FAB */ OP_MODRM, + /* 0FAC */ OP_MODRM | OP_DATA_I8, + /* 0FAD */ OP_MODRM, + /* 0FAE */ OP_MODRM, + /* 0FAF */ OP_MODRM, + + /* 0FB0 */ OP_MODRM, + /* 0FB1 */ OP_MODRM, + /* 0FB2 */ OP_MODRM, + /* 0FB3 */ OP_MODRM, + /* 0FB4 */ OP_MODRM, + /* 0FB5 */ OP_MODRM, + /* 0FB6 */ OP_MODRM, + /* 0FB7 */ OP_MODRM, + /* 0FB8 */ OP_MODRM, + /* 0FB9 */ OP_MODRM, + /* 0FBA */ OP_MODRM | OP_DATA_I8, + /* 0FBB */ OP_MODRM, + /* 0FBC */ OP_MODRM, + /* 0FBD */ OP_MODRM, + /* 0FBE */ OP_MODRM, + /* 0FBF */ OP_MODRM, + + /* 0FC0 */ OP_MODRM, + /* 0FC1 */ OP_MODRM, + /* 0FC2 */ OP_MODRM | OP_DATA_I8, + /* 0FC3 */ OP_MODRM, + /* 0FC4 */ OP_MODRM | OP_DATA_I8, + /* 0FC5 */ OP_MODRM | OP_DATA_I8, + /* 0FC6 */ OP_MODRM | OP_DATA_I8, + /* 0FC7 */ OP_MODRM, + /* 0FC8 */ OP_NONE, + /* 0FC9 */ OP_NONE, + /* 0FCA */ OP_NONE, + /* 0FCB */ OP_NONE, + /* 0FCC */ OP_NONE, + /* 0FCD */ OP_NONE, + /* 0FCE */ OP_NONE, + /* 0FCF */ OP_NONE, + + /* 0FD0 */ OP_MODRM, + /* 0FD1 */ OP_MODRM, + /* 0FD2 */ OP_MODRM, + /* 0FD3 */ OP_MODRM, + /* 0FD4 */ OP_MODRM, + /* 0FD5 */ OP_MODRM, + /* 0FD6 */ OP_MODRM, + /* 0FD7 */ OP_MODRM, + /* 0FD8 */ OP_MODRM, + /* 0FD9 */ OP_MODRM, + /* 0FDA */ OP_MODRM, + /* 0FDB */ OP_MODRM, + /* 0FDC */ OP_MODRM, + /* 0FDD */ OP_MODRM, + /* 0FDE */ OP_MODRM, + /* 0FDF */ OP_MODRM, + + /* 0FE0 */ OP_MODRM, + /* 0FE1 */ OP_MODRM, + /* 0FE2 */ OP_MODRM, + /* 0FE3 */ OP_MODRM, + /* 0FE4 */ OP_MODRM, + /* 0FE5 */ OP_MODRM, + /* 0FE6 */ OP_MODRM, + /* 0FE7 */ OP_MODRM, + /* 0FE8 */ OP_MODRM, + /* 0FE9 */ OP_MODRM, + /* 0FEA */ OP_MODRM, + /* 0FEB */ OP_MODRM, + /* 0FEC */ OP_MODRM, + /* 0FED */ OP_MODRM, + /* 0FEE */ OP_MODRM, + /* 0FEF */ OP_MODRM, + + /* 0FF0 */ OP_MODRM, + /* 0FF1 */ OP_MODRM, + /* 0FF2 */ OP_MODRM, + /* 0FF3 */ OP_MODRM, + /* 0FF4 */ OP_MODRM, + /* 0FF5 */ OP_MODRM, + /* 0FF6 */ OP_MODRM, + /* 0FF7 */ OP_MODRM, + /* 0FF8 */ OP_MODRM, + /* 0FF9 */ OP_MODRM, + /* 0FFA */ OP_MODRM, + /* 0FFB */ OP_MODRM, + /* 0FFC */ OP_MODRM, + /* 0FFD */ OP_MODRM, + /* 0FFE */ OP_MODRM, + /* 0FFF */ OP_INVALID, + }; + + static byte cflags(byte op) + { + return flags_table[op]; + } + + static byte cflags_ex(byte op) + { + return flags_table_ex[op]; + } + + /// + /// 计算大于等于 size 字节的最少指令的长度 + /// + /// + /// + public static uint SizeofMinNumByte(void* code, int size) + { + if (IsAndroidARM()) + { + if (IsIL2CPP()) + return CalcARMThumbMinLen(code, size); + else + return (uint)((size + 3) / 4) * 4; // 此为 jit 模式下的长度 + } + + UInt32 Length; + byte* pOpcode; + UInt32 Result = 0; + ldasm_data data = new ldasm_data(); + bool is64 = IntPtr.Size == 8; + do + { + Length = ldasm(code, data, is64); + + pOpcode = (byte*)code + data.opcd_offset; + Result += Length; + if (Result >= size) + break; + if ((Length == 1) && (*pOpcode == 0xCC)) + break; + + code = (void*)((ulong)code + Length); + + } while (Length>0); + + return Result; + } + + public static bool IsAndroidARM() + { + return UnityEngine.SystemInfo.operatingSystem.Contains("Android") + && UnityEngine.SystemInfo.processorType.Contains("ARM"); + } + + public static bool IsiOS() + { + return UnityEngine.SystemInfo.operatingSystem.ToLower().Contains("ios"); + } + + public static bool IsIL2CPP() + { + bool isIL2CPP = false; + try + { + byte[] ilBody = typeof(LDasm).GetMethod("IsIL2CPP").GetMethodBody().GetILAsByteArray(); + if (ilBody == null || ilBody.Length == 0) + isIL2CPP = true; + } + catch + { + isIL2CPP = true; + } + return isIL2CPP; + } + + /// + /// 计算 thumb 指令长度 + /// + /// + /// + /// + public static uint CalcARMThumbMinLen(void* code, int size) + { + uint len = 0; + + ushort* ins = (ushort*)code; + while (true) + { + if (len >= size) + return len; + + if (((*ins >> 13) & 3) == 3) + { + ins += 2; + len += 4; + } + else + { + ins++; + len += 2; + } + } + } + + static uint ldasm(void* code, ldasm_data ld, bool is64) + { + byte* p = (byte*)code; + byte s, op, f; + byte rexw, pr_66, pr_67; + + s = rexw = pr_66 = pr_67 = 0; + + /* dummy check */ + if ((int)code==0) + return 0; + + /* init output data */ + //memset(ld, 0, sizeof(ldasm_data)); + + /* phase 1: parse prefixies */ + while ((cflags(*p) & OP_PREFIX)!=0) + { + if (*p == 0x66) + pr_66 = 1; + if (*p == 0x67) + pr_67 = 1; + p++; s++; + ld.flags |= F_PREFIX; + if (s == 15) + { + ld.flags |= F_INVALID; + return s; + } + } + + /* parse REX prefix */ + if (is64 && *p >> 4 == 4) + { + ld.rex = *p; + rexw = (byte)((ld.rex >> 3) & 1); + ld.flags |= F_REX; + p++; s++; + } + + /* can be only one REX prefix */ + if (is64 && *p >> 4 == 4) + { + ld.flags |= F_INVALID; + s++; + return s; + } + + /* phase 2: parse opcode */ + ld.opcd_offset = (byte)(p - (byte*)code); + ld.opcd_size = 1; + op = *p++; s++; + + /* is 2 byte opcode? */ + if (op == 0x0F) + { + op = *p++; s++; + ld.opcd_size++; + f = cflags_ex(op); + if ((f & OP_INVALID)!=0) + { + ld.flags |= F_INVALID; + return s; + } + /* for SSE instructions */ + if ((f & OP_EXTENDED)!=0) + { + op = *p++; s++; + ld.opcd_size++; + } + } + else { + f = cflags(op); + /* pr_66 = pr_67 for opcodes A0-A3 */ + if (op >= 0xA0 && op <= 0xA3) + pr_66 = pr_67; + } + + /* phase 3: parse ModR/M, SIB and DISP */ + if ((f & OP_MODRM)!=0) + { + byte mod = (byte)(*p >> 6); + byte ro = (byte)((*p & 0x38) >> 3); + byte rm = (byte)(*p & 7); + + ld.modrm = *p++; s++; + ld.flags |= F_MODRM; + + /* in F6,F7 opcodes immediate data present if R/O == 0 */ + if (op == 0xF6 && (ro == 0 || ro == 1)) + f |= OP_DATA_I8; + if (op == 0xF7 && (ro == 0 || ro == 1)) + f |= OP_DATA_I16_I32_I64; + + /* is SIB byte exist? */ + if (mod != 3 && rm == 4 && !(!is64 && pr_67!=0)) + { + ld.sib = *p++; s++; + ld.flags |= F_SIB; + + /* if base == 5 and mod == 0 */ + if ((ld.sib & 7) == 5 && mod == 0) + { + ld.disp_size = 4; + } + } + + switch (mod) + { + case 0: + if (is64) + { + if (rm == 5) + { + ld.disp_size = 4; + if (is64) + ld.flags |= F_RELATIVE; + } + } + else if (pr_67!=0) + { + if (rm == 6) + ld.disp_size = 2; + } + else { + if (rm == 5) + ld.disp_size = 4; + } + break; + case 1: + ld.disp_size = 1; + break; + case 2: + if (is64) + ld.disp_size = 4; + else if (pr_67!=0) + ld.disp_size = 2; + else + ld.disp_size = 4; + break; + } + + if (ld.disp_size>0) + { + ld.disp_offset = (byte)(p - (byte*)code); + p += ld.disp_size; + s += ld.disp_size; + ld.flags |= F_DISP; + } + } + + /* phase 4: parse immediate data */ + if (rexw!=0 && (f & OP_DATA_I16_I32_I64)!=0) + ld.imm_size = 8; + else if ((f & OP_DATA_I16_I32)!=0 || (f & OP_DATA_I16_I32_I64)!=0) + ld.imm_size = (byte)(4 - (pr_66 << 1)); + + /* if exist, add OP_DATA_I16 and OP_DATA_I8 size */ + ld.imm_size += (byte)(f & 3); + + if ((ld.imm_size)!=0) + { + s += ld.imm_size; + ld.imm_offset = (byte)(p - (byte*)code); + ld.flags |= F_IMM; + if ((f & OP_RELATIVE)!=0) + ld.flags |= F_RELATIVE; + } + + /* instruction is too long */ + if (s > 15) + ld.flags |= F_INVALID; + + return s; + } + } +} +#endif diff --git a/Assets/Slua/LuaProfiler/UnityHook/LDasm.cs.meta b/Assets/Slua/LuaProfiler/UnityHook/LDasm.cs.meta new file mode 100644 index 00000000..969a5a17 --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook/LDasm.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c561c9729c367e4fbef63f4ec56f268 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs b/Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs new file mode 100644 index 00000000..523bc712 --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs @@ -0,0 +1,268 @@ +/* + Desc: 一个可以运行时 Hook Mono 方法的工具,让你可以无需修改 UnityEditor.dll 等文件就可以重写其函数功能 + Author: Misaka Mikoto + Github: https://github.com/easy66/MonoHooker + */ +#if UNITY_EDITOR +using DotNetDetour; +using System; +using System.Reflection; + + +/* +>>>>>>> 原始 UnityEditor.LogEntries.Clear 一型(.net 4.x) +0000000000403A00 < | 55 | push rbp | +0000000000403A01 | 48 8B EC | mov rbp,rsp | +0000000000403A04 | 48 81 EC 80 00 00 00 | sub rsp,80 | +0000000000403A0B | 48 89 65 B0 | mov qword ptr ss:[rbp-50],rsp | +0000000000403A0F | 48 89 6D A8 | mov qword ptr ss:[rbp-58],rbp | +0000000000403A13 | 48 89 5D C8 | mov qword ptr ss:[rbp-38],rbx | << +0000000000403A17 | 48 89 75 D0 | mov qword ptr ss:[rbp-30],rsi | +0000000000403A1B | 48 89 7D D8 | mov qword ptr ss:[rbp-28],rdi | +0000000000403A1F | 4C 89 65 E0 | mov qword ptr ss:[rbp-20],r12 | +0000000000403A23 | 4C 89 6D E8 | mov qword ptr ss:[rbp-18],r13 | +0000000000403A27 | 4C 89 75 F0 | mov qword ptr ss:[rbp-10],r14 | +0000000000403A2B | 4C 89 7D F8 | mov qword ptr ss:[rbp-8],r15 | +0000000000403A2F | 49 BB 00 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D00 | +0000000000403A39 | 4C 89 5D B8 | mov qword ptr ss:[rbp-48],r11 | +0000000000403A3D | 49 BB 08 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D08 | + + +>>>>>>> 二型(.net 2.x) +0000000000403E8F | 55 | push rbp | +0000000000403E90 | 48 8B EC | mov rbp,rsp | +0000000000403E93 | 48 83 EC 70 | sub rsp,70 | +0000000000403E97 | 48 89 65 C8 | mov qword ptr ss:[rbp-38],rsp | +0000000000403E9B | 48 89 5D B8 | mov qword ptr ss:[rbp-48],rbx | +0000000000403E9F | 48 89 6D C0 | mov qword ptr ss:[rbp-40],rbp | <<(16) +0000000000403EA3 | 48 89 75 F8 | mov qword ptr ss:[rbp-8],rsi | +0000000000403EA7 | 48 89 7D F0 | mov qword ptr ss:[rbp-10],rdi | +0000000000403EAB | 4C 89 65 D0 | mov qword ptr ss:[rbp-30],r12 | +0000000000403EAF | 4C 89 6D D8 | mov qword ptr ss:[rbp-28],r13 | +0000000000403EB3 | 4C 89 75 E0 | mov qword ptr ss:[rbp-20],r14 | +0000000000403EB7 | 4C 89 7D E8 | mov qword ptr ss:[rbp-18],r15 | +0000000000403EBB | 48 83 EC 20 | sub rsp,20 | +0000000000403EBF | 49 BB 18 3F 15 13 FE 7F 00 00 | mov r11,7FFE13153F18 | +0000000000403EC9 | 41 FF D3 | call r11 | +0000000000403ECC | 48 83 C4 20 | add rsp,20 | + + */ + + +/// +/// Hook 类,用来 Hook 某个 C# 方法 +/// +public unsafe class MethodHooker +{ + public bool isHooked { get; private set; } + + private MethodBase _targetMethod; // 需要被hook的目标方法 + private MethodBase _replacementMethod; // 被hook后的替代方法 + private MethodBase _proxyMethod; // 目标方法的代理方法(可以通过此方法调用被hook后的原方法) + + private IntPtr _targetPtr; // 目标方法被 jit 后的地址指针 + private IntPtr _replacementPtr; + private IntPtr _proxyPtr; + + private static readonly byte[] s_jmpBuff; + private static readonly byte[] s_jmpBuff_32 = new byte[] // 6 bytes + { + 0x68, 0x00, 0x00, 0x00, 0x00, // push $val + 0xC3 // ret + }; + private static readonly byte[] s_jmpBuff_64 = new byte[] // 14 bytes + { + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp [rip] + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // $val + }; + private static readonly byte[] s_jmpBuff_arm32_arm = new byte[] // 8 bytes + { + 0x04, 0xF0, 0x1F, 0xE5, // LDR PC, [PC, #-4] + 0x00, 0x00, 0x00, 0x00, // $val + }; + private static readonly byte[] s_jmpBuff_arm32_thumb = new byte[] // 38 bytes + { + 0x00, 0xB5, // PUSH {LR} + 0x10, 0xB4, // PUSH {R0} + 0x03, 0xB4, // PUSH {R0, R1} + 0x78, 0x46, // MOV R0, PC + 0x16, 0x30, // ADD R0, #0x16 + 0x00, 0x68, // LDR R0, [R0, #0x00] + 0x69, 0x46, // MOV R1, SP + 0x08, 0x31, // ADD R1, #0x08 + 0x08, 0x60, // STR R0, [R1, #0x00] + 0x79, 0x46, // MOV R1, PC + 0x0E, 0x31, // ADD R1, #0x0E + 0x8E, 0x46, // MOV LR, R1 + 0x01, 0xBC, // POP {R0} + 0x02, 0xBC, // POP {R1} + 0x00, 0xBD, // POP {PC} + 0xC0, 0x46, // NOP + + 0x00, 0x00, 0x00, 0x00, // $val + 0x00, 0xBD, // POP {PC} + }; + private static readonly byte[] s_jmpBuff_arm64 = new byte[] + { + 0x04, 0xF0, 0x1F, 0xE5, // LDR PC, [PC, #-4] + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // $val + }; + private static readonly int s_addrOffset; + + + private byte[] _jmpBuff; + private byte[] _proxyBuff; + + static MethodHooker() + { + if (LDasm.IsAndroidARM()) + { + s_addrOffset = 4; + if (IntPtr.Size == 4) + { + if (!LDasm.IsIL2CPP()) + s_jmpBuff = s_jmpBuff_arm32_arm; + else + { + s_jmpBuff = s_jmpBuff_arm32_thumb; + s_addrOffset = 32; + } + + } + else + s_jmpBuff = s_jmpBuff_arm64; + } + else + { + if (IntPtr.Size == 4) + { + s_jmpBuff = s_jmpBuff_32; + s_addrOffset = 1; + } + else + { + s_jmpBuff = s_jmpBuff_64; + s_addrOffset = 6; + } + } + + } + + /// + /// 创建一个 Hooker + /// + /// 需要替换的目标方法 + /// 准备好的替换方法 + /// 如果还需要调用原始目标方法,可以通过此参数的方法调用,如果不需要可以填 null + public MethodHooker(MethodBase targetMethod, MethodBase replacementMethod, MethodBase proxyMethod = null) + { + _targetMethod = targetMethod; + _replacementMethod = replacementMethod; + _proxyMethod = proxyMethod; + + _targetPtr = _targetMethod.MethodHandle.GetFunctionPointer(); + _replacementPtr = _replacementMethod.MethodHandle.GetFunctionPointer(); + if(proxyMethod != null) + _proxyPtr = _proxyMethod.MethodHandle.GetFunctionPointer(); + + _jmpBuff = new byte[s_jmpBuff.Length]; + } + + public void Install() + { + if(LDasm.IsiOS()) // iOS 不支持修改 code 所在区域 page + return; + + if (isHooked) + return; + + HookerPool.AddHooker(_targetMethod, this); + + InitProxyBuff(); + BackupHeader(); + PatchTargetMethod(); + PatchProxyMethod(); + + isHooked = true; + } + + public void Uninstall() + { + if (!isHooked) + return; + + byte* pTarget = (byte*)_targetPtr.ToPointer(); + for (int i = 0; i < _proxyBuff.Length; i++) + *pTarget++ = _proxyBuff[i]; + + isHooked = false; + HookerPool.RemoveHooker(_targetMethod); + } + +#region private + /// + /// 根据具体指令填充 ProxyBuff + /// + /// + private void InitProxyBuff() + { + byte* pTarget = (byte*)_targetPtr.ToPointer(); + + uint requireSize = DotNetDetour.LDasm.SizeofMinNumByte(pTarget, s_jmpBuff.Length); + _proxyBuff = new byte[requireSize]; + } + + /// + /// 备份原始方法头 + /// + private void BackupHeader() + { + byte* pTarget = (byte*)_targetPtr.ToPointer(); + for (int i = 0; i < _proxyBuff.Length; i++) + _proxyBuff[i] = *pTarget++; + } + + // 将原始方法跳转到我们的方法 + private void PatchTargetMethod() + { + Array.Copy(s_jmpBuff, _jmpBuff, _jmpBuff.Length); + fixed (byte* p = &_jmpBuff[s_addrOffset]) + { + if(IntPtr.Size == 4) + *((uint*)p) = (uint)_replacementPtr.ToInt32(); + else + *((ulong*)p) = (ulong)_replacementPtr.ToInt64(); + } + + byte* pTarget = (byte*)_targetPtr.ToPointer(); + for (int i = 0; i < _jmpBuff.Length; i++) + *pTarget++ = _jmpBuff[i]; + } + + /// + /// 让 Proxy 方法的功能变成跳转向原始方法 + /// + private void PatchProxyMethod() + { + if (_proxyPtr == IntPtr.Zero) + return; + + byte* pProxy = (byte*)_proxyPtr.ToPointer(); + for (int i = 0; i < _proxyBuff.Length; i++) // 先填充头 + *pProxy++ = _proxyBuff[i]; + + fixed (byte* p = &_jmpBuff[s_addrOffset]) // 将跳转指向原函数跳过头的位置 + { + if (IntPtr.Size == 4) + * ((uint*)p) = (uint)_targetPtr.ToInt32() + (uint)_proxyBuff.Length; + else + *((ulong*)p) = (ulong)_targetPtr.ToInt64() + (ulong)_proxyBuff.Length; + } + + for (int i = 0; i < _jmpBuff.Length; i++) // 再填充跳转 + *pProxy++ = _jmpBuff[i]; + } + +#endregion +} +#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs.meta b/Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs.meta new file mode 100644 index 00000000..007e62ca --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd0b8071cf434d6498160259e3829980 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs b/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs new file mode 100644 index 00000000..e672917e --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs @@ -0,0 +1,54 @@ +/* +* ============================================================================== +* Filename: StarUp +* Created: 2018/7/2 11:36:16 +* Author: エル・プサイ・コングルゥ +* Purpose: +* ============================================================================== +*/ + +#if UNITY_EDITOR +using System; +using System.Reflection; +using UnityEditor; + +[InitializeOnLoad] +public static class Startup +{ + private static MethodHooker hookNewLuaEnv; + + public static readonly string luaPath; + static Startup() + { + HookNewLuaEnv(); + } + + public static void HookNewLuaEnv() + { + if (hookNewLuaEnv == null) + { + Type envReplace = typeof(LuaEnvReplace); + Type typeEnv = typeof(SLua.LuaState); + var clickFun = typeEnv.GetConstructors()[0]; + MethodInfo clickReplace = envReplace.GetMethod("Ctor"); + MethodInfo clickProxy = envReplace.GetMethod("Proxy", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(clickFun, clickReplace, clickProxy); + + hookNewLuaEnv.Install(); + } + } + + public static class LuaEnvReplace + { + public static void Ctor(SLua.LuaState env) + { + Proxy(env); + MikuLuaProfiler.LuaProfiler.SetMainLuaEnv(env); + + } + public static void Proxy(SLua.LuaState env) + { + } + } +} +#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs.meta b/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs.meta new file mode 100644 index 00000000..d471a9fd --- /dev/null +++ b/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: e1d2def6afaf4bf468700da4f1b6e6cf +timeCreated: 1530502621 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Utils.meta b/Assets/Slua/LuaProfiler/Utils.meta new file mode 100644 index 00000000..61775c67 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Utils.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a1acd1215960f1148bd9d8b10beceba0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs b/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs new file mode 100644 index 00000000..bb8d632d --- /dev/null +++ b/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs @@ -0,0 +1,85 @@ +/* +* ============================================================================== +* Filename: LuaDeepProfilerSetting +* Created: 2018/7/13 14:29:22 +* Author: エル・プサイ・コングルゥ +* Purpose: +* ============================================================================== +*/ +#if UNITY_EDITOR +namespace MikuLuaProfiler +{ + using System.Collections.Generic; + using UnityEditor; + using UnityEngine; + + public class LuaDeepProfilerSetting : ScriptableObject + { + public const string SettingsAssetName = "LuaDeepProfilerSettings"; + private static LuaDeepProfilerSetting instance; + public static LuaDeepProfilerSetting Instance + { + get + { + if (instance == null) + { + instance = AssetDatabase.LoadAssetAtPath("Assets/" + SettingsAssetName + ".asset"); + if (instance == null) + { + Debug.Log("Lua Profiler: cannot find integration settings, creating default settings"); + instance = CreateInstance(); + instance.name = "Lua Profiler Integration Settings"; +#if UNITY_EDITOR + AssetDatabase.CreateAsset(instance, "Assets/" + SettingsAssetName + ".asset"); +#endif + } + } + return instance; + } + } + [SerializeField] + private bool m_isDeepProfiler = false; + public bool isDeepProfiler + { + get + { + return m_isDeepProfiler; + } + set + { + m_isDeepProfiler = value; + } + } + + + [SerializeField] + private bool m_stableGC = true; + public bool stableGC + { + get + { + return m_stableGC; + } + set + { + m_stableGC = value; + } + } + + [MenuItem("Tools/LuaProfilerSetting", priority = 10)] + public static void EditSettings() + { + //string text = System.IO.File.ReadAllText("Lua/TemplateCommon.lua"); + //text = MikuLuaProfiler.Parse.InsertSample(text, "Template"); + //System.IO.File.WriteAllText("TemplateCommon.lua", text); + + Selection.activeObject = Instance; +#if UNITY_2018_1_OR_NEWER + EditorApplication.ExecuteMenuItem("Window/General/Inspector"); +#else + EditorApplication.ExecuteMenuItem("Window/Inspector"); +#endif + } + } +} +#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs.meta b/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs.meta new file mode 100644 index 00000000..c144daa7 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 144cbe4d057341d479a8e09b5bf689dc +timeCreated: 1529329742 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs b/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs new file mode 100644 index 00000000..bad2bd64 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs @@ -0,0 +1,868 @@ +#pragma warning disable CS0219//故意在c#这里产生于lua那边的等量GC +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using UnityEngine; +#if UNITY_5_5_OR_NEWER +using UnityEngine.Profiling; +#endif +using SLua; +using LuaLib = SLua.LuaDLL; +using System.Runtime.InteropServices; +using System.Reflection; +using UnityEditorInternal; +using UnityEditor; + +namespace MikuLuaProfiler +{ + + [InitializeOnLoad] + static class HookSetup + { +#if !UNITY_2017_1_OR_NEWER + static bool isPlaying = false; +#endif + static HookSetup() + { +#if UNITY_2017_1_OR_NEWER + EditorApplication.playModeStateChanged += OnEditorPlaying; +#else + EditorApplication.playmodeStateChanged += () => + { + + if (isPlaying == true && EditorApplication.isPlaying == false) + { + LuaProfiler.SetMainLuaEnv(null); + } + + isPlaying = EditorApplication.isPlaying; + }; +#endif + } + +#if UNITY_2017_1_OR_NEWER + public static void OnEditorPlaying(PlayModeStateChange playModeStateChange) + { + if (playModeStateChange == PlayModeStateChange.ExitingPlayMode) + { + LuaProfiler.SetMainLuaEnv(null); + } + } +#endif + + #region hook + + #region hook tostring + + public class LuaDll + { + #region luastring + public static readonly Dictionary stringDict = new Dictionary(); + public static bool TryGetLuaString(IntPtr p, out string result) + { + + return stringDict.TryGetValue((long)p, out result); + } + public static void RefString(IntPtr strPoint, int index, string s, IntPtr L) + { + int oldTop = LuaLib.lua_gettop(L); + LuaLib.lua_pushvalue(L, index); + //把字符串ref了之后就不GC了 + LuaLib.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX); + LuaLib.lua_settop(L, oldTop); + stringDict[(long)strPoint] = s; + } + #endregion + + public static int luaL_loadbuffer(IntPtr L, byte[] buff, int size, string name) + { + if (LuaDeepProfilerSetting.Instance.isDeepProfiler + && LuaProfiler.mainEnv.L == L)//&& name != "chunk" + { + var utf8WithoutBom = new System.Text.UTF8Encoding(true); + string fileName = name.Replace("@", "").Replace("/", ".") + ".lua"; + string value = utf8WithoutBom.GetString(buff); + value = Parse.InsertSample(value, fileName); + + //System.IO.File.WriteAllText(fileName, value); + + buff = utf8WithoutBom.GetBytes(value); + size = buff.Length; + } + + return ProxyLoadbuffer(L, buff, size, name); + } + + public static int ProxyLoadbuffer(IntPtr L, byte[] buff, int size, string name) + { + return 0; + } + + public static string lua_tostring(IntPtr luaState, int index) + { + int strlen; + + IntPtr str = LuaLib.luaS_tolstring32(luaState, index, out strlen); // fix il2cpp 64 bit + string s = null; + if (TryGetLuaString(str, out s)) + { + return s; + } + + if (strlen > 0 && str != IntPtr.Zero) + { + s = Marshal.PtrToStringAnsi(str); + // fallback method + if (s == null) + { + byte[] b = new byte[strlen]; + Marshal.Copy(str, b, 0, strlen); + s = System.Text.Encoding.Default.GetString(b); + } + } + if (s != null) + { + RefString(str, index, s, luaState); + } + + return (s == null) ? string.Empty : s; + } + + public static string PoxyToString(IntPtr L, int index) + { + return null; + } + } + #endregion + + + #region hook profiler + public class Profiler + { + private static Stack m_Stack = new Stack(); + private static int m_currentFrame = 0; + public static void BeginSampleOnly(string name) + { + if (ProfilerDriver.deepProfiling) return; + if (Time.frameCount != m_currentFrame) + { + m_Stack.Clear(); + m_currentFrame = Time.frameCount; + } + m_Stack.Push(name); + ProxyBeginSample(name); + } + public static void BeginSample(string name, UnityEngine.Object targetObject) + { + if (ProfilerDriver.deepProfiling) return; + m_Stack.Push(name); + ProxyBeginSample(name, targetObject); + } + + public static void EndSample() + { + if (ProfilerDriver.deepProfiling) return; + if (m_Stack.Count <= 0) + { + return; + } + m_Stack.Pop(); + ProxyEndSample(); + } + + public static void ProxyBeginSample(string name) + { + } + public static void ProxyBeginSample(string name, UnityEngine.Object targetObject) + { + } + + public static void ProxyEndSample() + { + } + } + #endregion + + #region do hook + private static MethodHooker beginSampeOnly; + private static MethodHooker beginObjetSample; + private static MethodHooker endSample; + private static MethodHooker tostringHook; + private static MethodHooker loaderHook; + + private static bool m_hooked = false; + public static void HookLuaFuns() + { + if (m_hooked) return; + if (tostringHook == null) + { + Type typeLogReplace = typeof(LuaDll); + Type typeLog = typeof(LuaLib); + MethodInfo tostringFun = typeLog.GetMethod("lua_tostring"); + MethodInfo tostringReplace = typeLogReplace.GetMethod("lua_tostring"); + MethodInfo tostringProxy = typeLogReplace.GetMethod("ProxyToString"); + + tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); + tostringHook.Install(); + + tostringFun = typeLog.GetMethod("luaL_loadbuffer"); + tostringReplace = typeLogReplace.GetMethod("luaL_loadbuffer"); + tostringProxy = typeLogReplace.GetMethod("ProxyLoadbuffer"); + + tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); + tostringHook.Install(); + } + + if (beginSampeOnly == null) + { + Type typeTarget = typeof(UnityEngine.Profiling.Profiler); + Type typeReplace = typeof(Profiler); + + MethodInfo hookTarget = typeTarget.GetMethod("BeginSampleOnly", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + MethodInfo hookReplace = typeReplace.GetMethod("BeginSampleOnly"); + MethodInfo hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + beginSampeOnly = new MethodHooker(hookTarget, hookReplace, hookProxy); + beginSampeOnly.Install(); + + hookTarget = typeTarget.GetMethod("BeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); + hookReplace = typeReplace.GetMethod("BeginSample"); + hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); + beginObjetSample = new MethodHooker(hookTarget, hookReplace, hookProxy); + beginObjetSample.Install(); + + hookTarget = typeTarget.GetMethod("EndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); + hookReplace = typeReplace.GetMethod("EndSample"); + hookProxy = typeReplace.GetMethod("ProxyEndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); + endSample = new MethodHooker(hookTarget, hookReplace, hookProxy); + endSample.Install(); + } + + m_hooked = true; + } + + public static void Uninstall() + { + if (beginSampeOnly != null) + { + beginSampeOnly.Uninstall(); + beginSampeOnly = null; + } + if (beginObjetSample != null) + { + beginObjetSample.Uninstall(); + beginObjetSample = null; + } + if (endSample != null) + { + endSample.Uninstall(); + endSample = null; + } + if (tostringHook != null) + { + tostringHook.Uninstall(); + tostringHook = null; + } + if (loaderHook != null) + { + loaderHook.Uninstall(); + loaderHook = null; + } + + m_hooked = false; + } + #endregion + + #endregion + } + + public class LuaProfiler + { + public static LuaState mainEnv + { + get + { + return _mainEnv; + } + } + private static LuaState _mainEnv; + public static void SetMainLuaEnv(LuaState env) + { + //不支持多栈 + if (_mainEnv != null && env != null) return; + + _mainEnv = env; + if (LuaDeepProfilerSetting.Instance.isDeepProfiler) + { + if (env != null) + { + Lua_MikuLuaProfiler_LuaProfiler.reg(env.L); + env.doString(@" +BeginMikuSample = MikuLuaProfiler.LuaProfiler.BeginSample +EndMikuSample = MikuLuaProfiler.LuaProfiler.EndSample + +function miku_unpack_return_value(...) + EndMikuSample() + return ... +end +"); + HookSetup.HookLuaFuns(); + } + } + + if (env == null) + { + HookSetup.Uninstall(); + } + } + + public static string GetLuaMemory() + { + long result = 0; + if (_mainEnv != null) + { + try + { + result = GetLuaMemory(_mainEnv.L); + } + catch { } + } + + return GetMemoryString(result); + } + + public static long GetLuaMemory(IntPtr luaState) + { + long result = 0; + + result = LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNT, 0); + result = result * 1024 + LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNTB, 0); + + return result; + } + + public class Sample + { + private static ObjectPool samplePool = new ObjectPool(250); + public static Sample Create(float time, long memory, string name) + { + Sample s = samplePool.GetObject(); + s.currentTime = time; + s.currentLuaMemory = memory; + s.realCurrentLuaMemory = memory; + s.costGC = 0; + s.name = name; + s.costTime = 0; + s.childs.Clear(); + s._father = null; + s._fullName = null; + + return s; + } + + public void Restore() + { + for (int i = 0, imax = childs.Count; i < imax; i++) + { + childs[i].Restore(); + } + samplePool.Store(this); + } + + public int oneFrameCall + { + get + { + return 1; + } + } + public float currentTime { private set; get; } + public long realCurrentLuaMemory { private set; get; } + private string _name; + public string name + { + private set + { + _name = value; + } + get + { + return _name; + } + } + + private static Dictionary> m_fullNamePool = new Dictionary>(); + private string _fullName = null; + public string fullName + { + get + { + if (_father == null) return _name; + + if (_fullName == null) + { + Dictionary childDict; + if (!m_fullNamePool.TryGetValue(_father.fullName, out childDict)) + { + childDict = new Dictionary(); + m_fullNamePool.Add(_father.fullName, childDict); + } + + if (!childDict.TryGetValue(_name, out _fullName)) + { + string value = _name; + var f = _father; + while (f != null) + { + value = f.name + value; + f = f.fahter; + } + _fullName = value; + childDict[_name] = _fullName; + } + + return _fullName; + } + else + { + return _fullName; + } + } + } + //这玩意在统计的window里面没啥卵用 + public long currentLuaMemory { set; get; } + + private float _costTime; + public float costTime + { + set + { + _costTime = value; + } + get + { + float result = _costTime; + return result; + } + } + + private long _costGC; + public long costGC + { + set + { + _costGC = value; + } + get + { + return _costGC; + } + } + private Sample _father; + public Sample fahter + { + set + { + _father = value; + if (_father != null) + { + _father.childs.Add(this); + } + } + get + { + return _father; + } + } + + public readonly List childs = new List(256); + } + //开始采样时候的lua内存情况,因为中间有可能会有二次采样,所以要丢到一个盏中 + public static readonly List beginSampleMemoryStack = new List(); + + private static Action m_SampleEndAction; + + private static bool isDeep + { + get + { +#if UNITY_EDITOR + return ProfilerDriver.deepProfiling; +#else + return false; +#endif + } + } + public static void SetSampleEnd(Action action) + { + m_SampleEndAction = action; + } + public static void BeginSample(string name) + { +#if DEBUG + if (_mainEnv != null) + { + BeginSample(_mainEnv.L, name); + } +#endif + } + public static void BeginSample(IntPtr luaState) + { +#if DEBUG + BeginSample(luaState, "lua gc"); +#endif + } + private static int m_currentFrame = 0; + public static void BeginSample(IntPtr luaState, string name) + { + if (m_currentFrame != Time.frameCount) + { + PopAllSampleWhenLateUpdate(); + m_currentFrame = Time.frameCount; + } + +#if DEBUG + //if (beginSampleMemoryStack.Count == 0 && LuaDeepProfilerSetting.Instance.isDeepProfiler) + // LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCSTOP, 0); + + long memoryCount = GetLuaMemory(luaState); + Sample sample = Sample.Create(Time.realtimeSinceStartup, memoryCount, name); + + beginSampleMemoryStack.Add(sample); + if (!isDeep) + { + Profiler.BeginSample(name); + } +#endif + } + public static void PopAllSampleWhenLateUpdate() + { + for (int i = 0, imax = beginSampleMemoryStack.Count; i < imax; i++) + { + var item = beginSampleMemoryStack[i]; + if (item.fahter == null) + { + item.Restore(); + } + } + beginSampleMemoryStack.Clear(); + } + public static void EndSample() + { +#if DEBUG + if (_mainEnv != null) + { + EndSample(_mainEnv.L); + } +#endif + } + public static void EndSample(IntPtr luaState) + { +#if DEBUG + if (beginSampleMemoryStack.Count <= 0) + { + return; + } + int count = beginSampleMemoryStack.Count; + Sample sample = beginSampleMemoryStack[beginSampleMemoryStack.Count - 1]; + long oldMemoryCount = sample.currentLuaMemory; + beginSampleMemoryStack.RemoveAt(count - 1); + long nowMemoryCount = GetLuaMemory(luaState); + sample.fahter = count > 1 ? beginSampleMemoryStack[count - 2] : null; + + if (!isDeep) + { + long delta = nowMemoryCount - oldMemoryCount; + + long tmpDelta = delta; + if (delta > 0) + { + delta = Math.Max(delta - 40, 0);//byte[0] 的字节占用是40 + byte[] luagc = new byte[delta]; + } + for (int i = 0, imax = beginSampleMemoryStack.Count; i < imax; i++) + { + Sample s = beginSampleMemoryStack[i]; + s.currentLuaMemory += tmpDelta; + beginSampleMemoryStack[i] = s; + } + Profiler.EndSample(); + } + + sample.costTime = Time.realtimeSinceStartup - sample.currentTime; + var gc = nowMemoryCount - sample.realCurrentLuaMemory; + sample.costGC = gc > 0 ? gc : 0; + //if (beginSampleMemoryStack.Count == 0 && LuaDeepProfilerSetting.Instance.isDeepProfiler) + //{ + // LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCRESTART, 0); + // LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCCOLLECT, 0); + //} + + + if (m_SampleEndAction != null && beginSampleMemoryStack.Count == 0) + { + m_SampleEndAction(sample); + } + + if (sample.fahter == null) + { + sample.Restore(); + } +#endif + } + + const long MaxB = 1024; + const long MaxK = MaxB * 1024; + const long MaxM = MaxK * 1024; + const long MaxG = MaxM * 1024; + + public static string GetMemoryString(long value, string unit = "B") + { + string result = null; + if (value < MaxB) + { + result = string.Format("{0}{1}", value, unit); + } + else if (value < MaxK) + { + result = string.Format("{0:N2}K{1}", (float)value / MaxB, unit); + } + else if (value < MaxM) + { + result = string.Format("{0:N2}M{1}", (float)value / MaxK, unit); + } + else if (value < MaxG) + { + result = string.Format("{0:N2}G{1}", (float)value / MaxM, unit); + } + return result; + } + } + + #region bind + [UnityEngine.Scripting.Preserve] + public class Lua_MikuLuaProfiler_LuaProfiler : LuaObject + { + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int constructor(IntPtr l) + { + try + { + MikuLuaProfiler.LuaProfiler o; + o = new MikuLuaProfiler.LuaProfiler(); + pushValue(l, true); + pushValue(l, o); + return 2; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int GetLuaMemory_s(IntPtr l) + { + try + { + int argc = LuaDLL.lua_gettop(l); + if (argc == 0) + { + var ret = MikuLuaProfiler.LuaProfiler.GetLuaMemory(); + pushValue(l, true); + pushValue(l, ret); + return 2; + } + else if (argc == 1) + { + System.IntPtr a1; + checkType(l, 1, out a1); + var ret = MikuLuaProfiler.LuaProfiler.GetLuaMemory(a1); + pushValue(l, true); + pushValue(l, ret); + return 2; + } + pushValue(l, false); + LuaDLL.lua_pushstring(l, "No matched override function GetLuaMemory to call"); + return 2; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int SetSampleEnd_s(IntPtr l) + { + try + { + System.Action a1; + checkDelegate(l, 1, out a1); + MikuLuaProfiler.LuaProfiler.SetSampleEnd(a1); + pushValue(l, true); + return 1; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int BeginSample_s(IntPtr l) + { + try + { + int argc = LuaDLL.lua_gettop(l); + if (matchType(l, argc, 1, typeof(System.IntPtr))) + { + System.IntPtr a1; + checkType(l, 1, out a1); + MikuLuaProfiler.LuaProfiler.BeginSample(a1); + pushValue(l, true); + return 1; + } + else if (matchType(l, argc, 1, typeof(string))) + { + System.String a1; + checkType(l, 1, out a1); + MikuLuaProfiler.LuaProfiler.BeginSample(a1); + pushValue(l, true); + return 1; + } + else if (argc == 2) + { + System.IntPtr a1; + checkType(l, 1, out a1); + System.String a2; + checkType(l, 2, out a2); + MikuLuaProfiler.LuaProfiler.BeginSample(a1, a2); + pushValue(l, true); + return 1; + } + pushValue(l, false); + LuaDLL.lua_pushstring(l, "No matched override function BeginSample to call"); + return 2; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int PopAllSampleWhenLateUpdate_s(IntPtr l) + { + try + { + MikuLuaProfiler.LuaProfiler.PopAllSampleWhenLateUpdate(); + pushValue(l, true); + return 1; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int EndSample_s(IntPtr l) + { + try + { + int argc = LuaDLL.lua_gettop(l); + if (argc == 0) + { + MikuLuaProfiler.LuaProfiler.EndSample(); + pushValue(l, true); + return 1; + } + else if (argc == 1) + { + System.IntPtr a1; + checkType(l, 1, out a1); + MikuLuaProfiler.LuaProfiler.EndSample(a1); + pushValue(l, true); + return 1; + } + pushValue(l, false); + LuaDLL.lua_pushstring(l, "No matched override function EndSample to call"); + return 2; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int GetMemoryString_s(IntPtr l) + { + try + { + System.Int64 a1; + checkType(l, 1, out a1); + System.String a2; + checkType(l, 2, out a2); + var ret = MikuLuaProfiler.LuaProfiler.GetMemoryString(a1, a2); + pushValue(l, true); + pushValue(l, ret); + return 2; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int get_beginSampleMemoryStack(IntPtr l) + { + try + { + pushValue(l, true); + pushValue(l, MikuLuaProfiler.LuaProfiler.beginSampleMemoryStack); + return 2; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int get_mainEnv(IntPtr l) + { + try + { + pushValue(l, true); + pushValue(l, MikuLuaProfiler.LuaProfiler.mainEnv); + return 2; + } + catch (Exception e) + { + return error(l, e); + } + } + [UnityEngine.Scripting.Preserve] + static public void reg(IntPtr l) + { + getTypeTable(l, "MikuLuaProfiler.LuaProfiler"); + addMember(l, GetLuaMemory_s); + addMember(l, SetSampleEnd_s); + addMember(l, BeginSample_s); + addMember(l, PopAllSampleWhenLateUpdate_s); + addMember(l, EndSample_s); + addMember(l, GetMemoryString_s); + addMember(l, "beginSampleMemoryStack", get_beginSampleMemoryStack, null, false); + addMember(l, "mainEnv", get_mainEnv, null, false); + createTypeMetatable(l, constructor, typeof(MikuLuaProfiler.LuaProfiler)); + } + } + #endregion + +} + +#endif + diff --git a/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs.meta b/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs.meta new file mode 100644 index 00000000..5959b682 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 352e62d8c1f35874f83c198dcbe84d8f +timeCreated: 1527823831 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs b/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs new file mode 100644 index 00000000..10109148 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs @@ -0,0 +1,170 @@ +/* + * 对象池 + */ +#if UNITY_EDITOR +using System; +using System.Collections; +using System.Collections.Generic; + +namespace MikuLuaProfiler +{ + public class ObjectPool where T : class, new() + { + public delegate T CreateFunc(); + + public ObjectPool() + { + + } + public ObjectPool(int poolSize, CreateFunc createFunc = null, Action resetAction = null) + { + Init(poolSize, createFunc, resetAction); + } + public T GetObject() + { + if (m_objStack.Count > 0) + { + T t = m_objStack.Pop(); + if (m_resetAction != null) + { + m_resetAction(t); + } + return t; + } + return (m_createFunc != null) ? m_createFunc() : new T(); + } + + public void Init(int poolSize, CreateFunc createFunc = null, Action resetAction = null) + { + m_objStack = new Stack(); + m_resetAction = resetAction; + m_createFunc = createFunc; + for (int i = 0; i < poolSize; i++) + { + T item = (m_createFunc != null) ? m_createFunc() : new T(); + m_objStack.Push(item); + } + } + + public void Store(T obj) + { + if (obj == null) + return; + if (m_resetAction != null) + m_resetAction(obj); + m_objStack.Push(obj); + } + + // 少用,调用这个池的作用就没有了 + public void Clear() + { + if (m_objStack != null) + m_objStack.Clear(); + } + + public int Count + { + get + { + if (m_objStack == null) + return 0; + return m_objStack.Count; + } + } + + public Stack.Enumerator GetIter() + { + if (m_objStack == null) + return new Stack.Enumerator(); + return m_objStack.GetEnumerator(); + } + + private Stack m_objStack = null; + private Action m_resetAction = null; + private CreateFunc m_createFunc = null; + } + + public class ListObjectPool where T : class + { + public delegate T CreateFunc(); + + public ListObjectPool(int poolSize, CreateFunc createFunc, Action resetAction = null) + { + Init(poolSize, createFunc, resetAction); + } + public T GetObject() + { + T t; + if (m_topIndex >= 0) + { + t = m_objStack[m_topIndex]; + m_topIndex--; + if (m_resetAction != null) + { + m_resetAction(t); + } + + } + else + { + t = m_createFunc(); + m_objStack.Add(t); + } + return t; + } + + public void Init(int poolSize, CreateFunc createFunc = null, Action resetAction = null) + { + m_objStack = new List(); + m_resetAction = resetAction; + m_createFunc = createFunc; + for (int i = 0; i < poolSize; i++) + { + T item = m_createFunc(); + m_objStack.Add(item); + } + m_topIndex = poolSize - 1; + } + + public void Store() + { + for (int i = Math.Max(0, m_topIndex); i < m_objStack.Count; i++) + { + if (m_resetAction != null) + { + m_resetAction(m_objStack[i]); + } + } + m_topIndex = m_objStack.Count - 1; + } + + // 少用,调用这个池的作用就没有了 + public void Clear() + { + if (m_objStack != null) + m_objStack.Clear(); + } + + public int Count + { + get + { + if (m_objStack == null) + return 0; + return m_objStack.Count; + } + } + + public List.Enumerator GetIter() + { + if (m_objStack == null) + return new List.Enumerator(); + return m_objStack.GetEnumerator(); + } + private int m_topIndex = 0; + private List m_objStack = null; + private Action m_resetAction = null; + private CreateFunc m_createFunc = null; + } +} +#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs.meta b/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs.meta new file mode 100644 index 00000000..19fea857 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 047a18bfa9169b94da0db4aa973b9980 +timeCreated: 1468553547 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 2126363062ced75bf9c51dc10abec4efe79b8cd0 Mon Sep 17 00:00:00 2001 From: ElPsyCongree <345036769@qq.com> Date: Fri, 16 Nov 2018 18:59:24 +0800 Subject: [PATCH 2/9] bug fix --- .../Editor/TreeView/LuaProfilerWindow.cs | 1 + .../LuaProfiler/Utils/LuaDeepProfilerSetting.cs | 2 +- README.md | 4 ++++ profiler.png | Bin 0 -> 44660 bytes 4 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 profiler.png diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs index a6f21ed7..822be412 100644 --- a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs +++ b/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs @@ -25,6 +25,7 @@ void OnEnable() if (m_TreeViewState == null) m_TreeViewState = new TreeViewState(); + LuaProfilerTreeView.m_nodeDict.Clear(); m_TreeView = new LuaProfilerTreeView(m_TreeViewState, position.width - 40); m_SearchField = new SearchField(); m_SearchField.downOrUpArrowKeyPressed += m_TreeView.SetFocusAndEnsureSelectedItem; diff --git a/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs b/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs index bb8d632d..41ee37a2 100644 --- a/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs +++ b/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs @@ -66,7 +66,7 @@ public bool stableGC } } - [MenuItem("Tools/LuaProfilerSetting", priority = 10)] + //[MenuItem("Tools/LuaProfilerSetting", priority = 10)] public static void EditSettings() { //string text = System.IO.File.ReadAllText("Lua/TemplateCommon.lua"); diff --git a/README.md b/README.md index 6197f0fd..29f8a0b5 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,10 @@ public class HelloWorld { ~~~~~~~~~~ +### LuaProfiler +if you want to profiler lua like C#,Tap "Window->Lua Profiler Window" to Open Profiler Window and click "Deep Profiler" Toggle +
+![](profiler.png) ### Benchmark diff --git a/profiler.png b/profiler.png new file mode 100644 index 0000000000000000000000000000000000000000..5e70a246620e591900bdf2e22fd224269521a479 GIT binary patch literal 44660 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

;uQw6{mn4J|6!m@~bVrT&HP^D6%X z-EED6x_bmDs`5ijr{?LRKA4`z%JJ#zTIQnrEn|_KHTDZ4iPA#_q59I_-#%Zq?T6wV zm(?*VOwBV5+}s~o&Eg_rqCT!9IwUs#EXAgVS0!oxHj!KRM2Fl5D}IP3c=Dlfn8&LZHG7@| zGmyZ*>${Z`dhM#2YaD%7VpmWQ8&5F{Dhz z3HzG|^((49kE%Vy5tZ7@Exe~kGotoAhj!<{<*wdUS2qtIb}W`*P|F3UsG+&bIIUjr za*{_AHrg}BD%Sb2Kdz_`SSJOZx^bg%C+eykUmcfdENoUDm&D{UE0@K}QG+_U2Md}v z7l|)FpuF}5g1hg_$#bHWz!7v6{pT~1N*T3Z5c~`4hB}vi{q%6dkUf?orZ*SS6H*vH zU`-Zh&ga|V$(mgkR~ZWYEnbfPYJktYF_aE}6cqQ<_UMbZ zb%(7#PBgUoUDl`k$?s{jfZt2C|IP0;o%oz-e`@o@wpC7UGPTng^U4P*x8}Dth5u}nDwF@;j+`Q262$iX1@4MW9olnwij{qu1qy<<&FtCa|YFhILC1Pyt54^nGfcv z(S~ypi&zRF6rk+?plX4t7qF!|9^9eZnS2+0>roFr#BM{(bIxhq)c^RE?@=VMAPYxM z(?7!SG`Gy~nQIXIPs_Ujss!m5B=Sv!<4n)Lf7YKLjKBffS3qlc>r{d%F3e&x$A5Pm z1NmNHUb2(YKVuyAfBZiPbf6T4vnQ2b9g^$BpN*H_$M@+ZNTZ$p0q{-3;ITc+BSZr+ zCE$Z^4aRdfgEb5hpMrzt#6v{O{JT{^PY*6Y7*zl8{E&P$pEy?s_7)%tcAtcWjZIBO z73U3{ydZuC_8Mxnb092#ZQWm0#Z;lH&%9a+~L043zjtFe_Lr}+?3YrPn^<~q!YC#9dlJ6(+h2m2Gi3FWM zk{%&~koH1qITap7`~DkgnWx2ym^Bf$g|0O@g`fMczh0AvcbRtnD3$Vlq1nvqHYMpz z;|=1d43W2r&f@TG^8BmuaL~3GNh;(aiwP6XXO*9TPrn-3SkB8}x4`iekFPoip4J$Y zwB%l#olMZ8YK&V>KqOt7XOPEy)~N*yVFa=xir1I#9E#=b4L%~A=GMseBR|Wr z)VHNXx7q};a0|o&ucneguJg!0lhGvO&T1qYBlABT^}~8Brlllu9zcy@Ol2}2P*Kx9 zAJn~9v{e-P(_w$~+hM=aM?Pc>~@zJ7S7>WdNudVY?>H3=EMLyi2- zyY>R}mvv+*KO2aqkv?b$b)+;~@R6d_KIEiEfFgR*YMS`zH5|)+Cw9 zmb#Oziizk}4SC+wvWBLYT&+c3Bz}j*H@RGN!*$UQ6W%PepQzUNZ>!OD%lm$Xi?k`S zQ=U_3h9>8hfPmrJ%1VDJ-|+9af~r68|6kN=7liq77BZN}V<)=mYb*?~q^o|BeI)S6Ea z59ZmvaIuDl1~4rgP=-8fQLU@3{piVbQpf@eEbh=!weDb{DAR58UTSiR$k-v0B8Z*D zqBS)!(E+(iVAuMd?y@W?RDgKf304(ZivaKO&ytT7e@yMjPwgL5wO=I};>LF!L8Isf z-v1HYQG-A&BPpUn@xZulq6M@xDFyTB_@dE(f6z zVuneEfLsNSdaC2k)NaawW6}@vL1Xug#t}?ESr88ROb$>PVw{%otWmWE&AU&9 zvKk8-Pb}j}`6(D%)Z_CY5>o+M=d2wGQBP38T|$Iby)v^|kBkvUsa`_vDRE#&(SJFYokLGVl*6A5qy3dV1KOn_{=&x6py|bNsg1;PTrDLaQHL6>t+JBAx#wGzkT*F>)v4w@qKBfD1g`D z1s$a~Ceo3r)ck5&rtd6ytIvzd^v$gv|1RXN#wjz&;+=gK9eMD&nw*+qtl9irei)1y zW5(l+gv%x3R|!TVXP==q(C76)F?nBLU?6C=;Wg2~C!x`8O*3x-W+TF4l3~~D%|w41 z%p8*SqQXM`+py0!X-{4&u3Kb$|8 zVu3{vljo$5m}IrYW~Fs$NV>m%G=uk7>dI};I(bjejej>XJAF}cF{b++=w+3uSA~T- z1)3Dd`e)cn8~pn5DXM|A(qgKpD;QacErCoap+mEph&FcmWj{zAk7Ti*fKX^QJ0X-c z;x4j@Iw?%x_VM=}32%OoX?!Z0#abROSv~P!=q_fwv4%XVnw#GP1oXuzyk_zPX5oXSR`;`j7QcG&Kk23)#zRE8K-$ zMu%=cFPre(Ya)f^0bccN%RSwNoqR@MKW!k?>gY8!Uj>JT;x zf$kZtBj2DPet_MOo?WY;RJxP}>C2&rSz4$NgsDsD!+8HKzt8I0Jm5JW|*l>+0OwV`oN{V}9*#_)9O zhVy)3?XkJP@qM;n`-y)B0L8^>V-1p?$Qsre`<#-gh>pln%h+ZK!BZCsg&z1oany zeGh{pu-I?dQq`+Hvoa7ty8^MbFr@pP{LC$zPS8R8Y239mO5q5m5Z=M%JE&bUVK?U& zM47}zbb2n}W*AnEml=`UKN9WJ4#D7dVl-}haelbe3}jLw|Rp$=LX3O(Eb}TxSf7)fkN+a^{1=;$47;91us+9iHu(B zP?_COEHeY!nwQMNby#OAn5D75JmF0p`I8)$cAJ3RUJ4bluzXK3Ao(IqAk15*J9c?M zSkd8dSu;mKB61?V(Zw+OQ_W19&!@CGg90g;n3yLRFgGcwlhvPWKj;Ne@$+X+eq>~P z*wFMmDIif#OL@}&9lN+F=P~bG<2xpl$H8}d)O9}(CyTvT`ahvPI>D@Vd^h%zq;M!0 zEX2@j!kI(IHCo8nbGP{!M=yK7G~tg-GM{? zC5r>>mqL9)FEySR!iD(iGd{C=MIS*3f%>Xzlm?F*k2`iqQF7=uwy03qVt_d#`EwqV zG*35bsyu^U+AJM+MrV(J)jA^2Gefv)*is3hEJQds+BVz0fy614vNvm|2|fkG6c9;2PY*oUR(kizn;1;z!t zGe$JjfiMNndN$kT+bgDog*lJZ{VOgiR#y8}pQcMx5k4o;%Y5?E*tm>FW3`Z{70;X+ zk?Z3T=XG$bg4_8;u@m);18Gfsbce&Yx-WHA`2#0DY4jIFinVU#Zc34#wLGtS6dBL= zOKH0b#oj($i7>Y7V!$gEh>ll%J;v(SBiS26Rexr^k#K3HGf|S4m;446=gsYyNY|{^ zy8_a@NL^|ancOLonO&epPCd^~sAQJX6kKR{xS)U6IS60KVVTrqCq0l!Bj-jh+LVss zh--Rk1&08^7IbnTt5pcmqB&RG(h6~C^>jC`laA>7`C0oC&gU-5g8)Yg2c}~iXSBfA zOy;X@L|TP;lVYNxg!gc2y>A8DAn~_L;=gh_=*C4?^}+t+PE>HXwy=7XPJBO{*LBA^ssL{j)R!NRnw&is66u4 zCw}!&s>@i{cT|cIy}Sesx_A|{dkD3kpk4SS=&v^j40R2bJyzDyq;!viGh9Q_ELF8F zko6*0!HsGhUs(M>AKDvP@-}i7RsR-y5`9JGa+quXV=)!nM~BWrC=MqH@}3I`ia`ZQ zBn@8zYEk-U5n%Qz`t9|crG$151_$9&n@}IAZbeO+gjhpILqTbTAdcQSix^9BrcO@H z;ewvI50t@58|u`b=LcK5Uj<^`3fac{E<^}fKlINGi9Q$Q86o2mqOj4hEeXB_CJCC@-@&4X?Tq1eIy)W?nESsj`aGA zh>=DoufRYq$~}=q}5zvWx8}ZS}0-E z(`?*WM$Sry*Rre`N_q)IHuMQ!g|NAx5z{)q#IqRDKi;?6Ecfg_{W@kI7Irxj74kt6 z6e6e!V*E99OrJh^pBI9fl+O?t8|~>dJq@S!xhwRdKc*g-*>ry*M`RXIUYo<={2EI4l9;M& zEGYT?&>7_x=_b^2iF26lJcL80A^JXFpdnV%&8em`%ePr)$2u-IQCT^)$!3n@v?Me$ z4Mz|D*0!Yn!4u_te(yQ&7WV={T(COo!6ZC3Ht!|xt(lzMrP9Lv3jb{v^RdEx&1^*_ zVi3X9t74YbeaTNpExOth6np6u97`B%*@8cuX1(3J;Y23QgWpe}_K&=#5QZcX`1el- z%F)F;;@!{+a-5Nh6HW-|=SR`d7PYSQuPq42Uopx-VGA2E;5>tie)6}Uw=a%p)Eglf zN87wT2l;tUWrS+jhYT`QO{z1XB*00A|M{ag3wzbn#C4~MaNZhJs^%>6?@#~pn=m-Q zUfs6w6W)#-X{>TO&=Jy-LcApS&mUhD^6z^)7xX{(@66-vG`BI=esJ>q{g*(zd`$@D z`F3N+g)i_Ir%*&*@_+t0fB$orM0>c^f==5+d}H0N95E7c4dI@J-8#M&HK*B})~k)e zRZ1fh6F#sB1bb4wnQrY0{hPmb>zs_s+s+1VM`v4TV>R{Ry|)b6fD*Xym3bdRf})qw#iVXyDP z-^^@mmg{XZeT+{E6lA;LcwT3KtHyxxL+S^WM^#B}7VEv0=Y?$*ahaGv*3az<4>3`l zP#GUq-gEh)?09VC+JvGYGw8GesDmQ$;(1%ksG<)8x8*}uYpzbOZRjmS3&qUAfBjPR zePB~Galv6B$1?034Bwr`i=ezj1kz0xUkEfLZYKiDoXv=aYXPj$2!#*dqM?wL?NH&Z_Uks#}i zBJjhi&O>Wm%Y#q{Ndq0QT!(_r0e?Y=;LXiVVOF*)jWy^%T?`j+wZOjdN_!ZHcKh?S zdtmVZ2N!khy$cMwy}bp{57;go1_=kWDSdo>3hG&-Ko=L-rUO~o6!?px=Au7;{_NY7 zF4Pa&*1!KqL9YselK7s0TA-tgtMr0-^Jl(@ny9Xp%eqU@ajoI*x{{-Jx}*KwhXGB# zdA(78dYnSZNlS1~ zbrTah6H$C5$|{Lyo~&@`y-|8i=>V<_xQkkl;W(wQBWMIwzjq%--~Os zPeMRoeqMkU3}WglV7w88h&@!6jx`jwnUs`dJeXj;{KXA87+|}hE8YS32mmpE0Wg39 zl}Z65X9QgJpbCj4_$dulEmv%8?2GL$-goY%rlu+?Q(&Y)8bC};43^MCAjS&u6Xg^H z$aaeYG=>%K&W>ieBrR!grhsMaYIc{Rg?W-|5&cFUgjAj}=`kw9=~_~z460IeDZ2W@ z3XzeKv9YniZ!!Rr7ou8eUEM2zNjy=<%uyIpp(s}jrm6@(H=bxt=lxl5SBZ&kE78u4 z>oBkmoA&6Eo_@-cO{Xe@L|UZ^4{|PH)@>!={#r1^EH%~71lN%Gq}B8@u7q#_^Ffq3 z2(dpC=0fG_A|t|>ReW9cc)u2AKA`w+H<2G&rPa6mEO+Z#3|iX`le^nRyH)H`v75u|0dzkJIakuu?w*^u^5bl6s{D=#T z>H-NncO_n(kVWjkmL6PWp>f38oIj3YMAPILQge1@Im>o?Ynm@ z19nmh{NQA>DE|8^P;pg-p}=q!7)^tN{sp^x9|s8y?VV1uv-QLM-IG9(xzp{@PhysZ zbuO-EfY8C+F5`Iq9n5G5wCpF_038RBu^li)!MI~#VWDV>@m+vfWOmWi{PPp~w%ZfX zPlNCJ-4_g1@cY|cZz}XU`~XyEm*Vc#B+6ZIhscgi+r{Uyy^?4@fZ~ef_WE$oPvQC# z$7}s#>`<1p7dyI}*$dhpa`7_e#Q{b05O5=;L}ErCmae<`Nu7J0wr@2fUkgla72TSi z64Q(7v|d)>#9%)M4-}yW1UZ#x{?gbIh;vsz@M_}3R9~Ey=@2MP>%6E=D&TBf|D4~2 z>Ox0%nmhC(+?#yGDXnxLv5h->N_1alSH$AUOQ`4vg9Jv;$Q(LR!dF~3KYm2kM3yzs zRr6SOMm@QxLnN$6^<2#$v}NYwTV6jVh#^yhBNBb&M>~>ezwoT&bn1`s@qs@IB(BiH zpO$KLoN6^XkiHbJuCCVgxpSf<`w|xu6O+tr8YWdLd)04eKL(B;bJ`it1;-7c?D4Gj z9xk_FkMRcx=OU|WY0;b)Lp$p3s{h&)eDCK#llPT-Q8XIE%bfNrr9X27EO|~in5gMk zS{9qZ;^SrLNJ~Gbdv(>diF*FjojppNrPu#GXq5dPCSk355f+TmxqvYry@ri}L5?+F zDeG}9a@ye(RwwKfpLi;AX&SK)_IW zeBljQHEK-?MIX%Zalp&nEYZPLuOp{CM0e#=X=`X*3k~!0p?VLg<}f{no#7(n78)mhS z`Y>(~0`BU!Z;9&d@`iOyy7JEuce6YAXEcfi3~TK6^-JDy+rJXjc1^XbdjTQlXW5pK zKL<4zsLxZ-OD6pGQd9Yz;iX^uMG@8{lk&M`I!;g{!w=cOB3~Wj(EL$$e5mRE|BL(MX_|gVWJ^o!?9qL+uT9~bxrGMCzUUl2zO`TyWE;Khm zg?xwV!=lgve@F$^Ub4rPxqD{wgnUqGS^A?5d63`;1mDj)9Ws@6c`*&B6W6_jnNar` z`{@w@b3=|LxLk!G$QYBUFX~VpfitCdIQqHm8~Cz8LB;jDrJ7?dV~Fr>uT7x7WGLX> zVsAcRP|+KWcb;~DbFhK z&7^$D7HdeSFyj}`fh+Sk589n{>C``U)PJ$^6a}_1iM%H)-TY`64|=CFJ3{&GZ6`eZ zvuzYsSFfwp;MqDDMmg3*bk0l(C~Xki>`dx=j~=7?(=HPQm6=1~sN`sm;H_qeF2oDO zTCR_u;eVp(5J7s`nS%i;Nx*R`(d%}wc&sv9azAm@HH)0ZQU;bXC2p(IDGm9MZUgjU z)lik(X+?iKo1fBoMByS@Nyz;8g<~OZQfNUzC-RnlJjJx_gyPCtT$vgkwTM$ve_hw7 z47$cn$~3X>J>ZP?``Z)PzVcJ|Fz@fLT$Bd-b9TzLKYJNU{<2!9&!ODcVM8_H*{g4L z@KsEEF6t6i#p>FqO0Qz?Jn>H>?Qu!@X^)*||c$2}%JUVm`>(lA}NUQ&xu7qF5pf5$LSX~{2c7UHG# z@lEt`a0J7z79wt$5~CV?6l(pMDUU5$9bZA8pXHgZTgj#|>z{_;ip!YI?VgGi(W;tv z&P5Nyv;d=uUfcI~x_$3Of2h#7)bLYDA<;tQm!Eu#Oql;WpTNSo+8WIV$+sS$V3|py zCt%5@vb|ZFnpS1LgeBYx?JM=@8T=UYcK`}NhZy}rO0ECxGQ;~e`Cyc!_rqe6>050T ztiSUW;ErsOc5mJ14>+ea`;e_QY=ix!YNUwCx(?ZQLy zfB9uxC5LGuG8Ejyu-|vo#ypK5HWtjl$`CR!*#a50DzIN%-Ld)B0yWx8n}CG%0{d@& z`uA@j`5MwQ@Y>cX$8D_Humj$zs)D5}tx@07(h_8p3r4{?4L~#l=Y&yyeMBPN?7xtx zwL21*+ixti4h{|u=$yW(5bUibB?wz-oQ^$_BkkITHNMdM?_LOyxo)~FyO|0K3K|#~ zfJ*XSd>@1bW0x!FF>nZ-Mf0PrdbKMU98!>5kq&8=mFV^B!pX%&c-fr8IcTzc=sV4U z8ZER00UE59x@+!QCH%4B@oPCtLCdL6DhKto;!|SA_i84d_Fr&53s(JPuksLEo#~p; zeIv&kM@6cw@Ef5JE_yOQjNZR-?w*3}s;Gq&R2{&7h2aIauaO?q(QsLzyH^7#s|+6$ zWWKLc5%dwfwVA-~KKFtwE4^ucV4nZA;q+f#fcRR(Q!K7=Zad`u+0>6`>M>jh^>MXE zi{2B@5!!lZ{H1ko{Ks?1!%Il$S3DXo=q;Mpnm$KNS$*_TqN8tGWGs8i+#zK;I}^Oe z3)89yb${x5G&+}L3T&*-2?ghN+CY#-1==);Lc2cDI_p*ms8(S^G3LFs5vC6Uib$ms zzRxf{9#a7ZIKY_Lg^ArgyEtpK*kJ$IZu^))9TUgz7>y%qhs$KOOyFTb13Q*yyUy^4 z;3S16u=hip7rM6lmJn2>fP)S9UI6G3!(FUYVW2D`|3S`u)dhY+SDOBz7@OU?nnYu@ z`RinF{?;wm_L}?U-H6@!&2Cay5~UA6X%p4W8rSgEc#gp#7S>%sh$b_#h$aIL zGBQKp;b5~}{sN+Vph#pcys^Ul+=4@)V6?9fv@gY@yvOS3+0`Jwb%Ul#=wgCLjl zI1QvHQd0z}S&xOlM(qX#FI%S-SrSX-gL2i%v;-j%2FPIn?Ph?a>Ej6#MPB_(DZf1V zzN`mI9n*~rFu`vf3v7XHIk?|ZAtBN8A{|!rEQ{c55C#~rH|bm|TF^LK+uPw}&Ly!e zW`SdYtBP1A&!Lp1|JL*3Szw2bLFZWgcrIJ#GsvYjLWIb-OY@l!ZT4 z@pV7H5{h;vK+GPg;m>2mU`-V-Mu)8$Uwm#+!-c)YhQ_bvxvQ3c^i_6YfHq!ZZ?eM@ zY-=WE4IS8g;A_|+&SRkIw(of`M&A)k;7)v42tjeiH9bIx3u->gBkwJz4GD&wSIVjK zHuu^(1e^D_;Uv4J!EH5j`ZVAqxhGcm47OZwk`$XFqOBq z;wcb#>SEk{WDCU-)&*x<1{lRl__~*l$noEufjY&ava%pf zyZF?Ulz?Y77FJe^|0b^}7G;t=AT^3y5WxqvqQnI5`MLNfrFZ%I`Hg@F@nEG5KkGXS zxPf@A8q&2bUEwz7=5D}_YWGTiS2X+;E{9$FZJc0Dt$Cswj~P;W2rV)GFYv6&i|5p0 zY=9G%H$lB3-%eB=X*EM=9ZWaM?zEh?~WoNk6saJ$rr78jZa>fB3!zdytrf8 zz2*7oXqmHVf`{i(X!xPODye6e*!|EO_$rf_WJvpClw>JL%W+gqMB#P#08**&gDXo?nrLN&7kG$+_)=Igt zcCotp#KjU@NkyMK-*a}xf~pi+r@=j}Z8if)*QWI0Zk!rUS@xQyZH}d%6+4&LiEhr9 zbf;$?nIrBQAv&Gx{>%hJ!s>dwWp=CkCz>vPP=vYj2pZ0b`$;!eBH$RT-qwNcCtw~sm zo5S=5()3ZpcMx9)bVQm#vg9aTl+B(SX?EkuLC1qCd0PpoCPGzNrN;9GWx4fR5Snh| z_-w1wk3OkcJIdf)c&UlE1imOF@{P4jmf{H=2i+Xl+bo^LVMXB&e_DG)AJ;)E1y^=G3**hVGVQE5B+mImqY9jTJ#5#Q>tO%+~=A~tFCeU3Ko&d$#U9qsvbs?bn5#ImsByblpDl%p$v6Ct;Z$c{Rx{XWN?L*~0sYH*XRD z3BE6VF?d9?+JCwk7%nW!^;hs{y_W;a3*^j;&_a!e&-wWgH_Uv&1@A2CfJXJb)haI3kU|V;|4WVip8kENZ?#xlaoQgLv>?U z^d#l;pjM+6l@2SXwyYB6?B(y7mb9$w;?yGUFKE6NrY$GNRif6hrV^xL_xQV8F$Fla ziHfqTR>IV&O=Try59B!+FkA$}xI^bw(nPOBMLNC`bt+0@p^ER8Cxj)w!Iw-8`maB$67)%pLHl({*o(*r;8-{r zFsl3(z$l(o2j+Yd33Sx%kZo4t#2kfTIxi z`ljsw#DL{)wcyKFiuxU#B8o&7N00g($=~(4x#eY~88H=#0lQn2BtHa2#!dC^W^l;V z$DJ?<32l@5asdg#~pbe>gdz9N?pF$onZ}KM*FUq1FeTCdf zpEZJ%tNK)Y7uujG(guK7;W-t34wVjEn5JNf}FJ2}DPj*$@w3dl(`^b4p;K*G%i*Mmn&$SUf%mx0DPkPAO$D`dl#}hb-oGxwaUIceB$j(5b}!ddI>_I>-lI7|_g@J^;=#mB|l^}Rq(9Lb;7p|nz| zFif|yRGRgK&VBYuf?yxc=#K6=rx0tgrNMwfRpuy?Rr!^e--nPt#ItDhw;YorCi}-e zq@b9Uu@%SkLDjJE@wxC!Klf3W+iQ2JI)0d|KMqnQ_YTh5$S1wJNNAjBD^2&%)!0*Z zeB4?Wr4KNH@hkLv>U-y13+nZe-gxgev}|P_&HkIH)&;*k4og#lybr~;?hf;o&&Rva zpihOxKF=F`Xit2&F}5gzd%VX z`JeJuJ1rELc%7ZU>$=9N zaJ`i_x#g^uTfICy<4j^RvJayE4yS*7k{A|*?n+Rm7vre@+=}yO#=Mq)N?Qz7aA$5A zv&LpgmzY>{v*x`2gvpf6kT!_E%S%>wvfClc`ah;C zOw0f4Q!GIl2ovNtBODlQSR~3jE~}Yb6|sf^o~8CWs%B2 z*bGwTZz$T1^75#cC%bI)0Y~U7QZYC|0Mq`Q=>c$rS#1m3G{HlijmKg zk&pnv3)855RM%+9r(Qf=dgfy#yRb9MIQ*X~8zda|X$xH~RM{X9VGIlnzh3=8 zh1c@QPj#igWF74pRM?O!)2dfNwG}sljWL);rSo|;Wo79VJi}RYKRG>ptf2+{Nq8J~ zdf-rUgPL#*dOq*lD^Tr!1`aS^0%i6lsDASxMh0lKp7^^RjR ze?eT4rZcWAG%e$lK1brfP214Qxck z{Jc8E9h(|s^-=$%V~G)8Ys7GW)Qmb1KK>J?`yMBnYcFxKUe*e?-^TP0khQr(>)R`! zZJ0XU-`({)4eiUXfCp0sx-3PUN9!u!KVW2HXm++>P)aZ~FnG*ayPa;aT22$7{f+d6CnkDZlu?TUKSM$!CanFvfz9|@&C2=m0?k>|Jn@Q z454%=N_PkXDh*0^3R2P`NDm+#3eq4lgMdhPiqa_}APykiASjJ=yw9-rK4+`{>wJ8_ zoHL&Wu4}C|E1vZ{_wSCMPOw8zD`;353wfG(@4!&$vvG|yb#g1MEL4D>zr4I$gDpG| zM1$Aa0jCFA)*c%Q1G16&w7-juv!dvo59|dX;SFd>NILtxK99GuVZa4sU$xLt3A?Xq zKf2_Ct7jloWdT3j#u{apw?b$Xs3zZKLgLQ!TaE<^g@}DQOC2^wI#8KdOXuR+Q>{*V zQGM0ae+?#5f&JyT^>UkG%eyRcoAhM_Ni(`4^H0Yz{ztRn_U$ zl~wNxc~C}>KqyS+8Dt>#RiZch^R-Q#*6qm`>V z3-t$L3-3?>_Nd>&0X21}9jpz_01vA8<(Ahb)BAe|3@F`S(zSSt*+)Z2q;e|XEHLZG zo4dQaQ=!c{xKK@b%@ZN*-TgtpC&{f{=-@pNwm!?j{88IRlJmgAcYi%PCPtoGm3@^} z4FPg5VG-s_BZzd za#xd)!7%^W-E{`Wp?0;QuhbdvlY=~sAHdDL31BNTHI*9UWiEY}^@*ER)fVu*?GOGq zpV`M~N-B_@=y)M4fqe_ArrGBt_*3Ev(_XR2l!rI$v{&EdcgsAr;lE9Sui}ROtfxnF zRF)HJnWH{MKY72Y==Sk+rIqQD^ndYeR~tUThUJ6}%~wIq=I{B~{?5jXMQYASMafW6 zk?pY)t>${146P>*v4CRGuc$J}4w=Iys{&$eCgX+!k;WPqi&;i&u^Zah zYVwG1pCbwps?~PIY;Pv408Y-FV9+R5lEQzuKK z6D3n@ksIjkvjP#^(vP*T!J;|>b&F@bON`%=MP`qVj)JiBdJ1xrdw1`C0MiGk#IT-N z*45bnoL>KUWf0I65$=dh3(t+I=x>aznS(f-JUq!qLqTtOe-@o?Gf7I_y!>U3e*Nm- zNUnuiK7{;p2k@bnR#)o?g^-Y_mFDDNQ4*ZYZbu$L8eVoWr_D<>16#L?-0K^hz8!4@ zvugy7id)f7ch240KgPI^ePm4|DbcE;FDT>+Yw;wbB1W$-QX@_L9<$OJpHz|iaoc|| zB~O5E{!s}DJV)f`n@hMQ6E96aQ2Ao;Bd#)wCC9cY_v}ui7oGM zN_IXWnCE0D1+xr_XInkqUb@#OCNS&GD&)sgb!l%IE$v@GPC~wr`v>P1|A(=-OC?UM zJQ>_wTtd8pd-6e)tQA*DVdO^F&GLTX)IxdT*yqOf`5ezbx0Z*O53aQv zV7kvbwvNhUQsI@H`XC;XTT+H?y<&JtXg28#B$&}6=;MWU>L1mIO5I(lmRetlUn;=% zN&1BGfC!I}1On4)Z}S(G+m34*D+L&CQL(2Zyv;1%`hk*J^~I7jlQ*D3VzD8y*@@Ms zE2#dBfr^UYODH}3AUkqZ$3@RTNm*D5uo*dH`CJK=hdU+kobKkX8`20>we8Of1<_|P zp7)z{_){!S1m#xcre_~h0JpZZ1_-#$(F+1jV}$ct<6#jzu=Vo$aMbH;$hqZ=?n%Lf z=|at7b2u&%^uuqXZl-0b$p@Fqrv3iaX)xvXMgoFB7ZRT~oE+g9ll+S)K2 zr~?T{UR(1+>Fy`O4iKzms2*ejdeT1H++d@EJApY)Ia_g$y0~GxNT(&n9l|p`Bi8Yt zSFpz}q=+?8ohjCZ15!flx=<#F*%Nlfy~A_JuaekEmj=uwHdgt)Z@%DT0&CSj+Qm~c z4C`zB<~ek_A$y#3J?Mt!RAt%e#bUz2SFLxJ?~5yutFj=^yty@9%6GVfUIbl(X;=nZ zxN%|LS**8uRKVPj?IYv9D(-ju;9ZQ{3_FFYr}R#EtGe5Kwpi@VnYGQ%ly(c}D@EEH zk(=j7or=DZ?v<`Tiz1T4$uTLrBpy+h2@*AqW;5LvO^m~TYN{{QGDDYzT?$&;LC5!) zmhzV>t}c^DzWPT8DLuGKzfNv$j?IdXxa3cdX-}}l$&{6;%Nnme3vX4DeMQ3aDN`mq zgwJ0K69c3S+@M?X$9b?$@QKtYxBxEhX%}9My3ld@Mdm{s2fnA%S!mAmQ$gd)$^)LO zOQ<(^CSTMs?hD(l>ooV1APj*K3LUKAvFX{_Z?zE2b6|EtH!X78oaEkFM!heviB_Ok4aL zk^gJ#W31d`X>2a(QuDXjry3frHh7|bN#Pz*pr{Rn_>w2l^*=0x< z&|hk`q{a*zi(gIFsjZhS?KgJL#U9UViN_$26n4JCdcxl?+9~0@w)kMQY4rv1UqnJJ)&6Gn0wEscduR_93lu_+BEEZaw-fthq8lbz1qa zl_P}NA7t+7+=ch`Y7~6meG{8VWjkD=>Il z0>@yj3!&;`dd*lkU#!@#K$RGhF})V@adiKVxC&k3hks!&eH8XG`UiW}Lw?0xjS2q) z_9BwVkv47zO?71z6wc4~nAQEd+p6T`GBh9= zNu+Tr8U*aj%Ag9cAhc*UYMgrQiKSDdEe6e%&^APKQm7sWLt$!-V_I32hm~X7`>>eZ zn7pyPXARVMGp;kM${U zs2bRpCO;cC(VJKg%5<#zrgz!6WwMdGmb;$CXX5P9-LBIBDX~ruZCD|C(pK(#C8B>@ z*kYf$QQkIJ_p8g6cLV8xR6le}Vzy=yblm`pcd&4me1_h*m;zFUE;+5x5P>cZmLJIK z(iCFoR#sN-s;PC6_>b*Mg!hyf%HGs7ZaMHzek!vPpV<~ro_AfJ_FQPv{8(Xg+;bkLu{p7lYm9{ai*{&m`HtY`h z-gWZy9^YRNr<*Rz>M7JxRBKC^#O>C~_mj5{W+W0>so48}?{M#ö|e7-?va2}V_ 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 Date: Fri, 16 Nov 2018 19:02:05 +0800 Subject: [PATCH 3/9] change picture --- profiler.png | Bin 44660 -> 41557 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/profiler.png b/profiler.png index 5e70a246620e591900bdf2e22fd224269521a479..7f31d288139f8e65222b0cb076b0f375304785ce 100644 GIT binary patch literal 41557 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

;uQw6{mn4J|6!m@~bVrT&HP^D6%X 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ö|e7-?va2}V_ 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 Date: Fri, 16 Nov 2018 19:17:30 +0800 Subject: [PATCH 4/9] pic fix --- profiler.png | Bin 41557 -> 63443 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/profiler.png b/profiler.png index 7f31d288139f8e65222b0cb076b0f375304785ce..5b68bd49b16d7166847629442fd7d8217e28edfb 100644 GIT binary patch literal 63443 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'Ofbb++GQB)BH^?r}i)`}= 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 Ok-MWLl`Wy~`~Lvp4zh0m literal 41557 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 Date: Fri, 16 Nov 2018 23:48:59 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=87=A0=E4=B8=AABUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Slua/LuaProfiler/Parse/Parse.cs | 27 ++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/Assets/Slua/LuaProfiler/Parse/Parse.cs b/Assets/Slua/LuaProfiler/Parse/Parse.cs index ced06047..f6817eab 100644 --- a/Assets/Slua/LuaProfiler/Parse/Parse.cs +++ b/Assets/Slua/LuaProfiler/Parse/Parse.cs @@ -96,13 +96,18 @@ static void InsertSample(LLex l, ref int lastPos, ref int nextPos, int tokenType { needLastSample = false; } - + Token lastTokenType = null; + int commentPos = insertPos; while (tokenType != (int)TK.EOS) { + lastTokenType = l.Token; l.Next(); tokenType = l.Token.TokenType; - + if (l.Token is CommentToken && !(lastTokenType is CommentToken) ) + { + commentPos = nextPos - 1; + } lastPos = nextPos; nextPos = l.pos; @@ -110,17 +115,31 @@ static void InsertSample(LLex l, ref int lastPos, ref int nextPos, int tokenType { InsertSample(l, ref lastPos, ref nextPos, tokenType, true); tokenType = l.Token.TokenType; + if (l.Token is CommentToken && !(lastTokenType is CommentToken)) + { + commentPos = lastPos - 1; + } + lastTokenType = l.Token; } if (tokenType == (int)TK.END || tokenType == (int)TK.ELSEIF || tokenType == (int)TK.ELSE - || tokenType == (int)TK.EOS - || (l.Token is CommentToken)) + || tokenType == (int)TK.EOS) { + if (lastTokenType is CommentToken) + { + lastPos = commentPos; + } + string returnStr = l.ReadString(insertPos, lastPos - 1); ; returnStr = returnStr.Trim(); + + if (returnStr.Length > 0 && returnStr[returnStr.Length - 1] == ';') + { + returnStr = returnStr.Substring(0, returnStr.Length - 1); + } returnStr = "\r\nreturn miku_unpack_return_value(" + returnStr.Substring(6, returnStr.Length - 6).Trim() + ")\r\n"; l.Replace(insertPos, lastPos - 1, returnStr); From 2aea5a40ba004364d712fa12d7b61b049c5dad3a Mon Sep 17 00:00:00 2001 From: ElPsyCongree <345036769@qq.com> Date: Sat, 17 Nov 2018 20:01:27 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LuaProfiler/{Utils.meta => Common.meta} | 2 +- .../Slua/LuaProfiler/{ => Common}/Editor.meta | 2 +- .../{ => Common}/Editor/TreeView.meta | 2 +- .../Editor/TreeView/LuaProfilerTreeView.cs | 0 .../TreeView/LuaProfilerTreeView.cs.meta | 2 +- .../Editor/TreeView/LuaProfilerWindow.cs | 20 +- .../TreeView/LuaProfilerWindow.cs.meta} | 4 +- .../LuaDeepProfilerSetting.cs | 17 +- .../LuaDeepProfilerSetting.cs.meta | 2 - Assets/Slua/LuaProfiler/Common/LuaProfiler.cs | 333 +++++++ .../{Utils => Common}/LuaProfiler.cs.meta | 0 .../{Utils => Common}/ObjectPool.cs | 0 .../{Utils => Common}/ObjectPool.cs.meta | 3 +- .../Slua/LuaProfiler/{ => Common}/Parse.meta | 0 .../LuaProfiler/{ => Common}/Parse/Common.cs | 0 .../{ => Common}/Parse/Common.cs.meta | 0 .../LuaProfiler/{ => Common}/Parse/LLex.cs | 0 .../{ => Common}/Parse/LLex.cs.meta | 0 .../LuaProfiler/{ => Common}/Parse/Parse.cs | 0 .../{ => Common}/Parse/Parse.cs.meta | 0 .../LuaProfiler/{ => Common}/Parse/Util.cs | 0 .../{ => Common}/Parse/Util.cs.meta | 0 .../LuaProfiler/{ => Common}/UnityHook.meta | 0 .../{ => Common}/UnityHook/HookerPool.cs | 0 .../{ => Common}/UnityHook/HookerPool.cs.meta | 0 .../{ => Common}/UnityHook/LDasm.cs | 0 .../{ => Common}/UnityHook/LDasm.cs.meta | 0 .../{ => Common}/UnityHook/MethodHooker.cs | 0 .../UnityHook/MethodHooker.cs.meta | 0 Assets/Slua/LuaProfiler/HookSetup.meta | 8 + .../LuaProfiler/HookSetup/SLuaHookSetup.cs | 459 +++++++++ .../SLuaHookSetup.cs.meta} | 5 +- .../LuaProfiler/HookSetup/ToLuaHookSetup.cs | 391 ++++++++ .../HookSetup/ToLuaHookSetup.cs.meta | 11 + .../LuaProfiler/HookSetup/XLuaHookSetup.cs | 420 +++++++++ .../HookSetup/XLuaHookSetup.cs.meta | 11 + Assets/Slua/LuaProfiler/UnityHook/StarUp.cs | 54 -- Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs | 868 ------------------ 38 files changed, 1647 insertions(+), 967 deletions(-) rename Assets/Slua/LuaProfiler/{Utils.meta => Common.meta} (77%) rename Assets/Slua/LuaProfiler/{ => Common}/Editor.meta (77%) rename Assets/Slua/LuaProfiler/{ => Common}/Editor/TreeView.meta (77%) rename Assets/Slua/LuaProfiler/{ => Common}/Editor/TreeView/LuaProfilerTreeView.cs (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Editor/TreeView/LuaProfilerTreeView.cs.meta (83%) rename Assets/Slua/LuaProfiler/{ => Common}/Editor/TreeView/LuaProfilerWindow.cs (85%) rename Assets/Slua/LuaProfiler/{UnityHook/StarUp.cs.meta => Common/Editor/TreeView/LuaProfilerWindow.cs.meta} (71%) rename Assets/Slua/LuaProfiler/{Utils => Common}/LuaDeepProfilerSetting.cs (87%) rename Assets/Slua/LuaProfiler/{Utils => Common}/LuaDeepProfilerSetting.cs.meta (85%) create mode 100644 Assets/Slua/LuaProfiler/Common/LuaProfiler.cs rename Assets/Slua/LuaProfiler/{Utils => Common}/LuaProfiler.cs.meta (100%) rename Assets/Slua/LuaProfiler/{Utils => Common}/ObjectPool.cs (100%) rename Assets/Slua/LuaProfiler/{Utils => Common}/ObjectPool.cs.meta (84%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse.meta (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse/Common.cs (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse/Common.cs.meta (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse/LLex.cs (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse/LLex.cs.meta (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse/Parse.cs (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse/Parse.cs.meta (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse/Util.cs (100%) rename Assets/Slua/LuaProfiler/{ => Common}/Parse/Util.cs.meta (100%) rename Assets/Slua/LuaProfiler/{ => Common}/UnityHook.meta (100%) rename Assets/Slua/LuaProfiler/{ => Common}/UnityHook/HookerPool.cs (100%) rename Assets/Slua/LuaProfiler/{ => Common}/UnityHook/HookerPool.cs.meta (100%) rename Assets/Slua/LuaProfiler/{ => Common}/UnityHook/LDasm.cs (100%) rename Assets/Slua/LuaProfiler/{ => Common}/UnityHook/LDasm.cs.meta (100%) rename Assets/Slua/LuaProfiler/{ => Common}/UnityHook/MethodHooker.cs (100%) rename Assets/Slua/LuaProfiler/{ => Common}/UnityHook/MethodHooker.cs.meta (100%) create mode 100644 Assets/Slua/LuaProfiler/HookSetup.meta create mode 100644 Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs rename Assets/Slua/LuaProfiler/{Editor/TreeView/LuaProfilerWindow.cs.meta => HookSetup/SLuaHookSetup.cs.meta} (69%) create mode 100644 Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs create mode 100644 Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs.meta create mode 100644 Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs create mode 100644 Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs.meta delete mode 100644 Assets/Slua/LuaProfiler/UnityHook/StarUp.cs delete mode 100644 Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs diff --git a/Assets/Slua/LuaProfiler/Utils.meta b/Assets/Slua/LuaProfiler/Common.meta similarity index 77% rename from Assets/Slua/LuaProfiler/Utils.meta rename to Assets/Slua/LuaProfiler/Common.meta index 61775c67..407dbe18 100644 --- a/Assets/Slua/LuaProfiler/Utils.meta +++ b/Assets/Slua/LuaProfiler/Common.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a1acd1215960f1148bd9d8b10beceba0 +guid: 5b42cf4e195733142af7e5ec6ed657f2 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Slua/LuaProfiler/Editor.meta b/Assets/Slua/LuaProfiler/Common/Editor.meta similarity index 77% rename from Assets/Slua/LuaProfiler/Editor.meta rename to Assets/Slua/LuaProfiler/Common/Editor.meta index bb665425..a1d2ed0f 100644 --- a/Assets/Slua/LuaProfiler/Editor.meta +++ b/Assets/Slua/LuaProfiler/Common/Editor.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 830ddb418ab883c4f84dc1f5824f969e +guid: 15c6e2e95af9f664a992b01a678a1fa8 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView.meta b/Assets/Slua/LuaProfiler/Common/Editor/TreeView.meta similarity index 77% rename from Assets/Slua/LuaProfiler/Editor/TreeView.meta rename to Assets/Slua/LuaProfiler/Common/Editor/TreeView.meta index 65ada809..32af665f 100644 --- a/Assets/Slua/LuaProfiler/Editor/TreeView.meta +++ b/Assets/Slua/LuaProfiler/Common/Editor/TreeView.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ee04b2b4ef5a40c4b8300302016a18e4 +guid: 0c63c0f81959e204793846088a3b3c18 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs similarity index 100% rename from Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs rename to Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs.meta b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs.meta similarity index 83% rename from Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs.meta rename to Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs.meta index f95c182d..44d4382a 100644 --- a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerTreeView.cs.meta +++ b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d4ee9033653a2184da71af94f390339a +guid: 2bf7eb6c860d30044aab7f400fcd5b71 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs similarity index 85% rename from Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs rename to Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs index 822be412..92467b88 100644 --- a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs +++ b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs @@ -9,6 +9,7 @@ using UnityEngine; using UnityEditor.IMGUI.Controls; using UnityEditor; +using System; namespace MikuLuaProfiler { @@ -56,6 +57,7 @@ void DoToolbar() if (flag != LuaDeepProfilerSetting.Instance.isDeepProfiler) { LuaDeepProfilerSetting.Instance.isDeepProfiler = flag; + EditorApplication.isPlaying = false; } GUILayout.Space(5); #endregion @@ -67,20 +69,12 @@ void DoToolbar() { if (isStop) { - var env = LuaProfiler.mainEnv; - if (env != null) - { - SLua.LuaDLL.lua_gc(env.L, SLua.LuaGCOptions.LUA_GCSTOP, 0); - } + LuaLib.StopGC(); m_isStop = true; } else { - var env = LuaProfiler.mainEnv; - if (env != null) - { - SLua.LuaDLL.lua_gc(env.L, SLua.LuaGCOptions.LUA_GCRESTART, 0); - } + LuaLib.ResumeGC(); m_isStop = false; } } @@ -91,11 +85,7 @@ void DoToolbar() bool isRunGC = GUILayout.Button("Run GC", EditorStyles.toolbarButton, GUILayout.Height(30)); if (isRunGC) { - var env = LuaProfiler.mainEnv; - if (env != null) - { - SLua.LuaDLL.lua_gc(env.L, SLua.LuaGCOptions.LUA_GCCOLLECT, 0); - } + LuaLib.RunGC(); } GUILayout.Space(20); GUILayout.FlexibleSpace(); diff --git a/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs.meta b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs.meta similarity index 71% rename from Assets/Slua/LuaProfiler/UnityHook/StarUp.cs.meta rename to Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs.meta index d471a9fd..801ba080 100644 --- a/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs.meta +++ b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs.meta @@ -1,7 +1,5 @@ fileFormatVersion: 2 -guid: e1d2def6afaf4bf468700da4f1b6e6cf -timeCreated: 1530502621 -licenseType: Pro +guid: d00b76b720f401640829dbe4539d5ff1 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs b/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs similarity index 87% rename from Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs rename to Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs index 41ee37a2..d7286789 100644 --- a/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs +++ b/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs @@ -51,22 +51,7 @@ public bool isDeepProfiler } } - - [SerializeField] - private bool m_stableGC = true; - public bool stableGC - { - get - { - return m_stableGC; - } - set - { - m_stableGC = value; - } - } - - //[MenuItem("Tools/LuaProfilerSetting", priority = 10)] + [MenuItem("Tools/LuaProfilerSetting", priority = 10)] public static void EditSettings() { //string text = System.IO.File.ReadAllText("Lua/TemplateCommon.lua"); diff --git a/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs.meta b/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs.meta similarity index 85% rename from Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs.meta rename to Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs.meta index c144daa7..6cdba291 100644 --- a/Assets/Slua/LuaProfiler/Utils/LuaDeepProfilerSetting.cs.meta +++ b/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs.meta @@ -1,7 +1,5 @@ fileFormatVersion: 2 guid: 144cbe4d057341d479a8e09b5bf689dc -timeCreated: 1529329742 -licenseType: Pro MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Slua/LuaProfiler/Common/LuaProfiler.cs b/Assets/Slua/LuaProfiler/Common/LuaProfiler.cs new file mode 100644 index 00000000..25d2ddd9 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Common/LuaProfiler.cs @@ -0,0 +1,333 @@ +#pragma warning disable CS0219//故意在c#这里产生于lua那边的等量GC +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using UnityEngine; +#if UNITY_5_5_OR_NEWER +using UnityEngine.Profiling; +#endif +using UnityEditorInternal; + +namespace MikuLuaProfiler +{ + public class LuaProfiler + { + private static IntPtr _mainL = IntPtr.Zero; + public static IntPtr mainL + { + get + { + return _mainL; + } + set + { + _mainL = value; + } + } + + public static string GetLuaMemory() + { + long result = 0; + if (mainL != IntPtr.Zero) + { + try + { + result = LuaLib.GetLuaMemory(mainL); + } + catch { } + } + + return GetMemoryString(result); + } + + public class Sample + { + private static ObjectPool samplePool = new ObjectPool(250); + public static Sample Create(float time, long memory, string name) + { + Sample s = samplePool.GetObject(); + s.currentTime = time; + s.currentLuaMemory = memory; + s.realCurrentLuaMemory = memory; + s.costGC = 0; + s.name = name; + s.costTime = 0; + s.childs.Clear(); + s._father = null; + s._fullName = null; + + return s; + } + + public void Restore() + { + for (int i = 0, imax = childs.Count; i < imax; i++) + { + childs[i].Restore(); + } + samplePool.Store(this); + } + + public int oneFrameCall + { + get + { + return 1; + } + } + public float currentTime { private set; get; } + public long realCurrentLuaMemory { private set; get; } + private string _name; + public string name + { + private set + { + _name = value; + } + get + { + return _name; + } + } + + private static Dictionary> m_fullNamePool = new Dictionary>(); + private string _fullName = null; + public string fullName + { + get + { + if (_father == null) return _name; + + if (_fullName == null) + { + Dictionary childDict; + if (!m_fullNamePool.TryGetValue(_father.fullName, out childDict)) + { + childDict = new Dictionary(); + m_fullNamePool.Add(_father.fullName, childDict); + } + + if (!childDict.TryGetValue(_name, out _fullName)) + { + string value = _name; + var f = _father; + while (f != null) + { + value = f.name + value; + f = f.fahter; + } + _fullName = value; + childDict[_name] = _fullName; + } + + return _fullName; + } + else + { + return _fullName; + } + } + } + //这玩意在统计的window里面没啥卵用 + public long currentLuaMemory { set; get; } + + private float _costTime; + public float costTime + { + set + { + _costTime = value; + } + get + { + float result = _costTime; + return result; + } + } + + private long _costGC; + public long costGC + { + set + { + _costGC = value; + } + get + { + return _costGC; + } + } + private Sample _father; + public Sample fahter + { + set + { + _father = value; + if (_father != null) + { + _father.childs.Add(this); + } + } + get + { + return _father; + } + } + + public readonly List childs = new List(256); + } + //开始采样时候的lua内存情况,因为中间有可能会有二次采样,所以要丢到一个盏中 + public static readonly List beginSampleMemoryStack = new List(); + + private static Action m_SampleEndAction; + + private static bool isDeep + { + get + { +#if UNITY_EDITOR + return ProfilerDriver.deepProfiling; +#else + return false; +#endif + } + } + public static void SetSampleEnd(Action action) + { + m_SampleEndAction = action; + } + public static void BeginSample(string name) + { +#if DEBUG + if (mainL != IntPtr.Zero) + { + BeginSample(mainL, name); + } +#endif + } + public static void BeginSample(IntPtr luaState) + { +#if DEBUG + BeginSample(luaState, "lua gc"); +#endif + } + private static int m_currentFrame = 0; + public static void BeginSample(IntPtr luaState, string name) + { + if (m_currentFrame != Time.frameCount) + { + PopAllSampleWhenLateUpdate(); + m_currentFrame = Time.frameCount; + } + +#if DEBUG + long memoryCount = LuaLib.GetLuaMemory(luaState); + Sample sample = Sample.Create(Time.realtimeSinceStartup, memoryCount, name); + + beginSampleMemoryStack.Add(sample); + if (!isDeep) + { + Profiler.BeginSample(name); + } +#endif + } + public static void PopAllSampleWhenLateUpdate() + { + for (int i = 0, imax = beginSampleMemoryStack.Count; i < imax; i++) + { + var item = beginSampleMemoryStack[i]; + if (item.fahter == null) + { + item.Restore(); + } + } + beginSampleMemoryStack.Clear(); + } + public static void EndSample() + { +#if DEBUG + if (_mainL != IntPtr.Zero) + { + EndSample(_mainL); + } +#endif + } + public static void EndSample(IntPtr luaState) + { +#if DEBUG + if (beginSampleMemoryStack.Count <= 0) + { + return; + } + int count = beginSampleMemoryStack.Count; + Sample sample = beginSampleMemoryStack[beginSampleMemoryStack.Count - 1]; + long oldMemoryCount = sample.currentLuaMemory; + beginSampleMemoryStack.RemoveAt(count - 1); + long nowMemoryCount = LuaLib.GetLuaMemory(luaState); + sample.fahter = count > 1 ? beginSampleMemoryStack[count - 2] : null; + + if (!isDeep) + { + long delta = nowMemoryCount - oldMemoryCount; + + long tmpDelta = delta; + if (delta > 0) + { + delta = Math.Max(delta - 40, 0);//byte[0] 的字节占用是40 + byte[] luagc = new byte[delta]; + } + for (int i = 0, imax = beginSampleMemoryStack.Count; i < imax; i++) + { + Sample s = beginSampleMemoryStack[i]; + s.currentLuaMemory += tmpDelta; + beginSampleMemoryStack[i] = s; + } + Profiler.EndSample(); + } + + sample.costTime = Time.realtimeSinceStartup - sample.currentTime; + var gc = nowMemoryCount - sample.realCurrentLuaMemory; + sample.costGC = gc > 0 ? gc : 0; + + if (m_SampleEndAction != null && beginSampleMemoryStack.Count == 0) + { + m_SampleEndAction(sample); + } + + if (sample.fahter == null) + { + sample.Restore(); + } +#endif + } + + const long MaxB = 1024; + const long MaxK = MaxB * 1024; + const long MaxM = MaxK * 1024; + const long MaxG = MaxM * 1024; + + public static string GetMemoryString(long value, string unit = "B") + { + string result = null; + if (value < MaxB) + { + result = string.Format("{0}{1}", value, unit); + } + else if (value < MaxK) + { + result = string.Format("{0:N2}K{1}", (float)value / MaxB, unit); + } + else if (value < MaxM) + { + result = string.Format("{0:N2}M{1}", (float)value / MaxK, unit); + } + else if (value < MaxG) + { + result = string.Format("{0:N2}G{1}", (float)value / MaxM, unit); + } + return result; + } + } +} +#endif + diff --git a/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs.meta b/Assets/Slua/LuaProfiler/Common/LuaProfiler.cs.meta similarity index 100% rename from Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs.meta rename to Assets/Slua/LuaProfiler/Common/LuaProfiler.cs.meta diff --git a/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs b/Assets/Slua/LuaProfiler/Common/ObjectPool.cs similarity index 100% rename from Assets/Slua/LuaProfiler/Utils/ObjectPool.cs rename to Assets/Slua/LuaProfiler/Common/ObjectPool.cs diff --git a/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs.meta b/Assets/Slua/LuaProfiler/Common/ObjectPool.cs.meta similarity index 84% rename from Assets/Slua/LuaProfiler/Utils/ObjectPool.cs.meta rename to Assets/Slua/LuaProfiler/Common/ObjectPool.cs.meta index 19fea857..f9df1335 100644 --- a/Assets/Slua/LuaProfiler/Utils/ObjectPool.cs.meta +++ b/Assets/Slua/LuaProfiler/Common/ObjectPool.cs.meta @@ -1,8 +1,7 @@ fileFormatVersion: 2 guid: 047a18bfa9169b94da0db4aa973b9980 -timeCreated: 1468553547 -licenseType: Pro MonoImporter: + externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 diff --git a/Assets/Slua/LuaProfiler/Parse.meta b/Assets/Slua/LuaProfiler/Common/Parse.meta similarity index 100% rename from Assets/Slua/LuaProfiler/Parse.meta rename to Assets/Slua/LuaProfiler/Common/Parse.meta diff --git a/Assets/Slua/LuaProfiler/Parse/Common.cs b/Assets/Slua/LuaProfiler/Common/Parse/Common.cs similarity index 100% rename from Assets/Slua/LuaProfiler/Parse/Common.cs rename to Assets/Slua/LuaProfiler/Common/Parse/Common.cs diff --git a/Assets/Slua/LuaProfiler/Parse/Common.cs.meta b/Assets/Slua/LuaProfiler/Common/Parse/Common.cs.meta similarity index 100% rename from Assets/Slua/LuaProfiler/Parse/Common.cs.meta rename to Assets/Slua/LuaProfiler/Common/Parse/Common.cs.meta diff --git a/Assets/Slua/LuaProfiler/Parse/LLex.cs b/Assets/Slua/LuaProfiler/Common/Parse/LLex.cs similarity index 100% rename from Assets/Slua/LuaProfiler/Parse/LLex.cs rename to Assets/Slua/LuaProfiler/Common/Parse/LLex.cs diff --git a/Assets/Slua/LuaProfiler/Parse/LLex.cs.meta b/Assets/Slua/LuaProfiler/Common/Parse/LLex.cs.meta similarity index 100% rename from Assets/Slua/LuaProfiler/Parse/LLex.cs.meta rename to Assets/Slua/LuaProfiler/Common/Parse/LLex.cs.meta diff --git a/Assets/Slua/LuaProfiler/Parse/Parse.cs b/Assets/Slua/LuaProfiler/Common/Parse/Parse.cs similarity index 100% rename from Assets/Slua/LuaProfiler/Parse/Parse.cs rename to Assets/Slua/LuaProfiler/Common/Parse/Parse.cs diff --git a/Assets/Slua/LuaProfiler/Parse/Parse.cs.meta b/Assets/Slua/LuaProfiler/Common/Parse/Parse.cs.meta similarity index 100% rename from Assets/Slua/LuaProfiler/Parse/Parse.cs.meta rename to Assets/Slua/LuaProfiler/Common/Parse/Parse.cs.meta diff --git a/Assets/Slua/LuaProfiler/Parse/Util.cs b/Assets/Slua/LuaProfiler/Common/Parse/Util.cs similarity index 100% rename from Assets/Slua/LuaProfiler/Parse/Util.cs rename to Assets/Slua/LuaProfiler/Common/Parse/Util.cs diff --git a/Assets/Slua/LuaProfiler/Parse/Util.cs.meta b/Assets/Slua/LuaProfiler/Common/Parse/Util.cs.meta similarity index 100% rename from Assets/Slua/LuaProfiler/Parse/Util.cs.meta rename to Assets/Slua/LuaProfiler/Common/Parse/Util.cs.meta diff --git a/Assets/Slua/LuaProfiler/UnityHook.meta b/Assets/Slua/LuaProfiler/Common/UnityHook.meta similarity index 100% rename from Assets/Slua/LuaProfiler/UnityHook.meta rename to Assets/Slua/LuaProfiler/Common/UnityHook.meta diff --git a/Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs b/Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs similarity index 100% rename from Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs rename to Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs diff --git a/Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs.meta b/Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs.meta similarity index 100% rename from Assets/Slua/LuaProfiler/UnityHook/HookerPool.cs.meta rename to Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs.meta diff --git a/Assets/Slua/LuaProfiler/UnityHook/LDasm.cs b/Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs similarity index 100% rename from Assets/Slua/LuaProfiler/UnityHook/LDasm.cs rename to Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs diff --git a/Assets/Slua/LuaProfiler/UnityHook/LDasm.cs.meta b/Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs.meta similarity index 100% rename from Assets/Slua/LuaProfiler/UnityHook/LDasm.cs.meta rename to Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs.meta diff --git a/Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs b/Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs similarity index 100% rename from Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs rename to Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs diff --git a/Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs.meta b/Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs.meta similarity index 100% rename from Assets/Slua/LuaProfiler/UnityHook/MethodHooker.cs.meta rename to Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs.meta diff --git a/Assets/Slua/LuaProfiler/HookSetup.meta b/Assets/Slua/LuaProfiler/HookSetup.meta new file mode 100644 index 00000000..fdee4569 --- /dev/null +++ b/Assets/Slua/LuaProfiler/HookSetup.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3026c398091420c44925795549cfcd6e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs new file mode 100644 index 00000000..d7543b8f --- /dev/null +++ b/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs @@ -0,0 +1,459 @@ +/* +* ============================================================================== +* Filename: SLuaHooSetup +* Created: 2018/7/2 11:36:16 +* Author: エル・プサイ・コングルゥ +* Purpose: +* ============================================================================== +*/ + +#define SLUA + +#if SLUA +#if UNITY_EDITOR + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using SLua; +using LuaDLL = SLua.LuaDLL; + +namespace MikuLuaProfiler { + + public class LuaLib + { + public static void RunGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCCOLLECT, 0); + } + } + public static void StopGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCSTOP, 0); + } + } + public static void ResumeGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCRESTART, 0); + } + } + + public static long GetLuaMemory(IntPtr luaState) + { + long result = 0; + + result = LuaDLL.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNT, 0); + result = result * 1024 + LuaDLL.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNTB, 0); + + return result; + } + } + + [InitializeOnLoad] + public static class Startup + { + private static MethodHooker hookNewLuaEnv; + + public static readonly string luaPath; + static Startup() + { + HookNewLuaEnv(); + } + + public static void HookNewLuaEnv() + { + if (hookNewLuaEnv == null) + { + Type envReplace = typeof(LuaEnvReplace); + Type typeEnv = typeof(LuaState); + var clickFun = typeEnv.GetConstructors()[0]; + MethodInfo clickReplace = envReplace.GetMethod("Ctor"); + MethodInfo clickProxy = envReplace.GetMethod("Proxy", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(clickFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); + + Type typeDll = typeof(LuaDLL); + var newstateFun = typeDll.GetMethod("luaL_newstate"); + clickReplace = envReplace.GetMethod("luaL_newstate"); + clickProxy = envReplace.GetMethod("ProxyNewstate", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); + } + } + + public static class LuaEnvReplace + { + public static void Ctor(LuaState env) + { + Proxy(env); + MikuLuaProfiler.HookSetup.SetMainLuaEnv(env); + } + public static void Proxy(LuaState env) + { + } + + public static IntPtr luaL_newstate() + { + IntPtr l = ProxyNewstate(); + MikuLuaProfiler.LuaProfiler.mainL = l; + return l; + } + public static IntPtr ProxyNewstate() + { + return IntPtr.Zero; + } + } + + } + + [InitializeOnLoad] + static class HookSetup + { +#if !UNITY_2017_1_OR_NEWER + static bool isPlaying = false; +#endif + static HookSetup() + { +#if UNITY_2017_1_OR_NEWER + EditorApplication.playModeStateChanged += OnEditorPlaying; +#else + EditorApplication.playmodeStateChanged += () => + { + + if (isPlaying == true && EditorApplication.isPlaying == false) + { + LuaProfiler.SetMainLuaEnv(null); + } + + isPlaying = EditorApplication.isPlaying; + }; +#endif + } + + + public static void SetMainLuaEnv(LuaState env) + { + if (LuaDeepProfilerSetting.Instance.isDeepProfiler) + { + Lua_MikuLuaProfiler_LuaProfiler.reg(LuaProfiler.mainL); + if (env != null) + { + env.doString(@" +BeginMikuSample = MikuLuaProfiler.LuaProfiler.BeginSample +EndMikuSample = MikuLuaProfiler.LuaProfiler.EndSample + +function miku_unpack_return_value(...) + EndMikuSample() + return ... +end +"); + HookSetup.HookLuaFuns(); + } + } + + if (env == null) + { + HookSetup.Uninstall(); + LuaProfiler.mainL = IntPtr.Zero; + } + } + +#if UNITY_2017_1_OR_NEWER + public static void OnEditorPlaying(PlayModeStateChange playModeStateChange) + { + if (playModeStateChange == PlayModeStateChange.ExitingPlayMode) + { + SetMainLuaEnv(null); + } + } +#endif + +#region hook + +#region hook tostring + + public class LuaDll + { +#region luastring + public static readonly Dictionary stringDict = new Dictionary(); + public static bool TryGetLuaString(IntPtr p, out string result) + { + + return stringDict.TryGetValue((long)p, out result); + } + public static void RefString(IntPtr strPoint, int index, string s, IntPtr L) + { + int oldTop = LuaDLL.lua_gettop(L); + LuaDLL.lua_pushvalue(L, index); + //把字符串ref了之后就不GC了 + LuaDLL.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX); + LuaDLL.lua_settop(L, oldTop); + stringDict[(long)strPoint] = s; + } +#endregion + + public static int luaL_loadbuffer(IntPtr L, byte[] buff, int size, string name) + { + if (LuaDeepProfilerSetting.Instance.isDeepProfiler)//&& name != "chunk" + { + var utf8WithoutBom = new System.Text.UTF8Encoding(true); + string fileName = name.Replace("@", "").Replace("/", ".") + ".lua"; + string value = utf8WithoutBom.GetString(buff); + value = Parse.InsertSample(value, fileName); + + //System.IO.File.WriteAllText(fileName, value); + + buff = utf8WithoutBom.GetBytes(value); + size = buff.Length; + } + + return ProxyLoadbuffer(L, buff, size, name); + } + + public static int ProxyLoadbuffer(IntPtr L, byte[] buff, int size, string name) + { + return 0; + } + + public static string lua_tostring(IntPtr L, int index) + { + int strlen; + + IntPtr str = LuaDLL.luaS_tolstring32(L, index, out strlen); // fix il2cpp 64 bit + string s = null; + if (TryGetLuaString(str, out s)) + { + return s; + } + + if (strlen > 0 && str != IntPtr.Zero) + { + s = Marshal.PtrToStringAnsi(str); + // fallback method + if (s == null) + { + byte[] b = new byte[strlen]; + Marshal.Copy(str, b, 0, strlen); + s = System.Text.Encoding.Default.GetString(b); + } + } + if (s != null) + { + RefString(str, index, s, L); + } + + return (s == null) ? string.Empty : s; + } + + public static string PoxyToString(IntPtr L, int index) + { + return null; + } + } +#endregion + + +#region hook profiler + public class Profiler + { + private static Stack m_Stack = new Stack(); + private static int m_currentFrame = 0; + public static void BeginSampleOnly(string name) + { + if (ProfilerDriver.deepProfiling) return; + if (Time.frameCount != m_currentFrame) + { + m_Stack.Clear(); + m_currentFrame = Time.frameCount; + } + m_Stack.Push(name); + ProxyBeginSample(name); + } + public static void BeginSample(string name, UnityEngine.Object targetObject) + { + if (ProfilerDriver.deepProfiling) return; + m_Stack.Push(name); + ProxyBeginSample(name, targetObject); + } + + public static void EndSample() + { + if (ProfilerDriver.deepProfiling) return; + if (m_Stack.Count <= 0) + { + return; + } + m_Stack.Pop(); + ProxyEndSample(); + } + + public static void ProxyBeginSample(string name) + { + } + public static void ProxyBeginSample(string name, UnityEngine.Object targetObject) + { + } + + public static void ProxyEndSample() + { + } + } +#endregion + +#region do hook + private static MethodHooker beginSampeOnly; + private static MethodHooker beginObjetSample; + private static MethodHooker endSample; + private static MethodHooker tostringHook; + private static MethodHooker loaderHook; + + private static bool m_hooked = false; + public static void HookLuaFuns() + { + if (m_hooked) return; + if (tostringHook == null) + { + Type typeLogReplace = typeof(LuaDll); + Type typeLog = typeof(LuaDLL); + MethodInfo tostringFun = typeLog.GetMethod("lua_tostring"); + MethodInfo tostringReplace = typeLogReplace.GetMethod("lua_tostring"); + MethodInfo tostringProxy = typeLogReplace.GetMethod("ProxyToString"); + + tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); + tostringHook.Install(); + + tostringFun = typeLog.GetMethod("luaL_loadbuffer"); + tostringReplace = typeLogReplace.GetMethod("luaL_loadbuffer"); + tostringProxy = typeLogReplace.GetMethod("ProxyLoadbuffer"); + + tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); + tostringHook.Install(); + } + + if (beginSampeOnly == null) + { + Type typeTarget = typeof(UnityEngine.Profiling.Profiler); + Type typeReplace = typeof(Profiler); + + MethodInfo hookTarget = typeTarget.GetMethod("BeginSampleOnly", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + MethodInfo hookReplace = typeReplace.GetMethod("BeginSampleOnly"); + MethodInfo hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + beginSampeOnly = new MethodHooker(hookTarget, hookReplace, hookProxy); + beginSampeOnly.Install(); + + hookTarget = typeTarget.GetMethod("BeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); + hookReplace = typeReplace.GetMethod("BeginSample"); + hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); + beginObjetSample = new MethodHooker(hookTarget, hookReplace, hookProxy); + beginObjetSample.Install(); + + hookTarget = typeTarget.GetMethod("EndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); + hookReplace = typeReplace.GetMethod("EndSample"); + hookProxy = typeReplace.GetMethod("ProxyEndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); + endSample = new MethodHooker(hookTarget, hookReplace, hookProxy); + endSample.Install(); + } + + m_hooked = true; + } + + public static void Uninstall() + { + if (beginSampeOnly != null) + { + beginSampeOnly.Uninstall(); + beginSampeOnly = null; + } + if (beginObjetSample != null) + { + beginObjetSample.Uninstall(); + beginObjetSample = null; + } + if (endSample != null) + { + endSample.Uninstall(); + endSample = null; + } + if (tostringHook != null) + { + tostringHook.Uninstall(); + tostringHook = null; + } + if (loaderHook != null) + { + loaderHook.Uninstall(); + loaderHook = null; + } + + m_hooked = false; + } +#endregion + +#endregion + } + + #region bind + [UnityEngine.Scripting.Preserve] + public class Lua_MikuLuaProfiler_LuaProfiler : LuaObject + { + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int BeginSample_s(IntPtr l) + { + try + { + System.String a1; + checkType(l, 1, out a1); + MikuLuaProfiler.LuaProfiler.BeginSample(a1); + pushValue(l, true); + return 1; + } + catch (Exception e) + { + return error(l, e); + } + } + [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + [UnityEngine.Scripting.Preserve] + static public int EndSample_s(IntPtr l) + { + try + { + MikuLuaProfiler.LuaProfiler.EndSample(); + pushValue(l, true); + return 1; + } + catch (Exception e) + { + return error(l, e); + } + } + [UnityEngine.Scripting.Preserve] + static public void reg(IntPtr l) + { + getTypeTable(l, "MikuLuaProfiler.LuaProfiler"); + addMember(l, BeginSample_s); + addMember(l, EndSample_s); + createTypeMetatable(l, typeof(MikuLuaProfiler.LuaProfiler)); + } + } + #endregion +} + +#endif + +#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs.meta b/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs.meta similarity index 69% rename from Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs.meta rename to Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs.meta index 7d56260c..45135d2f 100644 --- a/Assets/Slua/LuaProfiler/Editor/TreeView/LuaProfilerWindow.cs.meta +++ b/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs.meta @@ -1,8 +1,7 @@ fileFormatVersion: 2 -guid: 55ce904866ab5b74f98c6fbff125c69b -timeCreated: 1471510735 -licenseType: Pro +guid: f34f72e56a74d4f49bf98472ecaa4bb0 MonoImporter: + externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 diff --git a/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs new file mode 100644 index 00000000..fcc8c1bb --- /dev/null +++ b/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs @@ -0,0 +1,391 @@ +/* +* ============================================================================== +* Filename: ToLuaHooSetup +* Created: 2018/7/2 11:36:16 +* Author: エル・プサイ・コングルゥ +* Purpose: +* ============================================================================== +*/ + +//#define TOLUA +#if TOLUA +#if UNITY_EDITOR + + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using XLua; +using LuaDLL = XLua.LuaDLL.Lua; + +namespace MikuLuaProfiler { + + [InitializeOnLoad] + public static class Startup + { + private static MethodHooker hookNewLuaEnv; + + public static readonly string luaPath; + static Startup() + { + HookNewLuaEnv(); + } + + public static void HookNewLuaEnv() + { + if (hookNewLuaEnv == null) + { + Type envReplace = typeof(LuaEnvReplace); + Type typeEnv = typeof(XLua.LuaEnv); + var clickFun = typeEnv.GetConstructors()[0]; + MethodInfo clickReplace = envReplace.GetMethod("Ctor"); + MethodInfo clickProxy = envReplace.GetMethod("Proxy", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(clickFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); + + Type typeDll = typeof(XLua.LuaDLL.Lua); + var newstateFun = typeDll.GetMethod("luaL_newstate"); + clickReplace = envReplace.GetMethod("luaL_newstate"); + clickProxy = envReplace.GetMethod("ProxyNewstate", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); + } + } + + public static class LuaEnvReplace + { + public static void Ctor(LuaEnv env) + { + Proxy(env); + MikuLuaProfiler.HookSetup.SetMainLuaEnv(env); + } + public static void Proxy(LuaEnv env) + { + } + + public static IntPtr luaL_newstate() + { + IntPtr l = ProxyNewstate(); + MikuLuaProfiler.LuaProfiler.mainL = l; + return l; + } + public static IntPtr ProxyNewstate() + { + return IntPtr.Zero; + } + } + + } + + [InitializeOnLoad] + static class HookSetup + { +#if !UNITY_2017_1_OR_NEWER + static bool isPlaying = false; +#endif + static HookSetup() + { +#if UNITY_2017_1_OR_NEWER + EditorApplication.playModeStateChanged += OnEditorPlaying; +#else + EditorApplication.playmodeStateChanged += () => + { + + if (isPlaying == true && EditorApplication.isPlaying == false) + { + LuaProfiler.SetMainLuaEnv(null); + } + + isPlaying = EditorApplication.isPlaying; + }; +#endif + } + + + public static void SetMainLuaEnv(LuaEnv env) + { + if (LuaDeepProfilerSetting.Instance.isDeepProfiler) + { + if (env != null) + { + env.DoString(@" +BeginMikuSample = CS.MikuLuaProfiler.LuaProfiler.BeginSample +EndMikuSample = CS.MikuLuaProfiler.LuaProfiler.EndSample + +function miku_unpack_return_value(...) + EndMikuSample() + return ... +end +"); + HookSetup.HookLuaFuns(); + } + } + + if (env == null) + { + HookSetup.Uninstall(); + LuaProfiler.mainL = IntPtr.Zero; + } + } + +#if UNITY_2017_1_OR_NEWER + public static void OnEditorPlaying(PlayModeStateChange playModeStateChange) + { + if (playModeStateChange == PlayModeStateChange.ExitingPlayMode) + { + SetMainLuaEnv(null); + } + } +#endif + +#region hook + +#region hook tostring + + public class LuaDll + { +#region luastring + public static readonly Dictionary stringDict = new Dictionary(); + public static bool TryGetLuaString(IntPtr p, out string result) + { + + return stringDict.TryGetValue((long)p, out result); + } + public static void RefString(IntPtr strPoint, int index, string s, IntPtr L) + { + int oldTop = LuaDLL.lua_gettop(L); + LuaDLL.lua_pushvalue(L, index); + //把字符串ref了之后就不GC了 + LuaDLL.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX); + LuaDLL.lua_settop(L, oldTop); + stringDict[(long)strPoint] = s; + } +#endregion + + public static int xluaL_loadbuffer(IntPtr L, byte[] buff, int size, string name) + { + if (LuaDeepProfilerSetting.Instance.isDeepProfiler)//&& name != "chunk" + { + var utf8WithoutBom = new System.Text.UTF8Encoding(true); + string fileName = name.Replace("@", "").Replace("/", ".") + ".lua"; + string value = utf8WithoutBom.GetString(buff); + value = Parse.InsertSample(value, fileName); + + //System.IO.File.WriteAllText(fileName, value); + + buff = utf8WithoutBom.GetBytes(value); + size = buff.Length; + } + + return ProxyLoadbuffer(L, buff, size, name); + } + + public static int ProxyLoadbuffer(IntPtr L, byte[] buff, int size, string name) + { + return 0; + } + + public static string lua_tostring(IntPtr L, int index) + { + IntPtr strlen; + + IntPtr str = LuaDLL.lua_tolstring(L, index, out strlen); + if (str != IntPtr.Zero) + { +#if XLUA_GENERAL || (UNITY_WSA && !UNITY_EDITOR) + int len = strlen.ToInt32(); + byte[] buffer = new byte[len]; + Marshal.Copy(str, buffer, 0, len); + return Encoding.UTF8.GetString(buffer); +#else + string ret; + if (TryGetLuaString(str, out ret)) + { + return ret; + } + + ret = Marshal.PtrToStringAnsi(str, strlen.ToInt32()); + if (ret == null) + { + int len = strlen.ToInt32(); + byte[] buffer = new byte[len]; + Marshal.Copy(str, buffer, 0, len); + ret = Encoding.UTF8.GetString(buffer); + } + if (ret != null) + { + RefString(str, index, ret, L); + } + return ret; +#endif + } + else + { + return null; + } + } + + public static string PoxyToString(IntPtr L, int index) + { + return null; + } + } +#endregion + + +#region hook profiler + public class Profiler + { + private static Stack m_Stack = new Stack(); + private static int m_currentFrame = 0; + public static void BeginSampleOnly(string name) + { + if (ProfilerDriver.deepProfiling) return; + if (Time.frameCount != m_currentFrame) + { + m_Stack.Clear(); + m_currentFrame = Time.frameCount; + } + m_Stack.Push(name); + ProxyBeginSample(name); + } + public static void BeginSample(string name, UnityEngine.Object targetObject) + { + if (ProfilerDriver.deepProfiling) return; + m_Stack.Push(name); + ProxyBeginSample(name, targetObject); + } + + public static void EndSample() + { + if (ProfilerDriver.deepProfiling) return; + if (m_Stack.Count <= 0) + { + return; + } + m_Stack.Pop(); + ProxyEndSample(); + } + + public static void ProxyBeginSample(string name) + { + } + public static void ProxyBeginSample(string name, UnityEngine.Object targetObject) + { + } + + public static void ProxyEndSample() + { + } + } +#endregion + +#region do hook + private static MethodHooker beginSampeOnly; + private static MethodHooker beginObjetSample; + private static MethodHooker endSample; + private static MethodHooker tostringHook; + private static MethodHooker loaderHook; + + private static bool m_hooked = false; + public static void HookLuaFuns() + { + if (m_hooked) return; + if (tostringHook == null) + { + Type typeLogReplace = typeof(LuaDll); + Type typeLog = typeof(LuaDLL); + MethodInfo tostringFun = typeLog.GetMethod("lua_tostring"); + MethodInfo tostringReplace = typeLogReplace.GetMethod("lua_tostring"); + MethodInfo tostringProxy = typeLogReplace.GetMethod("ProxyToString"); + + tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); + tostringHook.Install(); + + tostringFun = typeLog.GetMethod("xluaL_loadbuffer"); + tostringReplace = typeLogReplace.GetMethod("xluaL_loadbuffer"); + tostringProxy = typeLogReplace.GetMethod("ProxyLoadbuffer"); + + tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); + tostringHook.Install(); + } + + if (beginSampeOnly == null) + { + Type typeTarget = typeof(UnityEngine.Profiling.Profiler); + Type typeReplace = typeof(Profiler); + + MethodInfo hookTarget = typeTarget.GetMethod("BeginSampleOnly", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + MethodInfo hookReplace = typeReplace.GetMethod("BeginSampleOnly"); + MethodInfo hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + beginSampeOnly = new MethodHooker(hookTarget, hookReplace, hookProxy); + beginSampeOnly.Install(); + + hookTarget = typeTarget.GetMethod("BeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); + hookReplace = typeReplace.GetMethod("BeginSample"); + hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); + beginObjetSample = new MethodHooker(hookTarget, hookReplace, hookProxy); + beginObjetSample.Install(); + + hookTarget = typeTarget.GetMethod("EndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); + hookReplace = typeReplace.GetMethod("EndSample"); + hookProxy = typeReplace.GetMethod("ProxyEndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); + endSample = new MethodHooker(hookTarget, hookReplace, hookProxy); + endSample.Install(); + } + + m_hooked = true; + } + + public static void Uninstall() + { + if (beginSampeOnly != null) + { + beginSampeOnly.Uninstall(); + beginSampeOnly = null; + } + if (beginObjetSample != null) + { + beginObjetSample.Uninstall(); + beginObjetSample = null; + } + if (endSample != null) + { + endSample.Uninstall(); + endSample = null; + } + if (tostringHook != null) + { + tostringHook.Uninstall(); + tostringHook = null; + } + if (loaderHook != null) + { + loaderHook.Uninstall(); + loaderHook = null; + } + + m_hooked = false; + } +#endregion + +#endregion + } + + public class LuaLib + { + public static int lua_gc(IntPtr L, LuaGCOptions what, int data) + { + return LuaDLL.lua_gc(L, what, data); + } + + } +} + +#endif +#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs.meta b/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs.meta new file mode 100644 index 00000000..af11a100 --- /dev/null +++ b/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f92f64e7d491c7c44827f9f4c992f655 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs new file mode 100644 index 00000000..7910417d --- /dev/null +++ b/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs @@ -0,0 +1,420 @@ +/* +* ============================================================================== +* Filename: XLuaHooSetup +* Created: 2018/7/2 11:36:16 +* Author: エル・プサイ・コングルゥ +* Purpose: +* ============================================================================== +*/ + +//#define XLUA +#if XLUA + +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using XLua; +using LuaState = XLua.LuaEnv; +using LuaDLL = XLua.LuaDLL.Lua; + +namespace MikuLuaProfiler { + + public class LuaLib + { + public static void RunGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCCOLLECT, 0); + } + } + public static void StopGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCSTOP, 0); + } + } + public static void ResumeGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCRESTART, 0); + } + } + + public static long GetLuaMemory(IntPtr luaState) + { + long result = 0; + + result = LuaDLL.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNT, 0); + result = result * 1024 + LuaDLL.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNTB, 0); + + return result; + } + } + + [InitializeOnLoad] + public static class Startup + { + private static MethodHooker hookNewLuaEnv; + + public static readonly string luaPath; + static Startup() + { + HookNewLuaEnv(); + } + + public static void HookNewLuaEnv() + { + if (hookNewLuaEnv == null) + { + Type envReplace = typeof(LuaEnvReplace); + Type typeEnv = typeof(XLua.LuaEnv); + var clickFun = typeEnv.GetConstructors()[0]; + MethodInfo clickReplace = envReplace.GetMethod("Ctor"); + MethodInfo clickProxy = envReplace.GetMethod("Proxy", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(clickFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); + + Type typeDll = typeof(XLua.LuaDLL.Lua); + var newstateFun = typeDll.GetMethod("luaL_newstate"); + clickReplace = envReplace.GetMethod("luaL_newstate"); + clickProxy = envReplace.GetMethod("ProxyNewstate", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); + } + } + + public static class LuaEnvReplace + { + public static void Ctor(LuaState env) + { + Proxy(env); + MikuLuaProfiler.HookSetup.SetMainLuaEnv(env); + } + public static void Proxy(LuaState env) + { + } + + public static IntPtr luaL_newstate() + { + IntPtr l = ProxyNewstate(); + MikuLuaProfiler.LuaProfiler.mainL = l; + return l; + } + public static IntPtr ProxyNewstate() + { + return IntPtr.Zero; + } + } + + } + + [InitializeOnLoad] + static class HookSetup + { +#if !UNITY_2017_1_OR_NEWER + static bool isPlaying = false; +#endif + static HookSetup() + { +#if UNITY_2017_1_OR_NEWER + EditorApplication.playModeStateChanged += OnEditorPlaying; +#else + EditorApplication.playmodeStateChanged += () => + { + + if (isPlaying == true && EditorApplication.isPlaying == false) + { + LuaProfiler.SetMainLuaEnv(null); + } + + isPlaying = EditorApplication.isPlaying; + }; +#endif + } + + + public static void SetMainLuaEnv(LuaState env) + { + if (LuaDeepProfilerSetting.Instance.isDeepProfiler) + { + if (env != null) + { + env.DoString(@" +BeginMikuSample = CS.MikuLuaProfiler.LuaProfiler.BeginSample +EndMikuSample = CS.MikuLuaProfiler.LuaProfiler.EndSample + +function miku_unpack_return_value(...) + EndMikuSample() + return ... +end +"); + HookSetup.HookLuaFuns(); + } + } + + if (env == null) + { + HookSetup.Uninstall(); + LuaProfiler.mainL = IntPtr.Zero; + } + } + +#if UNITY_2017_1_OR_NEWER + public static void OnEditorPlaying(PlayModeStateChange playModeStateChange) + { + if (playModeStateChange == PlayModeStateChange.ExitingPlayMode) + { + SetMainLuaEnv(null); + } + } +#endif + +#region hook + +#region hook tostring + + public class LuaDll + { +#region luastring + public static readonly Dictionary stringDict = new Dictionary(); + public static bool TryGetLuaString(IntPtr p, out string result) + { + + return stringDict.TryGetValue((long)p, out result); + } + public static void RefString(IntPtr strPoint, int index, string s, IntPtr L) + { + int oldTop = LuaDLL.lua_gettop(L); + LuaDLL.lua_pushvalue(L, index); + //把字符串ref了之后就不GC了 + LuaDLL.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX); + LuaDLL.lua_settop(L, oldTop); + stringDict[(long)strPoint] = s; + } +#endregion + + public static int xluaL_loadbuffer(IntPtr L, byte[] buff, int size, string name) + { + if (LuaDeepProfilerSetting.Instance.isDeepProfiler)//&& name != "chunk" + { + var utf8WithoutBom = new System.Text.UTF8Encoding(true); + string fileName = name.Replace("@", "").Replace("/", ".") + ".lua"; + string value = utf8WithoutBom.GetString(buff); + value = Parse.InsertSample(value, fileName); + + //System.IO.File.WriteAllText(fileName, value); + + buff = utf8WithoutBom.GetBytes(value); + size = buff.Length; + } + + return ProxyLoadbuffer(L, buff, size, name); + } + + public static int ProxyLoadbuffer(IntPtr L, byte[] buff, int size, string name) + { + return 0; + } + + public static string lua_tostring(IntPtr L, int index) + { + IntPtr strlen; + + IntPtr str = LuaDLL.lua_tolstring(L, index, out strlen); + if (str != IntPtr.Zero) + { +#if XLUA_GENERAL || (UNITY_WSA && !UNITY_EDITOR) + int len = strlen.ToInt32(); + byte[] buffer = new byte[len]; + Marshal.Copy(str, buffer, 0, len); + return Encoding.UTF8.GetString(buffer); +#else + string ret; + if (TryGetLuaString(str, out ret)) + { + return ret; + } + + ret = Marshal.PtrToStringAnsi(str, strlen.ToInt32()); + if (ret == null) + { + int len = strlen.ToInt32(); + byte[] buffer = new byte[len]; + Marshal.Copy(str, buffer, 0, len); + ret = Encoding.UTF8.GetString(buffer); + } + if (ret != null) + { + RefString(str, index, ret, L); + } + return ret; +#endif + } + else + { + return null; + } + } + + public static string PoxyToString(IntPtr L, int index) + { + return null; + } + } +#endregion + + +#region hook profiler + public class Profiler + { + private static Stack m_Stack = new Stack(); + private static int m_currentFrame = 0; + public static void BeginSampleOnly(string name) + { + if (ProfilerDriver.deepProfiling) return; + if (Time.frameCount != m_currentFrame) + { + m_Stack.Clear(); + m_currentFrame = Time.frameCount; + } + m_Stack.Push(name); + ProxyBeginSample(name); + } + public static void BeginSample(string name, UnityEngine.Object targetObject) + { + if (ProfilerDriver.deepProfiling) return; + m_Stack.Push(name); + ProxyBeginSample(name, targetObject); + } + + public static void EndSample() + { + if (ProfilerDriver.deepProfiling) return; + if (m_Stack.Count <= 0) + { + return; + } + m_Stack.Pop(); + ProxyEndSample(); + } + + public static void ProxyBeginSample(string name) + { + } + public static void ProxyBeginSample(string name, UnityEngine.Object targetObject) + { + } + + public static void ProxyEndSample() + { + } + } +#endregion + +#region do hook + private static MethodHooker beginSampeOnly; + private static MethodHooker beginObjetSample; + private static MethodHooker endSample; + private static MethodHooker tostringHook; + private static MethodHooker loaderHook; + + private static bool m_hooked = false; + public static void HookLuaFuns() + { + if (m_hooked) return; + if (tostringHook == null) + { + Type typeLogReplace = typeof(LuaDll); + Type typeLog = typeof(LuaDLL); + MethodInfo tostringFun = typeLog.GetMethod("lua_tostring"); + MethodInfo tostringReplace = typeLogReplace.GetMethod("lua_tostring"); + MethodInfo tostringProxy = typeLogReplace.GetMethod("ProxyToString"); + + tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); + tostringHook.Install(); + + tostringFun = typeLog.GetMethod("xluaL_loadbuffer"); + tostringReplace = typeLogReplace.GetMethod("xluaL_loadbuffer"); + tostringProxy = typeLogReplace.GetMethod("ProxyLoadbuffer"); + + tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); + tostringHook.Install(); + } + + if (beginSampeOnly == null) + { + Type typeTarget = typeof(UnityEngine.Profiling.Profiler); + Type typeReplace = typeof(Profiler); + + MethodInfo hookTarget = typeTarget.GetMethod("BeginSampleOnly", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + MethodInfo hookReplace = typeReplace.GetMethod("BeginSampleOnly"); + MethodInfo hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null); + beginSampeOnly = new MethodHooker(hookTarget, hookReplace, hookProxy); + beginSampeOnly.Install(); + + hookTarget = typeTarget.GetMethod("BeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); + hookReplace = typeReplace.GetMethod("BeginSample"); + hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); + beginObjetSample = new MethodHooker(hookTarget, hookReplace, hookProxy); + beginObjetSample.Install(); + + hookTarget = typeTarget.GetMethod("EndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); + hookReplace = typeReplace.GetMethod("EndSample"); + hookProxy = typeReplace.GetMethod("ProxyEndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); + endSample = new MethodHooker(hookTarget, hookReplace, hookProxy); + endSample.Install(); + } + + m_hooked = true; + } + + public static void Uninstall() + { + if (beginSampeOnly != null) + { + beginSampeOnly.Uninstall(); + beginSampeOnly = null; + } + if (beginObjetSample != null) + { + beginObjetSample.Uninstall(); + beginObjetSample = null; + } + if (endSample != null) + { + endSample.Uninstall(); + endSample = null; + } + if (tostringHook != null) + { + tostringHook.Uninstall(); + tostringHook = null; + } + if (loaderHook != null) + { + loaderHook.Uninstall(); + loaderHook = null; + } + + m_hooked = false; + } +#endregion + +#endregion + } +} +#endif + +#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs.meta b/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs.meta new file mode 100644 index 00000000..8553be7c --- /dev/null +++ b/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d33d63a9cf6baa741908f7be36106221 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs b/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs deleted file mode 100644 index e672917e..00000000 --- a/Assets/Slua/LuaProfiler/UnityHook/StarUp.cs +++ /dev/null @@ -1,54 +0,0 @@ -/* -* ============================================================================== -* Filename: StarUp -* Created: 2018/7/2 11:36:16 -* Author: エル・プサイ・コングルゥ -* Purpose: -* ============================================================================== -*/ - -#if UNITY_EDITOR -using System; -using System.Reflection; -using UnityEditor; - -[InitializeOnLoad] -public static class Startup -{ - private static MethodHooker hookNewLuaEnv; - - public static readonly string luaPath; - static Startup() - { - HookNewLuaEnv(); - } - - public static void HookNewLuaEnv() - { - if (hookNewLuaEnv == null) - { - Type envReplace = typeof(LuaEnvReplace); - Type typeEnv = typeof(SLua.LuaState); - var clickFun = typeEnv.GetConstructors()[0]; - MethodInfo clickReplace = envReplace.GetMethod("Ctor"); - MethodInfo clickProxy = envReplace.GetMethod("Proxy", BindingFlags.Public | BindingFlags.Static); - hookNewLuaEnv = new MethodHooker(clickFun, clickReplace, clickProxy); - - hookNewLuaEnv.Install(); - } - } - - public static class LuaEnvReplace - { - public static void Ctor(SLua.LuaState env) - { - Proxy(env); - MikuLuaProfiler.LuaProfiler.SetMainLuaEnv(env); - - } - public static void Proxy(SLua.LuaState env) - { - } - } -} -#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs b/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs deleted file mode 100644 index bad2bd64..00000000 --- a/Assets/Slua/LuaProfiler/Utils/LuaProfiler.cs +++ /dev/null @@ -1,868 +0,0 @@ -#pragma warning disable CS0219//故意在c#这里产生于lua那边的等量GC -#if UNITY_EDITOR -using System; -using System.Collections.Generic; -using UnityEngine; -#if UNITY_5_5_OR_NEWER -using UnityEngine.Profiling; -#endif -using SLua; -using LuaLib = SLua.LuaDLL; -using System.Runtime.InteropServices; -using System.Reflection; -using UnityEditorInternal; -using UnityEditor; - -namespace MikuLuaProfiler -{ - - [InitializeOnLoad] - static class HookSetup - { -#if !UNITY_2017_1_OR_NEWER - static bool isPlaying = false; -#endif - static HookSetup() - { -#if UNITY_2017_1_OR_NEWER - EditorApplication.playModeStateChanged += OnEditorPlaying; -#else - EditorApplication.playmodeStateChanged += () => - { - - if (isPlaying == true && EditorApplication.isPlaying == false) - { - LuaProfiler.SetMainLuaEnv(null); - } - - isPlaying = EditorApplication.isPlaying; - }; -#endif - } - -#if UNITY_2017_1_OR_NEWER - public static void OnEditorPlaying(PlayModeStateChange playModeStateChange) - { - if (playModeStateChange == PlayModeStateChange.ExitingPlayMode) - { - LuaProfiler.SetMainLuaEnv(null); - } - } -#endif - - #region hook - - #region hook tostring - - public class LuaDll - { - #region luastring - public static readonly Dictionary stringDict = new Dictionary(); - public static bool TryGetLuaString(IntPtr p, out string result) - { - - return stringDict.TryGetValue((long)p, out result); - } - public static void RefString(IntPtr strPoint, int index, string s, IntPtr L) - { - int oldTop = LuaLib.lua_gettop(L); - LuaLib.lua_pushvalue(L, index); - //把字符串ref了之后就不GC了 - LuaLib.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX); - LuaLib.lua_settop(L, oldTop); - stringDict[(long)strPoint] = s; - } - #endregion - - public static int luaL_loadbuffer(IntPtr L, byte[] buff, int size, string name) - { - if (LuaDeepProfilerSetting.Instance.isDeepProfiler - && LuaProfiler.mainEnv.L == L)//&& name != "chunk" - { - var utf8WithoutBom = new System.Text.UTF8Encoding(true); - string fileName = name.Replace("@", "").Replace("/", ".") + ".lua"; - string value = utf8WithoutBom.GetString(buff); - value = Parse.InsertSample(value, fileName); - - //System.IO.File.WriteAllText(fileName, value); - - buff = utf8WithoutBom.GetBytes(value); - size = buff.Length; - } - - return ProxyLoadbuffer(L, buff, size, name); - } - - public static int ProxyLoadbuffer(IntPtr L, byte[] buff, int size, string name) - { - return 0; - } - - public static string lua_tostring(IntPtr luaState, int index) - { - int strlen; - - IntPtr str = LuaLib.luaS_tolstring32(luaState, index, out strlen); // fix il2cpp 64 bit - string s = null; - if (TryGetLuaString(str, out s)) - { - return s; - } - - if (strlen > 0 && str != IntPtr.Zero) - { - s = Marshal.PtrToStringAnsi(str); - // fallback method - if (s == null) - { - byte[] b = new byte[strlen]; - Marshal.Copy(str, b, 0, strlen); - s = System.Text.Encoding.Default.GetString(b); - } - } - if (s != null) - { - RefString(str, index, s, luaState); - } - - return (s == null) ? string.Empty : s; - } - - public static string PoxyToString(IntPtr L, int index) - { - return null; - } - } - #endregion - - - #region hook profiler - public class Profiler - { - private static Stack m_Stack = new Stack(); - private static int m_currentFrame = 0; - public static void BeginSampleOnly(string name) - { - if (ProfilerDriver.deepProfiling) return; - if (Time.frameCount != m_currentFrame) - { - m_Stack.Clear(); - m_currentFrame = Time.frameCount; - } - m_Stack.Push(name); - ProxyBeginSample(name); - } - public static void BeginSample(string name, UnityEngine.Object targetObject) - { - if (ProfilerDriver.deepProfiling) return; - m_Stack.Push(name); - ProxyBeginSample(name, targetObject); - } - - public static void EndSample() - { - if (ProfilerDriver.deepProfiling) return; - if (m_Stack.Count <= 0) - { - return; - } - m_Stack.Pop(); - ProxyEndSample(); - } - - public static void ProxyBeginSample(string name) - { - } - public static void ProxyBeginSample(string name, UnityEngine.Object targetObject) - { - } - - public static void ProxyEndSample() - { - } - } - #endregion - - #region do hook - private static MethodHooker beginSampeOnly; - private static MethodHooker beginObjetSample; - private static MethodHooker endSample; - private static MethodHooker tostringHook; - private static MethodHooker loaderHook; - - private static bool m_hooked = false; - public static void HookLuaFuns() - { - if (m_hooked) return; - if (tostringHook == null) - { - Type typeLogReplace = typeof(LuaDll); - Type typeLog = typeof(LuaLib); - MethodInfo tostringFun = typeLog.GetMethod("lua_tostring"); - MethodInfo tostringReplace = typeLogReplace.GetMethod("lua_tostring"); - MethodInfo tostringProxy = typeLogReplace.GetMethod("ProxyToString"); - - tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); - tostringHook.Install(); - - tostringFun = typeLog.GetMethod("luaL_loadbuffer"); - tostringReplace = typeLogReplace.GetMethod("luaL_loadbuffer"); - tostringProxy = typeLogReplace.GetMethod("ProxyLoadbuffer"); - - tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); - tostringHook.Install(); - } - - if (beginSampeOnly == null) - { - Type typeTarget = typeof(UnityEngine.Profiling.Profiler); - Type typeReplace = typeof(Profiler); - - MethodInfo hookTarget = typeTarget.GetMethod("BeginSampleOnly", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null); - MethodInfo hookReplace = typeReplace.GetMethod("BeginSampleOnly"); - MethodInfo hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, null); - beginSampeOnly = new MethodHooker(hookTarget, hookReplace, hookProxy); - beginSampeOnly.Install(); - - hookTarget = typeTarget.GetMethod("BeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); - hookReplace = typeReplace.GetMethod("BeginSample"); - hookProxy = typeReplace.GetMethod("ProxyBeginSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(UnityEngine.Object) }, null); - beginObjetSample = new MethodHooker(hookTarget, hookReplace, hookProxy); - beginObjetSample.Install(); - - hookTarget = typeTarget.GetMethod("EndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); - hookReplace = typeReplace.GetMethod("EndSample"); - hookProxy = typeReplace.GetMethod("ProxyEndSample", BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null); - endSample = new MethodHooker(hookTarget, hookReplace, hookProxy); - endSample.Install(); - } - - m_hooked = true; - } - - public static void Uninstall() - { - if (beginSampeOnly != null) - { - beginSampeOnly.Uninstall(); - beginSampeOnly = null; - } - if (beginObjetSample != null) - { - beginObjetSample.Uninstall(); - beginObjetSample = null; - } - if (endSample != null) - { - endSample.Uninstall(); - endSample = null; - } - if (tostringHook != null) - { - tostringHook.Uninstall(); - tostringHook = null; - } - if (loaderHook != null) - { - loaderHook.Uninstall(); - loaderHook = null; - } - - m_hooked = false; - } - #endregion - - #endregion - } - - public class LuaProfiler - { - public static LuaState mainEnv - { - get - { - return _mainEnv; - } - } - private static LuaState _mainEnv; - public static void SetMainLuaEnv(LuaState env) - { - //不支持多栈 - if (_mainEnv != null && env != null) return; - - _mainEnv = env; - if (LuaDeepProfilerSetting.Instance.isDeepProfiler) - { - if (env != null) - { - Lua_MikuLuaProfiler_LuaProfiler.reg(env.L); - env.doString(@" -BeginMikuSample = MikuLuaProfiler.LuaProfiler.BeginSample -EndMikuSample = MikuLuaProfiler.LuaProfiler.EndSample - -function miku_unpack_return_value(...) - EndMikuSample() - return ... -end -"); - HookSetup.HookLuaFuns(); - } - } - - if (env == null) - { - HookSetup.Uninstall(); - } - } - - public static string GetLuaMemory() - { - long result = 0; - if (_mainEnv != null) - { - try - { - result = GetLuaMemory(_mainEnv.L); - } - catch { } - } - - return GetMemoryString(result); - } - - public static long GetLuaMemory(IntPtr luaState) - { - long result = 0; - - result = LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNT, 0); - result = result * 1024 + LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNTB, 0); - - return result; - } - - public class Sample - { - private static ObjectPool samplePool = new ObjectPool(250); - public static Sample Create(float time, long memory, string name) - { - Sample s = samplePool.GetObject(); - s.currentTime = time; - s.currentLuaMemory = memory; - s.realCurrentLuaMemory = memory; - s.costGC = 0; - s.name = name; - s.costTime = 0; - s.childs.Clear(); - s._father = null; - s._fullName = null; - - return s; - } - - public void Restore() - { - for (int i = 0, imax = childs.Count; i < imax; i++) - { - childs[i].Restore(); - } - samplePool.Store(this); - } - - public int oneFrameCall - { - get - { - return 1; - } - } - public float currentTime { private set; get; } - public long realCurrentLuaMemory { private set; get; } - private string _name; - public string name - { - private set - { - _name = value; - } - get - { - return _name; - } - } - - private static Dictionary> m_fullNamePool = new Dictionary>(); - private string _fullName = null; - public string fullName - { - get - { - if (_father == null) return _name; - - if (_fullName == null) - { - Dictionary childDict; - if (!m_fullNamePool.TryGetValue(_father.fullName, out childDict)) - { - childDict = new Dictionary(); - m_fullNamePool.Add(_father.fullName, childDict); - } - - if (!childDict.TryGetValue(_name, out _fullName)) - { - string value = _name; - var f = _father; - while (f != null) - { - value = f.name + value; - f = f.fahter; - } - _fullName = value; - childDict[_name] = _fullName; - } - - return _fullName; - } - else - { - return _fullName; - } - } - } - //这玩意在统计的window里面没啥卵用 - public long currentLuaMemory { set; get; } - - private float _costTime; - public float costTime - { - set - { - _costTime = value; - } - get - { - float result = _costTime; - return result; - } - } - - private long _costGC; - public long costGC - { - set - { - _costGC = value; - } - get - { - return _costGC; - } - } - private Sample _father; - public Sample fahter - { - set - { - _father = value; - if (_father != null) - { - _father.childs.Add(this); - } - } - get - { - return _father; - } - } - - public readonly List childs = new List(256); - } - //开始采样时候的lua内存情况,因为中间有可能会有二次采样,所以要丢到一个盏中 - public static readonly List beginSampleMemoryStack = new List(); - - private static Action m_SampleEndAction; - - private static bool isDeep - { - get - { -#if UNITY_EDITOR - return ProfilerDriver.deepProfiling; -#else - return false; -#endif - } - } - public static void SetSampleEnd(Action action) - { - m_SampleEndAction = action; - } - public static void BeginSample(string name) - { -#if DEBUG - if (_mainEnv != null) - { - BeginSample(_mainEnv.L, name); - } -#endif - } - public static void BeginSample(IntPtr luaState) - { -#if DEBUG - BeginSample(luaState, "lua gc"); -#endif - } - private static int m_currentFrame = 0; - public static void BeginSample(IntPtr luaState, string name) - { - if (m_currentFrame != Time.frameCount) - { - PopAllSampleWhenLateUpdate(); - m_currentFrame = Time.frameCount; - } - -#if DEBUG - //if (beginSampleMemoryStack.Count == 0 && LuaDeepProfilerSetting.Instance.isDeepProfiler) - // LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCSTOP, 0); - - long memoryCount = GetLuaMemory(luaState); - Sample sample = Sample.Create(Time.realtimeSinceStartup, memoryCount, name); - - beginSampleMemoryStack.Add(sample); - if (!isDeep) - { - Profiler.BeginSample(name); - } -#endif - } - public static void PopAllSampleWhenLateUpdate() - { - for (int i = 0, imax = beginSampleMemoryStack.Count; i < imax; i++) - { - var item = beginSampleMemoryStack[i]; - if (item.fahter == null) - { - item.Restore(); - } - } - beginSampleMemoryStack.Clear(); - } - public static void EndSample() - { -#if DEBUG - if (_mainEnv != null) - { - EndSample(_mainEnv.L); - } -#endif - } - public static void EndSample(IntPtr luaState) - { -#if DEBUG - if (beginSampleMemoryStack.Count <= 0) - { - return; - } - int count = beginSampleMemoryStack.Count; - Sample sample = beginSampleMemoryStack[beginSampleMemoryStack.Count - 1]; - long oldMemoryCount = sample.currentLuaMemory; - beginSampleMemoryStack.RemoveAt(count - 1); - long nowMemoryCount = GetLuaMemory(luaState); - sample.fahter = count > 1 ? beginSampleMemoryStack[count - 2] : null; - - if (!isDeep) - { - long delta = nowMemoryCount - oldMemoryCount; - - long tmpDelta = delta; - if (delta > 0) - { - delta = Math.Max(delta - 40, 0);//byte[0] 的字节占用是40 - byte[] luagc = new byte[delta]; - } - for (int i = 0, imax = beginSampleMemoryStack.Count; i < imax; i++) - { - Sample s = beginSampleMemoryStack[i]; - s.currentLuaMemory += tmpDelta; - beginSampleMemoryStack[i] = s; - } - Profiler.EndSample(); - } - - sample.costTime = Time.realtimeSinceStartup - sample.currentTime; - var gc = nowMemoryCount - sample.realCurrentLuaMemory; - sample.costGC = gc > 0 ? gc : 0; - //if (beginSampleMemoryStack.Count == 0 && LuaDeepProfilerSetting.Instance.isDeepProfiler) - //{ - // LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCRESTART, 0); - // LuaLib.lua_gc(luaState, LuaGCOptions.LUA_GCCOLLECT, 0); - //} - - - if (m_SampleEndAction != null && beginSampleMemoryStack.Count == 0) - { - m_SampleEndAction(sample); - } - - if (sample.fahter == null) - { - sample.Restore(); - } -#endif - } - - const long MaxB = 1024; - const long MaxK = MaxB * 1024; - const long MaxM = MaxK * 1024; - const long MaxG = MaxM * 1024; - - public static string GetMemoryString(long value, string unit = "B") - { - string result = null; - if (value < MaxB) - { - result = string.Format("{0}{1}", value, unit); - } - else if (value < MaxK) - { - result = string.Format("{0:N2}K{1}", (float)value / MaxB, unit); - } - else if (value < MaxM) - { - result = string.Format("{0:N2}M{1}", (float)value / MaxK, unit); - } - else if (value < MaxG) - { - result = string.Format("{0:N2}G{1}", (float)value / MaxM, unit); - } - return result; - } - } - - #region bind - [UnityEngine.Scripting.Preserve] - public class Lua_MikuLuaProfiler_LuaProfiler : LuaObject - { - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int constructor(IntPtr l) - { - try - { - MikuLuaProfiler.LuaProfiler o; - o = new MikuLuaProfiler.LuaProfiler(); - pushValue(l, true); - pushValue(l, o); - return 2; - } - catch (Exception e) - { - return error(l, e); - } - } - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int GetLuaMemory_s(IntPtr l) - { - try - { - int argc = LuaDLL.lua_gettop(l); - if (argc == 0) - { - var ret = MikuLuaProfiler.LuaProfiler.GetLuaMemory(); - pushValue(l, true); - pushValue(l, ret); - return 2; - } - else if (argc == 1) - { - System.IntPtr a1; - checkType(l, 1, out a1); - var ret = MikuLuaProfiler.LuaProfiler.GetLuaMemory(a1); - pushValue(l, true); - pushValue(l, ret); - return 2; - } - pushValue(l, false); - LuaDLL.lua_pushstring(l, "No matched override function GetLuaMemory to call"); - return 2; - } - catch (Exception e) - { - return error(l, e); - } - } - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int SetSampleEnd_s(IntPtr l) - { - try - { - System.Action a1; - checkDelegate(l, 1, out a1); - MikuLuaProfiler.LuaProfiler.SetSampleEnd(a1); - pushValue(l, true); - return 1; - } - catch (Exception e) - { - return error(l, e); - } - } - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int BeginSample_s(IntPtr l) - { - try - { - int argc = LuaDLL.lua_gettop(l); - if (matchType(l, argc, 1, typeof(System.IntPtr))) - { - System.IntPtr a1; - checkType(l, 1, out a1); - MikuLuaProfiler.LuaProfiler.BeginSample(a1); - pushValue(l, true); - return 1; - } - else if (matchType(l, argc, 1, typeof(string))) - { - System.String a1; - checkType(l, 1, out a1); - MikuLuaProfiler.LuaProfiler.BeginSample(a1); - pushValue(l, true); - return 1; - } - else if (argc == 2) - { - System.IntPtr a1; - checkType(l, 1, out a1); - System.String a2; - checkType(l, 2, out a2); - MikuLuaProfiler.LuaProfiler.BeginSample(a1, a2); - pushValue(l, true); - return 1; - } - pushValue(l, false); - LuaDLL.lua_pushstring(l, "No matched override function BeginSample to call"); - return 2; - } - catch (Exception e) - { - return error(l, e); - } - } - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int PopAllSampleWhenLateUpdate_s(IntPtr l) - { - try - { - MikuLuaProfiler.LuaProfiler.PopAllSampleWhenLateUpdate(); - pushValue(l, true); - return 1; - } - catch (Exception e) - { - return error(l, e); - } - } - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int EndSample_s(IntPtr l) - { - try - { - int argc = LuaDLL.lua_gettop(l); - if (argc == 0) - { - MikuLuaProfiler.LuaProfiler.EndSample(); - pushValue(l, true); - return 1; - } - else if (argc == 1) - { - System.IntPtr a1; - checkType(l, 1, out a1); - MikuLuaProfiler.LuaProfiler.EndSample(a1); - pushValue(l, true); - return 1; - } - pushValue(l, false); - LuaDLL.lua_pushstring(l, "No matched override function EndSample to call"); - return 2; - } - catch (Exception e) - { - return error(l, e); - } - } - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int GetMemoryString_s(IntPtr l) - { - try - { - System.Int64 a1; - checkType(l, 1, out a1); - System.String a2; - checkType(l, 2, out a2); - var ret = MikuLuaProfiler.LuaProfiler.GetMemoryString(a1, a2); - pushValue(l, true); - pushValue(l, ret); - return 2; - } - catch (Exception e) - { - return error(l, e); - } - } - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int get_beginSampleMemoryStack(IntPtr l) - { - try - { - pushValue(l, true); - pushValue(l, MikuLuaProfiler.LuaProfiler.beginSampleMemoryStack); - return 2; - } - catch (Exception e) - { - return error(l, e); - } - } - [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] - [UnityEngine.Scripting.Preserve] - static public int get_mainEnv(IntPtr l) - { - try - { - pushValue(l, true); - pushValue(l, MikuLuaProfiler.LuaProfiler.mainEnv); - return 2; - } - catch (Exception e) - { - return error(l, e); - } - } - [UnityEngine.Scripting.Preserve] - static public void reg(IntPtr l) - { - getTypeTable(l, "MikuLuaProfiler.LuaProfiler"); - addMember(l, GetLuaMemory_s); - addMember(l, SetSampleEnd_s); - addMember(l, BeginSample_s); - addMember(l, PopAllSampleWhenLateUpdate_s); - addMember(l, EndSample_s); - addMember(l, GetMemoryString_s); - addMember(l, "beginSampleMemoryStack", get_beginSampleMemoryStack, null, false); - addMember(l, "mainEnv", get_mainEnv, null, false); - createTypeMetatable(l, constructor, typeof(MikuLuaProfiler.LuaProfiler)); - } - } - #endregion - -} - -#endif - From 29eb85fb14291594284b33c26839993171b868f2 Mon Sep 17 00:00:00 2001 From: ElPsyCongree <345036769@qq.com> Date: Mon, 19 Nov 2018 12:14:57 +0800 Subject: [PATCH 7/9] to fix unsafe error,thanks to panweiwei --- .../Common/{UnityHook.meta => Plugins.meta} | 2 +- .../LuaProfiler/Common/Plugins/MonoHook.dll | Bin 0 -> 9728 bytes .../Common/Plugins/MonoHook.dll.meta | 107 +++ .../Common/UnityHook/HookerPool.cs | 31 - .../Common/UnityHook/HookerPool.cs.meta | 11 - .../LuaProfiler/Common/UnityHook/LDasm.cs | 877 ------------------ .../Common/UnityHook/LDasm.cs.meta | 11 - .../Common/UnityHook/MethodHooker.cs | 268 ------ .../Common/UnityHook/MethodHooker.cs.meta | 11 - 9 files changed, 108 insertions(+), 1210 deletions(-) rename Assets/Slua/LuaProfiler/Common/{UnityHook.meta => Plugins.meta} (77%) create mode 100644 Assets/Slua/LuaProfiler/Common/Plugins/MonoHook.dll create mode 100644 Assets/Slua/LuaProfiler/Common/Plugins/MonoHook.dll.meta delete mode 100644 Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs delete mode 100644 Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs.meta delete mode 100644 Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs delete mode 100644 Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs.meta delete mode 100644 Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs delete mode 100644 Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs.meta diff --git a/Assets/Slua/LuaProfiler/Common/UnityHook.meta b/Assets/Slua/LuaProfiler/Common/Plugins.meta similarity index 77% rename from Assets/Slua/LuaProfiler/Common/UnityHook.meta rename to Assets/Slua/LuaProfiler/Common/Plugins.meta index d2a29dc6..ef91f0bb 100644 --- a/Assets/Slua/LuaProfiler/Common/UnityHook.meta +++ b/Assets/Slua/LuaProfiler/Common/Plugins.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 635b0b64fcd9b8b4aa20083ddfe769f2 +guid: ce88095a24ce5a04797f2335bf7c7fb7 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Slua/LuaProfiler/Common/Plugins/MonoHook.dll b/Assets/Slua/LuaProfiler/Common/Plugins/MonoHook.dll new file mode 100644 index 0000000000000000000000000000000000000000..8b10642e01692d051c6816cb8b50bbdd844cdf04 GIT binary patch literal 9728 zcmeHNYj7Labw0aTEC4=4g0x8LVG*)KiRJ^wgD=>UO^^U6+M-C2v`pFZKp?QBRsyi} z0+hu_)WD7Fi6^mAO=moDt%=pdnda58>)MIk#vRAbByHnNQzdbm#%<+Eo$0ihI+JEn zPusEkox2MVq!icvlj)CKYQKBVx%ZrV?z!jQeLxfU{th{aNWt~~`$R9I<}xhsgQ0}x zP3?cOiJtd-XXlI3!SC#xJ)swTOQx}CrgFY?DxWuszIn}OF6VuE-WQvk@#Tz+=C7}> z*=?JiiW41_9JD$&_~r6wSLr5StrQ^I0f}l!KYc4|AFg3sM2&)$Ex%d7{)`AKx{0>iRvJGB|cdr4hT*;6)UpOTvafU;7uzZa86=lsq7gw< zL5bsv!oJDmq=TJAMK6&gr2ltl4mju*s6pR*9-zogWWZicPqS)xu$-3AVKhP3Rc4x* z8L&9x1~7ZN{DsY^OKhTRhkO>(QMzuD9jk&MY7Qv$lD!b4q)w&8fEQo$y+%m0i{p*m4(ioa8V2 zvcUy=bm7U^r426F^LPlln3h)zA)n>ASa?D2#8$)%SahCO@{^oZb?&RR_EI;vPTA z7pJ{S>s&La${hZ&>!$O&(I{U8(P0rHR}NxM2$Z*_uF3JASLwu3 zsx`9bEDj1~uGX#2)w%PIlecV9Tf-jLnD2Gy`|ISqnxDJcNNP*}U%I<^zu$udL`b}H zO-o0!+R^OnXjW=vz4!K(E_JMZA?#>vc7j-BLRFEDNP#Q04br)8jMFjJcKph;tGyxI zP?5B<1iGEr(5kP5VUxWtC_Q0yY-@UJxTir<$5wlw>p07}CEc35(yUapw?exziqf;q z(z>D2F08W$t}a-TkG9@-^2#btxj|}O4WPCXwN7~HhGR#o1u(;XI>umO+TDqSRO>yK zk3Ab_;AwA6_6+<=?e1-EF~fSd?=t2Xs&o53i#niq)n=!seK;A(@j)=QJ$e>H9{A2%du+8A?yQgK4n)B1`z36JH+kJXPOrN)?DSyX80kMYNYS_6>)PTU z^?J63Z&!LTc<*4vHn#Kz+wF}o?l{X0Hzv1*>%6XBcZ0NbH3Wgz6-O$FTil(fXK{5e zD?Piso^9b=xs%c}Us(zfH`wc*^r}4#UeES$qCsjCwz~JR7Cf;`clYw@@F3m0l%AT3 zxCM&fY@LcHj77lN0LqMSncD{+B{p6y-t>G z!k}Jf>-^Cxq6L%S4jp5jEy*j$1;<%td-lUL6C%UQ*%gMMGU@hV4CHC|C40OTSve5c z2f_M^)Y=C-tb0?}8$P~!+c&*VShQ}DyFpdA++1nlz0c>_$jqH1lKtdR2mW-(AMp1F z21A2jaO!4(SsVwu&J+DD>VL)|v1_Ji>iNY2_jm={P(i)x?iu<9Xq-H>YwYgv80znX zzl7Kv;%*`GU^q*3ga4U!B#E(?C`4PfHc(NdWRVBBR@pZ>H)dp(A3{?7s!wboIWVA|2f$s?XQ-R+X zSS#?V<;w7@+=BCCFvN}~$nbB@0O&bxl#R1vF^44cC8qYfVNxP#?IfgvZ;GcK0D zBKq$W@=vKh;0aJjxXf$ukkm+Dhin|QwKgM&{5a;TfN!KCHV>9+wvvgzYA=2F#VYNDGcoV*pGi*x?~oZI0(T1hsE~&Q zy-nbcgyeey|6Jf+fnO!o9TT`B@Hrv>iNKqM?g5~e0*=eTCtR1Mu=I234}fpcmw*yI z1H4yad{p{lNu^HLSAfq6{F#&K1)1@*kQ^0~#{``f^lu3|4SLFl5Ypo`q`m~ZAGf(l z_3L;ydO~m)+a6KBB`Ned!9C)BO8z#uKNQ?U?k~%mv7#?l^>k8ZC!svjR#N{;a?(#N zjy{9TantUbL>qlkaOVW~5S0Y?tj%>JyWI3soBJN}(M{)evED+Tkf|0e z(I|3QqMyht|ApMx=%6g9u=4ZZYEvO4+un@)UEU=P^B*jtfN7vAMakTI|qSJ%8cXAJAmBhFX$_- zIK4$v&I!bdCBILTjwAF(lveM9{IsgkU(+{TXF+G^Vc;|LG2kxjkGJUCt|zfSZ=`=g zybsW;z=QNH;1soX{-^-G_zl z$As=D(0?zTMSnll($6J7PSO7YZlkTTpLWqMU^fMUVS%^NAm~vczl#!}XN3GdfoWQV zf8Cy*ydk4kxo?iZ{It1j4d zi}gpM1JOt*GTIjp$72JDXn>N*nPRG_r=zBs zS{cvl#o3i5ZASlywm%r6$*E*4IvY)n2M3bl;Sd#)Cv!_9%L@xhFtf&ZzKA-er;ECg zPnj#nuhB@35A;=Otz%MV4!m9f2vex5#`^%rLj40Y*dG`f2!!Gzi9p{#-(WZ#97+U7 z;=ouu5$f*`jt&e41_lN{z)W~(ZM-4cH(_L!v)b*nZ_3n9r;6HmZYitfv^+=0$j7u| zO3xN`HuYHhw4;k3n_((2?jtJViiSMIM?w z6i4H9{9tr;{79TzD|Dc5G!`6<_4P+W{lUP$>c~X zeF{%n30=!(Xew1qpO{UV(4EkVCyWdwi|aU3Tgs-=V#(|dLS@P{&a9L*@S(55cBG&b*$*gw|U5m@^h8^!%aa+{g-+WY%06(~4pt=`=4VO=~&hw3Ptdn~bG& zCRt!g&*dy4qZgJesei7dFg@azAS|i&zY%^9AY~}=STayr|#ByF(pEC4(5u#b6O!?DA!z6!NRQ5idDw>#ek*j3Y zUKP9u5K}Ruct|Vav^AE^vePj=wU{>wxJebtB70r^u!m_Z&1mLnJ*{2ST;6?^c55%; zOu=c7?rE{_%SOu~^RK0Rfs)pA3*}?sfRyVbPo( zHYpOsrmGH`q;|%3fGHCfXN{i9BD=)h7>CEw3XCS>4=h7kT-oTT^&s%MdrBX_@A35L z6UwW%ycGVs-zCK-Np6RaBqs>3m)T7Wvf%eCE~%-`y}4;ZZaNIYC(F&$*etnxveZn? ztOS9JWT{E_=J0&#hJyE~;v?@-7;SU9KW( zTP~WZY>#hhc|NPB@6uLgu>wdis&_jsAFkapzAdUw5pyhwlOze<@brfe${rtLdYJ$4`s>8o zj*RbH9BEVtp`(*zw}eMOE<5;rc~)+@$4o69GV<{=X-%BIvnNdB!GeT-98{uuD3p&I zd_drE(PhEq51yEe1Gp?AT!Y0 z7g$*69SYB9dT|@p=0knyP=9CvKk!K83R*$sfw)}WKlK{d?9ME%ckx@*yZA6)U3kH( z%k)faX0k3jyZ5{&dNFy|zsBG9e}g@b-+Jsp!#s7YJfZ6l#{9`+xY=m9BUf7eOPP5p z;;Fk_E->vYbL;E!Z%&ppIck`(Y<420=dFh!O%sPUx4z#28;#1(+TUJh;f2QM9nYUh zv;^pco^S`<$5#@Yf)^6f*W|C!%daFv-z z&J86Ri$;0ua#=H>3KA(KGL3!YBH6`lmuOU`;o)e^L0DNV4%@-U0Z}u}!?DWl-Re%( zFHxz)txT7Dl?08J;nJEf>@>QvjVUy6%|&#Dm8sHU(ZP~_c#NX38IB5T?cPvWR$VaO za1?#5y1J$8YIK-QqFt2g9v^}(U6VIuL%*qiMFjYZAQ67|Dy{FpCn0_?xQ?9r9l$j= zjK#z$TQ0*6{`z_ZUjmZAI8B2Z$1m-Nz>fnH*01jLvhvP*BDuu~tj!}4-67DrD(a!*9m1mX9Fe<^PB6{hNFJ8ye zIngJ8d0T&dG|w@ht#35=G;ml2>fg%(F(iB^QCi}=>WbGcUL!#6VD zb>{7(5IhUo|Er=4maA=BtCzu8IgDjuw&e)(e`4S{Oxok}>hT=PF<{P$_6N-+Bw{`V z8wO^$jCdETqQL9K*|I*?eInNDbv8zPW4woiP88l3#9Zd#=?dcUzv+p9|2hB(&EMTF RSKYgR9mD=+jQ<}I_#Y=GrGo$f literal 0 HcmV?d00001 diff --git a/Assets/Slua/LuaProfiler/Common/Plugins/MonoHook.dll.meta b/Assets/Slua/LuaProfiler/Common/Plugins/MonoHook.dll.meta new file mode 100644 index 00000000..5287a4b4 --- /dev/null +++ b/Assets/Slua/LuaProfiler/Common/Plugins/MonoHook.dll.meta @@ -0,0 +1,107 @@ +fileFormatVersion: 2 +guid: c6d09b9a6b740e34e8f15528fdc572ee +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + '': Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux: 1 + Exclude Linux64: 1 + Exclude LinuxUniversal: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Standalone: LinuxUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs b/Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs deleted file mode 100644 index 88b6c1b9..00000000 --- a/Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs +++ /dev/null @@ -1,31 +0,0 @@ -#if UNITY_EDITOR -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using UnityEngine; - -///

-/// Hooker 池,防止重复 Hook -/// -public static class HookerPool -{ - private static Dictionary _hookers = new Dictionary(); - - public static void AddHooker(MethodBase method, MethodHooker hooker) - { - MethodHooker preHooker; - if (_hookers.TryGetValue(method, out preHooker)) - { - preHooker.Uninstall(); - _hookers[method] = hooker; - } - else - _hookers.Add(method, hooker); - } - - public static void RemoveHooker(MethodBase method) - { - _hookers.Remove(method); - } -} -#endif diff --git a/Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs.meta b/Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs.meta deleted file mode 100644 index 75038591..00000000 --- a/Assets/Slua/LuaProfiler/Common/UnityHook/HookerPool.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6b7421e47f0ae1e4ebb72bf18d1d7d48 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs b/Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs deleted file mode 100644 index 1de7eb25..00000000 --- a/Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs +++ /dev/null @@ -1,877 +0,0 @@ -#if UNITY_EDITOR -using System; - -namespace DotNetDetour -{ - /// - /// 用于计算汇编指令长度,使用的是BlackBone的LDasm.c中的算法,我把他翻译成C#了 - /// - public unsafe class LDasm - { - const int F_INVALID = 0x01; - const int F_PREFIX = 0x02; - const int F_REX = 0x04; - const int F_MODRM = 0x08; - const int F_SIB = 0x10; - const int F_DISP = 0x20; - const int F_IMM = 0x40; - const int F_RELATIVE = 0x80; - - const int OP_NONE = 0x00; - const int OP_INVALID = 0x80; - - const int OP_DATA_I8 = 0x01; - const int OP_DATA_I16 = 0x02; - const int OP_DATA_I16_I32 = 0x04; - const int OP_DATA_I16_I32_I64 = 0x08; - const int OP_EXTENDED = 0x10; - const int OP_RELATIVE = 0x20; - const int OP_MODRM = 0x40; - const int OP_PREFIX = 0x80; - - struct ldasm_data - { - public byte flags; - public byte rex; - public byte modrm; - public byte sib; - public byte opcd_offset; - public byte opcd_size; - public byte disp_offset; - public byte disp_size; - public byte imm_offset; - public byte imm_size; - } - - static byte[] flags_table = - { - /* 00 */ OP_MODRM, - /* 01 */ OP_MODRM, - /* 02 */ OP_MODRM, - /* 03 */ OP_MODRM, - /* 04 */ OP_DATA_I8, - /* 05 */ OP_DATA_I16_I32, - /* 06 */ OP_NONE, - /* 07 */ OP_NONE, - /* 08 */ OP_MODRM, - /* 09 */ OP_MODRM, - /* 0A */ OP_MODRM, - /* 0B */ OP_MODRM, - /* 0C */ OP_DATA_I8, - /* 0D */ OP_DATA_I16_I32, - /* 0E */ OP_NONE, - /* 0F */ OP_NONE, - - /* 10 */ OP_MODRM, - /* 11 */ OP_MODRM, - /* 12 */ OP_MODRM, - /* 13 */ OP_MODRM, - /* 14 */ OP_DATA_I8, - /* 15 */ OP_DATA_I16_I32, - /* 16 */ OP_NONE, - /* 17 */ OP_NONE, - /* 18 */ OP_MODRM, - /* 19 */ OP_MODRM, - /* 1A */ OP_MODRM, - /* 1B */ OP_MODRM, - /* 1C */ OP_DATA_I8, - /* 1D */ OP_DATA_I16_I32, - /* 1E */ OP_NONE, - /* 1F */ OP_NONE, - - /* 20 */ OP_MODRM, - /* 21 */ OP_MODRM, - /* 22 */ OP_MODRM, - /* 23 */ OP_MODRM, - /* 24 */ OP_DATA_I8, - /* 25 */ OP_DATA_I16_I32, - /* 26 */ OP_PREFIX, - /* 27 */ OP_NONE, - /* 28 */ OP_MODRM, - /* 29 */ OP_MODRM, - /* 2A */ OP_MODRM, - /* 2B */ OP_MODRM, - /* 2C */ OP_DATA_I8, - /* 2D */ OP_DATA_I16_I32, - /* 2E */ OP_PREFIX, - /* 2F */ OP_NONE, - - /* 30 */ OP_MODRM, - /* 31 */ OP_MODRM, - /* 32 */ OP_MODRM, - /* 33 */ OP_MODRM, - /* 34 */ OP_DATA_I8, - /* 35 */ OP_DATA_I16_I32, - /* 36 */ OP_PREFIX, - /* 37 */ OP_NONE, - /* 38 */ OP_MODRM, - /* 39 */ OP_MODRM, - /* 3A */ OP_MODRM, - /* 3B */ OP_MODRM, - /* 3C */ OP_DATA_I8, - /* 3D */ OP_DATA_I16_I32, - /* 3E */ OP_PREFIX, - /* 3F */ OP_NONE, - - /* 40 */ OP_NONE, - /* 41 */ OP_NONE, - /* 42 */ OP_NONE, - /* 43 */ OP_NONE, - /* 44 */ OP_NONE, - /* 45 */ OP_NONE, - /* 46 */ OP_NONE, - /* 47 */ OP_NONE, - /* 48 */ OP_NONE, - /* 49 */ OP_NONE, - /* 4A */ OP_NONE, - /* 4B */ OP_NONE, - /* 4C */ OP_NONE, - /* 4D */ OP_NONE, - /* 4E */ OP_NONE, - /* 4F */ OP_NONE, - - /* 50 */ OP_NONE, - /* 51 */ OP_NONE, - /* 52 */ OP_NONE, - /* 53 */ OP_NONE, - /* 54 */ OP_NONE, - /* 55 */ OP_NONE, - /* 56 */ OP_NONE, - /* 57 */ OP_NONE, - /* 58 */ OP_NONE, - /* 59 */ OP_NONE, - /* 5A */ OP_NONE, - /* 5B */ OP_NONE, - /* 5C */ OP_NONE, - /* 5D */ OP_NONE, - /* 5E */ OP_NONE, - /* 5F */ OP_NONE, - /* 60 */ OP_NONE, - - /* 61 */ OP_NONE, - /* 62 */ OP_MODRM, - /* 63 */ OP_MODRM, - /* 64 */ OP_PREFIX, - /* 65 */ OP_PREFIX, - /* 66 */ OP_PREFIX, - /* 67 */ OP_PREFIX, - /* 68 */ OP_DATA_I16_I32, - /* 69 */ OP_MODRM | OP_DATA_I16_I32, - /* 6A */ OP_DATA_I8, - /* 6B */ OP_MODRM | OP_DATA_I8, - /* 6C */ OP_NONE, - /* 6D */ OP_NONE, - /* 6E */ OP_NONE, - /* 6F */ OP_NONE, - - /* 70 */ OP_RELATIVE | OP_DATA_I8, - /* 71 */ OP_RELATIVE | OP_DATA_I8, - /* 72 */ OP_RELATIVE | OP_DATA_I8, - /* 73 */ OP_RELATIVE | OP_DATA_I8, - /* 74 */ OP_RELATIVE | OP_DATA_I8, - /* 75 */ OP_RELATIVE | OP_DATA_I8, - /* 76 */ OP_RELATIVE | OP_DATA_I8, - /* 77 */ OP_RELATIVE | OP_DATA_I8, - /* 78 */ OP_RELATIVE | OP_DATA_I8, - /* 79 */ OP_RELATIVE | OP_DATA_I8, - /* 7A */ OP_RELATIVE | OP_DATA_I8, - /* 7B */ OP_RELATIVE | OP_DATA_I8, - /* 7C */ OP_RELATIVE | OP_DATA_I8, - /* 7D */ OP_RELATIVE | OP_DATA_I8, - /* 7E */ OP_RELATIVE | OP_DATA_I8, - /* 7F */ OP_RELATIVE | OP_DATA_I8, - - /* 80 */ OP_MODRM | OP_DATA_I8, - /* 81 */ OP_MODRM | OP_DATA_I16_I32, - /* 82 */ OP_MODRM | OP_DATA_I8, - /* 83 */ OP_MODRM | OP_DATA_I8, - /* 84 */ OP_MODRM, - /* 85 */ OP_MODRM, - /* 86 */ OP_MODRM, - /* 87 */ OP_MODRM, - /* 88 */ OP_MODRM, - /* 89 */ OP_MODRM, - /* 8A */ OP_MODRM, - /* 8B */ OP_MODRM, - /* 8C */ OP_MODRM, - /* 8D */ OP_MODRM, - /* 8E */ OP_MODRM, - /* 8F */ OP_MODRM, - - /* 90 */ OP_NONE, - /* 91 */ OP_NONE, - /* 92 */ OP_NONE, - /* 93 */ OP_NONE, - /* 94 */ OP_NONE, - /* 95 */ OP_NONE, - /* 96 */ OP_NONE, - /* 97 */ OP_NONE, - /* 98 */ OP_NONE, - /* 99 */ OP_NONE, - /* 9A */ OP_DATA_I16 | OP_DATA_I16_I32, - /* 9B */ OP_NONE, - /* 9C */ OP_NONE, - /* 9D */ OP_NONE, - /* 9E */ OP_NONE, - /* 9F */ OP_NONE, - - /* A0 */ OP_DATA_I8, - /* A1 */ OP_DATA_I16_I32_I64, - /* A2 */ OP_DATA_I8, - /* A3 */ OP_DATA_I16_I32_I64, - /* A4 */ OP_NONE, - /* A5 */ OP_NONE, - /* A6 */ OP_NONE, - /* A7 */ OP_NONE, - /* A8 */ OP_DATA_I8, - /* A9 */ OP_DATA_I16_I32, - /* AA */ OP_NONE, - /* AB */ OP_NONE, - /* AC */ OP_NONE, - /* AD */ OP_NONE, - /* AE */ OP_NONE, - /* AF */ OP_NONE, - - /* B0 */ OP_DATA_I8, - /* B1 */ OP_DATA_I8, - /* B2 */ OP_DATA_I8, - /* B3 */ OP_DATA_I8, - /* B4 */ OP_DATA_I8, - /* B5 */ OP_DATA_I8, - /* B6 */ OP_DATA_I8, - /* B7 */ OP_DATA_I8, - /* B8 */ OP_DATA_I16_I32_I64, - /* B9 */ OP_DATA_I16_I32_I64, - /* BA */ OP_DATA_I16_I32_I64, - /* BB */ OP_DATA_I16_I32_I64, - /* BC */ OP_DATA_I16_I32_I64, - /* BD */ OP_DATA_I16_I32_I64, - /* BE */ OP_DATA_I16_I32_I64, - /* BF */ OP_DATA_I16_I32_I64, - - /* C0 */ OP_MODRM | OP_DATA_I8, - /* C1 */ OP_MODRM | OP_DATA_I8, - /* C2 */ OP_DATA_I16, - /* C3 */ OP_NONE, - /* C4 */ OP_MODRM, - /* C5 */ OP_MODRM, - /* C6 */ OP_MODRM | OP_DATA_I8, - /* C7 */ OP_MODRM | OP_DATA_I16_I32, - /* C8 */ OP_DATA_I8 | OP_DATA_I16, - /* C9 */ OP_NONE, - /* CA */ OP_DATA_I16, - /* CB */ OP_NONE, - /* CC */ OP_NONE, - /* CD */ OP_DATA_I8, - /* CE */ OP_NONE, - /* CF */ OP_NONE, - - /* D0 */ OP_MODRM, - /* D1 */ OP_MODRM, - /* D2 */ OP_MODRM, - /* D3 */ OP_MODRM, - /* D4 */ OP_DATA_I8, - /* D5 */ OP_DATA_I8, - /* D6 */ OP_NONE, - /* D7 */ OP_NONE, - /* D8 */ OP_MODRM, - /* D9 */ OP_MODRM, - /* DA */ OP_MODRM, - /* DB */ OP_MODRM, - /* DC */ OP_MODRM, - /* DD */ OP_MODRM, - /* DE */ OP_MODRM, - /* DF */ OP_MODRM, - - /* E0 */ OP_RELATIVE | OP_DATA_I8, - /* E1 */ OP_RELATIVE | OP_DATA_I8, - /* E2 */ OP_RELATIVE | OP_DATA_I8, - /* E3 */ OP_RELATIVE | OP_DATA_I8, - /* E4 */ OP_DATA_I8, - /* E5 */ OP_DATA_I8, - /* E6 */ OP_DATA_I8, - /* E7 */ OP_DATA_I8, - /* E8 */ OP_RELATIVE | OP_DATA_I16_I32, - /* E9 */ OP_RELATIVE | OP_DATA_I16_I32, - /* EA */ OP_DATA_I16 | OP_DATA_I16_I32, - /* EB */ OP_RELATIVE | OP_DATA_I8, - /* EC */ OP_NONE, - /* ED */ OP_NONE, - /* EE */ OP_NONE, - /* EF */ OP_NONE, - - /* F0 */ OP_PREFIX, - /* F1 */ OP_NONE, - /* F2 */ OP_PREFIX, - /* F3 */ OP_PREFIX, - /* F4 */ OP_NONE, - /* F5 */ OP_NONE, - /* F6 */ OP_MODRM, - /* F7 */ OP_MODRM, - /* F8 */ OP_NONE, - /* F9 */ OP_NONE, - /* FA */ OP_NONE, - /* FB */ OP_NONE, - /* FC */ OP_NONE, - /* FD */ OP_NONE, - /* FE */ OP_MODRM, - /* FF */ OP_MODRM - }; - - static byte[] flags_table_ex = - { - /* 0F00 */ OP_MODRM, - /* 0F01 */ OP_MODRM, - /* 0F02 */ OP_MODRM, - /* 0F03 */ OP_MODRM, - /* 0F04 */ OP_INVALID, - /* 0F05 */ OP_NONE, - /* 0F06 */ OP_NONE, - /* 0F07 */ OP_NONE, - /* 0F08 */ OP_NONE, - /* 0F09 */ OP_NONE, - /* 0F0A */ OP_INVALID, - /* 0F0B */ OP_NONE, - /* 0F0C */ OP_INVALID, - /* 0F0D */ OP_MODRM, - /* 0F0E */ OP_INVALID, - /* 0F0F */ OP_MODRM | OP_DATA_I8, //3Dnow - - /* 0F10 */ OP_MODRM, - /* 0F11 */ OP_MODRM, - /* 0F12 */ OP_MODRM, - /* 0F13 */ OP_MODRM, - /* 0F14 */ OP_MODRM, - /* 0F15 */ OP_MODRM, - /* 0F16 */ OP_MODRM, - /* 0F17 */ OP_MODRM, - /* 0F18 */ OP_MODRM, - /* 0F19 */ OP_INVALID, - /* 0F1A */ OP_INVALID, - /* 0F1B */ OP_INVALID, - /* 0F1C */ OP_INVALID, - /* 0F1D */ OP_INVALID, - /* 0F1E */ OP_INVALID, - /* 0F1F */ OP_NONE, - - /* 0F20 */ OP_MODRM, - /* 0F21 */ OP_MODRM, - /* 0F22 */ OP_MODRM, - /* 0F23 */ OP_MODRM, - /* 0F24 */ OP_MODRM | OP_EXTENDED, //SSE5 - /* 0F25 */ OP_INVALID, - /* 0F26 */ OP_MODRM, - /* 0F27 */ OP_INVALID, - /* 0F28 */ OP_MODRM, - /* 0F29 */ OP_MODRM, - /* 0F2A */ OP_MODRM, - /* 0F2B */ OP_MODRM, - /* 0F2C */ OP_MODRM, - /* 0F2D */ OP_MODRM, - /* 0F2E */ OP_MODRM, - /* 0F2F */ OP_MODRM, - - /* 0F30 */ OP_NONE, - /* 0F31 */ OP_NONE, - /* 0F32 */ OP_NONE, - /* 0F33 */ OP_NONE, - /* 0F34 */ OP_NONE, - /* 0F35 */ OP_NONE, - /* 0F36 */ OP_INVALID, - /* 0F37 */ OP_NONE, - /* 0F38 */ OP_MODRM | OP_EXTENDED, - /* 0F39 */ OP_INVALID, - /* 0F3A */ OP_MODRM | OP_EXTENDED | OP_DATA_I8, - /* 0F3B */ OP_INVALID, - /* 0F3C */ OP_INVALID, - /* 0F3D */ OP_INVALID, - /* 0F3E */ OP_INVALID, - /* 0F3F */ OP_INVALID, - - /* 0F40 */ OP_MODRM, - /* 0F41 */ OP_MODRM, - /* 0F42 */ OP_MODRM, - /* 0F43 */ OP_MODRM, - /* 0F44 */ OP_MODRM, - /* 0F45 */ OP_MODRM, - /* 0F46 */ OP_MODRM, - /* 0F47 */ OP_MODRM, - /* 0F48 */ OP_MODRM, - /* 0F49 */ OP_MODRM, - /* 0F4A */ OP_MODRM, - /* 0F4B */ OP_MODRM, - /* 0F4C */ OP_MODRM, - /* 0F4D */ OP_MODRM, - /* 0F4E */ OP_MODRM, - /* 0F4F */ OP_MODRM, - - /* 0F50 */ OP_MODRM, - /* 0F51 */ OP_MODRM, - /* 0F52 */ OP_MODRM, - /* 0F53 */ OP_MODRM, - /* 0F54 */ OP_MODRM, - /* 0F55 */ OP_MODRM, - /* 0F56 */ OP_MODRM, - /* 0F57 */ OP_MODRM, - /* 0F58 */ OP_MODRM, - /* 0F59 */ OP_MODRM, - /* 0F5A */ OP_MODRM, - /* 0F5B */ OP_MODRM, - /* 0F5C */ OP_MODRM, - /* 0F5D */ OP_MODRM, - /* 0F5E */ OP_MODRM, - /* 0F5F */ OP_MODRM, - - /* 0F60 */ OP_MODRM, - /* 0F61 */ OP_MODRM, - /* 0F62 */ OP_MODRM, - /* 0F63 */ OP_MODRM, - /* 0F64 */ OP_MODRM, - /* 0F65 */ OP_MODRM, - /* 0F66 */ OP_MODRM, - /* 0F67 */ OP_MODRM, - /* 0F68 */ OP_MODRM, - /* 0F69 */ OP_MODRM, - /* 0F6A */ OP_MODRM, - /* 0F6B */ OP_MODRM, - /* 0F6C */ OP_MODRM, - /* 0F6D */ OP_MODRM, - /* 0F6E */ OP_MODRM, - /* 0F6F */ OP_MODRM, - - /* 0F70 */ OP_MODRM | OP_DATA_I8, - /* 0F71 */ OP_MODRM | OP_DATA_I8, - /* 0F72 */ OP_MODRM | OP_DATA_I8, - /* 0F73 */ OP_MODRM | OP_DATA_I8, - /* 0F74 */ OP_MODRM, - /* 0F75 */ OP_MODRM, - /* 0F76 */ OP_MODRM, - /* 0F77 */ OP_NONE, - /* 0F78 */ OP_MODRM, - /* 0F79 */ OP_MODRM, - /* 0F7A */ OP_INVALID, - /* 0F7B */ OP_INVALID, - /* 0F7C */ OP_MODRM, - /* 0F7D */ OP_MODRM, - /* 0F7E */ OP_MODRM, - /* 0F7F */ OP_MODRM, - - /* 0F80 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F81 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F82 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F83 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F84 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F85 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F86 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F87 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F88 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F89 */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F8A */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F8B */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F8C */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F8D */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F8E */ OP_RELATIVE | OP_DATA_I16_I32, - /* 0F8F */ OP_RELATIVE | OP_DATA_I16_I32, - - /* 0F90 */ OP_MODRM, - /* 0F91 */ OP_MODRM, - /* 0F92 */ OP_MODRM, - /* 0F93 */ OP_MODRM, - /* 0F94 */ OP_MODRM, - /* 0F95 */ OP_MODRM, - /* 0F96 */ OP_MODRM, - /* 0F97 */ OP_MODRM, - /* 0F98 */ OP_MODRM, - /* 0F99 */ OP_MODRM, - /* 0F9A */ OP_MODRM, - /* 0F9B */ OP_MODRM, - /* 0F9C */ OP_MODRM, - /* 0F9D */ OP_MODRM, - /* 0F9E */ OP_MODRM, - /* 0F9F */ OP_MODRM, - - /* 0FA0 */ OP_NONE, - /* 0FA1 */ OP_NONE, - /* 0FA2 */ OP_NONE, - /* 0FA3 */ OP_MODRM, - /* 0FA4 */ OP_MODRM | OP_DATA_I8, - /* 0FA5 */ OP_MODRM, - /* 0FA6 */ OP_INVALID, - /* 0FA7 */ OP_INVALID, - /* 0FA8 */ OP_NONE, - /* 0FA9 */ OP_NONE, - /* 0FAA */ OP_NONE, - /* 0FAB */ OP_MODRM, - /* 0FAC */ OP_MODRM | OP_DATA_I8, - /* 0FAD */ OP_MODRM, - /* 0FAE */ OP_MODRM, - /* 0FAF */ OP_MODRM, - - /* 0FB0 */ OP_MODRM, - /* 0FB1 */ OP_MODRM, - /* 0FB2 */ OP_MODRM, - /* 0FB3 */ OP_MODRM, - /* 0FB4 */ OP_MODRM, - /* 0FB5 */ OP_MODRM, - /* 0FB6 */ OP_MODRM, - /* 0FB7 */ OP_MODRM, - /* 0FB8 */ OP_MODRM, - /* 0FB9 */ OP_MODRM, - /* 0FBA */ OP_MODRM | OP_DATA_I8, - /* 0FBB */ OP_MODRM, - /* 0FBC */ OP_MODRM, - /* 0FBD */ OP_MODRM, - /* 0FBE */ OP_MODRM, - /* 0FBF */ OP_MODRM, - - /* 0FC0 */ OP_MODRM, - /* 0FC1 */ OP_MODRM, - /* 0FC2 */ OP_MODRM | OP_DATA_I8, - /* 0FC3 */ OP_MODRM, - /* 0FC4 */ OP_MODRM | OP_DATA_I8, - /* 0FC5 */ OP_MODRM | OP_DATA_I8, - /* 0FC6 */ OP_MODRM | OP_DATA_I8, - /* 0FC7 */ OP_MODRM, - /* 0FC8 */ OP_NONE, - /* 0FC9 */ OP_NONE, - /* 0FCA */ OP_NONE, - /* 0FCB */ OP_NONE, - /* 0FCC */ OP_NONE, - /* 0FCD */ OP_NONE, - /* 0FCE */ OP_NONE, - /* 0FCF */ OP_NONE, - - /* 0FD0 */ OP_MODRM, - /* 0FD1 */ OP_MODRM, - /* 0FD2 */ OP_MODRM, - /* 0FD3 */ OP_MODRM, - /* 0FD4 */ OP_MODRM, - /* 0FD5 */ OP_MODRM, - /* 0FD6 */ OP_MODRM, - /* 0FD7 */ OP_MODRM, - /* 0FD8 */ OP_MODRM, - /* 0FD9 */ OP_MODRM, - /* 0FDA */ OP_MODRM, - /* 0FDB */ OP_MODRM, - /* 0FDC */ OP_MODRM, - /* 0FDD */ OP_MODRM, - /* 0FDE */ OP_MODRM, - /* 0FDF */ OP_MODRM, - - /* 0FE0 */ OP_MODRM, - /* 0FE1 */ OP_MODRM, - /* 0FE2 */ OP_MODRM, - /* 0FE3 */ OP_MODRM, - /* 0FE4 */ OP_MODRM, - /* 0FE5 */ OP_MODRM, - /* 0FE6 */ OP_MODRM, - /* 0FE7 */ OP_MODRM, - /* 0FE8 */ OP_MODRM, - /* 0FE9 */ OP_MODRM, - /* 0FEA */ OP_MODRM, - /* 0FEB */ OP_MODRM, - /* 0FEC */ OP_MODRM, - /* 0FED */ OP_MODRM, - /* 0FEE */ OP_MODRM, - /* 0FEF */ OP_MODRM, - - /* 0FF0 */ OP_MODRM, - /* 0FF1 */ OP_MODRM, - /* 0FF2 */ OP_MODRM, - /* 0FF3 */ OP_MODRM, - /* 0FF4 */ OP_MODRM, - /* 0FF5 */ OP_MODRM, - /* 0FF6 */ OP_MODRM, - /* 0FF7 */ OP_MODRM, - /* 0FF8 */ OP_MODRM, - /* 0FF9 */ OP_MODRM, - /* 0FFA */ OP_MODRM, - /* 0FFB */ OP_MODRM, - /* 0FFC */ OP_MODRM, - /* 0FFD */ OP_MODRM, - /* 0FFE */ OP_MODRM, - /* 0FFF */ OP_INVALID, - }; - - static byte cflags(byte op) - { - return flags_table[op]; - } - - static byte cflags_ex(byte op) - { - return flags_table_ex[op]; - } - - /// - /// 计算大于等于 size 字节的最少指令的长度 - /// - /// - /// - public static uint SizeofMinNumByte(void* code, int size) - { - if (IsAndroidARM()) - { - if (IsIL2CPP()) - return CalcARMThumbMinLen(code, size); - else - return (uint)((size + 3) / 4) * 4; // 此为 jit 模式下的长度 - } - - UInt32 Length; - byte* pOpcode; - UInt32 Result = 0; - ldasm_data data = new ldasm_data(); - bool is64 = IntPtr.Size == 8; - do - { - Length = ldasm(code, data, is64); - - pOpcode = (byte*)code + data.opcd_offset; - Result += Length; - if (Result >= size) - break; - if ((Length == 1) && (*pOpcode == 0xCC)) - break; - - code = (void*)((ulong)code + Length); - - } while (Length>0); - - return Result; - } - - public static bool IsAndroidARM() - { - return UnityEngine.SystemInfo.operatingSystem.Contains("Android") - && UnityEngine.SystemInfo.processorType.Contains("ARM"); - } - - public static bool IsiOS() - { - return UnityEngine.SystemInfo.operatingSystem.ToLower().Contains("ios"); - } - - public static bool IsIL2CPP() - { - bool isIL2CPP = false; - try - { - byte[] ilBody = typeof(LDasm).GetMethod("IsIL2CPP").GetMethodBody().GetILAsByteArray(); - if (ilBody == null || ilBody.Length == 0) - isIL2CPP = true; - } - catch - { - isIL2CPP = true; - } - return isIL2CPP; - } - - /// - /// 计算 thumb 指令长度 - /// - /// - /// - /// - public static uint CalcARMThumbMinLen(void* code, int size) - { - uint len = 0; - - ushort* ins = (ushort*)code; - while (true) - { - if (len >= size) - return len; - - if (((*ins >> 13) & 3) == 3) - { - ins += 2; - len += 4; - } - else - { - ins++; - len += 2; - } - } - } - - static uint ldasm(void* code, ldasm_data ld, bool is64) - { - byte* p = (byte*)code; - byte s, op, f; - byte rexw, pr_66, pr_67; - - s = rexw = pr_66 = pr_67 = 0; - - /* dummy check */ - if ((int)code==0) - return 0; - - /* init output data */ - //memset(ld, 0, sizeof(ldasm_data)); - - /* phase 1: parse prefixies */ - while ((cflags(*p) & OP_PREFIX)!=0) - { - if (*p == 0x66) - pr_66 = 1; - if (*p == 0x67) - pr_67 = 1; - p++; s++; - ld.flags |= F_PREFIX; - if (s == 15) - { - ld.flags |= F_INVALID; - return s; - } - } - - /* parse REX prefix */ - if (is64 && *p >> 4 == 4) - { - ld.rex = *p; - rexw = (byte)((ld.rex >> 3) & 1); - ld.flags |= F_REX; - p++; s++; - } - - /* can be only one REX prefix */ - if (is64 && *p >> 4 == 4) - { - ld.flags |= F_INVALID; - s++; - return s; - } - - /* phase 2: parse opcode */ - ld.opcd_offset = (byte)(p - (byte*)code); - ld.opcd_size = 1; - op = *p++; s++; - - /* is 2 byte opcode? */ - if (op == 0x0F) - { - op = *p++; s++; - ld.opcd_size++; - f = cflags_ex(op); - if ((f & OP_INVALID)!=0) - { - ld.flags |= F_INVALID; - return s; - } - /* for SSE instructions */ - if ((f & OP_EXTENDED)!=0) - { - op = *p++; s++; - ld.opcd_size++; - } - } - else { - f = cflags(op); - /* pr_66 = pr_67 for opcodes A0-A3 */ - if (op >= 0xA0 && op <= 0xA3) - pr_66 = pr_67; - } - - /* phase 3: parse ModR/M, SIB and DISP */ - if ((f & OP_MODRM)!=0) - { - byte mod = (byte)(*p >> 6); - byte ro = (byte)((*p & 0x38) >> 3); - byte rm = (byte)(*p & 7); - - ld.modrm = *p++; s++; - ld.flags |= F_MODRM; - - /* in F6,F7 opcodes immediate data present if R/O == 0 */ - if (op == 0xF6 && (ro == 0 || ro == 1)) - f |= OP_DATA_I8; - if (op == 0xF7 && (ro == 0 || ro == 1)) - f |= OP_DATA_I16_I32_I64; - - /* is SIB byte exist? */ - if (mod != 3 && rm == 4 && !(!is64 && pr_67!=0)) - { - ld.sib = *p++; s++; - ld.flags |= F_SIB; - - /* if base == 5 and mod == 0 */ - if ((ld.sib & 7) == 5 && mod == 0) - { - ld.disp_size = 4; - } - } - - switch (mod) - { - case 0: - if (is64) - { - if (rm == 5) - { - ld.disp_size = 4; - if (is64) - ld.flags |= F_RELATIVE; - } - } - else if (pr_67!=0) - { - if (rm == 6) - ld.disp_size = 2; - } - else { - if (rm == 5) - ld.disp_size = 4; - } - break; - case 1: - ld.disp_size = 1; - break; - case 2: - if (is64) - ld.disp_size = 4; - else if (pr_67!=0) - ld.disp_size = 2; - else - ld.disp_size = 4; - break; - } - - if (ld.disp_size>0) - { - ld.disp_offset = (byte)(p - (byte*)code); - p += ld.disp_size; - s += ld.disp_size; - ld.flags |= F_DISP; - } - } - - /* phase 4: parse immediate data */ - if (rexw!=0 && (f & OP_DATA_I16_I32_I64)!=0) - ld.imm_size = 8; - else if ((f & OP_DATA_I16_I32)!=0 || (f & OP_DATA_I16_I32_I64)!=0) - ld.imm_size = (byte)(4 - (pr_66 << 1)); - - /* if exist, add OP_DATA_I16 and OP_DATA_I8 size */ - ld.imm_size += (byte)(f & 3); - - if ((ld.imm_size)!=0) - { - s += ld.imm_size; - ld.imm_offset = (byte)(p - (byte*)code); - ld.flags |= F_IMM; - if ((f & OP_RELATIVE)!=0) - ld.flags |= F_RELATIVE; - } - - /* instruction is too long */ - if (s > 15) - ld.flags |= F_INVALID; - - return s; - } - } -} -#endif diff --git a/Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs.meta b/Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs.meta deleted file mode 100644 index 969a5a17..00000000 --- a/Assets/Slua/LuaProfiler/Common/UnityHook/LDasm.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3c561c9729c367e4fbef63f4ec56f268 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs b/Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs deleted file mode 100644 index 523bc712..00000000 --- a/Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs +++ /dev/null @@ -1,268 +0,0 @@ -/* - Desc: 一个可以运行时 Hook Mono 方法的工具,让你可以无需修改 UnityEditor.dll 等文件就可以重写其函数功能 - Author: Misaka Mikoto - Github: https://github.com/easy66/MonoHooker - */ -#if UNITY_EDITOR -using DotNetDetour; -using System; -using System.Reflection; - - -/* ->>>>>>> 原始 UnityEditor.LogEntries.Clear 一型(.net 4.x) -0000000000403A00 < | 55 | push rbp | -0000000000403A01 | 48 8B EC | mov rbp,rsp | -0000000000403A04 | 48 81 EC 80 00 00 00 | sub rsp,80 | -0000000000403A0B | 48 89 65 B0 | mov qword ptr ss:[rbp-50],rsp | -0000000000403A0F | 48 89 6D A8 | mov qword ptr ss:[rbp-58],rbp | -0000000000403A13 | 48 89 5D C8 | mov qword ptr ss:[rbp-38],rbx | << -0000000000403A17 | 48 89 75 D0 | mov qword ptr ss:[rbp-30],rsi | -0000000000403A1B | 48 89 7D D8 | mov qword ptr ss:[rbp-28],rdi | -0000000000403A1F | 4C 89 65 E0 | mov qword ptr ss:[rbp-20],r12 | -0000000000403A23 | 4C 89 6D E8 | mov qword ptr ss:[rbp-18],r13 | -0000000000403A27 | 4C 89 75 F0 | mov qword ptr ss:[rbp-10],r14 | -0000000000403A2B | 4C 89 7D F8 | mov qword ptr ss:[rbp-8],r15 | -0000000000403A2F | 49 BB 00 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D00 | -0000000000403A39 | 4C 89 5D B8 | mov qword ptr ss:[rbp-48],r11 | -0000000000403A3D | 49 BB 08 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D08 | - - ->>>>>>> 二型(.net 2.x) -0000000000403E8F | 55 | push rbp | -0000000000403E90 | 48 8B EC | mov rbp,rsp | -0000000000403E93 | 48 83 EC 70 | sub rsp,70 | -0000000000403E97 | 48 89 65 C8 | mov qword ptr ss:[rbp-38],rsp | -0000000000403E9B | 48 89 5D B8 | mov qword ptr ss:[rbp-48],rbx | -0000000000403E9F | 48 89 6D C0 | mov qword ptr ss:[rbp-40],rbp | <<(16) -0000000000403EA3 | 48 89 75 F8 | mov qword ptr ss:[rbp-8],rsi | -0000000000403EA7 | 48 89 7D F0 | mov qword ptr ss:[rbp-10],rdi | -0000000000403EAB | 4C 89 65 D0 | mov qword ptr ss:[rbp-30],r12 | -0000000000403EAF | 4C 89 6D D8 | mov qword ptr ss:[rbp-28],r13 | -0000000000403EB3 | 4C 89 75 E0 | mov qword ptr ss:[rbp-20],r14 | -0000000000403EB7 | 4C 89 7D E8 | mov qword ptr ss:[rbp-18],r15 | -0000000000403EBB | 48 83 EC 20 | sub rsp,20 | -0000000000403EBF | 49 BB 18 3F 15 13 FE 7F 00 00 | mov r11,7FFE13153F18 | -0000000000403EC9 | 41 FF D3 | call r11 | -0000000000403ECC | 48 83 C4 20 | add rsp,20 | - - */ - - -/// -/// Hook 类,用来 Hook 某个 C# 方法 -/// -public unsafe class MethodHooker -{ - public bool isHooked { get; private set; } - - private MethodBase _targetMethod; // 需要被hook的目标方法 - private MethodBase _replacementMethod; // 被hook后的替代方法 - private MethodBase _proxyMethod; // 目标方法的代理方法(可以通过此方法调用被hook后的原方法) - - private IntPtr _targetPtr; // 目标方法被 jit 后的地址指针 - private IntPtr _replacementPtr; - private IntPtr _proxyPtr; - - private static readonly byte[] s_jmpBuff; - private static readonly byte[] s_jmpBuff_32 = new byte[] // 6 bytes - { - 0x68, 0x00, 0x00, 0x00, 0x00, // push $val - 0xC3 // ret - }; - private static readonly byte[] s_jmpBuff_64 = new byte[] // 14 bytes - { - 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp [rip] - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // $val - }; - private static readonly byte[] s_jmpBuff_arm32_arm = new byte[] // 8 bytes - { - 0x04, 0xF0, 0x1F, 0xE5, // LDR PC, [PC, #-4] - 0x00, 0x00, 0x00, 0x00, // $val - }; - private static readonly byte[] s_jmpBuff_arm32_thumb = new byte[] // 38 bytes - { - 0x00, 0xB5, // PUSH {LR} - 0x10, 0xB4, // PUSH {R0} - 0x03, 0xB4, // PUSH {R0, R1} - 0x78, 0x46, // MOV R0, PC - 0x16, 0x30, // ADD R0, #0x16 - 0x00, 0x68, // LDR R0, [R0, #0x00] - 0x69, 0x46, // MOV R1, SP - 0x08, 0x31, // ADD R1, #0x08 - 0x08, 0x60, // STR R0, [R1, #0x00] - 0x79, 0x46, // MOV R1, PC - 0x0E, 0x31, // ADD R1, #0x0E - 0x8E, 0x46, // MOV LR, R1 - 0x01, 0xBC, // POP {R0} - 0x02, 0xBC, // POP {R1} - 0x00, 0xBD, // POP {PC} - 0xC0, 0x46, // NOP - - 0x00, 0x00, 0x00, 0x00, // $val - 0x00, 0xBD, // POP {PC} - }; - private static readonly byte[] s_jmpBuff_arm64 = new byte[] - { - 0x04, 0xF0, 0x1F, 0xE5, // LDR PC, [PC, #-4] - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // $val - }; - private static readonly int s_addrOffset; - - - private byte[] _jmpBuff; - private byte[] _proxyBuff; - - static MethodHooker() - { - if (LDasm.IsAndroidARM()) - { - s_addrOffset = 4; - if (IntPtr.Size == 4) - { - if (!LDasm.IsIL2CPP()) - s_jmpBuff = s_jmpBuff_arm32_arm; - else - { - s_jmpBuff = s_jmpBuff_arm32_thumb; - s_addrOffset = 32; - } - - } - else - s_jmpBuff = s_jmpBuff_arm64; - } - else - { - if (IntPtr.Size == 4) - { - s_jmpBuff = s_jmpBuff_32; - s_addrOffset = 1; - } - else - { - s_jmpBuff = s_jmpBuff_64; - s_addrOffset = 6; - } - } - - } - - /// - /// 创建一个 Hooker - /// - /// 需要替换的目标方法 - /// 准备好的替换方法 - /// 如果还需要调用原始目标方法,可以通过此参数的方法调用,如果不需要可以填 null - public MethodHooker(MethodBase targetMethod, MethodBase replacementMethod, MethodBase proxyMethod = null) - { - _targetMethod = targetMethod; - _replacementMethod = replacementMethod; - _proxyMethod = proxyMethod; - - _targetPtr = _targetMethod.MethodHandle.GetFunctionPointer(); - _replacementPtr = _replacementMethod.MethodHandle.GetFunctionPointer(); - if(proxyMethod != null) - _proxyPtr = _proxyMethod.MethodHandle.GetFunctionPointer(); - - _jmpBuff = new byte[s_jmpBuff.Length]; - } - - public void Install() - { - if(LDasm.IsiOS()) // iOS 不支持修改 code 所在区域 page - return; - - if (isHooked) - return; - - HookerPool.AddHooker(_targetMethod, this); - - InitProxyBuff(); - BackupHeader(); - PatchTargetMethod(); - PatchProxyMethod(); - - isHooked = true; - } - - public void Uninstall() - { - if (!isHooked) - return; - - byte* pTarget = (byte*)_targetPtr.ToPointer(); - for (int i = 0; i < _proxyBuff.Length; i++) - *pTarget++ = _proxyBuff[i]; - - isHooked = false; - HookerPool.RemoveHooker(_targetMethod); - } - -#region private - /// - /// 根据具体指令填充 ProxyBuff - /// - /// - private void InitProxyBuff() - { - byte* pTarget = (byte*)_targetPtr.ToPointer(); - - uint requireSize = DotNetDetour.LDasm.SizeofMinNumByte(pTarget, s_jmpBuff.Length); - _proxyBuff = new byte[requireSize]; - } - - /// - /// 备份原始方法头 - /// - private void BackupHeader() - { - byte* pTarget = (byte*)_targetPtr.ToPointer(); - for (int i = 0; i < _proxyBuff.Length; i++) - _proxyBuff[i] = *pTarget++; - } - - // 将原始方法跳转到我们的方法 - private void PatchTargetMethod() - { - Array.Copy(s_jmpBuff, _jmpBuff, _jmpBuff.Length); - fixed (byte* p = &_jmpBuff[s_addrOffset]) - { - if(IntPtr.Size == 4) - *((uint*)p) = (uint)_replacementPtr.ToInt32(); - else - *((ulong*)p) = (ulong)_replacementPtr.ToInt64(); - } - - byte* pTarget = (byte*)_targetPtr.ToPointer(); - for (int i = 0; i < _jmpBuff.Length; i++) - *pTarget++ = _jmpBuff[i]; - } - - /// - /// 让 Proxy 方法的功能变成跳转向原始方法 - /// - private void PatchProxyMethod() - { - if (_proxyPtr == IntPtr.Zero) - return; - - byte* pProxy = (byte*)_proxyPtr.ToPointer(); - for (int i = 0; i < _proxyBuff.Length; i++) // 先填充头 - *pProxy++ = _proxyBuff[i]; - - fixed (byte* p = &_jmpBuff[s_addrOffset]) // 将跳转指向原函数跳过头的位置 - { - if (IntPtr.Size == 4) - * ((uint*)p) = (uint)_targetPtr.ToInt32() + (uint)_proxyBuff.Length; - else - *((ulong*)p) = (ulong)_targetPtr.ToInt64() + (ulong)_proxyBuff.Length; - } - - for (int i = 0; i < _jmpBuff.Length; i++) // 再填充跳转 - *pProxy++ = _jmpBuff[i]; - } - -#endregion -} -#endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs.meta b/Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs.meta deleted file mode 100644 index 007e62ca..00000000 --- a/Assets/Slua/LuaProfiler/Common/UnityHook/MethodHooker.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: bd0b8071cf434d6498160259e3829980 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From 844ce5892fa7bd3ef2fbc03d48a4dc6db6de5442 Mon Sep 17 00:00:00 2001 From: ElPsyCongree <345036769@qq.com> Date: Mon, 19 Nov 2018 12:25:26 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/LuaDeepProfilerSetting.cs | 2 +- .../LuaProfiler/HookSetup/SLuaHookSetup.cs | 23 +- .../LuaProfiler/HookSetup/ToLuaHookSetup.cs | 217 +++++++++++++----- .../LuaProfiler/HookSetup/XLuaHookSetup.cs | 21 +- 4 files changed, 206 insertions(+), 57 deletions(-) diff --git a/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs b/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs index d7286789..4a580e31 100644 --- a/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs +++ b/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs @@ -51,7 +51,7 @@ public bool isDeepProfiler } } - [MenuItem("Tools/LuaProfilerSetting", priority = 10)] + //[MenuItem("Tools/LuaProfilerSetting", priority = 10)] public static void EditSettings() { //string text = System.IO.File.ReadAllText("Lua/TemplateCommon.lua"); diff --git a/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs index d7543b8f..a7761215 100644 --- a/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs +++ b/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs @@ -1,6 +1,6 @@ /* * ============================================================================== -* Filename: SLuaHooSetup +* Filename: SLuaHookSetup * Created: 2018/7/2 11:36:16 * Author: エル・プサイ・コングルゥ * Purpose: @@ -92,6 +92,12 @@ public static void HookNewLuaEnv() clickProxy = envReplace.GetMethod("ProxyNewstate", BindingFlags.Public | BindingFlags.Static); hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); hookNewLuaEnv.Install(); + + newstateFun = typeDll.GetMethod("lua_close"); + clickReplace = envReplace.GetMethod("lua_close"); + clickProxy = envReplace.GetMethod("ProxyClose", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); } } @@ -106,6 +112,19 @@ public static void Proxy(LuaState env) { } + public static void lua_close(IntPtr luaState) + { + if (LuaProfiler.mainL == luaState) + { + LuaProfiler.mainL = IntPtr.Zero; + HookSetup.Uninstall(); + } + } + + public static void ProxyClose(IntPtr luaState) + { + } + public static IntPtr luaL_newstate() { IntPtr l = ProxyNewstate(); @@ -149,9 +168,9 @@ public static void SetMainLuaEnv(LuaState env) { if (LuaDeepProfilerSetting.Instance.isDeepProfiler) { - Lua_MikuLuaProfiler_LuaProfiler.reg(LuaProfiler.mainL); if (env != null) { + Lua_MikuLuaProfiler_LuaProfiler.reg(LuaProfiler.mainL); env.doString(@" BeginMikuSample = MikuLuaProfiler.LuaProfiler.BeginSample EndMikuSample = MikuLuaProfiler.LuaProfiler.EndSample diff --git a/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs index fcc8c1bb..bb4de23a 100644 --- a/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs +++ b/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs @@ -1,16 +1,16 @@ /* * ============================================================================== -* Filename: ToLuaHooSetup +* Filename: ToLuaHookSetup * Created: 2018/7/2 11:36:16 * Author: エル・プサイ・コングルゥ * Purpose: * ============================================================================== */ -//#define TOLUA -#if TOLUA -#if UNITY_EDITOR +//#define ToLua +#if ToLua +#if UNITY_EDITOR using System; using System.Collections.Generic; @@ -20,10 +20,49 @@ using UnityEditor; using UnityEditorInternal; using UnityEngine; -using XLua; -using LuaDLL = XLua.LuaDLL.Lua; +using LuaInterface; +using LuaDLL = LuaInterface.LuaDLL; + +namespace MikuLuaProfiler +{ + + public class LuaLib + { + public static void RunGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCCOLLECT, 0); + } + } + public static void StopGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCSTOP, 0); + } + } + public static void ResumeGC() + { + var env = LuaProfiler.mainL; + if (env != IntPtr.Zero) + { + LuaDLL.lua_gc(env, LuaGCOptions.LUA_GCRESTART, 0); + } + } + + public static long GetLuaMemory(IntPtr luaState) + { + long result = 0; -namespace MikuLuaProfiler { + result = LuaDLL.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNT, 0); + result = result * 1024 + LuaDLL.lua_gc(luaState, LuaGCOptions.LUA_GCCOUNTB, 0); + + return result; + } + } [InitializeOnLoad] public static class Startup @@ -41,30 +80,48 @@ public static void HookNewLuaEnv() if (hookNewLuaEnv == null) { Type envReplace = typeof(LuaEnvReplace); - Type typeEnv = typeof(XLua.LuaEnv); - var clickFun = typeEnv.GetConstructors()[0]; - MethodInfo clickReplace = envReplace.GetMethod("Ctor"); + Type typeEnv = typeof(LuaState); + var clickFun = typeEnv.GetMethod("Start"); + MethodInfo clickReplace = envReplace.GetMethod("Start"); MethodInfo clickProxy = envReplace.GetMethod("Proxy", BindingFlags.Public | BindingFlags.Static); hookNewLuaEnv = new MethodHooker(clickFun, clickReplace, clickProxy); hookNewLuaEnv.Install(); - Type typeDll = typeof(XLua.LuaDLL.Lua); + Type typeDll = typeof(LuaDLL); var newstateFun = typeDll.GetMethod("luaL_newstate"); clickReplace = envReplace.GetMethod("luaL_newstate"); clickProxy = envReplace.GetMethod("ProxyNewstate", BindingFlags.Public | BindingFlags.Static); hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); hookNewLuaEnv.Install(); + + newstateFun = typeDll.GetMethod("lua_close"); + clickReplace = envReplace.GetMethod("lua_close"); + clickProxy = envReplace.GetMethod("ProxyClose", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); } } public static class LuaEnvReplace { - public static void Ctor(LuaEnv env) + public static void Start(LuaState env) { Proxy(env); - MikuLuaProfiler.HookSetup.SetMainLuaEnv(env); + HookSetup.SetMainLuaEnv(env); } - public static void Proxy(LuaEnv env) + public static void Proxy(LuaState env) + { + } + + public static void lua_close(IntPtr luaState) + { + if (LuaProfiler.mainL == luaState) + { + LuaProfiler.mainL = IntPtr.Zero; + HookSetup.Uninstall(); + } + } + public static void ProxyClose(IntPtr luaState) { } @@ -107,15 +164,20 @@ static HookSetup() } - public static void SetMainLuaEnv(LuaEnv env) + public static void SetMainLuaEnv(LuaState env) { if (LuaDeepProfilerSetting.Instance.isDeepProfiler) { if (env != null) { + env.BeginModule(null); + env.BeginModule("MikuLuaProfiler"); + MikuLuaProfiler_LuaProfilerWrap.Register(env); + env.EndModule(); + env.EndModule(); env.DoString(@" -BeginMikuSample = CS.MikuLuaProfiler.LuaProfiler.BeginSample -EndMikuSample = CS.MikuLuaProfiler.LuaProfiler.EndSample +BeginMikuSample = MikuLuaProfiler.LuaProfiler.BeginSample +EndMikuSample = MikuLuaProfiler.LuaProfiler.EndSample function miku_unpack_return_value(...) EndMikuSample() @@ -167,7 +229,7 @@ public static void RefString(IntPtr strPoint, int index, string s, IntPtr L) } #endregion - public static int xluaL_loadbuffer(IntPtr L, byte[] buff, int size, string name) + public static int luaL_loadbuffer(IntPtr L, byte[] buff, int size, string name) { if (LuaDeepProfilerSetting.Instance.isDeepProfiler)//&& name != "chunk" { @@ -192,42 +254,20 @@ public static int ProxyLoadbuffer(IntPtr L, byte[] buff, int size, string name) public static string lua_tostring(IntPtr L, int index) { - IntPtr strlen; + int len = 0; + IntPtr str = LuaDLL.tolua_tolstring(L, index, out len); - IntPtr str = LuaDLL.lua_tolstring(L, index, out strlen); if (str != IntPtr.Zero) { -#if XLUA_GENERAL || (UNITY_WSA && !UNITY_EDITOR) - int len = strlen.ToInt32(); - byte[] buffer = new byte[len]; - Marshal.Copy(str, buffer, 0, len); - return Encoding.UTF8.GetString(buffer); -#else - string ret; - if (TryGetLuaString(str, out ret)) - { - return ret; - } - - ret = Marshal.PtrToStringAnsi(str, strlen.ToInt32()); - if (ret == null) - { - int len = strlen.ToInt32(); - byte[] buffer = new byte[len]; - Marshal.Copy(str, buffer, 0, len); - ret = Encoding.UTF8.GetString(buffer); - } - if (ret != null) + string s; + if (!TryGetLuaString(str, out s)) { - RefString(str, index, ret, L); + s = LuaDLL.lua_ptrtostring(str, len); } - return ret; -#endif - } - else - { - return null; + return s; } + + return null; } public static string PoxyToString(IntPtr L, int index) @@ -307,8 +347,8 @@ public static void HookLuaFuns() tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); tostringHook.Install(); - tostringFun = typeLog.GetMethod("xluaL_loadbuffer"); - tostringReplace = typeLogReplace.GetMethod("xluaL_loadbuffer"); + tostringFun = typeLog.GetMethod("luaL_loadbuffer"); + tostringReplace = typeLogReplace.GetMethod("luaL_loadbuffer"); tostringProxy = typeLogReplace.GetMethod("ProxyLoadbuffer"); tostringHook = new MethodHooker(tostringFun, tostringReplace, tostringProxy); @@ -377,15 +417,86 @@ public static void Uninstall() #endregion } - public class LuaLib + #region bind + public class MikuLuaProfiler_LuaProfilerWrap { - public static int lua_gc(IntPtr L, LuaGCOptions what, int data) + public static void Register(LuaState L) { - return LuaDLL.lua_gc(L, what, data); + L.BeginClass(typeof(MikuLuaProfiler.LuaProfiler), typeof(System.Object)); + L.RegFunction("BeginSample", BeginSample); + L.RegFunction("EndSample", EndSample); + L.EndClass(); } + [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + static int BeginSample(IntPtr L) + { + try + { + int count = LuaDLL.lua_gettop(L); + + if (count == 1 && TypeChecker.CheckTypes(L, 1)) + { + System.IntPtr arg0 = ToLua.CheckIntPtr(L, 1); + MikuLuaProfiler.LuaProfiler.BeginSample(arg0); + return 0; + } + else if (count == 1 && TypeChecker.CheckTypes(L, 1)) + { + string arg0 = ToLua.ToString(L, 1); + MikuLuaProfiler.LuaProfiler.BeginSample(arg0); + return 0; + } + else if (count == 2) + { + System.IntPtr arg0 = ToLua.CheckIntPtr(L, 1); + string arg1 = ToLua.CheckString(L, 2); + MikuLuaProfiler.LuaProfiler.BeginSample(arg0, arg1); + return 0; + } + else + { + return LuaDLL.luaL_throw(L, "invalid arguments to method: MikuLuaProfiler.LuaProfiler.BeginSample"); + } + } + catch (Exception e) + { + return LuaDLL.toluaL_exception(L, e); + } + } + + [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] + static int EndSample(IntPtr L) + { + try + { + int count = LuaDLL.lua_gettop(L); + + if (count == 0) + { + MikuLuaProfiler.LuaProfiler.EndSample(); + return 0; + } + else if (count == 1) + { + System.IntPtr arg0 = ToLua.CheckIntPtr(L, 1); + MikuLuaProfiler.LuaProfiler.EndSample(arg0); + return 0; + } + else + { + return LuaDLL.luaL_throw(L, "invalid arguments to method: MikuLuaProfiler.LuaProfiler.EndSample"); + } + } + catch (Exception e) + { + return LuaDLL.toluaL_exception(L, e); + } + } } + #endregion } #endif + #endif \ No newline at end of file diff --git a/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs index 7910417d..0a19cbc3 100644 --- a/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs +++ b/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs @@ -1,6 +1,6 @@ /* * ============================================================================== -* Filename: XLuaHooSetup +* Filename: XLuaHookSetup * Created: 2018/7/2 11:36:16 * Author: エル・プサイ・コングルゥ * Purpose: @@ -92,6 +92,12 @@ public static void HookNewLuaEnv() clickProxy = envReplace.GetMethod("ProxyNewstate", BindingFlags.Public | BindingFlags.Static); hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); hookNewLuaEnv.Install(); + + newstateFun = typeDll.GetMethod("lua_close"); + clickReplace = envReplace.GetMethod("lua_close"); + clickProxy = envReplace.GetMethod("ProxyClose", BindingFlags.Public | BindingFlags.Static); + hookNewLuaEnv = new MethodHooker(newstateFun, clickReplace, clickProxy); + hookNewLuaEnv.Install(); } } @@ -106,6 +112,19 @@ public static void Proxy(LuaState env) { } + public static void lua_close(IntPtr luaState) + { + if (LuaProfiler.mainL == luaState) + { + LuaProfiler.mainL = IntPtr.Zero; + HookSetup.Uninstall(); + } + } + + public static void ProxyClose(IntPtr luaState) + { + } + public static IntPtr luaL_newstate() { IntPtr l = ProxyNewstate(); From 37fbb3faea66bde9d6fa17a7aeb815e22076f642 Mon Sep 17 00:00:00 2001 From: ElPsyCongree <345036769@qq.com> Date: Mon, 19 Nov 2018 16:58:27 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=8E=89=E4=B8=80?= =?UTF-8?q?=E4=BA=9BBUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Editor/TreeView/LuaProfilerTreeView.cs | 2 +- .../Editor/TreeView/LuaProfilerWindow.cs | 2 +- .../Common/LuaDeepProfilerSetting.cs | 30 +- Assets/Slua/LuaProfiler/Common/Parse/LLex.cs | 862 +++++++++--------- Assets/Slua/LuaProfiler/Common/Parse/Parse.cs | 2 +- .../LuaProfiler/HookSetup/SLuaHookSetup.cs | 6 +- .../LuaProfiler/HookSetup/ToLuaHookSetup.cs | 4 +- .../LuaProfiler/HookSetup/XLuaHookSetup.cs | 4 +- 8 files changed, 476 insertions(+), 436 deletions(-) diff --git a/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs index fa084757..fdbc3b21 100644 --- a/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs +++ b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerTreeView.cs @@ -2,7 +2,7 @@ * ============================================================================== * Filename: LuaExport * Created: 2018/7/13 14:29:22 -* Author: エル・プサイ・コングルゥ +* Author: エル・プサイ・コングリィ * Purpose: * ============================================================================== */ diff --git a/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs index 92467b88..cf9c3db5 100644 --- a/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs +++ b/Assets/Slua/LuaProfiler/Common/Editor/TreeView/LuaProfilerWindow.cs @@ -2,7 +2,7 @@ * ============================================================================== * Filename: LuaExport * Created: 2018/7/13 14:29:22 -* Author: エル・プサイ・コングルゥ +* Author: エル・プサイ・コングリィ * Purpose: * ============================================================================== */ diff --git a/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs b/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs index 4a580e31..cebe4d5f 100644 --- a/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs +++ b/Assets/Slua/LuaProfiler/Common/LuaDeepProfilerSetting.cs @@ -2,7 +2,7 @@ * ============================================================================== * Filename: LuaDeepProfilerSetting * Created: 2018/7/13 14:29:22 -* Author: エル・プサイ・コングルゥ +* Author: エル・プサイ・コングリィ * Purpose: * ============================================================================== */ @@ -10,6 +10,7 @@ namespace MikuLuaProfiler { using System.Collections.Generic; + using System.IO; using UnityEditor; using UnityEngine; @@ -51,13 +52,32 @@ public bool isDeepProfiler } } - //[MenuItem("Tools/LuaProfilerSetting", priority = 10)] + [MenuItem("LuaProfiler/ExportFiles", priority = 10)] public static void EditSettings() { - //string text = System.IO.File.ReadAllText("Lua/TemplateCommon.lua"); - //text = MikuLuaProfiler.Parse.InsertSample(text, "Template"); - //System.IO.File.WriteAllText("TemplateCommon.lua", text); + string path = EditorUtility.OpenFolderPanel("请选择Lua脚本存放文件夹", "", "*"); + path = path.Replace("/", "\\"); + string rootProfilerDirPath = path + "Profiler"; + DirectoryInfo dir = new DirectoryInfo(path); + FileInfo[] files = dir.GetFiles("*.lua"); + int count = files.Length; + int process = 0; + foreach (FileInfo item in files) + { + process++; + EditorUtility.DisplayProgressBar("profiler lua", item.FullName, (float)process / count); + string allCode = File.ReadAllText(item.FullName); + allCode = MikuLuaProfiler.Parse.InsertSample(allCode, "Template"); + string profilerPath = item.FullName.Replace(path, rootProfilerDirPath); + string profilerDirPath = profilerPath.Replace(item.Name, ""); + if (!Directory.Exists(profilerDirPath)) + { + Directory.CreateDirectory(profilerDirPath); + } + File.WriteAllText(profilerPath, allCode); + } + EditorUtility.ClearProgressBar(); Selection.activeObject = Instance; #if UNITY_2018_1_OR_NEWER EditorApplication.ExecuteMenuItem("Window/General/Inspector"); diff --git a/Assets/Slua/LuaProfiler/Common/Parse/LLex.cs b/Assets/Slua/LuaProfiler/Common/Parse/LLex.cs index 622a7f51..a425ab56 100644 --- a/Assets/Slua/LuaProfiler/Common/Parse/LLex.cs +++ b/Assets/Slua/LuaProfiler/Common/Parse/LLex.cs @@ -1,51 +1,50 @@  using System; -using System.IO; -using System.Text; using System.Collections.Generic; - +using System.Text; using NumberStyles = System.Globalization.NumberStyles; namespace UniLua { public class LLexException : Exception { - public LLexException( string info ) : base( info ) { } + public LLexException(string info) : base(info) { } } public enum TK { - // reserved words + // reserved words AND = 257, - BREAK, - DO, - ELSE, - ELSEIF, - END, - FALSE, - FOR, - FUNCTION, - GOTO, - IF, - IN, - LOCAL, - NIL, - NOT, - OR, - REPEAT, - RETURN, - THEN, - TRUE, - UNTIL, - WHILE, - // other terminal symbols + BREAK, + CONTINUE, + DO, + ELSE, + ELSEIF, + END, + FALSE, + FOR, + FUNCTION, + GOTO, + IF, + IN, + LOCAL, + NIL, + NOT, + OR, + REPEAT, + RETURN, + THEN, + TRUE, + UNTIL, + WHILE, + // other terminal symbols CONCAT, DOTS, EQ, GE, LE, NE, - DBCOLON, + DBCOLON, NUMBER, STRING, NAME, @@ -155,17 +154,20 @@ public int PeekByte() public abstract class Token { - public abstract int TokenType{ get; } + public abstract int TokenType { get; } - public bool EqualsToToken( Token other ) { + public bool EqualsToToken(Token other) + { return TokenType == other.TokenType; } - public bool EqualsToToken( int other ) { + public bool EqualsToToken(int other) + { return TokenType == other; } - public bool EqualsToToken( TK other ) { + public bool EqualsToToken(TK other) + { return TokenType == (int)other; } } @@ -191,7 +193,7 @@ public class LiteralToken : Token { private int _Literal; - public LiteralToken( int literal ) + public LiteralToken(int literal) { _Literal = literal; } @@ -203,7 +205,7 @@ public override int TokenType public override string ToString() { - return string.Format( "LiteralToken: {0}", (char)_Literal ); + return string.Format("LiteralToken: {0}", (char)_Literal); } } @@ -211,7 +213,7 @@ public class TypedToken : Token { private TK _Type; - public TypedToken( TK type ) + public TypedToken(TK type) { _Type = type; } @@ -223,22 +225,22 @@ public override int TokenType public override string ToString() { - return string.Format( "TypedToken: {0}", _Type ); + return string.Format("TypedToken: {0}", _Type); } } public class StringToken : TypedToken { public string SemInfo; - - public StringToken( string seminfo ) : base( TK.STRING ) + + public StringToken(string seminfo) : base(TK.STRING) { SemInfo = seminfo; } public override string ToString() { - return string.Format( "StringToken: {0}", SemInfo ); + return string.Format("StringToken: {0}", SemInfo); } } @@ -246,14 +248,14 @@ public class NameToken : TypedToken { public string SemInfo; - public NameToken( string seminfo ) : base( TK.NAME ) + public NameToken(string seminfo) : base(TK.NAME) { SemInfo = seminfo; } public override string ToString() { - return string.Format( "NameToken: {0}", SemInfo ); + return string.Format("NameToken: {0}", SemInfo); } } @@ -261,14 +263,14 @@ public class NumberToken : TypedToken { public double SemInfo; - public NumberToken( double seminfo ) : base( TK.NUMBER ) + public NumberToken(double seminfo) : base(TK.NUMBER) { SemInfo = seminfo; } public override string ToString() { - return string.Format( "NumberToken: {0}", SemInfo ); + return string.Format("NumberToken: {0}", SemInfo); } } @@ -285,9 +287,9 @@ public int pos } private int Current; public int LineNumber; - public int LastLine; - private StringLoadInfo LoadInfo; - public string Source; + public int LastLine; + private StringLoadInfo LoadInfo; + public string Source; public Token Token; private Token LookAhead; @@ -295,49 +297,51 @@ public int pos private StringBuilder _Saved; private StringBuilder Saved { - get { - if( _Saved == null ) { _Saved = new StringBuilder(); } + get + { + if (_Saved == null) { _Saved = new StringBuilder(); } return _Saved; } } - private static Dictionary ReservedWordDict; - static LLex() - { - ReservedWordDict = new Dictionary(); - ReservedWordDict.Add("and", TK.AND); - ReservedWordDict.Add("break", TK.BREAK); - ReservedWordDict.Add("do", TK.DO); - ReservedWordDict.Add("else", TK.ELSE); - ReservedWordDict.Add("elseif", TK.ELSEIF); - ReservedWordDict.Add("end", TK.END); - ReservedWordDict.Add("false", TK.FALSE); - ReservedWordDict.Add("for", TK.FOR); - ReservedWordDict.Add("function", TK.FUNCTION); - ReservedWordDict.Add("goto", TK.GOTO); - ReservedWordDict.Add("if", TK.IF); - ReservedWordDict.Add("in", TK.IN); - ReservedWordDict.Add("local", TK.LOCAL); - ReservedWordDict.Add("nil", TK.NIL); - ReservedWordDict.Add("not", TK.NOT); - ReservedWordDict.Add("or", TK.OR); - ReservedWordDict.Add("repeat", TK.REPEAT); - ReservedWordDict.Add("return", TK.RETURN); - ReservedWordDict.Add("then", TK.THEN); - ReservedWordDict.Add("true", TK.TRUE); - ReservedWordDict.Add("until", TK.UNTIL); - ReservedWordDict.Add("while", TK.WHILE); - } - - public LLex(StringLoadInfo loadinfo, string name ) - { - LoadInfo = loadinfo; - LineNumber = 1; - LastLine = 1; - Token = null; - LookAhead = null; - _Saved = null; - Source = name; + private static Dictionary ReservedWordDict; + static LLex() + { + ReservedWordDict = new Dictionary(); + ReservedWordDict.Add("and", TK.AND); + ReservedWordDict.Add("break", TK.BREAK); + ReservedWordDict.Add("continue", TK.CONTINUE); + ReservedWordDict.Add("do", TK.DO); + ReservedWordDict.Add("else", TK.ELSE); + ReservedWordDict.Add("elseif", TK.ELSEIF); + ReservedWordDict.Add("end", TK.END); + ReservedWordDict.Add("false", TK.FALSE); + ReservedWordDict.Add("for", TK.FOR); + ReservedWordDict.Add("function", TK.FUNCTION); + ReservedWordDict.Add("goto", TK.GOTO); + ReservedWordDict.Add("if", TK.IF); + ReservedWordDict.Add("in", TK.IN); + ReservedWordDict.Add("local", TK.LOCAL); + ReservedWordDict.Add("nil", TK.NIL); + ReservedWordDict.Add("not", TK.NOT); + ReservedWordDict.Add("or", TK.OR); + ReservedWordDict.Add("repeat", TK.REPEAT); + ReservedWordDict.Add("return", TK.RETURN); + ReservedWordDict.Add("then", TK.THEN); + ReservedWordDict.Add("true", TK.TRUE); + ReservedWordDict.Add("until", TK.UNTIL); + ReservedWordDict.Add("while", TK.WHILE); + } + + public LLex(StringLoadInfo loadinfo, string name) + { + LoadInfo = loadinfo; + LineNumber = 1; + LastLine = 1; + Token = null; + LookAhead = null; + _Saved = null; + Source = name; _Next(); } @@ -357,8 +361,8 @@ public void InsertString(int startPos, string value) public void Next() { - LastLine = LineNumber; - if( LookAhead != null ) + LastLine = LineNumber; + if (LookAhead != null) { Token = LookAhead; LookAhead = null; @@ -369,12 +373,12 @@ public void Next() } } - public Token GetLookAhead() - { - Utl.Assert( LookAhead == null ); - LookAhead = _Lex(); - return LookAhead; - } + public Token GetLookAhead() + { + Utl.Assert(LookAhead == null); + LookAhead = _Lex(); + return LookAhead; + } public string Replace(int start, int end, string value) { @@ -389,19 +393,19 @@ public string ReadString(int start, int end) private void _Next() { - var c = LoadInfo.ReadByte(); - Current = (c == -1) ? EOZ : c; + var c = LoadInfo.ReadByte(); + Current = (c == -1) ? EOZ : c; } private void _SaveAndNext() { - Saved.Append( (char)Current ); - _Next(); + Saved.Append((char)Current); + _Next(); } - private void _Save( char c ) + private void _Save(char c) { - Saved.Append( c ); + Saved.Append(c); } private string _GetSavedString() @@ -421,232 +425,233 @@ private bool _CurrentIsNewLine() private bool _CurrentIsDigit() { - return Char.IsDigit( (char)Current ); + return Char.IsDigit((char)Current); } - private bool _CurrentIsXDigit() - { - return _CurrentIsDigit() || - ('A' <= Current && Current <= 'F') || - ('a' <= Current && Current <= 'f'); - } + private bool _CurrentIsXDigit() + { + return _CurrentIsDigit() || + ('A' <= Current && Current <= 'F') || + ('a' <= Current && Current <= 'f'); + } private bool _CurrentIsSpace() { - return Char.IsWhiteSpace( (char)Current ); + return Char.IsWhiteSpace((char)Current); } private bool _CurrentIsAlpha() { - return Char.IsLetter( (char)Current ); + return Char.IsLetter((char)Current); } - private bool _IsReserved( string identifier, out TK type ) - { - return ReservedWordDict.TryGetValue( identifier, out type ); - } + private bool _IsReserved(string identifier, out TK type) + { + return ReservedWordDict.TryGetValue(identifier, out type); + } - public bool IsReservedWord( string name ) - { - return ReservedWordDict.ContainsKey( name ); - } + public bool IsReservedWord(string name) + { + return ReservedWordDict.ContainsKey(name); + } private void _IncLineNumber() { var old = Current; _Next(); - if( _CurrentIsNewLine() && Current != old ) + if (_CurrentIsNewLine() && Current != old) _Next(); - if( ++LineNumber >= Int32.MaxValue ) - _Error( "chunk has too many lines" ); + if (++LineNumber >= Int32.MaxValue) + _Error("chunk has too many lines"); } - private string _ReadLongString( int sep ) + private string _ReadLongString(int sep) { _SaveAndNext(); - if( _CurrentIsNewLine() ) + if (_CurrentIsNewLine()) _IncLineNumber(); - while( true ) + while (true) { - switch( Current ) + switch (Current) { case EOZ: - _LexError( _GetSavedString(), - "unfinished long string/comment", - (int)TK.EOS ); + _LexError(_GetSavedString(), + "unfinished long string/comment", + (int)TK.EOS); break; case '[': - { - if( _SkipSep() == sep ) { - _SaveAndNext(); - if( sep == 0 ) + if (_SkipSep() == sep) { - _LexError( _GetSavedString(), - "nesting of [[...]] is deprecated", - (int)TK.EOS ); + _SaveAndNext(); + if (sep == 0) + { + _LexError(_GetSavedString(), + "nesting of [[...]] is deprecated", + (int)TK.EOS); + } } + break; } - break; - } case ']': - { - if( _SkipSep() == sep ) { - _SaveAndNext(); - goto endloop; + if (_SkipSep() == sep) + { + _SaveAndNext(); + goto endloop; + } + break; } - break; - } case '\n': case '\r': - { - _Save('\n'); - _IncLineNumber(); - break; - } + { + _Save('\n'); + _IncLineNumber(); + break; + } default: - { - _SaveAndNext(); - break; - } + { + _SaveAndNext(); + break; + } } } - endloop: - var r = _GetSavedString(); - return r.Substring( 2+sep, r.Length - 2*(2+sep) ); - } - - private void _EscapeError( string info, string msg ) - { - _LexError( "\\"+info, msg, (int)TK.STRING ); - } - - private byte _ReadHexEscape() - { - int r = 0; - var c = new char[3] { 'x', (char)0, (char)0 }; - // read two hex digits - for( int i=1; i<3; ++i ) - { - _Next(); - c[i] = (char)Current; - if( !_CurrentIsXDigit() ) - { - _EscapeError( new String(c, 0, i+1), - "hexadecimal digit expected" ); - // error - } - r = (r << 4) + Int32.Parse( Current.ToString(), - NumberStyles.HexNumber ); - } - return (byte)r; - } - - private byte _ReadDecEscape() - { - int r = 0; - var c = new char[3]; - // read up to 3 digits - int i = 0; - for( i=0; i<3 && _CurrentIsDigit(); ++i ) - { - c[i] = (char)Current; - r = r*10 + Current - '0'; - _Next(); - } - if( r > Byte.MaxValue ) - _EscapeError( new String(c, 0, i), - "decimal escape too large" ); - return (byte)r; - } + endloop: + var r = _GetSavedString(); + return r.Substring(2 + sep, r.Length - 2 * (2 + sep)); + } + + private void _EscapeError(string info, string msg) + { + _LexError("\\" + info, msg, (int)TK.STRING); + } + + private byte _ReadHexEscape() + { + int r = 0; + var c = new char[3] { 'x', (char)0, (char)0 }; + // read two hex digits + for (int i = 1; i < 3; ++i) + { + _Next(); + c[i] = (char)Current; + if (!_CurrentIsXDigit()) + { + _EscapeError(new String(c, 0, i + 1), + "hexadecimal digit expected"); + // error + } + r = (r << 4) + Int32.Parse(Current.ToString(), + NumberStyles.HexNumber); + } + return (byte)r; + } + + private byte _ReadDecEscape() + { + int r = 0; + var c = new char[3]; + // read up to 3 digits + int i = 0; + for (i = 0; i < 3 && _CurrentIsDigit(); ++i) + { + c[i] = (char)Current; + r = r * 10 + Current - '0'; + _Next(); + } + if (r > Byte.MaxValue) + _EscapeError(new String(c, 0, i), + "decimal escape too large"); + return (byte)r; + } private string _ReadString() { var del = Current; _Next(); - while( Current != del ) + while (Current != del) { - switch( Current ) + switch (Current) { case EOZ: - _Error( "unfinished string" ); + _Error("unfinished string"); continue; case '\n': case '\r': - _Error( "unfinished string" ); + _Error("unfinished string"); continue; case '\\': - { - byte c; - _Next(); - switch( Current ) { - case 'a': c=(byte)'\a'; break; - case 'b': c=(byte)'\b'; break; - case 'f': c=(byte)'\f'; break; - case 'n': c=(byte)'\n'; break; - case 'r': c=(byte)'\r'; break; - case 't': c=(byte)'\t'; break; - case 'v': c=(byte)'\v'; break; - case 'x': c=_ReadHexEscape(); break; - - case '\n': - case '\r': _Save('\n'); _IncLineNumber(); continue; - - case '\\': - case '\"': - case '\'': c=(byte)Current; break; - - case EOZ: continue; - - // zap following span of spaces - case 'z': { - _Next(); // skip `z' - while( _CurrentIsSpace() ) - { - if( _CurrentIsNewLine() ) - _IncLineNumber(); - else - _Next(); - } - continue; - } - - default: + byte c; + _Next(); + switch (Current) { - if( !_CurrentIsDigit() ) - _EscapeError( Current.ToString(), - "invalid escape sequence" ); - - // digital escape \ddd - c = _ReadDecEscape(); - _Save( (char)c ); - continue; - // { - // c = (char)0; - // for(int i=0; i<3 && _CurrentIsDigit(); ++i) - // { - // c = (char)(c*10 + Current - '0'); - // _Next(); - // } - // _Save( c ); - // } - // continue; + case 'a': c = (byte)'\a'; break; + case 'b': c = (byte)'\b'; break; + case 'f': c = (byte)'\f'; break; + case 'n': c = (byte)'\n'; break; + case 'r': c = (byte)'\r'; break; + case 't': c = (byte)'\t'; break; + case 'v': c = (byte)'\v'; break; + case 'x': c = _ReadHexEscape(); break; + + case '\n': + case '\r': _Save('\n'); _IncLineNumber(); continue; + + case '\\': + case '\"': + case '\'': c = (byte)Current; break; + + case EOZ: continue; + + // zap following span of spaces + case 'z': + { + _Next(); // skip `z' + while (_CurrentIsSpace()) + { + if (_CurrentIsNewLine()) + _IncLineNumber(); + else + _Next(); + } + continue; + } + + default: + { + if (!_CurrentIsDigit()) + _EscapeError(Current.ToString(), + "invalid escape sequence"); + + // digital escape \ddd + c = _ReadDecEscape(); + _Save((char)c); + continue; + // { + // c = (char)0; + // for(int i=0; i<3 && _CurrentIsDigit(); ++i) + // { + // c = (char)(c*10 + Current - '0'); + // _Next(); + // } + // _Save( c ); + // } + // continue; + } } + _Save((char)c); + _Next(); + continue; } - _Save( (char)c ); - _Next(); - continue; - } default: _SaveAndNext(); @@ -659,40 +664,40 @@ private string _ReadString() private double _ReadNumber() { - var expo = new char[] { 'E', 'e' }; - Utl.Assert( _CurrentIsDigit() ); - var first = Current; - _SaveAndNext(); - if( first == '0' && (Current == 'X' || Current == 'x')) - { - expo = new char[] { 'P', 'p' }; - _SaveAndNext(); - } - for(;;) + var expo = new char[] { 'E', 'e' }; + Utl.Assert(_CurrentIsDigit()); + var first = Current; + _SaveAndNext(); + if (first == '0' && (Current == 'X' || Current == 'x')) { - if( Current == expo[0] || Current == expo[1] ) - { - _SaveAndNext(); - if( Current == '+' || Current == '-' ) - _SaveAndNext(); - } - if( _CurrentIsXDigit() || Current == '.' ) - _SaveAndNext(); - else - break; + expo = new char[] { 'P', 'p' }; + _SaveAndNext(); + } + for (; ; ) + { + if (Current == expo[0] || Current == expo[1]) + { + _SaveAndNext(); + if (Current == '+' || Current == '-') + _SaveAndNext(); + } + if (_CurrentIsXDigit() || Current == '.') + _SaveAndNext(); + else + break; } double ret; - var str = _GetSavedString(); - if(O_Str2Decimal( str, out ret ) ) - { - return ret; - } - else - { - _Error( "malformed number: " + str ); - return 0.0; - } + var str = _GetSavedString(); + if (O_Str2Decimal(str, out ret)) + { + return ret; + } + else + { + _Error("malformed number: " + str); + return 0.0; + } } public static bool O_Str2Decimal(string s, out double result) @@ -735,184 +740,199 @@ public static bool O_Str2Decimal(string s, out double result) // return ret; // } - private void _Error( string error ) + private void _Error(string error) { - throw new Exception(string.Format("{0}:{1}: {2}",Source, LineNumber, error)); + throw new Exception(string.Format("{0}:{1}: {2}", Source, LineNumber, error)); } - private void _LexError( string info, string msg, int tokenType ) - { - // TODO - _Error( msg + ":" + info ); - } + private void _LexError(string info, string msg, int tokenType) + { + // TODO + _Error(msg + ":" + info); + } - public void SyntaxError( string msg ) - { - // TODO - _Error( msg ); - } + public void SyntaxError(string msg) + { + // TODO + _Error(msg); + } private int _SkipSep() { int count = 0; var boundary = Current; _SaveAndNext(); - while( Current == '=' ) { + while (Current == '=') + { _SaveAndNext(); count++; } - return ( Current == boundary ? count : (-count)-1 ); + return (Current == boundary ? count : (-count) - 1); } private Token _Lex() { _ClearSaved(); - while( true ) + while (true) { - switch( Current ) + switch (Current) { case '\n': - case '\r': { - _IncLineNumber(); - continue; - } + case '\r': + { + _IncLineNumber(); + continue; + } - case '-': { - _Next(); - if( Current != '-' ) return new LiteralToken('-'); + case '-': + { + _Next(); + if (Current != '-') return new LiteralToken('-'); - // else is a long comment - _Next(); - if( Current == '[' ) + // else is a long comment + _Next(); + if (Current == '[') + { + int sep = _SkipSep(); + _ClearSaved(); + if (sep >= 0) + { + _ReadLongString(sep); + _ClearSaved(); + return new CommentToken(); + } + } + + // else is a short comment + while (!_CurrentIsNewLine() && Current != EOZ) + _Next(); + return new CommentToken(); + } + + case '[': { int sep = _SkipSep(); - _ClearSaved(); - if( sep >= 0 ) + if (sep >= 0) { - _ReadLongString( sep ); - _ClearSaved(); - return new CommentToken(); + string seminfo = _ReadLongString(sep); + return new StringToken(seminfo); } + else if (sep == -1) return new LiteralToken('['); + else _Error("invalid long string delimiter"); + continue; } - // else is a short comment - while( !_CurrentIsNewLine() && Current != EOZ ) + case '=': + { _Next(); - return new CommentToken(); + if (Current != '=') return new LiteralToken('='); + _Next(); + return new TypedToken(TK.EQ); } - case '[': { - int sep = _SkipSep(); - if( sep >= 0 ) { - string seminfo = _ReadLongString( sep ); - return new StringToken( seminfo ); + case '<': + { + _Next(); + if (Current != '=') return new LiteralToken('<'); + _Next(); + return new TypedToken(TK.LE); } - else if( sep == -1 ) return new LiteralToken('['); - else _Error("invalid long string delimiter"); - continue; - } - - case '=': { - _Next(); - if( Current != '=' ) return new LiteralToken('='); - _Next(); - return new TypedToken( TK.EQ ); - } - - case '<': { - _Next(); - if( Current != '=' ) return new LiteralToken('<'); - _Next(); - return new TypedToken( TK.LE ); - } - - case '>': { - _Next(); - if( Current != '=' ) return new LiteralToken('>'); - _Next(); - return new TypedToken( TK.GE ); - } - - case '~': { - _Next(); - if( Current != '=' ) return new LiteralToken('~'); - _Next(); - return new TypedToken( TK.NE ); - } - - case ':': { - _Next(); - if( Current != ':' ) return new LiteralToken(':'); - _Next(); - return new TypedToken( TK.DBCOLON ); // new in 5.2 ? - } - case '"': - case '\'': { - return new StringToken( _ReadString() ); - } + case '>': + { + _Next(); + if (Current != '=') return new LiteralToken('>'); + _Next(); + return new TypedToken(TK.GE); + } - case '.': { - _SaveAndNext(); - if( Current == '.' ) + case '~': { - _SaveAndNext(); - if( Current == '.' ) - { - _SaveAndNext(); - return new TypedToken( TK.DOTS ); - } - else - { - return new TypedToken( TK.CONCAT ); - } + _Next(); + if (Current != '=') return new LiteralToken('~'); + _Next(); + return new TypedToken(TK.NE); } - else if( !_CurrentIsDigit() ) - return new LiteralToken('.'); - else - return new NumberToken( _ReadNumber() ); - } - - case EOZ: { - return new TypedToken( TK.EOS ); - } - - default: { - if( _CurrentIsSpace() ) + + case ':': { _Next(); - continue; + if (Current != ':') return new LiteralToken(':'); + _Next(); + return new TypedToken(TK.DBCOLON); // new in 5.2 ? } - else if( _CurrentIsDigit() ) + + case '"': + case '\'': { - return new NumberToken( _ReadNumber() ); + return new StringToken(_ReadString()); } - else if( _CurrentIsAlpha() || Current == '_' ) + + case '.': { - do { + _SaveAndNext(); + if (Current == '.') + { _SaveAndNext(); - } while( _CurrentIsAlpha() || - _CurrentIsDigit() || - Current == '_' ); - - string identifier = _GetSavedString(); - TK type; - if( _IsReserved( identifier, out type ) ) - { - return new TypedToken( type ); - } - else - { - return new NameToken( identifier ); - } + if (Current == '.') + { + _SaveAndNext(); + return new TypedToken(TK.DOTS); + } + else + { + return new TypedToken(TK.CONCAT); + } + } + else if (!_CurrentIsDigit()) + return new LiteralToken('.'); + else + return new NumberToken(_ReadNumber()); } - else + + case EOZ: { - var c = Current; - _Next(); - return new LiteralToken(c); + return new TypedToken(TK.EOS); + } + + default: + { + if (_CurrentIsSpace()) + { + _Next(); + continue; + } + else if (_CurrentIsDigit()) + { + return new NumberToken(_ReadNumber()); + } + else if (_CurrentIsAlpha() || Current == '_') + { + do + { + _SaveAndNext(); + } while (_CurrentIsAlpha() || + _CurrentIsDigit() || + Current == '_'); + + string identifier = _GetSavedString(); + TK type; + if (_IsReserved(identifier, out type)) + { + return new TypedToken(type); + } + else + { + return new NameToken(identifier); + } + } + else + { + var c = Current; + _Next(); + return new LiteralToken(c); + } } - } } } } diff --git a/Assets/Slua/LuaProfiler/Common/Parse/Parse.cs b/Assets/Slua/LuaProfiler/Common/Parse/Parse.cs index f6817eab..d75eea00 100644 --- a/Assets/Slua/LuaProfiler/Common/Parse/Parse.cs +++ b/Assets/Slua/LuaProfiler/Common/Parse/Parse.cs @@ -73,7 +73,7 @@ static void InsertSample(LLex l, ref int lastPos, ref int nextPos, int tokenType if (tokenType == (int)')') { - l.InsertString(nextPos, "\r\nBeginMikuSample(\"" + l.Source + l.InsertString(nextPos - 1, "\r\nBeginMikuSample(\"" + l.Source + ",line:" + l.LineNumber + " funName:" + funName + "\")\r\n"); nextPos = l.pos; break; diff --git a/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs index a7761215..878e91f5 100644 --- a/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs +++ b/Assets/Slua/LuaProfiler/HookSetup/SLuaHookSetup.cs @@ -2,12 +2,12 @@ * ============================================================================== * Filename: SLuaHookSetup * Created: 2018/7/2 11:36:16 -* Author: エル・プサイ・コングルゥ +* Author: エル・プサイ・コングリィ * Purpose: * ============================================================================== */ -#define SLUA +//#define SLUA #if SLUA #if UNITY_EDITOR @@ -155,7 +155,7 @@ static HookSetup() if (isPlaying == true && EditorApplication.isPlaying == false) { - LuaProfiler.SetMainLuaEnv(null); + SetMainLuaEnv(null); } isPlaying = EditorApplication.isPlaying; diff --git a/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs index bb4de23a..5d58d06c 100644 --- a/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs +++ b/Assets/Slua/LuaProfiler/HookSetup/ToLuaHookSetup.cs @@ -2,7 +2,7 @@ * ============================================================================== * Filename: ToLuaHookSetup * Created: 2018/7/2 11:36:16 -* Author: エル・プサイ・コングルゥ +* Author: エル・プサイ・コングリィ * Purpose: * ============================================================================== */ @@ -155,7 +155,7 @@ static HookSetup() if (isPlaying == true && EditorApplication.isPlaying == false) { - LuaProfiler.SetMainLuaEnv(null); + SetMainLuaEnv(null); } isPlaying = EditorApplication.isPlaying; diff --git a/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs b/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs index 0a19cbc3..35ee28fa 100644 --- a/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs +++ b/Assets/Slua/LuaProfiler/HookSetup/XLuaHookSetup.cs @@ -2,7 +2,7 @@ * ============================================================================== * Filename: XLuaHookSetup * Created: 2018/7/2 11:36:16 -* Author: エル・プサイ・コングルゥ +* Author: エル・プサイ・コングリィ * Purpose: * ============================================================================== */ @@ -155,7 +155,7 @@ static HookSetup() if (isPlaying == true && EditorApplication.isPlaying == false) { - LuaProfiler.SetMainLuaEnv(null); + SetMainLuaEnv(null); } isPlaying = EditorApplication.isPlaying;