// Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) internal String8 pe_name_from_export_parse(PE_ExportParse *exp) { String8 name; if (exp->is_forwarder) { name = exp->alias; } else if (exp->alias.size) { name = exp->alias; } else { name = exp->name; } return name; } internal U16 pe_hint_or_ordinal_from_export_parse(PE_ExportParse *exp) { U16 hint_or_ordinal = max_U16; if (exp->import_by == COFF_ImportBy_Ordinal) { hint_or_ordinal = exp->ordinal; } else if (exp->import_by == COFF_ImportBy_Name) { hint_or_ordinal = exp->hint; } else { NotImplemented; } return hint_or_ordinal; } internal PE_ExportParsePtrArray pe_array_from_export_list(Arena *arena, PE_ExportParseList list) { PE_ExportParsePtrArray result = {0}; result.v = push_array_no_zero(arena, PE_ExportParse *, list.count); for (PE_ExportParseNode *exp = list.first; exp != 0; exp = exp->next) { result.v[result.count++] = &exp->data; } return result; } internal void pe_export_parse_list_push_node(PE_ExportParseList *list, PE_ExportParseNode *node) { SLLQueuePush(list->first, list->last, node); list->count += 1; } internal PE_ExportParseNode * pe_export_parse_list_push(Arena *arena, PE_ExportParseList *list, PE_ExportParse data) { PE_ExportParseNode *node = push_array(arena, PE_ExportParseNode, 1); node->data = data; pe_export_parse_list_push_node(list, node); return node; } internal void pe_export_parse_list_concat_in_place(PE_ExportParseList *list, PE_ExportParseList *to_concat) { if (to_concat->count) { if (list->count) { list->last->next = to_concat->first; list->last = to_concat->last; } else { list->first = to_concat->first; list->last = to_concat->last; } list->count += to_concat->count; MemoryZeroStruct(to_concat); } } internal int pe_named_export_is_before(void *raw_a, void *raw_b) { PE_ExportParse *a = *(PE_ExportParse **)raw_a; PE_ExportParse *b = *(PE_ExportParse **)raw_b; int cmp = str8_compar_case_sensitive(&a->name, &b->name); return cmp < 0; } internal int pe_ordinal_export_is_before(void *raw_a, void *raw_b) { PE_ExportParse *a = raw_a; PE_ExportParse *b = raw_b; return a->ordinal < b->ordinal; } internal PE_FinalizedExports pe_finalize_export_list(Arena *arena, PE_ExportParseList export_list) { PE_ExportParsePtrArray named_exports = {0}; PE_ExportParsePtrArray ordinal_exports = {0}; PE_ExportParsePtrArray forwarder_exports = {0}; { // group exports based on flags PE_ExportParseList named_exports_list = {0}; PE_ExportParseList ordinal_exports_list = {0}; PE_ExportParseList forwarder_exports_list = {0}; for (PE_ExportParseNode *exp_n = export_list.first, *exp_n_next; exp_n != 0; exp_n = exp_n_next) { exp_n_next = exp_n->next; if (exp_n->data.is_forwarder) { pe_export_parse_list_push_node(&forwarder_exports_list, exp_n); } else if (exp_n->data.is_noname_present) { AssertAlways(exp_n->data.is_ordinal_assigned); pe_export_parse_list_push_node(&ordinal_exports_list, exp_n); } else { pe_export_parse_list_push_node(&named_exports_list, exp_n); } } // list -> array named_exports = pe_array_from_export_list(arena, named_exports_list); forwarder_exports = pe_array_from_export_list(arena, forwarder_exports_list); ordinal_exports = pe_array_from_export_list(arena, ordinal_exports_list); // sort exports radsort(named_exports.v, named_exports.count, pe_named_export_is_before); radsort(ordinal_exports.v, ordinal_exports.count, pe_ordinal_export_is_before); radsort(forwarder_exports.v, forwarder_exports.count, pe_named_export_is_before); MemoryZeroStruct(&export_list); pe_export_parse_list_concat_in_place(&export_list, &named_exports_list); pe_export_parse_list_concat_in_place(&export_list, &forwarder_exports_list); pe_export_parse_list_concat_in_place(&export_list, &ordinal_exports_list); } // compute max ordinal and used ordinal flag array U64 ordinal_low = max_U64; B8 *is_ordinal_used = push_array(arena, B8, max_U16); for (PE_ExportParseNode *exp_n = export_list.first; exp_n != 0; exp_n = exp_n->next) { PE_ExportParse *exp = &exp_n->data; if (exp->is_ordinal_assigned) { ordinal_low = Min(ordinal_low, exp->ordinal); is_ordinal_used[exp->ordinal] = 1; } } if (ordinal_low == max_U64) { ordinal_low = 1; } // assign omitted ordinals { U16 last_ordinal = ordinal_low; for (U64 exp_idx = 0; exp_idx < named_exports.count; exp_idx += 1) { PE_ExportParse *exp = named_exports.v[exp_idx]; if (!exp->is_ordinal_assigned) { for (; last_ordinal < max_U16 && is_ordinal_used[last_ordinal] != 0; last_ordinal += 1); exp->ordinal = last_ordinal; exp->is_ordinal_assigned = 1; is_ordinal_used[last_ordinal] = 1; } } for (U64 exp_idx = 0; exp_idx < forwarder_exports.count; exp_idx += 1) { PE_ExportParse *exp = forwarder_exports.v[exp_idx]; if (!exp->is_ordinal_assigned) { for (; last_ordinal < max_U16 && is_ordinal_used[last_ordinal] != 0; last_ordinal += 1); exp->ordinal = last_ordinal; exp->is_ordinal_assigned = 1; is_ordinal_used[last_ordinal] = 1; } } for (U64 exp_idx = 0; exp_idx < ordinal_exports.count; exp_idx += 1) { PE_ExportParse *exp = ordinal_exports.v[exp_idx]; if (!exp->is_ordinal_assigned) { exp->ordinal = last_ordinal; exp->is_ordinal_assigned = 1; is_ordinal_used[last_ordinal] = 1; } } } // assign hints { U64 hint = 0; for (U64 exp_idx = 0; exp_idx < named_exports.count; exp_idx += 1, hint += 1) { named_exports.v[exp_idx]->hint = hint; } for (U64 exp_idx = 0; exp_idx < forwarder_exports.count; exp_idx += 1, hint += 1) { forwarder_exports.v[exp_idx]->hint = hint; } } PE_FinalizedExports result = {0}; result.ordinal_low = ordinal_low; result.named_exports = named_exports; result.forwarder_exports = forwarder_exports; result.ordinal_exports = ordinal_exports; return result; } internal String8 pe_make_edata_obj(Arena *arena, String8 image_name, COFF_TimeStamp time_stamp, COFF_MachineType machine, PE_FinalizedExports finalized_exports) { Temp scratch = scratch_begin(&arena, 1); COFF_ObjWriter *obj_writer = coff_obj_writer_alloc(time_stamp, machine); // push sections COFF_ObjSection *voff_table_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".edata$2"), PE_EDATA_SECTION_FLAGS|COFF_SectionFlag_Align4Bytes, str8_zero()); COFF_ObjSection *name_voff_table_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".edata$3"), PE_EDATA_SECTION_FLAGS|COFF_SectionFlag_Align4Bytes, str8_zero()); COFF_ObjSection *ordinal_table_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".edata$4"), PE_EDATA_SECTION_FLAGS|COFF_SectionFlag_Align2Bytes, str8_zero()); COFF_ObjSection *string_table_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".edata$5"), PE_EDATA_SECTION_FLAGS|COFF_SectionFlag_Align1Bytes, str8_zero()); COFF_ObjSection *image_name_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".edata$6"), PE_EDATA_SECTION_FLAGS|COFF_SectionFlag_Align1Bytes, push_cstr(obj_writer->arena, image_name)); ProfBegin("Virtual Offset Table"); { B8 *is_ordinal_bound = push_array(scratch.arena, B8, max_U16); for (U64 arr_idx = 0; arr_idx < ArrayCount(finalized_exports.all); arr_idx += 1) { for (U64 exp_idx = 0; exp_idx < finalized_exports.all[arr_idx].count; exp_idx += 1) { PE_ExportParse *exp = finalized_exports.all[arr_idx].v[exp_idx]; if (is_ordinal_bound[exp->ordinal] == 0) { // alloc only one slot per ordinal, so it's possible to map ordinal to a virtual offset is_ordinal_bound[exp->ordinal] = 1; // create slot for the ordinal virtual offset U64 voff_offset = voff_table_sect->data.total_size; U32 *voff = push_array(obj_writer->arena, U32, 1); str8_list_push(obj_writer->arena, &voff_table_sect->data, str8_struct(voff)); COFF_ObjSymbol *exp_symbol; if (exp->is_forwarder) { U64 forwarder_name_offset = string_table_sect->data.total_size; String8 forwarder_name_cstr = push_cstr(obj_writer->arena, exp->name); str8_list_push(obj_writer->arena, &string_table_sect->data, forwarder_name_cstr); // symbol to the name string exp_symbol = coff_obj_writer_push_symbol_static(obj_writer, exp->name, forwarder_name_offset, string_table_sect); } else { // function or global var symbol exp_symbol = coff_obj_writer_push_symbol_undef(obj_writer, exp->name); } U16 ordinal_nb = exp->ordinal - finalized_exports.ordinal_low; coff_obj_writer_section_push_reloc_voff(obj_writer, voff_table_sect, ordinal_nb*sizeof(U32), exp_symbol); } } } } ProfEnd(); ProfBegin("Named & Forwarder Exports"); { for (U64 arr_idx = 0; arr_idx < ArrayCount(finalized_exports.exports_with_names); arr_idx += 1) { PE_ExportParsePtrArray exports = finalized_exports.exports_with_names[arr_idx]; for (U64 exp_idx = 0; exp_idx < exports.count; exp_idx += 1) { PE_ExportParse *exp = exports.v[exp_idx]; String8 name = pe_name_from_export_parse(exp); // store symbol name string U64 export_name_offset = string_table_sect->data.total_size; String8 export_name_cstr = push_cstr(obj_writer->arena, name); str8_list_push(obj_writer->arena, &string_table_sect->data, export_name_cstr); // create symbol for the name string String8 export_name_symbol_name = push_str8f(obj_writer->arena, "%S", name); COFF_ObjSymbol *export_name_symbol = coff_obj_writer_push_symbol_static(obj_writer, export_name_symbol_name, export_name_offset, string_table_sect); // create slot for export virtual offset U64 export_name_voff_offset = name_voff_table_sect->data.total_size; U8 *export_name_voff = push_array(obj_writer->arena, U8, sizeof(U32)); str8_list_push(obj_writer->arena, &name_voff_table_sect->data, str8_array(export_name_voff, sizeof(U32))); // write string's virtual offset coff_obj_writer_section_push_reloc_voff(obj_writer, name_voff_table_sect, export_name_voff_offset, export_name_symbol); // create and store export's ordinal U16 *ordinal = push_array(obj_writer->arena, U16, 1); *ordinal = exp->ordinal - finalized_exports.ordinal_low; str8_list_push(obj_writer->arena, &ordinal_table_sect->data, str8_struct(ordinal)); } } } ProfEnd(); ProfBegin("Ordinal Exports"); { for (U64 exp_idx = 0; exp_idx < finalized_exports.ordinal_exports.count; exp_idx += 1) { // create and store export's ordinal PE_ExportParse *exp = finalized_exports.ordinal_exports.v[exp_idx]; U16 *ordinal = push_array(obj_writer->arena, U16, 1); *ordinal = exp->ordinal - finalized_exports.ordinal_low; str8_list_push(obj_writer->arena, &ordinal_table_sect->data, str8_struct(ordinal)); } } ProfEnd(); // fill out export table header PE_ExportTableHeader *header = push_array(obj_writer->arena, PE_ExportTableHeader, 1); header->time_stamp = time_stamp; header->ordinal_base = safe_cast_u16(finalized_exports.ordinal_low); header->export_address_table_count = safe_cast_u32(voff_table_sect->data.node_count); header->name_pointer_table_count = safe_cast_u32(name_voff_table_sect->data.node_count); // push header field's symbols COFF_ObjSymbol *image_name_symbol = coff_obj_writer_push_symbol_static(obj_writer, str8_lit("EXPORT_HEADER_NAME_VOFF"), 0, image_name_sect); COFF_ObjSymbol *address_table_symbol = coff_obj_writer_push_symbol_static(obj_writer, str8_lit("EXPORT_HEADER_ADDRESS_TABLE_VOFF"), 0, voff_table_sect); COFF_ObjSymbol *name_table_symbol = coff_obj_writer_push_symbol_static(obj_writer, str8_lit("EXPORT_HEADER_NAME_POINTER_VOFF"), 0, name_voff_table_sect); COFF_ObjSymbol *ordinal_table_symbol = coff_obj_writer_push_symbol_static(obj_writer, str8_lit("EXPORT_HEADER_ORDINAL_TABLE_VOFF"), 0, ordinal_table_sect); // push export table header section COFF_ObjSection *header_sect = coff_obj_writer_push_section(obj_writer, str8_lit(".edata$1"), PE_EDATA_SECTION_FLAGS|COFF_SectionFlag_Align1Bytes, str8_struct(header)); coff_obj_writer_push_symbol_static(obj_writer, str8_lit("EXPORT_TABLE_HEADER"), 0, header_sect); // patch export table header coff_obj_writer_section_push_reloc_voff(obj_writer, header_sect, OffsetOf(PE_ExportTableHeader, name_voff), image_name_symbol); coff_obj_writer_section_push_reloc_voff(obj_writer, header_sect, OffsetOf(PE_ExportTableHeader, export_address_table_voff), address_table_symbol); coff_obj_writer_section_push_reloc_voff(obj_writer, header_sect, OffsetOf(PE_ExportTableHeader, name_pointer_table_voff), name_table_symbol); coff_obj_writer_section_push_reloc_voff(obj_writer, header_sect, OffsetOf(PE_ExportTableHeader, ordinal_table_voff), ordinal_table_symbol); String8 obj = coff_obj_writer_serialize(arena, obj_writer); coff_obj_writer_release(&obj_writer); scratch_end(scratch); return obj; } internal String8 pe_make_import_lib(Arena *arena, COFF_MachineType machine, COFF_TimeStamp time_stamp, String8 dll_name, String8 debug_symbols, PE_ExportParseList export_list) { ProfBeginFunction(); COFF_LibWriter *lib_writer = coff_lib_writer_alloc(); // These objects appear in first three members of any lib that linker produces with /dll. // Objects are used by MSVC linker to build import table. String8 import_entry_obj = pe_make_import_entry_obj(lib_writer->arena, dll_name, time_stamp, machine, debug_symbols); String8 null_import_descriptor_obj = pe_make_null_import_descriptor_obj(lib_writer->arena, time_stamp, machine, debug_symbols); String8 null_thunk_data_obj = pe_make_null_thunk_data_obj(lib_writer->arena, dll_name, time_stamp, machine, debug_symbols); // push import table nulls coff_lib_writer_push_obj(lib_writer, dll_name, import_entry_obj); coff_lib_writer_push_obj(lib_writer, dll_name, null_import_descriptor_obj); coff_lib_writer_push_obj(lib_writer, dll_name, null_thunk_data_obj); // push exports for (PE_ExportParseNode *exp_n = export_list.first; exp_n != 0; exp_n = exp_n->next) { PE_ExportParse *exp = &exp_n->data; if (exp->is_private) { continue; } String8 name = pe_name_from_export_parse(exp); U16 hint_or_ordinal = pe_hint_or_ordinal_from_export_parse(exp); coff_lib_writer_push_import(lib_writer, machine, time_stamp, dll_name, exp->import_by, name, hint_or_ordinal, exp->type); } // serialize lib String8 lib = coff_lib_writer_serialize(arena, lib_writer, COFF_TimeStamp_Max, 0, /* emit second member: */ 1); coff_lib_writer_release(&lib_writer); ProfEnd(); return lib; }