├── .clang-format ├── .clangd ├── .github └── workflows │ ├── macos.yml │ ├── ubuntu.yml │ └── windows.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── dap ├── CMakeLists.txt ├── Client.cpp ├── Client.hpp ├── ConnectionString.cpp ├── ConnectionString.hpp ├── DAPEvent.cpp ├── DAPEvent.hpp ├── Exception.cpp ├── Exception.hpp ├── JSON.cpp ├── JSON.hpp ├── JsonRPC.cpp ├── JsonRPC.hpp ├── Log.cpp ├── Log.hpp ├── Process.cpp ├── Process.hpp ├── Queue.hpp ├── ServerProtocol.cpp ├── ServerProtocol.hpp ├── Socket.cpp ├── Socket.hpp ├── SocketClient.cpp ├── SocketClient.hpp ├── SocketServer.cpp ├── SocketServer.hpp ├── StringUtils.cpp ├── StringUtils.hpp ├── UnixProcess.cpp ├── UnixProcess.hpp ├── cJSON.cpp ├── cJSON.hpp ├── dap.cpp ├── dap.hpp ├── dap_exports.hpp ├── dapcxx.project ├── linux.cpp └── msw.cpp ├── dbgcli ├── CMakeLists.txt ├── ConsoleApp.cpp ├── ConsoleApp.hpp ├── MainFrame.cpp ├── MainFrame.hpp ├── UI.cpp ├── UI.hpp ├── UI.wxcp ├── UI_dbgcli_bitmaps.cpp ├── dbgcli.project └── resources.rc ├── tests ├── CMakeLists.txt ├── gdbd_tests.cpp ├── tester.cpp └── tester.h ├── wxdap-linux.workspace └── wxdap.workspace /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignEscapedNewlinesLeft: true 5 | AlignTrailingComments: true 6 | AllowAllParametersOfDeclarationOnNextLine: true 7 | AlwaysBreakBeforeMultilineStrings: false 8 | AlwaysBreakTemplateDeclarations: true 9 | BinPackParameters: true 10 | BreakBeforeBraces: Linux 11 | BreakBeforeTernaryOperators: true 12 | BreakConstructorInitializersBeforeComma: true 13 | ColumnLimit: 120 14 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 15 | ConstructorInitializerIndentWidth: 4 16 | ContinuationIndentWidth: 4 17 | Cpp11BracedListStyle: false 18 | DerivePointerAlignment: false 19 | DisableFormat: false 20 | ExperimentalAutoDetectBinPacking: false 21 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 22 | IndentCaseLabels: false 23 | IndentWidth: 4 24 | IndentWrappedFunctionNames: false 25 | KeepEmptyLinesAtTheStartOfBlocks: true 26 | MaxEmptyLinesToKeep: 1 27 | NamespaceIndentation: Inner 28 | ObjCSpaceAfterProperty: true 29 | ObjCSpaceBeforeProtocolList: true 30 | PenaltyBreakBeforeFirstCallParameter: 19 31 | PenaltyBreakComment: 300 32 | PenaltyBreakFirstLessLess: 120 33 | PenaltyBreakString: 1000 34 | PenaltyExcessCharacter: 1000000 35 | PenaltyReturnTypeOnItsOwnLine: 60 36 | PointerAlignment: Left 37 | SpaceBeforeAssignmentOperators: true 38 | SpaceBeforeParens: true 39 | SpaceInEmptyParentheses: false 40 | SpacesBeforeTrailingComments: 1 41 | SpacesInAngles: false 42 | SpacesInContainerLiterals: true 43 | SpacesInCStyleCastParentheses: false 44 | SpacesInParentheses: false 45 | Standard: C++11 46 | TabWidth: 4 47 | UseTab: Never 48 | SortIncludes: true 49 | IncludeBlocks: Regroup -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: 3 | - "-ferror-limit=0" 4 | 5 | --- 6 | 7 | If: 8 | PathMatch: .*\.h 9 | CompileFlags: 10 | Add: 11 | - "-xc++" 12 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: macos 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - 'CMakeLists.txt' 8 | - 'dap/**' 9 | - 'dbgcli/**' 10 | - 'tests/**' 11 | - '.github/workflows/macos.yml' 12 | pull_request: 13 | paths: 14 | - 'CMakeLists.txt' 15 | - 'dap/**' 16 | - 'dbgcli/**' 17 | - 'tests/**' 18 | - '.github/workflows/macos.yml' 19 | 20 | jobs: 21 | macos: 22 | runs-on: macos-latest 23 | 24 | steps: 25 | 26 | # WxWidgets 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | with: 30 | repository: wxWidgets/wxWidgets 31 | path: wxWidgets 32 | ref: v3.2.4 33 | submodules: recursive 34 | 35 | - name: Build and install wxWidgets 36 | run: | 37 | mkdir wxWidgets/build-release 38 | cd wxWidgets/build-release 39 | ../configure --enable-shared --enable-monolithic --with-osx_cocoa CXX='clang++ -std=c++17 -stdlib=libc++ -I../src/tiff/libtiff' CC=clang --disable-debug --disable-mediactrl --enable-stl 40 | make -j$(sysctl -n hw.physicalcpu) 41 | sudo make install 42 | 43 | # wxdap 44 | - name: Checkout wxdap 45 | uses: actions/checkout@v4 46 | with: 47 | submodules: recursive 48 | 49 | - name: Build wxdap 50 | run: | 51 | mkdir build-release 52 | cd build-release 53 | cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=1 54 | cmake --build . -j $(sysctl -n hw.physicalcpu) 55 | 56 | - name: test 57 | run: | 58 | cd build-release 59 | ctest --output-on-failure 60 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: ubuntu 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - 'CMakeLists.txt' 8 | - 'dap/**' 9 | - 'dbgcli/**' 10 | - 'tests/**' 11 | - '.github/workflows/ubuntu.yml' 12 | pull_request: 13 | paths: 14 | - 'CMakeLists.txt' 15 | - 'dap/**' 16 | - 'dbgcli/**' 17 | - 'tests/**' 18 | - '.github/workflows/ubuntu.yml' 19 | 20 | jobs: 21 | linux: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | # WxWidgets 26 | - name: install dependencies for wxWidgets 27 | run: sudo apt-get install build-essential clang-format-12 cmake git libedit-dev libgtk-3-dev libhunspell-dev libsqlite3-dev libssh-dev pkg-config xterm 28 | 29 | - name: Checkout WxWidgets 30 | uses: actions/checkout@v4 31 | with: 32 | repository: wxWidgets/wxWidgets 33 | path: wxWidgets 34 | ref: v3.2.4 35 | submodules: recursive 36 | 37 | - name: build and install wxWidgets 38 | run: | 39 | mkdir -p wxWidgets/build-release 40 | cd wxWidgets/build-release 41 | ../configure --disable-debug_flag --with-gtk=3 --enable-stl 42 | make -j$(nproc) && sudo make install 43 | 44 | # wxdap 45 | - name: Checkout wxdap 46 | uses: actions/checkout@v4 47 | 48 | - name: build wxdap 49 | run: | 50 | mkdir build-release 51 | cd build-release 52 | cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=1 53 | cmake --build . -j $(nproc) 54 | 55 | - name: test 56 | run: | 57 | cd build-release 58 | ctest --output-on-failure 59 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: windows 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - 'CMakeLists.txt' 8 | - 'dap/**' 9 | - 'dbgcli/**' 10 | - 'tests/**' 11 | - '.github/workflows/windows.yml' 12 | pull_request: 13 | paths: 14 | - 'CMakeLists.txt' 15 | - 'dap/**' 16 | - 'dbgcli/**' 17 | - 'tests/**' 18 | - '.github/workflows/windows.yml' 19 | 20 | jobs: 21 | windows: 22 | runs-on: windows-latest 23 | 24 | steps: 25 | 26 | # WxWidgets 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | with: 30 | repository: wxWidgets/wxWidgets 31 | path: wxWidgets 32 | ref: v3.2.4 33 | submodules: recursive 34 | 35 | - name: build and install wxWidgets 36 | run: | 37 | mkdir wxWidgets/build-release 38 | cd wxWidgets/build-release 39 | cmake .. -DCMAKE_BUILD_TYPE=Release -DwxBUILD_DEBUG_LEVEL=1 -DwxBUILD_MONOLITHIC=0 -DwxBUILD_SAMPLES=SOME -DwxUSE_STL=1 40 | cmake --build . --config Release && cmake --build . -t install 41 | 42 | # wxdap 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | with: 46 | submodules: recursive 47 | 48 | - name: build wxdap 49 | run: | 50 | mkdir build-release 51 | cd build-release 52 | cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=1 53 | cmake --build . --config Release 54 | 55 | # Upload artefact 56 | - name: artifact 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: codelite 60 | path: | 61 | build-release/dap/Release/**.* 62 | build-release/dbgcli/Release/**.* 63 | build-release/tests/Release/**.* 64 | 65 | - name: artifact # Cannot be with above artifact as no common path :-( 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: wxwidgets 69 | path: | 70 | c:/Program Files (x86)/wxWidgets/**/* 71 | 72 | #- name: test # DLL issue? 73 | # shell: bash 74 | # run: | 75 | # cd build-release 76 | # cp dap/Release/dapcxx.dll tests/Release/ 77 | # cp "/c/Program Files (x86)/wxWidgets/lib/vc_x64_dll/*.dll" tests/Release/ 78 | # ctest -C Release --output-on-failure 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | *.mk 3 | compile_commands.json 4 | build-release/* 5 | builde-debug/* 6 | *.xrc -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(wxdap) 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS 1) 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | find_package(wxWidgets COMPONENTS adv base core xml xrc net stc richtext REQUIRED) 9 | include( "${wxWidgets_USE_FILE}" ) 10 | 11 | add_subdirectory(dap) 12 | add_subdirectory(dbgcli) 13 | 14 | include(CTest) 15 | if(BUILD_TESTING) 16 | add_subdirectory(tests) 17 | endif() 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Eran Ifrah 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Mac-OS-badge](https://github.com/eranif/wxdap/actions/workflows/macos.yml/badge.svg) 2 | ![Unix-badge](https://github.com/eranif/wxdap/actions/workflows/ubuntu.yml/badge.svg) 3 | ![windows-badge](https://github.com/eranif/wxdap/actions/workflows/windows.yml/badge.svg) 4 | 5 | # wxdap 6 | Provide a wxWidgets library for implementing DAP (Debug Adapter Protocol) clients 7 | 8 | ## Implemented 9 | 10 | ### Requests 11 | 12 | #### Mandatory requests 13 | 14 | - [x] Connect 15 | - [x] Initialize 16 | - [x] Launch - This launch request is sent from the client to the debug adapter to start the debuggee 17 | - [x] SetBreakpointsFile - Sets multiple breakpoints for a single source and clears all previous breakpoints in that source 18 | - [x] Threads - The request retrieves a list of all threads. 19 | - [x] Scopes - The request returns the variable scopes for a given stackframe ID (this does not return the variables themselves, but only their groups, like "Locals", "Registers" etc) 20 | - [x] GetFrames - return list of frames for a given thread ID 21 | - [x] Continue - continue the execution 22 | - [x] Next - executes one step for the specified thread (with custom granularity: line/statement/instruction) 23 | - [X] StepIn - resumes the given thread to step into a function/method and allows all other threads to run freely by resuming them 24 | - [X] StepOut - The request resumes the given thread to step out (return) from a function/method and allows all other threads to run freely by resuming them 25 | - [X] Pause - pause the debugger execution 26 | - [X] BreakpointLocations - returns all possible locations for source breakpoints in a given range 27 | - [X] SetFunctionBreakpoints - Replaces all existing function breakpoints with new function breakpoints 28 | - [X] Variables - return list of variables 29 | - [X] EvaluateExpression - asks the debugger to evaluate an expression 30 | 31 | #### Lower priority requests 32 | 33 | - [X] NextInstruction - executes one instruction for the specified thread 34 | - [ ] Goto - sets the location where the debuggee will continue to run. his makes it possible to skip the execution of 35 | code or to execute code again 36 | - [ ] ReadMemory - Reads bytes from memory at the provided location 37 | - [ ] Disassemble - Disassembles code stored at the provided location 38 | 39 | ### Events 40 | 41 | - [x] Stopped - the execution stopped due to ... (breakpoint hit, exception, step etc) 42 | - [x] Terminated - the debugging session terminated 43 | - [x] Exited - the debuggee process exited 44 | - [x] Initialized - dap server is initialized 45 | - [x] Process - the debuggee process started 46 | - [x] Output - The event indicates that the target has produced some output 47 | - [x] Continued - The debugger continued 48 | - [x] Breakpoint - a breakpoint state changed 49 | -------------------------------------------------------------------------------- /dap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(dapcxx) 3 | 4 | include_directories(${CMAKE_SOURCE_DIR}) 5 | FILE(GLOB SRCS "*.cpp") 6 | 7 | # find wxWidgets once 8 | if (MINGW) 9 | if (NOT wxWidgets_USE_FILE) 10 | if (NOT WXWIN) 11 | message(FATAL_ERROR "Missing -DWXWIN=") 12 | endif() 13 | if (NOT WXCFG) 14 | set(WXCFG "clang_x64_dll/mswu") 15 | endif() 16 | 17 | message(STATUS "WXWIN is set to ${WXWIN}") 18 | message(STATUS "WXCFG is set to ${WXCFG}") 19 | 20 | execute_process(COMMAND 21 | ${CL_WX_CONFIG} --cmake --prefix=${WXWIN} --wxcfg=${WXCFG} 22 | OUTPUT_VARIABLE wxWidgets_USE_FILE 23 | OUTPUT_STRIP_TRAILING_WHITESPACE) 24 | message(STATUS "wxWidgets_USE_FILE is set to: ${wxWidgets_USE_FILE}") 25 | endif() 26 | else() 27 | find_package(wxWidgets COMPONENTS adv aui base core html propgrid xml xrc net stc ribbon richtext REQUIRED) 28 | endif() 29 | 30 | include( "${wxWidgets_USE_FILE}" ) 31 | 32 | set( ADDITIONAL_LIBRARIES "" ) 33 | 34 | if(MINGW) 35 | set(ADDITIONAL_LIBRARIES "-lws2_32") 36 | endif() 37 | 38 | add_library(dapcxx SHARED ${SRCS}) 39 | target_compile_definitions(dapcxx PRIVATE WXMAKINGDLL_DAP) 40 | target_compile_definitions(dapcxx INTERFACE WXUSINGDLL_DAP) 41 | target_link_libraries(dapcxx ${wxWidgets_LIBRARIES} ${ADDITIONAL_LIBRARIES}) 42 | set_property(TARGET dapcxx PROPERTY CXX_STANDARD 17) 43 | set_property(TARGET dapcxx PROPERTY CXX_EXTENSIONS OFF) 44 | STRING(REPLACE "-std=c++11" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 45 | 46 | -------------------------------------------------------------------------------- /dap/Client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JsonRPC.hpp" 4 | #include "Process.hpp" 5 | #include "Queue.hpp" 6 | #include "Socket.hpp" 7 | #include "dap_exports.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace dap 16 | { 17 | /// The transport class used to communicate with the DAP server 18 | /// Note about thread safety: 19 | /// This class must be stateless and thread-safe since it is 20 | /// used by both an internal thread and the caller thread 21 | class WXDLLIMPEXP_DAP Transport 22 | { 23 | public: 24 | Transport() {} 25 | virtual ~Transport() {} 26 | 27 | /** 28 | * @brief return from the network with a given timeout 29 | * @returns true on success, false in case of an error. True is also returned when timeout occurs, check the buffer 30 | * length if it is 0, timeout occurred 31 | */ 32 | virtual bool Read(std::string& WXUNUSED(buffer), int msTimeout) = 0; 33 | 34 | /** 35 | * @brief send data over the network 36 | * @return number of bytes written 37 | */ 38 | virtual size_t Send(const std::string& WXUNUSED(buffer)) = 0; 39 | }; 40 | 41 | /// simple socket implementation for Socket 42 | class WXDLLIMPEXP_DAP SocketTransport : public Transport 43 | { 44 | Socket* m_socket = nullptr; 45 | 46 | public: 47 | SocketTransport(); 48 | virtual ~SocketTransport(); 49 | 50 | bool Read(std::string& buffer, int msTimeout) override; 51 | size_t Send(const std::string& buffer) override; 52 | 53 | // socket specific 54 | bool Connect(const std::string& connection_string, int timeoutSeconds); 55 | }; 56 | 57 | /// simple socket implementation for Socket 58 | class WXDLLIMPEXP_DAP StdoutTransport : public Transport 59 | { 60 | public: 61 | StdoutTransport(); 62 | virtual ~StdoutTransport(); 63 | 64 | bool Read(std::string& buffer, int msTimeout) override; 65 | size_t Send(const std::string& buffer) override; 66 | 67 | /// Execute the DAP server and connect to it by redirecting stdin/out 68 | bool Execute(const std::vector& command, const wxString& workingDirectory = {}); 69 | 70 | protected: 71 | bool IsAlive() const; 72 | 73 | private: 74 | Process* m_process = nullptr; 75 | }; 76 | 77 | typedef std::function source_loaded_cb; 78 | typedef std::function evaluate_cb; 79 | 80 | class WXDLLIMPEXP_DAP Client : public wxEvtHandler 81 | { 82 | enum eFeatures { 83 | supportsConfigurationDoneRequest = (1 << 0), 84 | supportsFunctionBreakpoints = (1 << 1), 85 | supportsConditionalBreakpoints = (1 << 2), 86 | supportsHitConditionalBreakpoints = (1 << 3), 87 | supportsEvaluateForHovers = (1 << 4), 88 | supportsStepBack = (1 << 5), 89 | supportsSetVariable = (1 << 6), 90 | supportsRestartFrame = (1 << 7), 91 | supportsGotoTargetsRequest = (1 << 8), 92 | supportsStepInTargetsRequest = (1 << 9), 93 | supportsCompletionsRequest = (1 << 10), 94 | supportsModulesRequest = (1 << 11), 95 | supportsRestartRequest = (1 << 12), 96 | supportsExceptionOptions = (1 << 13), 97 | supportsValueFormattingOptions = (1 << 14), 98 | supportsExceptionInfoRequest = (1 << 15), 99 | supportTerminateDebuggee = (1 << 16), 100 | supportsDelayedStackTraceLoading = (1 << 17), 101 | supportsLoadedSourcesRequest = (1 << 18), 102 | supportsProgressReporting = (1 << 19), 103 | supportsRunInTerminalRequest = (1 << 20), 104 | supportsBreakpointLocationsRequest = (1 << 21), 105 | }; 106 | 107 | protected: 108 | enum class eHandshakeState { kNotPerformed, kInProgress, kCompleted }; 109 | Transport* m_transport = nullptr; 110 | dap::JsonRPC m_rpc; 111 | std::atomic_bool m_shutdown; 112 | std::atomic_bool m_terminated; 113 | std::thread* m_readerThread = nullptr; 114 | size_t m_requestSeuqnce = 0; 115 | eHandshakeState m_handshake_state = eHandshakeState::kNotPerformed; 116 | int m_active_thread_id = wxNOT_FOUND; 117 | bool m_can_interact = false; 118 | std::unordered_map m_requestIdToFilepath; 119 | size_t m_features = 0; 120 | 121 | /// set this to true if you wish to receive (in addition to the regular events) 122 | /// logging events that can be used to trace the protocol exchange between 123 | /// wxdap and the dap-server 124 | bool m_wants_log_events = false; 125 | 126 | /// the ID of threads that called GetFrames() 127 | std::vector m_get_frames_queue; 128 | std::vector m_get_scopes_queue; 129 | std::vector> m_get_variables_queue; 130 | std::vector m_load_sources_queue; 131 | std::vector m_evaluate_queue; 132 | std::vector m_source_breakpoints_queue; 133 | std::unordered_map m_in_flight_requests; 134 | 135 | protected: 136 | bool IsSupported(eFeatures feature) const { return m_features & feature; } 137 | bool SendRequest(dap::Request* request); 138 | void HandleSourceResponse(Json json); 139 | void HandleEvaluateResponse(Json json); 140 | /// Return the originating request for `response` 141 | /// Might return null 142 | dap::Request* GetOriginatingRequest(dap::Response* response); 143 | 144 | protected: 145 | void SendDAPEvent(wxEventType type, ProtocolMessage* dap_message, Json json, Request* req); 146 | 147 | /** 148 | * @brief we maintain a reader thread that is responsible for reading 149 | * from the socket and post events when new messages arrived 150 | */ 151 | void StartReaderThread(); 152 | 153 | /** 154 | * @brief stop the reader thread if needed and clean any resources allocated 155 | */ 156 | void StopReaderThread(); 157 | 158 | /** 159 | * @brief this callback is called by the reader thread whenever data arrives on the socket 160 | */ 161 | void OnDataRead(const std::string& buffer); 162 | 163 | /** 164 | * @brief lost connection to the DAP server 165 | */ 166 | void OnConnectionError(); 167 | 168 | /** 169 | * @brief handle Json payload received from the DAP server 170 | * @param json 171 | */ 172 | void OnMessage(Json json); 173 | static void StaticOnDataRead(Json json, wxObject* o); 174 | 175 | public: 176 | Client(); 177 | virtual ~Client(); 178 | 179 | /** 180 | * @brief enable/disable logging events 181 | */ 182 | void SetWantsLogEvents(bool b) { m_wants_log_events = b; } 183 | 184 | template 185 | RequestType* MakeRequest() 186 | { 187 | RequestType* req = new RequestType(); 188 | req->seq = GetNextSequence(); 189 | return req; 190 | } 191 | 192 | /** 193 | * @brief send back a response to the dap server 194 | * should be used when receiving a reverse request from the dap server 195 | */ 196 | bool SendResponse(dap::Response& response); 197 | 198 | /** 199 | * @brief return the next message sequence 200 | */ 201 | size_t GetNextSequence() 202 | { 203 | m_requestSeuqnce++; 204 | return m_requestSeuqnce; 205 | } 206 | 207 | /** 208 | * @brief set the transport for this client. The `Client` takes 209 | * the ownership for this pointer and will free it when its done with it. 210 | * This means that transport **must** be allocated on the heap 211 | */ 212 | void SetTransport(dap::Transport* transport); 213 | 214 | /** 215 | * @brief can we interact with the debugger? 216 | */ 217 | bool CanInteract() const { return m_can_interact; } 218 | 219 | /** 220 | * @brief return the currently active thread ID 221 | */ 222 | int GetActiveThreadId() const { return m_active_thread_id; } 223 | 224 | /** 225 | * @brief initiate the handshake between the server and the client 226 | */ 227 | void Initialize(const dap::InitializeRequestArguments* initArgs = nullptr); 228 | 229 | /** 230 | * @brief are we still connected? 231 | */ 232 | bool IsConnected() const; 233 | 234 | /** 235 | * @brief set multiple breakpoints in a source file 236 | */ 237 | void SetBreakpointsFile(const wxString& file, const std::vector& lines); 238 | 239 | /** 240 | * @brief set breakpoint on a function 241 | */ 242 | void SetFunctionBreakpoints(const std::vector& breakpoints); 243 | 244 | /** 245 | * @brief tell the debugger that we are done and ready to start the main loop 246 | */ 247 | void ConfigurationDone(); 248 | 249 | /** 250 | * @brief start the debuggee 251 | * @param cmd the cmd in [0] is the program, the remainder are the arguments 252 | * @param workingDirectory the debuggee working directory 253 | * @param env extra environment variable to pass to the debuggee 254 | */ 255 | void Launch(std::vector&& cmd, const wxString& workingDirectory = wxEmptyString, 256 | const dap::Environment& env = {}); 257 | /** 258 | * @brief attach to dap server 259 | */ 260 | void Attach(int pid = wxNOT_FOUND, const std::vector& arguments = {}); 261 | 262 | /** 263 | * @brief ask for list of threads 264 | */ 265 | void GetThreads(); 266 | 267 | /** 268 | * @brief get list of frames for a given thread ID 269 | * @param threadId if wxNOT_FOUND is specified, use the thread ID as returned by GetActiveThreadId() 270 | * @param starting_frame 271 | * @param frame_count number of frames to return 272 | */ 273 | void GetFrames(int threadId = wxNOT_FOUND, int starting_frame = 0, int frame_count = 0); 274 | 275 | /** 276 | * @brief continue execution 277 | */ 278 | void Continue(int threadId = wxNOT_FOUND, bool all_threads = true); 279 | 280 | /** 281 | * @brief The request executes one step (in the given granularity) for the specified thread and allows all other 282 | * threads to run freely by resuming them 283 | * @param threadId execute one step for this thread. If wxNOT_FOUND is passed, use the thread returned by 284 | * GetActiveThreadId() 285 | * @param singleThread If this optional flag is true, execution is resumed only for the thread 286 | */ 287 | void Next(int threadId = wxNOT_FOUND, bool singleThread = true, 288 | SteppingGranularity granularity = SteppingGranularity::LINE); 289 | 290 | /** 291 | * @brief return the variable scopes for a given frame 292 | * @param frameId 293 | */ 294 | void GetScopes(int frameId); 295 | 296 | /** 297 | * @brief reset the session and clear all states 298 | */ 299 | void Reset(); 300 | 301 | /** 302 | * @brief step into function 303 | */ 304 | void StepIn(int threadId = wxNOT_FOUND, bool singleThread = true); 305 | 306 | /** 307 | * @brief step out of a function 308 | */ 309 | void StepOut(int threadId = wxNOT_FOUND, bool singleThread = true); 310 | 311 | /** 312 | * @brief return the list of all children variables for `variablesReference` 313 | * @param variablesReference the parent ID 314 | * @param context the context of variablesReference 315 | * @param count number of children. If count 0, all variables are returned 316 | */ 317 | void GetChildrenVariables(int variablesReference, EvaluateContext context = EvaluateContext::VARIABLES, 318 | size_t count = 10, ValueDisplayFormat format = ValueDisplayFormat::NATIVE); 319 | 320 | /** 321 | * @brief The request suspends the debuggee. 322 | * @param threadId 323 | */ 324 | void Pause(int threadId = wxNOT_FOUND); 325 | 326 | /** 327 | * @brief request list of all breakpoints in a file 328 | */ 329 | void BreakpointLocations(const wxString& filepath, int start_line, int end_line); 330 | 331 | /** 332 | * @brief request to load a source and execute callback once the source is loaded. If the `source` contains 333 | * a local path, the callback is called immediately without accessing the server, if the `source` contains 334 | * sourceReference, then this function queues a load source request to the server and executes the callback once 335 | * the source is loaded 336 | */ 337 | bool LoadSource(const dap::Source& source, source_loaded_cb callback); 338 | 339 | /** 340 | * @brief evaluate an expression. This method uses callback instead of event since a context is required 341 | * (e.g. the caller want to associate the evaluated expression with the expression) 342 | * 343 | * evaluate_cb(bool success, const wxString& result, const wxString& type, int variablesReference): 344 | * - success: the evaluate succeeded 345 | * - result: The result of the evaluate request 346 | * - type: The type of the evaluate result 347 | * - variablesReference: If variablesReference is > 0, the evaluate result is structured and its 348 | * children can be retrieved by passing variablesReference to the 349 | * VariablesRequest 350 | */ 351 | void EvaluateExpression(const wxString& expression, int frameId, EvaluateContext context, evaluate_cb callback, 352 | ValueDisplayFormat format = ValueDisplayFormat::NATIVE); 353 | }; 354 | 355 | }; // namespace dap 356 | -------------------------------------------------------------------------------- /dap/ConnectionString.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionString.hpp" 2 | 3 | #include "StringUtils.hpp" 4 | 5 | namespace dap 6 | { 7 | ConnectionString::ConnectionString(const wxString& connectionString) 8 | : m_port(-1) 9 | , m_isOK(false) 10 | { 11 | DoParse(connectionString); 12 | } 13 | 14 | ConnectionString::~ConnectionString() {} 15 | 16 | void ConnectionString::DoParse(const wxString& connectionString) 17 | { 18 | m_isOK = false; // default 19 | // get the protocol part 20 | wxString protocol = DapStringUtils::BeforeFirst(connectionString, ':'); 21 | if(protocol == "tcp") { 22 | m_protocol = kTcp; 23 | } else if(protocol == "unix") { 24 | #ifdef _WIN32 25 | return; 26 | #endif 27 | m_protocol = kUnixLocalSocket; 28 | } else { 29 | return; 30 | } 31 | 32 | wxString address = DapStringUtils::AfterFirst(connectionString, ':'); 33 | address = address.substr(2); 34 | if(m_protocol == kUnixLocalSocket) { 35 | // The rest is the file path 36 | m_path = address; 37 | m_isOK = !m_path.empty(); 38 | } else { 39 | // we now expect host[:port] 40 | m_host = DapStringUtils::BeforeFirst(address, ':'); 41 | wxString port = DapStringUtils::AfterFirst(address, ':'); 42 | if(!port.empty()) { 43 | m_port = atol(port.c_str()); 44 | } 45 | m_isOK = !m_host.empty() && (m_port != -1); 46 | } 47 | } 48 | }; // namespace dap -------------------------------------------------------------------------------- /dap/ConnectionString.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CLCONNECTIONSTRING_H 2 | #define CLCONNECTIONSTRING_H 3 | 4 | #include "dap_exports.hpp" 5 | 6 | #include 7 | 8 | namespace dap 9 | { 10 | class WXDLLIMPEXP_DAP ConnectionString 11 | { 12 | public: 13 | enum eProtocol { 14 | kTcp, 15 | kUnixLocalSocket, 16 | }; 17 | 18 | protected: 19 | eProtocol m_protocol; 20 | wxString m_host; 21 | long m_port; 22 | wxString m_path; 23 | bool m_isOK; 24 | 25 | protected: 26 | void DoParse(const wxString& connectionString); 27 | 28 | public: 29 | ConnectionString(const wxString& connectionString); 30 | ~ConnectionString(); 31 | 32 | void SetHost(const wxString& host) { this->m_host = host; } 33 | void SetIsOK(bool isOK) { this->m_isOK = isOK; } 34 | void SetPath(const wxString& path) { this->m_path = path; } 35 | void SetPort(long port) { this->m_port = port; } 36 | void SetProtocol(const eProtocol& protocol) { this->m_protocol = protocol; } 37 | const wxString& GetHost() const { return m_host; } 38 | bool IsOK() const { return m_isOK; } 39 | const wxString& GetPath() const { return m_path; } 40 | long GetPort() const { return m_port; } 41 | const eProtocol& GetProtocol() const { return m_protocol; } 42 | }; 43 | }; // namespace dap 44 | #endif // CLCONNECTIONSTRING_H 45 | -------------------------------------------------------------------------------- /dap/DAPEvent.cpp: -------------------------------------------------------------------------------- 1 | #include "DAPEvent.hpp" 2 | 3 | wxDEFINE_EVENT(wxEVT_DAP_LOG_EVENT, DAPEvent); 4 | 5 | wxDEFINE_EVENT(wxEVT_DAP_LOST_CONNECTION, DAPEvent); 6 | 7 | wxDEFINE_EVENT(wxEVT_DAP_INITIALIZE_RESPONSE, DAPEvent); 8 | wxDEFINE_EVENT(wxEVT_DAP_STACKTRACE_RESPONSE, DAPEvent); 9 | wxDEFINE_EVENT(wxEVT_DAP_SCOPES_RESPONSE, DAPEvent); 10 | wxDEFINE_EVENT(wxEVT_DAP_VARIABLES_RESPONSE, DAPEvent); 11 | wxDEFINE_EVENT(wxEVT_DAP_BREAKPOINT_LOCATIONS_RESPONSE, DAPEvent); 12 | wxDEFINE_EVENT(wxEVT_DAP_CONFIGURARIONE_DONE_RESPONSE, DAPEvent); 13 | wxDEFINE_EVENT(wxEVT_DAP_SET_SOURCE_BREAKPOINT_RESPONSE, DAPEvent); 14 | wxDEFINE_EVENT(wxEVT_DAP_SET_FUNCTION_BREAKPOINT_RESPONSE, DAPEvent); 15 | wxDEFINE_EVENT(wxEVT_DAP_LAUNCH_RESPONSE, DAPEvent); 16 | wxDEFINE_EVENT(wxEVT_DAP_THREADS_RESPONSE, DAPEvent); 17 | 18 | wxDEFINE_EVENT(wxEVT_DAP_RUN_IN_TERMINAL_REQUEST, DAPEvent); 19 | 20 | wxDEFINE_EVENT(wxEVT_DAP_STOPPED_EVENT, DAPEvent); 21 | wxDEFINE_EVENT(wxEVT_DAP_STOPPED_ON_ENTRY_EVENT, DAPEvent); 22 | wxDEFINE_EVENT(wxEVT_DAP_PROCESS_EVENT, DAPEvent); 23 | wxDEFINE_EVENT(wxEVT_DAP_EXITED_EVENT, DAPEvent); 24 | wxDEFINE_EVENT(wxEVT_DAP_TERMINATED_EVENT, DAPEvent); 25 | wxDEFINE_EVENT(wxEVT_DAP_INITIALIZED_EVENT, DAPEvent); 26 | wxDEFINE_EVENT(wxEVT_DAP_OUTPUT_EVENT, DAPEvent); 27 | wxDEFINE_EVENT(wxEVT_DAP_BREAKPOINT_EVENT, DAPEvent); 28 | wxDEFINE_EVENT(wxEVT_DAP_CONTINUED_EVENT, DAPEvent); 29 | wxDEFINE_EVENT(wxEVT_DAP_MODULE_EVENT, DAPEvent); 30 | wxDEFINE_EVENT(wxEVT_DAP_DEBUGPYWAITINGFORSERVER_EVENT, DAPEvent); 31 | 32 | DAPEvent::DAPEvent(wxEventType commandType, int winid) 33 | : wxCommandEvent(commandType, winid) 34 | { 35 | } 36 | DAPEvent::~DAPEvent() {} 37 | DAPEvent::DAPEvent(const DAPEvent& event) { *this = event; } 38 | DAPEvent& DAPEvent::operator=(const DAPEvent& src) 39 | { 40 | m_object = src.m_object; 41 | m_originatingRequest = src.m_originatingRequest; 42 | return *this; 43 | } 44 | 45 | wxEvent* DAPEvent::Clone() const { return new DAPEvent(*this); } 46 | dap::Event* DAPEvent::GetDapEvent() const 47 | { 48 | if(!m_object) { 49 | return nullptr; 50 | } 51 | return m_object->As(); 52 | } 53 | 54 | dap::Response* DAPEvent::GetDapResponse() const 55 | { 56 | if(!m_object) { 57 | return nullptr; 58 | } 59 | return m_object->As(); 60 | } 61 | 62 | dap::Request* DAPEvent::GetDapRequest() const 63 | { 64 | if(!m_object) { 65 | return nullptr; 66 | } 67 | return m_object->As(); 68 | } 69 | -------------------------------------------------------------------------------- /dap/DAPEvent.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DAPEVENT_HPP 2 | #define DAPEVENT_HPP 3 | 4 | #include "dap.hpp" 5 | #include "dap_exports.hpp" 6 | 7 | #include 8 | 9 | class WXDLLIMPEXP_DAP DAPEvent : public wxCommandEvent 10 | { 11 | protected: 12 | std::shared_ptr m_object; 13 | std::shared_ptr 14 | m_originatingRequest; // for events that holds a response, this pointer will contain the originating request 15 | 16 | public: 17 | DAPEvent(wxEventType commandType = wxEVT_NULL, int winid = 0); 18 | DAPEvent(const DAPEvent& event); 19 | DAPEvent& operator=(const DAPEvent& src); 20 | virtual ~DAPEvent(); 21 | 22 | void SetAnyObject(std::shared_ptr any) { m_object = any; } 23 | void SetOriginatingRequest(std::shared_ptr req) { m_originatingRequest = req; } 24 | std::shared_ptr GetOriginatingRequest() { return m_originatingRequest; } 25 | 26 | wxEvent* Clone() const override; 27 | dap::Event* GetDapEvent() const; 28 | dap::Response* GetDapResponse() const; 29 | dap::Request* GetDapRequest() const; 30 | }; 31 | 32 | typedef void (wxEvtHandler::*DAPEventFunction)(DAPEvent&); 33 | #define DAPEventHandler(func) wxEVENT_HANDLER_CAST(DAPEventFunction, func) 34 | 35 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_LOG_EVENT, DAPEvent); 36 | 37 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_LOST_CONNECTION, DAPEvent); 38 | 39 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_INITIALIZE_RESPONSE, DAPEvent); 40 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_STACKTRACE_RESPONSE, DAPEvent); 41 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_SCOPES_RESPONSE, DAPEvent); 42 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_VARIABLES_RESPONSE, DAPEvent); 43 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_SET_SOURCE_BREAKPOINT_RESPONSE, DAPEvent); 44 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_SET_FUNCTION_BREAKPOINT_RESPONSE, DAPEvent); 45 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_BREAKPOINT_LOCATIONS_RESPONSE, DAPEvent); 46 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_CONFIGURARIONE_DONE_RESPONSE, DAPEvent); 47 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_LAUNCH_RESPONSE, DAPEvent); 48 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_THREADS_RESPONSE, DAPEvent); 49 | 50 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_RUN_IN_TERMINAL_REQUEST, DAPEvent); 51 | 52 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_STOPPED_EVENT, DAPEvent); 53 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_STOPPED_ON_ENTRY_EVENT, DAPEvent); 54 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_PROCESS_EVENT, DAPEvent); 55 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_EXITED_EVENT, DAPEvent); 56 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_TERMINATED_EVENT, DAPEvent); 57 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_INITIALIZED_EVENT, DAPEvent); 58 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_OUTPUT_EVENT, DAPEvent); 59 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_BREAKPOINT_EVENT, DAPEvent); 60 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_CONTINUED_EVENT, DAPEvent); 61 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_MODULE_EVENT, DAPEvent); 62 | wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_DAP, wxEVT_DAP_DEBUGPYWAITINGFORSERVER_EVENT, DAPEvent); 63 | 64 | #endif // DAPEVENT_HPP 65 | -------------------------------------------------------------------------------- /dap/Exception.cpp: -------------------------------------------------------------------------------- 1 | #include "Exception.hpp" 2 | 3 | #include "StringUtils.hpp" 4 | 5 | dap::Exception::Exception(const wxString& what) 6 | : m_what(what) 7 | { 8 | DapStringUtils::Trim(m_what); 9 | } 10 | 11 | dap::Exception::~Exception() {} 12 | 13 | const wxString& dap::Exception::What() const { return m_what; } 14 | -------------------------------------------------------------------------------- /dap/Exception.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DAPEXCEPTION_HPP 2 | #define DAPEXCEPTION_HPP 3 | 4 | #include "dap_exports.hpp" 5 | 6 | #include 7 | 8 | namespace dap 9 | { 10 | class WXDLLIMPEXP_DAP Exception 11 | { 12 | wxString m_what; 13 | 14 | public: 15 | Exception(const wxString& what); 16 | virtual ~Exception(); 17 | 18 | const wxString& What() const; 19 | }; 20 | 21 | }; // namespace dap 22 | #endif // EXCEPTION_HPP 23 | -------------------------------------------------------------------------------- /dap/JSON.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Eran Ifrah 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #include "JSON.hpp" 24 | 25 | namespace dap 26 | { 27 | #define CHECK_IS_CONTAINER() \ 28 | if(!m_cjson) { \ 29 | return Json(nullptr); \ 30 | } \ 31 | if(!IsArray() && !IsObject()) { \ 32 | return Json(m_cjson); \ 33 | } 34 | 35 | Json::Json(cJsonDap* ptr) 36 | : m_cjson(ptr) 37 | { 38 | } 39 | 40 | void Json::DecRef() 41 | { 42 | if(m_refCount) { 43 | (*m_refCount)--; 44 | if(m_refCount->load() == 0) { 45 | // Releas the underlying pointer 46 | Delete(); 47 | delete m_refCount; 48 | m_refCount = nullptr; 49 | } 50 | } 51 | } 52 | 53 | void Json::IncRef() 54 | { 55 | if(m_refCount) { 56 | (*m_refCount)++; 57 | } 58 | } 59 | 60 | void Json::Manage() 61 | { 62 | if(!IsManaged()) { 63 | m_refCount = new std::atomic_int; 64 | m_refCount->store(1); 65 | } 66 | } 67 | 68 | void Json::UnManage() 69 | { 70 | if(m_refCount) { 71 | delete m_refCount; 72 | m_refCount = nullptr; 73 | } 74 | } 75 | 76 | Json& Json::operator=(const Json& other) 77 | { 78 | if(this == &other) { 79 | return *this; 80 | } 81 | DecRef(); 82 | m_refCount = other.m_refCount; 83 | m_cjson = other.m_cjson; 84 | // Increase the ref count if needed 85 | IncRef(); 86 | return *this; 87 | } 88 | 89 | Json::Json(const Json& other) { *this = other; } 90 | 91 | Json::~Json() 92 | { 93 | DecRef(); 94 | m_cjson = nullptr; 95 | } 96 | 97 | Json Json::operator[](const wxString& index) const 98 | { 99 | if(m_cjson == nullptr) { 100 | return Json(nullptr); 101 | } 102 | 103 | cJsonDap* child = m_cjson->child; 104 | while(child) { 105 | if(child->string && strcmp(child->string, index.c_str()) == 0) { 106 | return Json(child); 107 | } 108 | child = child->next; 109 | } 110 | return Json(nullptr); 111 | } 112 | 113 | Json Json::AddItem(const wxString& name, cJsonDap* item) 114 | { 115 | if(m_cjson == nullptr) { 116 | cJSON_Delete(item); 117 | return Json(nullptr); 118 | } 119 | if(m_cjson->type != cJsonDap_Array && m_cjson->type != cJsonDap_Object) { 120 | cJSON_Delete(item); 121 | return Json(nullptr); 122 | } 123 | if(m_cjson->type == cJsonDap_Array) { 124 | cJSON_AddItemToArray(m_cjson, item); 125 | } else { 126 | cJSON_AddItemToObject(m_cjson, name.c_str(), item); 127 | } 128 | return Json(item); 129 | } 130 | 131 | wxString Json::ToString(bool pretty) const 132 | { 133 | if(m_cjson == nullptr) { 134 | return ""; 135 | } 136 | char* c = pretty ? cJSON_Print(m_cjson) : cJSON_PrintUnformatted(m_cjson); 137 | wxString str(c); 138 | free(c); 139 | return str; 140 | } 141 | 142 | Json Json::CreateArray() 143 | { 144 | Json arr(cJSON_CreateArray()); 145 | arr.Manage(); 146 | return arr; 147 | } 148 | 149 | Json Json::CreateObject() 150 | { 151 | Json obj(cJSON_CreateObject()); 152 | obj.Manage(); 153 | return obj; 154 | } 155 | 156 | void Json::Delete() 157 | { 158 | // Delete only when owned 159 | if(m_cjson) { 160 | cJSON_Delete(m_cjson); 161 | m_cjson = nullptr; 162 | } 163 | } 164 | 165 | Json Json::Add(const char* name, const wxString& value) { return Add(name, value.mb_str(wxConvUTF8).data()); } 166 | 167 | Json Json::Add(const char* name, const char* value) 168 | { 169 | CHECK_IS_CONTAINER(); 170 | if(IsObject()) { 171 | cJSON_AddItemToObject(m_cjson, name, cJSON_CreateString(value)); 172 | } else { 173 | // Array 174 | cJSON_AddItemToArray(m_cjson, cJSON_CreateString(value)); 175 | } 176 | return Json(m_cjson); 177 | } 178 | 179 | Json Json::Add(const char* name, double value) 180 | { 181 | CHECK_IS_CONTAINER(); 182 | if(IsObject()) { 183 | cJSON_AddItemToObject(m_cjson, name, cJSON_CreateNumber(value)); 184 | } else { 185 | // Array 186 | cJSON_AddItemToArray(m_cjson, cJSON_CreateNumber(value)); 187 | } 188 | return Json(m_cjson); 189 | } 190 | 191 | Json Json::Add(const char* name, bool value) 192 | { 193 | CHECK_IS_CONTAINER(); 194 | if(IsObject()) { 195 | cJSON_AddItemToObject(m_cjson, name, cJSON_CreateBool(value ? 1 : 0)); 196 | } else { 197 | // Array 198 | cJSON_AddItemToArray(m_cjson, cJSON_CreateBool(value ? 1 : 0)); 199 | } 200 | return Json(m_cjson); 201 | } 202 | 203 | wxString Json::GetString(const wxString& defaultVaule) const 204 | { 205 | if(!m_cjson || m_cjson->type != cJsonDap_String) { 206 | return defaultVaule; 207 | } 208 | return m_cjson->valuestring; 209 | } 210 | 211 | double Json::GetNumber(double defaultVaule) const 212 | { 213 | if(!m_cjson || m_cjson->type != cJsonDap_Number) { 214 | return defaultVaule; 215 | } 216 | return m_cjson->valuedouble; 217 | } 218 | 219 | int Json::GetInteger(int defaultVaule) const 220 | { 221 | if(!m_cjson || m_cjson->type != cJsonDap_Number) { 222 | return defaultVaule; 223 | } 224 | return m_cjson->valueint; 225 | } 226 | 227 | bool Json::GetBool(bool defaultVaule) const 228 | { 229 | if(!m_cjson || (m_cjson->type != cJsonDap_True && m_cjson != cJsonDap_False)) { 230 | return defaultVaule; 231 | } 232 | return m_cjson->type == cJsonDap_True ? true : false; 233 | } 234 | 235 | Json Json::operator[](size_t index) const 236 | { 237 | if(index >= GetCount()) { 238 | return Json(nullptr); 239 | } 240 | cJsonDap* child = m_cjson->child; 241 | size_t where = 0; 242 | while(where != index) { 243 | child = child->next; 244 | ++where; 245 | } 246 | return Json(child); 247 | } 248 | 249 | size_t Json::GetCount() const 250 | { 251 | if(m_cjson == nullptr) { 252 | return 0; 253 | } 254 | size_t count(0); 255 | cJsonDap* child = m_cjson->child; 256 | while(child) { 257 | ++count; 258 | child = child->next; 259 | } 260 | return count; 261 | } 262 | 263 | Json Json::AddObject(const char* name, const Json& obj) 264 | { 265 | if(!m_cjson) { 266 | return obj; 267 | } 268 | cJSON_AddItemToObject(m_cjson, name, obj.m_cjson); 269 | if(obj.IsManaged()) { 270 | Json& o = const_cast(obj); 271 | o.UnManage(); // We take ownership 272 | } 273 | return obj; 274 | } 275 | 276 | Json Json::Add(const char* name, const std::vector& value) 277 | { 278 | auto a = AddArray(name); 279 | for(const auto& s : value) { 280 | a.Add(s); 281 | } 282 | return a; 283 | } 284 | 285 | std::vector Json::GetStringArray() const 286 | { 287 | if(!m_cjson || m_cjson->type != cJsonDap_Array) { 288 | return {}; 289 | } 290 | std::vector arr; 291 | size_t count = GetCount(); 292 | arr.reserve(count); 293 | for(size_t i = 0; i < count; ++i) { 294 | arr.push_back((*this)[i].GetString()); 295 | } 296 | return arr; 297 | } 298 | 299 | Json Json::Add(const char* name, const Json& value) 300 | { 301 | CHECK_IS_CONTAINER(); 302 | if(IsObject()) { 303 | return AddObject(name, value); 304 | } else { 305 | if(value.IsManaged()) { 306 | Json& o = const_cast(value); 307 | o.UnManage(); // We take ownership 308 | } 309 | cJSON_AddItemToArray(m_cjson, value.m_cjson); 310 | return value; 311 | } 312 | } 313 | 314 | Json Json::Parse(const wxString& source) 315 | { 316 | Json json(cJSON_Parse(source.c_str())); 317 | json.Manage(); 318 | return json; 319 | } 320 | } // namespace dap -------------------------------------------------------------------------------- /dap/JSON.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 Eran Ifrah 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef DAPJSON_HPP 24 | #define DAPJSON_HPP 25 | 26 | #include "cJSON.hpp" 27 | #include "dap_exports.hpp" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | namespace dap 38 | { 39 | struct WXDLLIMPEXP_DAP Json { 40 | cJsonDap* m_cjson = nullptr; 41 | std::atomic_int* m_refCount = nullptr; 42 | 43 | private: 44 | Json(cJsonDap* ptr); 45 | Json AddItem(const wxString& name, cJsonDap* item); 46 | 47 | void DecRef(); 48 | void IncRef(); 49 | void Manage(); 50 | void UnManage(); 51 | void Delete(); 52 | bool IsManaged() const { return m_refCount != nullptr; } 53 | 54 | public: 55 | ~Json(); 56 | Json() {} 57 | 58 | bool IsArray() const { return m_cjson && m_cjson->type == cJsonDap_Array; } 59 | bool IsObject() const { return m_cjson && m_cjson->type == cJsonDap_Object; } 60 | 61 | Json& operator=(const Json& other); 62 | Json(const Json& other); 63 | 64 | /** 65 | * @brief return the property name 66 | */ 67 | wxString GetName() const 68 | { 69 | if(m_cjson == nullptr || !m_cjson->string) { 70 | return ""; 71 | } 72 | return wxString(m_cjson->string); 73 | } 74 | 75 | /** 76 | * @brief return true if this object is not null 77 | */ 78 | bool IsOK() const { return m_cjson != nullptr; } 79 | 80 | /** 81 | * @brief create new TOP level array 82 | * If you need to array to an existing Json, 83 | * call AddArray() 84 | */ 85 | static Json CreateArray(); 86 | 87 | /** 88 | * @brief create new TOP level object 89 | * If you need to object to an existing Json, 90 | * call AddObject() 91 | */ 92 | static Json CreateObject(); 93 | 94 | /** 95 | * @brief create Json from wxString buffer 96 | */ 97 | static Json Parse(const wxString& source); 98 | 99 | /** 100 | * @brief object property access 101 | */ 102 | Json operator[](const wxString& index) const; 103 | 104 | /** 105 | * @brief index access 106 | */ 107 | Json operator[](size_t index) const; 108 | 109 | /** 110 | * @brief get number of children 111 | */ 112 | size_t GetCount() const; 113 | 114 | /** 115 | * @brief add array to this Json. 116 | * @param name the name of the array. If this Json is of type array 117 | * the name is ignored 118 | * @return the newly added array. Check for IsOK() 119 | */ 120 | Json AddArray(const wxString& name = "") { return AddItem(name, cJSON_CreateArray()); } 121 | 122 | /** 123 | * @brief create and add object to this Json. 124 | * @param name the name of the array. If this Json is of type array 125 | * the name is ignored 126 | * @return the newly added object. Check for IsOK() 127 | */ 128 | Json AddObject(const wxString& name = "") { return AddItem(name, cJSON_CreateObject()); } 129 | 130 | /** 131 | * @brief add object to this Json. 132 | * @return the newly added object 133 | */ 134 | Json AddObject(const wxString& name, const Json& obj) { return AddObject(name.mb_str(wxConvUTF8).data(), obj); } 135 | Json AddObject(const char* name, const Json& obj); 136 | 137 | /** 138 | * @brief return value as wxString 139 | */ 140 | wxString GetString(const wxString& defaultVaule = "") const; 141 | 142 | /** 143 | * @brief return value as number 144 | */ 145 | double GetNumber(double defaultVaule = -1) const; 146 | 147 | /** 148 | * @brief return value as number 149 | */ 150 | int GetInteger(int defaultVaule = -1) const; 151 | 152 | /** 153 | * @brief return value as boolean 154 | */ 155 | bool GetBool(bool defaultVaule = false) const; 156 | 157 | /** 158 | * @brief return wxString array 159 | */ 160 | std::vector GetStringArray() const; 161 | 162 | /** 163 | * @brief return wxString representation for this object 164 | */ 165 | wxString ToString(bool pretty = true) const; 166 | 167 | // Add properties to container (can be object or array) 168 | Json Add(const wxString& name, const wxString& value) { return Add(name.mb_str(wxConvUTF8).data(), value); } 169 | Json Add(const wxString& name, const std::vector& value) 170 | { 171 | return Add(name.mb_str(wxConvUTF8).data(), value); 172 | } 173 | Json Add(const wxString& name, const Json& value) { return Add(name.mb_str(wxConvUTF8).data(), value); } 174 | Json Add(const wxString& name, const char* value) { return Add(name.mb_str(wxConvUTF8).data(), value); } 175 | Json Add(const wxString& name, double value) { return Add(name.mb_str(wxConvUTF8).data(), value); } 176 | Json Add(const wxString& name, int value) { return Add(name.mb_str(wxConvUTF8).data(), (double)value); } 177 | Json Add(const wxString& name, long value) { return Add(name.mb_str(wxConvUTF8).data(), (double)value); } 178 | Json Add(const wxString& name, size_t value) { return Add(name.mb_str(wxConvUTF8).data(), (double)value); } 179 | Json Add(const wxString& name, bool value) { return Add(name.mb_str(wxConvUTF8).data(), value); } 180 | 181 | Json Add(const char* name, const wxString& value); 182 | Json Add(const char* name, const char* value); 183 | Json Add(const char* name, bool value); 184 | Json Add(const char* name, double value); 185 | Json Add(const char* name, const std::vector& value); 186 | Json Add(const char* name, const Json& value); 187 | Json Add(const char* name, long value) { return Add(name, (double)value); } 188 | Json Add(const char* name, size_t value) { return Add(name, (double)value); } 189 | Json Add(const char* name, int value) { return Add(name, (double)value); } 190 | 191 | // Same as the above but without providing 'name' 192 | // useful for array 193 | Json Add(const wxString& value) { return Add("", value); } 194 | Json Add(const char* value) { return Add("", value); } 195 | Json Add(double value) { return Add("", value); } 196 | Json Add(long value) { return Add("", (double)value); } 197 | Json Add(int value) { return Add("", (double)value); } 198 | Json Add(size_t value) { return Add("", (double)value); } 199 | Json Add(bool value) { return Add("", value); } 200 | Json Add(const Json& value) { return Add("", value); } 201 | }; 202 | 203 | } // namespace dap 204 | #endif // JSON_HPP 205 | -------------------------------------------------------------------------------- /dap/JsonRPC.cpp: -------------------------------------------------------------------------------- 1 | #include "JsonRPC.hpp" 2 | 3 | #include "Exception.hpp" 4 | #include "Log.hpp" 5 | #include "SocketServer.hpp" 6 | #include "StringUtils.hpp" 7 | 8 | #include 9 | 10 | dap::JsonRPC::JsonRPC() {} 11 | 12 | dap::JsonRPC::~JsonRPC() {} 13 | 14 | dap::Json dap::JsonRPC::DoProcessBuffer() 15 | { 16 | if (m_buffer.empty()) { 17 | return {}; 18 | } 19 | 20 | // Find the "Content-Length:" string 21 | std::unordered_map headers; 22 | int headerSize = ReadHeaders(headers); 23 | if (headerSize == -1) { 24 | return {}; 25 | } 26 | 27 | LOG_DEBUG() << "Headers:" << headers.size() << endl; 28 | for (const auto& [k, v] : headers) { 29 | LOG_DEBUG() << k << ":" << v << endl; 30 | } 31 | 32 | // We got the headers, check to see that we have the "Content-Length" one 33 | auto iter = headers.find("Content-Length"); 34 | if (iter == headers.end()) { 35 | // this is a problem in the protocol. If we restore this section back to the buffer 36 | // we will simply stuck with it again later. So we remove it and return null 37 | m_buffer.erase(headerSize); 38 | LOG_ERROR() << "ERROR: Read complete header section. But no Content-Length header was found" << endl; 39 | return {}; 40 | } 41 | 42 | std::string contentLength = iter->second; 43 | long msglen = std::atol(contentLength.c_str()); 44 | if (msglen <= 0) { 45 | LOG_ERROR() << "ERROR: Invalid Content-Length header value: 0 or lower than 0" << endl; 46 | return {}; 47 | } 48 | 49 | long buflen = m_buffer.length(); 50 | if ((headerSize + msglen) > buflen) { 51 | LOG_INFO() << "Not enough buffer" << endl; 52 | // not enough buffer 53 | return {}; 54 | } 55 | 56 | // Read the payload into a separate buffer and remove the full message 57 | // from the m_buffer member 58 | std::string payload(m_buffer.begin() + headerSize, m_buffer.begin() + headerSize + msglen); 59 | m_buffer.erase(0, headerSize + msglen); 60 | return Json::Parse(payload); 61 | } 62 | 63 | void dap::JsonRPC::ProcessBuffer(std::function callback, wxObject* o) 64 | { 65 | Json json = DoProcessBuffer(); 66 | while (json.IsOK()) { 67 | if (json.IsOK()) { 68 | callback(json, o); 69 | } 70 | json = DoProcessBuffer(); 71 | } 72 | } 73 | 74 | int dap::JsonRPC::ReadHeaders(unordered_map& headers) 75 | { 76 | size_t where = m_buffer.find("\r\n\r\n"); 77 | if (where == wxString::npos) { 78 | return -1; 79 | } 80 | std::string headerSection = m_buffer.substr(0, where); // excluding the "\r\n\r\n" 81 | std::vector lines = DapStringUtils::Split(headerSection, "\n"); 82 | for (std::string& header : lines) { 83 | DapStringUtils::Trim(header); 84 | std::string name = DapStringUtils::BeforeFirst(header, ':'); 85 | std::string value = DapStringUtils::AfterFirst(header, ':'); 86 | headers.insert({ DapStringUtils::Trim(name), DapStringUtils::Trim(value) }); 87 | } 88 | // return the headers section + the separator 89 | return (where + 4); 90 | } 91 | 92 | void dap::JsonRPC::SetBuffer(const std::string& buffer) { m_buffer = buffer; } 93 | 94 | void dap::JsonRPC::AppendBuffer(const std::string& buffer) { m_buffer.append(buffer); } 95 | -------------------------------------------------------------------------------- /dap/JsonRPC.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DAPJSONRPC_HPP 2 | #define DAPJSONRPC_HPP 3 | 4 | #include "Exception.hpp" 5 | #include "Queue.hpp" 6 | #include "dap.hpp" 7 | #include "dap_exports.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace dap 16 | { 17 | class WXDLLIMPEXP_DAP JsonRPC 18 | { 19 | protected: 20 | std::string m_buffer; 21 | 22 | protected: 23 | int ReadHeaders(std::unordered_map& headers); 24 | Json DoProcessBuffer(); 25 | 26 | public: 27 | JsonRPC(); 28 | ~JsonRPC(); 29 | 30 | /** 31 | * @brief provide input buffer. 32 | * NOTE: this method is intended for testing purposes and should not be used 33 | */ 34 | void SetBuffer(const std::string& buffer); 35 | 36 | /** 37 | * @brief append content to the existing buffer 38 | */ 39 | void AppendBuffer(const std::string& buffer); 40 | 41 | /** 42 | * @brief Check if we have a complete Json message in the internal buffer and invoke callback 43 | * If successful, callback is called. Note that it will get called as long there are complete messages in the 44 | * internal buffer 45 | * @param callback 46 | * @param o user object that is sent back to the callback 47 | */ 48 | void ProcessBuffer(std::function callback, wxObject* o); 49 | 50 | /** 51 | * @brief send protocol message over the network 52 | * TransportPtr must have a Send(const std::string&) method 53 | */ 54 | template 55 | void Send(ProtocolMessage& msg, TransportPtr conn) const 56 | { 57 | if (!conn) { 58 | throw Exception("Invalid connection"); 59 | } 60 | std::string network_buffer; 61 | std::string payload = msg.ToString().ToStdString(); 62 | network_buffer = "Content-Length: "; 63 | network_buffer += std::to_string(payload.length()); 64 | network_buffer += "\r\n\r\n"; 65 | network_buffer += payload; 66 | conn->Send(network_buffer); 67 | } 68 | 69 | /** 70 | * @brief send protocol message over the network 71 | * TransportPtr must have a Send(const wxString&) method 72 | */ 73 | template 74 | void Send(ProtocolMessage::Ptr_t msg, TransportPtr conn) const 75 | { 76 | if (!msg) { 77 | throw Exception("Unable to send empty message"); 78 | } 79 | if (!conn) { 80 | throw Exception("Invalid connection"); 81 | } 82 | Send(*msg.get(), conn); 83 | } 84 | }; 85 | }; // namespace dap 86 | #endif // JSONRPC_HPP 87 | -------------------------------------------------------------------------------- /dap/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.hpp" 2 | 3 | #include "StringUtils.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace dap 10 | { 11 | int Log::m_verbosity = Log::Error; 12 | wxString Log::m_logfile; 13 | bool Log::m_useStdout = false; 14 | 15 | static const wxString GREEN = "\x1b[32m"; 16 | static const wxString RED = "\x1b[31m"; 17 | static const wxString YELLOW = "\x1b[93m"; 18 | static const wxString CYAN = "\x1b[96m"; 19 | static const wxString WHITE = "\x1b[37m"; 20 | static const wxString COLOUR_END = "\x1b[0m"; 21 | static const wxString EMPTY_STR = ""; 22 | 23 | // Needed for Windows 24 | #ifdef _WIN32 25 | #include 26 | #endif 27 | 28 | #ifdef _WIN32 29 | // Some old MinGW/CYGWIN distributions don't define this: 30 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 31 | #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 32 | #endif 33 | 34 | static DWORD initMode = 0; 35 | static void SetupConsole() 36 | { 37 | DWORD outMode = 0; 38 | HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); 39 | 40 | if(stdoutHandle == INVALID_HANDLE_VALUE) { 41 | return; 42 | } 43 | 44 | if(!GetConsoleMode(stdoutHandle, &outMode)) { 45 | return; 46 | } 47 | initMode = outMode; 48 | // Enable ANSI escape codes 49 | outMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 50 | 51 | if(!SetConsoleMode(stdoutHandle, outMode)) { 52 | return; 53 | } 54 | } 55 | static void ResetConsole() 56 | { 57 | HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); 58 | if(stdoutHandle != INVALID_HANDLE_VALUE) { 59 | SetConsoleMode(stdoutHandle, initMode); 60 | } 61 | } 62 | #else 63 | static void SetupConsole() {} 64 | static void ResetConsole() {} 65 | #endif 66 | 67 | Log::Log(int requestedVerbo) 68 | : m_requestedLogLevel(requestedVerbo) 69 | , m_fp(nullptr) 70 | { 71 | SetupConsole(); 72 | } 73 | 74 | Log::~Log() 75 | { 76 | // flush any content that remain 77 | Flush(); 78 | ResetConsole(); 79 | } 80 | 81 | void Log::AddLogLine(const wxString& msg, int verbosity) 82 | { 83 | if(msg.empty()) { 84 | return; 85 | } 86 | if((m_verbosity >= verbosity)) { 87 | wxString formattedMsg = Prefix(verbosity); 88 | m_buffer << formattedMsg << " " << msg; 89 | m_buffer << "\n"; 90 | } 91 | } 92 | 93 | void Log::SetVerbosity(int level) 94 | { 95 | if(level > Log::Warning) { 96 | LOG_SYSTEM() << Log::GetVerbosityAsString(level) << wxString(""); 97 | } 98 | m_verbosity = level; 99 | } 100 | 101 | int Log::GetVerbosityAsNumber(const wxString& verbosity) 102 | { 103 | if(verbosity == "Debug") { 104 | return Log::Dbg; 105 | 106 | } else if(verbosity == "Error") { 107 | return Log::Error; 108 | 109 | } else if(verbosity == "Warning") { 110 | return Log::Warning; 111 | 112 | } else if(verbosity == "System") { 113 | return Log::System; 114 | 115 | } else if(verbosity == "Developer") { 116 | return Log::Developer; 117 | 118 | } else if(verbosity == "Info") { 119 | return Log::Info; 120 | } else { 121 | return Log::Error; 122 | } 123 | } 124 | 125 | wxString Log::GetVerbosityAsString(int verbosity) 126 | { 127 | switch(verbosity) { 128 | case Log::Dbg: 129 | return "Debug"; 130 | 131 | case Log::Error: 132 | return "Error"; 133 | 134 | case Log::Warning: 135 | return "Warning"; 136 | 137 | case Log::Developer: 138 | return "Developer"; 139 | 140 | case Log::Info: 141 | return "Info"; 142 | 143 | default: 144 | return "Error"; 145 | } 146 | } 147 | 148 | void Log::SetVerbosity(const wxString& verbosity) { SetVerbosity(GetVerbosityAsNumber(verbosity)); } 149 | 150 | void Log::OpenLog(const wxString& fullpath, int verbosity) 151 | { 152 | m_logfile = fullpath; 153 | m_verbosity = verbosity; 154 | m_useStdout = false; 155 | } 156 | 157 | void Log::OpenStdout(int verbosity) 158 | { 159 | m_logfile.clear(); 160 | m_useStdout = true; 161 | m_verbosity = verbosity; 162 | } 163 | 164 | void Log::Flush() 165 | { 166 | if(m_buffer.empty()) { 167 | return; 168 | } 169 | 170 | if(m_useStdout) { 171 | m_fp = stdout; 172 | } 173 | 174 | if(!m_fp) { 175 | m_fp = fopen(m_logfile.c_str(), "a+"); 176 | } 177 | 178 | if(m_fp) { 179 | wxFprintf(m_fp, "%s\n", m_buffer); 180 | // Dont close stdout 181 | if(!m_useStdout) { 182 | fclose(m_fp); 183 | } 184 | m_fp = nullptr; 185 | } 186 | m_buffer.clear(); 187 | } 188 | 189 | wxString Log::Prefix(int verbosity) 190 | { 191 | if(verbosity <= m_verbosity) { 192 | auto start = std::chrono::system_clock::now(); 193 | auto as_time_t = std::chrono::system_clock::to_time_t(start); 194 | wxString timeString = ctime(&as_time_t); 195 | DapStringUtils::Trim(timeString); 196 | 197 | std::stringstream prefix; 198 | switch(verbosity) { 199 | case Info: 200 | prefix << "[" << timeString << "] " << GetColour(verbosity) << " [ INFO ]" << GetColourEnd(); 201 | break; 202 | 203 | case System: 204 | prefix << "[" << timeString << "] " << GetColour(verbosity) << " [ SYSTEM ]" << GetColourEnd(); 205 | break; 206 | 207 | case Error: 208 | prefix << "[" << timeString << "] " << GetColour(verbosity) << " [ ERROR ]" << GetColourEnd(); 209 | break; 210 | 211 | case Warning: 212 | prefix << "[" << timeString << "] " << GetColour(verbosity) << " [ WARNING ]" << GetColourEnd(); 213 | break; 214 | 215 | case Dbg: 216 | prefix << "[" << timeString << "] " << GetColour(verbosity) << " [ DEBUG ]" << GetColourEnd(); 217 | break; 218 | 219 | case Developer: 220 | prefix << "[" << timeString << "] " << GetColour(verbosity) << " [ TRACE ]" << GetColourEnd(); 221 | break; 222 | } 223 | 224 | prefix << " "; 225 | return prefix.str(); 226 | } else { 227 | return ""; 228 | } 229 | } 230 | 231 | const wxString& Log::GetColour(int verbo) 232 | { 233 | if(!m_useStdout) { 234 | return EMPTY_STR; 235 | } 236 | switch(verbo) { 237 | case Info: 238 | return GREEN; 239 | case System: 240 | return CYAN; 241 | case Error: 242 | return RED; 243 | case Warning: 244 | return YELLOW; 245 | case Dbg: 246 | return CYAN; 247 | default: 248 | return WHITE; 249 | } 250 | } 251 | 252 | const wxString& Log::GetColourEnd() 253 | { 254 | if(!m_useStdout) { 255 | return EMPTY_STR; 256 | } else { 257 | return COLOUR_END; 258 | } 259 | } 260 | }; // namespace dap -------------------------------------------------------------------------------- /dap/Log.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DAPLOG_HPP 2 | #define DAPLOG_HPP 3 | 4 | #include "dap_exports.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // manipulator function 12 | class Log; 13 | namespace dap 14 | { 15 | class WXDLLIMPEXP_DAP Log 16 | { 17 | public: 18 | enum eLogVerbosity { System = -1, Error, Warning, Info, Dbg, Developer }; 19 | 20 | protected: 21 | static int m_verbosity; 22 | static wxString m_logfile; 23 | static bool m_useStdout; 24 | int m_requestedLogLevel = Error; 25 | FILE* m_fp = nullptr; 26 | wxString m_buffer; 27 | 28 | protected: 29 | static const wxString& GetColour(int verbo); 30 | static const wxString& GetColourEnd(); 31 | 32 | public: 33 | Log(int requestedVerbo); 34 | ~Log(); 35 | 36 | /** 37 | * @brief return the internal stream buffer 38 | */ 39 | wxString& GetStream() { return m_buffer; } 40 | 41 | Log& SetRequestedLogLevel(int level) 42 | { 43 | m_requestedLogLevel = level; 44 | return *this; 45 | } 46 | 47 | int GetRequestedLogLevel() const { return m_requestedLogLevel; } 48 | 49 | /** 50 | * @brief create log entry prefix 51 | */ 52 | static wxString Prefix(int verbosity); 53 | 54 | void AddLogLine(const wxString& msg, int verbosity); 55 | static void SetVerbosity(int level); 56 | 57 | // Set the verbosity as wxString 58 | static void SetVerbosity(const wxString& verbosity); 59 | 60 | /** 61 | * @brief open the log file 62 | */ 63 | static void OpenLog(const wxString& fullpath, int verbosity); 64 | /** 65 | * @brief open stdout as the log stream 66 | */ 67 | static void OpenStdout(int verbosity); 68 | 69 | // Various util methods 70 | static wxString GetVerbosityAsString(int verbosity); 71 | static int GetVerbosityAsNumber(const wxString& verbosity); 72 | 73 | inline Log& Append(const std::vector& arr, int level) 74 | { 75 | if(arr.empty()) { 76 | return *this; 77 | } 78 | wxString str; 79 | str += "["; 80 | for(auto s : arr) { 81 | str += s; 82 | str += ", "; 83 | } 84 | str.RemoveLast(); 85 | str.RemoveLast(); 86 | str += "]"; 87 | Append(str, GetRequestedLogLevel()); 88 | return *this; 89 | } 90 | 91 | inline Log& operator<<(const wxString& str) 92 | { 93 | if(GetRequestedLogLevel() > m_verbosity) { 94 | return *this; 95 | } 96 | if(!m_buffer.empty()) { 97 | m_buffer << " "; 98 | } 99 | m_buffer << str; 100 | return *this; 101 | } 102 | 103 | /** 104 | * @brief append any type to the buffer, take log level into consideration 105 | */ 106 | template 107 | Log& Append(const T& elem, int level) 108 | { 109 | if(level > m_verbosity) { 110 | return *this; 111 | } 112 | if(!m_buffer.empty()) { 113 | m_buffer << " "; 114 | } 115 | m_buffer << elem; 116 | return *this; 117 | } 118 | 119 | /** 120 | * @brief flush the logger content 121 | */ 122 | void Flush(); 123 | }; 124 | 125 | inline Log& endl(Log& d) 126 | { 127 | d.Flush(); 128 | return d; 129 | } 130 | 131 | typedef Log& (*LogFunction)(Log&); 132 | inline Log& operator<<(Log& logger, const LogFunction&) 133 | { 134 | logger.Flush(); 135 | return logger; 136 | } 137 | 138 | template 139 | Log& operator<<(Log& logger, const T& obj) 140 | { 141 | logger.Append(obj, logger.GetRequestedLogLevel()); 142 | return logger; 143 | } 144 | 145 | // New API 146 | #define LOG_DEBUG() dap::Log(dap::Log::Dbg) << dap::Log::Prefix(dap::Log::Dbg) 147 | #define LOG_DEBUG1() dap::Log(dap::Log::Developer) << dap::Log::Prefix(dap::Log::Developer) 148 | #define LOG_ERROR() dap::Log(dap::Log::Error) << dap::Log::Prefix(dap::Log::Error) 149 | #define LOG_WARNING() dap::Log(dap::Log::Warning) << dap::Log::Prefix(dap::Log::Warning) 150 | #define LOG_SYSTEM() dap::Log(dap::Log::System) << dap::Log::Prefix(dap::Log::System) 151 | #define LOG_INFO() dap::Log(dap::Log::Info) << dap::Log::Prefix(dap::Log::Info) 152 | }; // namespace dap 153 | #endif // LOG_HPP 154 | -------------------------------------------------------------------------------- /dap/Process.cpp: -------------------------------------------------------------------------------- 1 | #include "Process.hpp" 2 | 3 | #include "Log.hpp" 4 | 5 | void dap::Process::StartThreads() 6 | { 7 | m_shutdown.store(false); 8 | m_readerThread = new std::thread( 9 | [](dap::Process* process, Queue& outq, Queue& errq, std::atomic_bool& shutdown) { 10 | while (!shutdown.load()) { 11 | std::string stdoutBuff; 12 | std::string stderrBuff; 13 | bool readSuccess = process->DoRead(stdoutBuff, stderrBuff); 14 | bool readSomething = (!stdoutBuff.empty() || !stderrBuff.empty()); 15 | if (readSomething && readSuccess) { 16 | if (!stdoutBuff.empty()) { 17 | outq.push(stdoutBuff); 18 | } 19 | if (!stderrBuff.empty()) { 20 | errq.push(stderrBuff); 21 | } 22 | } else { 23 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); 24 | } 25 | } 26 | LOG_ERROR() << "Going down"; 27 | }, 28 | this, std::ref(m_stdoutQueue), std::ref(m_stderrQueue), std::ref(m_shutdown)); 29 | 30 | m_isAliveThread = new std::thread( 31 | [](dap::Process* process, std::atomic_bool& shutdown) { 32 | while (process->IsAlive() && !shutdown.load()) { 33 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 34 | } 35 | // if we reached here, we the process terminated, fire event, set the shutdown flag 36 | // and exit 37 | shutdown.store(true); 38 | LOG_ERROR() << "Process terminated." << endl; 39 | }, 40 | this, std::ref(m_shutdown)); 41 | } 42 | 43 | dap::Process::~Process() {} 44 | 45 | void dap::Process::Cleanup() 46 | { 47 | m_shutdown = true; 48 | if (m_readerThread) { 49 | m_readerThread->join(); 50 | } 51 | 52 | if (m_isAliveThread) { 53 | m_isAliveThread->join(); 54 | } 55 | wxDELETE(m_readerThread); 56 | wxDELETE(m_isAliveThread); 57 | m_shutdown = false; 58 | } 59 | 60 | std::optional dap::Process::ReadStdout(int timeout_ms) 61 | { 62 | return m_stdoutQueue.pop(std::chrono::milliseconds(timeout_ms)); 63 | } 64 | 65 | std::optional dap::Process::ReadStderr(int timeout_ms) 66 | { 67 | return m_stderrQueue.pop(std::chrono::milliseconds(timeout_ms)); 68 | } 69 | -------------------------------------------------------------------------------- /dap/Process.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_H__ 2 | #define PROCESS_H__ 3 | 4 | #include "Queue.hpp" 5 | #include "dap_exports.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace dap 14 | { 15 | class WXDLLIMPEXP_DAP Process 16 | { 17 | public: 18 | /** 19 | * @brief launch a background thread that will perform the reading from the process 20 | */ 21 | void StartThreads(); 22 | 23 | Process() {} 24 | virtual ~Process(); 25 | 26 | virtual bool Write(const std::string& str) = 0; 27 | virtual bool WriteLn(const std::string& str) = 0; 28 | virtual bool IsAlive() const = 0; 29 | virtual void Terminate() = 0; 30 | virtual void Cleanup(); 31 | 32 | void SetProcessId(int processId) { this->m_processId = processId; } 33 | int GetProcessId() const { return m_processId; } 34 | 35 | std::optional ReadStdout(int timeout_ms); 36 | std::optional ReadStderr(int timeout_ms); 37 | 38 | protected: 39 | /** 40 | * @brief implement the actual read call. This method is not accessible outside of this class 41 | */ 42 | virtual bool DoRead(std::string& str, std::string& err_buff) = 0; 43 | 44 | private: 45 | std::thread* m_readerThread = nullptr; 46 | std::thread* m_isAliveThread = nullptr; 47 | std::atomic_bool m_shutdown; 48 | int m_processId = wxNOT_FOUND; 49 | Queue m_stdoutQueue; 50 | Queue m_stderrQueue; 51 | }; 52 | 53 | /** 54 | * @brief Create process and return the handle to it 55 | * @param cmd process command 56 | * @param workingDir process's working directory 57 | * @return pointer to Process object 58 | */ 59 | WXDLLIMPEXP_DAP Process* ExecuteProcess(const wxString& cmd, // Command Line 60 | const wxString& workingDir = "."); 61 | 62 | }; // namespace dap 63 | #endif // PROCESS_H__ 64 | -------------------------------------------------------------------------------- /dap/Queue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef QUEUE_HPP 2 | #define QUEUE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace dap 10 | { 11 | template 12 | class Queue 13 | { 14 | std::vector Q; 15 | std::mutex mutex_lock; 16 | std::condition_variable cv; 17 | 18 | public: 19 | bool empty() const { return Q.empty(); } 20 | 21 | void push(T o) 22 | { 23 | std::unique_lock locker(mutex_lock); 24 | Q.emplace_back(o); 25 | cv.notify_all(); 26 | } 27 | 28 | std::optional pop(const std::chrono::milliseconds& ms) 29 | { 30 | std::unique_lock locker(mutex_lock); 31 | if (cv.wait_for(locker, ms, [this]() { return !Q.empty(); })) { 32 | if (Q.empty()) { 33 | // spuriously wakeup? 34 | return {}; 35 | } 36 | // get the first item from the list 37 | T o = (*Q.begin()); 38 | Q.erase(Q.begin()); 39 | return o; 40 | } 41 | return {}; 42 | } 43 | }; 44 | }; // namespace dap 45 | #endif // QUEUE_HPP -------------------------------------------------------------------------------- /dap/ServerProtocol.cpp: -------------------------------------------------------------------------------- 1 | #include "ServerProtocol.hpp" 2 | 3 | #include "Log.hpp" 4 | 5 | dap::ServerProtocol::ServerProtocol(Socket::Ptr_t conn) 6 | : m_conn(conn) 7 | { 8 | } 9 | 10 | dap::ServerProtocol::~ServerProtocol() {} 11 | 12 | void dap::ServerProtocol::Initialize() 13 | { 14 | // Attempt to read something from the network 15 | enum eState { kWaitingInitRequest, kDone }; 16 | eState state = kWaitingInitRequest; 17 | while (state != kDone) { 18 | std::string network_buffer; 19 | if (m_conn->SelectReadMS(10) == dap::Socket::kSuccess) { 20 | if (m_conn->Read(network_buffer) == dap::Socket::kSuccess) { 21 | LOG_DEBUG1() << "Read: " << network_buffer << endl; 22 | 23 | // Append the buffer to what we already have 24 | m_rpc.AppendBuffer(network_buffer); 25 | 26 | // Try to construct a message and process it 27 | m_rpc.ProcessBuffer( 28 | [&](Json json, wxObject*) { 29 | dap::ProtocolMessage::Ptr_t request = ObjGenerator::Get().FromJSON(json); 30 | if (request && request->type == "request" && request->As()) { 31 | dap::InitializeResponse initResponse; 32 | m_rpc.Send(initResponse, m_conn); 33 | LOG_DEBUG() << "Sending InitializeRequest"; 34 | 35 | // Send InitializedEvent 36 | dap::InitializedEvent initEvent; 37 | m_rpc.Send(initEvent, m_conn); 38 | LOG_DEBUG() << "Sending InitializedEvent"; 39 | LOG_INFO() << "Initialization completed"; 40 | state = kDone; 41 | }; 42 | }, 43 | nullptr); 44 | } 45 | } 46 | } 47 | } 48 | 49 | void dap::ServerProtocol::Check() 50 | { 51 | if (m_onNetworkMessage) { 52 | // First try to read something from the network 53 | std::string content; 54 | if (m_conn->SelectReadMS(10) == dap::Socket::kSuccess && m_conn->Read(content) == Socket::kSuccess) { 55 | m_rpc.AppendBuffer(content); 56 | } 57 | 58 | // Process it 59 | m_rpc.ProcessBuffer( 60 | [&](Json json, wxObject*) { 61 | dap::ProtocolMessage::Ptr_t message = ObjGenerator::Get().FromJSON(json); 62 | if (message) { 63 | return m_onNetworkMessage(message); 64 | } 65 | }, 66 | nullptr); 67 | } 68 | } 69 | 70 | void dap::ServerProtocol::ProcessGdbMessage(dap::ProtocolMessage::Ptr_t message) 71 | { 72 | // message was generated by the GDB driver. Send it over to the client 73 | LOG_DEBUG() << "-->" << message->ToString(); 74 | m_rpc.Send(message, m_conn); 75 | } 76 | -------------------------------------------------------------------------------- /dap/ServerProtocol.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PROTOCOL_HPP 2 | #define PROTOCOL_HPP 3 | 4 | #include "JsonRPC.hpp" 5 | #include "Socket.hpp" 6 | #include "dap_exports.hpp" 7 | 8 | namespace dap 9 | { 10 | class WXDLLIMPEXP_DAP ServerProtocol 11 | { 12 | JsonRPC m_rpc; 13 | Socket::Ptr_t m_conn; 14 | function m_onNetworkMessage = nullptr; 15 | 16 | public: 17 | ServerProtocol(Socket::Ptr_t conn); 18 | virtual ~ServerProtocol(); 19 | 20 | void Initialize(); 21 | 22 | /** 23 | * @brief register a callback for handling network messages 24 | */ 25 | void RegisterNetworkCallback(std::function onNetworkMessage) 26 | { 27 | m_onNetworkMessage = onNetworkMessage; 28 | } 29 | 30 | /** 31 | * @brief Check to see if any messages have arrived on the network 32 | * and process them 33 | */ 34 | void Check(); 35 | 36 | /** 37 | * @brief process gdb output 38 | */ 39 | void ProcessGdbMessage(dap::ProtocolMessage::Ptr_t message); 40 | }; 41 | }; // namespace dap 42 | #endif // PROTOCOL_HPP 43 | -------------------------------------------------------------------------------- /dap/Socket.cpp: -------------------------------------------------------------------------------- 1 | #include "Socket.hpp" 2 | 3 | #include "Exception.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef _WIN32 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #endif 17 | namespace dap 18 | { 19 | Socket::Socket(socket_t sockfd) 20 | : m_socket(sockfd) 21 | , m_closeOnExit(true) 22 | { 23 | if (m_socket != INVALID_SOCKET) { 24 | MakeSocketBlocking(false); 25 | } 26 | } 27 | 28 | Socket::~Socket() { DestroySocket(); } 29 | 30 | void Socket::Initialize() 31 | { 32 | #ifdef _WIN32 33 | WSADATA wsa; 34 | WSAStartup(MAKEWORD(2, 2), &wsa); 35 | #endif 36 | } 37 | 38 | int Socket::Read(std::string& content) 39 | { 40 | char buffer[16 << 10]; 41 | size_t bytesRead = 0; 42 | int rc = Read(buffer, sizeof(buffer), bytesRead); 43 | if (rc == kSuccess) { 44 | content = std::string(buffer, bytesRead); 45 | } 46 | return rc; 47 | } 48 | 49 | int Socket::Read(char* buffer, size_t bufferSize, size_t& bytesRead) 50 | { 51 | int res = recv(m_socket, buffer, bufferSize, 0); 52 | if (res < 0) { 53 | int err = GetLastError(); 54 | if (eWouldBlock == err) { 55 | return kTimeout; 56 | } 57 | throw Exception("Read failed: " + error(err)); 58 | } else if (0 == res) { 59 | throw Exception("Read failed: " + error()); 60 | } 61 | 62 | bytesRead = static_cast(res); 63 | return kSuccess; 64 | } 65 | 66 | // Send API 67 | void Socket::Send(const std::string& msg) 68 | { 69 | if (m_socket == INVALID_SOCKET) { 70 | throw Exception("Invalid socket!"); 71 | } 72 | if (msg.empty()) { 73 | return; 74 | } 75 | 76 | const char* pdata = msg.data(); 77 | int bytesLeft = msg.length(); 78 | while (bytesLeft) { 79 | if (SelectWriteMS(1000) == kTimeout) 80 | continue; 81 | const int bytesSent = ::send(m_socket, pdata, bytesLeft, 0); 82 | if (bytesSent <= 0) 83 | throw Exception("Send error: " + error()); 84 | pdata += bytesSent; 85 | bytesLeft -= bytesSent; 86 | } 87 | } 88 | 89 | int Socket::GetLastError() 90 | { 91 | #ifdef _WIN32 92 | return ::WSAGetLastError(); 93 | #else 94 | return errno; 95 | #endif 96 | } 97 | 98 | wxString Socket::error() { return error(GetLastError()); } 99 | 100 | wxString Socket::error(const int errorCode) 101 | { 102 | wxString err; 103 | #ifdef _WIN32 104 | // Get the error message, if any. 105 | if (errorCode == 0) 106 | return "No error message has been recorded"; 107 | 108 | LPSTR messageBuffer = nullptr; 109 | size_t size = 110 | FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 111 | NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); 112 | 113 | wxString message(messageBuffer, size); 114 | 115 | // Free the buffer. 116 | LocalFree(messageBuffer); 117 | err = message; 118 | #else 119 | err = strerror(errorCode); 120 | #endif 121 | return err; 122 | } 123 | 124 | void Socket::DestroySocket() 125 | { 126 | if (IsCloseOnExit()) { 127 | if (m_socket != INVALID_SOCKET) { 128 | #ifdef _WIN32 129 | ::shutdown(m_socket, 2); 130 | ::closesocket(m_socket); 131 | #else 132 | ::shutdown(m_socket, 2); 133 | ::close(m_socket); 134 | #endif 135 | } 136 | } 137 | m_socket = INVALID_SOCKET; 138 | } 139 | 140 | socket_t Socket::Release() 141 | { 142 | int fd = m_socket; 143 | m_socket = INVALID_SOCKET; 144 | return fd; 145 | } 146 | 147 | void Socket::MakeSocketBlocking(bool blocking) 148 | { 149 | #ifndef _WIN32 150 | // set socket to non-blocking mode 151 | int flags; 152 | flags = ::fcntl(m_socket, F_GETFL); 153 | if (blocking) { 154 | flags &= ~O_NONBLOCK; 155 | } else { 156 | flags |= O_NONBLOCK; 157 | } 158 | ::fcntl(m_socket, F_SETFL, flags); 159 | #else 160 | u_long iMode = blocking ? 0 : 1; 161 | ::ioctlsocket(m_socket, FIONBIO, &iMode); 162 | #endif 163 | } 164 | 165 | int Socket::SelectWriteMS(long milliSeconds) 166 | { 167 | if (milliSeconds < 0) { 168 | throw Exception("Invalid timeout"); 169 | } 170 | 171 | if (m_socket == INVALID_SOCKET) { 172 | throw Exception("Invalid socket!"); 173 | } 174 | #ifdef __WXMAC__ 175 | struct timeval tv = { milliSeconds / 1000, ((int)milliSeconds % 1000) * 1000 }; 176 | #else 177 | struct timeval tv = { milliSeconds / 1000, (milliSeconds % 1000) * 1000 }; 178 | #endif 179 | fd_set write_set; 180 | FD_ZERO(&write_set); 181 | FD_SET(m_socket, &write_set); 182 | errno = 0; 183 | int rc = select(m_socket + 1, NULL, &write_set, NULL, &tv); 184 | if (rc == 0) { 185 | // timeout 186 | return kTimeout; 187 | 188 | } else if (rc < 0) { 189 | // an error occurred 190 | throw Exception("SelectWriteMS failed: " + error()); 191 | 192 | } else { 193 | // we got something to read 194 | return kSuccess; 195 | } 196 | } 197 | 198 | int Socket::SelectReadMS(long milliSeconds) 199 | { 200 | if (milliSeconds < 0) { 201 | throw Exception("Invalid timeout"); 202 | } 203 | 204 | if (m_socket == INVALID_SOCKET) { 205 | throw Exception("Invalid socket!"); 206 | } 207 | int seconds = milliSeconds / 1000; // convert the number into seconds 208 | int ms = milliSeconds % 1000; // the remainder is less than a second 209 | struct timeval tv = { seconds, ms * 1000 }; 210 | 211 | fd_set readfds; 212 | FD_ZERO(&readfds); 213 | FD_SET(m_socket, &readfds); 214 | int rc = select(m_socket + 1, &readfds, NULL, NULL, &tv); 215 | if (rc == 0) { 216 | // timeout 217 | return kTimeout; 218 | 219 | } else if (rc < 0) { 220 | // an error occurred 221 | throw Exception("SelectRead failed: " + error()); 222 | 223 | } else { 224 | // we got something to read 225 | return kSuccess; 226 | } 227 | } 228 | }; // namespace dap -------------------------------------------------------------------------------- /dap/Socket.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DAP_SOCKET_H 2 | #define DAP_SOCKET_H 3 | 4 | #include "dap_exports.hpp" 5 | 6 | #include 7 | #include 8 | #if defined(__WXOSX__) || defined(BSD) 9 | #include 10 | #endif 11 | 12 | #ifdef _WIN32 13 | #include 14 | #endif 15 | 16 | #ifdef _WIN32 17 | typedef SOCKET socket_t; 18 | typedef int socklen_t; 19 | #else 20 | typedef int socket_t; 21 | #define INVALID_SOCKET -1 22 | #endif 23 | 24 | using namespace std; 25 | namespace dap 26 | { 27 | class WXDLLIMPEXP_DAP Socket 28 | { 29 | protected: 30 | socket_t m_socket; 31 | bool m_closeOnExit; 32 | 33 | public: 34 | typedef shared_ptr Ptr_t; 35 | 36 | enum { 37 | kSuccess = 1, 38 | kTimeout = 2, 39 | }; 40 | 41 | #ifdef _WIN32 42 | static const int eWouldBlock = WSAEWOULDBLOCK; 43 | #else 44 | static const int eWouldBlock = EWOULDBLOCK; 45 | #endif 46 | 47 | static int GetLastError(); 48 | static wxString error(); 49 | static wxString error(const int errorCode); 50 | 51 | public: 52 | /** 53 | * @brief set the socket into blocking/non-blocking mode 54 | * @param blocking 55 | */ 56 | void MakeSocketBlocking(bool blocking); 57 | 58 | Socket(socket_t sockfd = INVALID_SOCKET); 59 | virtual ~Socket(); 60 | 61 | void SetCloseOnExit(bool closeOnExit) { this->m_closeOnExit = closeOnExit; } 62 | bool IsCloseOnExit() const { return m_closeOnExit; } 63 | /** 64 | * @brief return the descriptor and clear this socket. 65 | */ 66 | socket_t Release(); 67 | 68 | /** 69 | * @brief initialize the socket library 70 | */ 71 | static void Initialize(); 72 | 73 | /** 74 | * @brief return platform specific socket handle 75 | */ 76 | socket_t GetSocket() const { return m_socket; } 77 | 78 | /** 79 | * @brief send message. This function blocks until the entire buffer is sent 80 | * @throws SocketException 81 | */ 82 | void Send(const std::string& msg); 83 | 84 | /** 85 | * @brief 86 | * @param timeout milliseconds to wait 87 | * @return kSuccess or kTimeout 88 | * @throws SocketException 89 | */ 90 | int Read(char* buffer, size_t bufferSize, size_t& bytesRead); 91 | 92 | /** 93 | * @brief read std::string content from remote server 94 | * @param content [output] 95 | * @return kSuccess or kTimeout 96 | * @throws SocketException 97 | */ 98 | int Read(std::string& content); 99 | 100 | /** 101 | * @brief select for read. Same as above, but use milli seconds instead 102 | * @param milliSeconds number of _milliseconds_ to wait 103 | * @return kSuccess or kTimeout 104 | * @throws SocketException 105 | */ 106 | int SelectReadMS(long milliSeconds); 107 | 108 | /** 109 | * @brief select for write (milli seconds version) 110 | * @return kSuccess or kTimeout 111 | * @throws SocketException 112 | */ 113 | int SelectWriteMS(long milliSeconds); 114 | 115 | template 116 | T* As() const 117 | { 118 | return dynamic_cast(const_cast(this)); 119 | } 120 | 121 | protected: 122 | /** 123 | * @brief 124 | */ 125 | void DestroySocket(); 126 | }; 127 | }; // namespace dap 128 | #endif // CLSOCKETBASE_H 129 | -------------------------------------------------------------------------------- /dap/SocketClient.cpp: -------------------------------------------------------------------------------- 1 | #include "SocketClient.hpp" 2 | 3 | #include "ConnectionString.hpp" 4 | #include "Exception.hpp" 5 | 6 | #ifndef _WIN32 7 | #include 8 | #include 9 | #include 10 | #include /* superset of previous */ 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | #ifdef _WIN32 17 | #define RESET_ERRNO() WSASetLastError(0) 18 | #else 19 | #define RESET_ERRNO() errno = 0 20 | #endif 21 | namespace dap 22 | { 23 | 24 | SocketClient::SocketClient() {} 25 | 26 | SocketClient::~SocketClient() {} 27 | 28 | bool SocketClient::ConnectRemote(const wxString& address, int port) 29 | { 30 | DestroySocket(); 31 | m_socket = ::socket(AF_INET, SOCK_STREAM, 0); 32 | const char* ip_addr = address.c_str(); 33 | struct sockaddr_in serv_addr; 34 | serv_addr.sin_family = AF_INET; 35 | serv_addr.sin_port = htons(port); 36 | 37 | #ifndef _WIN32 38 | if(inet_pton(AF_INET, ip_addr, &serv_addr.sin_addr) <= 0) { 39 | // restore socket to blocking mode 40 | return false; 41 | } 42 | #else 43 | serv_addr.sin_addr.s_addr = inet_addr(ip_addr); 44 | #endif 45 | 46 | RESET_ERRNO(); 47 | int rc = ::connect(m_socket, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 48 | if(rc == 0) { 49 | MakeSocketBlocking(false); 50 | } 51 | return rc == 0; 52 | } 53 | 54 | bool SocketClient::Connect(const wxString& connectionString) 55 | { 56 | ConnectionString cs(connectionString); 57 | if(!cs.IsOK()) { 58 | return false; 59 | } 60 | if(cs.GetProtocol() == ConnectionString::kUnixLocalSocket) { 61 | throw Exception("Unsupported protocol"); 62 | } else { 63 | // TCP 64 | return ConnectRemote(cs.GetHost(), cs.GetPort()); 65 | } 66 | } 67 | }; // namespace dap -------------------------------------------------------------------------------- /dap/SocketClient.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CLSOCKETCLIENT_H 2 | #define CLSOCKETCLIENT_H 3 | 4 | #include "Socket.hpp" 5 | #include "dap_exports.hpp" 6 | 7 | #include 8 | 9 | using namespace std; 10 | namespace dap 11 | { 12 | class WXDLLIMPEXP_DAP SocketClient : public Socket 13 | { 14 | wxString m_path; 15 | 16 | public: 17 | SocketClient(); 18 | virtual ~SocketClient(); 19 | 20 | /** 21 | * @brief connect to a remote server using ip/port 22 | * when using non-blocking mode, wouldBlock will be set to true 23 | * incase the connect fails 24 | */ 25 | bool ConnectRemote(const wxString& address, int port); 26 | 27 | /** 28 | * @brief connect using connection wxString 29 | * @param connectionString in the format of tcp://127.0.0.1:1234 30 | * @return 31 | */ 32 | bool Connect(const wxString& connectionString); 33 | }; 34 | }; // namespace dap 35 | #endif // CLSOCKETCLIENT_H 36 | -------------------------------------------------------------------------------- /dap/SocketServer.cpp: -------------------------------------------------------------------------------- 1 | #include "SocketServer.hpp" 2 | 3 | #include "ConnectionString.hpp" 4 | #include "Exception.hpp" 5 | 6 | #ifndef _WIN32 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #endif 16 | 17 | namespace dap 18 | { 19 | SocketServer::SocketServer() {} 20 | 21 | SocketServer::~SocketServer() { DestroySocket(); } 22 | 23 | int SocketServer::CreateServer(const wxString& address, int port) 24 | { 25 | // Create a socket 26 | if((m_socket = ::socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { 27 | throw Exception("Could not create socket: " + error()); 28 | } 29 | 30 | // must set reuse-address 31 | int optval; 32 | 33 | // set SO_REUSEADDR on a socket to true (1): 34 | optval = 1; 35 | ::setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&optval, sizeof(optval)); 36 | 37 | // Prepare the sockaddr_in structure 38 | struct sockaddr_in server; 39 | server.sin_family = AF_INET; 40 | #ifdef _WIN32 41 | server.sin_addr.s_addr = inet_addr(address.c_str()); 42 | #else 43 | inet_pton(AF_INET, address.c_str(), &server.sin_addr); 44 | #endif 45 | server.sin_port = htons(port); 46 | 47 | // Bind 48 | if(::bind(m_socket, (struct sockaddr*)&server, sizeof(server)) != 0) { 49 | throw Exception("CreateServer: bind() error: " + error()); 50 | } 51 | 52 | if(port == 0) { 53 | struct sockaddr_in socket_name; 54 | #ifdef _WIN32 55 | int name_len = sizeof(socket_name); 56 | #else 57 | socklen_t name_len = sizeof(socket_name); 58 | #endif 59 | if(::getsockname(m_socket, (struct sockaddr*)&socket_name, &name_len) != 0) { 60 | throw Exception("CreateServer: getsockname() error: " + error()); 61 | } 62 | port = ntohs(socket_name.sin_port); 63 | } 64 | // define the accept queue size 65 | if(::listen(m_socket, 10) != 0) { 66 | throw Exception("CreateServer: listen() error: " + error()); 67 | } 68 | 69 | // return the bound port number 70 | return port; 71 | } 72 | 73 | int SocketServer::Start(const wxString& connectionString) 74 | { 75 | ConnectionString cs(connectionString); 76 | if(!cs.IsOK()) { 77 | throw Exception("Invalid connection string provided"); 78 | } 79 | if(cs.GetProtocol() == ConnectionString::kTcp) { 80 | return CreateServer(cs.GetHost(), cs.GetPort()); 81 | } else { 82 | throw Exception("Unsupported protocol"); 83 | } 84 | } 85 | 86 | Socket::Ptr_t SocketServer::WaitForNewConnection(long timeout) 87 | { 88 | return Socket::Ptr_t(WaitForNewConnectionRaw(timeout)); 89 | } 90 | 91 | Socket* SocketServer::WaitForNewConnectionRaw(long timeout) 92 | { 93 | if(timeout < 0) { 94 | return nullptr; 95 | } 96 | if(SelectReadMS(timeout * 1000) == kTimeout) { 97 | return nullptr; 98 | } 99 | int fd = ::accept(m_socket, 0, 0); 100 | if(fd < 0) { 101 | throw Exception("accept error: " + error()); 102 | } 103 | return new Socket(fd); 104 | } 105 | }; // namespace dap -------------------------------------------------------------------------------- /dap/SocketServer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETSERVER_H 2 | #define SOCKETSERVER_H 3 | 4 | #include "Socket.hpp" 5 | #include "dap_exports.hpp" 6 | 7 | #include 8 | 9 | using namespace std; 10 | namespace dap 11 | { 12 | class WXDLLIMPEXP_DAP SocketServer : public Socket 13 | { 14 | public: 15 | SocketServer(); 16 | virtual ~SocketServer(); 17 | 18 | protected: 19 | /** 20 | * @throw clSocketException 21 | * @return port number 22 | */ 23 | int CreateServer(const wxString& address, int port); 24 | 25 | public: 26 | /** 27 | * @brief Create server using connection string 28 | * @return port number on success 29 | * @throw clSocketException 30 | */ 31 | int Start(const wxString& connectionString); 32 | Socket::Ptr_t WaitForNewConnection(long timeout); 33 | /** 34 | * @brief same as above, however, return a pointer to the connection that should be freed by the caller 35 | */ 36 | Socket* WaitForNewConnectionRaw(long timeout); 37 | }; 38 | }; // namespace dap 39 | #endif // CLSOCKETSERVER_H 40 | -------------------------------------------------------------------------------- /dap/StringUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "StringUtils.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(_MSC_VER) 12 | /* We are on Windows using MSVC */ 13 | #define strtok_r strtok_s 14 | #endif 15 | 16 | wxString& DapStringUtils::Rtrim(wxString& str) { return str.Trim(); } 17 | 18 | std::string& DapStringUtils::Rtrim(std::string& str) 19 | { 20 | str.erase(str.find_last_not_of(" \n\r\t") + 1); 21 | return str; 22 | } 23 | 24 | wxString& DapStringUtils::Ltrim(wxString& str) { return str.Trim(false); } 25 | 26 | wxString& DapStringUtils::Trim(wxString& str) { return str.Trim().Trim(false); } 27 | 28 | std::string& DapStringUtils::Trim(std::string& str) 29 | { 30 | str.erase(0, str.find_first_not_of(" \n\r\t")); 31 | str.erase(str.find_last_not_of(" \n\r\t") + 1); 32 | return str; 33 | } 34 | 35 | wxString DapStringUtils::BeforeFirst(const wxString& str, char ch) 36 | { 37 | size_t where = str.find(ch); 38 | if (where == wxString::npos) { 39 | return str; 40 | } 41 | return str.substr(0, where); 42 | } 43 | 44 | wxString DapStringUtils::AfterFirst(const wxString& str, char ch) 45 | { 46 | size_t where = str.find(ch); 47 | if (where == wxString::npos) { 48 | return ""; 49 | } 50 | return str.substr(where + 1); 51 | } 52 | 53 | std::string DapStringUtils::BeforeFirst(const std::string& str, char ch) 54 | { 55 | size_t where = str.find(ch); 56 | if (where == std::string::npos) { 57 | return str; 58 | } 59 | return str.substr(0, where); 60 | } 61 | 62 | std::string DapStringUtils::AfterFirst(const std::string& str, char ch) 63 | { 64 | size_t where = str.find(ch); 65 | if (where == std::string::npos) { 66 | return ""; 67 | } 68 | return str.substr(where + 1); 69 | } 70 | 71 | std::vector DapStringUtils::Split(const std::string& str, const std::string& delims) 72 | { 73 | std::vector v; 74 | std::string tmp = str; 75 | char* p = (char*)tmp.c_str(); 76 | char* saved_ptr = nullptr; 77 | char* token = strtok_r(p, delims.c_str(), &saved_ptr); 78 | while (token) { 79 | std::string t = token; 80 | Trim(t); 81 | if (!t.empty()) { 82 | v.push_back(t); 83 | } 84 | token = strtok_r(nullptr, delims.c_str(), &saved_ptr); 85 | } 86 | return v; 87 | } 88 | 89 | std::vector DapStringUtils::Split(const wxString& str, char ch) 90 | { 91 | std::vector v; 92 | auto arr = ::wxStringTokenize(str, wxString() << ch, wxTOKEN_STRTOK); 93 | v.reserve(arr.size()); 94 | v.insert(v.end(), arr.begin(), arr.end()); 95 | return v; 96 | } 97 | 98 | wxString DapStringUtils::ToUpper(const wxString& str) { return str.Upper(); } 99 | 100 | #define ARGV_STATE_NORMAL 0 101 | #define ARGV_STATE_DQUOTE 1 102 | #define ARGV_STATE_SQUOTE 2 103 | #define ARGV_STATE_ESCAPE 3 104 | #define ARGV_STATE_BACKTICK 4 105 | #define PUSH_CURTOKEN() \ 106 | { \ 107 | if (!curstr.empty()) { \ 108 | A.push_back(curstr); \ 109 | curstr.clear(); \ 110 | } \ 111 | } 112 | 113 | #define CHANGE_STATE(new_state) \ 114 | { \ 115 | prev_state = state; \ 116 | state = new_state; \ 117 | } 118 | 119 | #define RESTORE_STATE() \ 120 | { \ 121 | state = prev_state; \ 122 | prev_state = ARGV_STATE_NORMAL; \ 123 | } 124 | 125 | char** DapStringUtils::BuildArgv(const wxString& str, int& argc) 126 | { 127 | std::vector A; 128 | int state = ARGV_STATE_NORMAL; 129 | int prev_state = ARGV_STATE_NORMAL; 130 | wxString curstr; 131 | for (char ch : str) { 132 | switch (state) { 133 | case ARGV_STATE_NORMAL: { 134 | switch (ch) { 135 | case ' ': 136 | case '\t': 137 | PUSH_CURTOKEN(); 138 | break; 139 | case '\'': 140 | CHANGE_STATE(ARGV_STATE_SQUOTE); 141 | curstr << ch; 142 | break; 143 | case '"': 144 | CHANGE_STATE(ARGV_STATE_DQUOTE); 145 | curstr << ch; 146 | break; 147 | case '`': 148 | CHANGE_STATE(ARGV_STATE_BACKTICK); 149 | curstr << ch; 150 | break; 151 | default: 152 | curstr << ch; 153 | break; 154 | } 155 | } break; 156 | case ARGV_STATE_ESCAPE: { 157 | if (prev_state == ARGV_STATE_DQUOTE) { 158 | switch (ch) { 159 | case '"': 160 | curstr << "\""; 161 | RESTORE_STATE(); 162 | break; 163 | default: 164 | curstr << "\\" << ch; 165 | RESTORE_STATE(); 166 | break; 167 | } 168 | } else if (prev_state == ARGV_STATE_BACKTICK) { 169 | switch (ch) { 170 | case '`': 171 | curstr << "`"; 172 | RESTORE_STATE(); 173 | break; 174 | default: 175 | curstr << "\\" << ch; 176 | RESTORE_STATE(); 177 | break; 178 | } 179 | } else { // single quote 180 | switch (ch) { 181 | case '\'': 182 | curstr << "'"; 183 | RESTORE_STATE(); 184 | break; 185 | default: 186 | curstr << "\\" << ch; 187 | RESTORE_STATE(); 188 | break; 189 | } 190 | } 191 | } break; 192 | case ARGV_STATE_DQUOTE: { 193 | switch (ch) { 194 | case '\\': 195 | CHANGE_STATE(ARGV_STATE_ESCAPE); 196 | break; 197 | case '"': 198 | curstr << ch; 199 | RESTORE_STATE(); 200 | break; 201 | default: 202 | curstr << ch; 203 | break; 204 | } 205 | } break; 206 | case ARGV_STATE_SQUOTE: { 207 | switch (ch) { 208 | case '\\': 209 | CHANGE_STATE(ARGV_STATE_ESCAPE); 210 | break; 211 | case '\'': 212 | curstr << ch; 213 | RESTORE_STATE(); 214 | break; 215 | default: 216 | curstr << ch; 217 | break; 218 | } 219 | } break; 220 | case ARGV_STATE_BACKTICK: { 221 | switch (ch) { 222 | case '\\': 223 | CHANGE_STATE(ARGV_STATE_ESCAPE); 224 | break; 225 | case '`': 226 | curstr << ch; 227 | RESTORE_STATE(); 228 | break; 229 | default: 230 | curstr << ch; 231 | break; 232 | } 233 | } break; 234 | } 235 | } 236 | 237 | if (!curstr.empty()) { 238 | A.push_back(curstr); 239 | curstr.clear(); 240 | } 241 | 242 | if (A.empty()) { 243 | return nullptr; 244 | } 245 | 246 | char** argv = new char*[A.size() + 1]; 247 | argv[A.size()] = NULL; 248 | for (size_t i = 0; i < A.size(); ++i) { 249 | argv[i] = strdup(A[i].c_str()); 250 | } 251 | argc = (int)A.size(); 252 | return argv; 253 | } 254 | 255 | void DapStringUtils::FreeArgv(char** argv, int argc) 256 | { 257 | for (int i = 0; i < argc; ++i) { 258 | free(argv[i]); 259 | } 260 | delete[] argv; 261 | } 262 | 263 | std::vector DapStringUtils::BuildArgv(const wxString& str) 264 | { 265 | int argc = 0; 266 | char** argv = BuildArgv(str, argc); 267 | std::vector arrArgv; 268 | for (int i = 0; i < argc; ++i) { 269 | arrArgv.push_back(argv[i]); 270 | } 271 | FreeArgv(argv, argc); 272 | 273 | for (wxString& s : arrArgv) { 274 | if ((s.length() > 1) && (s[0] == '"') && (s.Last() == '"')) { 275 | s.RemoveLast(); 276 | s.erase(0, 1); 277 | } 278 | } 279 | return arrArgv; 280 | } 281 | 282 | #ifdef __WIN32 283 | #define PATH_SEP '\\' 284 | #define SOURCE_SEP '/' 285 | #else 286 | #define PATH_SEP '/' 287 | #define SOURCE_SEP '\\' 288 | #endif 289 | 290 | static wxString& ConvertSlashes(wxString& path, char source, char target) 291 | { 292 | char last_char = 0; 293 | wxString tmp; 294 | tmp.reserve(path.length()); 295 | for (wxChar ch : path) { 296 | if (ch == source) { 297 | ch = target; 298 | } 299 | if (ch == target && last_char == target) { 300 | // Skip it 301 | } else { 302 | tmp.append(1, ch); 303 | } 304 | last_char = ch; 305 | } 306 | path = tmp; 307 | return path; 308 | } 309 | const wxString& std::to_string(const wxString& str) { return str; } 310 | 311 | wxString& DapStringUtils::ToNativePath(wxString& path) { return ConvertSlashes(path, SOURCE_SEP, PATH_SEP); } 312 | 313 | wxString& DapStringUtils::ToUnixPath(wxString& path) { return ConvertSlashes(path, '\\', '/'); } 314 | 315 | wxString DapStringUtils::ToUnixPath(const wxString& path) 316 | { 317 | wxString tmppath = path; 318 | tmppath = ConvertSlashes(tmppath, '\\', '/'); 319 | return tmppath; 320 | } 321 | 322 | wxString DapStringUtils::ToNativePath(const wxString& path) 323 | { 324 | wxString tmppath = path; 325 | tmppath = ConvertSlashes(tmppath, SOURCE_SEP, PATH_SEP); 326 | return tmppath; 327 | } 328 | 329 | wxString& DapStringUtils::WrapWithQuotes(wxString& str) 330 | { 331 | if (str.empty()) { 332 | return str; 333 | } 334 | if (str.find(' ') == wxString::npos) { 335 | return str; 336 | } 337 | str.insert(str.begin(), '"'); 338 | str.append(1, '"'); 339 | return str; 340 | } 341 | 342 | wxString DapStringUtils::WrapWithQuotes(const wxString& str) 343 | { 344 | if (str.empty()) { 345 | return str; 346 | } 347 | if (str.find(' ') == wxString::npos) { 348 | return str; 349 | } 350 | wxString tmpstr = str; 351 | tmpstr.insert(tmpstr.begin(), '"'); 352 | tmpstr.append(1, '"'); 353 | return tmpstr; 354 | } 355 | 356 | bool DapStringUtils::StartsWith(const wxString& str, const wxString& prefix) 357 | { 358 | if (str.length() < prefix.length()) { 359 | return false; 360 | } 361 | 362 | for (size_t i = 0; i < prefix.length(); ++i) { 363 | if (str[i] != prefix[i]) { 364 | return false; 365 | } 366 | } 367 | return true; 368 | } 369 | -------------------------------------------------------------------------------- /dap/StringUtils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STRINGUTILS_H 2 | #define STRINGUTILS_H 3 | 4 | #include "dap_exports.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // for some obscure reason, to_string() does not accept wxString 12 | // we add one here 13 | namespace std 14 | { 15 | WXDLLIMPEXP_DAP const wxString& to_string(const wxString& str); 16 | }; 17 | 18 | #define UNUSED(x) ((void)x) 19 | 20 | class WXDLLIMPEXP_DAP DapStringUtils 21 | { 22 | protected: 23 | static char** BuildArgv(const wxString& str, int& argc); 24 | static void FreeArgv(char** argv, int argc); 25 | 26 | public: 27 | /// Right trim 28 | static wxString& Rtrim(wxString& str); 29 | static std::string& Rtrim(std::string& str); 30 | 31 | /// Left trim 32 | static wxString& Ltrim(wxString& str); 33 | 34 | /// Both left + right trim 35 | static wxString& Trim(wxString& str); 36 | static std::string& Trim(std::string& str); 37 | 38 | /// Gets all characters before the first occurrence of ch. 39 | /// Returns the whole wxString if ch is not found. 40 | static wxString BeforeFirst(const wxString& str, char ch); 41 | static std::string BeforeFirst(const std::string& str, char ch); 42 | 43 | /// Gets all the characters after the first occurrence of ch. 44 | /// Returns the empty wxString if ch is not found. 45 | static wxString AfterFirst(const wxString& str, char ch); 46 | static std::string AfterFirst(const std::string& str, char ch); 47 | 48 | /// Check if wxString starts with a given prefix 49 | static bool StartsWith(const wxString& str, const wxString& prefix); 50 | 51 | /// Split a wxString 52 | static std::vector Split(const wxString& str, char ch = '\n'); 53 | static std::vector Split(const std::string& str, const std::string& delims = "\n"); 54 | 55 | /// Convert to wxString to uppercase 56 | static wxString ToUpper(const wxString& str); 57 | 58 | /// Split command line into array 59 | static std::vector BuildArgv(const wxString& str); 60 | 61 | /// Convert file's path to native path 62 | /// this function also removes double \\ or // 63 | static wxString& ToNativePath(wxString& path); 64 | 65 | /// Const version 66 | static wxString ToNativePath(const wxString& path); 67 | 68 | /// Convert file's path to UNIX slashes 69 | static wxString& ToUnixPath(wxString& path); 70 | 71 | /// Const version 72 | static wxString ToUnixPath(const wxString& path); 73 | 74 | /// Wrap wxString with quotes if needed 75 | static wxString& WrapWithQuotes(wxString& str); 76 | 77 | /// Wrap wxString with quotes if needed 78 | static wxString WrapWithQuotes(const wxString& str); 79 | }; 80 | 81 | template 82 | wxString& operator<<(wxString& str, const T& t) 83 | { 84 | str.append(to_string(t)); 85 | return str; 86 | } 87 | 88 | #endif // STRINGUTILS_H 89 | -------------------------------------------------------------------------------- /dap/UnixProcess.cpp: -------------------------------------------------------------------------------- 1 | #include "UnixProcess.hpp" 2 | 3 | #if defined(__APPLE__) || defined(__linux__) 4 | #include "Log.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | UnixProcess::UnixProcess(const vector& args) 13 | { 14 | m_goingDown.store(false); 15 | 16 | // Open the pipes 17 | if (!m_childStdin.Open() || !m_childStderr.Open() || !m_childStdout.Open()) { 18 | LOG_ERROR() << "Could not open redirection pipes." << strerror(errno); 19 | return; 20 | } 21 | 22 | child_pid = fork(); 23 | if (child_pid == -1) { 24 | LOG_ERROR() << "Failed to start child process" << strerror(errno); 25 | } 26 | if (child_pid == 0) { 27 | // In child process 28 | dup2(m_childStdin.GetReadFd(), STDIN_FILENO); 29 | dup2(m_childStdout.GetWriteFd(), STDOUT_FILENO); 30 | dup2(m_childStderr.GetWriteFd(), STDERR_FILENO); 31 | m_childStdin.Close(); 32 | m_childStdout.Close(); 33 | m_childStderr.Close(); 34 | 35 | // prevent descriptor leak into child process 36 | const int fd_max = (sysconf(_SC_OPEN_MAX) != -1 ? sysconf(_SC_OPEN_MAX) : FD_SETSIZE); 37 | for (int fd = 3; fd < fd_max; fd++) { 38 | close(fd); 39 | } 40 | 41 | char** argv = new char*[args.size() + 1]; 42 | for (size_t i = 0; i < args.size(); ++i) { 43 | const wxString& arg = args[i]; 44 | argv[i] = new char[arg.length() + 1]; 45 | strcpy(argv[i], arg.c_str()); 46 | argv[i][arg.length()] = 0; 47 | } 48 | argv[args.size()] = 0; 49 | int result = execvp(argv[0], const_cast(argv)); 50 | int errNo = errno; 51 | if (result == -1) { 52 | // Note: no point writing to stdout here, it has been redirected 53 | LOG_ERROR() << "Error: Failed to launch program:" << args; 54 | exit(EXIT_FAILURE); 55 | } 56 | } else { 57 | // parent process 58 | m_childStdin.CloseReadFd(); 59 | m_childStdout.CloseWriteFd(); 60 | m_childStderr.CloseWriteFd(); 61 | } 62 | } 63 | 64 | UnixProcess::~UnixProcess() 65 | { 66 | // Kill the child process (if it is still alive) 67 | Terminate(); 68 | Process::Cleanup(); 69 | } 70 | 71 | #define CHUNK_SIZE 1024 72 | #define MAX_BUFF_SIZE (1024 * 2048) 73 | 74 | bool UnixProcess::ReadAll(int fd, std::string& content, int timeoutMilliseconds) 75 | { 76 | fd_set rset; 77 | char buff[CHUNK_SIZE]; 78 | FD_ZERO(&rset); 79 | FD_SET(fd, &rset); 80 | 81 | int seconds = timeoutMilliseconds / 1000; 82 | int ms = timeoutMilliseconds % 1000; 83 | 84 | struct timeval tv = { seconds, ms * 1000 }; // 10 milliseconds timeout 85 | while (true) { 86 | int rc = ::select(fd + 1, &rset, nullptr, nullptr, &tv); 87 | if (rc > 0) { 88 | int len = read(fd, buff, (sizeof(buff) - 1)); 89 | if (len > 0) { 90 | buff[len] = 0; 91 | content.append(buff); 92 | if (content.length() >= MAX_BUFF_SIZE) { 93 | return true; 94 | } 95 | // clear the tv struct so next select() call will return immediately 96 | tv.tv_usec = 0; 97 | tv.tv_sec = 0; 98 | FD_ZERO(&rset); 99 | FD_SET(fd, &rset); 100 | continue; 101 | } 102 | } else if (rc == 0) { 103 | // timeout 104 | return true; 105 | } 106 | break; 107 | } 108 | // error 109 | return false; 110 | } 111 | 112 | bool UnixProcess::Write(int fd, const wxString& message, atomic_bool& shutdown) 113 | { 114 | int bytes = 0; 115 | wxString tmp = message; 116 | const int chunkSize = 4096; 117 | while (!tmp.empty() && !shutdown.load()) { 118 | errno = 0; 119 | bytes = ::write(fd, tmp.c_str(), tmp.length() > chunkSize ? chunkSize : tmp.length()); 120 | int errCode = errno; 121 | if (bytes < 0) { 122 | if ((errCode == EWOULDBLOCK) || (errCode == EAGAIN)) { 123 | this_thread::sleep_for(chrono::milliseconds(10)); 124 | } else if (errCode == EINTR) { 125 | continue; 126 | } else { 127 | break; 128 | } 129 | } else if (bytes) { 130 | tmp.erase(0, bytes); 131 | } 132 | } 133 | LOG_DEBUG() << "Wrote message of size:" << message.length(); 134 | return tmp.empty(); 135 | } 136 | 137 | int UnixProcess::Wait() 138 | { 139 | if (child_pid != -1) { 140 | int status = 0; 141 | waitpid(child_pid, &status, WNOHANG); 142 | return WEXITSTATUS(status); 143 | } else { 144 | return 0; 145 | } 146 | } 147 | 148 | void UnixProcess::Stop() 149 | { 150 | if (child_pid != -1) { 151 | ::kill(child_pid, SIGTERM); 152 | } 153 | } 154 | 155 | bool UnixProcess::Write(const std::string& message) 156 | { 157 | return UnixProcess::Write(m_childStdin.GetWriteFd(), message, m_goingDown); 158 | } 159 | 160 | bool UnixProcess::DoRead(std::string& str, std::string& err_buff) 161 | { 162 | if (!IsAlive()) { 163 | return false; 164 | } 165 | ReadAll(m_childStdout.GetReadFd(), str, 10); 166 | ReadAll(m_childStderr.GetReadFd(), err_buff, 10); 167 | return !str.empty() || !err_buff.empty(); 168 | } 169 | 170 | bool UnixProcess::IsAlive() const { return (::kill(child_pid, 0) == 0); } 171 | 172 | void UnixProcess::Terminate() 173 | { 174 | Stop(); 175 | Wait(); 176 | } 177 | 178 | bool UnixProcess::WriteLn(const std::string& message) { return Write(message + "\n"); } 179 | 180 | #endif // OSX & GTK 181 | -------------------------------------------------------------------------------- /dap/UnixProcess.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UNIX_PROCESS_H 2 | #define UNIX_PROCESS_H 3 | 4 | #if defined(__APPLE__) || defined(__linux__) 5 | 6 | #include "Process.hpp" 7 | #include "Queue.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // Wrapping pipe in a class makes sure they are closed when we leave scope 21 | #define CLOSE_FD(fd) \ 22 | if (fd != -1) { \ 23 | ::close(fd); \ 24 | fd = -1; \ 25 | } 26 | 27 | using namespace std; 28 | class CPipe 29 | { 30 | private: 31 | int m_readFd = -1; 32 | int m_writeFd = -1; 33 | 34 | public: 35 | const inline int GetReadFd() const { return m_readFd; } 36 | const inline int GetWriteFd() const { return m_writeFd; } 37 | CPipe() {} 38 | void Close() 39 | { 40 | CLOSE_FD(m_readFd); 41 | CLOSE_FD(m_writeFd); 42 | } 43 | ~CPipe() { Close(); } 44 | bool Open() 45 | { 46 | int fd[2]; 47 | if (pipe(fd) == 0) { 48 | m_readFd = fd[0]; 49 | m_writeFd = fd[1]; 50 | return true; 51 | } 52 | return false; 53 | } 54 | void CloseWriteFd() { CLOSE_FD(m_writeFd); } 55 | void CloseReadFd() { CLOSE_FD(m_readFd); } 56 | }; 57 | 58 | class UnixProcess : public dap::Process 59 | { 60 | private: 61 | CPipe m_childStdin; 62 | CPipe m_childStdout; 63 | CPipe m_childStderr; 64 | atomic_bool m_goingDown; 65 | std::string m_stdout; 66 | std::string m_stderr; 67 | 68 | protected: 69 | // sync operations 70 | static bool ReadAll(int fd, std::string& content, int timeoutMilliseconds); 71 | static bool Write(int fd, const wxString& message, atomic_bool& shutdown); 72 | 73 | bool DoRead(std::string& str, std::string& err_buff) override; 74 | 75 | public: 76 | int child_pid = -1; 77 | 78 | UnixProcess(const vector& args); 79 | virtual ~UnixProcess(); 80 | 81 | // wait for process termination 82 | int Wait(); 83 | 84 | // Write to the process 85 | bool Write(const std::string& message) override; 86 | 87 | // Same as Write, but add LF at the end of the message 88 | bool WriteLn(const std::string& message) override; 89 | 90 | // stop the running process 91 | void Stop(); 92 | 93 | /** 94 | * @brief is the process still alive? 95 | */ 96 | bool IsAlive() const override; 97 | 98 | /** 99 | * @brief terminate the process 100 | */ 101 | void Terminate() override; 102 | }; 103 | #endif // defined(__linux__) 104 | #endif // UNIX_PROCESS_H 105 | -------------------------------------------------------------------------------- /dap/cJSON.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef DAP_cJSON__h 24 | #define DAP_cJSON__h 25 | #include "dap_exports.hpp" 26 | 27 | #include 28 | 29 | /* cJsonDap Types: */ 30 | #define cJsonDap_False 0 31 | #define cJsonDap_True 1 32 | #define cJsonDap_Null 2 33 | #define cJsonDap_Number 3 34 | #define cJsonDap_String 4 35 | #define cJsonDap_Array 5 36 | #define cJsonDap_Object 6 37 | 38 | #define cJsonDap_IsReference 256 39 | 40 | namespace dap 41 | { 42 | /* The cJsonDap structure: */ 43 | typedef struct cJsonDap { 44 | struct cJsonDap *next, *prev; /* next/prev allow you to walk array/object chains. Alternatively, use 45 | GetArraySize/GetArrayItem/GetObjectItem */ 46 | struct cJsonDap* child; /* An array or object item will have a child pointer pointing to a chain of the items in the 47 | array/object. */ 48 | 49 | int type; /* The type of the item, as above. */ 50 | 51 | char* valuestring; /* The item's string, if type==cJsonDap_String */ 52 | int valueint; /* The item's number, if type==cJsonDap_Number */ 53 | double valuedouble; /* The item's number, if type==cJsonDap_Number */ 54 | 55 | char* 56 | string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ 57 | } cJsonDap; 58 | 59 | typedef struct cJSONDap_Hooks { 60 | void* (*malloc_fn)(size_t sz); 61 | void (*free_fn)(void* ptr); 62 | } cJSONDap_Hooks; 63 | 64 | /* Supply malloc, realloc and free functions to cJsonDap */ 65 | WXDLLIMPEXP_DAP void cJSON_InitHooks(cJSONDap_Hooks* hooks); 66 | 67 | /* Supply a block of Json, and this returns a cJsonDap object you can interrogate. Call cJSON_Delete when finished. */ 68 | WXDLLIMPEXP_DAP cJsonDap* cJSON_Parse(const char* value); 69 | /* Render a cJsonDap entity to text for transfer/storage. Free the char* when finished. */ 70 | WXDLLIMPEXP_DAP char* cJSON_Print(cJsonDap* item); 71 | /* Render a cJsonDap entity to text for transfer/storage without any formatting. Free the char* when finished. */ 72 | WXDLLIMPEXP_DAP char* cJSON_PrintUnformatted(cJsonDap* item); 73 | /* Delete a cJsonDap entity and all subentities. */ 74 | WXDLLIMPEXP_DAP void cJSON_Delete(cJsonDap* c); 75 | 76 | /* Returns the number of items in an array (or object). */ 77 | WXDLLIMPEXP_DAP int cJSON_GetArraySize(cJsonDap* array); 78 | /* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ 79 | WXDLLIMPEXP_DAP cJsonDap* cJSON_GetArrayItem(cJsonDap* array, int item); 80 | /* Get item "string" from object. Case insensitive. */ 81 | WXDLLIMPEXP_DAP cJsonDap* cJSON_GetObjectItem(cJsonDap* object, const char* string); 82 | 83 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back 84 | * to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ 85 | WXDLLIMPEXP_DAP const char* cJSON_GetErrorPtr(); 86 | 87 | /* These calls create a cJsonDap item of the appropriate type. */ 88 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateNull(); 89 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateTrue(); 90 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateFalse(); 91 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateBool(int b); 92 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateNumber(double num); 93 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateString(const char* string); 94 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateArray(); 95 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateObject(); 96 | 97 | /* These utilities create an Array of count items. */ 98 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateIntArray(int* numbers, int count); 99 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateFloatArray(float* numbers, int count); 100 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateDoubleArray(double* numbers, int count); 101 | WXDLLIMPEXP_DAP cJsonDap* cJSON_CreateStringArray(const char** strings, int count); 102 | 103 | /* Append item to the specified array/object. */ 104 | WXDLLIMPEXP_DAP void cJSON_AddItemToArray(cJsonDap* array, cJsonDap* item); 105 | WXDLLIMPEXP_DAP void cJSON_AddItemToObject(cJsonDap* object, const char* string, cJsonDap* item); 106 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJsonDap to a new 107 | * cJsonDap, but don't want to corrupt your existing cJsonDap. */ 108 | WXDLLIMPEXP_DAP void cJSON_AddItemReferenceToArray(cJsonDap* array, cJsonDap* item); 109 | WXDLLIMPEXP_DAP void cJSON_AddItemReferenceToObject(cJsonDap* object, const char* string, cJsonDap* item); 110 | 111 | /* Remove/Detatch items from Arrays/Objects. */ 112 | WXDLLIMPEXP_DAP cJsonDap* cJSON_DetachItemFromArray(cJsonDap* array, int which); 113 | WXDLLIMPEXP_DAP void cJSON_DeleteItemFromArray(cJsonDap* array, int which); 114 | WXDLLIMPEXP_DAP cJsonDap* cJSON_DetachItemFromObject(cJsonDap* object, const char* string); 115 | WXDLLIMPEXP_DAP void cJSON_DeleteItemFromObject(cJsonDap* object, const char* string); 116 | 117 | /* Update array items. */ 118 | WXDLLIMPEXP_DAP void cJSON_ReplaceItemInArray(cJsonDap* array, int which, cJsonDap* newitem); 119 | WXDLLIMPEXP_DAP void cJSON_ReplaceItemInObject(cJsonDap* object, const char* string, cJsonDap* newitem); 120 | 121 | #define cJSON_AddNullToObject(object, name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) 122 | #define cJSON_AddTrueToObject(object, name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) 123 | #define cJSON_AddFalseToObject(object, name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) 124 | #define cJSON_AddNumberToObject(object, name, n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) 125 | #define cJSON_AddStringToObject(object, name, s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) 126 | }; // namespace dap 127 | #endif 128 | -------------------------------------------------------------------------------- /dap/dap_exports.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DAPEXPORTS_HPP 2 | #define DAPEXPORTS_HPP 3 | 4 | #include 5 | #ifdef __WXMSW__ 6 | 7 | #ifdef WXMAKINGDLL_DAP 8 | #define WXDLLIMPEXP_DAP __declspec(dllexport) 9 | #elif defined(WXUSINGDLL_DAP) 10 | #define WXDLLIMPEXP_DAP __declspec(dllimport) 11 | #else // not making nor using DLL 12 | #define WXDLLIMPEXP_DAP 13 | #endif 14 | 15 | #else // ! MSW 16 | 17 | #define WXDLLIMPEXP_DAP 18 | 19 | #endif 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /dap/dapcxx.project: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /dap/linux.cpp: -------------------------------------------------------------------------------- 1 | #if defined(__APPLE__) || defined(__linux__) 2 | 3 | #include "Log.hpp" 4 | #include "Process.hpp" 5 | #include "StringUtils.hpp" 6 | #include "UnixProcess.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace dap 12 | { 13 | Process* ExecuteProcess(const wxString& cmd, const wxString& workingDir) 14 | { 15 | std::vector args = DapStringUtils::BuildArgv(cmd); 16 | LOG_DEBUG() << "Starting process:" << args; 17 | UnixProcess* process = new UnixProcess(args); 18 | process->StartThreads(); 19 | process->SetProcessId(process->child_pid); 20 | return process; 21 | } 22 | 23 | }; // namespace dap 24 | #endif -------------------------------------------------------------------------------- /dap/msw.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef __WIN32__ 4 | #include "Process.hpp" 5 | #include "StringUtils.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | namespace dap 15 | { 16 | static bool CheckIsAlive(HANDLE hProcess) 17 | { 18 | DWORD dwExitCode; 19 | if (GetExitCodeProcess(hProcess, &dwExitCode)) { 20 | if (dwExitCode == STILL_ACTIVE) 21 | return true; 22 | } 23 | return false; 24 | } 25 | 26 | template 27 | bool WriteStdin(const T& buffer, HANDLE hStdin, HANDLE hProcess) 28 | { 29 | DWORD dwMode; 30 | 31 | // Make the pipe to non-blocking mode 32 | dwMode = PIPE_READMODE_BYTE | PIPE_NOWAIT; 33 | SetNamedPipeHandleState(hStdin, &dwMode, NULL, NULL); 34 | DWORD bytesLeft = buffer.length(); 35 | long offset = 0; 36 | size_t retryCount = 0; 37 | while (bytesLeft > 0 && (retryCount < 100)) { 38 | DWORD dwWritten = 0; 39 | if (!WriteFile(hStdin, buffer.c_str() + offset, bytesLeft, &dwWritten, NULL)) { 40 | return false; 41 | } 42 | if (!CheckIsAlive(hProcess)) { 43 | return false; 44 | } 45 | if (dwWritten == 0) { 46 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 47 | } 48 | bytesLeft -= dwWritten; 49 | offset += dwWritten; 50 | ++retryCount; 51 | } 52 | return true; 53 | } 54 | 55 | #define CLOSE_HANDLE(h) \ 56 | if (h != INVALID_HANDLE_VALUE) { \ 57 | CloseHandle(h); \ 58 | } \ 59 | h = INVALID_HANDLE_VALUE; 60 | 61 | class ProcessMSW : public Process 62 | { 63 | public: 64 | HANDLE m_stdinRead = INVALID_HANDLE_VALUE; 65 | HANDLE m_stdinWrite = INVALID_HANDLE_VALUE; 66 | HANDLE m_stdoutWrite = INVALID_HANDLE_VALUE; 67 | HANDLE m_stdoutRead = INVALID_HANDLE_VALUE; 68 | HANDLE m_stderrWrite = INVALID_HANDLE_VALUE; 69 | HANDLE m_stderrRead = INVALID_HANDLE_VALUE; 70 | 71 | DWORD m_dwProcessId = -1; 72 | PROCESS_INFORMATION m_piProcInfo; 73 | char m_buffer[65537]; 74 | 75 | protected: 76 | bool DoReadFromPipe(HANDLE pipe, std::string& buff); 77 | bool DoRead(std::string& ostrout, std::string& ostrerr) override; 78 | bool DoWrite(const std::string& str, bool appendLf); 79 | 80 | public: 81 | ProcessMSW() {} 82 | ~ProcessMSW() override { Cleanup(); } 83 | bool Write(const std::string& str) override; 84 | bool WriteLn(const std::string& str) override; 85 | bool IsAlive() const override; 86 | void Cleanup() override; 87 | void Terminate() override; 88 | }; 89 | 90 | void ProcessMSW::Cleanup() 91 | { 92 | Process::Cleanup(); 93 | if (IsAlive()) { 94 | TerminateProcess(m_piProcInfo.hProcess, 255); 95 | } 96 | CLOSE_HANDLE(m_piProcInfo.hProcess); 97 | CLOSE_HANDLE(m_piProcInfo.hThread); 98 | CLOSE_HANDLE(m_stdinRead); 99 | CLOSE_HANDLE(m_stdinWrite); 100 | CLOSE_HANDLE(m_stdoutWrite); 101 | CLOSE_HANDLE(m_stdoutRead); 102 | CLOSE_HANDLE(m_stderrWrite); 103 | CLOSE_HANDLE(m_stderrRead); 104 | } 105 | 106 | bool ProcessMSW::DoRead(std::string& ostrout, std::string& ostrerr) 107 | { 108 | if (!IsAlive()) { 109 | return false; 110 | } 111 | 112 | DoReadFromPipe(m_stdoutRead, ostrout); 113 | DoReadFromPipe(m_stderrRead, ostrerr); 114 | return !ostrerr.empty() || !ostrout.empty(); 115 | } 116 | 117 | Process* ExecuteProcess(const wxString& cmd, const wxString& workingDir) 118 | { 119 | UNUSED(workingDir); 120 | // Set the bInheritHandle flag so pipe handles are inherited. 121 | SECURITY_ATTRIBUTES saAttr; 122 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 123 | saAttr.bInheritHandle = TRUE; 124 | saAttr.lpSecurityDescriptor = NULL; 125 | 126 | ProcessMSW* prc = new ProcessMSW(); 127 | PROCESS_INFORMATION& process_info = prc->m_piProcInfo; 128 | 129 | // Save the handle to the current STDOUT. 130 | HANDLE savedStdout = GetStdHandle(STD_OUTPUT_HANDLE); 131 | HANDLE savedStderr = GetStdHandle(STD_ERROR_HANDLE); 132 | HANDLE savedStdin = GetStdHandle(STD_INPUT_HANDLE); 133 | 134 | // Create a pipe for the child process's STDOUT. 135 | if (!CreatePipe(&prc->m_stdoutRead, &prc->m_stdoutWrite, &saAttr, 0)) { 136 | delete prc; 137 | return nullptr; 138 | } 139 | 140 | // Create a pipe for the child process's STDERR. 141 | if (!CreatePipe(&prc->m_stderrRead, &prc->m_stderrWrite, &saAttr, 0)) { 142 | delete prc; 143 | return NULL; 144 | } 145 | // Create a pipe for the child process's STDIN. 146 | if (!CreatePipe(&prc->m_stdinRead, &prc->m_stdinWrite, &saAttr, 0)) { 147 | delete prc; 148 | return NULL; 149 | } 150 | 151 | // Execute the child process 152 | STARTUPINFO startup_info; 153 | ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); 154 | ZeroMemory(&startup_info, sizeof(STARTUPINFO)); 155 | 156 | startup_info.cb = sizeof(STARTUPINFO); 157 | startup_info.hStdInput = prc->m_stdinRead; 158 | startup_info.hStdOutput = prc->m_stdoutWrite; 159 | startup_info.hStdError = prc->m_stderrWrite; 160 | startup_info.dwFlags |= STARTF_USESTDHANDLES; 161 | BOOL ret = CreateProcess(NULL, 162 | cmd.wchar_str(), // shell line execution command 163 | NULL, // process security attributes 164 | NULL, // primary thread security attributes 165 | TRUE, // handles are inherited 166 | 0, // creation flags 167 | NULL, // use parent's environment 168 | NULL, // CD to tmp dir 169 | &startup_info, // STARTUPINFO pointer 170 | &prc->m_piProcInfo); // receives PROCESS_INFORMATION 171 | if (ret) { 172 | prc->m_dwProcessId = prc->m_piProcInfo.dwProcessId; 173 | } else { 174 | delete prc; 175 | return NULL; 176 | } 177 | 178 | prc->StartThreads(); 179 | prc->SetProcessId(prc->m_dwProcessId); 180 | return prc; 181 | } 182 | 183 | bool ProcessMSW::DoReadFromPipe(HANDLE pipe, std::string& buff) 184 | { 185 | DWORD dwRead; 186 | DWORD dwMode; 187 | DWORD dwTimeout; 188 | 189 | // Make the pipe to non-blocking mode 190 | dwMode = PIPE_READMODE_BYTE | PIPE_NOWAIT; 191 | dwTimeout = 1000; 192 | SetNamedPipeHandleState(pipe, &dwMode, NULL, &dwTimeout); 193 | 194 | bool read_something = false; 195 | while (true) { 196 | BOOL bRes = ReadFile(pipe, m_buffer, sizeof(m_buffer) - 1, &dwRead, NULL); 197 | if (bRes) { 198 | wxString tmpBuff; 199 | // Success read 200 | m_buffer[dwRead / sizeof(char)] = 0; 201 | tmpBuff = m_buffer; 202 | buff += tmpBuff; 203 | read_something = true; 204 | continue; 205 | } 206 | break; 207 | } 208 | return read_something; 209 | } 210 | 211 | bool ProcessMSW::Write(const std::string& buff) { return DoWrite(buff, false); } 212 | 213 | bool ProcessMSW::WriteLn(const std::string& buff) { return DoWrite(buff, true); } 214 | 215 | bool ProcessMSW::DoWrite(const std::string& buff, bool appendLf) 216 | { 217 | DWORD dwMode; 218 | DWORD dwTimeout; 219 | 220 | std::string tmpCmd = buff; 221 | DapStringUtils::Rtrim(tmpCmd); 222 | if (appendLf) { 223 | tmpCmd += "\n"; 224 | } 225 | // Make the pipe to non-blocking mode 226 | dwMode = PIPE_READMODE_BYTE | PIPE_NOWAIT; 227 | dwTimeout = 30000; 228 | UNUSED(dwTimeout); 229 | SetNamedPipeHandleState(m_stdinWrite, &dwMode, NULL, 230 | NULL); // Timeout of 30 seconds 231 | return WriteStdin(tmpCmd, m_stdinWrite, m_piProcInfo.hProcess); 232 | } 233 | 234 | bool ProcessMSW::IsAlive() const { return CheckIsAlive(m_piProcInfo.hProcess); } 235 | 236 | void ProcessMSW::Terminate() 237 | { 238 | // terminate and perform cleanup 239 | Cleanup(); 240 | } 241 | }; // namespace dap 242 | #endif //__WXMSW__ 243 | -------------------------------------------------------------------------------- /dbgcli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(dap_demo) 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS 1) 5 | 6 | find_package(wxWidgets COMPONENTS adv base core xml xrc net stc richtext REQUIRED) 7 | include( "${wxWidgets_USE_FILE}" ) 8 | 9 | if(APPLE) 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -stdlib=libc++") 11 | endif() 12 | 13 | include_directories(${CMAKE_SOURCE_DIR}) 14 | FILE(GLOB SRCS "*.cpp") 15 | 16 | add_executable(dap_demo WIN32 ${SRCS}) 17 | target_link_libraries(dap_demo dapcxx ${wxWidgets_LIBRARIES}) 18 | set_property(TARGET dapcxx PROPERTY CXX_STANDARD 17) 19 | set_property(TARGET dapcxx PROPERTY CXX_EXTENSIONS OFF) 20 | STRING(REPLACE "-std=c++11" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 21 | 22 | -------------------------------------------------------------------------------- /dbgcli/ConsoleApp.cpp: -------------------------------------------------------------------------------- 1 | #include "ConsoleApp.hpp" 2 | 3 | #include "MainFrame.hpp" 4 | #include "dap/Client.hpp" 5 | #include "dap/Exception.hpp" 6 | #include "dap/Log.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | IMPLEMENT_APP(DAPCli) 13 | 14 | DAPCli::DAPCli() 15 | : wxApp() 16 | { 17 | m_ExecutableFileName = wxEmptyString; 18 | } 19 | 20 | DAPCli::~DAPCli() {} 21 | 22 | void DAPCli::DoExitApp() {} 23 | 24 | bool DAPCli::OnInit() 25 | { 26 | SetAppName("DAPCli"); 27 | wxLog::EnableLogging(false); 28 | 29 | m_parser.SetCmdLine(wxAppConsole::argc, wxAppConsole::argv); 30 | if(!DoParseCommandLine()) 31 | return false; 32 | 33 | MainFrame* frame = new MainFrame(nullptr, m_ExecutableFileName); 34 | frame->Show(); 35 | SetTopWindow(frame); 36 | return true; 37 | } 38 | 39 | int DAPCli::OnExit() { return true; } 40 | int DAPCli::OnRun() { return wxApp::OnRun(); } 41 | 42 | bool DAPCli::DoParseCommandLine() 43 | { 44 | const wxCmdLineEntryDesc cmdLineDesc[] = { 45 | { wxCMD_LINE_SWITCH, "h", "help", "show this help message", wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP }, 46 | { wxCMD_LINE_SWITCH, "?", "?", "show this help message", wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP }, 47 | { wxCMD_LINE_PARAM, "", "", "Executable filename to debug", wxCMD_LINE_VAL_STRING, 48 | wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE }, 49 | { wxCMD_LINE_NONE } 50 | }; 51 | 52 | m_parser.SetDesc(cmdLineDesc); 53 | 54 | switch(m_parser.Parse()) { 55 | case -1: 56 | return false; // The parameter -h was passed, help was given, so abort the app 57 | case 0: 58 | break; // OK, so break to deal with any parameters etc 59 | default: 60 | return false; // Some syntax error occurred. Abort 61 | } 62 | 63 | int paramCount = m_parser.GetParamCount(); 64 | 65 | if(paramCount == 1) { 66 | const wxString& strParam = m_parser.GetParam(0); 67 | wxFileName fn(strParam); 68 | // Really important so that two same files with different names are not loaded 69 | // twice. Use the CurrentWorkingDirectory of the client instance to restore the 70 | // absolute path to the file. 71 | fn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_TILDE | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG | 72 | wxPATH_NORM_SHORTCUT); 73 | const wxString& paramFullPath = fn.GetFullPath(); 74 | 75 | if(wxFileName::FileExists(paramFullPath)) { 76 | m_ExecutableFileName = paramFullPath; 77 | } 78 | } 79 | 80 | return true; 81 | } 82 | -------------------------------------------------------------------------------- /dbgcli/ConsoleApp.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CONSOLEAPP_HPP 2 | #define CONSOLEAPP_HPP 3 | 4 | #include 5 | #include 6 | 7 | // ----------------------------------------------------------- 8 | // ----------------------------------------------------------- 9 | class DAPCli : public wxApp 10 | { 11 | wxCmdLineParser m_parser; 12 | wxString m_ExecutableFileName; 13 | 14 | protected: 15 | bool DoParseCommandLine(); 16 | 17 | public: 18 | DAPCli(); 19 | virtual ~DAPCli(); 20 | 21 | void DoExitApp(); 22 | bool OnInit() override; 23 | int OnExit() override; 24 | int OnRun() override; 25 | }; 26 | 27 | DECLARE_APP(DAPCli) 28 | 29 | #endif // CONSOLEAPP_HPP 30 | -------------------------------------------------------------------------------- /dbgcli/MainFrame.cpp: -------------------------------------------------------------------------------- 1 | #include "MainFrame.hpp" 2 | 3 | #include "dap/Log.hpp" 4 | #include "dap/Process.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace 13 | { 14 | 15 | struct Config { 16 | wxString debuggee; 17 | wxString debugger; 18 | 19 | static std::vector GetDebuggerCommand(const wxString& path) 20 | { 21 | if (path.Contains("gdb")) { 22 | return { path, "-i=dap" }; 23 | } else { 24 | return { path }; 25 | } 26 | } 27 | }; 28 | 29 | constexpr int MARKER_NUMBER = 4; 30 | 31 | void center_line(wxStyledTextCtrl* ctrl, int line = wxNOT_FOUND, bool add_marker = false) 32 | { 33 | if (line == wxNOT_FOUND) { 34 | line = ctrl->LineFromPosition(ctrl->GetLastPosition()); 35 | } 36 | int lines_on_screen = ctrl->LinesOnScreen(); 37 | int first_visible_line = line - lines_on_screen / 2; 38 | first_visible_line = wxMax(0, first_visible_line); 39 | 40 | int pos = ctrl->PositionFromLine(line); 41 | ctrl->SetCurrentPos(pos); 42 | ctrl->SetSelectionStart(pos); 43 | ctrl->SetSelectionEnd(pos); 44 | ctrl->SetAnchor(pos); 45 | ctrl->EnsureCaretVisible(); 46 | if (add_marker) { 47 | ctrl->MarkerDeleteAll(MARKER_NUMBER); 48 | ctrl->MarkerAdd(line, MARKER_NUMBER); 49 | } 50 | ctrl->SetFirstVisibleLine(first_visible_line); 51 | } 52 | 53 | void SaveConfig(const Config& config) 54 | { 55 | wxFileName conf_file{ wxStandardPaths::Get().GetUserDataDir(), "wxdap.json" }; 56 | conf_file.Mkdir(wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL); 57 | dap::Json root = dap::Json::CreateObject(); 58 | root.Add("debuggee", config.debuggee); 59 | root.Add("debugger", config.debugger); 60 | 61 | wxFFile fp(conf_file.GetFullPath(), "w+"); 62 | fp.Write(root.ToString()); 63 | fp.Close(); 64 | } 65 | 66 | Config ReadConfig() 67 | { 68 | wxFileName conf_file{ wxStandardPaths::Get().GetUserDataDir(), "wxdap.json" }; 69 | conf_file.Mkdir(wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL); 70 | wxFFile fp(conf_file.GetFullPath(), "r"); 71 | wxString content; 72 | fp.ReadAll(&content); 73 | fp.Close(); 74 | 75 | Config config; 76 | auto root = dap::Json::Parse(content); 77 | config.debuggee = root["debuggee"].GetString(); 78 | config.debugger = root["debugger"].GetString(); 79 | return config; 80 | } 81 | 82 | } // namespace 83 | 84 | MainFrame::MainFrame(wxWindow* parent, wxString executableFileName) 85 | : MainFrameBase(parent) 86 | , m_executableFileName(executableFileName) 87 | { 88 | #if defined(__WXMAC__) || defined(__WXGTK__) 89 | dap::Log::OpenLog("/tmp/dap_demo.log", dap::Log::Developer); 90 | LOG_INFO() << "DAP demo started" << dap::endl; 91 | #endif 92 | 93 | auto config = ReadConfig(); 94 | wxFont code_font = wxFont(wxFontInfo(12).Family(wxFONTFAMILY_TELETYPE)); 95 | 96 | m_ctrls = { m_stcLog, m_stcTextSourceFile, m_stcThreads, m_stcStack, m_stcScopes }; 97 | for (int i = 0; i < wxSTC_STYLE_MAX; ++i) { 98 | for (auto ctrl : m_ctrls) { 99 | ctrl->StyleSetFont(i, code_font); 100 | } 101 | } 102 | 103 | for (auto ctrl : m_ctrls) { 104 | ctrl->MarkerDefine(MARKER_NUMBER, wxSTC_MARK_ARROW, *wxGREEN, *wxGREEN); 105 | } 106 | 107 | m_filePickerDebugger->SetPath(config.debugger); 108 | m_filePickerSelectDebugFileName->SetPath(config.debuggee); 109 | 110 | if (!m_executableFileName.empty()) { 111 | m_filePickerSelectDebugFileName->SetPath(m_executableFileName); 112 | config.debuggee = m_executableFileName; 113 | SaveConfig(config); 114 | } else { 115 | m_executableFileName = config.debuggee; 116 | } 117 | 118 | // bind the client events 119 | m_client.Bind(wxEVT_DAP_STOPPED_EVENT, &MainFrame::OnStopped, this); 120 | m_client.Bind(wxEVT_DAP_INITIALIZED_EVENT, &MainFrame::OnInitializedEvent, this); 121 | m_client.Bind(wxEVT_DAP_INITIALIZE_RESPONSE, &MainFrame::OnInitializeResponse, this); 122 | m_client.Bind(wxEVT_DAP_EXITED_EVENT, &MainFrame::OnExited, this); 123 | m_client.Bind(wxEVT_DAP_TERMINATED_EVENT, &MainFrame::OnTerminated, this); 124 | m_client.Bind(wxEVT_DAP_STACKTRACE_RESPONSE, &MainFrame::OnStackTrace, this); 125 | m_client.Bind(wxEVT_DAP_SCOPES_RESPONSE, &MainFrame::OnScopes, this); 126 | m_client.Bind(wxEVT_DAP_VARIABLES_RESPONSE, &MainFrame::OnVariables, this); 127 | m_client.Bind(wxEVT_DAP_OUTPUT_EVENT, &MainFrame::OnOutput, this); 128 | m_client.Bind(wxEVT_DAP_BREAKPOINT_LOCATIONS_RESPONSE, &MainFrame::OnBreakpointLocations, this); 129 | m_client.Bind(wxEVT_DAP_LOST_CONNECTION, &MainFrame::OnConnectionError, this); 130 | m_client.Bind(wxEVT_DAP_SET_SOURCE_BREAKPOINT_RESPONSE, &MainFrame::OnBreakpointSet, this); 131 | m_client.Bind(wxEVT_DAP_SET_FUNCTION_BREAKPOINT_RESPONSE, &MainFrame::OnBreakpointSet, this); 132 | m_client.Bind(wxEVT_DAP_LAUNCH_RESPONSE, &MainFrame::OnLaunchResponse, this); 133 | m_client.Bind(wxEVT_DAP_RUN_IN_TERMINAL_REQUEST, &MainFrame::OnRunInTerminalRequest, this); 134 | m_client.Bind(wxEVT_DAP_LOG_EVENT, &MainFrame::OnDapLog, this); 135 | m_client.Bind(wxEVT_DAP_MODULE_EVENT, &MainFrame::OnDapModuleEvent, this); 136 | m_client.SetWantsLogEvents(true); // send use log events 137 | } 138 | 139 | MainFrame::~MainFrame() 140 | { 141 | // Store the configuration 142 | Config config; 143 | config.debugger = m_filePickerDebugger->GetPath(); 144 | config.debuggee = m_filePickerSelectDebugFileName->GetPath(); 145 | SaveConfig(config); 146 | } 147 | 148 | /// Initialize the DAP client: 149 | /// - Bind events 150 | /// - Connect 151 | /// - And launch our debuggee process 152 | void MainFrame::InitializeClient() 153 | { 154 | // Reset the client 155 | m_client.Reset(); 156 | 157 | wxBusyCursor cursor; 158 | // For this demo, we use socket transport. But you may choose 159 | // to write your own transport that implements the dap::Transport interface 160 | // This is useful when the user wishes to use stdin/out for communicating with 161 | // the dap and not over socket 162 | dap::StdoutTransport* transport = new dap::StdoutTransport(); 163 | auto command = Config::GetDebuggerCommand(m_filePickerDebugger->GetPath()); 164 | transport->Execute(command); 165 | // if (!transport->Connect("tcp://127.0.0.1:4711", 10)) { 166 | // wxMessageBox("Failed to connect to DAP server", "DAP Demo", wxICON_ERROR | wxOK | wxCENTRE); 167 | // exit(1); 168 | // } 169 | 170 | // construct new client with the transport 171 | m_client.SetTransport(transport); 172 | 173 | // The protocol starts by us sending an initialize request 174 | dap::InitializeRequestArguments args; 175 | args.linesStartAt1 = true; 176 | 177 | m_frame_id = wxNOT_FOUND; 178 | m_current_source = {}; 179 | m_client.Initialize(&args); 180 | } 181 | 182 | void MainFrame::OnNext(wxCommandEvent& event) 183 | { 184 | wxUnusedVar(event); 185 | m_client.Next(); 186 | } 187 | 188 | void MainFrame::OnStepIn(wxCommandEvent& event) 189 | { 190 | wxUnusedVar(event); 191 | m_client.StepIn(); 192 | } 193 | 194 | void MainFrame::OnStepOut(wxCommandEvent& event) 195 | { 196 | wxUnusedVar(event); 197 | m_client.StepOut(); 198 | } 199 | 200 | void MainFrame::OnAttach(wxCommandEvent& event) 201 | { 202 | wxUnusedVar(event); 203 | m_attaching = true; 204 | InitializeClient(); 205 | } 206 | 207 | void MainFrame::OnConnect(wxCommandEvent& event) 208 | { 209 | wxUnusedVar(event); 210 | m_attaching = false; 211 | InitializeClient(); 212 | } 213 | 214 | /// ---------------------------------- 215 | /// -- DAP EVENTS START -- 216 | /// ---------------------------------- 217 | 218 | void MainFrame::OnLaunchResponse(DAPEvent& event) 219 | { 220 | // Check that the debugee was started successfully 221 | dap::LaunchResponse* resp = event.GetDapResponse()->As(); 222 | if (resp && !resp->success) { 223 | // launch failed! 224 | wxMessageBox("Failed to launch debuggee: " + resp->message, "DAP", 225 | wxICON_ERROR | wxOK | wxOK_DEFAULT | wxCENTRE); 226 | m_client.CallAfter(&dap::Client::Reset); 227 | } 228 | } 229 | 230 | /// DAP server responded to our `initialize` request 231 | void MainFrame::OnInitializeResponse(DAPEvent& event) 232 | { 233 | wxUnusedVar(event); 234 | if (m_attaching) { 235 | AddLog("Attaching to dap server"); 236 | m_client.Attach({}); 237 | 238 | } else { 239 | AddLog("Launching program: " + m_executableFileName); 240 | 241 | // On Windows, gdb requires paths to be using forward slash "/" 242 | bool is_gdb = m_filePickerDebugger->GetPath().Contains("gdb"); 243 | wxString exe = m_executableFileName; 244 | if (is_gdb) { 245 | exe.Replace("\\", "/"); 246 | } 247 | 248 | m_client.Launch({ exe }, ::wxGetCwd()); 249 | } 250 | } 251 | 252 | void MainFrame::OnInitializedEvent(DAPEvent& event) 253 | { 254 | // got initialized event, place breakpoints and continue 255 | AddLog("Got Initialized event"); 256 | AddLog("Placing breakpoint at main..."); 257 | 258 | // Set breakpoint on "main" 259 | m_client.SetFunctionBreakpoints({ { "main" } }); 260 | m_client.ConfigurationDone(); 261 | } 262 | 263 | /// DAP server stopped. This can happen for multiple reasons: 264 | /// - exception 265 | /// - breakpoint hit 266 | /// - step (user previously issued `Next` command) 267 | void MainFrame::OnStopped(DAPEvent& event) 268 | { 269 | // got stopped event 270 | dap::StoppedEvent* stopped_data = event.GetDapEvent()->As(); 271 | if (stopped_data) { 272 | AddLog(wxString() << "Stopped reason:" << stopped_data->reason); 273 | AddLog(wxString() << "All threads stopped:" << stopped_data->allThreadsStopped); 274 | AddLog(wxString() << "Stopped thread ID:" << stopped_data->threadId 275 | << "(active thread ID:" << m_client.GetActiveThreadId() << ")"); 276 | 277 | m_client.GetFrames(); 278 | } 279 | } 280 | 281 | /// Received a response to `GetFrames()` call 282 | void MainFrame::OnScopes(DAPEvent& event) 283 | { 284 | m_stcScopes->AppendText("-- Requesting variables for scopes --\n"); 285 | dap::ScopesResponse* resp = event.GetDapResponse()->As(); 286 | if (resp) { 287 | wxString scopes_display = "["; 288 | for (const auto& scope : resp->scopes) { 289 | scopes_display << scope.name << " id: " << scope.variablesReference << ", "; 290 | m_client.GetChildrenVariables(scope.variablesReference); 291 | } 292 | scopes_display.RemoveLast(2); 293 | scopes_display << "]\n"; 294 | m_stcScopes->AppendText(scopes_display); 295 | } 296 | center_line(m_stcScopes); 297 | } 298 | 299 | void MainFrame::OnVariables(DAPEvent& event) 300 | { 301 | dap::VariablesResponse* resp = event.GetDapResponse()->As(); 302 | if (resp) { 303 | for (const auto& var : resp->variables) { 304 | wxString button = (var.variablesReference > 0 ? "> " : " "); 305 | wxString value = var.value.empty() ? "\"\"" : var.value; 306 | button << " [ref: " << resp->refId << "] "; 307 | m_stcScopes->AppendText(wxString() << button << "(" << var.variablesReference << ") " << var.name << " = " 308 | << value << "\n"); 309 | } 310 | } 311 | center_line(m_stcScopes); 312 | } 313 | 314 | /// Received a response to `GetFrames()` call 315 | void MainFrame::OnStackTrace(DAPEvent& event) 316 | { 317 | dap::StackTraceResponse* stack_trace_data = event.GetDapResponse()->As(); 318 | if (stack_trace_data) { 319 | m_stcStack->ClearAll(); 320 | AddLog("Received stack trace event"); 321 | if (!stack_trace_data->stackFrames.empty()) { 322 | LoadFile(stack_trace_data->stackFrames[0].source, 323 | stack_trace_data->stackFrames[0].line - 1 /* 0 based lines*/); 324 | 325 | m_frame_id = stack_trace_data->stackFrames[0].id; 326 | 327 | // request the scopes for the first stack 328 | m_client.GetScopes(stack_trace_data->stackFrames[0].id); 329 | } 330 | 331 | for (const auto& stack : stack_trace_data->stackFrames) { 332 | m_stcStack->AppendText(wxString() << stack.id << "," << stack.name << "," << stack.source.path << "," 333 | << stack.line << "\n"); 334 | } 335 | } 336 | } 337 | 338 | /// Debuggee process exited, print the exit code 339 | void MainFrame::OnExited(DAPEvent& event) 340 | { 341 | AddLog(wxString() << "Debuggee exited. Exit code:" << event.GetDapEvent()->As()->exitCode); 342 | } 343 | 344 | /// Debug session terminated 345 | void MainFrame::OnTerminated(DAPEvent& event) 346 | { 347 | wxUnusedVar(event); 348 | AddLog(wxString() << "Session terminated!"); 349 | m_client.Reset(); 350 | m_current_source = {}; 351 | m_frame_id = wxNOT_FOUND; 352 | } 353 | 354 | void MainFrame::OnOutput(DAPEvent& event) 355 | { 356 | dap::OutputEvent* output_data = event.GetDapEvent()->As(); 357 | if (output_data) { 358 | AddLog(wxString() << output_data->category << ":" << output_data->output); 359 | } 360 | } 361 | 362 | void MainFrame::OnBreakpointLocations(DAPEvent& event) 363 | { 364 | dap::BreakpointLocationsResponse* d = event.GetDapResponse()->As(); 365 | if (d) { 366 | AddLog(_("==> Breakpoints:\n")); 367 | for (const auto& bp : d->breakpoints) { 368 | AddLog(wxString() << d->filepath << ":" << bp.line); 369 | } 370 | } 371 | } 372 | 373 | void MainFrame::OnConnectionError(DAPEvent& event) 374 | { 375 | wxUnusedVar(event); 376 | wxMessageBox(_("Lost connection to dap server")); 377 | } 378 | 379 | void MainFrame::OnBreakpointSet(DAPEvent& event) 380 | { 381 | dap::SetBreakpointsResponse* resp = event.GetDapResponse()->As(); 382 | auto request = event.GetOriginatingRequest(); 383 | if (!request) { 384 | return; 385 | } 386 | auto set_func_bp_req = request->As(); 387 | auto set_bp_req = request->As(); 388 | if (!set_func_bp_req && !set_bp_req) 389 | return; 390 | if (set_func_bp_req) { 391 | for (const auto& bp : set_func_bp_req->arguments.breakpoints) { 392 | AddLog("Got reply for SetFunctionBreakpoints command for function: " + bp.name); 393 | } 394 | 395 | } else if (set_bp_req) { 396 | AddLog("Got reply for setBreakpoint command for file: " + set_bp_req->arguments.source.path); 397 | for (const auto& bp : resp->breakpoints) { 398 | wxString message; 399 | message << "ID: " << bp.id << ". Verified: " << bp.verified 400 | << ". File: " << (bp.source.path.empty() ? set_bp_req->arguments.source.path : bp.source.path) 401 | << ". Line: " << bp.line; 402 | AddLog(message); 403 | } 404 | } 405 | } 406 | 407 | void MainFrame::OnDapLog(DAPEvent& event) { AddLog(event.GetString()); } 408 | void MainFrame::OnDapModuleEvent(DAPEvent& event) 409 | { 410 | AddLog("Got MODULE event!"); 411 | auto event_data = event.GetDapEvent()->As(); 412 | if (!event_data) 413 | return; 414 | 415 | wxString log_entry; 416 | log_entry << event_data->module.id << ": " << event_data->module.name << " " << event_data->module.symbolStatus 417 | << event_data->module.version << " " << event_data->module.path; 418 | AddLog(log_entry); 419 | } 420 | 421 | void MainFrame::OnRunInTerminalRequest(DAPEvent& event) 422 | { 423 | AddLog("Handling `OnRunInTerminalRequest` event"); 424 | auto request = event.GetDapRequest()->As(); 425 | if (!request) { 426 | return; 427 | } 428 | wxString command; 429 | for (const wxString& cmd : request->arguments.args) { 430 | command << cmd << " "; 431 | } 432 | 433 | AddLog("Starting process: " + command); 434 | m_process = dap::ExecuteProcess(command); 435 | auto response = m_client.MakeRequest(); 436 | response->request_seq = request->seq; 437 | if (!m_process) { 438 | response->success = false; 439 | response->processId = 0; 440 | } else { 441 | response->success = true; 442 | response->processId = m_process->GetProcessId(); 443 | } 444 | m_client.SendResponse(*response); 445 | wxDELETE(response); 446 | } 447 | 448 | /// ---------------------------------- 449 | /// -- DAP EVENTS END -- 450 | /// ---------------------------------- 451 | 452 | void MainFrame::AddLog(const wxString& log) 453 | { 454 | m_stcLog->AppendText(log + "\n"); 455 | center_line(m_stcLog); 456 | } 457 | 458 | void MainFrame::LoadFile(const dap::Source& sourceId, int line_number) 459 | { 460 | // easy path 461 | if (sourceId == m_current_source) { 462 | center_line(m_stcTextSourceFile, line_number, true); 463 | return; 464 | } 465 | 466 | if (!m_client.LoadSource( 467 | sourceId, [this, sourceId, line_number](bool success, const wxString& content, const wxString& mimeType) { 468 | if (!success) { 469 | return; 470 | } 471 | m_current_source = sourceId; 472 | m_stcTextSourceFile->SetText(content); 473 | center_line(m_stcTextSourceFile, line_number, true); 474 | })) { 475 | // not a server file, load it locally 476 | wxFileName fp(sourceId.path); 477 | 478 | // the is already loaded 479 | wxString file_to_load = fp.GetFullPath(); 480 | AddLog(wxString() << "Loading file.." << file_to_load); 481 | wxFileName fn(file_to_load); 482 | if (fn.FileExists()) { 483 | m_current_source = sourceId; 484 | m_stcTextSourceFile->LoadFile(fn.GetFullPath()); 485 | center_line(m_stcTextSourceFile, line_number, true); 486 | } 487 | } 488 | } 489 | 490 | void MainFrame::OnNextUI(wxUpdateUIEvent& event) { event.Enable(m_client.IsConnected() && m_client.CanInteract()); } 491 | void MainFrame::OnStepInUI(wxUpdateUIEvent& event) { event.Enable(m_client.IsConnected() && m_client.CanInteract()); } 492 | void MainFrame::OnStepOutUI(wxUpdateUIEvent& event) { event.Enable(m_client.IsConnected() && m_client.CanInteract()); } 493 | void MainFrame::OnConnectUI(wxUpdateUIEvent& event) { event.Enable(!m_client.IsConnected()); } 494 | void MainFrame::OnPause(wxCommandEvent& event) 495 | { 496 | wxUnusedVar(event); 497 | m_client.Pause(); 498 | } 499 | 500 | void MainFrame::OnPauseUI(wxUpdateUIEvent& event) { event.Enable(m_client.IsConnected() && !m_client.CanInteract()); } 501 | void MainFrame::OnContinueUI(wxUpdateUIEvent& event) { event.Enable(m_client.IsConnected() && m_client.CanInteract()); } 502 | void MainFrame::OnContinue(wxCommandEvent& event) 503 | { 504 | wxUnusedVar(event); 505 | m_client.Continue(); 506 | } 507 | 508 | void MainFrame::OnDebugFileNameChanged(wxFileDirPickerEvent& evt) { evt.Skip(); } 509 | 510 | void MainFrame::OnSetBreakpointUI(wxUpdateUIEvent& event) 511 | { 512 | event.Enable(m_client.IsConnected() && m_client.CanInteract()); 513 | } 514 | 515 | void MainFrame::OnSetBreakpoint(wxCommandEvent& event) 516 | { 517 | wxString location = 518 | wxGetTextFromUser("Set breakpoint", "Location", 519 | wxString() << m_current_source.path << ":" << (m_stcTextSourceFile->GetCurrentLine() + 1)); 520 | if (location.empty()) { 521 | return; 522 | } 523 | 524 | if (location.Contains(":")) { 525 | // file:line 526 | wxString file = location.BeforeLast(':'); 527 | file.Trim().Trim(false); 528 | 529 | long line = wxNOT_FOUND; 530 | location.AfterLast(':').ToCLong(&line); 531 | m_client.SetBreakpointsFile(file, { { static_cast(line), wxEmptyString } }); 532 | 533 | } else { 534 | // function 535 | m_client.SetFunctionBreakpoints({ { location, wxEmptyString } }); 536 | } 537 | } 538 | void MainFrame::OnEval(wxCommandEvent& event) 539 | { 540 | wxString text = wxGetTextFromUser("Expression", "Evaluate expression", m_stcTextSourceFile->GetSelectedText()); 541 | if (text.empty()) { 542 | return; 543 | } 544 | 545 | m_client.EvaluateExpression( 546 | text, m_frame_id, dap::EvaluateContext::HOVER, 547 | [this, text](bool success, const wxString& result, const wxString& type, int variablesReference) { 548 | wxString output; 549 | if (!success) { 550 | output << "ERROR: failed to evaluate expression: `" << text << "`"; 551 | AddLog(output); 552 | return; 553 | } 554 | 555 | output << text << " = " << result << " [" << type << "]. variablesReference: " << variablesReference; 556 | AddLog(output); 557 | }); 558 | } 559 | 560 | void MainFrame::OnEvalUI(wxUpdateUIEvent& event) { event.Enable(m_client.IsConnected() && m_client.CanInteract()); } 561 | void MainFrame::OnAttachUI(wxUpdateUIEvent& event) { event.Enable(!m_client.IsConnected()); } 562 | void MainFrame::OnClear(wxCommandEvent& event) 563 | { 564 | for (auto ctrl : m_ctrls) { 565 | ctrl->ClearAll(); 566 | } 567 | } 568 | 569 | void MainFrame::OnExit(wxCommandEvent& event) 570 | { 571 | event.Skip(); 572 | Close(); 573 | } 574 | -------------------------------------------------------------------------------- /dbgcli/MainFrame.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MAINFRAME_HPP 2 | #define MAINFRAME_HPP 3 | 4 | #include "UI.hpp" 5 | #include "dap/Client.hpp" 6 | #include "dap/DAPEvent.hpp" 7 | #include "dap/Process.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class MainFrame : public MainFrameBase 14 | { 15 | dap::Client m_client; 16 | wxString m_executableFileName; 17 | dap::Source m_current_source; 18 | std::vector m_ctrls; 19 | dap::Process* m_process = nullptr; 20 | int m_frame_id = wxNOT_FOUND; 21 | bool m_attaching = false; 22 | 23 | public: 24 | MainFrame(wxWindow* parent, wxString executableFileName); 25 | ~MainFrame() override; 26 | 27 | protected: 28 | void OnClear(wxCommandEvent& event) override; 29 | void OnExit(wxCommandEvent& event) override; 30 | void OnAttach(wxCommandEvent& event) override; 31 | void OnAttachUI(wxUpdateUIEvent& event) override; 32 | void OnEval(wxCommandEvent& event) override; 33 | void OnEvalUI(wxUpdateUIEvent& event) override; 34 | void InitializeClient(); 35 | void AddLog(const wxString& log); 36 | void LoadFile(const dap::Source& sourceId, int line_number); 37 | 38 | protected: 39 | void OnSetBreakpoint(wxCommandEvent& event) override; 40 | void OnSetBreakpointUI(wxUpdateUIEvent& event) override; 41 | void OnPause(wxCommandEvent& event) override; 42 | void OnPauseUI(wxUpdateUIEvent& event) override; 43 | void OnConnectUI(wxUpdateUIEvent& event) override; 44 | void OnNextUI(wxUpdateUIEvent& event) override; 45 | void OnStepInUI(wxUpdateUIEvent& event) override; 46 | void OnStepOutUI(wxUpdateUIEvent& event) override; 47 | void OnConnect(wxCommandEvent& event) override; 48 | void OnNext(wxCommandEvent& event) override; 49 | void OnStepIn(wxCommandEvent& event) override; 50 | void OnStepOut(wxCommandEvent& event) override; 51 | void OnContinue(wxCommandEvent& event) override; 52 | void OnContinueUI(wxUpdateUIEvent& event) override; 53 | 54 | void OnDebugFileNameChanged(wxFileDirPickerEvent& evt); 55 | 56 | /// Dap events 57 | void OnStopped(DAPEvent& event); 58 | void OnStackTrace(DAPEvent& event); 59 | void OnScopes(DAPEvent& event); 60 | void OnVariables(DAPEvent& event); 61 | void OnInitializedEvent(DAPEvent& event); 62 | void OnInitializeResponse(DAPEvent& event); 63 | void OnExited(DAPEvent& event); 64 | void OnTerminated(DAPEvent& event); 65 | void OnOutput(DAPEvent& event); 66 | void OnBreakpointLocations(DAPEvent& event); 67 | void OnConnectionError(DAPEvent& event); 68 | void OnBreakpointSet(DAPEvent& event); 69 | void OnLaunchResponse(DAPEvent& event); 70 | void OnRunInTerminalRequest(DAPEvent& event); 71 | void OnDapLog(DAPEvent& event); 72 | void OnDapModuleEvent(DAPEvent& event); 73 | }; 74 | #endif // MAINFRAME_HPP 75 | -------------------------------------------------------------------------------- /dbgcli/UI.cpp: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // This file was auto-generated by codelite's wxCrafter Plugin 3 | // wxCrafter project file: UI.wxcp 4 | // Do not modify this file by hand! 5 | ////////////////////////////////////////////////////////////////////// 6 | 7 | #include "UI.hpp" 8 | 9 | // Declare the bitmap loading function 10 | extern void wxC10A1InitBitmapResources(); 11 | 12 | namespace 13 | { 14 | // return the wxBORDER_SIMPLE that matches the current application theme 15 | wxBorder get_border_simple_theme_aware_bit() 16 | { 17 | #if wxVERSION_NUMBER >= 3300 && defined(__WXMSW__) 18 | return wxSystemSettings::GetAppearance().IsDark() ? wxBORDER_SIMPLE : wxBORDER_DEFAULT; 19 | #else 20 | return wxBORDER_DEFAULT; 21 | #endif 22 | } // get_border_simple_theme_aware_bit 23 | bool bBitmapLoaded = false; 24 | } // namespace 25 | 26 | MainFrameBase::MainFrameBase(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, 27 | const wxSize& size, long style) 28 | : wxFrame(parent, id, title, pos, size, style) 29 | { 30 | if (!bBitmapLoaded) { 31 | // We need to initialise the default bitmap handler 32 | wxXmlResource::Get()->AddHandler(new wxBitmapXmlHandler); 33 | wxC10A1InitBitmapResources(); 34 | bBitmapLoaded = true; 35 | } 36 | 37 | m_menuBar59 = new wxMenuBar(0); 38 | this->SetMenuBar(m_menuBar59); 39 | 40 | m_menu60 = new wxMenu(); 41 | m_menuBar59->Append(m_menu60, _("&File")); 42 | 43 | m_menuItem61 = new wxMenuItem(m_menu60, wxID_CLEAR, _("Clear"), wxT(""), wxITEM_NORMAL); 44 | m_menu60->Append(m_menuItem61); 45 | 46 | m_menu60->AppendSeparator(); 47 | 48 | m_menuItem62 = new wxMenuItem(m_menu60, wxID_EXIT, _("&Exit"), wxT(""), wxITEM_NORMAL); 49 | m_menu60->Append(m_menuItem62); 50 | 51 | m_toolbar12 = this->CreateToolBar(wxTB_HORZ_TEXT | wxTB_NOICONS | wxTB_FLAT, wxID_ANY); 52 | m_toolbar12->SetToolBitmapSize(wxSize(16, 16)); 53 | 54 | m_toolbar12->AddTool(wxID_NETWORK, _("Launch"), wxNullBitmap, wxNullBitmap, wxITEM_NORMAL, wxT(""), wxT(""), NULL); 55 | 56 | m_toolbar12->AddTool(ID_ATTACH, _("Attach"), wxNullBitmap, wxNullBitmap, wxITEM_NORMAL, wxT(""), wxT(""), NULL); 57 | 58 | m_toolbar12->AddSeparator(); 59 | 60 | m_toolbar12->AddTool(wxID_FORWARD, _("Next"), 61 | wxArtProvider::GetBitmap(wxART_GO_DOWN, wxART_TOOLBAR, wxSize(24, 24)), wxNullBitmap, 62 | wxITEM_NORMAL, wxT(""), wxT(""), NULL); 63 | 64 | m_toolbar12->AddTool(wxID_DOWN, _("Step In"), 65 | wxArtProvider::GetBitmap(wxART_GO_FORWARD, wxART_TOOLBAR, wxSize(24, 24)), wxNullBitmap, 66 | wxITEM_NORMAL, wxT(""), wxT(""), NULL); 67 | 68 | m_toolbar12->AddTool(wxID_UP, _("Step Out"), wxArtProvider::GetBitmap(wxART_GO_BACK, wxART_TOOLBAR, wxSize(24, 24)), 69 | wxNullBitmap, wxITEM_NORMAL, wxT(""), wxT(""), NULL); 70 | 71 | m_toolbar12->AddTool(wxID_EXECUTE, _("Continue"), wxNullBitmap, wxNullBitmap, wxITEM_NORMAL, wxT(""), wxT(""), 72 | NULL); 73 | 74 | m_toolbar12->AddSeparator(); 75 | 76 | m_toolbar12->AddTool(wxID_ABORT, _("Pause"), wxNullBitmap, wxNullBitmap, wxITEM_NORMAL, wxT(""), wxT(""), NULL); 77 | 78 | m_toolbar12->AddSeparator(); 79 | 80 | m_toolbar12->AddTool(wxID_STOP, _("Set Breakpoint..."), wxNullBitmap, wxNullBitmap, wxITEM_NORMAL, wxT(""), wxT(""), 81 | NULL); 82 | 83 | m_toolbar12->AddSeparator(); 84 | 85 | m_toolbar12->AddTool(wxID_INFO, _("Evaluate"), wxNullBitmap, wxNullBitmap, wxITEM_NORMAL, wxT(""), wxT(""), NULL); 86 | m_toolbar12->Realize(); 87 | 88 | wxBoxSizer* boxSizer1 = new wxBoxSizer(wxVERTICAL); 89 | this->SetSizer(boxSizer1); 90 | 91 | m_panel2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(this, wxSize(-1, -1)), wxTAB_TRAVERSAL); 92 | 93 | boxSizer1->Add(m_panel2, 1, wxEXPAND, WXC_FROM_DIP(5)); 94 | 95 | wxBoxSizer* boxSizer48 = new wxBoxSizer(wxVERTICAL); 96 | m_panel2->SetSizer(boxSizer48); 97 | 98 | wxFlexGridSizer* flexGridSizer51 = new wxFlexGridSizer(0, 2, 0, 0); 99 | flexGridSizer51->SetFlexibleDirection(wxBOTH); 100 | flexGridSizer51->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); 101 | flexGridSizer51->AddGrowableCol(1); 102 | 103 | boxSizer48->Add(flexGridSizer51, 0, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 104 | 105 | m_staticTextSelectDebugFileName = new wxStaticText(m_panel2, wxID_ANY, _("Executable to debug:"), wxDefaultPosition, 106 | wxDLG_UNIT(m_panel2, wxSize(-1, -1)), 0); 107 | 108 | flexGridSizer51->Add(m_staticTextSelectDebugFileName, 0, wxLEFT | wxTOP | wxBOTTOM | wxALIGN_RIGHT, 109 | WXC_FROM_DIP(5)); 110 | 111 | m_filePickerSelectDebugFileName = 112 | new wxFilePickerCtrl(m_panel2, wxID_ANY, wxEmptyString, _("Select a file"), wxT("*"), wxDefaultPosition, 113 | wxDLG_UNIT(m_panel2, wxSize(600, -1)), wxFLP_DEFAULT_STYLE | wxFLP_SMALL); 114 | 115 | flexGridSizer51->Add(m_filePickerSelectDebugFileName, 1, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 116 | 117 | m_staticText64 = new wxStaticText(m_panel2, wxID_ANY, _("Debugger:"), wxDefaultPosition, 118 | wxDLG_UNIT(m_panel2, wxSize(-1, -1)), 0); 119 | 120 | flexGridSizer51->Add(m_staticText64, 0, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, WXC_FROM_DIP(5)); 121 | 122 | m_filePickerDebugger = 123 | new wxFilePickerCtrl(m_panel2, wxID_ANY, wxEmptyString, _("Select a file"), wxT("*"), wxDefaultPosition, 124 | wxDLG_UNIT(m_panel2, wxSize(-1, -1)), wxFLP_DEFAULT_STYLE | wxFLP_SMALL); 125 | 126 | flexGridSizer51->Add(m_filePickerDebugger, 0, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 127 | 128 | m_splitter4 = new wxSplitterWindow(m_panel2, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(m_panel2, wxSize(-1, -1)), 129 | wxSP_LIVE_UPDATE | wxSP_NO_XP_THEME | wxSP_3DSASH); 130 | m_splitter4->SetSashGravity(0.5); 131 | m_splitter4->SetMinimumPaneSize(10); 132 | 133 | boxSizer48->Add(m_splitter4, 1, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 134 | 135 | m_splitterPageSourceFile = 136 | new wxPanel(m_splitter4, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(m_splitter4, wxSize(-1, -1)), wxTAB_TRAVERSAL); 137 | 138 | wxBoxSizer* boxSizerSourceFile = new wxBoxSizer(wxVERTICAL); 139 | m_splitterPageSourceFile->SetSizer(boxSizerSourceFile); 140 | 141 | m_stcTextSourceFile = new wxStyledTextCtrl(m_splitterPageSourceFile, wxID_ANY, wxDefaultPosition, 142 | wxDLG_UNIT(m_splitterPageSourceFile, wxSize(-1, -1)), 0); 143 | // Configure the fold margin 144 | m_stcTextSourceFile->SetMarginType(4, wxSTC_MARGIN_SYMBOL); 145 | m_stcTextSourceFile->SetMarginMask(4, wxSTC_MASK_FOLDERS); 146 | m_stcTextSourceFile->SetMarginSensitive(4, true); 147 | m_stcTextSourceFile->SetMarginWidth(4, 0); 148 | 149 | // Configure the tracker margin 150 | m_stcTextSourceFile->SetMarginWidth(1, 0); 151 | 152 | // Configure the symbol margin 153 | m_stcTextSourceFile->SetMarginType(2, wxSTC_MARGIN_SYMBOL); 154 | m_stcTextSourceFile->SetMarginMask(2, ~(wxSTC_MASK_FOLDERS)); 155 | m_stcTextSourceFile->SetMarginWidth(2, 0); 156 | m_stcTextSourceFile->SetMarginSensitive(2, true); 157 | 158 | // Configure the line numbers margin 159 | int m_stcTextSourceFile_PixelWidth = 4 + 5 * m_stcTextSourceFile->TextWidth(wxSTC_STYLE_LINENUMBER, wxT("9")); 160 | m_stcTextSourceFile->SetMarginType(0, wxSTC_MARGIN_NUMBER); 161 | m_stcTextSourceFile->SetMarginWidth(0, m_stcTextSourceFile_PixelWidth); 162 | 163 | // Configure the line symbol margin 164 | m_stcTextSourceFile->SetMarginType(3, wxSTC_MARGIN_FORE); 165 | m_stcTextSourceFile->SetMarginMask(3, 0); 166 | m_stcTextSourceFile->SetMarginWidth(3, 0); 167 | // Select the lexer 168 | m_stcTextSourceFile->SetLexer(wxSTC_LEX_NULL); 169 | // Set default font / styles 170 | m_stcTextSourceFile->StyleClearAll(); 171 | m_stcTextSourceFile->SetWrapMode(0); 172 | m_stcTextSourceFile->SetIndentationGuides(0); 173 | m_stcTextSourceFile->SetKeyWords(0, wxT("")); 174 | m_stcTextSourceFile->SetKeyWords(1, wxT("")); 175 | m_stcTextSourceFile->SetKeyWords(2, wxT("")); 176 | m_stcTextSourceFile->SetKeyWords(3, wxT("")); 177 | m_stcTextSourceFile->SetKeyWords(4, wxT("")); 178 | 179 | boxSizerSourceFile->Add(m_stcTextSourceFile, 1, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 180 | 181 | m_splitterPageDAPDebugInfo = 182 | new wxPanel(m_splitter4, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(m_splitter4, wxSize(-1, -1)), wxTAB_TRAVERSAL); 183 | m_splitter4->SplitHorizontally(m_splitterPageSourceFile, m_splitterPageDAPDebugInfo, 0); 184 | 185 | wxBoxSizer* boxSizerDAPDebugInfo = new wxBoxSizer(wxVERTICAL); 186 | m_splitterPageDAPDebugInfo->SetSizer(boxSizerDAPDebugInfo); 187 | 188 | m_notebookDAPDebugInfo = new wxNotebook(m_splitterPageDAPDebugInfo, wxID_ANY, wxDefaultPosition, 189 | wxDLG_UNIT(m_splitterPageDAPDebugInfo, wxSize(-1, -1)), wxBK_DEFAULT); 190 | m_notebookDAPDebugInfo->SetName(wxT("m_notebookDAPDebugInfo")); 191 | 192 | boxSizerDAPDebugInfo->Add(m_notebookDAPDebugInfo, 1, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 193 | 194 | m_panel18 = new wxPanel(m_notebookDAPDebugInfo, wxID_ANY, wxDefaultPosition, 195 | wxDLG_UNIT(m_notebookDAPDebugInfo, wxSize(-1, -1)), wxTAB_TRAVERSAL); 196 | m_notebookDAPDebugInfo->AddPage(m_panel18, _("Stack"), false); 197 | 198 | wxBoxSizer* boxSizer20 = new wxBoxSizer(wxVERTICAL); 199 | m_panel18->SetSizer(boxSizer20); 200 | 201 | m_stcStack = new wxStyledTextCtrl(m_panel18, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(m_panel18, wxSize(-1, -1)), 0); 202 | // Configure the fold margin 203 | m_stcStack->SetMarginType(4, wxSTC_MARGIN_SYMBOL); 204 | m_stcStack->SetMarginMask(4, wxSTC_MASK_FOLDERS); 205 | m_stcStack->SetMarginSensitive(4, true); 206 | m_stcStack->SetMarginWidth(4, 0); 207 | 208 | // Configure the tracker margin 209 | m_stcStack->SetMarginWidth(1, 0); 210 | 211 | // Configure the symbol margin 212 | m_stcStack->SetMarginType(2, wxSTC_MARGIN_SYMBOL); 213 | m_stcStack->SetMarginMask(2, ~(wxSTC_MASK_FOLDERS)); 214 | m_stcStack->SetMarginWidth(2, 0); 215 | m_stcStack->SetMarginSensitive(2, true); 216 | 217 | // Configure the line numbers margin 218 | int m_stcStack_PixelWidth = 4 + 5 * m_stcStack->TextWidth(wxSTC_STYLE_LINENUMBER, wxT("9")); 219 | m_stcStack->SetMarginType(0, wxSTC_MARGIN_NUMBER); 220 | m_stcStack->SetMarginWidth(0, m_stcStack_PixelWidth); 221 | 222 | // Configure the line symbol margin 223 | m_stcStack->SetMarginType(3, wxSTC_MARGIN_FORE); 224 | m_stcStack->SetMarginMask(3, 0); 225 | m_stcStack->SetMarginWidth(3, 0); 226 | // Select the lexer 227 | m_stcStack->SetLexer(wxSTC_LEX_NULL); 228 | // Set default font / styles 229 | m_stcStack->StyleClearAll(); 230 | m_stcStack->SetWrapMode(0); 231 | m_stcStack->SetIndentationGuides(0); 232 | m_stcStack->SetKeyWords(0, wxT("")); 233 | m_stcStack->SetKeyWords(1, wxT("")); 234 | m_stcStack->SetKeyWords(2, wxT("")); 235 | m_stcStack->SetKeyWords(3, wxT("")); 236 | m_stcStack->SetKeyWords(4, wxT("")); 237 | 238 | boxSizer20->Add(m_stcStack, 1, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 239 | 240 | m_panel19 = new wxPanel(m_notebookDAPDebugInfo, wxID_ANY, wxDefaultPosition, 241 | wxDLG_UNIT(m_notebookDAPDebugInfo, wxSize(-1, -1)), wxTAB_TRAVERSAL); 242 | m_notebookDAPDebugInfo->AddPage(m_panel19, _("Threads"), false); 243 | 244 | wxBoxSizer* boxSizer21 = new wxBoxSizer(wxVERTICAL); 245 | m_panel19->SetSizer(boxSizer21); 246 | 247 | m_stcThreads = 248 | new wxStyledTextCtrl(m_panel19, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(m_panel19, wxSize(-1, -1)), 0); 249 | // Configure the fold margin 250 | m_stcThreads->SetMarginType(4, wxSTC_MARGIN_SYMBOL); 251 | m_stcThreads->SetMarginMask(4, wxSTC_MASK_FOLDERS); 252 | m_stcThreads->SetMarginSensitive(4, true); 253 | m_stcThreads->SetMarginWidth(4, 0); 254 | 255 | // Configure the tracker margin 256 | m_stcThreads->SetMarginWidth(1, 0); 257 | 258 | // Configure the symbol margin 259 | m_stcThreads->SetMarginType(2, wxSTC_MARGIN_SYMBOL); 260 | m_stcThreads->SetMarginMask(2, ~(wxSTC_MASK_FOLDERS)); 261 | m_stcThreads->SetMarginWidth(2, 0); 262 | m_stcThreads->SetMarginSensitive(2, true); 263 | 264 | // Configure the line numbers margin 265 | int m_stcThreads_PixelWidth = 4 + 5 * m_stcThreads->TextWidth(wxSTC_STYLE_LINENUMBER, wxT("9")); 266 | m_stcThreads->SetMarginType(0, wxSTC_MARGIN_NUMBER); 267 | m_stcThreads->SetMarginWidth(0, m_stcThreads_PixelWidth); 268 | 269 | // Configure the line symbol margin 270 | m_stcThreads->SetMarginType(3, wxSTC_MARGIN_FORE); 271 | m_stcThreads->SetMarginMask(3, 0); 272 | m_stcThreads->SetMarginWidth(3, 0); 273 | // Select the lexer 274 | m_stcThreads->SetLexer(wxSTC_LEX_NULL); 275 | // Set default font / styles 276 | m_stcThreads->StyleClearAll(); 277 | m_stcThreads->SetWrapMode(0); 278 | m_stcThreads->SetIndentationGuides(0); 279 | m_stcThreads->SetKeyWords(0, wxT("")); 280 | m_stcThreads->SetKeyWords(1, wxT("")); 281 | m_stcThreads->SetKeyWords(2, wxT("")); 282 | m_stcThreads->SetKeyWords(3, wxT("")); 283 | m_stcThreads->SetKeyWords(4, wxT("")); 284 | 285 | boxSizer21->Add(m_stcThreads, 1, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 286 | 287 | m_panel27 = new wxPanel(m_notebookDAPDebugInfo, wxID_ANY, wxDefaultPosition, 288 | wxDLG_UNIT(m_notebookDAPDebugInfo, wxSize(-1, -1)), wxTAB_TRAVERSAL); 289 | m_notebookDAPDebugInfo->AddPage(m_panel27, _("Log"), false); 290 | 291 | wxBoxSizer* boxSizer28 = new wxBoxSizer(wxVERTICAL); 292 | m_panel27->SetSizer(boxSizer28); 293 | 294 | m_stcLog = new wxStyledTextCtrl(m_panel27, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(m_panel27, wxSize(-1, -1)), 0); 295 | // Configure the fold margin 296 | m_stcLog->SetMarginType(4, wxSTC_MARGIN_SYMBOL); 297 | m_stcLog->SetMarginMask(4, wxSTC_MASK_FOLDERS); 298 | m_stcLog->SetMarginSensitive(4, true); 299 | m_stcLog->SetMarginWidth(4, 0); 300 | 301 | // Configure the tracker margin 302 | m_stcLog->SetMarginWidth(1, 0); 303 | 304 | // Configure the symbol margin 305 | m_stcLog->SetMarginType(2, wxSTC_MARGIN_SYMBOL); 306 | m_stcLog->SetMarginMask(2, ~(wxSTC_MASK_FOLDERS)); 307 | m_stcLog->SetMarginWidth(2, 0); 308 | m_stcLog->SetMarginSensitive(2, true); 309 | 310 | // Configure the line numbers margin 311 | int m_stcLog_PixelWidth = 4 + 5 * m_stcLog->TextWidth(wxSTC_STYLE_LINENUMBER, wxT("9")); 312 | m_stcLog->SetMarginType(0, wxSTC_MARGIN_NUMBER); 313 | m_stcLog->SetMarginWidth(0, m_stcLog_PixelWidth); 314 | 315 | // Configure the line symbol margin 316 | m_stcLog->SetMarginType(3, wxSTC_MARGIN_FORE); 317 | m_stcLog->SetMarginMask(3, 0); 318 | m_stcLog->SetMarginWidth(3, 0); 319 | // Select the lexer 320 | m_stcLog->SetLexer(wxSTC_LEX_NULL); 321 | // Set default font / styles 322 | m_stcLog->StyleClearAll(); 323 | m_stcLog->SetWrapMode(1); 324 | m_stcLog->SetIndentationGuides(0); 325 | m_stcLog->SetKeyWords(0, wxT("")); 326 | m_stcLog->SetKeyWords(1, wxT("")); 327 | m_stcLog->SetKeyWords(2, wxT("")); 328 | m_stcLog->SetKeyWords(3, wxT("")); 329 | m_stcLog->SetKeyWords(4, wxT("")); 330 | 331 | boxSizer28->Add(m_stcLog, 1, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 332 | 333 | m_panel30 = new wxPanel(m_notebookDAPDebugInfo, wxID_ANY, wxDefaultPosition, 334 | wxDLG_UNIT(m_notebookDAPDebugInfo, wxSize(-1, -1)), wxTAB_TRAVERSAL); 335 | m_notebookDAPDebugInfo->AddPage(m_panel30, _("Scope Variables"), false); 336 | 337 | wxBoxSizer* boxSizer31 = new wxBoxSizer(wxVERTICAL); 338 | m_panel30->SetSizer(boxSizer31); 339 | 340 | m_stcScopes = 341 | new wxStyledTextCtrl(m_panel30, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(m_panel30, wxSize(-1, -1)), 0); 342 | // Configure the fold margin 343 | m_stcScopes->SetMarginType(4, wxSTC_MARGIN_SYMBOL); 344 | m_stcScopes->SetMarginMask(4, wxSTC_MASK_FOLDERS); 345 | m_stcScopes->SetMarginSensitive(4, true); 346 | m_stcScopes->SetMarginWidth(4, 0); 347 | 348 | // Configure the tracker margin 349 | m_stcScopes->SetMarginWidth(1, 0); 350 | 351 | // Configure the symbol margin 352 | m_stcScopes->SetMarginType(2, wxSTC_MARGIN_SYMBOL); 353 | m_stcScopes->SetMarginMask(2, ~(wxSTC_MASK_FOLDERS)); 354 | m_stcScopes->SetMarginWidth(2, 0); 355 | m_stcScopes->SetMarginSensitive(2, true); 356 | 357 | // Configure the line numbers margin 358 | int m_stcScopes_PixelWidth = 4 + 5 * m_stcScopes->TextWidth(wxSTC_STYLE_LINENUMBER, wxT("9")); 359 | m_stcScopes->SetMarginType(0, wxSTC_MARGIN_NUMBER); 360 | m_stcScopes->SetMarginWidth(0, m_stcScopes_PixelWidth); 361 | 362 | // Configure the line symbol margin 363 | m_stcScopes->SetMarginType(3, wxSTC_MARGIN_FORE); 364 | m_stcScopes->SetMarginMask(3, 0); 365 | m_stcScopes->SetMarginWidth(3, 0); 366 | // Select the lexer 367 | m_stcScopes->SetLexer(wxSTC_LEX_NULL); 368 | // Set default font / styles 369 | m_stcScopes->StyleClearAll(); 370 | m_stcScopes->SetWrapMode(0); 371 | m_stcScopes->SetIndentationGuides(0); 372 | m_stcScopes->SetKeyWords(0, wxT("")); 373 | m_stcScopes->SetKeyWords(1, wxT("")); 374 | m_stcScopes->SetKeyWords(2, wxT("")); 375 | m_stcScopes->SetKeyWords(3, wxT("")); 376 | m_stcScopes->SetKeyWords(4, wxT("")); 377 | 378 | boxSizer31->Add(m_stcScopes, 1, wxALL | wxEXPAND, WXC_FROM_DIP(5)); 379 | 380 | #if wxVERSION_NUMBER >= 2900 381 | if (!wxPersistenceManager::Get().Find(m_notebookDAPDebugInfo)) { 382 | wxPersistenceManager::Get().RegisterAndRestore(m_notebookDAPDebugInfo); 383 | } else { 384 | wxPersistenceManager::Get().Restore(m_notebookDAPDebugInfo); 385 | } 386 | #endif 387 | 388 | SetName(wxT("MainFrameBase")); 389 | SetSize(wxDLG_UNIT(this, wxSize(800, 600))); 390 | if (GetSizer()) { 391 | GetSizer()->Fit(this); 392 | } 393 | if (GetParent()) { 394 | CentreOnParent(wxBOTH); 395 | } else { 396 | CentreOnScreen(wxBOTH); 397 | } 398 | if (!wxPersistenceManager::Get().Find(this)) { 399 | wxPersistenceManager::Get().RegisterAndRestore(this); 400 | } else { 401 | wxPersistenceManager::Get().Restore(this); 402 | } 403 | // Connect events 404 | this->Bind(wxEVT_MENU, &MainFrameBase::OnClear, this, m_menuItem61->GetId()); 405 | this->Bind(wxEVT_MENU, &MainFrameBase::OnExit, this, m_menuItem62->GetId()); 406 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnConnect, this, wxID_NETWORK); 407 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnConnectUI, this, wxID_NETWORK); 408 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnAttach, this, ID_ATTACH); 409 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnAttachUI, this, ID_ATTACH); 410 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnNext, this, wxID_FORWARD); 411 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnNextUI, this, wxID_FORWARD); 412 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnStepIn, this, wxID_DOWN); 413 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnStepInUI, this, wxID_DOWN); 414 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnStepOut, this, wxID_UP); 415 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnStepOutUI, this, wxID_UP); 416 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnContinue, this, wxID_EXECUTE); 417 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnContinueUI, this, wxID_EXECUTE); 418 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnPause, this, wxID_ABORT); 419 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnPauseUI, this, wxID_ABORT); 420 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnSetBreakpoint, this, wxID_STOP); 421 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnSetBreakpointUI, this, wxID_STOP); 422 | this->Bind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnEval, this, wxID_INFO); 423 | this->Bind(wxEVT_UPDATE_UI, &MainFrameBase::OnEvalUI, this, wxID_INFO); 424 | } 425 | 426 | MainFrameBase::~MainFrameBase() 427 | { 428 | this->Unbind(wxEVT_MENU, &MainFrameBase::OnClear, this, m_menuItem61->GetId()); 429 | this->Unbind(wxEVT_MENU, &MainFrameBase::OnExit, this, m_menuItem62->GetId()); 430 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnConnect, this, wxID_NETWORK); 431 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnConnectUI, this, wxID_NETWORK); 432 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnAttach, this, ID_ATTACH); 433 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnAttachUI, this, ID_ATTACH); 434 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnNext, this, wxID_FORWARD); 435 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnNextUI, this, wxID_FORWARD); 436 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnStepIn, this, wxID_DOWN); 437 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnStepInUI, this, wxID_DOWN); 438 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnStepOut, this, wxID_UP); 439 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnStepOutUI, this, wxID_UP); 440 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnContinue, this, wxID_EXECUTE); 441 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnContinueUI, this, wxID_EXECUTE); 442 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnPause, this, wxID_ABORT); 443 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnPauseUI, this, wxID_ABORT); 444 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnSetBreakpoint, this, wxID_STOP); 445 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnSetBreakpointUI, this, wxID_STOP); 446 | this->Unbind(wxEVT_COMMAND_TOOL_CLICKED, &MainFrameBase::OnEval, this, wxID_INFO); 447 | this->Unbind(wxEVT_UPDATE_UI, &MainFrameBase::OnEvalUI, this, wxID_INFO); 448 | } 449 | -------------------------------------------------------------------------------- /dbgcli/UI.hpp: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////// 2 | // This file was auto-generated by codelite's wxCrafter Plugin 3 | // wxCrafter project file: UI.wxcp 4 | // Do not modify this file by hand! 5 | ////////////////////////////////////////////////////////////////////// 6 | 7 | #ifndef _WXDAP_DBGCLI_UI_BASE_CLASSES_HPP 8 | #define _WXDAP_DBGCLI_UI_BASE_CLASSES_HPP 9 | 10 | // clang-format off 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #if wxVERSION_NUMBER >= 2900 28 | #include 29 | #include 30 | #include 31 | #include 32 | #endif 33 | 34 | #ifdef WXC_FROM_DIP 35 | #undef WXC_FROM_DIP 36 | #endif 37 | #if wxVERSION_NUMBER >= 3100 38 | #define WXC_FROM_DIP(x) wxWindow::FromDIP(x, NULL) 39 | #else 40 | #define WXC_FROM_DIP(x) x 41 | #endif 42 | 43 | // clang-format on 44 | 45 | class MainFrameBase : public wxFrame 46 | { 47 | public: 48 | enum { 49 | ID_ATTACH = 10001, 50 | }; 51 | 52 | protected: 53 | wxMenuBar* m_menuBar59; 54 | wxMenu* m_menu60; 55 | wxMenuItem* m_menuItem61; 56 | wxMenuItem* m_menuItem63; 57 | wxMenuItem* m_menuItem62; 58 | wxToolBar* m_toolbar12; 59 | wxPanel* m_panel2; 60 | wxStaticText* m_staticTextSelectDebugFileName; 61 | wxFilePickerCtrl* m_filePickerSelectDebugFileName; 62 | wxStaticText* m_staticText64; 63 | wxFilePickerCtrl* m_filePickerDebugger; 64 | wxSplitterWindow* m_splitter4; 65 | wxPanel* m_splitterPageSourceFile; 66 | wxStyledTextCtrl* m_stcTextSourceFile; 67 | wxPanel* m_splitterPageDAPDebugInfo; 68 | wxNotebook* m_notebookDAPDebugInfo; 69 | wxPanel* m_panel18; 70 | wxStyledTextCtrl* m_stcStack; 71 | wxPanel* m_panel19; 72 | wxStyledTextCtrl* m_stcThreads; 73 | wxPanel* m_panel27; 74 | wxStyledTextCtrl* m_stcLog; 75 | wxPanel* m_panel30; 76 | wxStyledTextCtrl* m_stcScopes; 77 | 78 | protected: 79 | virtual void OnClear(wxCommandEvent& event) { event.Skip(); } 80 | virtual void OnExit(wxCommandEvent& event) { event.Skip(); } 81 | virtual void OnConnect(wxCommandEvent& event) { event.Skip(); } 82 | virtual void OnConnectUI(wxUpdateUIEvent& event) { event.Skip(); } 83 | virtual void OnAttach(wxCommandEvent& event) { event.Skip(); } 84 | virtual void OnAttachUI(wxUpdateUIEvent& event) { event.Skip(); } 85 | virtual void OnNext(wxCommandEvent& event) { event.Skip(); } 86 | virtual void OnNextUI(wxUpdateUIEvent& event) { event.Skip(); } 87 | virtual void OnStepIn(wxCommandEvent& event) { event.Skip(); } 88 | virtual void OnStepInUI(wxUpdateUIEvent& event) { event.Skip(); } 89 | virtual void OnStepOut(wxCommandEvent& event) { event.Skip(); } 90 | virtual void OnStepOutUI(wxUpdateUIEvent& event) { event.Skip(); } 91 | virtual void OnContinue(wxCommandEvent& event) { event.Skip(); } 92 | virtual void OnContinueUI(wxUpdateUIEvent& event) { event.Skip(); } 93 | virtual void OnPause(wxCommandEvent& event) { event.Skip(); } 94 | virtual void OnPauseUI(wxUpdateUIEvent& event) { event.Skip(); } 95 | virtual void OnSetBreakpoint(wxCommandEvent& event) { event.Skip(); } 96 | virtual void OnSetBreakpointUI(wxUpdateUIEvent& event) { event.Skip(); } 97 | virtual void OnEval(wxCommandEvent& event) { event.Skip(); } 98 | virtual void OnEvalUI(wxUpdateUIEvent& event) { event.Skip(); } 99 | 100 | public: 101 | wxMenuBar* GetMenuBar59() { return m_menuBar59; } 102 | wxToolBar* GetToolbar12() { return m_toolbar12; } 103 | wxStaticText* GetStaticTextSelectDebugFileName() { return m_staticTextSelectDebugFileName; } 104 | wxFilePickerCtrl* GetFilePickerSelectDebugFileName() { return m_filePickerSelectDebugFileName; } 105 | wxStaticText* GetStaticText64() { return m_staticText64; } 106 | wxFilePickerCtrl* GetFilePickerDebugger() { return m_filePickerDebugger; } 107 | wxStyledTextCtrl* GetStcTextSourceFile() { return m_stcTextSourceFile; } 108 | wxPanel* GetSplitterPageSourceFile() { return m_splitterPageSourceFile; } 109 | wxStyledTextCtrl* GetStcStack() { return m_stcStack; } 110 | wxPanel* GetPanel18() { return m_panel18; } 111 | wxStyledTextCtrl* GetStcThreads() { return m_stcThreads; } 112 | wxPanel* GetPanel19() { return m_panel19; } 113 | wxStyledTextCtrl* GetStcLog() { return m_stcLog; } 114 | wxPanel* GetPanel27() { return m_panel27; } 115 | wxStyledTextCtrl* GetStcScopes() { return m_stcScopes; } 116 | wxPanel* GetPanel30() { return m_panel30; } 117 | wxNotebook* GetNotebookDAPDebugInfo() { return m_notebookDAPDebugInfo; } 118 | wxPanel* GetSplitterPageDAPDebugInfo() { return m_splitterPageDAPDebugInfo; } 119 | wxSplitterWindow* GetSplitter4() { return m_splitter4; } 120 | wxPanel* GetPanel2() { return m_panel2; } 121 | MainFrameBase(wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("DAP UI"), 122 | const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(800, 600), 123 | long style = wxDEFAULT_FRAME_STYLE); 124 | virtual ~MainFrameBase(); 125 | }; 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /dbgcli/UI_dbgcli_bitmaps.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // This file was automatically generated by wxrc, do not edit by hand. 3 | // 4 | 5 | #include 6 | 7 | #ifdef __BORLANDC__ 8 | #pragma hdrstop 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #if wxCHECK_VERSION(2,8,5) && wxABI_VERSION >= 20805 17 | #define XRC_ADD_FILE(name, data, size, mime) \ 18 | wxMemoryFSHandler::AddFileWithMimeType(name, data, size, mime) 19 | #else 20 | #define XRC_ADD_FILE(name, data, size, mime) \ 21 | wxMemoryFSHandler::AddFile(name, data, size) 22 | #endif 23 | 24 | static size_t xml_res_size_0 = 137; 25 | static unsigned char xml_res_file_0[] = { 26 | 60,63,120,109,108,32,118,101,114,115,105,111,110,61,34,49,46,48,34,32,101, 27 | 110,99,111,100,105,110,103,61,34,85,84,70,45,56,34,63,62,10,60,114,101, 28 | 115,111,117,114,99,101,32,120,109,108,110,115,61,34,104,116,116,112,58, 29 | 47,47,119,119,119,46,119,120,119,105,100,103,101,116,115,46,111,114,103, 30 | 47,119,120,120,114,99,34,62,10,32,32,60,33,45,45,32,72,97,110,100,108,101, 31 | 114,32,71,101,110,101,114,97,116,105,111,110,32,105,115,32,79,78,32,45, 32 | 45,62,10,60,47,114,101,115,111,117,114,99,101,62,10}; 33 | 34 | void wxC10A1InitBitmapResources() 35 | { 36 | 37 | // Check for memory FS. If not present, load the handler: 38 | { 39 | wxMemoryFSHandler::AddFile(wxT("XRC_resource/dummy_file"), wxT("dummy one")); 40 | wxFileSystem fsys; 41 | wxFSFile *f = fsys.OpenFile(wxT("memory:XRC_resource/dummy_file")); 42 | wxMemoryFSHandler::RemoveFile(wxT("XRC_resource/dummy_file")); 43 | if (f) delete f; 44 | else wxFileSystem::AddHandler(new wxMemoryFSHandlerBase); 45 | } 46 | 47 | XRC_ADD_FILE(wxT("XRC_resource/UI_dbgcli_bitmaps.cpp$C__msys64_home_eran_devl_wxdap_dbgcli_UI_dbgcli_bitmaps.xrc"), xml_res_file_0, xml_res_size_0, wxT("text/xml")); 48 | wxXmlResource::Get()->Load(wxT("memory:XRC_resource/UI_dbgcli_bitmaps.cpp$C__msys64_home_eran_devl_wxdap_dbgcli_UI_dbgcli_bitmaps.xrc")); 49 | } 50 | -------------------------------------------------------------------------------- /dbgcli/dbgcli.project: -------------------------------------------------------------------------------- 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 | None 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 | None 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /dbgcli/resources.rc: -------------------------------------------------------------------------------- 1 | // set this to 1 if you don't want to use manifest resource (manifest resource 2 | // is needed to enable visual styles on Windows XP - see docs/msw/winxp.txt 3 | // for more information) 4 | #define wxUSE_NO_MANIFEST 0 5 | 6 | // To get DPI change events, we need to opt in into per monitor DPI support. 7 | #define wxUSE_DPI_AWARE_MANIFEST 2 8 | 9 | // this is not always needed but doesn't hurt (except making the executable 10 | // very slightly larger): this file contains the standard icons, cursors, ... 11 | #include "wx/msw/wx.rc" 12 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(daptests) 3 | 4 | include_directories(${CMAKE_SOURCE_DIR}) 5 | FILE(GLOB SRCS "*.cpp") 6 | 7 | add_executable(daptests ${SRCS}) 8 | target_link_libraries(daptests dapcxx) 9 | add_test(NAME tests COMMAND daptests) 10 | -------------------------------------------------------------------------------- /tests/gdbd_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "dap/JsonRPC.hpp" 2 | #include "dap/dap.hpp" 3 | #include "tester.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "dap/StringUtils.hpp" 9 | 10 | using namespace std; 11 | 12 | #define CHECK_REQUEST(obj, str) \ 13 | CHECK_CONDITION(obj, str); \ 14 | CHECK_STRING(obj->As()->command.c_str().AsChar(), str) 15 | 16 | #define CHECK_RESPONSE(obj, str) \ 17 | CHECK_CONDITION(obj, str); \ 18 | CHECK_STRING(obj->As()->command.c_str().AsChar(), str) 19 | 20 | #define CHECK_EVENT(obj, str) \ 21 | CHECK_CONDITION(obj, str); \ 22 | CHECK_STRING(obj->As()->event.c_str().AsChar(), str) 23 | 24 | int main(int, char**) 25 | { 26 | dap::Initialize(); 27 | 28 | Tester::Instance()->RunTests(); 29 | Tester::Release(); 30 | return 0; 31 | } 32 | 33 | TEST_FUNC(Check_Request_Allocations) 34 | { 35 | dap::ProtocolMessage::Ptr_t obj; 36 | 37 | // Requests 38 | obj = dap::ObjGenerator::Get().New("request", "cancel"); 39 | CHECK_REQUEST(obj, "cancel"); 40 | obj = dap::ObjGenerator::Get().New("request", "initialize"); 41 | CHECK_REQUEST(obj, "initialize"); 42 | obj = dap::ObjGenerator::Get().New("request", "configurationDone"); 43 | CHECK_REQUEST(obj, "configurationDone"); 44 | obj = dap::ObjGenerator::Get().New("request", "launch"); 45 | CHECK_REQUEST(obj, "launch"); 46 | obj = dap::ObjGenerator::Get().New("request", "disconnect"); 47 | CHECK_REQUEST(obj, "disconnect"); 48 | obj = dap::ObjGenerator::Get().New("request", "breakpointLocations"); 49 | CHECK_REQUEST(obj, "breakpointLocations"); 50 | obj = dap::ObjGenerator::Get().New("request", "setBreakpoints"); 51 | CHECK_REQUEST(obj, "setBreakpoints"); 52 | obj = dap::ObjGenerator::Get().New("request", "continue"); 53 | CHECK_REQUEST(obj, "continue"); 54 | return true; 55 | } 56 | 57 | TEST_FUNC(Check_Response_Allocations) 58 | { 59 | dap::ProtocolMessage::Ptr_t obj; 60 | // Responses 61 | obj = dap::ObjGenerator::Get().New("response", "initialize"); 62 | CHECK_RESPONSE(obj, "initialize"); 63 | obj = dap::ObjGenerator::Get().New("response", "cancel"); 64 | CHECK_RESPONSE(obj, "cancel"); 65 | obj = dap::ObjGenerator::Get().New("response", "configurationDone"); 66 | CHECK_RESPONSE(obj, "configurationDone"); 67 | obj = dap::ObjGenerator::Get().New("response", "launch"); 68 | CHECK_RESPONSE(obj, "launch"); 69 | obj = dap::ObjGenerator::Get().New("response", "disconnect"); 70 | CHECK_RESPONSE(obj, "disconnect"); 71 | obj = dap::ObjGenerator::Get().New("response", "breakpointLocations"); 72 | CHECK_RESPONSE(obj, "breakpointLocations"); 73 | obj = dap::ObjGenerator::Get().New("response", "continue"); 74 | CHECK_RESPONSE(obj, "continue"); 75 | obj = dap::ObjGenerator::Get().New("response", "setBreakpoints"); 76 | CHECK_RESPONSE(obj, "setBreakpoints"); 77 | return true; 78 | } 79 | 80 | TEST_FUNC(Check_Event_Allocations) 81 | { 82 | dap::ProtocolMessage::Ptr_t obj; 83 | // Events 84 | obj = dap::ObjGenerator::Get().New("event", "initialized"); 85 | CHECK_EVENT(obj, "initialized"); 86 | obj = dap::ObjGenerator::Get().New("event", "stopped"); 87 | CHECK_EVENT(obj, "stopped"); 88 | obj = dap::ObjGenerator::Get().New("event", "continued"); 89 | CHECK_EVENT(obj, "continued"); 90 | obj = dap::ObjGenerator::Get().New("event", "exited"); 91 | CHECK_EVENT(obj, "exited"); 92 | obj = dap::ObjGenerator::Get().New("event", "output"); 93 | CHECK_EVENT(obj, "output"); 94 | obj = dap::ObjGenerator::Get().New("event", "process"); 95 | CHECK_EVENT(obj, "process"); 96 | obj = dap::ObjGenerator::Get().New("event", "stopped"); 97 | CHECK_EVENT(obj, "stopped"); 98 | obj = dap::ObjGenerator::Get().New("event", "terminated"); 99 | CHECK_EVENT(obj, "terminated"); 100 | obj = dap::ObjGenerator::Get().New("event", "thread"); 101 | CHECK_EVENT(obj, "thread"); 102 | return true; 103 | } 104 | 105 | TEST_FUNC(Check_Parsing_JSON_RPC_Message) 106 | { 107 | const std::string jsonStr = "{\n" 108 | " \"seq\": 153,\n" 109 | " \"type\": \"request\",\n" 110 | " \"command\": \"next\",\n" 111 | " \"arguments\": {\n" 112 | " \"threadId\": 3\n" 113 | " }\n" 114 | "}"; 115 | const string header = "Content-Length: " + std::to_string(jsonStr.size()) + 116 | "\r\n" 117 | "\r\n"; 118 | 119 | dap::JsonRPC rpc; 120 | rpc.SetBuffer(header + jsonStr); 121 | 122 | int count = 0; 123 | rpc.ProcessBuffer([&](const dap::Json& json, wxObject*) { 124 | 125 | CHECK_NUMBER(json["seq"].GetNumber(), 153); 126 | CHECK_STRING(json["type"].GetString().c_str().AsChar(), "request"); 127 | CHECK_STRING(json["command"].GetString().c_str().AsChar(), "next"); 128 | CHECK_NUMBER(json["arguments"]["threadId"].GetNumber(), 3); 129 | dap::NextRequest nextRequest; 130 | nextRequest.From(json); 131 | CHECK_NUMBER(nextRequest.seq, 153); 132 | CHECK_STRING(nextRequest.type.c_str().AsChar(), "request"); 133 | CHECK_STRING(nextRequest.command.c_str().AsChar(), "next"); 134 | CHECK_NUMBER(nextRequest.arguments.threadId, 3); 135 | 136 | ++count; 137 | return true; 138 | }, nullptr); 139 | CHECK_NUMBER(count, 1); 140 | return true; 141 | } 142 | -------------------------------------------------------------------------------- /tests/tester.cpp: -------------------------------------------------------------------------------- 1 | #include "tester.h" 2 | #include 3 | 4 | Tester* Tester::ms_instance = 0; 5 | 6 | Tester::Tester() {} 7 | 8 | Tester::~Tester() {} 9 | 10 | Tester* Tester::Instance() 11 | { 12 | if(ms_instance == 0) { 13 | ms_instance = new Tester(); 14 | } 15 | return ms_instance; 16 | } 17 | 18 | void Tester::Release() 19 | { 20 | if(ms_instance) { 21 | delete ms_instance; 22 | } 23 | ms_instance = 0; 24 | } 25 | 26 | void Tester::AddTest(ITest* t) { m_tests.push_back(t); } 27 | 28 | void Tester::RunTests() 29 | { 30 | size_t totalTests = m_tests.size(); 31 | size_t success = 0; 32 | size_t errors = 0; 33 | for(size_t i = 0; i < m_tests.size(); i++) { 34 | m_tests[i]->test() ? success++ : errors++; 35 | } 36 | 37 | printf("\n====> Summary: <====\n\n"); 38 | 39 | if(success == totalTests) { 40 | printf(" All tests passed successfully!!\n"); 41 | } else { 42 | printf(" %u of %u tests passed\n", (unsigned int)success, (unsigned int)totalTests); 43 | printf(" %u of %u tests failed\n", (unsigned int)errors, (unsigned int)totalTests); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/tester.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTER_H 2 | #define TESTER_H 3 | 4 | #include 5 | 6 | class ITest; 7 | /** 8 | * @class Tester 9 | * @author eran 10 | * @date 07/08/10 11 | * @file tester.h 12 | * @brief the tester class 13 | */ 14 | class Tester 15 | { 16 | 17 | static Tester* ms_instance; 18 | std::vector m_tests; 19 | 20 | public: 21 | static Tester* Instance(); 22 | static void Release(); 23 | 24 | void AddTest(ITest* t); 25 | void RunTests(); 26 | 27 | private: 28 | Tester(); 29 | ~Tester(); 30 | }; 31 | 32 | /** 33 | * @class ITest 34 | * @author eran 35 | * @date 07/08/10 36 | * @file tester.h 37 | * @brief the test interface 38 | */ 39 | class ITest 40 | { 41 | protected: 42 | int m_testCount; 43 | 44 | public: 45 | ITest() 46 | : m_testCount(0) 47 | { 48 | Tester::Instance()->AddTest(this); 49 | } 50 | virtual ~ITest() {} 51 | virtual bool test() = 0; 52 | }; 53 | 54 | /////////////////////////////////////////////////////////// 55 | // Helper macros: 56 | /////////////////////////////////////////////////////////// 57 | 58 | #define TEST_FUNC(Name) \ 59 | class Test_##Name : public ITest \ 60 | { \ 61 | public: \ 62 | virtual bool test(); \ 63 | virtual bool Name(); \ 64 | }; \ 65 | Test_##Name theTest##Name; \ 66 | bool Test_##Name::test() \ 67 | { \ 68 | printf("Checking %s...", #Name); \ 69 | bool res = Name(); \ 70 | if(res) { \ 71 | printf("...OK!\n"); \ 72 | } else { \ 73 | printf("...ERROR!\n"); \ 74 | } \ 75 | return res; \ 76 | } \ 77 | bool Test_##Name::Name() 78 | 79 | // Check values macros 80 | #define CHECK_SIZE(actualSize, expcSize) \ 81 | { \ 82 | m_testCount++; \ 83 | if(actualSize != (int)expcSize) { \ 84 | printf("%-40s(%d): ERROR\n%s:%d: Expected size: %d, Actual Size:%d\n", __FUNCTION__, m_testCount, \ 85 | __FILE__, __LINE__, (int)expcSize, (int)actualSize); \ 86 | return false; \ 87 | } \ 88 | } 89 | 90 | #define CHECK_NUMBER(actualNumber, expcNumber) \ 91 | { \ 92 | m_testCount++; \ 93 | if(actualNumber != (int)expcNumber) { \ 94 | printf("%-40s(%d): ERROR\n%s:%d: Expected Number: %d, Actual Number:%d\n", __FUNCTION__, m_testCount, \ 95 | __FILE__, __LINE__, (int)expcNumber, (int)actualNumber); \ 96 | return false; \ 97 | } \ 98 | } 99 | 100 | #define CHECK_STRING(str, expcStr) \ 101 | { \ 102 | if(strcmp(str, expcStr) != 0) { \ 103 | printf("%-40s(%d): ERROR\n%s:%d: Expected string: %s, Actual string:%s\n", __FUNCTION__, m_testCount, \ 104 | __FILE__, __LINE__, expcStr, str); \ 105 | return false; \ 106 | } \ 107 | } 108 | 109 | #define CHECK_CONDITION(cond, msg) \ 110 | { \ 111 | if(!cond) { \ 112 | printf("%-40s(%d): ERROR\n%s:%d: %s\n", __FUNCTION__, m_testCount, __FILE__, __LINE__, msg); \ 113 | return false; \ 114 | } \ 115 | } 116 | 117 | #endif // TESTER_H 118 | -------------------------------------------------------------------------------- /wxdap-linux.workspace: -------------------------------------------------------------------------------- 1 | { 2 | "workspace_type": "File System Workspace", 3 | "name": "wxdap-linux", 4 | "configs": [{ 5 | "name": "Debug", 6 | "targets": [["build", "cd build-debug && make -j8"], ["clean", "cd build-debug && make clean"], ["cmake", "mkdir -p build-debug && cd build-debug && cmake .. -DCMAKE_BUILD_TYPE=Debug"]], 7 | "file_extensions": "*.cpp;*.c;*.txt;*.json;*.hpp;*.cc;*.cxx;*.xml;*.h;*.wxcp", 8 | "excludeFilesPattern": "*.o;*.pyc;*.obj;*.workspace;*.o.d;*.exe;*.dll;*.project", 9 | "excludePaths": "", 10 | "debugger": "GNU gdb debugger" 11 | }] 12 | } -------------------------------------------------------------------------------- /wxdap.workspace: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PATH=C:\compilers\mingw64-9.2\bin;$PATH 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------