From 198cb02abcace8e526e3d8c9ea2ef88306009686 Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:53:57 +0000 Subject: [PATCH] Add DWARF testing infrastructure --- CMakeLists.txt | 3 + src/ccc/dwarf_forge.cpp | 146 ++++++++++++++++++++++++++++++ src/ccc/dwarf_forge.h | 64 +++++++++++++ test/ccc/dwarf_importer_tests.cpp | 72 +++++++++++++++ 4 files changed, 285 insertions(+) create mode 100644 src/ccc/dwarf_forge.cpp create mode 100644 src/ccc/dwarf_forge.h create mode 100644 test/ccc/dwarf_importer_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ce04b98..fdfc784 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,8 @@ add_library(ccc STATIC src/ccc/dependency.h src/ccc/dwarf_attributes.cpp src/ccc/dwarf_attributes.h + src/ccc/dwarf_forge.cpp + src/ccc/dwarf_forge.h src/ccc/dwarf_importer.cpp src/ccc/dwarf_importer.h src/ccc/dwarf_printer.cpp @@ -105,6 +107,7 @@ add_library(ccc_platform STATIC set(TEST_SOURCES test/demangler_tests.cpp test/collision_tests.cpp + test/ccc/dwarf_importer_tests.cpp test/ccc/elf_tests.cpp test/ccc/int128_tests.cpp test/ccc/mdebug_importer_tests.cpp diff --git a/src/ccc/dwarf_forge.cpp b/src/ccc/dwarf_forge.cpp new file mode 100644 index 0000000..6d2799a --- /dev/null +++ b/src/ccc/dwarf_forge.cpp @@ -0,0 +1,146 @@ +// This file is part of the Chaos Compiler Collection. +// SPDX-License-Identifier: MIT + +#include "dwarf_forge.h" + +namespace ccc::dwarf { + +void Forge::begin_die(std::string id, Tag tag) +{ + u32 offset = push(0xbaadbaad); + push(tag); + + push((AT_sibling << 4) | FORM_REF); + push(0xbaadbaad); + + CCC_ABORT_IF_FALSE(!m_prev_siblings.empty(), "Unmatched begin_children/end_children calls."); + + // Link the sibling attribute of the previous DIE to this one. + if (m_prev_siblings.back().has_value()) { + u32 prev_die = *m_prev_siblings.back(); + CCC_ASSERT(prev_die + 12 <= m_debug.size()) + memcpy(&m_debug[prev_die + 8], &offset, sizeof(u32)); + } + + m_prev_siblings.back() = offset; + + m_dies.emplace(std::move(id), static_cast(m_debug.size())); +} + +void Forge::end_die() +{ + // Fill in the size field. + CCC_ASSERT(m_prev_siblings.back().has_value()); + u32 begin_offset = *m_prev_siblings.back(); + CCC_ASSERT(begin_offset + 4 <= m_debug.size()); + u32 size = static_cast(m_debug.size()) - begin_offset; + memcpy(&m_debug[begin_offset], &size, sizeof(u32)); +} + +void Forge::address(Attribute attribute, u32 address) +{ + push((attribute << 4) | FORM_ADDR); + push(address); +} + +void Forge::reference(Attribute attribute, std::string id) +{ + push((attribute << 4) | FORM_REF); + u32 offset = push(0xbaadbaad); + m_references.emplace(offset, id); +} + +void Forge::constant_2(Attribute attribute, u16 constant) +{ + push((attribute << 4) | FORM_DATA2); + push(constant); +} + +void Forge::constant_4(Attribute attribute, u32 constant) +{ + push((attribute << 4) | FORM_DATA4); + push(constant); +} + +void Forge::constant_8(Attribute attribute, u64 constant) +{ + push((attribute << 4) | FORM_DATA8); + push(constant); +} + +void Forge::block_2(Attribute attribute, std::initializer_list block, std::initializer_list ids) +{ + CCC_ASSERT(block.size() <= UINT16_MAX); + + push((attribute << 4) | FORM_BLOCK2); + push(static_cast(block.size())); + u32 offset = static_cast(m_debug.size()); + for (u8 byte : block) { + push(byte); + } + + for (BlockId id : ids) { + m_references.emplace(offset + id.offset, id.id); + } +} + +void Forge::block_4(Attribute attribute, std::initializer_list block, std::initializer_list ids) +{ + CCC_ASSERT(block.size() <= UINT32_MAX); + + push((attribute << 4) | FORM_BLOCK4); + push(static_cast(block.size())); + u32 offset = static_cast(m_debug.size()); + for (u8 byte : block) { + push(byte); + } + + for (BlockId id : ids) { + m_references.emplace(offset + id.offset, id.id); + } +} + +void Forge::string(Attribute attribute, std::string_view string) +{ + push((attribute << 4) | FORM_STRING); + for (char c : string) { + push(c); + } + push('\0'); +} + +void Forge::begin_children() +{ + m_prev_siblings.emplace_back(std::nullopt); +} + +void Forge::end_children() +{ + // Add a null entry and link it up if necessary. + if (m_prev_siblings.back().has_value()) { + u32 offset = push(6); + push(0); + + u32 prev_die = *m_prev_siblings.back(); + CCC_ASSERT(prev_die + 12 <= m_debug.size()) + memcpy(&m_debug[prev_die + 8], &offset, sizeof(u32)); + } + + m_prev_siblings.pop_back(); +} + +std::vector Forge::finish() +{ + // Link up all the references. + for (auto& [reference_offset, id] : m_references) { + auto die = m_dies.find(id); + CCC_ASSERT(die != m_dies.end()); + CCC_ASSERT(reference_offset + 4 <= m_debug.size()); + memcpy(&m_debug[reference_offset], &die->second, sizeof(u32)); + } + + // Return the finished section data. + return std::move(m_debug); +} + +} diff --git a/src/ccc/dwarf_forge.h b/src/ccc/dwarf_forge.h new file mode 100644 index 0000000..00e51ee --- /dev/null +++ b/src/ccc/dwarf_forge.h @@ -0,0 +1,64 @@ +// This file is part of the Chaos Compiler Collection. +// SPDX-License-Identifier: MIT + +#pragma once + +#include "dwarf_section.h" + +namespace ccc::dwarf { + +// DWARF 1 section builder for testing purposes. +class Forge { +public: + // Start crafting a DIE. + void begin_die(std::string id, Tag tag); + + // Finish crafting a DIE. + void end_die(); + + // Used for specifying references inside a block attribute that need to be + // linked up. + struct BlockId { + u32 offset; + std::string id; + }; + + // Craft attributes. These should be called between a pair of begin_die and + // end_die calls. + void address(Attribute attribute, u32 address); + void reference(Attribute attribute, std::string id); + void constant_2(Attribute attribute, u16 constant); + void constant_4(Attribute attribute, u32 constant); + void constant_8(Attribute attribute, u64 constant); + void block_2(Attribute attribute, std::initializer_list block, std::initializer_list ids = {}); + void block_4(Attribute attribute, std::initializer_list block, std::initializer_list ids = {}); + void string(Attribute attribute, std::string_view string); + + // Make the next DIEs children of the last DIE crafted. + void begin_children(); + + // Go up one level. + void end_children(); + + // Output the result. + std::vector finish(); + +protected: + template + u32 push(T value) + { + size_t offset = m_debug.size(); + m_debug.resize(offset + sizeof(T)); + memcpy(&m_debug[offset], &value, sizeof(T)); + return static_cast(offset); + } + + std::vector m_debug; + + // These are used to patch references to DIEs. + std::map m_dies; + std::map m_references; + std::vector> m_prev_siblings = {std::nullopt}; +}; + +} diff --git a/test/ccc/dwarf_importer_tests.cpp b/test/ccc/dwarf_importer_tests.cpp new file mode 100644 index 0000000..df4e8f5 --- /dev/null +++ b/test/ccc/dwarf_importer_tests.cpp @@ -0,0 +1,72 @@ +// This file is part of the Chaos Compiler Collection. +// SPDX-License-Identifier: MIT + +#include +#include "ccc/dwarf_forge.h" +#include "ccc/dwarf_importer.h" +#include "ccc/dwarf_printer.h" +#include "ccc/importer_flags.h" + +using namespace ccc; +using namespace ccc::dwarf; + +//#define VERBOSE_DWARF_TESTING + +static Result import_test_dwarf_symbol_table(Forge& forge) +{ + std::vector debug = forge.finish(); + std::vector line; + + SectionReader reader(debug, line, STRICT_PARSING); + +#ifdef VERBOSE_DWARF_TESTING + SymbolPrinter printer(reader); + Result print_result = printer.print_dies(stdout, *reader.first_die(), 0); + CCC_RETURN_IF_ERROR(print_result); +#endif + + SymbolDatabase database; + DemanglerFunctions demangler; + SymbolTableImporter importer(database, reader, STRICT_PARSING, demangler, nullptr); + + Result source = database.symbol_sources.create_symbol("Test Source", SymbolSourceHandle(), nullptr); + CCC_RETURN_IF_ERROR(source); + + SymbolGroup group; + group.source = (*source)->handle(); + + Result import_result = importer.import_symbol_table(group); + CCC_RETURN_IF_ERROR(import_result); + + return database; +} + +#define DWARF_IMPORTER_TEST(name, recipe) \ + static void dwarf_importer_test_##name(SymbolDatabase& database); \ + TEST(CCCDwarf, name) \ + { \ + Forge forge; \ + recipe; \ + Result database = import_test_dwarf_symbol_table(forge); \ + CCC_GTEST_FAIL_IF_ERROR(database); \ + dwarf_importer_test_##name(*database); \ + } \ + static void dwarf_importer_test_##name(SymbolDatabase& database) + +DWARF_IMPORTER_TEST(Test, + ({ + forge.begin_die("source1", TAG_compile_unit); + forge.string(AT_name, "gold.c"); + forge.end_die(); + forge.begin_children(); + forge.begin_die("func", TAG_global_subroutine); + forge.end_die(); + forge.end_children(); + + forge.begin_die("source2", TAG_compile_unit); + forge.string(AT_name, "sapphire.c"); + forge.end_die(); + })) +{ + EXPECT_EQ(database.source_files.size(), 2); +}