#define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include #include #define COLOR_RESET "\033[0m" #define COLOR_RED "\033[31m" #define COLOR_GREEN "\033[32m" #define COLOR_YELLOW "\033[33m" #define COLOR_BLUE "\033[34m" #define COLOR_MAGENTA "\033[35m" #define COLOR_CYAN "\033[36m" bool is_utf8(const std::string& data) { for (size_t i = 0; i < data.size();) { uint8_t c = static_cast(data[i]); if (c <= 0x7F) { // ASCII i++; } else if ((c & 0xE0) == 0xC0) { // 2字节 if (i + 1 >= data.size() || (data[i + 1] & 0xC0) != 0x80) return false; i += 2; } else if ((c & 0xF0) == 0xE0) { // 3字节 if (i + 2 >= data.size() || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80) return false; i += 3; } else if ((c & 0xF8) == 0xF0) { // 4字节 if (i + 3 >= data.size() || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80) return false; i += 4; } else { return false; // 非法UTF-8序列 } } return true; } std::string to_u8(const std::string& str) { if (str.empty()) { return ""; } // 1. ANSI (当前代码页) → UTF-16 (WideChar) int wide_len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, nullptr, 0); if (wide_len == 0) { return ""; } std::wstring wide_str(wide_len, L'\0'); MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, &wide_str[0], wide_len); // 2. UTF-16 → UTF-8 int utf8_len = WideCharToMultiByte(CP_UTF8, 0, wide_str.c_str(), -1, nullptr, 0, nullptr, nullptr); if (utf8_len == 0) { return ""; } std::string utf8_str(utf8_len, '\0'); WideCharToMultiByte(CP_UTF8, 0, wide_str.c_str(), -1, &utf8_str[0], utf8_len, nullptr, nullptr); // 移除末尾的\0 if (!utf8_str.empty() && utf8_str.back() == '\0') { utf8_str.pop_back(); } return utf8_str; } void enable_ansi_color() { #if defined(_WIN32) HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); DWORD mode = 0; GetConsoleMode(hConsole, &mode); mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; SetConsoleMode(hConsole, mode); #endif } void PrintSource(const std::string& str) { std::string location = str; size_t colon_pos = location.find_last_of(':'); if (colon_pos == std::string::npos) { return; // 格式不符 } std::string key("at "); auto sp = location.find(key); std::string file_path = location.substr(sp + key.size(), colon_pos - sp - key.size()); int line_number = std::stoi(location.substr(colon_pos + 1)); // 读取源文件并提取对应行 int upper = line_number + 5; int lower = line_number - 5; lower = lower < 1 ? 1 : lower; boost::nowide::ifstream source_file(file_path); if (!source_file) { boost::nowide::cerr << "无法打开源文件: " << file_path << std::endl; return; } std::string line_content; int current_line = 0; while (std::getline(source_file, line_content)) { current_line++; if (current_line >= lower && current_line <= upper) { if (current_line == line_number) { boost::nowide::cout << COLOR_YELLOW << current_line << ":" << line_content << COLOR_RESET << std::endl; } else { boost::nowide::cout << current_line << ":" << line_content << std::endl; } } if (current_line > upper) { break; } } } std::pair exe_cmd(const std::string& exe, const std::vector& args) { boost::asio::io_context ctx; auto bin_path = boost::process::environment::find_executable(exe); if (bin_path.empty()) { return {-1, "Error: Command not found: " + exe}; } boost::asio::readable_pipe rp_out(ctx), rp_err(ctx); std::string out, err; try { // 启动进程 boost::process::process proc = boost::process::process(ctx, bin_path, args, boost::process::process_stdio{{}, rp_out, rp_err}); // 同步读取(可能阻塞) boost::system::error_code ec_out, ec_err; boost::asio::read(rp_out, boost::asio::dynamic_buffer(out), ec_out); boost::asio::read(rp_err, boost::asio::dynamic_buffer(err), ec_err); proc.wait(); return {proc.exit_code(), out + (err.empty() ? "" : "\n" + err)}; } catch (const std::exception& e) { return {-1, std::string("Exception: ") + e.what()}; } catch (...) { return {-1, "Unknown exception"}; } } class CrashAnalyzer { public: CrashAnalyzer(const std::string& logPath, const std::string& exePath) : m_logPath(logPath), m_exePath(exePath) { } bool Analyze() { if (!CheckTools()) return false; if (!ParseLog()) return false; return ResolveSymbols(); } private: struct StackFrame { uint64_t address; uint64_t rva; std::string module; }; bool CheckTools() { auto check = [](const std::string& cmd) { auto r = exe_cmd(cmd, {"--version"}); return r.first == 0; }; if (!check("addr2line")) { boost::nowide::cerr << "错误:未找到 addr2line 工具\n"; return false; } if (!check("objdump")) { boost::nowide::cerr << "错误:未找到 objdump 工具\n"; return false; } return true; } bool ParseLog() { boost::nowide::ifstream file(m_logPath); if (!file) { boost::nowide::cerr << "无法打开日志文件: " << m_logPath << "\n"; return false; } std::regex addrRegex(R"(0x([0-9a-fA-F]+)\s+\(RVA:\s+(0x[0-9a-fA-F]+)\)\s+(.+))"); std::string line; while (getline(file, line)) { if (line.find("ExceptionAddr:") != std::string::npos) { sscanf(line.c_str(), "ExceptionAddr: %llx", &m_exceptionAddr); } std::smatch match; if (regex_match(line, match, addrRegex)) { StackFrame frame; frame.address = std::stoull(match[1].str(), nullptr, 16); frame.rva = std::stoull(match[2].str(), nullptr, 16); frame.module = match[3].str(); if (!is_utf8(frame.module)) { frame.module = to_u8(frame.module); } m_stackFrames.push_back(frame); } } if (m_stackFrames.empty()) { boost::nowide::cerr << "日志中未找到有效的调用栈信息\n"; return false; } // else { // int i = 0; // for (const auto& f : m_stackFrames) { // ++i; // boost::nowide::cout << i << "PARSEADDR:" << f.address << "\n"; // } // } // 计算运行时基址(取第一个栈帧作为基准) m_runtimeBase = m_stackFrames[0].address - m_stackFrames[0].rva; return true; } bool ResolveSymbols() { // 获取编译时基址 auto result = exe_cmd("objdump", {"-p", m_exePath}); if (result.first != 0) { boost::nowide::cerr << "objdump 执行失败: " << result.second << "\n"; return false; } uint64_t imageBase = 0; std::istringstream iss(result.second); std::string line; while (getline(iss, line)) { if (line.find("ImageBase") != std::string::npos) { sscanf(line.c_str(), " ImageBase %llx", &imageBase); break; } } if (imageBase == 0) { boost::nowide::cerr << "无法获取可执行文件的ImageBase\n"; return false; } // 解析异常地址 uint64_t exceptionRva = m_exceptionAddr - m_runtimeBase; uint64_t compileAddr = imageBase + exceptionRva; PrintSourceInfo("\n============================= 异常位置 ===============================\n", m_exePath, compileAddr); // 解析调用栈 int l = 0; for (const auto& frame : m_stackFrames) { ++l; uint64_t compileAddr = imageBase + frame.rva; PrintSourceInfo("调用栈" + std::to_string(l) + " ", frame.module, compileAddr); } return true; } void PrintSourceInfo(const std::string& type, const std::string& module, uint64_t addr) { // 将 addr 转为全小写 16 进制字符串 std::ostringstream oss; oss << std::hex << std::nouppercase << addr; // 无前缀、全小写 std::string addr_hex = oss.str(); // 调用 addr2line // boost::nowide::cout << "READY:" << module << "|" << addr_hex << std::endl; auto result = exe_cmd("addr2line", {"-e", module, "-f", "-C", "-p", addr_hex}); if (result.first == 0) { boost::nowide::cout << COLOR_CYAN << type << COLOR_RESET << "[" << addr_hex << "]: " << result.second; boost::nowide::cout << COLOR_YELLOW << "--> " << module << COLOR_RESET << std::endl; } if (type.find("=") != std::string::npos) { PrintSource(result.second); boost::nowide::cout << COLOR_CYAN << "======================================================================\n" << COLOR_RESET << std::endl; } } std::string m_logPath; std::string m_exePath; uint64_t m_exceptionAddr = 0; uint64_t m_runtimeBase = 0; std::vector m_stackFrames; }; int main(int argc, char* argv[]) { boost::nowide::args args(argc, argv); boost::nowide::nowide_filesystem(); enable_ansi_color(); if (argc != 3) { boost::nowide::cerr << "用法: " << argv[0] << " <崩溃日志文件> <可执行文件路径>\n"; return 1; } CrashAnalyzer analyzer(argv[1], argv[2]); if (!analyzer.Analyze()) { return 1; } return 0; }