|
| 1 | +/* |
| 2 | + * Copyright 2026, Datadog, Inc. |
| 3 | + * SPDX-License-Identifier: Apache-2.0 |
| 4 | + */ |
| 5 | + |
| 6 | +#include <gtest/gtest.h> |
| 7 | + |
| 8 | +#include "dwarf.h" |
| 9 | +#include "../../main/cpp/gtest_crash_handler.h" |
| 10 | + |
| 11 | +#include <cstdint> |
| 12 | +#include <cstring> |
| 13 | +#include <vector> |
| 14 | + |
| 15 | +// Test name for crash handler |
| 16 | +static constexpr char DWARF_TEST_NAME[] = "DwarfTest"; |
| 17 | + |
| 18 | +class DwarfGlobalSetup { |
| 19 | + public: |
| 20 | + DwarfGlobalSetup() { |
| 21 | + installGtestCrashHandler<DWARF_TEST_NAME>(); |
| 22 | + } |
| 23 | + ~DwarfGlobalSetup() { |
| 24 | + restoreDefaultSignalHandlers(); |
| 25 | + } |
| 26 | +}; |
| 27 | +static DwarfGlobalSetup dwarf_global_setup; |
| 28 | + |
| 29 | +#if DWARF_SUPPORTED |
| 30 | + |
| 31 | +// Helpers to write little-endian integers into a byte buffer. |
| 32 | +static void put32(std::vector<uint8_t>& buf, uint32_t v) { |
| 33 | + buf.push_back(static_cast<uint8_t>(v)); |
| 34 | + buf.push_back(static_cast<uint8_t>(v >> 8)); |
| 35 | + buf.push_back(static_cast<uint8_t>(v >> 16)); |
| 36 | + buf.push_back(static_cast<uint8_t>(v >> 24)); |
| 37 | +} |
| 38 | + |
| 39 | +static void put8(std::vector<uint8_t>& buf, uint8_t v) { |
| 40 | + buf.push_back(v); |
| 41 | +} |
| 42 | + |
| 43 | +// Append a minimal CIE with "z" augmentation to buf. |
| 44 | +// Layout: [4-len=11][4-cie_id=0][1-ver=1][2-aug="z\0"][1-code_align=4][1-data_align=-8][1-ra=30][1-aug_data_len=0] |
| 45 | +// Total: 15 bytes. |
| 46 | +static void appendCie(std::vector<uint8_t>& buf) { |
| 47 | + // body size = cie_id(4) + version(1) + "z\0"(2) + code_align(1) + data_align(1) + ra_col(1) + aug_data_len(1) = 11 |
| 48 | + put32(buf, 11); // length |
| 49 | + put32(buf, 0); // cie_id = 0 |
| 50 | + put8(buf, 1); // version |
| 51 | + put8(buf, 'z'); // augmentation "z" |
| 52 | + put8(buf, 0); // null terminator |
| 53 | + put8(buf, 4); // code_align = 4 (LEB128) |
| 54 | + put8(buf, 0x78); // data_align = -8 (SLEB128: 0x78) |
| 55 | + put8(buf, 30); // return address column = 30 (lr) |
| 56 | + put8(buf, 0); // augmentation data length = 0 (LEB128) |
| 57 | +} |
| 58 | + |
| 59 | +// Append an FDE referencing the CIE at cie_start_offset from the buf start. |
| 60 | +// cie_offset in the FDE = offset from FDE's cie_id field to the CIE start. |
| 61 | +// range_start is encoded as a 4-byte PC-relative signed integer (pcrel). |
| 62 | +// With pcrel=0 and image_base=&buf[0]: range_start = offset_of_pcrel_field_within_buf. |
| 63 | +// Layout: [4-len=13][4-cie_offset][4-pcrel=0][4-range_len][1-aug_data_len=0] |
| 64 | +// Total: 17 bytes. |
| 65 | +static void appendFde(std::vector<uint8_t>& buf, uint32_t cie_start_offset, uint32_t range_len) { |
| 66 | + // The FDE's cie_id field will be at buf.size() + 4 (after length field). |
| 67 | + uint32_t cie_id_field_offset = static_cast<uint32_t>(buf.size()) + 4; |
| 68 | + uint32_t cie_offset = cie_id_field_offset - cie_start_offset; |
| 69 | + |
| 70 | + // body = cie_offset(4) + range_start(4) + range_len(4) + aug_data_len(1) = 13 |
| 71 | + put32(buf, 13); // length |
| 72 | + put32(buf, cie_offset); // cie_offset from this field back to CIE start |
| 73 | + put32(buf, 0); // range_start pcrel = 0 (absolute value = field_address - image_base) |
| 74 | + put32(buf, range_len); // range_len |
| 75 | + put8(buf, 0); // aug data length = 0 (LEB128, for "z" augmentation) |
| 76 | + // no DWARF call frame instructions |
| 77 | +} |
| 78 | + |
| 79 | +static void appendTerminator(std::vector<uint8_t>& buf) { |
| 80 | + put32(buf, 0); |
| 81 | +} |
| 82 | + |
| 83 | +// Parse a raw __eh_frame section using the linear DwarfParser constructor. |
| 84 | +// image_base is set to buf.data() so that pcrel=0 yields range_start = field_offset_in_buf. |
| 85 | +static DwarfParser* parseBuf(const std::vector<uint8_t>& buf) { |
| 86 | + const char* base = reinterpret_cast<const char*>(buf.data()); |
| 87 | + return new DwarfParser("test", base, base, buf.size()); |
| 88 | +} |
| 89 | + |
| 90 | +TEST(DwarfEhFrame, EmptySection) { |
| 91 | + std::vector<uint8_t> buf; |
| 92 | + DwarfParser* dwarf = parseBuf(buf); |
| 93 | + EXPECT_EQ(dwarf->count(), 0); |
| 94 | + delete dwarf; |
| 95 | +} |
| 96 | + |
| 97 | +TEST(DwarfEhFrame, TerminatorOnly) { |
| 98 | + std::vector<uint8_t> buf; |
| 99 | + appendTerminator(buf); |
| 100 | + DwarfParser* dwarf = parseBuf(buf); |
| 101 | + EXPECT_EQ(dwarf->count(), 0); |
| 102 | + delete dwarf; |
| 103 | +} |
| 104 | + |
| 105 | +TEST(DwarfEhFrame, CieOnly) { |
| 106 | + std::vector<uint8_t> buf; |
| 107 | + appendCie(buf); |
| 108 | + appendTerminator(buf); |
| 109 | + DwarfParser* dwarf = parseBuf(buf); |
| 110 | + // CIE alone generates no frame records. |
| 111 | + EXPECT_EQ(dwarf->count(), 0); |
| 112 | + delete dwarf; |
| 113 | +} |
| 114 | + |
| 115 | +TEST(DwarfEhFrame, CieAndFde) { |
| 116 | + // CIE starts at offset 0. |
| 117 | + std::vector<uint8_t> buf; |
| 118 | + appendCie(buf); // 15 bytes |
| 119 | + appendFde(buf, 0, 256); // 17 bytes (cie_offset = 19) |
| 120 | + appendTerminator(buf); // 4 bytes |
| 121 | + ASSERT_EQ(buf.size(), static_cast<size_t>(36)); |
| 122 | + |
| 123 | + DwarfParser* dwarf = parseBuf(buf); |
| 124 | + // The FDE with no instructions generates two records: |
| 125 | + // one from parseInstructions (initial state at range_start) and one sentinel (at range_start + range_len). |
| 126 | + EXPECT_EQ(dwarf->count(), 2); |
| 127 | + |
| 128 | + // Table must be in ascending loc order (sorted). |
| 129 | + const FrameDesc* table = dwarf->table(); |
| 130 | + ASSERT_NE(table, nullptr); |
| 131 | + EXPECT_LT(table[0].loc, table[1].loc); |
| 132 | + |
| 133 | + // Sentinel record covers the end of the FDE's range. |
| 134 | + // range_start = offset of pcrel field in buf = 15+4+4 = 23; range_end = 23+256 = 279. |
| 135 | + EXPECT_EQ(table[1].loc, static_cast<uint32_t>(279)); |
| 136 | + |
| 137 | + delete dwarf; |
| 138 | +} |
| 139 | + |
| 140 | +#endif // DWARF_SUPPORTED |
0 commit comments