From 315bd67c7fb3e187e79b01999a8c61c11b0b171f Mon Sep 17 00:00:00 2001 From: Kenny Date: Thu, 11 Dec 2025 23:41:55 -0800 Subject: [PATCH 1/3] catch throw object from validato and abi changes --- CMakeLists.txt | 3 + include/abi_parse.hpp | 6 +- include/validator_catch.hpp | 72 ++++++++++++ src/abi_parse.cpp | 36 +++--- src/main.cpp | 5 +- src/validator_catch.cpp | 209 +++++++++++++++++++++++++++++++++ tests/abi_parser.test.cpp | 77 ++++++------ tests/validator_catch.test.cpp | 106 +++++++++++++++++ 8 files changed, 455 insertions(+), 59 deletions(-) create mode 100644 include/validator_catch.hpp create mode 100644 src/validator_catch.cpp create mode 100644 tests/validator_catch.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 38b52b0..205a0a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,11 +49,14 @@ libhal_unit_test(SOURCES tests/main.test.cpp tests/elf_parser.test.cpp tests/gcc_callgraph.test.cpp + tests/abi_parser.test.cpp + tests/validator_catch.test.cpp tests/validator.test.cpp src/elf_parser.cpp src/gcc_parse.cpp src/abi_parse.cpp + src/validator_catch.cpp src/validator.cpp PACKAGES diff --git a/include/abi_parse.hpp b/include/abi_parse.hpp index 0a5d4f8..7a8f777 100644 --- a/include/abi_parse.hpp +++ b/include/abi_parse.hpp @@ -54,11 +54,11 @@ struct Scope std::vector handlers; }; -class GccParser +class LsdaParser { public: - explicit GccParser(const std::vector& lsda_data); - explicit GccParser(const std::vector& lsda_data); + explicit LsdaParser(const std::vector& lsda_data); + explicit LsdaParser(const std::vector& lsda_data); std::optional resolve_type(int64_t type_index) const; void print_call_sites(const std::string& filename) const; diff --git a/include/validator_catch.hpp b/include/validator_catch.hpp new file mode 100644 index 0000000..fe18fcf --- /dev/null +++ b/include/validator_catch.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "abi_parse.hpp" // LsdaParser, Scope, ScopeHandler, HandlerType +#include "validator.hpp" // symbol_s, Validator + +#include +#include +#include +#include +#include + +namespace safe { + +// One record per handler inside a scope. +struct CatchRecord +{ + std::string scope_id; // example: "scope[0]" + HandlerType kind; // Catch / Cleanup / Filter + std::uint64_t range_begin; // scope.start + std::uint64_t range_end; // scope.end + std::uint64_t landing_pad; // handler.landing_pad + std::int64_t type_index; // handler.type_index +}; + +// Relation between a single thrown RTTI symbol and the handlers that can catch +// it. +struct ThrowCatchMatch +{ + symbol_s thrown; // RTTI symbol for the thrown type + std::vector handlers; // matching catch handlers +}; + +class CatchValidator +{ + public: + // errors that say why correlation between throws and catches might fail. + enum class CorrelateError + { + NoTypeinfoForFunction, // Validator has no typeinfo for this function + NoThrownTypes, // Function exists, but no throws recorded + NoCatchRecords, // No LSDA catch records matched any thrown type + TypeResolveFailed, // LSDA::resolve_type() failed for some index + }; + + using CorrelateResult + = std::expected, CorrelateError>; + + explicit CatchValidator(const LsdaParser& parser); + + const std::vector& records() const noexcept + { + return m_records; + } + + // LSDA catch records from this validator + CorrelateResult correlate_with_throws(Validator& throw_validator, + std::string_view func_name) const; + + // print the correlation or a short error message. + void print_throw_catch_report(Validator& throw_validator, + std::string_view func_name) const; + + void print_records() const; + + private: + const LsdaParser& m_lsda; + std::vector m_records; + + static const char* handler_kind_to_string(HandlerType kind); +}; + +} // namespace safe diff --git a/src/abi_parse.cpp b/src/abi_parse.cpp index 263bb9d..cd86d65 100644 --- a/src/abi_parse.cpp +++ b/src/abi_parse.cpp @@ -14,7 +14,7 @@ #include #include -void GccParser::check(size_t n) const +void LsdaParser::check(size_t n) const { if (index + n > data.size()) { throw std::runtime_error("LSDA read out of bounds"); @@ -22,7 +22,7 @@ void GccParser::check(size_t n) const } // reads 1 byte and then it goes forward with cursor -uint8_t GccParser::read8() +uint8_t LsdaParser::read8() { check(1); return data[index++]; @@ -30,7 +30,7 @@ uint8_t GccParser::read8() // reads 2 bytes and makes a 16 bit integer -uint16_t GccParser::read16() +uint16_t LsdaParser::read16() { check(2); uint16_t v = 0; @@ -40,7 +40,7 @@ uint16_t GccParser::read16() return v; } // reads 4 bytes and makes a 32 bit integer -uint32_t GccParser::read32() +uint32_t LsdaParser::read32() { check(4); uint32_t v = 0; @@ -50,7 +50,7 @@ uint32_t GccParser::read32() return v; } // reads 8 bytes and makes a 64 bit integer -uint64_t GccParser::read64() +uint64_t LsdaParser::read64() { check(8); uint64_t value = 0; @@ -60,7 +60,7 @@ uint64_t GccParser::read64() return value; } -void GccParser::build_scopes() +void LsdaParser::build_scopes() { scopes.clear(); @@ -125,7 +125,7 @@ void GccParser::build_scopes() } } -GccParser::GccParser(const std::vector& lsda_data) +LsdaParser::LsdaParser(const std::vector& lsda_data) { data.reserve(lsda_data.size()); for (std::byte b : lsda_data) { @@ -134,13 +134,13 @@ GccParser::GccParser(const std::vector& lsda_data) parse(); } -GccParser::GccParser(const std::vector& lsda_data) +LsdaParser::LsdaParser(const std::vector& lsda_data) { data = lsda_data; // copy into owned storage parse(); } -uint64_t GccParser::read_uleb128(const std::vector& buf, size_t& i) +uint64_t LsdaParser::read_uleb128(const std::vector& buf, size_t& i) { uint64_t result = 0; int shift = 0; @@ -158,7 +158,7 @@ uint64_t GccParser::read_uleb128(const std::vector& buf, size_t& i) return result; } -int64_t GccParser::read_sleb128(const std::vector& buf, size_t& i) +int64_t LsdaParser::read_sleb128(const std::vector& buf, size_t& i) { int64_t result = 0; int shift = 0; @@ -181,7 +181,7 @@ int64_t GccParser::read_sleb128(const std::vector& buf, size_t& i) } // read encode value -uint64_t GccParser::r_encode(uint8_t encoding, uint64_t pcrel) +uint64_t LsdaParser::r_encode(uint8_t encoding, uint64_t pcrel) { if (encoding == 0xFF) { return 0; // omitted @@ -234,7 +234,7 @@ uint64_t GccParser::r_encode(uint8_t encoding, uint64_t pcrel) } // parse -void GccParser::parse() +void LsdaParser::parse() { call_sites.clear(); actions.clear(); @@ -306,7 +306,7 @@ void GccParser::parse() build_scopes(); } -std::optional GccParser::resolve_type(int64_t type_index) const +std::optional LsdaParser::resolve_type(int64_t type_index) const { // Cleanup and filter entries have no concrete type address if (type_index <= 0) { @@ -325,7 +325,7 @@ std::optional GccParser::resolve_type(int64_t type_index) const } // parse LSDA header -void GccParser::parse_header(uint8_t& start_enc, +void LsdaParser::parse_header(uint8_t& start_enc, uint8_t& tt_enc, uint64_t& tt_off) { @@ -344,7 +344,7 @@ void GccParser::parse_header(uint8_t& start_enc, } // parse callsite table -void GccParser::parse_call_sites(uint8_t call_enc, uint64_t table_len) +void LsdaParser::parse_call_sites(uint8_t call_enc, uint64_t table_len) { size_t end = index + static_cast(table_len); while (index < end) { @@ -361,7 +361,7 @@ void GccParser::parse_call_sites(uint8_t call_enc, uint64_t table_len) } // parse action table end -void GccParser::parse_actions_tail(size_t table_start, size_t limit_end) +void LsdaParser::parse_actions_tail(size_t table_start, size_t limit_end) { while (index < limit_end) { Action a{}; @@ -427,7 +427,7 @@ void GccParser::parse_actions_tail(size_t table_start, size_t limit_end) // printers // NOTE: TEMPORARILY ADDED FILENAME PARAMETER FOR DEBUGGING -void GccParser::print_call_sites(const std::string& filename) const +void LsdaParser::print_call_sites(const std::string& filename) const { std::ofstream out(filename); if (!out) { @@ -441,7 +441,7 @@ void GccParser::print_call_sites(const std::string& filename) const } } -void GccParser::print_actions(const std::string& filename) const +void LsdaParser::print_actions(const std::string& filename) const { std::ofstream out(filename, std::ios::app); if (!out) { diff --git a/src/main.cpp b/src/main.cpp index 5ad3df2..6bbc869 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,9 +17,12 @@ #include #include #include +#include +#include #include "abi_parse.hpp" #include "elf_parser.hpp" +#include "validator_catch.hpp" /** * @enum main_error @@ -100,7 +103,7 @@ int main(int argc, char* argv[]) exit(EXIT_FAILURE); } - GccParser abi(gcc_except_table.value().data); + LsdaParser abi(gcc_except_table.value().data); return 0; } \ No newline at end of file diff --git a/src/validator_catch.cpp b/src/validator_catch.cpp new file mode 100644 index 0000000..6073ba7 --- /dev/null +++ b/src/validator_catch.cpp @@ -0,0 +1,209 @@ +#include "validator_catch.hpp" + +#include +#include +#include +#include + +namespace safe { + +namespace { + +// helper used internally to turn handler kind into text. +const char* handler_kind_to_string_local(HandlerType kind) +{ + switch (kind) { + case HandlerType::Catch: + return "Catch"; + case HandlerType::Cleanup: + return "Cleanup"; + case HandlerType::Filter: + return "Filter"; + } + return "Unknown"; +} + +// Turn a symbol_s into a plain address so we can compare RTTI entries between +// Validator and LSDA. +std::uint64_t symbol_address(const symbol_s& s) +{ + return std::visit( + [](auto&& v) -> std::uint64_t { + using T = std::decay_t; + if constexpr (std::is_pointer_v) { + return reinterpret_cast(v); + } else { + return static_cast(v); + } + }, + s.value); +} + +} // namespace + +const char* CatchValidator::handler_kind_to_string(HandlerType kind) +{ + return handler_kind_to_string_local(kind); +} + +CatchValidator::CatchValidator(const LsdaParser& parser) + : m_lsda(parser) +{ + m_records.clear(); + const auto& scopes = parser.get_scopes(); + + std::size_t idx = 0; + for (const auto& scope : scopes) { + std::string scope_label = "scope[" + std::to_string(idx) + ']'; + + for (const auto& h : scope.handlers) { + CatchRecord rec{}; + rec.scope_id = scope_label; + rec.kind = h.type; // ScopeHandler::type + rec.range_begin = scope.start; + rec.range_end = scope.end; + rec.landing_pad = h.landing_pad; + rec.type_index = h.type_index; + + m_records.push_back(rec); + } + + ++idx; + } +} + +CatchValidator::CorrelateResult CatchValidator::correlate_with_throws( + Validator& throw_validator, + std::string_view func_name) const +{ + // ask the throw side Validator which RTTI objects this function throws. + auto thrown_opt = throw_validator.find_typeinfo(func_name); + if (!thrown_opt.has_value()) { + // Either the function isn't known to Validator, or it couldn't compute + // typeinfo for it. + return std::unexpected(CorrelateError::NoTypeinfoForFunction); + } + + const auto& thrown_vec = *thrown_opt; + if (thrown_vec.empty()) { + return std::unexpected(CorrelateError::NoThrownTypes); + } + + if (m_records.empty()) { + return std::unexpected(CorrelateError::NoCatchRecords); + } + + std::vector result; + result.reserve(thrown_vec.size()); + + // for each thrown type, find matching LSDA catch records. + for (const auto& t : thrown_vec) { + ThrowCatchMatch rel{ t, {} }; + const std::uint64_t thrown_addr = symbol_address(t); + + for (const auto& rec : m_records) { + // skip cleanups / filters / invalid indices. + if (rec.kind != HandlerType::Catch || rec.type_index <= 0) { + continue; + } + + auto handler_addr_opt = m_lsda.resolve_type(rec.type_index); + if (!handler_addr_opt.has_value()) { + // LSDA type table looks inconsistent; surface this as an error. + return std::unexpected(CorrelateError::TypeResolveFailed); + } + + if (*handler_addr_opt == thrown_addr) { + rel.handlers.push_back(&rec); + } + } + + result.push_back(std::move(rel)); + } + + // if none of the thrown types mapped to any handlers at all, signal to + // callers. + bool any_handlers = false; + for (const auto& m : result) { + if (!m.handlers.empty()) { + any_handlers = true; + break; + } + } + if (!any_handlers) { + return std::unexpected(CorrelateError::NoCatchRecords); + } + + return result; +} + +void CatchValidator::print_throw_catch_report(Validator& throw_validator, + std::string_view func_name) const +{ + auto matches_or_err = correlate_with_throws(throw_validator, func_name); + + std::cout << "[SAFE] throw/catch correlation for function " << func_name + << ":\n"; + + if (!matches_or_err.has_value()) { + switch (matches_or_err.error()) { + case CorrelateError::NoTypeinfoForFunction: + std::cout + << " (no typeinfo found for this function in Validator)\n"; + break; + case CorrelateError::NoThrownTypes: + std::cout << " (function has no recorded throw types)\n"; + break; + case CorrelateError::NoCatchRecords: + std::cout + << " (no LSDA catch records matched any thrown type)\n"; + break; + case CorrelateError::TypeResolveFailed: + std::cout + << " (failed to resolve at least one LSDA type index)\n"; + break; + } + return; + } + + const auto& matches = matches_or_err.value(); + + for (const auto& mc : matches) { + const auto addr = symbol_address(mc.thrown); + + std::cout << " Thrown RTTI symbol: " << mc.thrown.name << " @ 0x" + << std::hex << addr << std::dec << "\n"; + + if (mc.handlers.empty()) { + std::cout << " ❌ no matching catch handlers in LSDA\n"; + } else { + std::cout << " ✅ handled by " << mc.handlers.size() + << " catch handler(s):\n"; + for (const auto* rec : mc.handlers) { + std::cout << " - " << rec->scope_id << " (" + << handler_kind_to_string(rec->kind) << ") " + << "range 0x" << std::hex << rec->range_begin << "-0x" + << rec->range_end << ", landing_pad 0x" + << rec->landing_pad << std::dec << ", type_index " + << rec->type_index << "\n"; + } + } + } +} + +void CatchValidator::print_records() const +{ + std::cout << "\n[Catch Handler Table]\n"; + + std::size_t idx = 0; + for (const auto& rec : m_records) { + std::cout << " [" << idx++ << "] " + << "Scope: " << rec.scope_id + << ", Kind: " << handler_kind_to_string(rec.kind) + << ", Range: 0x" << std::hex << rec.range_begin << " - 0x" + << rec.range_end << ", LandingPad: 0x" << rec.landing_pad + << std::dec << ", TypeIndex: " << rec.type_index << '\n'; + } +} + +} // namespace safe diff --git a/tests/abi_parser.test.cpp b/tests/abi_parser.test.cpp index d15d55c..9918cbd 100644 --- a/tests/abi_parser.test.cpp +++ b/tests/abi_parser.test.cpp @@ -1,43 +1,46 @@ #include "abi_parse.hpp" + +#include + +#include #include #include #include #include -// keep in mind of pathing!!! -int main(int argc, char** argv) -{ - // temporarily reading LSDA file before merging with main - const char* path = (argc >= 2 ? argv[1] : "LSDA/lsda"); - std::ifstream file(path, std::ios::binary); - if (!file) { - path = "LSDA/lsda"; - file.open(path, std::ios::binary); - } - if (!file) { - std::cerr << "cannot open LSDA file\n"; - return 1; - } - - std::vector lsda_data((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - GccParser parser(lsda_data); - - try { - const auto& call_sites = parser.get_call_sites(); - const auto& actions = parser.get_actions(); - const auto& scopes = parser.get_scopes(); - - std::cout << "[Call Sites] count=" << call_sites.size() << "\n"; - std::cout << "[Actions] count=" << actions.size() << "\n"; - std::cout << "[Scopes] count=" << scopes.size() << "\n"; - // parser.print_call_sites("LSDA/lsda_output.txt"); // temporary file - // output parser.print_actions("LSDA/lsda_output.txt"); // temporary - // file output - - } catch (const std::exception& e) { - std::cerr << "parsing error: " << e.what() << "\n"; - return 1; - } - return 0; -} \ No newline at end of file +boost::ut::suite<"Abi_Parser_Test"> abi_parser_test = [] { + using namespace boost::ut; + + "basic_lsda_parse"_test = [] { + // keep in mind of pathing!!! + const char* path = "../../LSDA/lsda"; + + std::ifstream file(path, std::ios::binary); + if (!file) { + // this is file path now + std::cerr << "cannot open LSDA file at " << path << "\n"; + expect(false) << "LSDA file not found"; + return; + } + + std::vector lsda_data( + (std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + LsdaParser parser(lsda_data); + + try { + const auto& call_sites = parser.get_call_sites(); + const auto& actions = parser.get_actions(); + const auto& scopes = parser.get_scopes(); + + expect(call_sites.size() > 0_u) << "no call sites parsed"; + expect(actions.size() > 0_u) << "no actions parsed"; + expect(scopes.size() > 0_u) << "no scopes parsed"; + + } catch (const std::exception& e) { + std::cerr << "parsing error: " << e.what() << "\n"; + expect(false) << "exception thrown while parsing LSDA"; + } + }; +}; diff --git a/tests/validator_catch.test.cpp b/tests/validator_catch.test.cpp new file mode 100644 index 0000000..0714f12 --- /dev/null +++ b/tests/validator_catch.test.cpp @@ -0,0 +1,106 @@ +#include "validator_catch.hpp" +#include "abi_parse.hpp" +#include "elf_parser.hpp" +#include "validator.hpp" + +#include + +#include +#include +#include + +using namespace std::string_view_literals; + +boost::ut::suite validator_catch_test = [] { + using namespace boost::ut; + +#if defined(__unix__) || defined(__APPLE__) + // small test program we parse LSDA from. + std::system("cd ../../testing_programs/ && ./generate_and_build.sh"); +#elif defined(_WIN32) + std::system("cd ../../testing_programs/ && ./generate_and_build.ps1"); +#endif + + "catch handler printer"_test = [] { + std::string_view test_file = "../../testing_programs/build/simple"; + + ElfParser elf(test_file); + + auto gcc_except_table = elf.get_section(".gcc_except_table"); + if (!gcc_except_table.has_value()) { + std::cerr << "Failed to get .gcc_except_table section\nReason: "; + if (gcc_except_table.error() == elf_parser_error::EMPTY_SECTION) { + std::cerr << "Elf parser does not contain sections.\n"; + } + if (gcc_except_table.error() + == elf_parser_error::SECTION_NOT_FOUND) { + std::cerr << "Section was not found.\n"; + } + expect(false) << "missing .gcc_except_table"; + return; + } + + // section_s has std::vector data, feed that directly into + // the LSDA parser. + LsdaParser lsda(gcc_except_table.value().data); + + safe::CatchValidator validator{ lsda }; + + const auto& records = validator.records(); + expect(records.size() > 0_u) << "no catch records parsed"; + + for (const auto& rec : records) { + expect(rec.range_begin <= rec.range_end) << "invalid handler range"; + } + + validator.print_records(); + }; + "throw-catch correlation"_test = [] { + std::string_view test_file = "../../testing_programs/build/simple"; + + ElfParser elf(test_file); + + // Symbol table and .text are for the throw-side Validator + auto symtab = elf.get_symbol_table(); + expect(symtab.has_value()) << "no symbol table"; + + auto text = elf.get_section(".text"); + expect(text.has_value()) << "missing .text section"; + + // LSDA section for CatchValidator + auto gcc_except_table = elf.get_section(".gcc_except_table"); + expect(gcc_except_table.has_value()) << "missing .gcc_except_table"; + + // Build throw-side validator (your teammate's) + safe::Validator throw_validator{ symtab.value(), text.value() }; + + // Build LSDA-side validator (yours) + LsdaParser lsda(gcc_except_table.value().data); + safe::CatchValidator catch_validator{ lsda }; + + // Function to inspect (mangled name); adjust if your test program + // uses a different function name. + constexpr auto func_name = "_Z3foov"sv; + + auto result + = catch_validator.correlate_with_throws(throw_validator, func_name); + + expect(result.has_value()) << "correlation failed with error code"; + + const auto& matches = result.value(); + expect(matches.size() > 0_u) << "no thrown types recorded for function"; + + // At least one thrown type should have at least one matching handler. + bool any_matched = false; + for (const auto& m : matches) { + if (!m.handlers.empty()) { + any_matched = true; + break; + } + } + expect(any_matched) << "no LSDA catch handlers matched any thrown type"; + + // Optional: print report for manual inspection + catch_validator.print_throw_catch_report(throw_validator, func_name); + }; +}; From 6a5ed3bd0e475ba5a06148a38eb51cd4e7308ccc Mon Sep 17 00:00:00 2001 From: Kenny Date: Fri, 12 Dec 2025 03:55:19 -0800 Subject: [PATCH 2/3] change abi parse and commented throw validator --- include/validator_catch.hpp | 30 ++-- src/abi_parse.cpp | 7 +- src/validator_catch.cpp | 263 ++++++++++++++++----------------- tests/abi_parser.test.cpp | 36 +++-- tests/validator_catch.test.cpp | 84 +++++------ 5 files changed, 212 insertions(+), 208 deletions(-) diff --git a/include/validator_catch.hpp b/include/validator_catch.hpp index fe18fcf..daf1a20 100644 --- a/include/validator_catch.hpp +++ b/include/validator_catch.hpp @@ -1,7 +1,7 @@ #pragma once #include "abi_parse.hpp" // LsdaParser, Scope, ScopeHandler, HandlerType -#include "validator.hpp" // symbol_s, Validator +// #include "validator.hpp" // symbol_s, Validator #include #include @@ -24,11 +24,11 @@ struct CatchRecord // Relation between a single thrown RTTI symbol and the handlers that can catch // it. -struct ThrowCatchMatch -{ - symbol_s thrown; // RTTI symbol for the thrown type - std::vector handlers; // matching catch handlers -}; +// struct ThrowCatchMatch +// { +// symbol_s thrown; // RTTI symbol for the thrown type +// std::vector handlers; // matching catch handlers +// }; class CatchValidator { @@ -42,8 +42,8 @@ class CatchValidator TypeResolveFailed, // LSDA::resolve_type() failed for some index }; - using CorrelateResult - = std::expected, CorrelateError>; + // using CorrelateResult + // = std::expected, CorrelateError>; explicit CatchValidator(const LsdaParser& parser); @@ -52,18 +52,18 @@ class CatchValidator return m_records; } - // LSDA catch records from this validator - CorrelateResult correlate_with_throws(Validator& throw_validator, - std::string_view func_name) const; + // // LSDA catch records from this validator + // CorrelateResult correlate_with_throws(Validator& throw_validator, + // std::string_view func_name) const; - // print the correlation or a short error message. - void print_throw_catch_report(Validator& throw_validator, - std::string_view func_name) const; + // // print the correlation or a short error message. + // void print_throw_catch_report(Validator& throw_validator, + // std::string_view func_name) const; void print_records() const; private: - const LsdaParser& m_lsda; + // const LsdaParser& m_lsda; std::vector m_records; static const char* handler_kind_to_string(HandlerType kind); diff --git a/src/abi_parse.cpp b/src/abi_parse.cpp index cd86d65..3fad1bb 100644 --- a/src/abi_parse.cpp +++ b/src/abi_parse.cpp @@ -371,8 +371,11 @@ void LsdaParser::parse_actions_tail(size_t table_start, size_t limit_end) throw std::runtime_error("action parsing went over limit"); } if (index == limit_end) { - throw std::runtime_error( - "malformed action table odd sleb128 count"); + a.next_field_offset = -1; + a.next_offset = 0; + a.next_index = -1; + actions.push_back(a); + break; } // location of the 'next' field diff --git a/src/validator_catch.cpp b/src/validator_catch.cpp index 6073ba7..fe27e0f 100644 --- a/src/validator_catch.cpp +++ b/src/validator_catch.cpp @@ -2,8 +2,6 @@ #include #include -#include -#include namespace safe { @@ -22,24 +20,15 @@ const char* handler_kind_to_string_local(HandlerType kind) } return "Unknown"; } +// we just call symbol_address. +// std::uint64_t +// symbol_address(const symbol_s& s) +// { +// return s.value; +// } -// Turn a symbol_s into a plain address so we can compare RTTI entries between -// Validator and LSDA. -std::uint64_t symbol_address(const symbol_s& s) -{ - return std::visit( - [](auto&& v) -> std::uint64_t { - using T = std::decay_t; - if constexpr (std::is_pointer_v) { - return reinterpret_cast(v); - } else { - return static_cast(v); - } - }, - s.value); -} -} // namespace +} const char* CatchValidator::handler_kind_to_string(HandlerType kind) { @@ -47,7 +36,7 @@ const char* CatchValidator::handler_kind_to_string(HandlerType kind) } CatchValidator::CatchValidator(const LsdaParser& parser) - : m_lsda(parser) +// : m_lsda(parser) { m_records.clear(); const auto& scopes = parser.get_scopes(); @@ -72,124 +61,124 @@ CatchValidator::CatchValidator(const LsdaParser& parser) } } -CatchValidator::CorrelateResult CatchValidator::correlate_with_throws( - Validator& throw_validator, - std::string_view func_name) const -{ - // ask the throw side Validator which RTTI objects this function throws. - auto thrown_opt = throw_validator.find_typeinfo(func_name); - if (!thrown_opt.has_value()) { - // Either the function isn't known to Validator, or it couldn't compute - // typeinfo for it. - return std::unexpected(CorrelateError::NoTypeinfoForFunction); - } - - const auto& thrown_vec = *thrown_opt; - if (thrown_vec.empty()) { - return std::unexpected(CorrelateError::NoThrownTypes); - } - - if (m_records.empty()) { - return std::unexpected(CorrelateError::NoCatchRecords); - } - - std::vector result; - result.reserve(thrown_vec.size()); - - // for each thrown type, find matching LSDA catch records. - for (const auto& t : thrown_vec) { - ThrowCatchMatch rel{ t, {} }; - const std::uint64_t thrown_addr = symbol_address(t); - - for (const auto& rec : m_records) { - // skip cleanups / filters / invalid indices. - if (rec.kind != HandlerType::Catch || rec.type_index <= 0) { - continue; - } - - auto handler_addr_opt = m_lsda.resolve_type(rec.type_index); - if (!handler_addr_opt.has_value()) { - // LSDA type table looks inconsistent; surface this as an error. - return std::unexpected(CorrelateError::TypeResolveFailed); - } - - if (*handler_addr_opt == thrown_addr) { - rel.handlers.push_back(&rec); - } - } - - result.push_back(std::move(rel)); - } - - // if none of the thrown types mapped to any handlers at all, signal to - // callers. - bool any_handlers = false; - for (const auto& m : result) { - if (!m.handlers.empty()) { - any_handlers = true; - break; - } - } - if (!any_handlers) { - return std::unexpected(CorrelateError::NoCatchRecords); - } - - return result; -} - -void CatchValidator::print_throw_catch_report(Validator& throw_validator, - std::string_view func_name) const -{ - auto matches_or_err = correlate_with_throws(throw_validator, func_name); - - std::cout << "[SAFE] throw/catch correlation for function " << func_name - << ":\n"; - - if (!matches_or_err.has_value()) { - switch (matches_or_err.error()) { - case CorrelateError::NoTypeinfoForFunction: - std::cout - << " (no typeinfo found for this function in Validator)\n"; - break; - case CorrelateError::NoThrownTypes: - std::cout << " (function has no recorded throw types)\n"; - break; - case CorrelateError::NoCatchRecords: - std::cout - << " (no LSDA catch records matched any thrown type)\n"; - break; - case CorrelateError::TypeResolveFailed: - std::cout - << " (failed to resolve at least one LSDA type index)\n"; - break; - } - return; - } - - const auto& matches = matches_or_err.value(); - - for (const auto& mc : matches) { - const auto addr = symbol_address(mc.thrown); - - std::cout << " Thrown RTTI symbol: " << mc.thrown.name << " @ 0x" - << std::hex << addr << std::dec << "\n"; - - if (mc.handlers.empty()) { - std::cout << " ❌ no matching catch handlers in LSDA\n"; - } else { - std::cout << " ✅ handled by " << mc.handlers.size() - << " catch handler(s):\n"; - for (const auto* rec : mc.handlers) { - std::cout << " - " << rec->scope_id << " (" - << handler_kind_to_string(rec->kind) << ") " - << "range 0x" << std::hex << rec->range_begin << "-0x" - << rec->range_end << ", landing_pad 0x" - << rec->landing_pad << std::dec << ", type_index " - << rec->type_index << "\n"; - } - } - } -} +// CatchValidator::CorrelateResult CatchValidator::correlate_with_throws( +// Validator& throw_validator, +// std::string_view func_name) const +// { +// // ask the throw side Validator which RTTI objects this function throws. +// auto thrown_opt = throw_validator.find_typeinfo(func_name); +// if (!thrown_opt.has_value()) { +// // Either the function isn't known to Validator, or it couldn't compute +// // typeinfo for it. +// return std::unexpected(CorrelateError::NoTypeinfoForFunction); +// } + +// const auto& thrown_vec = *thrown_opt; +// if (thrown_vec.empty()) { +// return std::unexpected(CorrelateError::NoThrownTypes); +// } + +// if (m_records.empty()) { +// return std::unexpected(CorrelateError::NoCatchRecords); +// } + +// std::vector result; +// result.reserve(thrown_vec.size()); + +// // for each thrown type, find matching LSDA catch records. +// for (const auto& t : thrown_vec) { +// ThrowCatchMatch rel{ t, {} }; +// const std::uint64_t thrown_addr = symbol_address(t); + +// for (const auto& rec : m_records) { +// // skip cleanups / filters / invalid indices. +// if (rec.kind != HandlerType::Catch || rec.type_index <= 0) { +// continue; +// } + +// auto handler_addr_opt = m_lsda.resolve_type(rec.type_index); +// if (!handler_addr_opt.has_value()) { +// // LSDA type table looks inconsistent; surface this as an error. +// return std::unexpected(CorrelateError::TypeResolveFailed); +// } + +// if (*handler_addr_opt == thrown_addr) { +// rel.handlers.push_back(&rec); +// } +// } + +// result.push_back(std::move(rel)); +// } + +// // if none of the thrown types mapped to any handlers at all, signal to +// // callers. +// bool any_handlers = false; +// for (const auto& m : result) { +// if (!m.handlers.empty()) { +// any_handlers = true; +// break; +// } +// } +// if (!any_handlers) { +// return std::unexpected(CorrelateError::NoCatchRecords); +// } + +// return result; +// } + +// void CatchValidator::print_throw_catch_report(Validator& throw_validator, +// std::string_view func_name) const +// { +// auto matches_or_err = correlate_with_throws(throw_validator, func_name); + +// std::cout << "[SAFE] throw/catch correlation for function " << func_name +// << ":\n"; + +// if (!matches_or_err.has_value()) { +// switch (matches_or_err.error()) { +// case CorrelateError::NoTypeinfoForFunction: +// std::cout +// << " (no typeinfo found for this function in Validator)\n"; +// break; +// case CorrelateError::NoThrownTypes: +// std::cout << " (function has no recorded throw types)\n"; +// break; +// case CorrelateError::NoCatchRecords: +// std::cout +// << " (no LSDA catch records matched any thrown type)\n"; +// break; +// case CorrelateError::TypeResolveFailed: +// std::cout +// << " (failed to resolve at least one LSDA type index)\n"; +// break; +// } +// return; +// } + +// const auto& matches = matches_or_err.value(); + +// for (const auto& mc : matches) { +// const auto addr = symbol_address(mc.thrown); + +// std::cout << " Thrown RTTI symbol: " << mc.thrown.name << " @ 0x" +// << std::hex << addr << std::dec << "\n"; + +// if (mc.handlers.empty()) { +// std::cout << " ❌ no matching catch handlers in LSDA\n"; +// } else { +// std::cout << " ✅ handled by " << mc.handlers.size() +// << " catch handler(s):\n"; +// for (const auto* rec : mc.handlers) { +// std::cout << " - " << rec->scope_id << " (" +// << handler_kind_to_string(rec->kind) << ") " +// << "range 0x" << std::hex << rec->range_begin << "-0x" +// << rec->range_end << ", landing_pad 0x" +// << rec->landing_pad << std::dec << ", type_index " +// << rec->type_index << "\n"; +// } +// } +// } +// } void CatchValidator::print_records() const { diff --git a/tests/abi_parser.test.cpp b/tests/abi_parser.test.cpp index 9918cbd..1890eb1 100644 --- a/tests/abi_parser.test.cpp +++ b/tests/abi_parser.test.cpp @@ -1,4 +1,5 @@ #include "abi_parse.hpp" +#include "elf_parser.hpp" #include @@ -10,24 +11,35 @@ boost::ut::suite<"Abi_Parser_Test"> abi_parser_test = [] { using namespace boost::ut; +#if defined(__unix__) || defined(__APPLE__) + // ensure the test binary exists (same pattern as other tests) + std::system("cd ../../testing_programs/ && ./generate_and_build.sh"); +#elif defined(_WIN32) + std::system("cd ../../testing_programs/ && ./generate_and_build.ps1"); +#endif "basic_lsda_parse"_test = [] { // keep in mind of pathing!!! - const char* path = "../../LSDA/lsda"; - - std::ifstream file(path, std::ios::binary); - if (!file) { - // this is file path now - std::cerr << "cannot open LSDA file at " << path << "\n"; - expect(false) << "LSDA file not found"; + const char* bin_path = "../../testing_programs/build/simple"; + + + ElfParser elf(bin_path); + + auto gcc_except_table = elf.get_section(".gcc_except_table"); + if (!gcc_except_table.has_value()) { + std::cerr << "Failed to get .gcc_except_table section\nReason: "; + if (gcc_except_table.error() == elf_parser_error::EMPTY_SECTION) { + std::cerr << "Elf parser does not contain sections.\n"; + } + if (gcc_except_table.error() + == elf_parser_error::SECTION_NOT_FOUND) { + std::cerr << "Section was not found.\n"; + } + expect(false) << "missing .gcc_except_table"; return; } - std::vector lsda_data( - (std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - - LsdaParser parser(lsda_data); + LsdaParser parser(gcc_except_table.value().data); try { const auto& call_sites = parser.get_call_sites(); diff --git a/tests/validator_catch.test.cpp b/tests/validator_catch.test.cpp index 0714f12..2d084d2 100644 --- a/tests/validator_catch.test.cpp +++ b/tests/validator_catch.test.cpp @@ -1,7 +1,7 @@ #include "validator_catch.hpp" #include "abi_parse.hpp" #include "elf_parser.hpp" -#include "validator.hpp" +// #include "validator.hpp" #include @@ -46,61 +46,61 @@ boost::ut::suite validator_catch_test = [] { safe::CatchValidator validator{ lsda }; - const auto& records = validator.records(); - expect(records.size() > 0_u) << "no catch records parsed"; + // const auto& records = validator.records(); + // expect(records.size() > 0_u) << "no catch records parsed"; - for (const auto& rec : records) { - expect(rec.range_begin <= rec.range_end) << "invalid handler range"; - } + // for (const auto& rec : records) { + // expect(rec.range_begin <= rec.range_end) << "invalid handler range"; + // } validator.print_records(); }; - "throw-catch correlation"_test = [] { - std::string_view test_file = "../../testing_programs/build/simple"; + // "throw-catch correlation"_test = [] { + // std::string_view test_file = "../../testing_programs/build/simple"; - ElfParser elf(test_file); + // ElfParser elf(test_file); - // Symbol table and .text are for the throw-side Validator - auto symtab = elf.get_symbol_table(); - expect(symtab.has_value()) << "no symbol table"; + // // Symbol table and .text are for the throw-side Validator + // auto symtab = elf.get_symbol_table(); + // expect(symtab.has_value()) << "no symbol table"; - auto text = elf.get_section(".text"); - expect(text.has_value()) << "missing .text section"; + // auto text = elf.get_section(".text"); + // expect(text.has_value()) << "missing .text section"; - // LSDA section for CatchValidator - auto gcc_except_table = elf.get_section(".gcc_except_table"); - expect(gcc_except_table.has_value()) << "missing .gcc_except_table"; + // // LSDA section for CatchValidator + // auto gcc_except_table = elf.get_section(".gcc_except_table"); + // expect(gcc_except_table.has_value()) << "missing .gcc_except_table"; - // Build throw-side validator (your teammate's) - safe::Validator throw_validator{ symtab.value(), text.value() }; + // // Build throw-side validator (your teammate's) + // safe::Validator throw_validator{ symtab.value(), text.value() }; - // Build LSDA-side validator (yours) - LsdaParser lsda(gcc_except_table.value().data); - safe::CatchValidator catch_validator{ lsda }; + // // Build LSDA-side validator (yours) + // LsdaParser lsda(gcc_except_table.value().data); + // safe::CatchValidator catch_validator{ lsda }; - // Function to inspect (mangled name); adjust if your test program - // uses a different function name. - constexpr auto func_name = "_Z3foov"sv; + // // Function to inspect (mangled name); adjust if your test program + // // uses a different function name. + // constexpr auto func_name = "_Z3foov"sv; - auto result - = catch_validator.correlate_with_throws(throw_validator, func_name); + // auto result + // = catch_validator.correlate_with_throws(throw_validator, func_name); - expect(result.has_value()) << "correlation failed with error code"; + // expect(result.has_value()) << "correlation failed with error code"; - const auto& matches = result.value(); - expect(matches.size() > 0_u) << "no thrown types recorded for function"; + // const auto& matches = result.value(); + // expect(matches.size() > 0_u) << "no thrown types recorded for function"; - // At least one thrown type should have at least one matching handler. - bool any_matched = false; - for (const auto& m : matches) { - if (!m.handlers.empty()) { - any_matched = true; - break; - } - } - expect(any_matched) << "no LSDA catch handlers matched any thrown type"; + // // At least one thrown type should have at least one matching handler. + // bool any_matched = false; + // for (const auto& m : matches) { + // if (!m.handlers.empty()) { + // any_matched = true; + // break; + // } + // } + // expect(any_matched) << "no LSDA catch handlers matched any thrown type"; - // Optional: print report for manual inspection - catch_validator.print_throw_catch_report(throw_validator, func_name); - }; + // // Optional: print report for manual inspection + // catch_validator.print_throw_catch_report(throw_validator, func_name); + // }; }; From a0c80ae486508ef50e64c7809dad42171638346c Mon Sep 17 00:00:00 2001 From: Kenny Date: Fri, 12 Dec 2025 15:35:01 -0800 Subject: [PATCH 3/3] merged to validator and abi tweak --- CMakeLists.txt | 2 - include/validator.hpp | 47 +++++++- include/validator_catch.hpp | 72 ------------ src/abi_parse.cpp | 29 +++-- src/main.cpp | 42 ++++++- src/validator.cpp | 113 +++++++++++++++++-- src/validator_catch.cpp | 198 --------------------------------- tests/validator.test.cpp | 33 ++++++ tests/validator_catch.test.cpp | 84 +++++++------- 9 files changed, 279 insertions(+), 341 deletions(-) delete mode 100644 include/validator_catch.hpp delete mode 100644 src/validator_catch.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 205a0a0..fde72e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,13 +50,11 @@ libhal_unit_test(SOURCES tests/elf_parser.test.cpp tests/gcc_callgraph.test.cpp tests/abi_parser.test.cpp - tests/validator_catch.test.cpp tests/validator.test.cpp src/elf_parser.cpp src/gcc_parse.cpp src/abi_parse.cpp - src/validator_catch.cpp src/validator.cpp PACKAGES diff --git a/include/validator.hpp b/include/validator.hpp index b5d6365..3f037df 100644 --- a/include/validator.hpp +++ b/include/validator.hpp @@ -2,8 +2,13 @@ #include #include +#include #include +#include +#include +#include #include +#include #include #include #include @@ -11,13 +16,27 @@ #include #include #include -#include -#include +#include "abi_parse.hpp" #include "elf_parser.hpp" #include "gelf.h" namespace safe { +struct CatchRecord +{ + std::string scope_id; // example: "scope[0]" + HandlerType kind; // Catch / Cleanup / Filter + std::uint64_t range_begin; // scope.start + std::uint64_t range_end; // scope.end + std::uint64_t landing_pad; // handler.landing_pad + std::int64_t type_index; // handler.type_index +}; + +struct ThrowCatchMatch +{ + symbol_s thrown; // RTTI symbol for the thrown type + std::vector handlers; // matching catch handlers +}; class Validator { @@ -32,16 +51,34 @@ class Validator collect_rtti_sym(); } ~Validator() = default; - std::optional> find_typeinfo( - std::string_view func_name); + std::optional> find_typeinfo(std::string_view func_name); std::optional demangle(const char* mangled); std::optional get_symbol(std::string_view name); + enum class CorrelateError + { + NoTypeinfoForFunction, // Validator has no typeinfo for this function + NoThrownTypes, // Function exists, but no throws recorded + NoCatchRecords, // No LSDA catch records matched any thrown type + TypeResolveFailed, // LSDA::resolve_type() failed for some index + NoLsdaLoaded, // load_lsda() never called + }; + + using Result = std::expected, CorrelateError>; + + void load_lsda(const LsdaParser& lsda); + Result analyze_exceptions(std::string_view func_name) const; + + const std::vector& records() const noexcept { return m_records; } + private: std::span m_sym; section_s m_text; - std::unordered_map rtti_sym; + std::unordered_map rtti_sym; + const LsdaParser* m_lsda = nullptr; + std::vector m_records; // flattened handler table void collect_rtti_sym(); }; + } // namespace safe diff --git a/include/validator_catch.hpp b/include/validator_catch.hpp deleted file mode 100644 index daf1a20..0000000 --- a/include/validator_catch.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include "abi_parse.hpp" // LsdaParser, Scope, ScopeHandler, HandlerType -// #include "validator.hpp" // symbol_s, Validator - -#include -#include -#include -#include -#include - -namespace safe { - -// One record per handler inside a scope. -struct CatchRecord -{ - std::string scope_id; // example: "scope[0]" - HandlerType kind; // Catch / Cleanup / Filter - std::uint64_t range_begin; // scope.start - std::uint64_t range_end; // scope.end - std::uint64_t landing_pad; // handler.landing_pad - std::int64_t type_index; // handler.type_index -}; - -// Relation between a single thrown RTTI symbol and the handlers that can catch -// it. -// struct ThrowCatchMatch -// { -// symbol_s thrown; // RTTI symbol for the thrown type -// std::vector handlers; // matching catch handlers -// }; - -class CatchValidator -{ - public: - // errors that say why correlation between throws and catches might fail. - enum class CorrelateError - { - NoTypeinfoForFunction, // Validator has no typeinfo for this function - NoThrownTypes, // Function exists, but no throws recorded - NoCatchRecords, // No LSDA catch records matched any thrown type - TypeResolveFailed, // LSDA::resolve_type() failed for some index - }; - - // using CorrelateResult - // = std::expected, CorrelateError>; - - explicit CatchValidator(const LsdaParser& parser); - - const std::vector& records() const noexcept - { - return m_records; - } - - // // LSDA catch records from this validator - // CorrelateResult correlate_with_throws(Validator& throw_validator, - // std::string_view func_name) const; - - // // print the correlation or a short error message. - // void print_throw_catch_report(Validator& throw_validator, - // std::string_view func_name) const; - - void print_records() const; - - private: - // const LsdaParser& m_lsda; - std::vector m_records; - - static const char* handler_kind_to_string(HandlerType kind); -}; - -} // namespace safe diff --git a/src/abi_parse.cpp b/src/abi_parse.cpp index 3fad1bb..50ab1e0 100644 --- a/src/abi_parse.cpp +++ b/src/abi_parse.cpp @@ -74,12 +74,18 @@ void LsdaParser::build_scopes() s.end = cs.start + cs.length; // LSDA: action is a byte offset into the action table (0 = none) - if (cs.action == 0) { + if (cs.action <= 0) { + // landing_pad != 0 means there is a cleanup landing pad + ScopeHandler h{}; + h.type = HandlerType::Cleanup; + h.type_index = 0; + h.landing_pad = cs.landing_pad; + s.handlers.push_back(h); + scopes.push_back(std::move(s)); continue; } - - const int64_t action_offset = cs.action; + const int64_t action_offset = cs.action - 1; // Map offset -> first action index int64_t action_index = -1; @@ -91,13 +97,16 @@ void LsdaParser::build_scopes() } if (action_index < 0) { - std::cerr - << "[AbiParser] warning: call-site action offset " - << action_offset - << " not found in action table – treating call-site as having " - "no handlers\n"; + std::cerr << "... not found ... adding cleanup handler\n"; + + ScopeHandler h{}; + h.type = HandlerType::Cleanup; + h.type_index = 0; + h.landing_pad = cs.landing_pad; + s.handlers.push_back(h); + scopes.push_back(std::move(s)); - continue; // go on to the next call-site + continue; } // Follow action chain using resolved next_index @@ -402,7 +411,7 @@ void LsdaParser::parse_actions_tail(size_t table_start, size_t limit_end) continue; } - const int64_t target_offset = a.next_field_offset + a.next_offset; + const int64_t target_offset = a.entry_offset + a.next_offset; int64_t found = -1; for (size_t j = 0; j < actions.size(); ++j) { diff --git a/src/main.cpp b/src/main.cpp index 6bbc869..b45daec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ #include "abi_parse.hpp" #include "elf_parser.hpp" -#include "validator_catch.hpp" +#include "validator.hpp" /** * @enum main_error @@ -86,10 +86,24 @@ int main(int argc, char* argv[]) { auto args = validate_args(argc, argv); if (!args.has_value()) { - exit(EXIT_FAILURE); + return EXIT_FAILURE; } - ElfParser elf(args.value().file_name); + ElfParser elf(args->file_name); + + auto sym = elf.get_symbol_table(); + if (!sym.has_value()) { + std::print("Failed to get symbol table\n"); + return EXIT_FAILURE; + } + + auto text = elf.get_section(".text"); + if (!text.has_value()) { + std::print("Failed to get .text section\n"); + return EXIT_FAILURE; + } + + safe::Validator val(sym.value(), text.value()); auto gcc_except_table = elf.get_section(".gcc_except_table"); if (!gcc_except_table.has_value()) { @@ -100,10 +114,28 @@ int main(int argc, char* argv[]) if (gcc_except_table.error() == elf_parser_error::SECTION_NOT_FOUND) { std::print("Section was not found.\n"); } - exit(EXIT_FAILURE); + return EXIT_FAILURE; } - LsdaParser abi(gcc_except_table.value().data); + LsdaParser lsda(gcc_except_table->data); + + // Load LSDA catch table into Validator + val.load_lsda(lsda); + + auto res = val.analyze_exceptions("_Z3fooi"); + if (!res.has_value()) { + std::print("analyze_exceptions failed\n"); + return EXIT_FAILURE; + } + + // we print it but we canremove this + for (const auto& m : res.value()) { + auto dn = val.demangle(m.thrown.name.c_str()).value_or(m.thrown.name); + std::print("Thrown: {}\n", dn); + for (auto* h : m.handlers) { + std::print(" caught by {} type_index={}\n", h->scope_id, h->type_index); + } + } return 0; } \ No newline at end of file diff --git a/src/validator.cpp b/src/validator.cpp index a8e6d28..5fbe381 100644 --- a/src/validator.cpp +++ b/src/validator.cpp @@ -5,7 +5,12 @@ namespace safe { std::optional> Validator::find_typeinfo( std::string_view func_name) { - symbol_s func_sym = get_symbol(func_name).value(); + auto func_sym_opt = get_symbol(func_name); + if (!func_sym_opt.has_value()) { + return std::nullopt; + } + + symbol_s func_sym = *func_sym_opt; uint64_t func_addr = func_sym.value; GElf_Shdr text_hdr = m_text.header; uint64_t text_addr = text_hdr.sh_addr; @@ -27,10 +32,11 @@ std::optional> Validator::find_typeinfo( demangle(func_name.data()).value_or(func_name.data())); out << std::format("===========================\n"); - for (size_t i = 0; i < func_size - 8; ++i) { + // safer than (func_size - 8) + for (size_t i = 0; i + 4 <= func_size; ++i) { uint64_t current_addr = func_addr + i; int32_t rel_offset = *reinterpret_cast(func_start + i); - uint64_t target_addr = current_addr + 4 + rel_offset; + uint64_t target_addr = current_addr + 4 + static_cast(rel_offset); out << std::format("Offset: {:4} | Bytes: {:02x} {:02x} {:02x} {:02x} " "| Target: 0x{:x}\n", @@ -68,8 +74,7 @@ void Validator::collect_rtti_sym() continue; } if (demangle_sym->starts_with("typeinfo")) { - out << std::format( - " {} | {}\n", sym.value, demangle_sym.value()); + out << std::format(" {} | {}\n", sym.value, demangle_sym.value()); rtti_sym.emplace(sym.value, sym); } } @@ -88,10 +93,10 @@ std::optional Validator::get_symbol(std::string_view name) std::optional Validator::demangle(const char* mangled) { - int status; + int status = 0; char* demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status); - if (status == 0) { + if (status == 0 && demangled) { std::string result(demangled); std::free(demangled); return result; @@ -99,4 +104,98 @@ std::optional Validator::demangle(const char* mangled) return std::nullopt; } +void Validator::load_lsda(const LsdaParser& lsda) +{ + std::println("[Validator] load_lsda: begin"); + m_lsda = &lsda; + m_records.clear(); + + const auto& scopes = lsda.get_scopes(); + std::println("[Validator] load_lsda: scopes = {}", scopes.size()); + std::size_t idx = 0; + + for (const auto& scope : scopes) { + std::string scope_label = "scope[" + std::to_string(idx) + ']'; + for (const auto& h : scope.handlers) { + CatchRecord rec{}; + rec.scope_id = scope_label; + rec.kind = h.type; + rec.range_begin = scope.start; + rec.range_end = scope.end; + rec.landing_pad = h.landing_pad; + rec.type_index = h.type_index; + m_records.push_back(std::move(rec)); + } + ++idx; + } + std::println("[Validator] load_lsda: records = {}", m_records.size()); +} + +Validator::Result Validator::analyze_exceptions(std::string_view func_name) const +{ + if (m_lsda == nullptr) { + return std::unexpected(CorrelateError::NoLsdaLoaded); + } + + // Use teammate's throw info as the source of truth + auto thrown_opt = const_cast(this)->find_typeinfo(func_name); + if (!thrown_opt.has_value()) { + return std::unexpected(CorrelateError::NoTypeinfoForFunction); + } + + const auto& thrown_vec = *thrown_opt; + if (thrown_vec.empty()) { + return std::unexpected(CorrelateError::NoThrownTypes); + } + + if (m_records.empty()) { + return std::unexpected(CorrelateError::NoCatchRecords); + } + + std::vector result; + result.reserve(thrown_vec.size()); + + for (const auto& t : thrown_vec) { + ThrowCatchMatch rel{ t, {} }; + const std::uint64_t thrown_addr = t.value; + + for (const auto& rec : m_records) { + if (rec.type_index == 0) { + rel.handlers.push_back(&rec); + continue; + } + if (rec.type_index < 0) { + continue; + } + if (rec.kind != HandlerType::Catch) { + continue; + } + + auto handler_addr_opt = m_lsda->resolve_type(rec.type_index); + if (!handler_addr_opt.has_value()) { + continue; + } + + if (*handler_addr_opt == thrown_addr) { + rel.handlers.push_back(&rec); + } + } + + result.push_back(std::move(rel)); + } + + bool any_handlers = false; + for (const auto& m : result) { + if (!m.handlers.empty()) { + any_handlers = true; + break; + } + } + if (!any_handlers) { + return std::unexpected(CorrelateError::NoCatchRecords); + } + + return result; +} + } // namespace safe diff --git a/src/validator_catch.cpp b/src/validator_catch.cpp deleted file mode 100644 index fe27e0f..0000000 --- a/src/validator_catch.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "validator_catch.hpp" - -#include -#include - -namespace safe { - -namespace { - -// helper used internally to turn handler kind into text. -const char* handler_kind_to_string_local(HandlerType kind) -{ - switch (kind) { - case HandlerType::Catch: - return "Catch"; - case HandlerType::Cleanup: - return "Cleanup"; - case HandlerType::Filter: - return "Filter"; - } - return "Unknown"; -} -// we just call symbol_address. -// std::uint64_t -// symbol_address(const symbol_s& s) -// { -// return s.value; -// } - - -} - -const char* CatchValidator::handler_kind_to_string(HandlerType kind) -{ - return handler_kind_to_string_local(kind); -} - -CatchValidator::CatchValidator(const LsdaParser& parser) -// : m_lsda(parser) -{ - m_records.clear(); - const auto& scopes = parser.get_scopes(); - - std::size_t idx = 0; - for (const auto& scope : scopes) { - std::string scope_label = "scope[" + std::to_string(idx) + ']'; - - for (const auto& h : scope.handlers) { - CatchRecord rec{}; - rec.scope_id = scope_label; - rec.kind = h.type; // ScopeHandler::type - rec.range_begin = scope.start; - rec.range_end = scope.end; - rec.landing_pad = h.landing_pad; - rec.type_index = h.type_index; - - m_records.push_back(rec); - } - - ++idx; - } -} - -// CatchValidator::CorrelateResult CatchValidator::correlate_with_throws( -// Validator& throw_validator, -// std::string_view func_name) const -// { -// // ask the throw side Validator which RTTI objects this function throws. -// auto thrown_opt = throw_validator.find_typeinfo(func_name); -// if (!thrown_opt.has_value()) { -// // Either the function isn't known to Validator, or it couldn't compute -// // typeinfo for it. -// return std::unexpected(CorrelateError::NoTypeinfoForFunction); -// } - -// const auto& thrown_vec = *thrown_opt; -// if (thrown_vec.empty()) { -// return std::unexpected(CorrelateError::NoThrownTypes); -// } - -// if (m_records.empty()) { -// return std::unexpected(CorrelateError::NoCatchRecords); -// } - -// std::vector result; -// result.reserve(thrown_vec.size()); - -// // for each thrown type, find matching LSDA catch records. -// for (const auto& t : thrown_vec) { -// ThrowCatchMatch rel{ t, {} }; -// const std::uint64_t thrown_addr = symbol_address(t); - -// for (const auto& rec : m_records) { -// // skip cleanups / filters / invalid indices. -// if (rec.kind != HandlerType::Catch || rec.type_index <= 0) { -// continue; -// } - -// auto handler_addr_opt = m_lsda.resolve_type(rec.type_index); -// if (!handler_addr_opt.has_value()) { -// // LSDA type table looks inconsistent; surface this as an error. -// return std::unexpected(CorrelateError::TypeResolveFailed); -// } - -// if (*handler_addr_opt == thrown_addr) { -// rel.handlers.push_back(&rec); -// } -// } - -// result.push_back(std::move(rel)); -// } - -// // if none of the thrown types mapped to any handlers at all, signal to -// // callers. -// bool any_handlers = false; -// for (const auto& m : result) { -// if (!m.handlers.empty()) { -// any_handlers = true; -// break; -// } -// } -// if (!any_handlers) { -// return std::unexpected(CorrelateError::NoCatchRecords); -// } - -// return result; -// } - -// void CatchValidator::print_throw_catch_report(Validator& throw_validator, -// std::string_view func_name) const -// { -// auto matches_or_err = correlate_with_throws(throw_validator, func_name); - -// std::cout << "[SAFE] throw/catch correlation for function " << func_name -// << ":\n"; - -// if (!matches_or_err.has_value()) { -// switch (matches_or_err.error()) { -// case CorrelateError::NoTypeinfoForFunction: -// std::cout -// << " (no typeinfo found for this function in Validator)\n"; -// break; -// case CorrelateError::NoThrownTypes: -// std::cout << " (function has no recorded throw types)\n"; -// break; -// case CorrelateError::NoCatchRecords: -// std::cout -// << " (no LSDA catch records matched any thrown type)\n"; -// break; -// case CorrelateError::TypeResolveFailed: -// std::cout -// << " (failed to resolve at least one LSDA type index)\n"; -// break; -// } -// return; -// } - -// const auto& matches = matches_or_err.value(); - -// for (const auto& mc : matches) { -// const auto addr = symbol_address(mc.thrown); - -// std::cout << " Thrown RTTI symbol: " << mc.thrown.name << " @ 0x" -// << std::hex << addr << std::dec << "\n"; - -// if (mc.handlers.empty()) { -// std::cout << " ❌ no matching catch handlers in LSDA\n"; -// } else { -// std::cout << " ✅ handled by " << mc.handlers.size() -// << " catch handler(s):\n"; -// for (const auto* rec : mc.handlers) { -// std::cout << " - " << rec->scope_id << " (" -// << handler_kind_to_string(rec->kind) << ") " -// << "range 0x" << std::hex << rec->range_begin << "-0x" -// << rec->range_end << ", landing_pad 0x" -// << rec->landing_pad << std::dec << ", type_index " -// << rec->type_index << "\n"; -// } -// } -// } -// } - -void CatchValidator::print_records() const -{ - std::cout << "\n[Catch Handler Table]\n"; - - std::size_t idx = 0; - for (const auto& rec : m_records) { - std::cout << " [" << idx++ << "] " - << "Scope: " << rec.scope_id - << ", Kind: " << handler_kind_to_string(rec.kind) - << ", Range: 0x" << std::hex << rec.range_begin << " - 0x" - << rec.range_end << ", LandingPad: 0x" << rec.landing_pad - << std::dec << ", TypeIndex: " << rec.type_index << '\n'; - } -} - -} // namespace safe diff --git a/tests/validator.test.cpp b/tests/validator.test.cpp index 7e84b73..e0f2c7d 100644 --- a/tests/validator.test.cpp +++ b/tests/validator.test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include boost::ut::suite<"Validator_Test"> validator_test = [] { using namespace boost::ut; @@ -69,5 +70,37 @@ boost::ut::suite<"Validator_Test"> validator_test = [] { "Got: " << type_name << "\n"; } }; + + "Exception correlation"_test = [test_file] { + ElfParser elf(test_file); + + auto sym = elf.get_symbol_table(); + expect(sym.has_value()) << "sym table fail\n"; + + auto text = elf.get_section(".text"); + expect(text.has_value()) << "text fail\n"; + + auto gcc_except = elf.get_section(".gcc_except_table"); + expect(gcc_except.has_value()) << ".gcc_except_table fail\n"; + + safe::Validator val(sym.value(), text.value()); + LsdaParser lsda(gcc_except->data); + + val.load_lsda(lsda); + + auto res = val.analyze_exceptions("_Z3fooi"); + expect(res.has_value()) << "analyze_exceptions failed\n"; + + if (!res.has_value()) { + std::println("analyze_exceptions error code = {}", static_cast(res.error())); + return; + } + + bool any_caught = false; + for (const auto& m : res.value()) { + if (!m.handlers.empty()) { any_caught = true; break; } + } + expect(any_caught) << "No thrown types matched any catch handlers\n"; + }; }; }; diff --git a/tests/validator_catch.test.cpp b/tests/validator_catch.test.cpp index 2d084d2..0714f12 100644 --- a/tests/validator_catch.test.cpp +++ b/tests/validator_catch.test.cpp @@ -1,7 +1,7 @@ #include "validator_catch.hpp" #include "abi_parse.hpp" #include "elf_parser.hpp" -// #include "validator.hpp" +#include "validator.hpp" #include @@ -46,61 +46,61 @@ boost::ut::suite validator_catch_test = [] { safe::CatchValidator validator{ lsda }; - // const auto& records = validator.records(); - // expect(records.size() > 0_u) << "no catch records parsed"; + const auto& records = validator.records(); + expect(records.size() > 0_u) << "no catch records parsed"; - // for (const auto& rec : records) { - // expect(rec.range_begin <= rec.range_end) << "invalid handler range"; - // } + for (const auto& rec : records) { + expect(rec.range_begin <= rec.range_end) << "invalid handler range"; + } validator.print_records(); }; - // "throw-catch correlation"_test = [] { - // std::string_view test_file = "../../testing_programs/build/simple"; + "throw-catch correlation"_test = [] { + std::string_view test_file = "../../testing_programs/build/simple"; - // ElfParser elf(test_file); + ElfParser elf(test_file); - // // Symbol table and .text are for the throw-side Validator - // auto symtab = elf.get_symbol_table(); - // expect(symtab.has_value()) << "no symbol table"; + // Symbol table and .text are for the throw-side Validator + auto symtab = elf.get_symbol_table(); + expect(symtab.has_value()) << "no symbol table"; - // auto text = elf.get_section(".text"); - // expect(text.has_value()) << "missing .text section"; + auto text = elf.get_section(".text"); + expect(text.has_value()) << "missing .text section"; - // // LSDA section for CatchValidator - // auto gcc_except_table = elf.get_section(".gcc_except_table"); - // expect(gcc_except_table.has_value()) << "missing .gcc_except_table"; + // LSDA section for CatchValidator + auto gcc_except_table = elf.get_section(".gcc_except_table"); + expect(gcc_except_table.has_value()) << "missing .gcc_except_table"; - // // Build throw-side validator (your teammate's) - // safe::Validator throw_validator{ symtab.value(), text.value() }; + // Build throw-side validator (your teammate's) + safe::Validator throw_validator{ symtab.value(), text.value() }; - // // Build LSDA-side validator (yours) - // LsdaParser lsda(gcc_except_table.value().data); - // safe::CatchValidator catch_validator{ lsda }; + // Build LSDA-side validator (yours) + LsdaParser lsda(gcc_except_table.value().data); + safe::CatchValidator catch_validator{ lsda }; - // // Function to inspect (mangled name); adjust if your test program - // // uses a different function name. - // constexpr auto func_name = "_Z3foov"sv; + // Function to inspect (mangled name); adjust if your test program + // uses a different function name. + constexpr auto func_name = "_Z3foov"sv; - // auto result - // = catch_validator.correlate_with_throws(throw_validator, func_name); + auto result + = catch_validator.correlate_with_throws(throw_validator, func_name); - // expect(result.has_value()) << "correlation failed with error code"; + expect(result.has_value()) << "correlation failed with error code"; - // const auto& matches = result.value(); - // expect(matches.size() > 0_u) << "no thrown types recorded for function"; + const auto& matches = result.value(); + expect(matches.size() > 0_u) << "no thrown types recorded for function"; - // // At least one thrown type should have at least one matching handler. - // bool any_matched = false; - // for (const auto& m : matches) { - // if (!m.handlers.empty()) { - // any_matched = true; - // break; - // } - // } - // expect(any_matched) << "no LSDA catch handlers matched any thrown type"; + // At least one thrown type should have at least one matching handler. + bool any_matched = false; + for (const auto& m : matches) { + if (!m.handlers.empty()) { + any_matched = true; + break; + } + } + expect(any_matched) << "no LSDA catch handlers matched any thrown type"; - // // Optional: print report for manual inspection - // catch_validator.print_throw_catch_report(throw_validator, func_name); - // }; + // Optional: print report for manual inspection + catch_validator.print_throw_catch_report(throw_validator, func_name); + }; };