diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 7d8b1917..d506f377 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -34,6 +34,7 @@ #include "msf/msf_parse.h" #include "pdb/pdb.h" #include "msvc_crt/msvc_crt.h" +#include "llvm/llvm.h" #include "base/base_inc.c" #include "os/os_inc.c" @@ -52,6 +53,7 @@ #include "msf/msf_parse.c" #include "pdb/pdb.c" #include "msvc_crt/msvc_crt.c" +#include "llvm/llvm.c" // --- RDI --------------------------------------------------------------------- @@ -120,7 +122,7 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) { LNK_CmdLine cmd_line = {0}; - // setup default flags + // default flags lnk_cmd_line_push_option_if_not_presentf(arena, &cmd_line, LNK_CmdSwitch_Align, "%u", KB(4)); lnk_cmd_line_push_option_if_not_presentf(arena, &cmd_line, LNK_CmdSwitch_Debug, "none"); lnk_cmd_line_push_option_if_not_presentf(arena, &cmd_line, LNK_CmdSwitch_FileAlign, "%u", 512); @@ -136,6 +138,7 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) if (!lnk_cmd_line_has_switch(user_cmd_line, LNK_CmdSwitch_Brepro)) { lnk_cmd_line_push_option_if_not_presentf(arena, &cmd_line, LNK_CmdSwitch_Rad_TimeStamp, "%u", os_get_process_start_time_unix()); } + lnk_cmd_line_push_option_if_not_presentf(arena, &cmd_line, LNK_CmdSwitch_Rad_TypeHashAlg, "BLAKE3"); lnk_cmd_line_push_option_if_not_presentf(arena, &cmd_line, LNK_CmdSwitch_Rad_Age, "%u", 1); lnk_cmd_line_push_option_if_not_presentf(arena, &cmd_line, LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll, ""); lnk_cmd_line_push_option_if_not_presentf(arena, &cmd_line, LNK_CmdSwitch_Rad_DoMerge, ""); @@ -5246,7 +5249,7 @@ lnk_run(TP_Context *tp, TP_Arena *arena, LNK_Config *config) // // CodeView // - LNK_CodeViewInput cv = lnk_make_code_view_input(tp, arena, config->io_flags, config->lib_dir_list, config->alt_pch_dirs, debug_info_objs_count, debug_info_objs); + LNK_CodeViewInput cv = lnk_make_code_view_input(tp, arena, config, debug_info_objs_count, debug_info_objs); LNK_MergedTypes cv_types = lnk_merge_types(tp, arena, &cv); // @@ -5354,7 +5357,7 @@ lnk_run(TP_Context *tp, TP_Arena *arena, LNK_Config *config) } LNK_CodeViewInput stripped_cv = {0}; - stripped_cv.io_flags = config->io_flags; + stripped_cv.config = config; stripped_cv.is_stripped = 1; stripped_cv.obj_arr = cv.obj_arr; stripped_cv.obj_count = cv.obj_count; diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 62b84851..78eee9f2 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -92,6 +92,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_Rad_Ignore, 0, "RAD_IGNORE", ":#", "Ignore the specified RAD linker warning." }, { LNK_CmdSwitch_Rad_WriteTempFiles, 0, "RAD_WRITE_TEMP_FILES", "[:NO]", "When speicifed linker writes image and debug info to temporary files and renames after link is done." }, { LNK_CmdSwitch_Rad_TimeStamp, 0, "RAD_TIME_STAMP", ":#", "Time stamp embeded in EXE and PDB." }, + { LNK_CmdSwitch_Rad_TypeHashAlg, 0, "RAD_TPYE_HASH_ALG", ":{BLAKE3}", "Sets hashing algorithm for type merging." }, { LNK_CmdSwitch_Rad_UnresolvedSymbolLimit, 0, "RAD_UNRESOLVED_SYMBOL_LIMIT", ":#", "Limits number of unresolved symbol errors linker reports." }, { LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit, 0, "RAD_UNRESOLVED_SYMBOL_REF_LIMIT", ":#", "Limit number of unresolved symbol references linker reports." }, { LNK_CmdSwitch_Rad_Version, 0, "RAD_VERSION", "", "Print version and exit." }, @@ -2065,6 +2066,17 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List lnk_cmd_switch_parse_u32(obj, cmd_switch, value_strings, &config->time_stamp, 0); } break; + case LNK_CmdSwitch_Rad_TypeHashAlg: { + String8 alg = {0}; + if (lnk_cmd_switch_parse_string(obj, cmd_switch, value_strings, &alg)) { + if (str8_match(alg, str8_lit("BLAKE3"), StringMatchFlag_CaseInsensitive)) { + config->type_hash_alg = LLVM_GHashAlg_BLAKE3; + } else { + lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "unknown hash alg: %S", alg); + } + } + } break; + case LNK_CmdSwitch_Rad_UnresolvedSymbolLimit: { lnk_cmd_switch_parse_u64(obj, cmd_switch, value_strings, &config->unresolved_symbol_limit, 0); } break; diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index e3bb81ba..513a3f9d 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -119,6 +119,7 @@ typedef enum LNK_CmdSwitch_Rad_SharedThreadPoolMaxWorkers, LNK_CmdSwitch_Rad_Ignore, LNK_CmdSwitch_Rad_TimeStamp, + LNK_CmdSwitch_Rad_TypeHashAlg, LNK_CmdSwitch_Rad_UnresolvedSymbolLimit, LNK_CmdSwitch_Rad_UnresolvedSymbolRefLimit, LNK_CmdSwitch_Rad_Version, @@ -360,6 +361,7 @@ typedef struct LNK_Config U64 unresolved_symbol_ref_limit; LNK_SwitchState map_lines_for_unresolved_symbols; String8List alt_pch_dirs; + LLVM_GHashAlg type_hash_alg; } LNK_Config; // --- MSVC Error Codes -------------------------------------------------------- diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 7a66c940..232d776d 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -57,6 +57,76 @@ THREAD_POOL_TASK_FUNC(lnk_parse_debug_s_task) } } +internal +THREAD_POOL_TASK_FUNC(lnk_parse_debug_h_task) +{ + U64 obj_idx = task_id; + LNK_CodeViewInput *task = raw_task; + + String8List sect_list = task->debug_h_list_arr[obj_idx]; + CV_DebugH *debug_h = &task->debug_h_arr [obj_idx]; + + if (sect_list.node_count > 0) { + if (sect_list.node_count > 1) { + lnk_error_obj(LNK_Warning_GHash, task->obj_arr[obj_idx], + "obj contains multiple .debug$H sections; using first one"); + } + + // select first .debug$H + String8 raw_debug_h = sect_list.first->string; + LLVM_GHash ghash = {0}; + U64 ghash_read_size = str8_deserial_read_struct(raw_debug_h, 0, &ghash); + + // was header read completely? + if (ghash_read_size != sizeof(ghash)) { + lnk_error_obj(LNK_Warning_GHash, task->obj_arr[obj_idx], + ".debug$H section is too small to contain the header"); + goto exit; + } + + // validate magic + if (ghash.magic != LLVM_GHash_Magic) { + lnk_error_obj(LNK_Warning_GHash, task->obj_arr[obj_idx], + ".debug$H contains invalid magic: got 0x%x, expected 0x%x", ghash.magic, LLVM_GHash_Magic); + goto exit; + } + + // validate version + if (ghash.version != LLVM_GHash_CurrentVersion) { + lnk_error_obj(LNK_Warning_GHash, task->obj_arr[obj_idx], + "mismatched .debug$H version: got %u, expected %u", + ghash.version, LLVM_GHash_CurrentVersion); + goto exit; + } + + // validate hashing algorithm + if (ghash.hash_alg != task->config->type_hash_alg) { + lnk_error_obj(LNK_Warning_GHash, task->obj_arr[obj_idx], + "mismatched .debug$H hash algorithm: got %S, expected %S", + llvm_string_from_ghash_alg(ghash.hash_alg), + llvm_string_from_ghash_alg(task->config->type_hash_alg)); + goto exit; + } + + // input.debug_h_arr must be 1:1 with input.debug_t_arr + U64 hash_size = llvm_hash_size_from_alg(ghash.hash_alg); + U64 hash_count = (raw_debug_h.size - sizeof(ghash)) / hash_size; + if (hash_count != task->debug_t_arr[obj_idx].count) { + lnk_error_obj(LNK_Warning_GHash, task->obj_arr[obj_idx], + "mismatched .debug$H hash count and type count: got %llu hashes for %llu types", + hash_count, task->debug_t_arr[obj_idx].count); + goto exit; + } + + // load hashes + String8 hashes = str8_substr(raw_debug_h, r1u64(sizeof(ghash), raw_debug_h.size)); + debug_h->count = hash_count; + debug_h->v = (U64 *)hashes.str; + + exit:; + } +} + internal THREAD_POOL_TASK_FUNC(lnk_strip_debug_t_sig_task) { @@ -106,7 +176,7 @@ THREAD_POOL_TASK_FUNC(lnk_read_type_servers_task) LNK_TypeServer *ts = &task->ts_arr.v[ts_idx]; // read PDB from disk - String8 msf_data = lnk_read_data_from_file_path(scratch.arena, task->io_flags, ts->ts_path); + String8 msf_data = lnk_read_data_from_file_path(scratch.arena, task->config->io_flags, ts->ts_path); // check magic if (!msf_check_magic_70(msf_data) && msf_check_magic_20(msf_data)) { goto exit; } @@ -198,48 +268,49 @@ THREAD_POOL_TASK_FUNC(lnk_read_type_servers_task) } internal LNK_CodeViewInput -lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_IO_Flags io_flags, String8List lib_dir_list, String8List alt_pch_dirs, U64 obj_count, LNK_Obj **obj_arr) +lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, U64 obj_count, LNK_Obj **obj_arr) { ProfBegin("Extract CodeView"); Temp scratch = scratch_begin(0,0); - LNK_CodeViewInput input = { .io_flags = io_flags, .obj_count = obj_count, .count = obj_count, .obj_arr = obj_arr, .ts_obj_range = r1u64(0,0) }; + LNK_CodeViewInput input = { .config = config, .obj_count = obj_count, .count = obj_count, .obj_arr = obj_arr, .ts_obj_range = r1u64(0,0) }; ProfBegin("Collect CodeView"); input.debug_s_list_arr = lnk_collect_obj_sections(tp, tp_arena, obj_count, obj_arr, str8_lit(".debug$S"), 0); input.debug_p_list_arr = lnk_collect_obj_sections(tp, tp_arena, obj_count, obj_arr, str8_lit(".debug$P"), 0); input.debug_t_list_arr = lnk_collect_obj_sections(tp, tp_arena, obj_count, obj_arr, str8_lit(".debug$T"), 0); + input.debug_h_list_arr = lnk_collect_obj_sections(tp, tp_arena, obj_count, obj_arr, str8_lit(".debug$H"), 0); ProfEnd(); if (lnk_get_log_status(LNK_Log_Debug) || PROFILE_TELEMETRY) { - U64 total_debug_s_size = 0, total_debug_t_size = 0, total_debug_p_size = 0; + U64 total_debug_s_size = 0, total_debug_t_size = 0, total_debug_p_size = 0, total_debug_h_size = 0; for EachIndex(obj_idx, obj_count) { for EachNode(n, String8Node, input.debug_s_list_arr[obj_idx].first) { total_debug_s_size += n->string.size; } for EachNode(n, String8Node, input.debug_t_list_arr[obj_idx].first) { total_debug_t_size += n->string.size; } for EachNode(n, String8Node, input.debug_p_list_arr[obj_idx].first) { total_debug_p_size += n->string.size; } + for EachNode(n, String8Node, input.debug_h_list_arr[obj_idx].first) { total_debug_h_size += n->string.size; } } ProfNoteV("Total .debug$S Input Size: %M", total_debug_s_size); ProfNoteV("Total .debug$T Input Size: %M", total_debug_t_size); ProfNoteV("Total .debug$P Input Size: %M", total_debug_p_size); + ProfNoteV("Total .debug$H Input Size: %M", total_debug_H_size); if (lnk_get_log_status(LNK_Log_Debug)) { lnk_log(LNK_Log_Debug, "[Total .debug$S Input Size %M]", total_debug_s_size); lnk_log(LNK_Log_Debug, "[Total .debug$T Input Size %M]", total_debug_t_size); lnk_log(LNK_Log_Debug, "[Total .debug$P Input Size %M]", total_debug_p_size); + lnk_log(LNK_Log_Debug, "[Total .debug$H Input Size %M]", total_debug_h_size); } } ProfBegin("Parse CodeView"); CV_DebugT *debug_p_arr; { - input.debug_s_arr = push_array(tp_arena->v[0], CV_DebugS, obj_count); + input.debug_s_arr = push_array(tp_arena->v[0], CV_DebugS, input.obj_count); tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_s_task, &input, "Parse .debug$S"); - input.debug_h_arr = push_array(tp_arena->v[0], CV_DebugH, obj_count); // TODO: collect & parse .debug$H - LNK_ParseCvTypes parse_types = { .input = &input }; - debug_p_arr = push_array(tp_arena->v[0], CV_DebugT, obj_count); parse_types.raw_types = str8_array_from_list_arr(scratch.arena, input.debug_p_list_arr, obj_count); parse_types.out_types = debug_p_arr; @@ -251,6 +322,9 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_IO_Flags io_fla parse_types.out_types = input.debug_t_arr; tp_for_parallel_prof(tp, 0, obj_count, lnk_strip_debug_t_sig_task, &parse_types, "Strip .debug$T"); tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_t_task, &parse_types, "Parse .debug$T"); + + input.debug_h_arr = push_array(tp_arena->v[0], CV_DebugH, input.obj_count); + tp_for_parallel_prof(tp, tp_arena, obj_count, lnk_parse_debug_h_task, &input, "Parse .debug$H"); } ProfEnd(); @@ -297,7 +371,7 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_IO_Flags io_fla CV_DebugT *debug_t = &input.debug_t_arr[obj_idx]; CV_Leaf leaf = cv_debug_t_get_leaf(debug_t, 0); CV_TypeServerInfo ts_info = cv_type_server_info_from_leaf(leaf); - String8 ts_path = lnk_find_first_file(scratch.arena, lib_dir_list, ts_info.name); + String8 ts_path = lnk_find_first_file(scratch.arena, config->lib_dir_list, ts_info.name); if (ts_path.size == 0) { lnk_discard_cv_debug_info(&input, obj_idx); @@ -424,7 +498,7 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_IO_Flags io_fla if ( ! hash_table_search_path_u64(debug_p_ht, obj_path, &debug_p_obj_idx)) { // try alternative directory for the PCH String8 obj_name = str8_skip_last_slash(obj_path); - for EachNode(alt_dir_n, String8Node, alt_pch_dirs.first) { + for EachNode(alt_dir_n, String8Node, config->alt_pch_dirs.first) { String8 alt_obj_path = str8f(scratch.arena, "%S/%S", alt_dir_n->string, obj_name); if (hash_table_search_path_u64(debug_p_ht, alt_obj_path, &debug_p_obj_idx)) { break; } } @@ -1325,36 +1399,47 @@ lnk_merge_types(TP_Context *tp, TP_Arena *tp_temp, LNK_CodeViewInput *input) ProfBegin("Produce Hashes"); { ProfBegin("Alloc Hashes"); - U32Array indices[] = { - input->debug_p_indices, - input->int_obj_indices, - input->type_server_indices, + struct HashTarget { + TP_TaskFunc *hasher_task; + U32Array indices; + U32Array hash_indices; + } hash_targets[] = { + { lnk_hash_debug_t_task, input->debug_p_indices }, // hash .debug$P first so we can mix in hashes for precompiled sub leaves when hashing leaves in .debug$T + { lnk_hash_debug_t_task, input->int_obj_indices }, + { lnk_hash_debug_t_deep_task, input->type_server_indices }, }; - for EachElement(i, indices) { - for EachIndex(k, indices[i].count) { - U64 obj_idx = indices[i].v[k]; - CV_DebugT *debug_t = &input->debug_t_arr[obj_idx]; + + for EachElement(i, hash_targets) { + // reserve array for obj indices that need hashing + U32Array *h = &hash_targets[i].hash_indices; + h->count = 0; + h->v = push_array(scratch.arena, U32, hash_targets[i].indices.count); + + for EachIndex(k, hash_targets[i].indices.count) { + U32 obj_idx = hash_targets[i].indices.v[k]; CV_DebugH *debug_h = &input->debug_h_arr[obj_idx]; - debug_h->count = debug_t->count; - debug_h->v = push_array(scratch.arena, U64, debug_h->count); + + if (debug_h->count == 0) { + // alloc hashes + CV_DebugT *debug_t = &input->debug_t_arr[obj_idx]; + debug_h->count = debug_t->count; + debug_h->v = push_array(scratch.arena, U64, debug_h->count); + + // schedule obj types to be hashed + h->v[h->count++] = obj_idx; + } else { + // hash was loaded from .debug$H + } } } ProfEnd(); - - // hash .debug$P first so we can mix in hashes for precompiled sub leaves when hashing leaves in .debug$T - ProfBegin("Hash"); - task.indices = input->debug_p_indices; - ProfScope(".debug$P [Count: %llu]", input->debug_p_indices.count) - tp_for_parallel(tp, 0, task.indices.count, lnk_hash_debug_t_task, &task); - task.indices = input->int_obj_indices; - ProfScope(".debug$T [Count: %.*s]", str8_varg(str8_from_count(scratch.arena, task.indices.count))) - tp_for_parallel(tp, 0, task.indices.count, lnk_hash_debug_t_task, &task); - - task.indices = input->type_server_indices; - ProfScope("Type Servers [Count: %.*s]", str8_varg(str8_from_count(scratch.arena, task.indices.count))) - tp_for_parallel(tp, 0, task.indices.count, lnk_hash_debug_t_deep_task, &task); - ProfEnd(); + for EachElement(i, hash_targets) { + task.indices = hash_targets[i].hash_indices; + ProfBegin("Hash [Count: %.*s]", str8_varg(str8_from_count(scratch.arena, task.indices.count))); + tp_for_parallel(tp, 0, hash_targets[i].indices.count, hash_targets[i].hasher_task, &task); + ProfEnd(); + } #if BUILD_DEBUG for EachIndex(i, input->count) { diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index 3ab66c4a..1f97fa23 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -25,7 +25,7 @@ typedef struct LNK_SymbolInput typedef struct { - LNK_IO_Flags io_flags; + LNK_Config *config; U64 obj_count; B32 is_stripped; @@ -39,6 +39,7 @@ typedef struct String8List *debug_s_list_arr; String8List *debug_p_list_arr; String8List *debug_t_list_arr; + String8List *debug_h_list_arr; U32Array int_obj_indices; U32Array ext_obj_indices; @@ -278,7 +279,7 @@ typedef struct //////////////////////////////// // CodeView -internal LNK_CodeViewInput lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_IO_Flags io_flags, String8List lib_dir_list, String8List alt_pch_dirs, U64 objs_count, LNK_Obj **objs); +internal LNK_CodeViewInput lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, U64 objs_count, LNK_Obj **objs); internal int lnk_leaf_ref_compare (LNK_LeafRef a, LNK_LeafRef b); internal int lnk_leaf_ref_is_before (void *raw_a, void *raw_b); diff --git a/src/linker/lnk_log.h b/src/linker/lnk_log.h index 75080f98..781a4a20 100644 --- a/src/linker/lnk_log.h +++ b/src/linker/lnk_log.h @@ -108,6 +108,7 @@ typedef enum LNK_Warning_NoLargeAddressAwarenessForDll, LNK_Warning_TryingToExportEntryPoint, LNK_Warning_InferAsanFail, + LNK_Warning_GHash, LNK_Warning_Last, LNK_Error_Count diff --git a/src/llvm/llvm.c b/src/llvm/llvm.c new file mode 100644 index 00000000..d7337bde --- /dev/null +++ b/src/llvm/llvm.c @@ -0,0 +1,24 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal String8 +llvm_string_from_ghash_alg(LLVM_GHashAlg v) +{ + switch (v) { + case LLVM_GHashAlg_SHA1: return str8_lit("SHA1"); + case LLVM_GHashAlg_SHA1_8: return str8_lit("SHA1_8"); + case LLVM_GHashAlg_BLAKE3: return str8_lit("BALK3"); + } + return str8_zero(); +} + +internal U64 +llvm_hash_size_from_alg(LLVM_GHashAlg v) +{ + switch (v) { + case LLVM_GHashAlg_SHA1: return 20; + case LLVM_GHashAlg_SHA1_8: return 8; + case LLVM_GHashAlg_BLAKE3: return 8; + } + return 0; +} diff --git a/src/llvm/llvm.h b/src/llvm/llvm.h new file mode 100644 index 00000000..e0b61cc7 --- /dev/null +++ b/src/llvm/llvm.h @@ -0,0 +1,29 @@ +// Copyright (c) Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef LLVM_H +#define LLVM_H + +#define LLVM_GHash_Magic 0x133c9c5 +#define LLVM_GHash_CurrentVersion 0 + +typedef U16 LLVM_GHashAlg; +typedef enum LLVM_GHashAlgEnum +{ + LLVM_GHashAlg_SHA1 = 0, + LLVM_GHashAlg_SHA1_8 = 1, + LLVM_GHashAlg_BLAKE3 = 2, +} LLVM_GHashAlgEnum; + +typedef struct LLVM_GHash +{ + U32 magic; + LLVM_GHashAlg hash_alg; + U16 version; + // * hashes[] +} LLVM_GHash; + +internal String8 llvm_string_from_ghash_alg(LLVM_GHashAlg v); + +#endif // LLVM_H + diff --git a/src/torture/torture_main.c b/src/torture/torture_main.c index 7571fa37..e38f8199 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -54,6 +54,7 @@ #include "pdb/pdb_parse.h" #include "pdb/pdb_stringize.h" #include "dwarf/dwarf_inc.h" +#include "llvm/llvm.h" #include "rdi_from_coff/rdi_from_coff.h" #include "rdi_from_elf/rdi_from_elf.h" #include "rdi_from_pdb/rdi_from_pdb.h" @@ -126,6 +127,7 @@ #include "pdb/pdb_parse.c" #include "pdb/pdb_stringize.c" #include "dwarf/dwarf_inc.c" +#include "llvm/llvm.c" #include "rdi_from_coff/rdi_from_coff.c" #include "rdi_from_elf/rdi_from_elf.c" #include "rdi_from_pdb/rdi_from_pdb.c" diff --git a/src/torture/torture_radlink.c b/src/torture/torture_radlink.c index d9beebde..3cb0c418 100644 --- a/src/torture/torture_radlink.c +++ b/src/torture/torture_radlink.c @@ -5006,6 +5006,257 @@ TEST(pdbstripped) T_Ok(ipi.size == sizeof(PDB_TpiHeader)); } +TEST(ghash_check_corrupt) +{ + String8List t = {0}; str8_serial_begin(arena, &t); + str8_serial_push_u32(arena, &t, CV_Signature_C13); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_STRUCTURE, str8_struct(&(CV_LeafStruct){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_UNION, str8_struct(&(CV_LeafUnion){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_ENUM, str8_struct(&(CV_LeafEnum){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + String8 debug_t = str8_serial_end(arena, &t); + + String8List h = {0}; str8_serial_begin(arena, &h); + String8 debug_h = str8_serial_end(arena, &h); + + COFF_ObjWriter *cow = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_section(cow, str8_lit(".debug$T"), PE_DEBUG_SECTION_FLAGS, debug_t); + coff_obj_writer_push_section(cow, str8_lit(".debug$H"), PE_DEBUG_SECTION_FLAGS, debug_h); + String8 debug_obj = coff_obj_writer_serialize(arena, cow); + coff_obj_writer_release(&cow); + + T_Ok(t_write_file(str8_lit("debug.obj"), debug_obj)); + T_Ok(t_write_entry_obj()); + + String8 output = {0}; + t_invoke_(t_radlink_path(), str8_lit("/subsystem:console /entry:entry /out:a.exe /debug:full entry.obj debug.obj"), max_U64, arena, &output); + T_Ok(g_last_exit_code == 0); + + B32 is_warning_found = 0; + String8 debug_obj_path = t_make_file_path(arena, str8_lit("debug.obj")); + String8 expected_line = str8f(arena, "Warning(%03u): %S: .debug$H section is too small to contain the header", LNK_Warning_GHash, debug_obj_path, LLVM_GHash_Magic); + for (String8 i = output; i.size > 0 && !is_warning_found; ) { + String8 line = t_chop_line(&i); + is_warning_found = str8_match(expected_line, line, StringMatchFlag_CaseInsensitive); + } + T_Ok(is_warning_found); +} + +TEST(ghash_check_magic) +{ + String8List t = {0}; str8_serial_begin(arena, &t); + str8_serial_push_u32(arena, &t, CV_Signature_C13); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_STRUCTURE, str8_struct(&(CV_LeafStruct){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_UNION, str8_struct(&(CV_LeafUnion){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_ENUM, str8_struct(&(CV_LeafEnum){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + String8 debug_t = str8_serial_end(arena, &t); + +#if 0 + U64 *hashes = push_array(arena, U64, t.node_count); + U64 i = 0; + for EachNode(n, String8Node, t.first->next) { + blake3(&hashes[i], sizeof(hashes[i]), n->string.str, n->string.size); + i += 1; + } +#endif + String8List h = {0}; str8_serial_begin(arena, &h); + str8_serial_push_struct(arena, &h, (&(LLVM_GHash){ .magic = 123, .hash_alg = LLVM_GHashAlg_BLAKE3, .version = LLVM_GHash_CurrentVersion })); + str8_serial_push_u64(arena, &h, 0x6ebacae08af4fda5ull); + str8_serial_push_u64(arena, &h, 0xc385876694f9769aull); + str8_serial_push_u64(arena, &h, 0x7ea8a529a89b2288ull); + String8 debug_h = str8_serial_end(arena, &h); + + COFF_ObjWriter *cow = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_section(cow, str8_lit(".debug$T"), PE_DEBUG_SECTION_FLAGS, debug_t); + coff_obj_writer_push_section(cow, str8_lit(".debug$H"), PE_DEBUG_SECTION_FLAGS, debug_h); + String8 debug_obj = coff_obj_writer_serialize(arena, cow); + coff_obj_writer_release(&cow); + + T_Ok(t_write_file(str8_lit("debug.obj"), debug_obj)); + T_Ok(t_write_entry_obj()); + + String8 output = {0}; + t_invoke_(t_radlink_path(), str8_lit("/subsystem:console /entry:entry /out:a.exe /debug:full entry.obj debug.obj"), max_U64, arena, &output); + T_Ok(g_last_exit_code == 0); + + B32 is_warning_found = 0; + String8 debug_obj_path = t_make_file_path(arena, str8_lit("debug.obj")); + String8 expected_line = str8f(arena, "Warning(%03u): %S: .debug$H contains invalid magic: got 0x7b, expected 0x%x", LNK_Warning_GHash, debug_obj_path, LLVM_GHash_Magic); + for (String8 i = output; i.size > 0 && !is_warning_found; ) { + String8 line = t_chop_line(&i); + is_warning_found = str8_match(expected_line, line, StringMatchFlag_CaseInsensitive); + } + T_Ok(is_warning_found); +} + +TEST(ghash_check_version) +{ + String8List t = {0}; str8_serial_begin(arena, &t); + str8_serial_push_u32(arena, &t, CV_Signature_C13); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_STRUCTURE, str8_struct(&(CV_LeafStruct){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_UNION, str8_struct(&(CV_LeafUnion){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_ENUM, str8_struct(&(CV_LeafEnum){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + String8 debug_t = str8_serial_end(arena, &t); + +#if 0 + U64 *hashes = push_array(arena, U64, t.node_count); + U64 i = 0; + for EachNode(n, String8Node, t.first->next) { + blake3(&hashes[i], sizeof(hashes[i]), n->string.str, n->string.size); + i += 1; + } +#endif + String8List h = {0}; str8_serial_begin(arena, &h); + str8_serial_push_struct(arena, &h, (&(LLVM_GHash){ .magic = LLVM_GHash_Magic, .hash_alg = LLVM_GHashAlg_BLAKE3, .version = 0xbeef })); + str8_serial_push_u64(arena, &h, 0x6ebacae08af4fda5ull); + str8_serial_push_u64(arena, &h, 0xc385876694f9769aull); + str8_serial_push_u64(arena, &h, 0x7ea8a529a89b2288ull); + String8 debug_h = str8_serial_end(arena, &h); + + COFF_ObjWriter *cow = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_section(cow, str8_lit(".debug$T"), PE_DEBUG_SECTION_FLAGS, debug_t); + coff_obj_writer_push_section(cow, str8_lit(".debug$H"), PE_DEBUG_SECTION_FLAGS, debug_h); + String8 debug_obj = coff_obj_writer_serialize(arena, cow); + coff_obj_writer_release(&cow); + + T_Ok(t_write_file(str8_lit("debug.obj"), debug_obj)); + T_Ok(t_write_entry_obj()); + + String8 output = {0}; + t_invoke_(t_radlink_path(), str8_lit("/subsystem:console /entry:entry /out:a.exe /debug:full entry.obj debug.obj"), max_U64, arena, &output); + T_Ok(g_last_exit_code == 0); + + B32 is_warning_found = 0; + String8 debug_obj_path = t_make_file_path(arena, str8_lit("debug.obj")); + String8 expected_line = str8f(arena, "Warning(%03u): %S: mismatched .debug$H version: got %u, expected %u", LNK_Warning_GHash, debug_obj_path, 0xbeef, LLVM_GHash_CurrentVersion); + for (String8 i = output; i.size > 0 && !is_warning_found; ) { + String8 line = t_chop_line(&i); + is_warning_found = str8_match(expected_line, line, StringMatchFlag_CaseInsensitive); + } + T_Ok(is_warning_found); +} + +TEST(ghash_check_hash_alg) +{ + String8List t = {0}; str8_serial_begin(arena, &t); + str8_serial_push_u32(arena, &t, CV_Signature_C13); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_STRUCTURE, str8_struct(&(CV_LeafStruct){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_UNION, str8_struct(&(CV_LeafUnion){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_ENUM, str8_struct(&(CV_LeafEnum){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + String8 debug_t = str8_serial_end(arena, &t); + + String8List h = {0}; str8_serial_begin(arena, &h); + str8_serial_push_struct(arena, &h, (&(LLVM_GHash){ .magic = LLVM_GHash_Magic, .hash_alg = LLVM_GHashAlg_SHA1_8, .version = LLVM_GHash_CurrentVersion })); + str8_serial_push_u64(arena, &h, 0x6ebacae08af4fda5ull); + str8_serial_push_u64(arena, &h, 0xc385876694f9769aull); + str8_serial_push_u64(arena, &h, 0x7ea8a529a89b2288ull); + String8 debug_h = str8_serial_end(arena, &h); + + COFF_ObjWriter *cow = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_section(cow, str8_lit(".debug$T"), PE_DEBUG_SECTION_FLAGS, debug_t); + coff_obj_writer_push_section(cow, str8_lit(".debug$H"), PE_DEBUG_SECTION_FLAGS, debug_h); + String8 debug_obj = coff_obj_writer_serialize(arena, cow); + coff_obj_writer_release(&cow); + + T_Ok(t_write_file(str8_lit("debug.obj"), debug_obj)); + T_Ok(t_write_entry_obj()); + + String8 output = {0}; + t_invoke_(t_radlink_path(), str8_lit("/subsystem:console /entry:entry /out:a.exe /debug:full entry.obj debug.obj"), max_U64, arena, &output); + T_Ok(g_last_exit_code == 0); + + B32 is_warning_found = 0; + String8 debug_obj_path = t_make_file_path(arena, str8_lit("debug.obj")); + String8 expected_line = str8f(arena, "Warning(%03u): %S: mismatched .debug$H hash algorithm: got SHA1_8, expected BALK3", LNK_Warning_GHash, debug_obj_path); + for (String8 i = output; i.size > 0 && !is_warning_found; ) { + String8 line = t_chop_line(&i); + is_warning_found = str8_match(expected_line, line, StringMatchFlag_CaseInsensitive); + } + T_Ok(is_warning_found); +} + +TEST(ghash_match_debug_t) +{ + String8List t = {0}; str8_serial_begin(arena, &t); + str8_serial_push_u32(arena, &t, CV_Signature_C13); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_STRUCTURE, str8_struct(&(CV_LeafStruct){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_UNION, str8_struct(&(CV_LeafUnion){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + //str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_ENUM, str8_struct(&(CV_LeafEnum){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + String8 debug_t = str8_serial_end(arena, &t); + + String8List h = {0}; str8_serial_begin(arena, &h); + str8_serial_push_struct(arena, &h, (&(LLVM_GHash){ .magic = LLVM_GHash_Magic, .hash_alg = LLVM_GHashAlg_BLAKE3, .version = LLVM_GHash_CurrentVersion })); + str8_serial_push_u64(arena, &h, 0x6ebacae08af4fda5ull); + str8_serial_push_u64(arena, &h, 0xc385876694f9769aull); + str8_serial_push_u64(arena, &h, 0x7ea8a529a89b2288ull); + String8 debug_h = str8_serial_end(arena, &h); + + COFF_ObjWriter *cow = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_section(cow, str8_lit(".debug$T"), PE_DEBUG_SECTION_FLAGS, debug_t); + coff_obj_writer_push_section(cow, str8_lit(".debug$H"), PE_DEBUG_SECTION_FLAGS, debug_h); + String8 debug_obj = coff_obj_writer_serialize(arena, cow); + coff_obj_writer_release(&cow); + + T_Ok(t_write_file(str8_lit("debug.obj"), debug_obj)); + T_Ok(t_write_entry_obj()); + + String8 output = {0}; + t_invoke_(t_radlink_path(), str8_lit("/subsystem:console /entry:entry /out:a.exe /debug:full entry.obj debug.obj"), max_U64, arena, &output); + T_Ok(g_last_exit_code == 0); + + B32 is_warning_found = 0; + String8 debug_obj_path = t_make_file_path(arena, str8_lit("debug.obj")); + String8 expected_line = str8f(arena, "Warning(%03u): %S: mismatched .debug$H hash count and type count: got 3 hashes for 2 types", LNK_Warning_GHash, debug_obj_path); + for (String8 i = output; i.size > 0 && !is_warning_found; ) { + String8 line = t_chop_line(&i); + is_warning_found = str8_match(expected_line, line, StringMatchFlag_CaseInsensitive); + } + T_Ok(is_warning_found); +} + +#if 0 +TEST(ghash_basic) +{ + String8 a_obj; + { + String8List t = {0}; str8_serial_begin(arena, &t); + str8_serial_push_u32(arena, &t, CV_Signature_C13); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_STRUCTURE, str8_struct(&(CV_LeafStruct){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_UNION, str8_struct(&(CV_LeafUnion){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + str8_serial_push_string(arena, &t, cv_make_leaf(arena, CV_LeafKind_ENUM, str8_struct(&(CV_LeafEnum){ .props = CV_TypeProp_FwdRef }), CV_LeafAlign)); + String8 debug_t = str8_serial_end(arena, &t); + + String8List h = {0}; str8_serial_begin(arena, &h); + str8_serial_push_struct(arena, &h, (&(LLVM_GHash){ .magic = LLVM_GHash_Magic, .hash_alg = LLVM_GHashAlg_BLAKE3, .version = LLVM_GHash_CurrentVersion })); + str8_serial_push_u64(arena, &h, 1); + str8_serial_push_u64(arena, &h, 2); + str8_serial_push_u64(arena, &h, 3); + String8 debug_h = str8_serial_end(arena, &h); + + COFF_ObjWriter *cow = coff_obj_writer_alloc(0, COFF_MachineType_X64); + coff_obj_writer_push_section(cow, str8_lit(".debug$T"), PE_DEBUG_SECTION_FLAGS, debug_t); + coff_obj_writer_push_section(cow, str8_lit(".debug$H"), PE_DEBUG_SECTION_FLAGS, debug_h); + a_obj = coff_obj_writer_serialize(arena, cow); + coff_obj_writer_release(&cow); + } + + T_Ok(t_write_file(str8_lit("a.obj"), a_obj)); + T_Ok(t_write_entry_obj()); + + String8 output = {0}; + t_invoke_(t_radlink_path(), str8_lit("/subsystem:console /entry:entry /out:a.exe /debug:full entry.obj a.obj"), max_U64, arena, &output); + T_Ok(g_last_exit_code == 0); + + B32 is_warning_found = 0; + String8 a_obj_path = t_make_file_path(arena, str8_lit("a.obj")); + String8 expected_line = str8f(arena, "Warning(%03u): %S: mismatched .debug$H hash count and type count: got 3 hashes for 2 types", LNK_Warning_GHash, a_obj_path); + for (String8 i = output; i.size > 0 && !is_warning_found; ) { + String8 line = t_chop_line(&i); + is_warning_found = str8_match(expected_line, line, StringMatchFlag_CaseInsensitive); + } + T_Ok(is_warning_found); +} +#endif + TEST(patch_cv_symbol_tree) { String8List raw_symbols = {0};