From ce9b09ea67fab9506640d42d762b8e4960fa1b5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 05:58:44 +0000 Subject: [PATCH 01/11] Initial plan From be6c547edf893bd70aade658b11552f2658aa684 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 06:05:45 +0000 Subject: [PATCH 02/11] Implement GetModuleList() for GDB MI adapter Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- core/adapters/gdbmiadapter.cpp | 156 +++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index 800d74f9..7be0ff12 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -1,5 +1,7 @@ #include "gdbmiadapter.h" #include +#include +#include #include "../debuggercontroller.h" // Assuming this path is correct for your project structure #include "../../cli/log.h" // For Log::print @@ -774,7 +776,161 @@ std::vector GdbMiAdapter::GetModuleList() if (!m_mi) return {}; + // Use -interpreter-exec to run the console command "info proc mappings" + auto result = m_mi->SendCommand("-interpreter-exec console \"info proc mappings\""); + if (result.command != "done") + { + LogWarn("Failed to get process mappings: %s", result.fullLine.c_str()); + return {}; + } + std::vector modules; + std::map moduleNameCount; // Track module name occurrences for duplicates + std::map>> moduleRanges; // path -> list of (start, end) + std::vector moduleOrder; // Track the order in which modules are first seen + + // Parse the console output from async records + // The output will be in console stream records ('~') + // We need to accumulate the console output and parse it + // For now, we'll try to parse from the result payload if available + + // Since the output is sent as console stream, we need a different approach. + // Let's send the command and wait for console output. + // Actually, the console output should be in the async records. + // For simplicity, let's use InvokeBackendCommand which also uses -interpreter-exec + std::string output = InvokeBackendCommand("info proc mappings"); + + if (output.empty() || output == "error, transport not ready") + { + LogWarn("Failed to get process mappings output"); + return {}; + } + + // Parse the output line by line + // Expected format (from the issue): + // process 25443 + // Mapped address spaces: + // + // Start Addr End Addr Size Offset Perms objfile + // 0x555555554000 0x555555555000 0x1000 0x0 r--p /path/to/file + + std::istringstream stream(output); + std::string line; + bool headerFound = false; + + while (std::getline(stream, line)) + { + // Skip until we find the header line + if (!headerFound) + { + if (line.find("Start Addr") != std::string::npos && + line.find("End Addr") != std::string::npos) + { + headerFound = true; + } + continue; + } + + // Parse data lines + // Format: Start_Addr End_Addr Size Offset Perms objfile + std::istringstream lineStream(line); + std::string startStr, endStr, sizeStr, offsetStr, perms, objfile; + + lineStream >> startStr >> endStr >> sizeStr >> offsetStr >> perms; + + // Rest of the line is the objfile (path) + std::getline(lineStream, objfile); + + // Trim leading whitespace from objfile + size_t firstNonSpace = objfile.find_first_not_of(" \t"); + if (firstNonSpace != std::string::npos) + { + objfile = objfile.substr(firstNonSpace); + } + + // Skip lines without valid addresses or without objfile + if (startStr.empty() || endStr.empty() || objfile.empty()) + continue; + + // Skip special mappings like [stack], [heap], [vvar], [vdso], etc. + if (objfile[0] == '[') + continue; + + try + { + uint64_t start = std::stoull(startStr, nullptr, 16); + uint64_t end = std::stoull(endStr, nullptr, 16); + + // Track the order of first occurrence + if (moduleRanges.find(objfile) == moduleRanges.end()) + { + moduleOrder.push_back(objfile); + } + + // Accumulate ranges for each object file + moduleRanges[objfile].emplace_back(start, end); + } + catch (const std::exception& e) + { + LogDebug("Failed to parse address range: %s", e.what()); + continue; + } + } + + // Now create DebugModule entries in the order they were first encountered + // For each unique object file, we need to determine its overall address range + for (const auto& path : moduleOrder) + { + const auto& ranges = moduleRanges[path]; + if (ranges.empty()) + continue; + + // Find the minimum start and maximum end + uint64_t minStart = ranges[0].first; + uint64_t maxEnd = ranges[0].second; + + for (const auto& [start, end] : ranges) + { + minStart = std::min(minStart, start); + maxEnd = std::max(maxEnd, end); + } + + // Extract the base name from the path for m_short_name + std::string shortName = path; + size_t lastSlash = path.find_last_of("/\\"); + if (lastSlash != std::string::npos) + { + shortName = path.substr(lastSlash + 1); + } + + // Handle duplicate names by appending -1, -2, etc. + // The first occurrence gets the original name, subsequent ones get -1, -2, etc. + std::string finalName = path; + if (moduleNameCount.find(path) != moduleNameCount.end()) + { + // This is a duplicate (shouldn't happen with current logic, but keep for safety) + int count = ++moduleNameCount[path]; + finalName = path + "-" + std::to_string(count); + } + else + { + // First occurrence + moduleNameCount[path] = 0; + } + + DebugModule module; + module.m_name = finalName; + module.m_short_name = shortName; + module.m_address = minStart; + module.m_size = maxEnd - minStart; + module.m_loaded = true; + + modules.push_back(module); + } + + return modules; +} + return modules; } From 5add33b83767136027d1b8ee612903329e4f2a62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 06:08:24 +0000 Subject: [PATCH 03/11] Fix parsing to skip anonymous mappings properly Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- core/adapters/gdbmiadapter.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index 7be0ff12..6864da14 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -841,11 +841,20 @@ std::vector GdbMiAdapter::GetModuleList() // Rest of the line is the objfile (path) std::getline(lineStream, objfile); - // Trim leading whitespace from objfile + // Trim leading and trailing whitespace from objfile size_t firstNonSpace = objfile.find_first_not_of(" \t"); - if (firstNonSpace != std::string::npos) + if (firstNonSpace == std::string::npos) { - objfile = objfile.substr(firstNonSpace); + // Line is all whitespace, skip it + continue; + } + objfile = objfile.substr(firstNonSpace); + + // Trim trailing whitespace + size_t lastNonSpace = objfile.find_last_not_of(" \t"); + if (lastNonSpace != std::string::npos) + { + objfile = objfile.substr(0, lastNonSpace + 1); } // Skip lines without valid addresses or without objfile From 7f06378b869d536690bcfdc902b6af9a5ab0c507 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 06:09:49 +0000 Subject: [PATCH 04/11] Remove duplicate return statement Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- _codeql_detected_source_root | 1 + core/adapters/gdbmiadapter.cpp | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index 6864da14..25f40afe 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -939,9 +939,6 @@ std::vector GdbMiAdapter::GetModuleList() return modules; } - - return modules; -} bool GdbMiAdapter::Go() { From 692bfdbbbe0aa47ff4eec108e89157d89cc7e46e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 06:10:20 +0000 Subject: [PATCH 05/11] Remove accidentally added codeql symlink Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- _codeql_detected_source_root | 1 - 1 file changed, 1 deletion(-) delete mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b46..00000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file From f33a65a213aa1c857d8b25e6313fbe2ec162edaf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 06:41:36 +0000 Subject: [PATCH 06/11] Add process exit detection and event handling - Add m_exitCode member to store exit code from GDB - Parse exit-code field from stopped event when process exits - Post TargetExitedEventType event when process exits - Update ExitCode() to return stored exit code - Maintain GetModuleList() implementation for parsing info proc mappings Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- _codeql_detected_source_root | 1 + core/adapters/gdbmiadapter.cpp | 38 +++++++++++++++++++++++++++++----- core/adapters/gdbmiadapter.h | 1 + 3 files changed, 35 insertions(+), 5 deletions(-) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index 25f40afe..af142ad1 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -197,10 +197,38 @@ void GdbMiAdapter::AsyncRecordHandler(const MiRecord& record) // Update target state BEFORE posting events m_targetRunningAtomic.store(false, std::memory_order_release); - // Kick a background refresh so we don’t block the reader - ScheduleStateRefresh(); - - m_eventCV.notify_all(); + // Check if the process has exited + if (m_lastStopReason == ProcessExited) + { + // Parse exit code if available + if (value.Exists("exit-code")) + { + try { + m_exitCode = std::stoull(value["exit-code"].GetString(), nullptr, 0); + } catch(...) { + LogWarn("Failed to parse exit code"); + m_exitCode = 0; + } + } + else + { + m_exitCode = 0; + } + + // Post target exited event + DebuggerEvent dbgevt; + dbgevt.type = TargetExitedEventType; + dbgevt.data.exitData.exitCode = m_exitCode; + PostDebuggerEvent(dbgevt); + + m_eventCV.notify_all(); + } + else + { + // Normal stop - kick a background refresh so we don't block the reader + ScheduleStateRefresh(); + m_eventCV.notify_all(); + } } else if (record.command == "running") { @@ -987,7 +1015,7 @@ std::string GdbMiAdapter::InvokeBackendCommand(const std::string& command) { return (result.command == "done") ? result.payload : result.command; } -uint64_t GdbMiAdapter::ExitCode() { return 0; } +uint64_t GdbMiAdapter::ExitCode() { return m_exitCode; } DebugStopReason GdbMiAdapter::StopReason() { return m_lastStopReason; } diff --git a/core/adapters/gdbmiadapter.h b/core/adapters/gdbmiadapter.h index 1767b4e1..a2aa3a4f 100644 --- a/core/adapters/gdbmiadapter.h +++ b/core/adapters/gdbmiadapter.h @@ -15,6 +15,7 @@ class GdbMiAdapter : public BinaryNinjaDebugger::DebugAdapter std::vector m_registerNames; // In GDB's order BinaryNinjaDebugger::DebugStopReason m_lastStopReason; std::atomic m_targetRunningAtomic{false}; + uint64_t m_exitCode = 0; std::mutex m_eventMutex; std::mutex m_gdbCommandMutex; From 02d2b7ee9c9bc37175ce804d56f4c5b4d782efd5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 06:41:59 +0000 Subject: [PATCH 07/11] Remove codeql symlink --- _codeql_detected_source_root | 1 - 1 file changed, 1 deletion(-) delete mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b46..00000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file From 8ca040bbafa882160f21b9128599ba56da9083e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:34:46 +0000 Subject: [PATCH 08/11] Fix synchronization issue with console output buffering - Add console output buffering mechanism to collect async console output - Buffer console output ('~' records) when m_collectConsoleOutput is enabled - Use buffering in GetModuleList() to properly collect info proc mappings output - Increase timeout to 5 seconds for info proc mappings command - Remove dependency on InvokeBackendCommand which lost console output This fixes the race condition where console output from 'info proc mappings' arrived after the command timeout, causing subsequent commands to fail. Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- _codeql_detected_source_root | 1 + core/adapters/gdbmiadapter.cpp | 52 ++++++++++++++++++++++------------ core/adapters/gdbmiadapter.h | 5 ++++ 3 files changed, 40 insertions(+), 18 deletions(-) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index af142ad1..059f779e 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -273,6 +273,15 @@ void GdbMiAdapter::AsyncRecordHandler(const MiRecord& record) } } + // If we're collecting console output, buffer it + { + std::lock_guard lock(m_consoleBufferMutex); + if (m_collectConsoleOutput && record.type == '~') + { + m_consoleBuffer += message; + } + } + DebuggerEvent event; event.type = BackendMessageEventType; event.data.messageData.message = message; @@ -804,36 +813,43 @@ std::vector GdbMiAdapter::GetModuleList() if (!m_mi) return {}; + // Enable console output buffering + { + std::lock_guard lock(m_consoleBufferMutex); + m_consoleBuffer.clear(); + m_collectConsoleOutput = true; + } + // Use -interpreter-exec to run the console command "info proc mappings" - auto result = m_mi->SendCommand("-interpreter-exec console \"info proc mappings\""); + // Use a longer timeout since this command can generate a lot of output + auto result = m_mi->SendCommand("-interpreter-exec console \"info proc mappings\"", 5000); + + // Disable console output buffering and get the collected output + std::string output; + { + std::lock_guard lock(m_consoleBufferMutex); + m_collectConsoleOutput = false; + output = m_consoleBuffer; + m_consoleBuffer.clear(); + } + if (result.command != "done") { LogWarn("Failed to get process mappings: %s", result.fullLine.c_str()); return {}; } + if (output.empty()) + { + LogWarn("No console output received from info proc mappings"); + return {}; + } + std::vector modules; std::map moduleNameCount; // Track module name occurrences for duplicates std::map>> moduleRanges; // path -> list of (start, end) std::vector moduleOrder; // Track the order in which modules are first seen - // Parse the console output from async records - // The output will be in console stream records ('~') - // We need to accumulate the console output and parse it - // For now, we'll try to parse from the result payload if available - - // Since the output is sent as console stream, we need a different approach. - // Let's send the command and wait for console output. - // Actually, the console output should be in the async records. - // For simplicity, let's use InvokeBackendCommand which also uses -interpreter-exec - std::string output = InvokeBackendCommand("info proc mappings"); - - if (output.empty() || output == "error, transport not ready") - { - LogWarn("Failed to get process mappings output"); - return {}; - } - // Parse the output line by line // Expected format (from the issue): // process 25443 diff --git a/core/adapters/gdbmiadapter.h b/core/adapters/gdbmiadapter.h index a2aa3a4f..f2ffc31e 100644 --- a/core/adapters/gdbmiadapter.h +++ b/core/adapters/gdbmiadapter.h @@ -21,6 +21,11 @@ class GdbMiAdapter : public BinaryNinjaDebugger::DebugAdapter std::mutex m_gdbCommandMutex; std::condition_variable m_eventCV; + // --- Console output buffering for commands that need it --- + std::mutex m_consoleBufferMutex; + std::string m_consoleBuffer; + bool m_collectConsoleOutput = false; + // --- Cached Target State --- std::mutex m_cacheMutex; // To protect access to cached data std::vector m_cachedThreads; From 85cc2161e8057c2836febf720d62eb059669fa48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:35:18 +0000 Subject: [PATCH 09/11] Remove codeql symlink Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- _codeql_detected_source_root | 1 - 1 file changed, 1 deletion(-) delete mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b46..00000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file From d392a050c787558d58e59ed39b847662296bdb42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:51:03 +0000 Subject: [PATCH 10/11] Serialize GetModuleList() with gdbCommandMutex Acquire m_gdbCommandMutex in GetModuleList() to prevent concurrent GDB commands from being sent while waiting for the response to 'info proc mappings'. This fixes the issue where ReadMemory() and other commands were being sent before the module list response completed, causing command interleaving and timeouts. Without this lock, multiple threads could send commands simultaneously, resulting in GDB responses arriving out of order and causing confusion. Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- _codeql_detected_source_root | 1 + core/adapters/gdbmiadapter.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index 059f779e..5dc60381 100644 --- a/core/adapters/gdbmiadapter.cpp +++ b/core/adapters/gdbmiadapter.cpp @@ -813,9 +813,13 @@ std::vector GdbMiAdapter::GetModuleList() if (!m_mi) return {}; + // Acquire the command mutex to prevent other threads from sending commands + // while we're waiting for the response to "info proc mappings" + std::unique_lock lock(m_gdbCommandMutex); + // Enable console output buffering { - std::lock_guard lock(m_consoleBufferMutex); + std::lock_guard bufferLock(m_consoleBufferMutex); m_consoleBuffer.clear(); m_collectConsoleOutput = true; } @@ -827,7 +831,7 @@ std::vector GdbMiAdapter::GetModuleList() // Disable console output buffering and get the collected output std::string output; { - std::lock_guard lock(m_consoleBufferMutex); + std::lock_guard bufferLock(m_consoleBufferMutex); m_collectConsoleOutput = false; output = m_consoleBuffer; m_consoleBuffer.clear(); From a07ded7e307ecd653c32e228f74a939bb559b9a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:51:43 +0000 Subject: [PATCH 11/11] Remove codeql symlink Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com> --- _codeql_detected_source_root | 1 - 1 file changed, 1 deletion(-) delete mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b46..00000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file