diff --git a/src/coff/coff.c b/src/coff/coff.c index de5488e0..d925ae56 100644 --- a/src/coff/coff.c +++ b/src/coff/coff.c @@ -302,9 +302,9 @@ coff_ordinal_data_from_hint(Arena *arena, COFF_MachineType machine, U16 hint) internal String8 coff_make_import_header_by_name(Arena *arena, - String8 dll_name, COFF_MachineType machine, COFF_TimeStamp time_stamp, + String8 dll_name, String8 name, U16 hint, COFF_ImportType type) @@ -346,9 +346,9 @@ coff_make_import_header_by_name(Arena *arena, internal String8 coff_make_import_header_by_ordinal(Arena *arena, - String8 dll_name, COFF_MachineType machine, COFF_TimeStamp time_stamp, + String8 dll_name, U16 ordinal, COFF_ImportType type) { @@ -930,3 +930,27 @@ coff_import_header_type_from_string(String8 name) } return COFF_ImportType_Invalid; } +internal String8 +coff_make_lib_member_header(Arena *arena, String8 name, COFF_TimeStamp time_stamp, U16 user_id, U16 group_id, U16 mode, U32 size) +{ + Assert(name.size < 16); + Assert(user_id < 10000); + Assert(group_id < 10000); + Assert(mode < 10000); + Assert(size < 1000000000); + + Temp scratch = scratch_begin(&arena, 1); + String8List list = {0}; + str8_list_pushf(scratch.arena, &list, "%-16.*s", str8_varg(name)); + str8_list_pushf(scratch.arena, &list, "%-12u", time_stamp); + str8_list_pushf(scratch.arena, &list, "%-6u", user_id); + str8_list_pushf(scratch.arena, &list, "%-6u", group_id); + str8_list_pushf(scratch.arena, &list, "%-8u", mode); + str8_list_pushf(scratch.arena, &list, "%-10u", size); + str8_list_pushf(scratch.arena, &list, "`\n"); + String8 result = str8_list_join(arena, &list, 0); + + Assert(result.size == sizeof(COFF_ArchiveMemberHeader)); + scratch_end(scratch); + return result; +} diff --git a/src/coff/coff.h b/src/coff/coff.h index 89445216..3d6d7646 100644 --- a/src/coff/coff.h +++ b/src/coff/coff.h @@ -612,8 +612,6 @@ internal U64 coff_make_ordinal64(U16 hint); internal String8 coff_ordinal_data_from_hint(Arena *arena, COFF_MachineType machine, U16 hint); internal String8 coff_make_import_lookup (Arena *arena, U16 hint, String8 name); -internal String8 coff_make_import_header_by_name (Arena *arena, String8 dll_name, COFF_MachineType machine, COFF_TimeStamp time_stamp, String8 name, U16 hint, COFF_ImportType type); -internal String8 coff_make_import_header_by_ordinal(Arena *arena, String8 dll_name, COFF_MachineType machine, COFF_TimeStamp time_stamp, U16 ordinal, COFF_ImportType type); //////////////////////////////// // Misc @@ -647,5 +645,6 @@ internal String8 coff_string_from_reloc(COFF_MachineType machine, COFF_RelocType internal COFF_MachineType coff_machine_from_string(String8 string); internal COFF_ImportType coff_import_header_type_from_string(String8 name); +internal String8 coff_make_lib_member_header(Arena *arena, String8 name, COFF_TimeStamp time_stamp, U16 user_id, U16 group_id, U16 mode, U32 size); #endif // COFF_H diff --git a/src/coff/coff_lib_writer.c b/src/coff/coff_lib_writer.c new file mode 100644 index 00000000..1aebcc69 --- /dev/null +++ b/src/coff/coff_lib_writer.c @@ -0,0 +1,374 @@ +// Copyright (c) 2025 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal COFF_LibWriterSymbolNode * +coff_lib_writer_symbol_list_push(Arena *arena, COFF_LibWriterSymbolList *list, COFF_LibWriterSymbol symbol) +{ + COFF_LibWriterSymbolNode *node = push_array_no_zero(arena, COFF_LibWriterSymbolNode, 1); + node->next = 0; + node->data = symbol; + SLLQueuePush(list->first, list->last, node); + list->count += 1; + return node; +} + +internal COFF_LibWriterMemberNode * +coff_lib_writer_member_list_push(Arena *arena, COFF_LibWriterMemberList *list, COFF_LibWriterMember member) +{ + COFF_LibWriterMemberNode *node = push_array_no_zero(arena, COFF_LibWriterMemberNode, 1); + node->next = 0; + node->data = member; + SLLQueuePush(list->first, list->last, node); + list->count += 1; + return node; +} + +internal COFF_LibWriterSymbol * +coff_lib_writer_symbol_array_from_list(Arena *arena, COFF_LibWriterSymbolList list) +{ + COFF_LibWriterSymbol *arr = push_array_no_zero(arena, COFF_LibWriterSymbol, list.count + 2); + COFF_LibWriterSymbol *ptr = arr + 1; + for (COFF_LibWriterSymbolNode *i = list.first; i != 0; i = i->next, ptr += 1) { + ptr->name = push_str8_copy(arena, i->data.name); + ptr->member_idx = i->data.member_idx; + } + MemoryZeroStruct(&arr[0]); + MemoryZeroStruct(&arr[list.count+1]); + return arr; +} + +internal COFF_LibWriterMember * +coff_lib_writer_member_array_from_list(Arena *arena, COFF_LibWriterMemberList list) +{ + COFF_LibWriterMember *arr = push_array_no_zero(arena, COFF_LibWriterMember, list.count); + COFF_LibWriterMember *ptr = arr; + for (COFF_LibWriterMemberNode *i = list.first; i != 0; i = i->next, ptr += 1) { + ptr->name = push_str8_copy(arena, i->data.name); + ptr->data = push_str8_copy(arena, i->data.data); + } + return arr; +} + +internal int +coff_lib_writer_symbol_name_compar(const void *raw_a, const void *raw_b) +{ + const COFF_LibWriterSymbol *sa = raw_a; + const COFF_LibWriterSymbol *sb = raw_b; + return str8_compar_case_sensitive(&sa->name, &sb->name); +} + +internal int +coff_lib_writer_symbol_is_before(void *raw_a, void *raw_b) +{ + int compar = coff_lib_writer_symbol_name_compar(raw_a, raw_b); + return compar < 0; +} + +internal void +coff_lib_writer_symbol_array_sort(COFF_LibWriterSymbol *arr, U64 count) +{ + Assert(count >= 2); + radsort(arr + 1, count - 2, coff_lib_writer_symbol_is_before); +} + +internal COFF_LibWriter * +coff_lib_writer_alloc(void) +{ + Arena *arena = arena_alloc(); + COFF_LibWriter *writer = push_array(arena, COFF_LibWriter, 1); + writer->arena = arena; + return writer; +} + +internal void +coff_lib_writer_release(COFF_LibWriter **writer_ptr) +{ + arena_release((*writer_ptr)->arena); + *writer_ptr = 0; +} + +internal void +coff_lib_writer_push_obj(COFF_LibWriter *writer, String8 obj_path, String8 obj_data) +{ + U64 member_idx = writer->member_list.count; + + // push obj member + COFF_LibWriterMember member = {0}; + member.name = obj_path; + member.data = obj_data; + coff_lib_writer_member_list_push(writer->arena, &writer->member_list, member); + + // push external symbols + { + COFF_FileHeaderInfo obj_header = coff_file_header_info_from_data(obj_data); + String8 string_table = str8_substr(obj_data, obj_header.string_table_range); + String8 symbol_table = str8_substr(obj_data, obj_header.symbol_table_range); + + COFF_ParsedSymbol symbol; + for (U64 symbol_idx = 0; symbol_idx < obj_header.symbol_count; symbol_idx += (1 + symbol.aux_symbol_count)) { + void *symbol_ptr; + if (obj_header.is_big_obj) { + symbol_ptr = &((COFF_Symbol32 *)symbol_table.str)[symbol_idx]; + symbol = coff_parse_symbol32(string_table, symbol_ptr); + } else { + symbol_ptr = &((COFF_Symbol16 *)symbol_table.str)[symbol_idx]; + symbol = coff_parse_symbol16(string_table, symbol_ptr); + } + + COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); + if (interp == COFF_SymbolValueInterp_Regular) { + if (symbol.storage_class == COFF_SymStorageClass_External) { + COFF_LibWriterSymbol lib_symbol = {0}; + lib_symbol.name = symbol.name; + lib_symbol.member_idx = member_idx; + coff_lib_writer_symbol_list_push(writer->arena, &writer->symbol_list, lib_symbol); + } + } + } + } +} + +internal void +coff_lib_writer_push_export(COFF_LibWriter *writer, String8 raw_import_header) +{ + U64 member_idx = writer->member_list.count; + COFF_ParsedArchiveImportHeader import_header = coff_archive_import_from_data(raw_import_header); + + // push import member + COFF_LibWriterMember member = {0}; + member.name = import_header.dll_name; + member.data = raw_import_header; + coff_lib_writer_member_list_push(writer->arena, &writer->member_list, member); + + switch (import_header.type) { + case COFF_ImportHeader_Code: { + COFF_LibWriterSymbol def_symbol = {0}; + def_symbol.name = push_str8_copy(writer->arena, import_header.func_name); + def_symbol.member_idx = member_idx; + coff_lib_writer_symbol_list_push(writer->arena, &writer->symbol_list, def_symbol); + } break; + case COFF_ImportHeader_Data: { + COFF_LibWriterSymbol imp_symbol = {0}; + imp_symbol.name = push_str8f(writer->arena, "__imp_%S", import_header.func_name); + imp_symbol.member_idx = member_idx; + coff_lib_writer_symbol_list_push(writer->arena, &writer->symbol_list, imp_symbol); + } break; + case COFF_ImportHeader_Const: { + NotImplemented; + } break; + default: { InvalidPath; } break; + } +} + +internal void +coff_lib_writer_push_export_by_ordinal(COFF_LibWriter *lib_writer, COFF_MachineType machine, COFF_TimeStamp time_stamp, String8 dll_name, COFF_ImportType import_type, U16 ordinal) +{ + String8 import_header = coff_make_import_header_by_ordinal(lib_writer->arena, machine, time_stamp, dll_name, ordinal, import_type); + coff_lib_writer_push_export(lib_writer, import_header); +} + +internal void +coff_lib_writer_push_export_by_name(COFF_LibWriter *lib_writer, COFF_MachineType machine, COFF_TimeStamp time_stamp, String8 dll_name, COFF_ImportType import_type, String8 name, U16 hint) +{ + String8 import_header = coff_make_import_header_by_name(lib_writer->arena, machine, time_stamp, dll_name, name, hint, import_type); + coff_lib_writer_push_export(lib_writer, import_header); +} + +internal String8List +coff_lib_writer_serialize(Arena *arena, COFF_LibWriter *lib_writer, COFF_TimeStamp time_stamp, U16 mode, B32 emit_second_member) +{ + Temp scratch = scratch_begin(&arena, 1); + + // symbol & member lists -> arrays + U64 symbols_count; + COFF_LibWriterSymbol *symbols; + U64 member_count; + COFF_LibWriterMember *member_array; + { + U64 symbols_count_with_null = lib_writer->symbol_list.count + 2; + COFF_LibWriterSymbol *symbols_with_null = coff_lib_writer_symbol_array_from_list(scratch.arena, lib_writer->symbol_list); + coff_lib_writer_symbol_array_sort(symbols_with_null, symbols_count_with_null); + symbols_count = symbols_count_with_null - 2; + symbols = symbols_with_null + 1; + + member_count = lib_writer->member_list.count; + member_array = coff_lib_writer_member_array_from_list(scratch.arena, lib_writer->member_list); + } + + // serialize members + U64 *member_offsets = push_array_no_zero(scratch.arena, U64, member_count); + String8List long_names_list = {0}; + String8List member_data_list = {0}; + { + HashTable *name_ht = hash_table_init(scratch.arena, 1024); + for (U64 member_idx = 0; member_idx < member_count; member_idx += 1) { + COFF_LibWriterMember *member = &member_array[member_idx]; + + // make member name + String8 name; + U64 name_with_slash_size = member->name.size + 1; + if (name_with_slash_size > COFF_Archive_MaxShortNameSize) { + // have we seen this member name before? + KeyValuePair *is_present = hash_table_search_string(name_ht, member->name); + if (is_present) { + name = is_present->value_string; + } else { + name = push_str8f(scratch.arena, "/%u", long_names_list.total_size); + str8_list_pushf(scratch.arena, &long_names_list, "%S/\n", member->name); + hash_table_push_string_string(scratch.arena, name_ht, member->name, name); + } + } else { + name = push_str8f(scratch.arena, "%S/", member->name); + } + + member_offsets[member_idx] = member_data_list.total_size; + + String8 member_data = member->data; + String8 member_header = coff_make_lib_member_header(arena, name, time_stamp, 0, 0, mode, member_data.size); + + str8_list_push(arena, &member_data_list, member_header); + str8_list_push(arena, &member_data_list, member_data); + { + U64 pad_size = AlignPadPow2(member_data_list.total_size, COFF_Archive_MemberAlign); + U8 *pad = push_array(arena, U8, pad_size); + str8_list_push(arena, &member_data_list, str8(pad, pad_size)); + } + } + } + + // long names member + if (long_names_list.total_size) { + String8 header = coff_make_lib_member_header(arena, str8_lit("//"), time_stamp, 0, 0, mode, long_names_list.total_size); + String8 data = str8_list_join(arena, &long_names_list, 0); + U64 member_offset = member_data_list.total_size + data.size + header.size; + { + U64 pad_size = AlignPadPow2(member_offset, COFF_Archive_MemberAlign); + U8 *pad = push_array(arena, U8, pad_size); + str8_list_push_front(arena, &member_data_list, str8(pad, pad_size)); + } + str8_list_push_front(arena, &member_data_list, data); + str8_list_push_front(arena, &member_data_list, header); + } + + // compute size for symbol string table + U32 name_buffer_size = 0; + for (COFF_LibWriterSymbol *ptr = &symbols[0], *opl = ptr + symbols_count; ptr < opl; ptr += 1) { + name_buffer_size += ptr->name.size; + name_buffer_size += 1; // null + } + + // write symbol name buffer + U8 *name_buffer = push_array_no_zero(scratch.arena, U8, name_buffer_size); + { + U64 name_cursor = 0; + for (COFF_LibWriterSymbol *ptr = &symbols[0], *opl = ptr + symbols_count; ptr < opl; ptr += 1) { + MemoryCopy(name_buffer + name_cursor, ptr->name.str, ptr->name.size); + name_buffer[name_cursor + ptr->name.size] = '\0'; + name_cursor += ptr->name.size + 1; + } + } + + U64 members_base_offset; + { + U64 sizeof_first_header = sizeof(COFF_ArchiveMemberHeader) + sizeof(U32) + sizeof(U32) * symbols_count + name_buffer_size; + U64 sizeof_second_header = sizeof(COFF_ArchiveMemberHeader) + sizeof(U32) + sizeof(U32) * member_count + sizeof(U32) + sizeof(U16) * symbols_count + name_buffer_size; + U64 sizeof_long_names = sizeof(COFF_ArchiveMemberHeader) + long_names_list.total_size; + + sizeof_first_header = AlignPow2(sizeof_first_header, COFF_Archive_MemberAlign); + sizeof_second_header = AlignPow2(sizeof_second_header, COFF_Archive_MemberAlign); + sizeof_long_names = AlignPow2(sizeof_long_names, COFF_Archive_MemberAlign); + + members_base_offset = sizeof(g_coff_archive_sig); + members_base_offset += sizeof_first_header; + if (emit_second_member) { + members_base_offset += sizeof_second_header; + } + if (long_names_list.total_size) { + members_base_offset += sizeof_long_names; + } + } + + // second linker member + if (emit_second_member) { + U32 member_count32 = safe_cast_u32(member_count); + U32 symbol_count32 = safe_cast_u32(symbols_count); + + U32 *member_off32_arr = push_array_no_zero(scratch.arena, U32, member_count); + U16 *member_idx16_arr = push_array_no_zero(scratch.arena, U16, symbols_count); + + // write member offset array + for (U64 member_idx = 0; member_idx < member_count; member_idx += 1) { + U64 member_offset = members_base_offset + member_offsets[member_idx]; + U32 member_off32 = safe_cast_u32(member_offset); + member_off32_arr[member_idx] = member_off32; + } + + // write member offset indices for each symbol + for (U64 symbol_idx = 0; symbol_idx < symbols_count; symbol_idx += 1) { + // member offset indices are 1-based + U64 member_idx = symbols[symbol_idx].member_idx + 1; + U16 member_idx16 = safe_cast_u16(member_idx); + member_idx16_arr[symbol_idx] = member_idx16; + } + + // layout second member data + String8List second_member_data_list = {0}; + str8_list_push(scratch.arena, &second_member_data_list, str8_struct(&member_count32)); + str8_list_push(scratch.arena, &second_member_data_list, str8_array(member_off32_arr, member_count)); + str8_list_push(scratch.arena, &second_member_data_list, str8_struct(&symbol_count32)); + str8_list_push(scratch.arena, &second_member_data_list, str8_array(member_idx16_arr, symbols_count)); + str8_list_push(scratch.arena, &second_member_data_list, str8(name_buffer, name_buffer_size)); + + String8 member_data = str8_list_join(arena, &second_member_data_list, 0); + String8 member_header = coff_make_lib_member_header(arena, str8_lit("/"), time_stamp, 0, 0, mode, member_data.size); + + U64 member_offset = member_data_list.total_size + member_data.size + member_header.size; + { + U64 pad_size = AlignPadPow2(member_offset, COFF_Archive_MemberAlign); + U8 *pad = push_array(arena, U8, pad_size); + str8_list_push_front(arena, &member_data_list, str8(pad, pad_size)); + } + str8_list_push_front(arena, &member_data_list, member_data); + str8_list_push_front(arena, &member_data_list, member_header); + } + + // first linker member (obsolete, but kept for compatability reasons) + { + U32 symbol_count_be = from_be_u32(symbols_count); + U32 *member_off32_arr = push_array_no_zero(scratch.arena, U32, symbols_count); + + for (U64 symbol_idx = 0; symbol_idx < symbols_count; symbol_idx += 1) { + COFF_LibWriterSymbol *symbol = &symbols[symbol_idx]; + + // write big endian member offset + U64 member_offset = members_base_offset + member_offsets[symbol->member_idx]; + U32 member_off32 = from_be_u32(safe_cast_u32(member_offset)); + member_off32_arr[symbol_idx] = member_off32; + } + + // layout first member data + String8List first_member_data_list = {0}; + str8_list_push(scratch.arena, &first_member_data_list, str8_struct(&symbol_count_be)); + str8_list_push(scratch.arena, &first_member_data_list, str8_array(member_off32_arr, symbols_count)); + str8_list_push(scratch.arena, &first_member_data_list, str8(name_buffer, name_buffer_size)); + + String8 member_data = str8_list_join(arena, &first_member_data_list, 0); + String8 member_header = coff_make_lib_member_header(arena, str8_lit("/"), time_stamp, 0, 0, mode, member_data.size); + + U64 member_offset = sizeof(g_coff_archive_sig) + member_header.size + member_data.size; + { + U64 pad_size = AlignPadPow2(member_offset, COFF_Archive_MemberAlign); + U8 *pad = push_array(arena, U8, pad_size); + str8_list_push_front(arena, &member_data_list, str8(pad, pad_size)); + } + str8_list_push_front(arena, &member_data_list, member_data); + str8_list_push_front(arena, &member_data_list, member_header); + } + + // archive signature + str8_list_push_front(arena, &member_data_list, str8_struct(&g_coff_archive_sig)); + + scratch_end(scratch); + return member_data_list; +} + diff --git a/src/coff/coff_lib_writer.h b/src/coff/coff_lib_writer.h new file mode 100644 index 00000000..0e590ed3 --- /dev/null +++ b/src/coff/coff_lib_writer.h @@ -0,0 +1,69 @@ +// Copyright (c) 2025 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef COFF_LIB_WRITER_H +#define COFF_LIB_WRITER_H + +typedef struct COFF_LibWriterMember +{ + String8 name; + String8 data; +} COFF_LibWriterMember; + +typedef struct COFF_LibWriterMemberNode +{ + struct COFF_LibWriterMemberNode *next; + COFF_LibWriterMember data; +} COFF_LibWriterMemberNode; + +typedef struct COFF_LibWriterMemberList +{ + U64 count; + COFF_LibWriterMemberNode *first; + COFF_LibWriterMemberNode *last; +} COFF_LibWriterMemberList; + +typedef struct COFF_LibWriterSymbol +{ + String8 name; + U64 member_idx; +} COFF_LibWriterSymbol; + +typedef struct COFF_LibWriterSymbolNode +{ + struct COFF_LibWriterSymbolNode *next; + COFF_LibWriterSymbol data; +} COFF_LibWriterSymbolNode; + +typedef struct COFF_LibWriterSymbolList +{ + U64 count; + COFF_LibWriterSymbolNode *first; + COFF_LibWriterSymbolNode *last; +} COFF_LibWriterSymbolList; + +typedef struct COFF_LibWriter +{ + Arena *arena; + COFF_LibWriterMemberList member_list; + COFF_LibWriterSymbolList symbol_list; +} COFF_LibWriter; + +//////////////////////////////// + +internal COFF_LibWriterSymbolNode * coff_lib_writer_symbol_list_push(Arena *arena, COFF_LibWriterSymbolList *list, COFF_LibWriterSymbol symbol); +internal COFF_LibWriterMemberNode * coff_lib_writer_member_list_push(Arena *arena, COFF_LibWriterMemberList *list, COFF_LibWriterMember member); +internal COFF_LibWriterSymbol * coff_lib_writer_symbol_array_from_list(Arena *arena, COFF_LibWriterSymbolList list); +internal COFF_LibWriterMember * coff_lib_writer_member_array_from_list(Arena *arena, COFF_LibWriterMemberList list); +internal void coff_lib_writer_symbol_array_sort(COFF_LibWriterSymbol *arr, U64 count); + +internal COFF_LibWriter * coff_lib_writer_alloc(void); +internal void coff_lib_writer_release(COFF_LibWriter **writer_ptr); +internal void coff_lib_writer_push_obj(COFF_LibWriter *writer, String8 obj_path, String8 obj_data); +internal void coff_lib_writer_push_export(COFF_LibWriter *writer, String8 raw_import_header); +internal void coff_lib_writer_push_export_by_ordinal(COFF_LibWriter *lib_writer, COFF_MachineType machine, COFF_TimeStamp time_stamp, String8 dll_name, COFF_ImportType import_type, U16 ordinal); +internal void coff_lib_writer_push_export_by_name(COFF_LibWriter *lib_writer, COFF_MachineType machine, COFF_TimeStamp time_stamp, String8 dll_name, COFF_ImportType import_type, String8 name, U16 hint); +internal String8List coff_lib_writer_serialize(Arena *arena, COFF_LibWriter *lib_writer, COFF_TimeStamp time_stamp, U16 mode, B32 emit_second_member); + +#endif // COFF_LIB_WRITER_H +