diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 51d60620..d24464eb 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -26,6 +26,8 @@ file(GLOB ADAPTER_SOURCES CONFIGURE_DEPENDS adapters/lldbadapter.h adapters/gdbadapter.cpp adapters/gdbadapter.h + adapters/gdbmiadapter.cpp + adapters/gdbmiadapter.h adapters/rspconnector.cpp adapters/rspconnector.h adapters/corelliumadapter.cpp @@ -201,6 +203,32 @@ else() ) endif() +# Bundle GDB server from the existing bundle +if (APPLE) + # Extract gdbserver.zip and copy to output directory + add_custom_command(TARGET debuggercore PRE_LINK + COMMAND ${CMAKE_COMMAND} -E echo "Extracting and copying GDB Server" + COMMAND ${CMAKE_COMMAND} -E make_directory ${LIBRARY_OUTPUT_DIRECTORY_PATH} + COMMAND ${CMAKE_COMMAND} -E chdir ${PROJECT_SOURCE_DIR}/adapters/gdb ${CMAKE_COMMAND} -E tar xf gdbserver.zip + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/adapters/gdb/gdbserver ${LIBRARY_OUTPUT_DIRECTORY_PATH}/gdbserver + COMMAND chmod +x ${LIBRARY_OUTPUT_DIRECTORY_PATH}/gdbserver + ) +elseif (UNIX) + # Extract gdbserver.zip and copy to output directory + add_custom_command(TARGET debuggercore PRE_LINK + COMMAND ${CMAKE_COMMAND} -E echo "Extracting and copying GDB Server" + COMMAND ${CMAKE_COMMAND} -E make_directory ${LIBRARY_OUTPUT_DIRECTORY_PATH} + COMMAND ${CMAKE_COMMAND} -E chdir ${PROJECT_SOURCE_DIR}/adapters/gdb ${CMAKE_COMMAND} -E tar xf gdbserver.zip + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/adapters/gdb/gdbserver ${LIBRARY_OUTPUT_DIRECTORY_PATH}/gdbserver + COMMAND chmod +x ${LIBRARY_OUTPUT_DIRECTORY_PATH}/gdbserver + ) +else() + # For Windows, we would need a Windows GDB server build + add_custom_command(TARGET debuggercore PRE_LINK + COMMAND ${CMAKE_COMMAND} -E echo "GDB server bundling not implemented for Windows" + ) +endif() + if (WIN32) add_custom_command(TARGET debuggercore PRE_LINK COMMAND ${CMAKE_COMMAND} -E echo "Copying DbgEng DLLs" diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp new file mode 100644 index 00000000..76de0127 --- /dev/null +++ b/core/adapters/gdbmiadapter.cpp @@ -0,0 +1,909 @@ +/* +Copyright 2020-2025 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "gdbmiadapter.h" +#include "../debuggercontroller.h" +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +using namespace BinaryNinja; +using namespace BinaryNinjaDebugger; +using namespace std; + +// GdbProcess implementation +GdbMiAdapter::GdbProcess::GdbProcess() : valid(false) +{ +#ifdef _WIN32 + process = INVALID_HANDLE_VALUE; + stdinPipe = INVALID_HANDLE_VALUE; + stdoutPipe = INVALID_HANDLE_VALUE; + stderrPipe = INVALID_HANDLE_VALUE; +#else + pid = -1; + stdinFd = -1; + stdoutFd = -1; + stderrFd = -1; +#endif +} + +GdbMiAdapter::GdbProcess::~GdbProcess() +{ + Terminate(); +} + +bool GdbMiAdapter::GdbProcess::IsRunning() +{ +#ifdef _WIN32 + if (process == INVALID_HANDLE_VALUE) + return false; + DWORD exitCode; + if (GetExitCodeProcess(process, &exitCode)) + return exitCode == STILL_ACTIVE; + return false; +#else + if (pid <= 0) + return false; + int status; + pid_t result = waitpid(pid, &status, WNOHANG); + return result == 0; // 0 means still running +#endif +} + +void GdbMiAdapter::GdbProcess::Terminate() +{ +#ifdef _WIN32 + if (process != INVALID_HANDLE_VALUE) + { + TerminateProcess(process, 1); + CloseHandle(process); + process = INVALID_HANDLE_VALUE; + } + if (stdinPipe != INVALID_HANDLE_VALUE) + { + CloseHandle(stdinPipe); + stdinPipe = INVALID_HANDLE_VALUE; + } + if (stdoutPipe != INVALID_HANDLE_VALUE) + { + CloseHandle(stdoutPipe); + stdoutPipe = INVALID_HANDLE_VALUE; + } + if (stderrPipe != INVALID_HANDLE_VALUE) + { + CloseHandle(stderrPipe); + stderrPipe = INVALID_HANDLE_VALUE; + } +#else + if (pid > 0) + { + kill(pid, SIGTERM); + // Wait a bit for graceful shutdown + usleep(100000); // 100ms + if (IsRunning()) + kill(pid, SIGKILL); + waitpid(pid, nullptr, 0); + pid = -1; + } + if (stdinFd >= 0) + { + close(stdinFd); + stdinFd = -1; + } + if (stdoutFd >= 0) + { + close(stdoutFd); + stdoutFd = -1; + } + if (stderrFd >= 0) + { + close(stderrFd); + stderrFd = -1; + } +#endif + valid = false; +} + +// GdbMiAdapter implementation +GdbMiAdapter::GdbMiAdapter(BinaryView* data) : DebugAdapter(data) +{ + m_isTargetRunning = false; + m_responseReady = false; + m_exitCode = 0; + GenerateDefaultAdapterSettings(data); +} + +GdbMiAdapter::~GdbMiAdapter() +{ + StopGdbProcess(); +} + +std::string GdbMiAdapter::GetGdbExecutablePath() +{ + // First try to find system GDB + std::vector gdbPaths = { + "/usr/bin/gdb", + "/usr/local/bin/gdb", + "/opt/homebrew/bin/gdb", // macOS Homebrew + "/opt/local/bin/gdb" // macOS MacPorts + }; + +#ifdef _WIN32 + gdbPaths = { + "C:\\msys64\\mingw64\\bin\\gdb.exe", + "C:\\msys64\\usr\\bin\\gdb.exe", + "gdb.exe" // Try PATH + }; +#endif + + for (const auto& path : gdbPaths) + { + if (std::filesystem::exists(path)) + return path; + } + + // If no system GDB found, check for bundled GDB (future enhancement) + std::string pluginDir; + if (getenv("BN_STANDALONE_DEBUGGER") != nullptr) + pluginDir = GetUserPluginDirectory(); + else + pluginDir = GetBundledPluginDirectory(); + +#ifdef _WIN32 + std::string bundledPath = pluginDir + "\\gdb\\bin\\gdb.exe"; +#else + std::string bundledPath = pluginDir + "/gdb/bin/gdb"; +#endif + + if (std::filesystem::exists(bundledPath)) + return bundledPath; + + return ""; // No GDB found +} + +std::string GdbMiAdapter::GetGdbServerPath() +{ + // Get the directory where debugger plugins are installed + std::string pluginDir; + if (getenv("BN_STANDALONE_DEBUGGER") != nullptr) + pluginDir = GetUserPluginDirectory(); + else + pluginDir = GetBundledPluginDirectory(); + +#ifdef _WIN32 + return pluginDir + "\\gdbserver.exe"; // When Windows support is added +#else + return pluginDir + "/gdbserver"; +#endif +} + +bool GdbMiAdapter::StartGdbProcess() +{ + if (m_gdbProcess && m_gdbProcess->IsRunning()) + return true; + + m_gdbProcess = std::make_unique(); + + std::string gdbPath = GetGdbExecutablePath(); + if (!std::filesystem::exists(gdbPath)) + { + LogError("GDB executable not found at: %s", gdbPath.c_str()); + return false; + } + +#ifdef _WIN32 + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + HANDLE hStdinRead, hStdinWrite; + HANDLE hStdoutRead, hStdoutWrite; + HANDLE hStderrRead, hStderrWrite; + + if (!CreatePipe(&hStdinRead, &hStdinWrite, &saAttr, 0) || + !CreatePipe(&hStdoutRead, &hStdoutWrite, &saAttr, 0) || + !CreatePipe(&hStderrRead, &hStderrWrite, &saAttr, 0)) + { + LogError("Failed to create pipes for GDB process"); + return false; + } + + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.hStdError = hStderrWrite; + si.hStdOutput = hStdoutWrite; + si.hStdInput = hStdinRead; + si.dwFlags |= STARTF_USESTDHANDLES; + + std::string cmdLine = fmt::format("\"{}\" --interpreter=mi", gdbPath); + if (!CreateProcessA(NULL, const_cast(cmdLine.c_str()), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + { + LogError("Failed to start GDB process"); + CloseHandle(hStdinRead); + CloseHandle(hStdinWrite); + CloseHandle(hStdoutRead); + CloseHandle(hStdoutWrite); + CloseHandle(hStderrRead); + CloseHandle(hStderrWrite); + return false; + } + + m_gdbProcess->process = pi.hProcess; + m_gdbProcess->stdinPipe = hStdinWrite; + m_gdbProcess->stdoutPipe = hStdoutRead; + m_gdbProcess->stderrPipe = hStderrRead; + m_gdbProcess->valid = true; + + CloseHandle(pi.hThread); + CloseHandle(hStdinRead); + CloseHandle(hStdoutWrite); + CloseHandle(hStderrWrite); +#else + int stdinPipe[2], stdoutPipe[2], stderrPipe[2]; + + if (pipe(stdinPipe) == -1 || pipe(stdoutPipe) == -1 || pipe(stderrPipe) == -1) + { + LogError("Failed to create pipes for GDB process"); + return false; + } + + pid_t pid = fork(); + if (pid == -1) + { + LogError("Failed to fork GDB process"); + close(stdinPipe[0]); + close(stdinPipe[1]); + close(stdoutPipe[0]); + close(stdoutPipe[1]); + close(stderrPipe[0]); + close(stderrPipe[1]); + return false; + } + + if (pid == 0) + { + // Child process + dup2(stdinPipe[0], STDIN_FILENO); + dup2(stdoutPipe[1], STDOUT_FILENO); + dup2(stderrPipe[1], STDERR_FILENO); + + close(stdinPipe[0]); + close(stdinPipe[1]); + close(stdoutPipe[0]); + close(stdoutPipe[1]); + close(stderrPipe[0]); + close(stderrPipe[1]); + + execl(gdbPath.c_str(), "gdb", "--interpreter=mi", nullptr); + _exit(1); // If execl fails + } + + // Parent process + m_gdbProcess->pid = pid; + m_gdbProcess->stdinFd = stdinPipe[1]; + m_gdbProcess->stdoutFd = stdoutPipe[0]; + m_gdbProcess->stderrFd = stderrPipe[0]; + m_gdbProcess->valid = true; + + close(stdinPipe[0]); + close(stdoutPipe[1]); + close(stderrPipe[1]); +#endif + + // Start output processing threads + m_outputThread = std::thread(&GdbMiAdapter::ProcessOutput, this); + m_errorThread = std::thread(&GdbMiAdapter::ProcessError, this); + + return true; +} + +void GdbMiAdapter::StopGdbProcess() +{ + if (m_gdbProcess) + { + m_gdbProcess->Terminate(); + m_gdbProcess.reset(); + } + + if (m_outputThread.joinable()) + m_outputThread.join(); + if (m_errorThread.joinable()) + m_errorThread.join(); +} + +std::string GdbMiAdapter::SendCommand(const std::string& command) +{ + if (!m_gdbProcess || !m_gdbProcess->valid) + return ""; + + std::lock_guard lock(m_commandMutex); + + std::string fullCommand = command + "\n"; + +#ifdef _WIN32 + DWORD bytesWritten; + if (!WriteFile(m_gdbProcess->stdinPipe, fullCommand.c_str(), fullCommand.length(), &bytesWritten, NULL)) + return ""; +#else + if (write(m_gdbProcess->stdinFd, fullCommand.c_str(), fullCommand.length()) == -1) + return ""; +#endif + + // Wait for response + std::unique_lock responseLock(m_commandMutex); + m_responseCondition.wait(responseLock, [this] { return m_responseReady; }); + + std::string response = m_lastResponse; + m_responseReady = false; + return response; +} + +void GdbMiAdapter::ProcessOutput() +{ + char buffer[4096]; + std::string accumulated; + + while (m_gdbProcess && m_gdbProcess->valid) + { +#ifdef _WIN32 + DWORD bytesRead; + if (ReadFile(m_gdbProcess->stdoutPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) +#else + ssize_t bytesRead = read(m_gdbProcess->stdoutFd, buffer, sizeof(buffer) - 1); + if (bytesRead > 0) +#endif + { + buffer[bytesRead] = '\0'; + accumulated += buffer; + + // Process complete lines + size_t pos = 0; + while ((pos = accumulated.find('\n')) != std::string::npos) + { + std::string line = accumulated.substr(0, pos); + accumulated.erase(0, pos + 1); + + // Signal response ready + { + std::lock_guard lock(m_commandMutex); + m_lastResponse = line; + m_responseReady = true; + } + m_responseCondition.notify_one(); + } + } + else + { + break; // EOF or error + } + } +} + +void GdbMiAdapter::ProcessError() +{ + char buffer[4096]; + + while (m_gdbProcess && m_gdbProcess->valid) + { +#ifdef _WIN32 + DWORD bytesRead; + if (ReadFile(m_gdbProcess->stderrPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0) +#else + ssize_t bytesRead = read(m_gdbProcess->stderrFd, buffer, sizeof(buffer) - 1); + if (bytesRead > 0) +#endif + { + buffer[bytesRead] = '\0'; + LogWarn("GDB stderr: %s", buffer); + } + else + { + break; + } + } +} + +std::string GdbMiAdapter::ParseMiResponse(const std::string& response) +{ + // Basic MI response parsing - this is a simplified version + // Real implementation would need proper MI parsing + return response; +} + +void GdbMiAdapter::GenerateDefaultAdapterSettings(BinaryView* data) +{ + // For now, no special settings needed +} + +// Debug adapter interface implementation +bool GdbMiAdapter::Execute(const std::string& path, const LaunchConfigurations& configs) +{ + return ExecuteWithArgs(path, "", "", configs); +} + +bool GdbMiAdapter::ExecuteWithArgs(const std::string& path, const std::string& args, const std::string& workingDir, + const LaunchConfigurations& configs) +{ + if (!StartGdbProcess()) + { + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.shortError = "Failed to start GDB"; + event.data.errorData.error = "Could not start GDB process"; + PostDebuggerEvent(event); + return false; + } + + // For local debugging, we need to launch gdbserver first, then connect GDB to it + // This is a more complex setup but provides the full power of GDB MI + + // TODO: Launch gdbserver with the target + // gdbserver localhost:0 target_executable args... + // Then connect GDB to the gdbserver + // This requires additional process management for gdbserver + + // For now, use direct file execution (simpler case) + // Set executable file + std::string cmd = fmt::format("-file-exec-and-symbols \"{}\"", path); + std::string response = SendCommand(cmd); + + // Set arguments if provided + if (!args.empty()) + { + cmd = fmt::format("-exec-arguments {}", args); + SendCommand(cmd); + } + + // Set working directory if provided + if (!workingDir.empty()) + { + cmd = fmt::format("-environment-cd \"{}\"", workingDir); + SendCommand(cmd); + } + + // Start execution + response = SendCommand("-exec-run"); + + m_isTargetRunning = true; + + DebuggerEvent event; + event.type = LaunchEventType; + PostDebuggerEvent(event); + + return true; +} + +bool GdbMiAdapter::Attach(std::uint32_t pid) +{ + if (!StartGdbProcess()) + return false; + + std::string cmd = fmt::format("-target-attach {}", pid); + std::string response = SendCommand(cmd); + + m_isTargetRunning = true; + + DebuggerEvent event; + event.type = AttachEventType; + PostDebuggerEvent(event); + + return true; +} + +bool GdbMiAdapter::Connect(const std::string& server, std::uint32_t port) +{ + // This adapter is for local debugging, not remote + return false; +} + +bool GdbMiAdapter::ConnectToDebugServer(const std::string& server, std::uint32_t port) +{ + // This adapter is for local debugging, not remote + return false; +} + +bool GdbMiAdapter::Detach() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-target-detach"); + m_isTargetRunning = false; + + DebuggerEvent event; + event.type = DetachEventType; + PostDebuggerEvent(event); + + return true; +} + +bool GdbMiAdapter::Quit() +{ + if (m_gdbProcess) + { + SendCommand("-gdb-exit"); + StopGdbProcess(); + } + + DebuggerEvent event; + event.type = TargetExitedEventType; + event.data.exitData.exitCode = m_exitCode; + PostDebuggerEvent(event); + + return true; +} + +std::vector GdbMiAdapter::GetProcessList() +{ + // GDB MI doesn't provide process listing functionality + return {}; +} + +std::vector GdbMiAdapter::GetThreadList() +{ + std::vector threads; + + if (!m_gdbProcess) + return threads; + + std::string response = SendCommand("-thread-info"); + // Parse MI response and extract thread information + // This is a simplified implementation + + return threads; +} + +DebugThread GdbMiAdapter::GetActiveThread() const +{ + return DebugThread(); +} + +std::uint32_t GdbMiAdapter::GetActiveThreadId() const +{ + return 1; // Default thread ID +} + +bool GdbMiAdapter::SetActiveThread(const DebugThread& thread) +{ + return SetActiveThreadId(thread.m_tid); +} + +bool GdbMiAdapter::SetActiveThreadId(std::uint32_t tid) +{ + if (!m_gdbProcess) + return false; + + std::string cmd = fmt::format("-thread-select {}", tid); + SendCommand(cmd); + return true; +} + +std::vector GdbMiAdapter::GetBreakpointList() const +{ + return {}; +} + +DebugBreakpoint GdbMiAdapter::AddBreakpoint(const std::uintptr_t address, unsigned long breakpoint_type) +{ + if (!m_gdbProcess) + return DebugBreakpoint(); + + std::string cmd = fmt::format("-break-insert *0x{:x}", address); + SendCommand(cmd); + + DebugBreakpoint bp; + bp.m_id = address; // Simplified + bp.m_address = address; + bp.m_enabled = true; + return bp; +} + +DebugBreakpoint GdbMiAdapter::AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type) +{ + // For now, just use the offset as address + return AddBreakpoint(address.offset, breakpoint_type); +} + +bool GdbMiAdapter::RemoveBreakpoint(const DebugBreakpoint& breakpoint) +{ + if (!m_gdbProcess) + return false; + + std::string cmd = fmt::format("-break-delete {}", breakpoint.m_id); + SendCommand(cmd); + return true; +} + +std::vector GdbMiAdapter::GetModuleList() +{ + return {}; +} + +std::vector GdbMiAdapter::GetRegisters() +{ + return {}; +} + +bool GdbMiAdapter::SetRegisterValue(const std::string& name, std::uintptr_t value) +{ + if (!m_gdbProcess) + return false; + + std::string cmd = fmt::format("-gdb-set ${} = 0x{:x}", name, value); + SendCommand(cmd); + return true; +} + +std::vector GdbMiAdapter::GetFramesOfThread(uint32_t tid) +{ + return {}; +} + +DataBuffer GdbMiAdapter::ReadMemory(std::uintptr_t address, std::size_t size) +{ + DataBuffer buffer; + if (!m_gdbProcess) + return buffer; + + std::string cmd = fmt::format("-data-read-memory-bytes 0x{:x} {}", address, size); + std::string response = SendCommand(cmd); + + // Parse response and fill buffer + // This is a simplified implementation + + return buffer; +} + +bool GdbMiAdapter::WriteMemory(std::uintptr_t address, const DataBuffer& buffer) +{ + if (!m_gdbProcess) + return false; + + // Convert buffer to hex string + std::string hexData; + for (size_t i = 0; i < buffer.GetLength(); i++) + { + hexData += fmt::format("{:02x}", buffer[i]); + } + + std::string cmd = fmt::format("-data-write-memory-bytes 0x{:x} \"{}\"", address, hexData); + SendCommand(cmd); + return true; +} + +std::string GdbMiAdapter::GetTargetArchitecture() +{ + if (!m_gdbProcess) + return ""; + + SendCommand("-data-evaluate-expression \"sizeof(void*)\""); + // Parse response to determine architecture + return m_defaultArchitecture; +} + +uint64_t GdbMiAdapter::GetInstructionOffset() +{ + if (!m_gdbProcess) + return 0; + + std::string response = SendCommand("-data-evaluate-expression \"$pc\""); + // Parse response to get PC value + return 0; +} + +uint64_t GdbMiAdapter::GetStackPointer() +{ + if (!m_gdbProcess) + return 0; + + std::string response = SendCommand("-data-evaluate-expression \"$sp\""); + // Parse response to get SP value + return 0; +} + +DebugStopReason GdbMiAdapter::StopReason() +{ + return ProcessStopped; +} + +bool GdbMiAdapter::BreakInto() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-interrupt"); + m_isTargetRunning = false; + return true; +} + +bool GdbMiAdapter::Go() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-continue"); + m_isTargetRunning = true; + return true; +} + +bool GdbMiAdapter::StepInto() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-step"); + return true; +} + +bool GdbMiAdapter::StepOver() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-next"); + return true; +} + +bool GdbMiAdapter::StepReturn() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-finish"); + return true; +} + +bool GdbMiAdapter::GoReverse() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-continue --reverse"); + return true; +} + +bool GdbMiAdapter::StepIntoReverse() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-step --reverse"); + return true; +} + +bool GdbMiAdapter::StepOverReverse() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-next --reverse"); + return true; +} + +bool GdbMiAdapter::StepReturnReverse() +{ + if (!m_gdbProcess) + return false; + + SendCommand("-exec-finish --reverse"); + return true; +} + +std::string GdbMiAdapter::InvokeBackendCommand(const std::string& command) +{ + if (!m_gdbProcess) + return ""; + + // For direct GDB commands, use -interpreter-exec console + std::string cmd = fmt::format("-interpreter-exec console \"{}\"", command); + return SendCommand(cmd); +} + +bool GdbMiAdapter::SupportFeature(DebugAdapterCapacity feature) +{ + switch (feature) + { + case DebugAdapterCapacityBreakpoint: + case DebugAdapterCapacityStep: + case DebugAdapterCapacityMemoryRead: + case DebugAdapterCapacityMemoryWrite: + case DebugAdapterCapacityRegisters: + return true; + default: + return false; + } +} + +Ref GdbMiAdapter::GetAdapterSettings() +{ + return GdbMiAdapterType::GetAdapterSettings(); +} + +// GdbMiAdapterType implementation +GdbMiAdapterType::GdbMiAdapterType() : DebugAdapterType("GDB MI (Local)") {} + +DebugAdapter* GdbMiAdapterType::Create(BinaryNinja::BinaryView* data) +{ + return new GdbMiAdapter(data); +} + +bool GdbMiAdapterType::IsValidForData(BinaryNinja::BinaryView* data) +{ + // Check if system GDB is available + GdbMiAdapter adapter(data); + std::string gdbPath = adapter.GetGdbExecutablePath(); + if (gdbPath.empty()) + { + LogInfo("GDB MI adapter requires system GDB installation"); + return false; + } + + // Also check if gdbserver is bundled + std::string gdbServerPath = adapter.GetGdbServerPath(); + return std::filesystem::exists(gdbServerPath); +} + +bool GdbMiAdapterType::CanExecute(BinaryNinja::BinaryView* data) +{ + return IsValidForData(data); +} + +bool GdbMiAdapterType::CanConnect(BinaryNinja::BinaryView* data) +{ + return false; // This adapter is for local debugging only +} + +Ref GdbMiAdapterType::RegisterAdapterSettings() +{ + Ref settings = Settings::Instance("GdbMiAdapterSettings"); + settings->SetResourceId("gdb_mi_adapter_settings"); + + settings->RegisterSetting("common.inputFile", + R"({ + "title" : "Input File", + "type" : "string", + "default" : "", + "description" : "Input file to use to find the base address of the binary view", + "readOnly" : false, + "uiSelectionAction" : "file" + })"); + + return settings; +} + +Ref GdbMiAdapterType::GetAdapterSettings() +{ + static Ref settings = RegisterAdapterSettings(); + return settings; +} + +void BinaryNinjaDebugger::InitGdbMiAdapterType() +{ + static GdbMiAdapterType* gdbMiAdapterType = new GdbMiAdapterType(); + DebugAdapterType::Register(gdbMiAdapterType); +} \ No newline at end of file diff --git a/core/adapters/gdbmiadapter.h b/core/adapters/gdbmiadapter.h new file mode 100644 index 00000000..43a37bf1 --- /dev/null +++ b/core/adapters/gdbmiadapter.h @@ -0,0 +1,174 @@ +/* +Copyright 2020-2025 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + * GDB Machine Interface (MI) Adapter + * + * This adapter provides local debugging capabilities using GDB's Machine Interface + * protocol instead of the Remote Serial Protocol (RSP) used by the existing GdbAdapter. + * + * Key differences from GdbAdapter: + * - Uses GDB MI protocol for structured communication + * - Supports local process execution (not just remote connection) + * - Requires system GDB installation with MI support + * - Can use bundled gdbserver for enhanced debugging features + * + * Current implementation uses system GDB directly for simplicity, but can be + * extended to use gdbserver for more advanced scenarios. + */ + +#pragma once +#include "../debugadapter.h" +#include "../debugadaptertype.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + +namespace BinaryNinjaDebugger +{ + class GdbMiAdapter : public DebugAdapter + { + private: + struct GdbProcess + { +#ifdef _WIN32 + HANDLE process; + HANDLE stdinPipe; + HANDLE stdoutPipe; + HANDLE stderrPipe; +#else + pid_t pid; + int stdinFd; + int stdoutFd; + int stderrFd; +#endif + bool valid; + + GdbProcess(); + ~GdbProcess(); + bool IsRunning(); + void Terminate(); + }; + + std::unique_ptr m_gdbProcess; + std::thread m_outputThread; + std::thread m_errorThread; + std::mutex m_commandMutex; + std::condition_variable m_responseCondition; + std::string m_lastResponse; + bool m_responseReady; + bool m_isTargetRunning; + uint64_t m_exitCode; + + // GDB MI Communication + std::string SendCommand(const std::string& command); + void ProcessOutput(); + void ProcessError(); + std::string ParseMiResponse(const std::string& response); + bool StartGdbProcess(); + void StopGdbProcess(); + std::string GetGdbExecutablePath(); + std::string GetGdbServerPath(); + + // Helper methods + void GenerateDefaultAdapterSettings(BinaryView* data); + + public: + GdbMiAdapter(BinaryView* data); + ~GdbMiAdapter(); + + bool Execute(const std::string& path, const LaunchConfigurations& configs) override; + bool ExecuteWithArgs(const std::string& path, const std::string& args, const std::string& workingDir, + const LaunchConfigurations& configs) override; + bool Attach(std::uint32_t pid) override; + bool Connect(const std::string& server, std::uint32_t port) override; + bool ConnectToDebugServer(const std::string& server, std::uint32_t port) override; + + bool Detach() override; + bool Quit() override; + + std::vector GetProcessList() override; + std::vector GetThreadList() override; + DebugThread GetActiveThread() const override; + std::uint32_t GetActiveThreadId() const override; + bool SetActiveThread(const DebugThread& thread) override; + bool SetActiveThreadId(std::uint32_t tid) override; + + std::vector GetBreakpointList() const override; + DebugBreakpoint AddBreakpoint(const std::uintptr_t address, unsigned long breakpoint_type = 0) override; + DebugBreakpoint AddBreakpoint(const ModuleNameAndOffset& address, unsigned long breakpoint_type = 0) override; + bool RemoveBreakpoint(const DebugBreakpoint& breakpoint) override; + + std::vector GetModuleList() override; + std::vector GetRegisters() override; + bool SetRegisterValue(const std::string& name, std::uintptr_t value) override; + + std::vector GetFramesOfThread(uint32_t tid) override; + DataBuffer ReadMemory(std::uintptr_t address, std::size_t size) override; + bool WriteMemory(std::uintptr_t address, const DataBuffer& buffer) override; + + std::string GetTargetArchitecture() override; + uint64_t GetInstructionOffset() override; + uint64_t GetStackPointer() override; + DebugStopReason StopReason() override; + uint64_t ExitCode() override { return m_exitCode; } + + bool BreakInto() override; + bool Go() override; + bool StepInto() override; + bool StepOver() override; + bool StepReturn() override; + + bool GoReverse() override; + bool StepIntoReverse() override; + bool StepOverReverse() override; + bool StepReturnReverse() override; + + std::string InvokeBackendCommand(const std::string& command) override; + bool SupportFeature(DebugAdapterCapacity feature) override; + + Ref GetAdapterSettings() override; + + // Public helper for adapter type validation + std::string GetGdbServerPath(); + }; + + class GdbMiAdapterType : public DebugAdapterType + { + static Ref RegisterAdapterSettings(); + + public: + GdbMiAdapterType(); + virtual DebugAdapter* Create(BinaryNinja::BinaryView* data); + virtual bool IsValidForData(BinaryNinja::BinaryView* data); + virtual bool CanExecute(BinaryNinja::BinaryView* data); + virtual bool CanConnect(BinaryNinja::BinaryView* data); + static Ref GetAdapterSettings(); + }; + + void InitGdbMiAdapterType(); +}; \ No newline at end of file diff --git a/core/debugger.cpp b/core/debugger.cpp index 47ce5e27..159da5c3 100644 --- a/core/debugger.cpp +++ b/core/debugger.cpp @@ -16,6 +16,7 @@ limitations under the License. #include #include "adapters/gdbadapter.h" +#include "adapters/gdbmiadapter.h" #include "adapters/lldbrspadapter.h" #include "adapters/lldbadapter.h" #include "adapters/corelliumadapter.h" @@ -50,6 +51,7 @@ void InitDebugAdapterTypes() InitCorelliumAdapterType(); InitGdbAdapterType(); + InitGdbMiAdapterType(); InitLldbAdapterType(); InitEsrevenAdapterType(); InitLldbCoreDumpAdapterType();