/
George_Smith
/
GDBManager
Обзор
Документация
Войти
/
George_Smith
/
GDBManager
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
devel
gdbengine.cpp
823 строки
25 KB
George Smith
fix: handle nested arrays with <repeats ... times> pattern.
01 мар 2026, 13:50
01 мар 2026, 13:50
b0906bf
Код
Авторство
О чём код?
#include "gdbengine.h" #include <unistd.h> #include <sys/wait.h> #include <iostream> #include <stdio.h> //#include <QDebug> GdbEngine::~GdbEngine() { if (reader_thread.joinable()) { reader_thread.detach(); } } /* Trim whitespace from both ends of a string */ std::string GdbEngine::trim(const std::string& str) { size_t first = str.find_first_not_of(" \t\n\r"); if (first == std::string::npos) return ""; size_t last = str.find_last_not_of(" \t\n\r"); return str.substr(first, last - first + 1); } /* Parse a value that could be a number, boolean, or unquoted string */ nlohmann::json GdbEngine::parseValue(const std::string& valueStr) { std::string trimmed = trim(valueStr); // Only null and boolean convert to special types if (trimmed == "null") return nullptr; if (trimmed == "true") return true; if (trimmed == "false") return false; return trimmed; } /* Parse a GDB struct/object format: {key = value, key2 = value2, ...} */ void GdbEngine::parseGdbStruct(const std::string& content, nlohmann::json& j) { size_t i = 0; while (i < content.size()) { i = content.find_first_not_of(", \t\n\r", i); if (i == std::string::npos) break; size_t eq = content.find('=', i); if (eq == std::string::npos) break; std::string key = trim(content.substr(i, eq - i)); i = eq + 1; i = content.find_first_not_of(" \t\n\r", i); if (i >= content.size()) break; if (content[i] == '"') { j[key] = extractString(content, i); } else if (content[i] == '{') { size_t tempPos = i; std::string inner = extractContent(content, tempPos, '{', '}'); std::string trimmedInner = trim(inner); // Check if this is an array of structures // Array of structures starts with { and contains other { } inside if (trimmedInner.size() > 0 && trimmedInner[0] == '{') { j[key] = parseArrayOfStructs(trimmedInner); } // Check if this is an array of numbers (no '=' signs, but has commas) else { bool hasEquals = false; bool hasComma = false; for (char c : trimmedInner) { if (c == '=') { hasEquals = true; break; } if (c == ',') hasComma = true; } if (!hasEquals && hasComma) { nlohmann::json arr = nlohmann::json::array(); size_t numPos = 0; while (numPos < trimmedInner.size()) { numPos = trimmedInner.find_first_not_of(", \t\n\r", numPos); if (numPos == std::string::npos) break; size_t numEnd = trimmedInner.find_first_of(",", numPos); if (numEnd == std::string::npos) numEnd = trimmedInner.size(); std::string numStr = trimmedInner.substr(numPos, numEnd - numPos); arr.push_back(parseValue(numStr)); numPos = numEnd; } j[key] = arr; } else if (hasEquals) { nlohmann::json nested = nlohmann::json::object(); parseGdbStruct(inner, nested); j[key] = nested; } else { j[key] = nlohmann::json::object(); } } i = tempPos; } else if (content[i] == '[') { size_t tempPos = i; std::string inner = extractContent(content, tempPos, '[', ']'); if (key == "children") { // Для children - парсим специальным способом nlohmann::json childrenArray = nlohmann::json::array(); parseChildrenArray(inner, childrenArray); // Специальный парсер для child={...} j[key] = childrenArray; } else { // Для других массивов - обычный парсинг nlohmann::json nested = nlohmann::json::array(); parseArrayElements(inner, nested); j[key] = nested; } i = tempPos; } else { size_t end = content.find_first_of(",}", i); if (end == std::string::npos) end = content.size(); std::string val = content.substr(i, end - i); j[key] = parseValue(val); i = end; } } } /* Extracts content between matching brackets considering nested structures */ std::string GdbEngine::extractContent(const std::string& payload, size_t& pos, const char& openChar, const char& closeChar){ int depth = 1; size_t start = pos + 1; size_t curr = start; while ((curr < payload.size()) && (depth > 0)) { if (payload[curr] == openChar) depth++; if (payload[curr] == closeChar) depth--; curr++; } pos = curr; if (curr - start - 1 > payload.size()) return ""; return payload.substr(start, curr - start - 1); } /* String extraction */ std::string GdbEngine::extractString(const std::string& payload, size_t& i) { std::string result; bool escaped = false; // Пропускаем открывающую кавычку i++; // move past the opening quote while (i < payload.size()) { char c = payload[i]; if (!escaped && c == '\\') { // Начало escape-последовательности escaped = true; i++; continue; } else if (!escaped && c == '"') { // Закрывающая кавычка (не экранированная) i++; // move past closing quote break; } else if (escaped) { // Обработка escape-последовательности switch (c) { case 'n': result += '\n'; break; case 't': result += '\t'; break; case 'r': result += '\r'; break; case 'b': result += '\b'; break; case 'f': result += '\f'; break; case 'v': result += '\v'; break; case '\\': result += '\\'; break; case '\'': result += '\''; break; case '"': result += '"'; break; case '0': result += '\0'; break; default: // Если неизвестная escape-последовательность, сохраняем как есть result += '\\'; result += c; break; } escaped = false; i++; } else { // Обычный символ result += c; i++; } } return result; } /* Parse array elements (supports mixed types) */ void GdbEngine::parseArrayElements(const std::string& content, nlohmann::json& jArray) { size_t k = 0; while (k < content.size()) { k = content.find_first_not_of(", \t\n\r", k); if (k == std::string::npos) break; if (content[k] == '"') { jArray.push_back(extractString(content, k)); } else if (content[k] == '[') { nlohmann::json item = nlohmann::json::array(); size_t tempPos = k; std::string inner = extractContent(content, tempPos, '[', ']'); parseArrayElements(inner, item); jArray.push_back(item); k = tempPos; } else if (content[k] == '{') { nlohmann::json item = nlohmann::json::object(); size_t tempPos = k; std::string inner = extractContent(content, tempPos, '{', '}'); parseGdbStruct(inner, item); jArray.push_back(item); k = tempPos; } else { size_t end = content.find_first_of(",]", k); if (end == std::string::npos) end = content.size(); std::string val = content.substr(k, end - k); // Always string jArray.push_back(trim(val)); k = end; } } } /* Parse array of structures in format {{...}, {...}, ...} */ nlohmann::json GdbEngine::parseArrayOfStructs(const std::string& inner) { nlohmann::json result = nlohmann::json::array(); size_t arrPos = 0; std::string trimmedInner = trim(inner); while (arrPos < trimmedInner.size()) { arrPos = trimmedInner.find_first_not_of(", \t\n\r", arrPos); if (arrPos == std::string::npos) break; if (trimmedInner[arrPos] == '{') { size_t structPos = arrPos; // Находим закрывающую скобку для ЭТОГО элемента int depth = 1; size_t endPos = arrPos + 1; while (endPos < trimmedInner.size() && depth > 0) { if (trimmedInner[endPos] == '{') depth++; if (trimmedInner[endPos] == '}') depth--; endPos++; } // Извлекаем полный элемент (со скобками) std::string element = trimmedInner.substr(arrPos, endPos - arrPos); // Проверяем, не массив ли это внутри std::string innerContent = element.substr(1, element.size() - 2); nlohmann::json item; // Если внутри есть еще одна пара скобок с запятой - это массив if (innerContent.find('{') != std::string::npos && innerContent.find('}') != std::string::npos && innerContent.find(',') != std::string::npos) { // Это вложенный массив - рекурсивно парсим item = parseArrayOfStructs(innerContent); } else { // Это простая структура item = nlohmann::json::object(); parseGdbStruct(innerContent, item); } result.push_back(item); arrPos = endPos; } else { arrPos++; } } return result; } /* Parse array for children [child={...}, child={...}, ...] */ void GdbEngine::parseChildrenArray(const std::string& inner, nlohmann::json& jArray) { //default name in mi2 interface name for object children static const std::string CHILD_PATTERN = "child="; static const size_t PATTERN_LENGTH = CHILD_PATTERN.length(); size_t arrPos = 0; while (arrPos < inner.size()) { arrPos = inner.find_first_not_of(", \t\n\r", arrPos); if (arrPos == std::string::npos) break; if (inner.compare(arrPos, PATTERN_LENGTH, CHILD_PATTERN) == 0) { arrPos += PATTERN_LENGTH; arrPos = inner.find_first_not_of(" \t\n\r", arrPos); if (arrPos >= inner.size() || inner[arrPos] != '{') break; size_t tempPos = arrPos; std::string trimmedInner = extractContent(inner, tempPos, '{', '}'); // Using default parser! nlohmann::json childObj = nlohmann::json::object(); parseGdbStruct(trimmedInner, childObj); jArray.push_back(childObj); arrPos = tempPos; } else { arrPos++; } } } /* Main entry point for parsing any GDB value * Handles: structures {}, arrays [], strings, numbers, booleans, null */ nlohmann::json GdbEngine::parseVariableValue(const std::string& valueStr) { std::string trimmed = trim(valueStr); if (trimmed.empty()) return nullptr; if (trimmed[0] == '{') { size_t pos = 0; std::string inner = extractContent(trimmed, pos, '{', '}'); std::string trimmedInner = trim(inner); if (trimmedInner.size() > 0 && trimmedInner[0] == '{') { return parseArrayOfStructs(trimmedInner); } else { nlohmann::json result = nlohmann::json::object(); parseGdbStruct(inner, result); return result; } } if (trimmed[0] == '[') { nlohmann::json result = nlohmann::json::array(); size_t pos = 0; std::string inner = extractContent(trimmed, pos, '[', ']'); parseArrayElements(inner, result); return result; } // Simple value - always string return trimmed; } void GdbEngine::handleStopAsync(const GdbResponse& rawResponse){ if (!on_stopped) return; GdbResponse event; event.type = STOPPED; // Default event event.className = rawResponse.className; // Check if this is a signal (crash) if (rawResponse.results.contains("reason")) { std::string reason = rawResponse.results["reason"]; event.results["reason"] = reason; // If reason is signal, change type to SIGNAL if (reason == "signal-received" || reason == "exited-signalled") { event.type = SIGNAL; } } // Look for signal name (SIGSEGV, SIGILL, etc.) if (rawResponse.results.contains("signal-name")) { event.results["signal"] = rawResponse.results["signal-name"]; event.type = SIGNAL; // If signal name exists, this is definitely not a simple stop } if (rawResponse.results.contains("frame")) { const nlohmann::json& frame = rawResponse.results["frame"]; event.results["fullname"] = frame.value("fullname", "unknown"); event.results["line"] = frame.value("line", "unknown"); event.results["func"] = frame.value("func", "unknown"); } on_stopped(event); } void GdbEngine::handleDoneResponse(const GdbResponse& rawResponse){ GdbResponse response = rawResponse; if(rawResponse.results.contains("children")) { if (!on_children) return; response.type = CHILDREN; response.results = rawResponse.results.at("children"); on_children(response); } if(rawResponse.results.contains("bkpt")){ auto bkpt_location = rawResponse.results["bkpt"]["original-location"].get<std::string>(); auto bkpt_iterator = breakpoints.find(bkpt_location); if(bkpt_iterator != breakpoints.end()) breakpoints[bkpt_location] = std::stoi(rawResponse.results["bkpt"]["number"].get<std::string>()); } if (!on_locals) return; if(rawResponse.results.contains("locals")){ response.type = LOCALS; nlohmann::json parsedLocals = nlohmann::json::array(); const nlohmann::json& locals = rawResponse.results.at("locals"); for (const auto& local : locals) { nlohmann::json parsedLocal; parsedLocal["name"] = local["name"]; if (local.contains("value")) { parsedLocal["value"] = parseVariableValue(local["value"]); } parsedLocals.push_back(parsedLocal); } response.results = parsedLocals; on_locals(response); } } void GdbEngine::handleErrorMessage(const GdbResponse& rawResponse){ if (!on_error) return; //Preparing error event GdbResponse response = rawResponse; response.type = ERROR; //response ... on_error(response); } std::string GdbEngine::expandRepeats(const std::string& input) { std::string result = input; size_t repeatsPos = result.rfind("<repeats "); if (repeatsPos == std::string::npos) return result; size_t repeatsEnd = result.find('>', repeatsPos); if (repeatsEnd == std::string::npos) return result; // получаем число из repeats size_t numStart = repeatsPos + 9; std::string block = result.substr(numStart, repeatsEnd - numStart); size_t spacePos = block.find(' '); if (spacePos == std::string::npos) return result; int count = std::stoi(block.substr(0, spacePos)); // ищем начало значения слева size_t valueEnd = repeatsPos - 1; while (valueEnd > 0 && result[valueEnd] == ' ') --valueEnd; size_t valueStart = valueEnd; if (result[valueEnd] == '}') { // если это блок { ... } int depth = 1; while (valueStart > 0 && depth > 0) { --valueStart; if (result[valueStart] == '}') depth++; else if (result[valueStart] == '{') depth--; } } else { // это примитив (число, слово) while (valueStart > 0 && result[valueStart] != ',' && result[valueStart] != '{') { --valueStart; } if (result[valueStart] == ',' || result[valueStart] == '{') ++valueStart; } std::string value = result.substr(valueStart, valueEnd - valueStart + 1); // рекурсивно раскрываем вложенные value = expandRepeats(value); // заполняем повторы и записываем в result std::string expanded; for (int i = 0; i < count; ++i) { if (!expanded.empty()) expanded += ", "; expanded += value; } result.replace(valueStart, (repeatsEnd + 1) - valueStart, expanded); return expandRepeats(result); //выполняем до тех пор пока не будет repeats } /* Main dispatcher for filling JSON * Extract type GDB messages and parse payload to JSON * This function calling the callback funnctuin specified in setHandler() * and sends data in GdbResponse struct. */ void GdbEngine::processLine(const std::string& raw) { if (raw.empty() || raw == "(gdb) ") return; GdbResponse res; char prefix = raw[0]; // Save type: '*', '^', '=', '~' std::string payload = raw.substr(1); size_t comma = payload.find(','); if (comma != std::string::npos) { res.className = payload.substr(0, comma); parseGdbStruct(expandRepeats(payload.substr(comma + 1)), res.results); } else { res.className = payload; } if(on_event) on_event(res); //If this is a stop event (*stopped) if (prefix == '*' && res.className == "stopped") { handleStopAsync(res); } // If this is a response to a request else if (prefix == '^' && res.className == "done") { handleDoneResponse(res); } else if (prefix == '^' && res.className == "error") { handleErrorMessage(res); } } /* Launch GDB in mi2 mode */ int GdbEngine::launch(const std::string& path) { pipe(in_pipe); pipe(out_pipe); gdb_pid = fork(); if (gdb_pid == 0) { setpgid(0, 0); // Create a new process group for signals dup2(in_pipe[0], STDIN_FILENO); dup2(out_pipe[1], STDOUT_FILENO); execlp(path.c_str(), path.c_str(), "--interpreter=mi2", nullptr); _exit(1); } if(in_pipe[0] >= 0){ close(in_pipe[0]); in_pipe[0] = -1; } if(out_pipe[1] >= 0){ close(out_pipe[1]); out_pipe[1] = -1; } reader_thread = std::thread(&GdbEngine::readLoop, this); return EXIT_SUCCESS; } /* Send data to GDB via MI interfece */ void GdbEngine::send(const std::string& cmd, int token) { std::lock_guard<std::mutex> lock(write_mutex); std::string full = (token > 0 ? std::to_string(token) : "") + cmd + "\n"; int res = write(in_pipe[1], full.c_str(), full.length()); if(-1 == res){ if(errno == EPIPE){ full = "GDB connection lost, pipe is broken."; } else { full = "Write to GDB failed."; } std::cout << full; //if (on_error) on_error(); } } /* Reading data in MI interface */ void GdbEngine::readLoop() { char buffer[2048]; std::string line; while (true) { ssize_t n = read(out_pipe[0], buffer, sizeof(buffer) - 1); if (n <= 0) { // Close the input pipe (the output is already closed at startup) if (out_pipe[0] >= 0) { close(out_pipe[0]); out_pipe[0] = -1; } if(in_pipe[1] >= 0){ close(in_pipe[1]); in_pipe[1] = -1; } std::cout << "GDB DEAD " << n << std::endl; break; } buffer[n] = '\0'; for (int i = 0; i < n; ++i) { if (buffer[i] == '\n') { processLine(line); line.clear(); } else { line += buffer[i]; } } } } /* Set callback */ void GdbManager::setOnEvent(Callback cb) { on_event = cb; } void GdbManager::setOnStopped(Callback cb) { on_stopped = cb; } void GdbManager::setOnLocals(Callback cb) { on_locals = cb; } void GdbManager::setOnError(Callback cb){ on_error = cb; } void GdbManager::setOnChildren(Callback cb){ on_children = cb; } /* Run GDB in host */ int GdbManager::start() { breakpoints.clear(); types.clear(); return launch("gdb"); } /* Stop GDB in host */ void GdbManager::stop() { breakExecution(); send("-gdb-exit"); if (reader_thread.joinable()) { reader_thread.join(); // Wait for readLoop to actually exit the loop } if (gdb_pid > 0) { waitpid(gdb_pid, nullptr, 0); gdb_pid = -1; } } /* Specify the file to debug * Analogous to selecting a project in an IDE */ void GdbManager::loadProcess(const std::string& executablePath) { // -file-exec-and-symbols load code and the symbol table (function name, variables) send("-file-exec-and-symbols " + executablePath); } /* Start debug */ void GdbManager::run() { send("-exec-run"); } /* Stop GDB in host */ void GdbManager::abort(){ send("-gdb-abort"); } /* Remove debug (gdbserver) */ void GdbManager::connectToRemote(const std::string& host, int port) { send("-target-select remote " + host + ":" + std::to_string(port)); } /* Set breakpoint * @filename: path to source file (example "main.cpp") * @line: number breakpint in line */ void GdbManager::setBreakpoint(const std::string& filename, int line) { // Format: -break-insert filename:line auto place = filename + ":" + std::to_string(line); send("-break-insert -f " + place); // Store breakpoint with unknown ID for now)) breakpoints[place] = -1; } /* Remove breakpoint * @filename: path to source file (example "main.cpp") * @line: number breakpint in line */ void GdbManager::removeBreakpoint(const std::string& filename, int line) { auto place = filename + ":" + std::to_string(line); auto bkpt_iterator = breakpoints.find(place); if(bkpt_iterator == breakpoints.end()) return; removeBreakpoint(breakpoints.at(place)); // remove from map breakpoints.erase(place); } /* Remove breakpoint * @breakpointId: breakpoint id. */ void GdbManager::removeBreakpoint(int breakpointId) { send("-break-delete " + std::to_string(breakpointId)); } /* Enable breakpoint * @filename: path to source file (example "main.cpp") * @line: number breakpoint in line */ void GdbManager::enableBreakpoint(const std::string &filename, int line) { auto place = filename + ":" + std::to_string(line); auto bkpt_iterator = breakpoints.find(place); if(bkpt_iterator == breakpoints.end()) return; send("-break-enable " + std::to_string(breakpoints.at(place))); } /* Disable breakpoint * @filename: path to source file (example "main.cpp") * @line: number breakpoint in line */ void GdbManager::disableBreakpoint(const std::string &filename, int line) { auto place = filename + ":" + std::to_string(line); auto bkpt_iterator = breakpoints.find(place); if(bkpt_iterator == breakpoints.end()) return; send("-break-disable " + std::to_string(breakpoints.at(place))); } //#include <QString> //#include <QDebug> void GdbManager::listChildren(const std::string &name_) { auto splitFunction = [](std::string_view str, char delimiter) -> std::vector<std::string> { auto range = str | std::views::split(delimiter) | std::views::transform([](auto&& part) { return std::string(part.begin(), part.end()); }); return {range.begin(), range.end()}; }; auto splitName = splitFunction(name_, '.'); auto _type = splitName.front(); if(std::find(types.begin(), types.end(), _type) == types.end()) { std::cout << _type << " not found in types, creating variable" << std::endl; types.push_back(_type); send("-var-create " + _type + " * " + _type); } std::cout << "Listing children of " << name_ << std::endl; send("-var-list-children 1 " + name_); } /* Step control */ void GdbManager::stepOver() { send("-exec-next"); } // Move to next line (step over) void GdbManager::stepInto() { send("-exec-step"); } // Step into function void GdbManager::stepOut() { send("-exec-finish"); } // Step out of function void GdbManager::continueExecution() { send("-exec-continue"); } // Continue until next breakpoint /* Send SIGINT process group GDB (button pasue) */ void GdbManager::breakExecution() { if (gdb_pid > 0) { kill(-gdb_pid, SIGINT); } } /* Get current list local */ void GdbManager::updateLocals() { // Command asks GDB to print names and values of local variables send("-stack-list-locals 1"); }