Skip to content

Commit

Permalink
Add DWARF testing infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
chaoticgd committed Nov 22, 2024
1 parent 8388142 commit 198cb02
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
146 changes: 146 additions & 0 deletions src/ccc/dwarf_forge.cpp
Original file line number Diff line number Diff line change
@@ -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<u32>(0xbaadbaad);
push<u16>(tag);

push<u16>((AT_sibling << 4) | FORM_REF);
push<u32>(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<u32>(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<u32>(m_debug.size()) - begin_offset;
memcpy(&m_debug[begin_offset], &size, sizeof(u32));
}

void Forge::address(Attribute attribute, u32 address)
{
push<u16>((attribute << 4) | FORM_ADDR);
push<u32>(address);
}

void Forge::reference(Attribute attribute, std::string id)
{
push<u16>((attribute << 4) | FORM_REF);
u32 offset = push<u32>(0xbaadbaad);
m_references.emplace(offset, id);
}

void Forge::constant_2(Attribute attribute, u16 constant)
{
push<u16>((attribute << 4) | FORM_DATA2);
push<u16>(constant);
}

void Forge::constant_4(Attribute attribute, u32 constant)
{
push<u16>((attribute << 4) | FORM_DATA4);
push<u32>(constant);
}

void Forge::constant_8(Attribute attribute, u64 constant)
{
push<u16>((attribute << 4) | FORM_DATA8);
push<u64>(constant);
}

void Forge::block_2(Attribute attribute, std::initializer_list<u8> block, std::initializer_list<BlockId> ids)
{
CCC_ASSERT(block.size() <= UINT16_MAX);

push<u16>((attribute << 4) | FORM_BLOCK2);
push<u16>(static_cast<u16>(block.size()));
u32 offset = static_cast<u32>(m_debug.size());
for (u8 byte : block) {
push<u8>(byte);
}

for (BlockId id : ids) {
m_references.emplace(offset + id.offset, id.id);
}
}

void Forge::block_4(Attribute attribute, std::initializer_list<u8> block, std::initializer_list<BlockId> ids)
{
CCC_ASSERT(block.size() <= UINT32_MAX);

push<u16>((attribute << 4) | FORM_BLOCK4);
push<u32>(static_cast<u32>(block.size()));
u32 offset = static_cast<u32>(m_debug.size());
for (u8 byte : block) {
push<u8>(byte);
}

for (BlockId id : ids) {
m_references.emplace(offset + id.offset, id.id);
}
}

void Forge::string(Attribute attribute, std::string_view string)
{
push<u16>((attribute << 4) | FORM_STRING);
for (char c : string) {
push<char>(c);
}
push<char>('\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<u32>(6);
push<u16>(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<u8> 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);
}

}
64 changes: 64 additions & 0 deletions src/ccc/dwarf_forge.h
Original file line number Diff line number Diff line change
@@ -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<u8> block, std::initializer_list<BlockId> ids = {});
void block_4(Attribute attribute, std::initializer_list<u8> block, std::initializer_list<BlockId> 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<u8> finish();

protected:
template <typename T>
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<u32>(offset);
}

std::vector<u8> m_debug;

// These are used to patch references to DIEs.
std::map<std::string, u32> m_dies;
std::map<u32, std::string> m_references;
std::vector<std::optional<u32>> m_prev_siblings = {std::nullopt};
};

}
72 changes: 72 additions & 0 deletions test/ccc/dwarf_importer_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// This file is part of the Chaos Compiler Collection.
// SPDX-License-Identifier: MIT

#include <gtest/gtest.h>
#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<SymbolDatabase> import_test_dwarf_symbol_table(Forge& forge)
{
std::vector<u8> debug = forge.finish();
std::vector<u8> line;

SectionReader reader(debug, line, STRICT_PARSING);

#ifdef VERBOSE_DWARF_TESTING
SymbolPrinter printer(reader);
Result<void> 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<SymbolSource*> source = database.symbol_sources.create_symbol("Test Source", SymbolSourceHandle(), nullptr);
CCC_RETURN_IF_ERROR(source);

SymbolGroup group;
group.source = (*source)->handle();

Result<void> 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<SymbolDatabase> 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);
}

0 comments on commit 198cb02

Please sign in to comment.