kit

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

pe-mixed-archive.c (12501B)


      1 /* Mixed-member archive ingestion test.
      2  *
      3  * Verifies that a single archive containing BOTH a short-import member
      4  * and a long-form COFF object with a real defined symbol satisfies
      5  * references from both shapes in one pass — the same composition
      6  * llvm-mingw's libucrt.a uses (api-ms-win-crt-*.dll short imports
      7  * alongside lib64_libucrt_extra_a-*.o helpers).
      8  *
      9  * Composition:
     10  *   Member A: short-import record (Sig1=0/Sig2=0xFFFF) for `ImportedFn`
     11  *             living in `FOO.dll`.
     12  *   Member B: a normal COFF object (emit_coff'd from a tiny ObjBuilder)
     13  *             defining `g_helper_value` in `.data`.
     14  *
     15  * The program references both via R_PC32 from .text.  After link_resolve
     16  * we assert:
     17  *   - ImportedFn surfaces as imported (dso_input_id != 0).
     18  *   - g_helper_value resolves to a real defined LinkSym.
     19  * No external tools required. */
     20 
     21 #include <kit/archive.h>
     22 #include <kit/core.h>
     23 #include <kit/link.h>
     24 #include <kit/object.h>
     25 #include <setjmp.h>
     26 #include <stdarg.h>
     27 #include <stdio.h>
     28 #include <stdlib.h>
     29 #include <string.h>
     30 
     31 #include "core/core.h"
     32 #include "core/pool.h"
     33 #include "link/link.h"
     34 #include "obj/obj.h"
     35 
     36 /* ---- short-import wire constants (mirror pe-import-smoke.c). ---- */
     37 #define SHIM_HEADER_SIZE 20u
     38 #define SHIM_SYM_CSTR "ImportedFn"
     39 #define SHIM_DLL_CSTR "FOO.dll"
     40 #define SHIM_SYM_NUL_LEN 11u                                /* "ImportedFn\0" */
     41 #define SHIM_DLL_NUL_LEN 8u                                 /* "FOO.dll\0" */
     42 #define SHIM_DATA_LEN (SHIM_SYM_NUL_LEN + SHIM_DLL_NUL_LEN) /* 19 */
     43 #define SHIM_TOTAL_LEN (SHIM_HEADER_SIZE + SHIM_DATA_LEN)   /* 39 */
     44 #define COFF_MACHINE_AMD64 0x8664u
     45 #define COFF_SHIMP_SIG2 0xFFFFu
     46 #define COFF_SHIMP_TYPEFLAGS 0x0004u /* Type=CODE | NameType=NAME */
     47 
     48 #define HELPER_SYM_CSTR "g_helper_value"
     49 
     50 /* Program text: two `call disp32` instructions plus `ret`. Each call's
     51  * disp32 is patched by the linker via R_PC32 against an undef target. */
     52 static const uint8_t PROG_TEXT_X64[11] = {
     53     0xe8, 0, 0,
     54     0,    0, /* call ImportedFn   */
     55     0xe8, 0, 0,
     56     0,    0, /* call g_helper_value (target treated as PC-rel
     57                 reference; data symbols can be referenced the
     58                 same way for the purposes of this test — the
     59                 linker just resolves the symbol address) */
     60     0xc3,    /* ret               */
     61 };
     62 
     63 /* ---- env vtables --------------------------------------------------- */
     64 
     65 static void* heap_alloc(KitHeap* h, size_t n, size_t a) {
     66   (void)h;
     67   (void)a;
     68   return n ? malloc(n) : NULL;
     69 }
     70 static void* heap_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) {
     71   (void)h;
     72   (void)o;
     73   (void)a;
     74   return realloc(p, n);
     75 }
     76 static void heap_free(KitHeap* h, void* p, size_t n) {
     77   (void)h;
     78   (void)n;
     79   free(p);
     80 }
     81 static KitHeap g_heap = {heap_alloc, heap_realloc, heap_free, NULL};
     82 
     83 static void diag_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc,
     84                       const char* fmt, va_list ap) {
     85   static const char* names[] = {"note", "warning", "error", "fatal"};
     86   (void)s;
     87   (void)loc;
     88   fprintf(stderr, "%s: ", names[k]);
     89   vfprintf(stderr, fmt, ap);
     90   fputc('\n', stderr);
     91 }
     92 static KitDiagSink g_diag = {diag_emit, NULL, 0, 0};
     93 
     94 static int g_failures;
     95 #define EXPECT(cond, ...)                                  \
     96   do {                                                     \
     97     if (!(cond)) {                                         \
     98       fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
     99       fprintf(stderr, __VA_ARGS__);                        \
    100       fputc('\n', stderr);                                 \
    101       g_failures++;                                        \
    102     }                                                      \
    103   } while (0)
    104 
    105 /* ---- compiler / target -------------------------------------------- */
    106 
    107 static KitContext g_ctx;
    108 
    109 static void target_x64_windows(KitTargetSpec* t) {
    110   memset(t, 0, sizeof *t);
    111   t->arch = KIT_ARCH_X86_64;
    112   t->os = KIT_OS_WINDOWS;
    113   t->obj = KIT_OBJ_COFF;
    114   t->ptr_size = 8;
    115   t->ptr_align = 8;
    116   t->big_endian = false;
    117   t->pic = KIT_PIC_PIE;
    118   t->code_model = KIT_CM_SMALL;
    119 }
    120 
    121 static Compiler* make_compiler(const KitTargetSpec* t) {
    122   memset(&g_ctx, 0, sizeof g_ctx);
    123   g_ctx.heap = &g_heap;
    124   g_ctx.diag = &g_diag;
    125   g_ctx.now = -1;
    126   KitTargetOptions opts;
    127   memset(&opts, 0, sizeof opts);
    128   opts.spec = *t;
    129   KitTarget* target = NULL;
    130   if (kit_target_new(&g_ctx, &opts, &target) != KIT_OK || !target) return NULL;
    131   KitCompiler* cc = NULL;
    132   if (kit_compiler_new(target, &g_ctx, &cc) != KIT_OK || !cc) {
    133     kit_target_free(target);
    134     return NULL;
    135   }
    136   return (Compiler*)cc;
    137 }
    138 
    139 static void free_compiler(Compiler* c) {
    140   if (!c) return;
    141   const KitTarget* target = kit_compiler_target((KitCompiler*)c);
    142   kit_compiler_free((KitCompiler*)c);
    143   kit_target_free((KitTarget*)target);
    144 }
    145 
    146 /* ---- builders ----------------------------------------------------- */
    147 
    148 static void build_short_import_amd64(uint8_t buf[SHIM_TOTAL_LEN]) {
    149   memset(buf, 0, SHIM_TOTAL_LEN);
    150   /* Sig1=0, Sig2=0xFFFF. */
    151   buf[2] = (uint8_t)(COFF_SHIMP_SIG2 & 0xFF);
    152   buf[3] = (uint8_t)((COFF_SHIMP_SIG2 >> 8) & 0xFF);
    153   /* Machine. */
    154   buf[6] = (uint8_t)(COFF_MACHINE_AMD64 & 0xFF);
    155   buf[7] = (uint8_t)((COFF_MACHINE_AMD64 >> 8) & 0xFF);
    156   /* SizeOfData. */
    157   buf[12] = (uint8_t)(SHIM_DATA_LEN & 0xFFu);
    158   buf[13] = (uint8_t)((SHIM_DATA_LEN >> 8) & 0xFFu);
    159   /* TypeFlags = CODE | NAME. */
    160   buf[18] = (uint8_t)(COFF_SHIMP_TYPEFLAGS & 0xFF);
    161   buf[19] = (uint8_t)((COFF_SHIMP_TYPEFLAGS >> 8) & 0xFF);
    162   memcpy(buf + SHIM_HEADER_SIZE, SHIM_SYM_CSTR, SHIM_SYM_NUL_LEN);
    163   memcpy(buf + SHIM_HEADER_SIZE + SHIM_SYM_NUL_LEN, SHIM_DLL_CSTR,
    164          SHIM_DLL_NUL_LEN);
    165 }
    166 
    167 /* Build a long-form COFF object that defines `g_helper_value` in .data. */
    168 static uint8_t* build_helper_object(Compiler* c, size_t* len_out) {
    169   ObjBuilder* ob = obj_new(c);
    170   Pool* p = c->global;
    171   Sym data_name = pool_intern_slice(p, SLICE_LIT(".data"));
    172   Sym helper_name = pool_intern_slice(p, SLICE_LIT(HELPER_SYM_CSTR));
    173   ObjSecId data = obj_section(ob, data_name, SEC_DATA, SF_ALLOC | SF_WRITE, 4);
    174   static const uint8_t kHelperBytes[4] = {0x2A, 0x00, 0x00, 0x00};
    175   obj_write(ob, data, kHelperBytes, sizeof kHelperBytes);
    176   obj_symbol(ob, helper_name, SB_GLOBAL, SK_OBJ, data, 0, sizeof kHelperBytes);
    177   obj_finalize(ob);
    178 
    179   KitWriter* w = NULL;
    180   if (kit_writer_mem(&g_heap, &w) != KIT_OK || !w) return NULL;
    181   emit_coff(c, ob, w);
    182   size_t n = 0;
    183   const uint8_t* src = kit_writer_mem_bytes(w, &n);
    184   uint8_t* copy = (uint8_t*)malloc(n);
    185   if (copy && n) memcpy(copy, src, n);
    186   kit_writer_close(w);
    187   *len_out = n;
    188   return copy;
    189 }
    190 
    191 /* Program: .text references both ImportedFn (function) and
    192  * g_helper_value (data) via R_PC32 relocs. */
    193 static ObjBuilder* build_program(Compiler* c) {
    194   ObjBuilder* ob = obj_new(c);
    195   Pool* p = c->global;
    196   Sym text_name = pool_intern_slice(p, SLICE_LIT(".text"));
    197   Sym main_name = pool_intern_slice(p, SLICE_LIT("mainCRTStartup"));
    198   Sym import_name = pool_intern_slice(p, SLICE_LIT(SHIM_SYM_CSTR));
    199   Sym helper_name = pool_intern_slice(p, SLICE_LIT(HELPER_SYM_CSTR));
    200   ObjSecId text = obj_section(ob, text_name, SEC_TEXT, SF_ALLOC | SF_EXEC, 16);
    201   obj_write(ob, text, PROG_TEXT_X64, sizeof PROG_TEXT_X64);
    202   obj_symbol(ob, main_name, SB_GLOBAL, SK_FUNC, text, 0, sizeof PROG_TEXT_X64);
    203   ObjSymId import_sym =
    204       obj_symbol(ob, import_name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
    205   ObjSymId helper_sym =
    206       obj_symbol(ob, helper_name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
    207   obj_reloc(ob, text, 1, R_PC32, import_sym, -4);
    208   obj_reloc(ob, text, 6, R_PC32, helper_sym, -4);
    209   obj_finalize(ob);
    210   return ob;
    211 }
    212 
    213 /* ---- main --------------------------------------------------------- */
    214 
    215 int main(void) {
    216   KitTargetSpec t;
    217   target_x64_windows(&t);
    218   Compiler* c = make_compiler(&t);
    219   if (!c) {
    220     fprintf(stderr, "FAIL: compiler_new\n");
    221     return 1;
    222   }
    223   if (setjmp(c->panic)) {
    224     fprintf(stderr, "FAIL: panic during pe-mixed-archive\n");
    225     compiler_run_cleanups(c);
    226     free_compiler(c);
    227     return 1;
    228   }
    229 
    230   ObjBuilder* prog = build_program(c);
    231 
    232   /* Member A: short-import shim. */
    233   uint8_t shim[SHIM_TOTAL_LEN];
    234   build_short_import_amd64(shim);
    235 
    236   /* Member B: long-form COFF object defining g_helper_value. */
    237   size_t helper_len = 0;
    238   uint8_t* helper_bytes = build_helper_object(c, &helper_len);
    239   EXPECT(helper_bytes != NULL && helper_len > 0,
    240          "build_helper_object produced %zu bytes", helper_len);
    241   if (!helper_bytes) {
    242     free_compiler(c);
    243     return 1;
    244   }
    245 
    246   /* Assemble both into an archive. kit's archive ingestion walks
    247    * every member regardless of the symbol index, so symbol_index=0 is
    248    * sufficient — the linker rediscovers each member's exports during
    249    * scan_presence_before. */
    250   KitArInput members[2];
    251   members[0].name = KIT_SLICE_LIT("importfn.o");
    252   members[0].bytes.data = shim;
    253   members[0].bytes.len = SHIM_TOTAL_LEN;
    254   members[1].name = KIT_SLICE_LIT("helper.o");
    255   members[1].bytes.data = helper_bytes;
    256   members[1].bytes.len = helper_len;
    257 
    258   KitWriter* aw = NULL;
    259   if (kit_writer_mem(&g_heap, &aw) != KIT_OK || !aw) {
    260     fprintf(stderr, "FAIL: writer_mem for archive\n");
    261     free(helper_bytes);
    262     free_compiler(c);
    263     return 1;
    264   }
    265   KitArWriteOptions opts;
    266   memset(&opts, 0, sizeof opts);
    267   KitStatus arst = kit_ar_write(aw, members, 2, &opts);
    268   EXPECT(arst == KIT_OK, "kit_ar_write rc=%d", (int)arst);
    269 
    270   size_t ar_len = 0;
    271   const uint8_t* ar_view = kit_writer_mem_bytes(aw, &ar_len);
    272   uint8_t* ar_bytes = (uint8_t*)malloc(ar_len);
    273   if (ar_bytes && ar_len) memcpy(ar_bytes, ar_view, ar_len);
    274   kit_writer_close(aw);
    275   EXPECT(ar_bytes != NULL && ar_len > 0,
    276          "archive empty after kit_ar_write (len=%zu)", ar_len);
    277   if (!ar_bytes) {
    278     free(helper_bytes);
    279     free_compiler(c);
    280     return 1;
    281   }
    282 
    283   /* Drive the linker. Name the archive `libmixed.a` so
    284    * derive_dll_name_from_archive_path treats it as a potential import
    285    * archive (archive_dll_name="mixed.dll") — the short-import member
    286    * carries its own DLL name (FOO.dll) and overrides this fallback, and
    287    * the long-form COFF object is classified COFF_AR_KEEP and read as a
    288    * normal object regardless. */
    289   Linker* l = link_new(c);
    290   EXPECT(l != NULL, "link_new returned NULL");
    291   link_add_obj(l, prog);
    292   LinkInputId ar_id = link_add_archive_bytes(l, "libmixed.a", ar_bytes, ar_len,
    293                                              /*whole_archive=*/0,
    294                                              /*link_mode=*/0,
    295                                              /*group_id=*/0);
    296   EXPECT(ar_id != LINK_INPUT_NONE,
    297          "link_add_archive_bytes returned LINK_INPUT_NONE");
    298   link_set_entry(l, KIT_SLICE_LIT("mainCRTStartup"));
    299   link_set_pie(l, 1);
    300   link_set_emit_static_exe(l, 1);
    301 
    302   LinkImage* img = link_resolve(l);
    303   EXPECT(img != NULL, "link_resolve returned NULL");
    304   if (img) {
    305     Sym import_name = pool_intern_slice(c->global, SLICE_LIT(SHIM_SYM_CSTR));
    306     Sym helper_name = pool_intern_slice(c->global, SLICE_LIT(HELPER_SYM_CSTR));
    307     const LinkSymbol* importf = NULL;
    308     const LinkSymbol* helper = NULL;
    309     for (LinkSymId i = 1;; ++i) {
    310       const LinkSymbol* s = link_symbol(img, i);
    311       if (!s) break;
    312       if (s->name == import_name)
    313         importf = s;
    314       else if (s->name == helper_name)
    315         helper = s;
    316     }
    317     EXPECT(importf != NULL,
    318            "ImportedFn LinkSymbol not present after link_resolve");
    319     if (importf) {
    320       EXPECT(importf->imported,
    321              "ImportedFn.imported=0 (expected 1; short-import member "
    322              "should be pulled in and routed as DSO)");
    323       EXPECT(importf->dso_input_id != 0,
    324              "ImportedFn.dso_input_id=0 (expected nonzero — short-import "
    325              "member should be present as a DSO input)");
    326     }
    327     EXPECT(helper != NULL,
    328            "g_helper_value LinkSymbol not present after link_resolve");
    329     if (helper) {
    330       EXPECT(!helper->imported,
    331              "g_helper_value.imported=1 (expected 0; helper.o is a real "
    332              "COFF object, not an import shim)");
    333       EXPECT(helper->section_id != LINK_SEC_NONE,
    334              "g_helper_value.section_id=NONE (expected a real .data "
    335              "section after long-form COFF ingestion)");
    336     }
    337     link_image_free(img);
    338   }
    339   link_free(l);
    340 
    341   free(ar_bytes);
    342   free(helper_bytes);
    343   free_compiler(c);
    344 
    345   if (g_failures) {
    346     fprintf(stderr, "FAILED %d assertion(s)\n", g_failures);
    347     return 1;
    348   }
    349   fprintf(stderr, "OK pe-mixed-archive\n");
    350   return 0;
    351 }