kit

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

pe-dso-forwarder.c (11596B)


      1 /* read_coff_dso forwarder-export contract test.
      2  *
      3  * Synthesizes a minimal PE32+ DLL with two named exports — one direct
      4  * (EAT RVA outside the export directory's range) and one forwarder
      5  * (EAT RVA inside the export directory's range, contents
      6  * "OTHERDLL.OtherSym") — and asserts that read_coff_dso surfaces both
      7  * as OBJ_SEC_NONE globals on the returned ObjBuilder.  kit's linker
      8  * does not follow forwarder chains: the symbols just need to be
      9  * defined so import resolution succeeds, and the OS loader follows
     10  * the chain at runtime.  This test locks in that contract. */
     11 
     12 #include <kit/core.h>
     13 #include <kit/object.h>
     14 #include <setjmp.h>
     15 #include <stdarg.h>
     16 #include <stdio.h>
     17 #include <stdlib.h>
     18 #include <string.h>
     19 
     20 #include "core/core.h"
     21 #include "core/pool.h"
     22 #include "obj/coff/coff.h"
     23 #include "obj/obj.h"
     24 
     25 /* ---- env vtables --------------------------------------------------- */
     26 
     27 static void* heap_alloc(KitHeap* h, size_t n, size_t a) {
     28   (void)h;
     29   (void)a;
     30   return n ? malloc(n) : NULL;
     31 }
     32 static void* heap_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) {
     33   (void)h;
     34   (void)o;
     35   (void)a;
     36   return realloc(p, n);
     37 }
     38 static void heap_free(KitHeap* h, void* p, size_t n) {
     39   (void)h;
     40   (void)n;
     41   free(p);
     42 }
     43 static KitHeap g_heap = {heap_alloc, heap_realloc, heap_free, NULL};
     44 
     45 static void diag_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc,
     46                       const char* fmt, va_list ap) {
     47   static const char* names[] = {"note", "warning", "error", "fatal"};
     48   (void)s;
     49   (void)loc;
     50   fprintf(stderr, "%s: ", names[k]);
     51   vfprintf(stderr, fmt, ap);
     52   fputc('\n', stderr);
     53 }
     54 static KitDiagSink g_diag = {diag_emit, NULL, 0, 0};
     55 
     56 static int g_failures;
     57 #define EXPECT(cond, ...)                                  \
     58   do {                                                     \
     59     if (!(cond)) {                                         \
     60       fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
     61       fprintf(stderr, __VA_ARGS__);                        \
     62       fputc('\n', stderr);                                 \
     63       g_failures++;                                        \
     64     }                                                      \
     65   } while (0)
     66 
     67 /* ---- compiler ----------------------------------------------------- */
     68 
     69 static KitContext g_ctx;
     70 
     71 static void target_x64_windows(KitTargetSpec* t) {
     72   memset(t, 0, sizeof *t);
     73   t->arch = KIT_ARCH_X86_64;
     74   t->os = KIT_OS_WINDOWS;
     75   t->obj = KIT_OBJ_COFF;
     76   t->ptr_size = 8;
     77   t->ptr_align = 8;
     78   t->big_endian = false;
     79   t->pic = KIT_PIC_PIE;
     80   t->code_model = KIT_CM_SMALL;
     81 }
     82 
     83 static Compiler* make_compiler(const KitTargetSpec* t) {
     84   memset(&g_ctx, 0, sizeof g_ctx);
     85   g_ctx.heap = &g_heap;
     86   g_ctx.diag = &g_diag;
     87   g_ctx.now = -1;
     88   KitTargetOptions opts;
     89   memset(&opts, 0, sizeof opts);
     90   opts.spec = *t;
     91   KitTarget* target = NULL;
     92   if (kit_target_new(&g_ctx, &opts, &target) != KIT_OK || !target) return NULL;
     93   KitCompiler* cc = NULL;
     94   if (kit_compiler_new(target, &g_ctx, &cc) != KIT_OK || !cc) {
     95     kit_target_free(target);
     96     return NULL;
     97   }
     98   return (Compiler*)cc;
     99 }
    100 
    101 static void free_compiler(Compiler* c) {
    102   if (!c) return;
    103   const KitTarget* target = kit_compiler_target((KitCompiler*)c);
    104   kit_compiler_free((KitCompiler*)c);
    105   kit_target_free((KitTarget*)target);
    106 }
    107 
    108 /* ---- little-endian writers ---------------------------------------- */
    109 
    110 static void wr_u16(uint8_t* p, uint16_t v) {
    111   p[0] = (uint8_t)(v & 0xFF);
    112   p[1] = (uint8_t)((v >> 8) & 0xFF);
    113 }
    114 static void wr_u32(uint8_t* p, uint32_t v) {
    115   p[0] = (uint8_t)(v & 0xFF);
    116   p[1] = (uint8_t)((v >> 8) & 0xFF);
    117   p[2] = (uint8_t)((v >> 16) & 0xFF);
    118   p[3] = (uint8_t)((v >> 24) & 0xFF);
    119 }
    120 
    121 /* ---- synthetic PE32+ DLL builder ---------------------------------- */
    122 
    123 /* Layout (file offsets):
    124  *   0x000 .. 0x03F   DOS header (e_lfanew = 0x40)
    125  *   0x040 .. 0x043   "PE\0\0"
    126  *   0x044 .. 0x057   IMAGE_FILE_HEADER (20 bytes)
    127  *   0x058 .. 0x147   IMAGE_OPTIONAL_HEADER64 (240 bytes)
    128  *   0x148 .. 0x16F   one IMAGE_SECTION_HEADER (40 bytes)
    129  *   0x170 .. 0x36F   section raw data (RVA 0x1000, 0x200 bytes)
    130  *
    131  * The single section ".edata" at RVA 0x1000 carries the export
    132  * directory plus its tables and strings.  The export DataDirectory
    133  * record points at the start of that section and covers everything
    134  * including the forwarder target string so the reader classifies
    135  * "OTHERDLL.OtherSym" EAT entries as forwarders. */
    136 
    137 #define E_LFANEW 0x40u
    138 #define FH_OFF (E_LFANEW + 4u)
    139 #define OH_OFF (FH_OFF + COFF_FILE_HEADER_SIZE)
    140 #define SH_OFF (OH_OFF + COFF_OPT_HDR64_SIZE)
    141 #define RAW_OFF 0x170u
    142 #define SEC_VA 0x1000u
    143 #define SEC_RAW_SZ 0x200u
    144 #define FILE_SIZE (RAW_OFF + SEC_RAW_SZ)
    145 
    146 /* In-section offsets (relative to RAW_OFF / RVA = SEC_VA + off). */
    147 #define EXP_DIR_OFF 0u
    148 #define EAT_OFF (EXP_DIR_OFF + COFF_EXPORT_DIR_SIZE) /* +40 */
    149 #define EAT_COUNT 2u
    150 #define ENT_OFF (EAT_OFF + EAT_COUNT * 4u) /* +48 */
    151 #define ENT_COUNT 2u
    152 #define ORD_OFF (ENT_OFF + ENT_COUNT * 4u)     /* +56 */
    153 #define DLLNAME_OFF (ORD_OFF + ENT_COUNT * 2u) /* +60 */
    154 
    155 static const char kDllName[] = "TestDll.dll";
    156 static const char kDirect[] = "DirectFn";
    157 static const char kForwarded[] = "ForwardedFn";
    158 static const char kForwardTarget[] = "OTHERDLL.OtherSym";
    159 
    160 #define DIRECT_NAME_OFF (DLLNAME_OFF + (uint32_t)sizeof kDllName)
    161 #define FORWARDED_NAME_OFF (DIRECT_NAME_OFF + (uint32_t)sizeof kDirect)
    162 #define FORWARD_TGT_OFF (FORWARDED_NAME_OFF + (uint32_t)sizeof kForwarded)
    163 #define EXP_DIR_END (FORWARD_TGT_OFF + (uint32_t)sizeof kForwardTarget)
    164 
    165 /* Some RVA outside the export directory range — interpreted as a
    166  * direct export pointing into the (notional) code section. */
    167 #define DIRECT_FN_RVA 0x2000u
    168 
    169 static void build_dso(uint8_t* buf) {
    170   memset(buf, 0, FILE_SIZE);
    171 
    172   /* DOS header. */
    173   wr_u16(buf + 0, IMAGE_DOS_SIGNATURE);
    174   wr_u32(buf + 60, E_LFANEW);
    175 
    176   /* PE signature. */
    177   wr_u32(buf + E_LFANEW, IMAGE_NT_SIGNATURE);
    178 
    179   /* IMAGE_FILE_HEADER. */
    180   wr_u16(buf + FH_OFF + 0, IMAGE_FILE_MACHINE_AMD64);
    181   wr_u16(buf + FH_OFF + 2, 1);  /* NumberOfSections */
    182   wr_u32(buf + FH_OFF + 4, 0);  /* TimeDateStamp */
    183   wr_u32(buf + FH_OFF + 8, 0);  /* PointerToSymbolTable */
    184   wr_u32(buf + FH_OFF + 12, 0); /* NumberOfSymbols */
    185   wr_u16(buf + FH_OFF + 16, COFF_OPT_HDR64_SIZE);
    186   wr_u16(buf + FH_OFF + 18, IMAGE_FILE_DLL);
    187 
    188   /* IMAGE_OPTIONAL_HEADER64. Only the fields the reader inspects
    189    * matter: Magic, and the export DataDirectory at index 0. */
    190   wr_u16(buf + OH_OFF + 0, IMAGE_NT_OPTIONAL_HDR64_MAGIC);
    191   /* Data directories live at the tail of the optional header. */
    192   uint32_t dd_off = OH_OFF + COFF_OPT_HDR64_SIZE -
    193                     COFF_NUM_DATA_DIRECTORIES * COFF_DATA_DIRECTORY_SIZE;
    194   uint32_t exp_rva = SEC_VA + EXP_DIR_OFF;
    195   uint32_t exp_size = EXP_DIR_END;
    196   wr_u32(buf + dd_off + IMAGE_DIRECTORY_ENTRY_EXPORT * 8u + 0, exp_rva);
    197   wr_u32(buf + dd_off + IMAGE_DIRECTORY_ENTRY_EXPORT * 8u + 4, exp_size);
    198 
    199   /* One section header: ".edata". */
    200   memcpy(buf + SH_OFF + 0, ".edata\0\0", 8);
    201   wr_u32(buf + SH_OFF + 8, exp_size);     /* VirtualSize */
    202   wr_u32(buf + SH_OFF + 12, SEC_VA);      /* VirtualAddress */
    203   wr_u32(buf + SH_OFF + 16, SEC_RAW_SZ);  /* SizeOfRawData */
    204   wr_u32(buf + SH_OFF + 20, RAW_OFF);     /* PointerToRawData */
    205   wr_u32(buf + SH_OFF + 24, 0);           /* PtrToRelocations */
    206   wr_u32(buf + SH_OFF + 28, 0);           /* PtrToLinenumbers */
    207   wr_u16(buf + SH_OFF + 32, 0);           /* NumberOfRelocations */
    208   wr_u16(buf + SH_OFF + 34, 0);           /* NumberOfLinenumbers */
    209   wr_u32(buf + SH_OFF + 36, 0x40000040u); /* Characteristics:
    210                                              INITIALIZED_DATA |
    211                                              MEM_READ */
    212 
    213   /* Section raw data — written via RAW_OFF + off. */
    214   uint8_t* sec = buf + RAW_OFF;
    215 
    216   /* Export Directory header. */
    217   wr_u32(sec + EXP_DIR_OFF + 0, 0);                     /* Characteristics */
    218   wr_u32(sec + EXP_DIR_OFF + 4, 0);                     /* TimeDateStamp */
    219   wr_u16(sec + EXP_DIR_OFF + 8, 0);                     /* MajorVersion */
    220   wr_u16(sec + EXP_DIR_OFF + 10, 0);                    /* MinorVersion */
    221   wr_u32(sec + EXP_DIR_OFF + 12, SEC_VA + DLLNAME_OFF); /* Name */
    222   wr_u32(sec + EXP_DIR_OFF + 16, 1);                    /* Base */
    223   wr_u32(sec + EXP_DIR_OFF + 20, EAT_COUNT);            /* NumberOfFunctions */
    224   wr_u32(sec + EXP_DIR_OFF + 24, ENT_COUNT);            /* NumberOfNames */
    225   wr_u32(sec + EXP_DIR_OFF + 28, SEC_VA + EAT_OFF);     /* AddressOfFunctions */
    226   wr_u32(sec + EXP_DIR_OFF + 32, SEC_VA + ENT_OFF);     /* AddressOfNames */
    227   wr_u32(sec + EXP_DIR_OFF + 36, SEC_VA + ORD_OFF);     /* AddressOfNameOrds */
    228 
    229   /* EAT: index 0 = direct (outside export-dir range);
    230    *      index 1 = forwarder (inside export-dir range, pointing at
    231    *      the OTHERDLL.OtherSym string). */
    232   wr_u32(sec + EAT_OFF + 0u, DIRECT_FN_RVA);
    233   wr_u32(sec + EAT_OFF + 4u, SEC_VA + FORWARD_TGT_OFF);
    234 
    235   /* ENT: RVAs of the two name strings, in alphabetical-ish order.
    236    * The reader walks ENT[i] -> Ord[i] -> EAT[Ord[i]]. */
    237   wr_u32(sec + ENT_OFF + 0u, SEC_VA + DIRECT_NAME_OFF);
    238   wr_u32(sec + ENT_OFF + 4u, SEC_VA + FORWARDED_NAME_OFF);
    239 
    240   /* Ordinal table: index into the EAT. */
    241   wr_u16(sec + ORD_OFF + 0u, 0);
    242   wr_u16(sec + ORD_OFF + 2u, 1);
    243 
    244   /* Strings. */
    245   memcpy(sec + DLLNAME_OFF, kDllName, sizeof kDllName);
    246   memcpy(sec + DIRECT_NAME_OFF, kDirect, sizeof kDirect);
    247   memcpy(sec + FORWARDED_NAME_OFF, kForwarded, sizeof kForwarded);
    248   memcpy(sec + FORWARD_TGT_OFF, kForwardTarget, sizeof kForwardTarget);
    249 }
    250 
    251 /* ---- main --------------------------------------------------------- */
    252 
    253 static int has_sym(const ObjBuilder* ob, Pool* p, const char* name) {
    254   Sym needle = pool_intern_slice(p, slice_from_cstr(name));
    255   ObjSymIter* it = obj_symiter_new(ob);
    256   ObjSymEntry e;
    257   int found = 0;
    258   while (obj_symiter_next(it, &e)) {
    259     if (e.sym && !e.sym->removed && e.sym->name == needle &&
    260         e.sym->section_id == OBJ_SEC_NONE && e.sym->bind == SB_GLOBAL) {
    261       found = 1;
    262       break;
    263     }
    264   }
    265   obj_symiter_free(it);
    266   return found;
    267 }
    268 
    269 int main(void) {
    270   KitTargetSpec t;
    271   target_x64_windows(&t);
    272   Compiler* c = make_compiler(&t);
    273   if (!c) {
    274     fprintf(stderr, "FAIL: compiler_new\n");
    275     return 1;
    276   }
    277   if (setjmp(c->panic)) {
    278     fprintf(stderr, "FAIL: panic during pe-dso-forwarder\n");
    279     compiler_run_cleanups(c);
    280     free_compiler(c);
    281     return 1;
    282   }
    283 
    284   uint8_t* buf = (uint8_t*)malloc(FILE_SIZE);
    285   EXPECT(buf != NULL, "malloc PE buffer");
    286   if (!buf) {
    287     free_compiler(c);
    288     return 1;
    289   }
    290   build_dso(buf);
    291 
    292   Sym soname = 0;
    293   ObjBuilder* ob = read_coff_dso(c, "TestDll.dll", buf, FILE_SIZE, &soname);
    294   EXPECT(ob != NULL, "read_coff_dso returned NULL");
    295 
    296   /* soname propagated from the Export Directory's Name field. */
    297   Sym expected_soname = pool_intern_slice(c->global, slice_from_cstr(kDllName));
    298   EXPECT(soname == expected_soname,
    299          "soname mismatch (expected interned \"%s\")", kDllName);
    300 
    301   /* Both exports must surface — direct and forwarder treated the same
    302    * way by read_coff_dso (the OS loader chases the forwarder chain at
    303    * runtime; the linker just needs the name defined). */
    304   EXPECT(has_sym(ob, c->global, kDirect),
    305          "direct export \"%s\" missing from ObjBuilder", kDirect);
    306   EXPECT(has_sym(ob, c->global, kForwarded),
    307          "forwarded export \"%s\" missing from ObjBuilder", kForwarded);
    308 
    309   free(buf);
    310   free_compiler(c);
    311 
    312   if (g_failures) {
    313     fprintf(stderr, "FAILED %d assertion(s)\n", g_failures);
    314     return 1;
    315   }
    316   fprintf(stderr, "OK pe-dso-forwarder\n");
    317   return 0;
    318 }