├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── cmake └── standalone.cmake ├── resources └── icons │ ├── hook.icns │ ├── hook.svg │ └── svg2icns.sh ├── scripts ├── build.sh └── create_dmg.sh ├── src ├── backend.cpp ├── backend.h ├── config.h.in └── main.cpp └── test ├── hook.h ├── test.cpp └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake 2 | CMakeLists.txt.user 3 | CMakeCache.txt 4 | CMakeFiles 5 | CMakeScripts 6 | Testing 7 | Makefile 8 | cmake_install.cmake 9 | install_manifest.txt 10 | compile_commands.json 11 | CTestTestfile.cmake 12 | _deps 13 | 14 | # C++ 15 | ## Prerequisites 16 | *.d 17 | 18 | ## Compiled Object files 19 | *.slo 20 | *.lo 21 | *.o 22 | *.obj 23 | 24 | ## Precompiled Headers 25 | *.gch 26 | *.pch 27 | 28 | ## Compiled Dynamic libraries 29 | *.so 30 | *.dylib 31 | *.dll 32 | 33 | ## Fortran module files 34 | *.mod 35 | *.smod 36 | 37 | ## Compiled Static libraries 38 | *.lai 39 | *.la 40 | *.a 41 | *.lib 42 | 43 | ## Executables 44 | *.exe 45 | *.out 46 | *.app 47 | 48 | # Xcode 49 | ## App packaging 50 | *.ipa 51 | *.dSYM.zip 52 | *.dSYM 53 | 54 | # macOS 55 | .DS_Store 56 | 57 | # build 58 | build 59 | bin 60 | .dmg 61 | .app 62 | 63 | # vim 64 | tags 65 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/imgui"] 2 | path = external/imgui 3 | url = git@github.com:ocornut/imgui.git 4 | [submodule "external/glfw"] 5 | path = external/glfw 6 | url = git@github.com:glfw/glfw.git 7 | [submodule "external/llvm-project"] 8 | path = external/llvm-project 9 | url = git@github.com:llvm/llvm-project.git 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "" FORCE) 4 | 5 | project(Hook 6 | VERSION 0.1.1 7 | LANGUAGES CXX OBJCXX 8 | ) 9 | 10 | set(CMAKE_CXX_STANDARD 20) 11 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 12 | set(CMAKE_BUILD_TYPE RelWithDebInfo) 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wformat") 14 | 15 | if(APPLE) 16 | set(CMAKE_INSTALL_PREFIX "/Applications") 17 | endif() 18 | 19 | find_package(Git REQUIRED) 20 | if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") 21 | option(GIT_SUBMODULE "Check submodules during build" ON) 22 | if(GIT_SUBMODULE) 23 | message(STATUS "Submodule update") 24 | execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive 25 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 26 | RESULT_VARIABLE GIT_SUBMOD_RESULT) 27 | if(NOT GIT_SUBMOD_RESULT EQUAL "0") 28 | message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") 29 | endif() 30 | endif() 31 | endif() 32 | 33 | execute_process(COMMAND ${PROJECT_SOURCE_DIR}/external/llvm-project/lldb/scripts/macos-setup-codesign.sh) 34 | 35 | include(ExternalProject) 36 | 37 | ExternalProject_Add(${PROJECT_NAME}-llvm 38 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/llvm-project/llvm 39 | BINARY_DIR ${PROJECT_BINARY_DIR}/lldb-build 40 | CONFIGURE_COMMAND ${CMAKE_COMMAND} 41 | -G Ninja 42 | -DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.app/Contents/Frameworks 43 | -C ${CMAKE_CURRENT_SOURCE_DIR}/cmake/standalone.cmake 44 | ${CMAKE_CURRENT_SOURCE_DIR}/external/llvm-project/llvm 45 | BUILD_COMMAND ninja 46 | -C ${PROJECT_BINARY_DIR}/lldb-build 47 | lldb 48 | debugserver 49 | # darwin-debug 50 | INSTALL_COMMAND ninja 51 | -C ${PROJECT_BINARY_DIR}/lldb-build 52 | #install-lldb 53 | install-debugserver 54 | install-liblldb 55 | #install-darwin-debug 56 | TEST_COMMAND "" 57 | ) 58 | 59 | add_subdirectory(external/glfw) 60 | 61 | set(CPP_SOURCES 62 | src/main.cpp 63 | external/imgui/imgui.cpp 64 | external/imgui/misc/cpp/imgui_stdlib.cpp 65 | external/imgui/imgui_draw.cpp 66 | external/imgui/imgui_tables.cpp 67 | external/imgui/imgui_widgets.cpp 68 | external/imgui/backends/imgui_impl_glfw.cpp 69 | ) 70 | 71 | set(OBJC_SOURCES 72 | external/imgui/backends/imgui_impl_metal.mm 73 | src/backend.cpp 74 | ) 75 | 76 | set(APP_ICON_MACOSX resources/icons/hook.icns) 77 | 78 | set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES 79 | MACOSX_PACKAGE_LOCATION "Resources" 80 | ) 81 | 82 | add_executable(${PROJECT_NAME} MACOSX_BUNDLE 83 | ${CPP_SOURCES} 84 | ${OBJC_SOURCES} 85 | ${APP_ICON_MACOSX} 86 | ) 87 | 88 | set_target_properties(${PROJECT_NAME} PROPERTIES 89 | MACOSX_BUNDLE_ICON_FILE hook.icns 90 | ) 91 | 92 | configure_file(src/config.h.in config.h) 93 | 94 | target_include_directories(${PROJECT_NAME} 95 | PUBLIC ${PROJECT_BINARY_DIR} 96 | PUBLIC external/glfw/include 97 | PUBLIC external/imgui 98 | PUBLIC external/imgui/backends 99 | PUBLIC external/imgui/misc/cpp 100 | PUBLIC external/llvm-project/lldb/include 101 | PUBLIC src 102 | ) 103 | 104 | target_link_directories(${PROJECT_NAME} 105 | PUBLIC ${PROJECT_BINARY_DIR}/lldb-build/lib 106 | ) 107 | 108 | target_link_libraries(${PROJECT_NAME} 109 | "lldb" 110 | "glfw" 111 | "-framework Metal" 112 | "-framework MetalKit" 113 | "-framework Cocoa" 114 | "-framework IOKit" 115 | "-framework CoreVideo" 116 | "-framework QuartzCore" 117 | "-framework Security" 118 | ) 119 | 120 | set_source_files_properties(${OBJC_SOURCES} PROPERTIES 121 | LANGUAGE OBJCXX 122 | COMPILE_FLAGS "-x objective-c++" 123 | ) 124 | 125 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 126 | COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change @rpath/liblldb.17.0.4.dylib @executable_path/../Frameworks/lib/liblldb.dylib $ 127 | ) 128 | 129 | set(GLFW_INSTALL OFF CACHE BOOL "" FORCE) 130 | install(TARGETS ${PROJECT_NAME} 131 | BUNDLE DESTINATION . 132 | ) 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Hook 6 | A **graphical C/C++ runtime editor** for rapid experimentation. It attaches to your running program and allows you to easily change variables live, breaking the time-consuming edit-compile-run-edit cycle. 7 | 8 | hook_in_action 9 | 10 | # build and install 11 | > Warning: This will take a long time. (~20 minutes on an M2 MacBook air) 12 | ``` 13 | ./scripts/build.sh 14 | ``` 15 | 16 | # run 17 | ``` 18 | open -a Hook 19 | ``` 20 | -------------------------------------------------------------------------------- /cmake/standalone.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "") 2 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "") 3 | 4 | set(LLVM_TARGETS_TO_BUILD AArch64;X86 CACHE STRING "") 5 | set(LLVM_ENABLE_PROJECTS clang;lldb CACHE STRING "") 6 | set(LLDB_INCLUDE_TESTS OFF CACHE BOOL "") 7 | set(LLDB_SKIP_STRIP ON CACHE BOOL "") 8 | 9 | set(LLDB_NO_INSTALL_DEFAULT_RPATH OFF CACHE BOOL "") 10 | set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0 CACHE STRING "") 11 | 12 | set(LLDB_BUILD_FRAMEWORK OFF CACHE BOOL "") 13 | 14 | set(LLVM_ENABLE_ZSTD OFF CACHE BOOL "") 15 | set(LLVM_ENABLE_DOXYGEN OFF CACHE BOOL "") 16 | set(LLDB_ENABLE_SWIG OFF CACHE BOOL "") 17 | set(LLDB_ENABLE_PYTHON OFF CACHE BOOL "") 18 | set(LLDB_ENABLE_CURSES OFF CACHE BOOL "") 19 | set(LLDB_ENABLE_TERMIOS OFF CACHE BOOL "") 20 | set(LLDB_ENABLE_LIBEDIT OFF CACHE BOOL "") 21 | set(LLDB_ENABLE_LIBXML2 OFF CACHE BOOL "") 22 | set(LLDB_ENABLE_LZMA OFF CACHE BOOL "") 23 | set(LLDB_ENABLE_LUA OFF CACHE BOOL "") 24 | 25 | set(LLDB_USE_OS_LOG ON CACHE BOOL "") 26 | 27 | set(LLVM_DISTRIBUTION_COMPONENTS 28 | #lldb 29 | liblldb 30 | #lldb-argdumper 31 | #darwin-debug 32 | debugserver 33 | CACHE STRING "") 34 | -------------------------------------------------------------------------------- /resources/icons/hook.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abolinsky/Hook/4ca916cdec9dbefe98e43ef9fc08c0acdef2b28c/resources/icons/hook.icns -------------------------------------------------------------------------------- /resources/icons/hook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /resources/icons/svg2icns.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | SIZES=" 4 | 16,16x16 5 | 32,16x16@2x 6 | 32,32x32 7 | 64,32x32@2x 8 | 128,128x128 9 | 256,128x128@2x 10 | 256,256x256 11 | 512,256x256@2x 12 | 512,512x512 13 | 1024,512x512@2x 14 | " 15 | 16 | for SVG in "$@"; do 17 | BASE=$(basename "$SVG" | sed 's/\.[^\.]*$//') 18 | echo $BASE 19 | ICONSET="$BASE.iconset" 20 | mkdir -p "$ICONSET" 21 | for PARAMS in $SIZES; do 22 | SIZE=$(echo $PARAMS | cut -d, -f1) 23 | LABEL=$(echo $PARAMS | cut -d, -f2) 24 | svg2png -w $SIZE -h $SIZE "$SVG" "$ICONSET"/icon_$LABEL.png 25 | done 26 | 27 | iconutil -c icns "$ICONSET" 28 | rm -rf "$ICONSET" 29 | done 30 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | rm -rf build 2 | cmake -B build -S . 3 | cmake --build build 4 | cmake --install build 5 | -------------------------------------------------------------------------------- /scripts/create_dmg.sh: -------------------------------------------------------------------------------- 1 | create-dmg \ 2 | --volname "Hook Installer" \ 3 | --window-pos 200 120 \ 4 | --window-size 800 400 \ 5 | --icon-size 100 \ 6 | --icon "hook.app" 200 190 \ 7 | --hide-extension "Hook.app" \ 8 | --app-drop-link 600 185 \ 9 | "build/Hook.dmg" \ 10 | "build/Hook.app" 11 | -------------------------------------------------------------------------------- /src/backend.cpp: -------------------------------------------------------------------------------- 1 | #include "backend.h" 2 | #include "imgui_impl_glfw.h" 3 | #include "imgui_impl_metal.h" 4 | 5 | #include "config.h" 6 | 7 | #define GLFW_INCLUDE_NONE 8 | #define GLFW_EXPOSE_NATIVE_COCOA 9 | #include 10 | #include 11 | 12 | #import 13 | #import 14 | 15 | #include 16 | #include 17 | 18 | void glfw_error_callback(int error, const char* description) { 19 | throw std::runtime_error("Glfw Error " + std::to_string(error) + ": " + description); 20 | } 21 | 22 | void main_loop(void (*user_function)()) { 23 | glfwSetErrorCallback(glfw_error_callback); 24 | if (!glfwInit()) 25 | return; 26 | 27 | // Create window with graphics context 28 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 29 | std::string title = project::name + " " + project::version.to_string(); 30 | GLFWwindow* window = glfwCreateWindow(440, 720, title.c_str(), nullptr, nullptr); 31 | if (window == nullptr) 32 | return; 33 | 34 | id device = MTLCreateSystemDefaultDevice(); 35 | id commandQueue = [device newCommandQueue]; 36 | 37 | // Setup Platform/Renderer backends 38 | ImGui_ImplGlfw_InitForOpenGL(window, true); 39 | ImGui_ImplMetal_Init(device); 40 | 41 | NSWindow *nswin = glfwGetCocoaWindow(window); 42 | CAMetalLayer *layer = [CAMetalLayer layer]; 43 | layer.device = device; 44 | layer.pixelFormat = MTLPixelFormatBGRA8Unorm; 45 | nswin.contentView.layer = layer; 46 | nswin.contentView.wantsLayer = YES; 47 | 48 | MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor new]; 49 | 50 | float clear_color[4] = {0.45f, 0.55f, 0.60f, 1.00f}; 51 | 52 | while (!glfwWindowShouldClose(window)) 53 | { 54 | @autoreleasepool 55 | { 56 | glfwPollEvents(); 57 | 58 | int width, height; 59 | glfwGetFramebufferSize(window, &width, &height); 60 | layer.drawableSize = CGSizeMake(width, height); 61 | id drawable = [layer nextDrawable]; 62 | 63 | id commandBuffer = [commandQueue commandBuffer]; 64 | renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]); 65 | renderPassDescriptor.colorAttachments[0].texture = drawable.texture; 66 | renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; 67 | renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; 68 | id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; 69 | 70 | // Start the Dear ImGui frame 71 | ImGui_ImplMetal_NewFrame(renderPassDescriptor); 72 | ImGui_ImplGlfw_NewFrame(); 73 | 74 | user_function(); 75 | 76 | ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), commandBuffer, renderEncoder); 77 | 78 | [renderEncoder endEncoding]; 79 | 80 | [commandBuffer presentDrawable:drawable]; 81 | [commandBuffer commit]; 82 | } 83 | } 84 | 85 | // Cleanup 86 | ImGui_ImplMetal_Shutdown(); 87 | ImGui_ImplGlfw_Shutdown(); 88 | ImGui::DestroyContext(); 89 | 90 | glfwDestroyWindow(window); 91 | glfwTerminate(); 92 | } -------------------------------------------------------------------------------- /src/backend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void glfw_error_callback(int error, const char* description); 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | void main_loop(void (*user_function)()); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif -------------------------------------------------------------------------------- /src/config.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace project { 6 | 7 | struct Version { 8 | int major = @PROJECT_VERSION_MAJOR@; 9 | int minor = @PROJECT_VERSION_MINOR@; 10 | int patch = @PROJECT_VERSION_PATCH@; 11 | 12 | std::string to_string() const { 13 | return "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@"; 14 | } 15 | } version; 16 | 17 | std::string name { "@PROJECT_NAME@" }; 18 | 19 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "backend.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | namespace Hook { 18 | 19 | std::string GetExecutablePath() { 20 | char path[PATH_MAX]; 21 | uint32_t size = sizeof(path); 22 | if (_NSGetExecutablePath(path, &size) != 0) { 23 | throw std::runtime_error("Could not get executable path"); 24 | } 25 | 26 | return std::string(path); 27 | } 28 | 29 | std::string GetDebugServerPath() { 30 | std::string executablePath = GetExecutablePath(); 31 | size_t appDirPos = executablePath.find(".app"); 32 | if (appDirPos == std::string::npos) { 33 | throw std::runtime_error("Could not get debugserver path"); 34 | } 35 | 36 | std::string debugServerPath = executablePath.substr(0, appDirPos) + ".app/Contents/Frameworks/bin/debugserver"; 37 | return debugServerPath; 38 | } 39 | 40 | struct VariableInfo { 41 | VariableInfo(lldb::SBValue& value) { 42 | this->name = value.GetName(); 43 | this->function_name = (name.substr(0,2) == "::") ? "" : value.GetFrame().GetFunctionName(); 44 | this->id = value.GetID(); 45 | 46 | this->type = value.GetType().GetCanonicalType(); 47 | while (type.GetTypeClass() == lldb::eTypeClassTypedef) { 48 | this->type.GetTypedefedType(); 49 | } 50 | this->type_name = this->type.GetName(); 51 | this->type_class = this->type.GetTypeClass(); 52 | this->basic_type = this->type.GetBasicType(); 53 | if (this->basic_type == lldb::eBasicTypeUnsignedChar) { 54 | value.SetFormat(lldb::Format::eFormatDecimal); 55 | } 56 | 57 | this->is_nested = this->type.IsAggregateType(); 58 | if (!this->is_nested) { 59 | this->value = value.GetValue(); 60 | } 61 | } 62 | 63 | VariableInfo() = default; 64 | 65 | bool IsAggregateType() const { 66 | return is_nested; 67 | } 68 | 69 | std::string GetFullyQualifiedValue() const { 70 | if (type_class == lldb::eTypeClassEnumeration) { 71 | return type_name + "::" + value; 72 | } else { 73 | return value; 74 | } 75 | } 76 | 77 | const VariableInfo& GetRoot() const { 78 | const VariableInfo* root = this; 79 | while (root->parent) { 80 | root = root->parent; 81 | } 82 | return *root; 83 | } 84 | 85 | bool IsRoot() const { 86 | return !parent; 87 | } 88 | 89 | bool ParentIsContainer() const { 90 | return name.back() == ']'; 91 | } 92 | 93 | std::string GetFullyQualifiedName() const { 94 | std::string full_name; 95 | const VariableInfo* current = this; 96 | while (current != nullptr) { 97 | full_name = current->name + full_name; 98 | if (!current->IsRoot() && !current->ParentIsContainer()) { 99 | full_name = "." + full_name; 100 | } 101 | current = current->parent; 102 | } 103 | return full_name; 104 | } 105 | 106 | std::string name; 107 | std::string function_name; 108 | std::string value; 109 | lldb::SBType type; 110 | std::string type_name; 111 | lldb::TypeClass type_class; 112 | lldb::BasicType basic_type; 113 | bool is_nested = false; 114 | uint64_t id = std::numeric_limits::max(); 115 | std::vector children; 116 | VariableInfo* parent = nullptr; 117 | }; 118 | 119 | std::vector variables; 120 | const VariableInfo* current_var_info; 121 | bool published_changes = true; 122 | bool open_pid_popup = true; 123 | 124 | lldb::SBDebugger debugger; 125 | lldb::SBTarget target; 126 | lldb::SBListener listener; 127 | lldb::SBError error; 128 | lldb::SBProcess process; 129 | 130 | lldb::pid_t pid = 0; 131 | 132 | void HelpMarker(const char* desc) { 133 | ImGui::TextDisabled("(?)"); 134 | if (ImGui::BeginItemTooltip()) { 135 | ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); 136 | ImGui::TextUnformatted(desc); 137 | ImGui::PopTextWrapPos(); 138 | ImGui::EndTooltip(); 139 | } 140 | } 141 | 142 | void PublishChange(const VariableInfo& varInfo) { 143 | current_var_info = &varInfo; 144 | published_changes = false; 145 | process.Stop(); 146 | } 147 | 148 | lldb::SBValue FindVariableById(lldb::SBFrame& frame, uint64_t id) { 149 | lldb::SBValueList vars = frame.GetVariables(true, true, true, true); 150 | for (int i = 0; i < vars.GetSize(); ++i) { 151 | lldb::SBValue v = vars.GetValueAtIndex(i); 152 | if (v.GetID() == id) { 153 | return v; 154 | } 155 | } 156 | return lldb::SBValue(); 157 | } 158 | 159 | std::vector GetVariablesFromFrame(lldb::SBFrame& frame) { 160 | std::vector variables; 161 | 162 | lldb::SBValueList frameVariables = frame.GetVariables(true, true, true, true); // arguments, locals, statics, in_scope_only 163 | for (int i = 0; i < frameVariables.GetSize(); i++) { 164 | lldb::SBValue var = frameVariables.GetValueAtIndex(i); 165 | if (var) { 166 | variables.push_back(var); 167 | } 168 | } 169 | return variables; 170 | } 171 | 172 | lldb::SBThread GetThread(lldb::SBProcess& process) { 173 | lldb::SBThread thread = process.GetSelectedThread(); 174 | if (!thread) { 175 | std::cerr << "Failed to get thread" << std::endl; 176 | } 177 | return thread; 178 | } 179 | 180 | std::vector GetFrames(lldb::SBThread& thread) { 181 | std::vector frames; 182 | const auto num_frames = thread.GetNumFrames(); 183 | frames.reserve(num_frames); 184 | 185 | for (auto i = 0; i < num_frames; ++i) { 186 | lldb::SBFrame frame = thread.GetFrameAtIndex(i); 187 | if (frame) { 188 | frames.push_back(frame); 189 | } 190 | } 191 | return frames; 192 | } 193 | 194 | void DisplayVariable(VariableInfo& varInfo) { 195 | std::string prefix = !varInfo.IsRoot() ? "" : varInfo.function_name.empty() ? "" : "(" + varInfo.function_name + ") "; 196 | ImGui::Text("%s%s =", prefix.c_str(), varInfo.name.c_str()); 197 | ImGui::SameLine(); 198 | 199 | if (varInfo.IsAggregateType()) { 200 | std::string treeNodeLabel = "##varname" + std::to_string(varInfo.id); 201 | if (ImGui::TreeNode(treeNodeLabel.c_str())) { 202 | for (auto childVar : varInfo.children) { 203 | DisplayVariable(*childVar); 204 | } 205 | ImGui::TreePop(); 206 | } 207 | } else { 208 | std::string inputTextLabel = "##varname" + varInfo.function_name + varInfo.name; 209 | if (varInfo.type_class == lldb::eTypeClassEnumeration) { 210 | auto members = varInfo.type.GetEnumMembers(); 211 | auto num_members = members.GetSize(); 212 | std::vector member_names; 213 | for (unsigned i = 0; i < num_members; ++i) { 214 | auto current_enum_member = members.GetTypeEnumMemberAtIndex(i); 215 | if (current_enum_member.IsValid() && std::string(current_enum_member.GetName()) != "") { 216 | member_names.push_back(current_enum_member.GetName()); 217 | } 218 | } 219 | int index = 0; 220 | for (auto& n : member_names) { 221 | if (varInfo.value == n) { 222 | std::string inputTextLabel = "##sliderEnum" + varInfo.name; 223 | ImGui::SliderInt(inputTextLabel.c_str(), &index, 0, member_names.size() - 1, member_names[index].c_str()); 224 | varInfo.value = member_names[index]; 225 | break; 226 | } 227 | ++index; 228 | } 229 | if (ImGui::IsItemDeactivatedAfterEdit()) { 230 | PublishChange(varInfo); 231 | } 232 | } else if (varInfo.basic_type == lldb::eBasicTypeBool) { 233 | bool value = varInfo.value == "true"; 234 | ImGui::Checkbox(inputTextLabel.c_str(), &value); 235 | varInfo.value = value ? "true" : "false"; 236 | if (ImGui::IsItemDeactivatedAfterEdit()) { 237 | PublishChange(varInfo); 238 | } 239 | } else if (varInfo.basic_type == lldb::eBasicTypeUnsignedChar) { 240 | uint8_t value = std::stoul(varInfo.value); 241 | static auto min = std::numeric_limits::min(); 242 | static auto max = std::numeric_limits::max(); 243 | ImGui::SliderScalar(inputTextLabel.c_str(), ImGuiDataType_U8, &value, &min , &max); 244 | if (ImGui::IsItemDeactivatedAfterEdit()) { 245 | PublishChange(varInfo); 246 | } 247 | ImGui::SameLine(); HelpMarker("CTRL+click to input value"); 248 | varInfo.value = std::to_string(value); 249 | } else if (varInfo.basic_type == lldb::eBasicTypeInt) { 250 | int value = std::stoi(varInfo.value); 251 | static auto min = std::numeric_limits::min() / 2; 252 | static auto max = std::numeric_limits::max() / 2; 253 | ImGui::SliderScalar(inputTextLabel.c_str(), ImGuiDataType_S32, &value, &min , &max); 254 | if (ImGui::IsItemDeactivatedAfterEdit()) { 255 | PublishChange(varInfo); 256 | } 257 | ImGui::SameLine(); HelpMarker("CTRL+click to input value"); 258 | varInfo.value = std::to_string(value); 259 | } else { 260 | ImGui::InputText(inputTextLabel.c_str(), &varInfo.value, ImGuiInputTextFlags_CharsDecimal); 261 | if (ImGui::IsItemDeactivatedAfterEdit()) { 262 | PublishChange(varInfo); 263 | } 264 | } 265 | } 266 | } 267 | 268 | void FetchNestedMembers(lldb::SBValue& aggregateValue, VariableInfo& parent) { 269 | for (int i = 0; i < aggregateValue.GetNumChildren(); ++i) { 270 | lldb::SBValue childValue = aggregateValue.GetChildAtIndex(i); 271 | if (!childValue.IsValid()) continue; 272 | 273 | variables.emplace_back(childValue); 274 | variables.back().parent = &parent; 275 | parent.children.push_back(&variables.back()); 276 | 277 | if (parent.children.back()->IsAggregateType()) { 278 | FetchNestedMembers(childValue, *parent.children.back()); 279 | } 280 | } 281 | } 282 | 283 | std::vector GetVariablesFromThread(lldb::SBThread& thread) { 284 | std::vector variables; 285 | for (auto& frame : GetFrames(thread)) { 286 | auto frame_vars = GetVariablesFromFrame(frame); 287 | variables.insert(variables.end(), frame_vars.begin(), frame_vars.end()); 288 | } 289 | return variables; 290 | } 291 | 292 | void FetchAllVariables() { 293 | auto thread = GetThread(process); 294 | if (!thread) return; 295 | 296 | auto thread_variables = GetVariablesFromThread(thread); 297 | 298 | variables.clear(); 299 | variables.reserve(thread_variables.size() * 100); 300 | 301 | for (auto& var : thread_variables) { 302 | const VariableInfo varInfo(var); 303 | if (std::none_of(variables.begin(), variables.end(), [&varInfo](const VariableInfo& v) { 304 | return v.function_name == varInfo.function_name && v.name == varInfo.name; 305 | })) { 306 | variables.push_back(varInfo); 307 | if (varInfo.IsAggregateType()) { 308 | FetchNestedMembers(var, variables.back()); 309 | } 310 | } 311 | } 312 | } 313 | 314 | void AttachToProcess(lldb::SBAttachInfo& attachInfo) { 315 | target = debugger.CreateTarget(""); 316 | process = target.Attach(attachInfo, error); 317 | if (!process.IsValid() || error.Fail()) { 318 | throw std::runtime_error(std::string("Failed to attach to process: ") + error.GetCString()); 319 | } 320 | } 321 | 322 | void AttachToProcessWithID(lldb::pid_t pid) { 323 | lldb::SBAttachInfo attachInfo; 324 | attachInfo.SetProcessID(pid); 325 | AttachToProcess(attachInfo); 326 | } 327 | 328 | void SetupEventListener() { 329 | listener = debugger.GetListener(); 330 | auto event_mask = lldb::SBProcess::eBroadcastBitStateChanged; 331 | auto event_bits = process.GetBroadcaster().AddListener(listener, event_mask); 332 | if (event_bits != event_mask) { 333 | throw std::runtime_error("Could not set up event listener"); 334 | } 335 | } 336 | 337 | void HandleAttachProcess() { 338 | AttachToProcessWithID(pid); 339 | SetupEventListener(); 340 | FetchAllVariables(); 341 | process.Continue(); 342 | } 343 | 344 | void StyleColorsFunky() { 345 | auto yellow = ImVec4{0.996, 0.780, 0.008, 1.0}; 346 | auto blue = ImVec4{0.090, 0.729, 0.808, 1.0}; 347 | auto green = ImVec4{0.149, 0.918, 0.694, 1.0}; 348 | auto white = ImVec4{0.996, 0.996, 0.996, 1.0}; 349 | auto middle_gray = ImVec4{0.5f, 0.5f, 0.5f, 1.0}; 350 | auto light_gray = ImVec4{0.85f, 0.85f, 0.85f, 1.0}; 351 | auto off_white = ImVec4{0.96f, 0.96f, 0.96f, 1.0}; 352 | auto black = ImVec4{0.0, 0.0, 0.0, 1.0}; 353 | 354 | auto &colors = ImGui::GetStyle().Colors; 355 | colors[ImGuiCol_WindowBg] = green; 356 | colors[ImGuiCol_MenuBarBg] = blue; 357 | 358 | // Border 359 | colors[ImGuiCol_Border] = white; 360 | colors[ImGuiCol_BorderShadow] = ImVec4{0.0f, 0.0f, 0.0f, 0.24f}; 361 | 362 | // Text 363 | colors[ImGuiCol_Text] = black; 364 | colors[ImGuiCol_TextDisabled] = middle_gray; 365 | 366 | // Headers 367 | colors[ImGuiCol_Header] = yellow; 368 | colors[ImGuiCol_HeaderHovered] = light_gray; 369 | colors[ImGuiCol_HeaderActive] = white; 370 | 371 | // Buttons 372 | colors[ImGuiCol_Button] = blue; 373 | colors[ImGuiCol_ButtonHovered] = white; 374 | colors[ImGuiCol_ButtonActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 375 | colors[ImGuiCol_CheckMark] = blue; 376 | 377 | // Popups 378 | colors[ImGuiCol_PopupBg] = white; 379 | 380 | // Slider 381 | colors[ImGuiCol_SliderGrab] = yellow; 382 | colors[ImGuiCol_SliderGrabActive] = white; 383 | 384 | // Frame BG 385 | colors[ImGuiCol_FrameBg] = off_white; 386 | colors[ImGuiCol_FrameBgHovered] = light_gray; 387 | colors[ImGuiCol_FrameBgActive] = yellow; 388 | 389 | // Tabs 390 | colors[ImGuiCol_Tab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 391 | colors[ImGuiCol_TabHovered] = ImVec4{0.24, 0.24f, 0.32f, 1.0f}; 392 | colors[ImGuiCol_TabActive] = ImVec4{0.2f, 0.22f, 0.27f, 1.0f}; 393 | colors[ImGuiCol_TabUnfocused] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 394 | colors[ImGuiCol_TabUnfocusedActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 395 | 396 | // Title 397 | colors[ImGuiCol_TitleBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 398 | colors[ImGuiCol_TitleBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 399 | colors[ImGuiCol_TitleBgCollapsed] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 400 | 401 | // Scrollbar 402 | colors[ImGuiCol_ScrollbarBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0f}; 403 | colors[ImGuiCol_ScrollbarGrab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 404 | colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f}; 405 | colors[ImGuiCol_ScrollbarGrabActive] = ImVec4{0.24f, 0.24f, 0.32f, 1.0f}; 406 | 407 | // Seperator 408 | colors[ImGuiCol_Separator] = ImVec4{0.44f, 0.37f, 0.61f, 1.0f}; 409 | colors[ImGuiCol_SeparatorHovered] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f}; 410 | colors[ImGuiCol_SeparatorActive] = ImVec4{0.84f, 0.58f, 1.0f, 1.0f}; 411 | 412 | // Resize Grip 413 | colors[ImGuiCol_ResizeGrip] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f}; 414 | colors[ImGuiCol_ResizeGripHovered] = ImVec4{0.74f, 0.58f, 0.98f, 0.29f}; 415 | colors[ImGuiCol_ResizeGripActive] = ImVec4{0.84f, 0.58f, 1.0f, 0.29f}; 416 | 417 | auto &style = ImGui::GetStyle(); 418 | style.TabRounding = 4; 419 | style.ScrollbarRounding = 9; 420 | style.WindowBorderSize = 0; 421 | style.GrabRounding = 3; 422 | style.FrameRounding = 3; 423 | style.PopupRounding = 4; 424 | style.ChildRounding = 4; 425 | } 426 | 427 | void StyleColorsBlack() { 428 | auto &colors = ImGui::GetStyle().Colors; 429 | colors[ImGuiCol_WindowBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0}; 430 | colors[ImGuiCol_MenuBarBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 431 | 432 | // Border 433 | colors[ImGuiCol_Border] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f}; 434 | colors[ImGuiCol_BorderShadow] = ImVec4{0.0f, 0.0f, 0.0f, 0.24f}; 435 | 436 | // Text 437 | colors[ImGuiCol_Text] = ImVec4{1.0f, 1.0f, 1.0f, 1.0f}; 438 | colors[ImGuiCol_TextDisabled] = ImVec4{0.5f, 0.5f, 0.5f, 1.0f}; 439 | 440 | // Headers 441 | colors[ImGuiCol_Header] = ImVec4{0.13f, 0.13f, 0.17, 1.0f}; 442 | colors[ImGuiCol_HeaderHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f}; 443 | colors[ImGuiCol_HeaderActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 444 | 445 | // Buttons 446 | colors[ImGuiCol_Button] = ImVec4{0.13f, 0.13f, 0.17, 1.0f}; 447 | colors[ImGuiCol_ButtonHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f}; 448 | colors[ImGuiCol_ButtonActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 449 | colors[ImGuiCol_CheckMark] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f}; 450 | 451 | // Popups 452 | colors[ImGuiCol_PopupBg] = ImVec4{0.1f, 0.1f, 0.13f, 0.92f}; 453 | 454 | // Slider 455 | colors[ImGuiCol_SliderGrab] = ImVec4{0.44f, 0.37f, 0.61f, 0.54f}; 456 | colors[ImGuiCol_SliderGrabActive] = ImVec4{0.74f, 0.58f, 0.98f, 0.54f}; 457 | 458 | // Frame BG 459 | colors[ImGuiCol_FrameBg] = ImVec4{0.13f, 0.13, 0.17, 1.0f}; 460 | colors[ImGuiCol_FrameBgHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f}; 461 | colors[ImGuiCol_FrameBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 462 | 463 | // Tabs 464 | colors[ImGuiCol_Tab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 465 | colors[ImGuiCol_TabHovered] = ImVec4{0.24, 0.24f, 0.32f, 1.0f}; 466 | colors[ImGuiCol_TabActive] = ImVec4{0.2f, 0.22f, 0.27f, 1.0f}; 467 | colors[ImGuiCol_TabUnfocused] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 468 | colors[ImGuiCol_TabUnfocusedActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 469 | 470 | // Title 471 | colors[ImGuiCol_TitleBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 472 | colors[ImGuiCol_TitleBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 473 | colors[ImGuiCol_TitleBgCollapsed] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 474 | 475 | // Scrollbar 476 | colors[ImGuiCol_ScrollbarBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0f}; 477 | colors[ImGuiCol_ScrollbarGrab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f}; 478 | colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f}; 479 | colors[ImGuiCol_ScrollbarGrabActive] = ImVec4{0.24f, 0.24f, 0.32f, 1.0f}; 480 | 481 | // Seperator 482 | colors[ImGuiCol_Separator] = ImVec4{0.44f, 0.37f, 0.61f, 1.0f}; 483 | colors[ImGuiCol_SeparatorHovered] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f}; 484 | colors[ImGuiCol_SeparatorActive] = ImVec4{0.84f, 0.58f, 1.0f, 1.0f}; 485 | 486 | // Resize Grip 487 | colors[ImGuiCol_ResizeGrip] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f}; 488 | colors[ImGuiCol_ResizeGripHovered] = ImVec4{0.74f, 0.58f, 0.98f, 0.29f}; 489 | colors[ImGuiCol_ResizeGripActive] = ImVec4{0.84f, 0.58f, 1.0f, 0.29f}; 490 | 491 | auto &style = ImGui::GetStyle(); 492 | style.TabRounding = 4; 493 | style.ScrollbarRounding = 9; 494 | style.WindowBorderSize = 0; 495 | style.GrabRounding = 3; 496 | style.FrameRounding = 3; 497 | style.PopupRounding = 4; 498 | style.ChildRounding = 4; 499 | } 500 | 501 | void Draw() { 502 | ImGui::NewFrame(); 503 | 504 | ImGuiIO& io = ImGui::GetIO(); 505 | ImGui::SetNextWindowSize(io.DisplaySize); 506 | ImGui::SetNextWindowPos(ImVec2(0, 0)); 507 | 508 | if (!ImGui::Begin("Variables", nullptr, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { 509 | ImGui::End(); 510 | return; 511 | } 512 | 513 | if (ImGui::BeginMenuBar()) { 514 | if (ImGui::BeginMenu("File")) { 515 | if (ImGui::MenuItem("Attach with PID", "Ctrl+A")) { 516 | open_pid_popup = true; 517 | } 518 | if (ImGui::BeginMenu("Theme")) { 519 | if (ImGui::MenuItem("Dark", "Ctrl+D")) { 520 | ImGui::StyleColorsDark(); 521 | } 522 | if (ImGui::MenuItem("Light", "Ctrl+L")) { 523 | ImGui::StyleColorsLight(); 524 | } 525 | if (ImGui::MenuItem("Classic", "Ctrl+C")) { 526 | ImGui::StyleColorsClassic(); 527 | } 528 | if (ImGui::MenuItem("Black", "Ctrl+B")) { 529 | StyleColorsBlack(); 530 | } 531 | if (ImGui::MenuItem("Funky", "Ctrl+F")) { 532 | StyleColorsFunky(); 533 | } 534 | ImGui::EndMenu(); 535 | } 536 | ImGui::EndMenu(); 537 | } 538 | ImGui::EndMenuBar(); 539 | } 540 | 541 | ImVec2 center = ImGui::GetMainViewport()->GetCenter(); 542 | ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); 543 | 544 | if (ImGui::BeginPopup("AttachWithPID", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove)) { 545 | static bool attach_failed = false; 546 | 547 | std::string pidInput; 548 | pidInput.reserve(64); 549 | ImGui::Text("PID:"); 550 | ImGui::SameLine(); 551 | ImGui::SetKeyboardFocusHere(); 552 | if (ImGui::InputText("##pid", &pidInput, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CharsDecimal)) { 553 | pid = std::stoull(pidInput); 554 | try { 555 | HandleAttachProcess(); 556 | ImGui::CloseCurrentPopup(); 557 | attach_failed = false; 558 | } catch (const std::exception& e) { 559 | std::cerr << e.what() << std::endl; 560 | attach_failed = true; 561 | } 562 | } 563 | // TODO: progress bar 564 | 565 | if (attach_failed) { 566 | ImGui::BeginDisabled(); 567 | ImGui::TextColored(ImVec4{1.000, 0.353, 0.322, 1.0}, "Error: Could not attach to pid %llu", pid); 568 | ImGui::EndDisabled(); 569 | } 570 | ImGui::EndPopup(); 571 | } 572 | 573 | if (open_pid_popup) { 574 | open_pid_popup = false; 575 | ImGui::OpenPopup("AttachWithPID"); 576 | } 577 | 578 | for (auto& var : variables) { 579 | if (var.IsRoot()) { 580 | DisplayVariable(var); 581 | } 582 | } 583 | 584 | ImGui::End(); 585 | ImGui::Render(); 586 | } 587 | 588 | void UpdateVariableValue(const VariableInfo* var_info) { 589 | auto thread = GetThread(process); 590 | if (!thread) return; 591 | 592 | std::string fully_qualified_name = var_info->GetFullyQualifiedName(); 593 | std::string expression = fully_qualified_name + " = " + var_info->GetFullyQualifiedValue(); 594 | 595 | for (auto& frame : GetFrames(thread)) { 596 | lldb::SBValue var = FindVariableById(frame, var_info->GetRoot().id); 597 | if (!var) continue; 598 | 599 | lldb::SBValue value = frame.EvaluateExpression(expression.c_str()); 600 | if (value && value.GetValue()) return; 601 | } 602 | 603 | std::cerr << "Failed to evaluate " << expression << std::endl; 604 | } 605 | 606 | void HandleLLDBProcessEvents() { 607 | lldb::SBEvent event; 608 | while (listener.PeekAtNextEvent(event)) { 609 | if (lldb::SBProcess::EventIsProcessEvent(event)) { 610 | lldb::StateType state = lldb::SBProcess::GetStateFromEvent(event); 611 | 612 | if (state == lldb::eStateStopped) { 613 | if (!published_changes) { 614 | UpdateVariableValue(current_var_info); 615 | published_changes = true; 616 | } 617 | FetchAllVariables(); 618 | process.Continue(); 619 | } 620 | } 621 | listener.GetNextEvent(event); 622 | } 623 | } 624 | 625 | void HandleKeys() { 626 | if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) { 627 | if (ImGui::IsKeyDown(ImGuiKey_A)) { 628 | open_pid_popup = true; 629 | } else if (ImGui::IsKeyDown(ImGuiKey_D)) { 630 | ImGui::StyleColorsDark(nullptr); 631 | } else if (ImGui::IsKeyDown(ImGuiKey_C)) { 632 | ImGui::StyleColorsClassic(nullptr); 633 | } else if (ImGui::IsKeyDown(ImGuiKey_L)) { 634 | ImGui::StyleColorsLight(nullptr); 635 | } else if (ImGui::IsKeyDown(ImGuiKey_B)) { 636 | StyleColorsBlack(); 637 | } else if (ImGui::IsKeyDown(ImGuiKey_F)) { 638 | StyleColorsFunky(); 639 | } 640 | } else if (ImGui::IsKeyDown(ImGuiKey_S)) { 641 | process.Stop(); 642 | } 643 | } 644 | 645 | void core() { 646 | HandleKeys(); 647 | HandleLLDBProcessEvents(); 648 | Draw(); 649 | } 650 | 651 | void SetupLoop() { 652 | IMGUI_CHECKVERSION(); 653 | ImGui::CreateContext(); 654 | StyleColorsBlack(); 655 | } 656 | 657 | void TearDownDebugger() { 658 | lldb::SBDebugger::Destroy(debugger); 659 | } 660 | 661 | void SetupDebugger() { 662 | setenv("LLDB_DEBUGSERVER_PATH", GetDebugServerPath().c_str(), 1); 663 | lldb::SBDebugger::Initialize(); 664 | debugger = lldb::SBDebugger::Create(); 665 | } 666 | 667 | } 668 | 669 | int main() { 670 | using namespace Hook; 671 | try { 672 | SetupDebugger(); 673 | SetupLoop(); 674 | main_loop(core); 675 | TearDownDebugger(); 676 | } catch (const std::exception& e) { 677 | std::cout << e.what() << std::endl; 678 | } 679 | } 680 | -------------------------------------------------------------------------------- /test/hook.h: -------------------------------------------------------------------------------- 1 | #ifdef HOOK 2 | #define const volatile 3 | #endif -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "hook.h" 5 | 6 | using namespace std::chrono_literals; 7 | 8 | float global_float = 3.14f; 9 | 10 | struct Foo { 11 | int a = 0; 12 | int b = 0; 13 | }; 14 | std::ostream& operator<<(std::ostream& s, const Foo& foo) { 15 | s << "foo: (a: " << foo.a << ", b: " << foo.b << ")"; 16 | return s; 17 | } 18 | 19 | class Bar { 20 | public: 21 | int c = 0; 22 | 23 | private: 24 | Foo foo; 25 | friend std::ostream& operator<<(std::ostream& os, const Bar& obj); 26 | }; 27 | std::ostream& operator<<(std::ostream& s, const Bar& bar) { 28 | s << "bar: (" << bar.c << ", " << bar.foo << ")"; 29 | return s; 30 | } 31 | 32 | void infinite_sleep() { 33 | bool stop = false; 34 | while (!stop) { 35 | std::this_thread::sleep_for(20ms); 36 | } 37 | } 38 | 39 | int main() { 40 | Foo foo; 41 | Bar bar; 42 | bool stop = false; 43 | for (const int i = 0; !stop;) { 44 | infinite_sleep(); 45 | std::cout << "i: " << i << std::endl; 46 | std::cout << foo << std::endl; 47 | std::cout << bar << std::endl; 48 | std::cout << "global_float: " << global_float << std::endl; 49 | } 50 | 51 | std::cout << "exited!" << std::endl; 52 | } -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | g++ -std=c++20 -g -O0 -DHOOK -o test test.cpp 2 | ./test & --------------------------------------------------------------------------------