reloc_uleb128_unit.c (6651B)
1 /* test/link/reloc_uleb128_unit.c — direct unit test for the RISC-V 2 * R_RISCV_SET_ULEB128 / R_RISCV_SUB_ULEB128 relocation application. 3 * 4 * Why a direct unit test (not a corpus/roundtrip case): the relocs are 5 * APPLIED by the static/JIT linker (link_reloc_apply), not by the object 6 * roundtrip path, so the decisive assertion is that the rewritten 7 * ULEB128 field equals the encoded symbol difference AND keeps its 8 * original byte width (so the section layout never shifts). We construct 9 * the section bytes + the SET/SUB pair in memory and call 10 * link_reloc_apply directly. 11 * 12 * The fixtures are taken from a real RISC-V object: compiling 13 * void f(void){other();} void g(void){other();other();} 14 * with `clang -c -g -ffunction-sections --target=riscv64-linux-gnu 15 * -march=rv64gc` emits, in .debug_rnglists, two SET_ULEB128/SUB_ULEB128 16 * pairs encoding (sym_hi - sym_lo): 17 * off 0x12: SET .L0(=0x18), SUB .L0(=0x00) -> field = 0x18 (1 byte) 18 * off 0x15: SET .L0(=0x20), SUB .L0(=0x18) -> field = 0x08 (1 byte) 19 * We reproduce those, plus multi-byte width cases that exercise the 20 * fixed-width "redundant ULEB128" padding the in-place rewrite relies on. 21 * 22 * link_reloc_apply's ULEB128 success path never touches its Compiler* 23 * argument (it only does on the unsupported-kind panic), so we pass NULL. 24 * 25 * Exit 0 = pass; non-zero = fail (one line per failure on stderr). */ 26 27 #include <kit/core.h> 28 #include <stdint.h> 29 #include <stdio.h> 30 #include <string.h> 31 32 #include "lib/kit_unit.h" 33 #include "obj/obj.h" 34 #include "obj/reloc_apply.h" 35 36 /* Shared test context replaces the per-file counter global; CHECK aliases 37 * CU_CHECK so the call sites are unchanged. This file builds no compiler/ 38 * heap/diag — it only needs the check counters. */ 39 static KitUnit g_u; 40 #define CHECK(cond, ...) CU_CHECK(&g_u, cond, __VA_ARGS__) 41 42 /* Decode a ULEB128 at p, returning value and (via *len_out) byte length. */ 43 static uint64_t decode_uleb128(const uint8_t* p, uint32_t* len_out) { 44 uint64_t v = 0; 45 uint32_t shift = 0; 46 uint32_t n = 0; 47 for (;;) { 48 uint8_t byte = p[n++]; 49 v |= (uint64_t)(byte & 0x7fu) << shift; 50 shift += 7; 51 if (!(byte & 0x80u)) break; 52 } 53 *len_out = n; 54 return v; 55 } 56 57 /* Apply a SET_ULEB128 then SUB_ULEB128 pair at the same offset, exactly 58 * as the linker does: SET writes (S_hi + A_hi); SUB subtracts (S_lo + 59 * A_lo). Net field = (S_hi + A_hi) - (S_lo + A_lo). The original field 60 * width must be preserved. */ 61 static void apply_pair(uint8_t* site, uint64_t s_hi, int64_t a_hi, 62 uint64_t s_lo, int64_t a_lo) { 63 link_reloc_apply(NULL, R_SET_ULEB128, site, s_hi, a_hi, 0); 64 link_reloc_apply(NULL, R_SUB_ULEB128, site, s_lo, a_lo, 0); 65 } 66 67 /* Verify the field at site decodes to want_val and occupies exactly 68 * want_width bytes, and that bytes beyond the field are untouched. */ 69 static void expect_field(const char* label, const uint8_t* site, 70 uint64_t want_val, uint32_t want_width, uint8_t guard, 71 uint8_t actual_guard) { 72 uint32_t got_width = 0; 73 uint64_t got_val = decode_uleb128(site, &got_width); 74 CHECK(got_val == want_val, "%s: value got 0x%llx want 0x%llx", label, 75 (unsigned long long)got_val, (unsigned long long)want_val); 76 CHECK(got_width == want_width, "%s: width got %u want %u (layout shift!)", 77 label, got_width, want_width); 78 CHECK(actual_guard == guard, 79 "%s: trailing guard byte clobbered: got 0x%02x want 0x%02x", label, 80 actual_guard, guard); 81 } 82 83 int main(void) { 84 kit_unit_init(&g_u); 85 86 /* ---- Case 1: real-object fixtures (1-byte fields) ---- */ 87 { 88 /* off 0x12 of .debug_rnglists: pre-filled assembler value 0x18, a 89 * trailing guard byte follows (0x03 in the real section). SET 0x18, 90 * SUB 0x00 -> 0x18, still 1 byte. */ 91 uint8_t buf[2] = {0x18, 0x03}; 92 apply_pair(buf, /*hi*/ 0x18, 0, /*lo*/ 0x00, 0); 93 expect_field("rnglists@0x12", buf, 0x18u, 1u, 0x03, buf[1]); 94 } 95 { 96 /* off 0x15: pre-filled 0x20; SET 0x20, SUB 0x18 -> 0x08, 1 byte. */ 97 uint8_t buf[2] = {0x20, 0x00}; 98 apply_pair(buf, /*hi*/ 0x20, 0, /*lo*/ 0x18, 0); 99 expect_field("rnglists@0x15", buf, 0x08u, 1u, 0x00, buf[1]); 100 } 101 102 /* ---- Case 2: addends fold into S+A ---- */ 103 { 104 /* (S_hi + A_hi) - (S_lo + A_lo) = (0x10+4) - (0x08-2) = 0x14 - 6 = 0x0e. */ 105 uint8_t buf[2] = {0x00, 0xee}; 106 apply_pair(buf, 0x10, 4, 0x08, -2); 107 expect_field("addend-fold", buf, 0x0eu, 1u, 0xee, buf[1]); 108 } 109 110 /* ---- Case 3: fixed-width padding — a value that NATURALLY needs 1 111 * byte must be re-encoded into a reserved 2-byte field via a redundant 112 * continuation group, so the layout never shifts. ---- */ 113 { 114 /* Reserved field is 2 bytes (0x80,0x00 = redundant encoding of 0). 115 * SET 0x05, SUB 0x00 -> 0x05, but must STAY 2 bytes wide. */ 116 uint8_t buf[3] = {0x80, 0x00, 0x77}; 117 apply_pair(buf, 0x05, 0, 0x00, 0); 118 expect_field("pad-1-into-2", buf, 0x05u, 2u, 0x77, buf[2]); 119 /* The encoding must be {0x85, 0x00}: low group 0x05 + cont bit, then 120 * terminating 0x00. */ 121 CHECK(buf[0] == 0x85 && buf[1] == 0x00, 122 "pad-1-into-2: bytes got {0x%02x,0x%02x} want {0x85,0x00}", buf[0], 123 buf[1]); 124 } 125 126 /* ---- Case 4: genuine multi-byte value round-trips ---- */ 127 { 128 /* A 2-byte field reserved (0x80,0x00). Difference 0x100 = 256 needs 129 * two ULEB groups: 0x80 (low 7 = 0, cont) then 0x02. Width stays 2. */ 130 uint8_t buf[3] = {0x80, 0x00, 0x5a}; 131 apply_pair(buf, 0x100, 0, 0x00, 0); 132 expect_field("multibyte-0x100", buf, 0x100u, 2u, 0x5a, buf[2]); 133 CHECK(buf[0] == 0x80 && buf[1] == 0x02, 134 "multibyte-0x100: bytes got {0x%02x,0x%02x} want {0x80,0x02}", buf[0], 135 buf[1]); 136 } 137 138 /* ---- Case 5: 3-byte reserved field, value 0x3fff -> {0xff,0xff,0x00}. 139 * Naturally 0x3fff is 2 bytes; padding to 3 appends a redundant 0. ---- */ 140 { 141 uint8_t buf[4] = {0x80, 0x80, 0x00, 0xc3}; 142 apply_pair(buf, 0x3fff, 0, 0x00, 0); 143 expect_field("pad-2-into-3", buf, 0x3fffu, 3u, 0xc3, buf[3]); 144 CHECK( 145 buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0x00, 146 "pad-2-into-3: bytes got {0x%02x,0x%02x,0x%02x} want {0xff,0xff,0x00}", 147 buf[0], buf[1], buf[2]); 148 } 149 150 /* ---- Case 6: standalone SET then SUB-to-zero leaves field == SET. ---- */ 151 { 152 uint8_t buf[2] = {0x00, 0x9d}; 153 link_reloc_apply(NULL, R_SET_ULEB128, buf, 0x2a, 0, 0); 154 expect_field("set-only", buf, 0x2au, 1u, 0x9d, buf[1]); 155 } 156 157 if (g_u.fails) { 158 fprintf(stderr, "reloc_uleb128_unit: %d failure(s)\n", g_u.fails); 159 return 1; 160 } 161 fputs("reloc_uleb128_unit: OK\n", stderr); 162 return 0; 163 }