add support for LLVM ghash sections

This commit is contained in:
Nikita Smith
2026-04-20 10:39:50 -07:00
parent cd4f81d473
commit 0a791e5876
10 changed files with 449 additions and 39 deletions
+6 -3
View File
@@ -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;
+12
View File
@@ -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;
+2
View File
@@ -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 --------------------------------------------------------
+119 -34
View File
@@ -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) {
+3 -2
View File
@@ -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);
+1
View File
@@ -108,6 +108,7 @@ typedef enum
LNK_Warning_NoLargeAddressAwarenessForDll,
LNK_Warning_TryingToExportEntryPoint,
LNK_Warning_InferAsanFail,
LNK_Warning_GHash,
LNK_Warning_Last,
LNK_Error_Count
+24
View File
@@ -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;
}
+29
View File
@@ -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
+2
View File
@@ -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"
+251
View File
@@ -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};