diff --git a/core/adapters/gdbmiadapter.cpp b/core/adapters/gdbmiadapter.cpp index 800d74f9..5dc60381 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 @@ -195,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") { @@ -243,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; @@ -774,7 +813,178 @@ 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 bufferLock(m_consoleBufferMutex); + m_consoleBuffer.clear(); + m_collectConsoleOutput = true; + } + + // Use -interpreter-exec to run the console command "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 bufferLock(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 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 and trailing whitespace from objfile + size_t firstNonSpace = objfile.find_first_not_of(" \t"); + if (firstNonSpace == std::string::npos) + { + // 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 + 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; } @@ -825,7 +1035,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..f2ffc31e 100644 --- a/core/adapters/gdbmiadapter.h +++ b/core/adapters/gdbmiadapter.h @@ -15,11 +15,17 @@ 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; 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;