kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }