From c6ba47542714aea6adc58f6d51ea4429c06a0d0c Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Wed, 2 Apr 2025 11:14:33 -0700 Subject: [PATCH] produce image layout map --- src/linker/lnk.c | 178 +++++++++++++++++++++++++++++++++++++++- src/linker/lnk_chunk.h | 3 +- src/linker/lnk_config.c | 13 ++- src/linker/lnk_config.h | 4 + src/linker/lnk_obj.c | 5 ++ 5 files changed, 198 insertions(+), 5 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 461a28a5..6c2bc7ec 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -3139,6 +3139,169 @@ LNK_CHUNK_VISITOR_SIG(lnk_max_tls_align) return 0; } +global LNK_Section **g_rad_sort_sect_id_map = 0; + +typedef struct +{ + U64 off; + LNK_Chunk *chunk; +} LNK_ChunkOffPair; + +internal int +lnk_map_sort_on_chunk_file_off(void *raw_a, void *raw_b) +{ + LNK_Chunk **a = raw_a; + LNK_Chunk **b = raw_b; + + U64 a_file_off = lnk_virt_off_from_chunk_ref(g_rad_sort_sect_id_map, (*a)->ref); + U64 b_file_off = lnk_virt_off_from_chunk_ref(g_rad_sort_sect_id_map, (*b)->ref); + + int is_before = a_file_off < b_file_off; + return is_before; +} + +internal int +lnk_map_sort_on_chunk_off(void *raw_a, void *raw_b) +{ + LNK_ChunkOffPair *a = raw_a; + LNK_ChunkOffPair *b = raw_b; + + int is_before = a->off < b->off; + return is_before; +} + +internal U64 +lnk_chunk_off_pair_array_bsearch(LNK_ChunkOffPair *arr, U64 count, U64 value) +{ + if(count > 1 && arr[0].off <= value && value < arr[count-1].off) + { + U64 l = 0; + U64 r = count - 1; + for(; l <= r; ) + { + U64 m = l + (r - l) / 2; + if(arr[m].off == value) + { + return m; + } + else if(arr[m].off < value) + { + l = m + 1; + } + else + { + r = m - 1; + } + } + } + else if (count == 1 && arr[0].off == value) + { + return 0; + } + return max_U64; +} + +internal String8List +lnk_build_rad_chunk_map(Arena *arena, String8 image_data, U64 thread_count, LNK_ObjList objs, LNK_SectionTable *st, LNK_SymbolTable *symtab) +{ + Temp scratch = scratch_begin(&arena, 1); + + LNK_Section **sect_id_map = lnk_sect_id_map_from_section_table(scratch.arena, st); + + String8List map = {0}; + + str8_list_pushf(arena, &map, "# CHUNKS\n"); + for (LNK_SectionNode *sect_n = st->list.first; sect_n != 0; sect_n = sect_n->next) { + LNK_Section *sect = §_n->data; + if (sect->has_layout) { + LNK_Chunk **chunks = push_array_no_zero(scratch.arena, LNK_Chunk *, sect->layout.total_count); + MemoryCopyTyped(chunks, sect->layout.chunk_ptr_array, sect->layout.total_count); + + g_rad_sort_sect_id_map = sect_id_map; + radsort(chunks, sect->layout.total_count, lnk_map_sort_on_chunk_file_off); + + str8_list_pushf(arena, &map, "%S\n", sect->name); + str8_list_pushf(arena, &map, "%-16s %-8s %-8s %-16s %-8s %s\n", "Sect:Offset", "VirtSize", "FileSize", "Blake3", "ChunkRef", "Source"); + for (U64 chunk_idx = 0; chunk_idx < sect->layout.total_count; ++chunk_idx) { + LNK_Chunk *chunk = chunks[chunk_idx]; + if (chunk->type == LNK_Chunk_Leaf && chunk != g_null_chunk_ptr) { + Temp temp = temp_begin(scratch.arena); + + ISectOff sc = lnk_sc_from_chunk_ref(sect_id_map, chunk->ref); + U64 virt_size = lnk_virt_size_from_chunk_ref(sect_id_map, chunk->ref); + U64 file_size = lnk_file_size_from_chunk_ref(sect_id_map, chunk->ref); + String8 chunk_data = lnk_data_from_chunk_ref(sect_id_map, image_data, chunk->ref); + + U128 chunk_hash = {0}; + if (~sect->flags & COFF_SectionFlag_CntUninitializedData) { + blake3_hasher hasher; blake3_hasher_init(&hasher); + blake3_hasher_update(&hasher, chunk_data.str, chunk_data.size); + blake3_hasher_finalize(&hasher, (U8 *)&chunk_hash, sizeof(chunk_hash)); + } + + String8 address_str = push_str8f(temp.arena, "%04x:%08x", sc.isect, sc.off); + String8 virt_size_str = push_str8f(temp.arena, "%08x", virt_size); + String8 file_size_str = push_str8f(temp.arena, "%08x", file_size); + String8 chunk_hash_str = push_str8f(temp.arena, "%08x%08x", chunk_hash.u64[0], chunk_hash.u64[1]); + String8 chunk_ref_str = push_str8f(temp.arena, "{%llx,%llx}", chunk->ref.sect_id, chunk->ref.chunk_id); + String8 source_str = str8_lit("\?\?\?"); + if (chunk->obj) { + if (chunk->obj->lib_path.size) { + String8 lib_name = chunk->obj->lib_path; + lib_name = str8_skip_last_slash(lib_name); + lib_name = str8_chop_last_dot(lib_name); + + String8 obj_name = chunk->obj->path; + obj_name = str8_skip_last_slash(obj_name); + + source_str = push_str8f(temp.arena, "%S:%S", lib_name, obj_name); + } else { + source_str = push_str8f(temp.arena, "%S", chunk->obj->path); + } + } + + str8_list_pushf(arena, &map, "%-16S %-8S %-8S %-16S %-8S %S\n", address_str, virt_size_str, file_size_str, chunk_hash_str, chunk_ref_str, source_str); + + temp_end(temp); + } + } + str8_list_pushf(arena, &map, "\n"); + } + } + + str8_list_pushf(arena, &map, "# SYMBOLS\n"); + str8_list_pushf(arena, &map, "%-8s %s\n", "ChunkRef", "Symbol"); + for (LNK_ObjNode *obj_n = objs.first; obj_n != 0; obj_n = obj_n->next) { + LNK_Obj *obj = &obj_n->data; + for (LNK_SymbolNode *symbol_n = obj->symbol_list.first; symbol_n != 0; symbol_n = symbol_n->next) { + LNK_Symbol *symbol = symbol_n->data; + if (LNK_Symbol_IsDefined(symbol->type)) { + if (symbol->u.defined.value_type == LNK_DefinedSymbolValue_Chunk) { + LNK_Chunk *chunk = symbol->u.defined.u.chunk; + String8 chunk_ref_str = push_str8f(scratch.arena, "{%llx,%llx}", chunk->ref.sect_id, chunk->ref.chunk_id); + + String8 lib_name = obj->lib_path; + lib_name = str8_skip_last_slash(lib_name); + lib_name = str8_chop_last_dot(lib_name); + + String8 obj_name = obj->path; + obj_name = str8_skip_last_slash(obj_name); + + str8_list_pushf(arena, &map, "%-8S (%S%s%S) %S\n", + chunk_ref_str, + lib_name, lib_name.size ? ":" : "", obj_name, + symbol->name); + } + } + } + } + + //str8_list_pushf(arena, &map, "%16s %30s %23s %10s\n\n", "Address", "Publics by Value", "Rva+Base", "Lib:Object"); + + scratch_end(scratch); + return map; +} + internal void lnk_run(int argc, char **argv) { @@ -3167,6 +3330,7 @@ lnk_run(int argc, char **argv) State_BuildBaseRelocs, State_FinalizeImage, State_BuildImpLib, + State_BuildRadChunkMap, State_BuildDebugInfo, }; struct StateNode { @@ -3254,10 +3418,11 @@ lnk_run(int argc, char **argv) B32 report_unresolved_symbols = 1; B32 check_unused_delay_loads = !!(config->flags & LNK_ConfigFlag_CheckUnusedDelayLoadDll); B32 build_imp_lib = config->build_imp_lib; + B32 build_rad_chunk_map = (config->rad_chunk_map == LNK_SwitchState_Yes); LNK_ObjList obj_list = {0}; LNK_LibList lib_index[LNK_InputSource_Count] = {0}; String8 image_data = str8_zero(); - OS_Handle image_write_thread = {0}; + OS_Handle image_write_thread = {0}; // init state machine struct StateList state_list = {0}; @@ -4121,6 +4286,12 @@ lnk_run(int argc, char **argv) lnk_timer_end(LNK_Timer_Lib); ProfEnd(); } break; + case State_BuildRadChunkMap: { + ProfBegin("RAD Chunk Map"); + String8List map = lnk_build_rad_chunk_map(scratch.arena, image_data, config->worker_count, obj_list, st, symtab); + lnk_write_data_list_to_file_path(config->rad_chunk_map_name, config->temp_rad_chunk_map_name, map); + ProfEnd(); + } break; case State_BuildDebugInfo: { ProfBegin("Debug Info"); lnk_timer_begin(LNK_Timer_Debug); @@ -4319,6 +4490,11 @@ lnk_run(int argc, char **argv) continue; } } + if (build_rad_chunk_map) { + build_rad_chunk_map = 0; + state_list_push(scratch.arena, state_list, State_BuildRadChunkMap); + continue; + } if (build_debug_info) { build_debug_info = 0; state_list_push(scratch.arena, state_list, State_BuildDebugInfo); diff --git a/src/linker/lnk_chunk.h b/src/linker/lnk_chunk.h index 0b64d968..e2c54174 100644 --- a/src/linker/lnk_chunk.h +++ b/src/linker/lnk_chunk.h @@ -40,6 +40,7 @@ typedef struct LNK_Chunk struct LNK_ChunkList *list; struct LNK_ChunkArray *arr; } u; + struct LNK_Obj *obj; #if LNK_DEBUG_CHUNKS String8 debug; #endif @@ -115,7 +116,7 @@ typedef struct LNK_ChunkPadArrayNode } LNK_ChunkPadArrayNode; typedef struct LNK_ChunkPadArrayList { - U64 count; + U64 count; LNK_ChunkPadArrayNode *first; LNK_ChunkPadArrayNode *last; } LNK_ChunkPadArrayList; diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 72b3dd12..e1f20096 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -132,6 +132,7 @@ global read_only struct { LNK_CmdSwitch_Rad_Age, "RAD_AGE", ":#", "Age embeded in EXE and PDB, used to validate incremental build. Default is 1." }, { LNK_CmdSwitch_Rad_BuildInfo, "RAD_BUILD_INFO", "", "Print build info and exit." }, { LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll, "RAD_CHECK_UNUSED_DELAY_LOAD_DLL", "[:NO]", "" }, + { LNK_CmdSwitch_Rad_ChunkMap, "RAD_CHUNK_MAP", ":FILENAME", "Emit file with the output image's layout description." }, { LNK_CmdSwitch_Rad_Debug, "RAD_DEBUG", "[:NO]", "Emit RAD debug info file." }, { LNK_CmdSwitch_Rad_DebugAltPath, "RAD_DEBUGALTPATH", "", "" }, { LNK_CmdSwitch_Rad_DebugName, "RAD_DEBUG_NAME", ":FILENAME", "Sets file name for RAD debug info file." }, @@ -1522,6 +1523,11 @@ lnk_apply_cmd_option_to_config(Arena *arena, LNK_Config *config, String8 cmd_nam lnk_cmd_switch_set_flag_64(obj_path, lib_path, cmd_switch, value_strings, &config->flags, LNK_ConfigFlag_CheckUnusedDelayLoadDll); } break; + case LNK_CmdSwitch_Rad_ChunkMap: { + lnk_cmd_switch_parse_string_copy(arena, obj_path, lib_path, cmd_switch, value_strings, &config->rad_chunk_map_name); + config->rad_chunk_map = LNK_SwitchState_Yes; + } break; + case LNK_CmdSwitch_Rad_Debug: { lnk_cmd_switch_parse_flag(obj_path, lib_path, cmd_switch, value_strings, &config->rad_debug); } break; @@ -2060,9 +2066,10 @@ lnk_config_from_cmd_line(Arena *arena, String8List raw_cmd_line) // create temporary files names if (config->write_temp_files == LNK_SwitchState_Yes) { - config->temp_image_name = push_str8f(arena, "%S.tmp%x", config->image_name, config->time_stamp); - config->temp_pdb_name = push_str8f(arena, "%S.tmp%x", config->pdb_name, config->time_stamp); - config->temp_rad_debug_name = push_str8f(arena, "%S.tmp%x", config->rad_debug_name, config->time_stamp); + config->temp_rad_chunk_map_name = push_str8f(arena, "%S.tmp%x", config->rad_chunk_map_name, config->time_stamp); + config->temp_image_name = push_str8f(arena, "%S.tmp%x", config->image_name, config->time_stamp); + config->temp_pdb_name = push_str8f(arena, "%S.tmp%x", config->pdb_name, config->time_stamp); + config->temp_rad_debug_name = push_str8f(arena, "%S.tmp%x", config->rad_debug_name, config->time_stamp); } if (lnk_get_log_status(LNK_Log_Debug)) { diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index ea9d3edc..81f64066 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -128,6 +128,7 @@ typedef enum LNK_CmdSwitch_Rad_Age, LNK_CmdSwitch_Rad_BuildInfo, LNK_CmdSwitch_Rad_CheckUnusedDelayLoadDll, + LNK_CmdSwitch_Rad_ChunkMap, LNK_CmdSwitch_Rad_Debug, LNK_CmdSwitch_Rad_DebugName, LNK_CmdSwitch_Rad_DebugAltPath, @@ -355,6 +356,8 @@ typedef struct LNK_Config String8 manifest_ui_access; String8List manifest_dependency_list; LNK_SwitchState rad_debug; + LNK_SwitchState rad_chunk_map; + String8 rad_chunk_map_name; String8 rad_debug_name; String8 rad_debug_alt_path; String8List include_symbol_list; @@ -369,6 +372,7 @@ typedef struct LNK_Config String8 temp_image_name; String8 temp_pdb_name; String8 temp_rad_debug_name; + String8 temp_rad_chunk_map_name; } LNK_Config; typedef enum diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index d53802d2..1c3f7705 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -410,6 +410,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) chunk->input_idx = LNK_MakeChunkInputIdx(obj_idx, sect_idx); chunk->flags = coff_sect->flags; chunk->u.leaf = data; + chunk->obj = obj; lnk_chunk_set_debugf(arena, chunk, "%S: name: %S, obj_idx: 0x%llX isect: 0x%llX", cached_path, sect_name_arr[sect_idx], obj_idx, sect_idx); } @@ -425,6 +426,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) master_common_block->flags = LNK_BSS_SECTION_FLAGS; master_common_block->associate = 0; master_common_block->u.list = push_array(arena, LNK_ChunkList, 1); + master_common_block->obj = obj; lnk_chunk_set_debugf(arena, master_common_block, "%S: master common block", cached_path); LNK_ChunkPtr *chunk_ptr_arr = push_array_no_zero(arena, LNK_ChunkPtr, chunk_count); @@ -787,6 +789,7 @@ lnk_symbol_array_from_coff(Arena *arena, chunk_list->input_idx = chunk->input_idx; chunk_list->flags = chunk->flags; chunk_list->u.list = push_array(arena, LNK_ChunkList, 1); + chunk_list->obj = obj; lnk_chunk_set_debugf(arena, chunk_list, "%S: function chunk list for %S", obj_path, symbol.name); // update properties on first chunk @@ -834,6 +837,7 @@ lnk_symbol_array_from_coff(Arena *arena, split_chunk->is_discarded = current->data->is_discarded; split_chunk->flags = current->data->flags; split_chunk->u.leaf = right_data; + split_chunk->obj = obj; lnk_chunk_set_debugf(arena, split_chunk, "%S: chunk split on function %S SECT%x SPLIT_POS %#llx", obj_path, symbol.name, symbol.section_number, split_pos); LNK_ChunkNode *split_node = push_array(arena, LNK_ChunkNode, 1); @@ -985,6 +989,7 @@ lnk_symbol_array_from_coff(Arena *arena, chunk->type = LNK_Chunk_Leaf; chunk->flags = master_common_block->flags; chunk->u.leaf = str8(0, parsed_symbol.value); + chunk->obj = obj; lnk_chunk_set_debugf(arena, chunk, "%S: common block %S", obj_path, parsed_symbol.name); lnk_chunk_list_push(arena, master_common_block->u.list, chunk);