Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ 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.test.cpp

src/elf_parser.cpp
Expand Down
6 changes: 3 additions & 3 deletions include/abi_parse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ struct Scope
std::vector<ScopeHandler> handlers;
};

class GccParser
class LsdaParser
{
public:
explicit GccParser(const std::vector<std::byte>& lsda_data);
explicit GccParser(const std::vector<uint8_t>& lsda_data);
explicit LsdaParser(const std::vector<std::byte>& lsda_data);
explicit LsdaParser(const std::vector<uint8_t>& lsda_data);

std::optional<uint64_t> resolve_type(int64_t type_index) const;
void print_call_sites(const std::string& filename) const;
Expand Down
47 changes: 42 additions & 5 deletions include/validator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,41 @@
#include <cxxabi.h>

#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <expected>
#include <filesystem>
#include <format>
#include <fstream>
#include <optional>
#include <print>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <format>
#include <filesystem>

#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<const CatchRecord*> handlers; // matching catch handlers
};

class Validator
{
Expand All @@ -32,16 +51,34 @@ class Validator
collect_rtti_sym();
}
~Validator() = default;
std::optional<std::vector<symbol_s>> find_typeinfo(
std::string_view func_name);
std::optional<std::vector<symbol_s>> find_typeinfo(std::string_view func_name);
std::optional<std::string> demangle(const char* mangled);
std::optional<symbol_s> 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<std::vector<ThrowCatchMatch>, CorrelateError>;

void load_lsda(const LsdaParser& lsda);
Result analyze_exceptions(std::string_view func_name) const;

const std::vector<CatchRecord>& records() const noexcept { return m_records; }

private:
std::span<symbol_s> m_sym;
section_s m_text;
std::unordered_map<uint64_t, symbol_s> rtti_sym;
std::unordered_map<std::uint64_t, symbol_s> rtti_sym;
const LsdaParser* m_lsda = nullptr;
std::vector<CatchRecord> m_records; // flattened handler table

void collect_rtti_sym();
};

} // namespace safe
72 changes: 42 additions & 30 deletions src/abi_parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@
#include <iomanip>
#include <iostream>

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");
}
}

// reads 1 byte and then it goes forward with cursor
uint8_t GccParser::read8()
uint8_t LsdaParser::read8()
{
check(1);
return data[index++];
}

// reads 2 bytes and makes a 16 bit integer

uint16_t GccParser::read16()
uint16_t LsdaParser::read16()
{
check(2);
uint16_t v = 0;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -60,7 +60,7 @@ uint64_t GccParser::read64()
return value;
}

void GccParser::build_scopes()
void LsdaParser::build_scopes()
{
scopes.clear();

Expand All @@ -74,12 +74,18 @@ void GccParser::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;
Expand All @@ -91,13 +97,16 @@ void GccParser::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
Expand Down Expand Up @@ -125,7 +134,7 @@ void GccParser::build_scopes()
}
}

GccParser::GccParser(const std::vector<std::byte>& lsda_data)
LsdaParser::LsdaParser(const std::vector<std::byte>& lsda_data)
{
data.reserve(lsda_data.size());
for (std::byte b : lsda_data) {
Expand All @@ -134,13 +143,13 @@ GccParser::GccParser(const std::vector<std::byte>& lsda_data)
parse();
}

GccParser::GccParser(const std::vector<uint8_t>& lsda_data)
LsdaParser::LsdaParser(const std::vector<uint8_t>& lsda_data)
{
data = lsda_data; // copy into owned storage
parse();
}

uint64_t GccParser::read_uleb128(const std::vector<uint8_t>& buf, size_t& i)
uint64_t LsdaParser::read_uleb128(const std::vector<uint8_t>& buf, size_t& i)
{
uint64_t result = 0;
int shift = 0;
Expand All @@ -158,7 +167,7 @@ uint64_t GccParser::read_uleb128(const std::vector<uint8_t>& buf, size_t& i)
return result;
}

int64_t GccParser::read_sleb128(const std::vector<uint8_t>& buf, size_t& i)
int64_t LsdaParser::read_sleb128(const std::vector<uint8_t>& buf, size_t& i)
{
int64_t result = 0;
int shift = 0;
Expand All @@ -181,7 +190,7 @@ int64_t GccParser::read_sleb128(const std::vector<uint8_t>& 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
Expand Down Expand Up @@ -234,7 +243,7 @@ uint64_t GccParser::r_encode(uint8_t encoding, uint64_t pcrel)
}

// parse
void GccParser::parse()
void LsdaParser::parse()
{
call_sites.clear();
actions.clear();
Expand Down Expand Up @@ -306,7 +315,7 @@ void GccParser::parse()
build_scopes();
}

std::optional<uint64_t> GccParser::resolve_type(int64_t type_index) const
std::optional<uint64_t> LsdaParser::resolve_type(int64_t type_index) const
{
// Cleanup and filter entries have no concrete type address
if (type_index <= 0) {
Expand All @@ -325,7 +334,7 @@ std::optional<uint64_t> 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)
{
Expand All @@ -344,7 +353,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<size_t>(table_len);
while (index < end) {
Expand All @@ -361,7 +370,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{};
Expand All @@ -371,8 +380,11 @@ void GccParser::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
Expand All @@ -399,7 +411,7 @@ void GccParser::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) {
Expand Down Expand Up @@ -427,7 +439,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) {
Expand All @@ -441,7 +453,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) {
Expand Down
Loading