diff --git a/src/linker/lnk.c b/src/linker/lnk.c index ff9b235b..0ae43fdc 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1350,7 +1350,7 @@ lnk_load_inputs(TP_Context *tp, TP_Arena *arena, LNK_Config *config, LNK_Inputer AssertAlways(include_obj_count == 1); if (obj_with_includes) { - DLLInsert(link->objs.first, link->objs.last, obj_with_includes->node, include_obj); + DLLInsert(link->objs.first, link->objs.last, obj_with_includes->self, include_obj); link->objs.count += 1; } else { lnk_obj_list_push_node(&link->objs, include_obj); @@ -1579,6 +1579,40 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) } } +internal LNK_Lib * +lnk_find_first_crt_lib(LNK_Config *config, LNK_Inputer *inputer) +{ + Temp scratch = scratch_begin(0, 0); + + LNK_Lib *result = 0; + + String8 crt_lib_names[] = { + str8_lit("msvcrt"), + str8_lit("msvcrtd"), + str8_lit("libcmt"), + str8_lit("libcmtd"), + }; + + for EachNode(n, LNK_Input, inputer->libs.first) { + String8 lib_name = str8_chop_last_dot(str8_skip_last_slash(n->path)); + for EachElement(i, crt_lib_names) { + if (str8_match(lib_name, crt_lib_names[i], StringMatchFlag_CaseInsensitive)) { + if (result == 0) { + result = hash_table_search_path_raw(inputer->libs_ht, n->path); + break; + } else { + LNK_Lib *lib = hash_table_search_path_raw(inputer->libs_ht, n->path); + result = lib->input_idx < result->input_idx ? lib : result; + break; + } + } + } + } + + scratch_end(scratch); + return result; +} + internal void lnk_link_inputs(TP_Context *tp, TP_Arena *arena, @@ -1598,6 +1632,57 @@ lnk_link_inputs(TP_Context *tp, for EachNode(lib_n, LNK_LibNode, link->libs.first) { LNK_Lib *lib = &lib_n->data; + if (config->machine != COFF_MachineType_Unknown) { + if (config->infer_asan_libs == LNK_SwitchState_Yes) { + if ( ! link->asan_libs_resolved) { + LNK_Lib *crt_lib = lnk_find_first_crt_lib(config, inputer); + if (crt_lib != 0) { + String8 crt_lib_name = str8_chop_last_dot(str8_skip_last_slash(crt_lib->path)); + + String8 arch_name = {0}; + if (config->machine == COFF_MachineType_X64) { + arch_name = str8_lit("x86_64"); + } else if (config->machine == COFF_MachineType_X86) { + arch_name = str8_lit("i386"); + } + + if (arch_name.size) { + B32 link_vc_libs = lnk_symbol_table_searchf(symtab, "__you_must_link_with_VCAsan_lib") != 0 || + lnk_symbol_table_searchf(symtab, "___you_must_link_with_VCAsan_lib") != 0; + if (str8_match(crt_lib_name, str8_lit("msvcrt"), StringMatchFlag_CaseInsensitive) || str8_match(crt_lib_name, str8_lit("msvcrtd"), StringMatchFlag_CaseInsensitive)) { + String8 dynamic_lib_name = str8f(inputer->arena, "clang_rt.asan_dynamic-%S.lib", arch_name); + String8 thunk_lib_name = str8f(inputer->arena, "clang_rt.asan_static_runtime_thunk-%S.lib", arch_name); + lnk_whole_archive(config, thunk_lib_name); + lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Obj, dynamic_lib_name); + lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Obj, thunk_lib_name); + if (link_vc_libs) { + if (str8_match(crt_lib_name, str8_lit("msvcrtd"), StringMatchFlag_CaseInsensitive)) { + lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Obj, str8_lit("libvcasand.lib")); + } else { + lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Obj, str8_lit("libvcasan.lib")); + } + } + } else if (str8_match(crt_lib_name, str8_lit("libcmt"), StringMatchFlag_CaseInsensitive) || str8_match(crt_lib_name, str8_lit("libcmtd"), StringMatchFlag_CaseInsensitive)) { + String8 dynamic_lib_name = str8f(inputer->arena, "clang_rt.asan_dynamic-%S.lib", arch_name); + String8 thunk_lib_name = str8f(inputer->arena, "clang_rt.asan_dynamic_runtime_thunk-%S.lib", arch_name); + lnk_whole_archive(config, thunk_lib_name); + lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Obj, dynamic_lib_name); + lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Obj, thunk_lib_name); + if (link_vc_libs) { + if (str8_match(crt_lib_name, str8_lit("libcmtd"), StringMatchFlag_CaseInsensitive)) { + lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Obj, str8_lit("vcasand.lib")); + } else { + lnk_inputer_push_lib_thin(inputer, config, LNK_InputSource_Obj, str8_lit("vcasan.lib")); + } + } + } + link->asan_libs_resolved = 1; + } + } + } + } + } + LNK_LibMemberInfo *lib_member_infos = hash_table_search_raw_raw(link->lib_member_infos_ht, lib); if (lib_member_infos == 0) { lib_member_infos = push_array(link->arena, LNK_LibMemberInfo, lib->member_count); @@ -1615,15 +1700,15 @@ lnk_link_inputs(TP_Context *tp, lnk_load_inputs(tp, arena, config, inputer, symtab, link); if (link_whole_archive) { + local_persist LNK_Symbol *null_symbol = 0; + if (null_symbol == 0) { + null_symbol = push_array(inputer->arena, LNK_Symbol, 1); + null_symbol->refs = push_array(inputer->arena, LNK_ObjSymbolRefNode, 1); + null_symbol->refs->v.obj = &link->objs.first->data; + } LNK_LibMemberRef *member_refs = push_array(scratch.arena, LNK_LibMemberRef, lib->member_count); for EachIndex(member_idx, lib->member_count) { - static LNK_Symbol *null_symbol = 0; - if (null_symbol == 0) { - null_symbol = push_array(inputer->arena, LNK_Symbol, 1); - null_symbol->refs = push_array(inputer->arena, LNK_ObjSymbolRefNode, 1); - null_symbol->refs->v.obj = &link->objs.first->data; - } - lnk_queue_lib_member(scratch.arena, &member_ref_lists[0], null_symbol, lib, lib_member_infos, member_idx); + lnk_queue_lib_member(arena->v[0], &member_ref_lists[0], null_symbol, lib, lib_member_infos, member_idx); } } else { // search symbols in lib diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 2f8b8fbf..7ce1ebe3 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -37,7 +37,6 @@ typedef struct LNK_Input B32 has_disk_read_failed; B32 exclude_from_debug_info; LNK_LibMemberRef *link_member; - void *loaded_input; struct LNK_Input *next; } LNK_Input; @@ -104,6 +103,7 @@ typedef struct LNK_Link HashTable *lib_member_infos_ht; LNK_LibMemberRefList imports; B32 try_to_resolve_entry_point; + B32 asan_libs_resolved; } LNK_Link; typedef struct LNK_LinkResult diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 6d5d7496..ffb665b5 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -974,6 +974,15 @@ lnk_include_symbol(LNK_Config *config, String8 name, LNK_Obj *obj) hash_table_push_string_raw(config->arena, config->include_symbol_ht, name, node); } +internal void +lnk_whole_archive(LNK_Config *config, String8 lib_name) +{ + String8Node value = { .string = lib_name }; + String8List value_strings = {0}; + str8_list_push_node(&value_strings, &value); + lnk_apply_cmd_option_to_config(config, str8_lit("wholearchive"), value_strings, 0); +} + internal void lnk_print_build_info() { @@ -1443,6 +1452,10 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List } } break; + case LNK_CmdSwitch_InferAsanLibs: { + lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->infer_asan_libs); + } break; + case LNK_CmdSwitch_LargeAddressAware: { lnk_cmd_switch_set_flag_16(obj, cmd_switch, value_strings, &config->file_characteristics, PE_ImageFileCharacteristic_LARGE_ADDRESS_AWARE); } break; diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index 70d20ffb..56b1eb4a 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -293,6 +293,7 @@ typedef struct LNK_Config U64 function_pad_min; U64 *manifest_resource_id; B32 no_default_libs; + LNK_SwitchState infer_asan_libs; Version link_ver; Version os_ver; Version image_ver; @@ -550,6 +551,8 @@ internal B32 lnk_is_lib_disallowed(LNK_Config *config, String8 path); internal void lnk_include_symbol(LNK_Config *config, String8 name, struct LNK_Obj *obj); +internal void lnk_whole_archive(LNK_Config *config, String8 lib_name); + // --- Config ------------------------------------------------------------------ internal void lnk_apply_cmd_option_to_config(LNK_Config *config, String8 name, String8List value_list, struct LNK_Obj *obj); diff --git a/src/linker/lnk_log.h b/src/linker/lnk_log.h index c92b0e52..75080f98 100644 --- a/src/linker/lnk_log.h +++ b/src/linker/lnk_log.h @@ -107,6 +107,7 @@ typedef enum LNK_Warning_DirectiveSectionWithRelocs, LNK_Warning_NoLargeAddressAwarenessForDll, LNK_Warning_TryingToExportEntryPoint, + LNK_Warning_InferAsanFail, LNK_Warning_Last, LNK_Error_Count diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 90bf0464..220af7f3 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -336,7 +336,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) obj->exclude_from_debug_info = input->exclude_from_debug_info; obj->hotpatch = hotpatch; obj->associated_sections = associated_sections; - obj->node = &task->objs[task_id]; + obj->self = &task->objs[task_id]; obj->link_member = input->link_member; } diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 9fca1cb5..b852bce7 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -16,10 +16,8 @@ typedef struct LNK_Obj B8 exclude_from_debug_info; U32Node **associated_sections; LNK_SymbolHashTrie **symlinks; - struct LNK_LibMemberRef *link_member; - - struct LNK_ObjNode *node; + struct LNK_ObjNode *self; } LNK_Obj; typedef struct LNK_ObjNode diff --git a/src/torture/torture.c b/src/torture/torture.c index e458edb9..a9b5668f 100644 --- a/src/torture/torture.c +++ b/src/torture/torture.c @@ -119,6 +119,24 @@ t_radbin_path(void) return path; } +internal String8 +t_cl_path(void) +{ + local_persist String8 path = {0}; + if (path.size == 0) { + local_persist U8 buffer[4096]; + ArenaParams params = { .reserve_size = sizeof(buffer), .commit_size = sizeof(buffer), .optional_backing_buffer = buffer }; + Arena *arena = arena_alloc_(¶ms); +#if OS_WINDOWS + path = str8_lit("cl.exe"); +#else + path = str8_lit("cl"); +#endif + AssertAlways(path.size); + } + return path; +} + internal String8 t_radlink_path(void) { diff --git a/src/torture/torture.h b/src/torture/torture.h index 2c5b5a89..f0e1fe8b 100644 --- a/src/torture/torture.h +++ b/src/torture/torture.h @@ -91,6 +91,10 @@ internal void t_run_caller(void *raw_ctx); internal void t_run_fail_handler(void *raw_ctx); internal T_RunResult t_run(T_Run run); +internal String8 t_radbin_path(void); +internal String8 t_cl_path(void); +internal String8 t_radlink_path(void); + internal B32 t_invoke(String8 exe, String8 cmdline, U64 timeout); diff --git a/src/torture/torture_radlink.c b/src/torture/torture_radlink.c index 8bbf6846..49c38e74 100644 --- a/src/torture/torture_radlink.c +++ b/src/torture/torture_radlink.c @@ -4837,19 +4837,111 @@ T_BeginTest(whole_archive) } T_EndTest; +#if OS_WINDOWS + +internal B32 +t_radlink_validate_asan_out(String8 obj_name) +{ + Temp scratch = scratch_begin(0,0); + B32 is_ok = 0; + + t_invoke_(t_radlink_path(), str8f(scratch.arena, "%S /debug:full", obj_name), max_U64, 0, 0); + if (g_last_exit_code != 0) { goto exit; } + + String8 exe_path = t_make_file_path(scratch.arena, str8f(scratch.arena, "%S.exe", str8_chop_last_dot(obj_name))); + String8 out = {0}; + t_invoke_(exe_path, str8_zero(), max_U64, scratch.arena, &out); + if (g_last_exit_code == 0) { + goto exit; + } + + String8 s = out; + + String8 header = t_chop_line(&s); + if ( ! str8_match(header, str8_lit("================================================================="), 0)) { + goto exit; + } + + String8 cause = t_chop_line(&s); + if ( str8_find_needle(cause, 0, str8_lit("AddressSanitizer: heap-use-after-free on address"), 0) >= cause.size) { + goto exit; + } + + is_ok = 1; + exit:; + scratch_end(scratch); + return is_ok; +} + +T_BeginTest(infer_asan) +{ + char *program = + "#include \n" + " int main(void) {\n" + "int *foo = malloc(sizeof(*foo));\n" + "free(foo);\n" + "*foo = 1;\n" + "}\n" + ; + + // /MD + { + T_Ok(t_write_file(str8_lit("main.c"), str8_cstring(program))); + String8 cl_output = {0}; + t_invoke_(t_cl_path(), str8_lit("/MD /fsanitize=address /Z7 /c /Fo:main_md.obj main.c"), max_U64, scratch.arena, &cl_output); + T_Ok(g_last_exit_code == 0); + T_Ok(t_radlink_validate_asan_out(str8_lit("main_md.obj"))); + } + + // /MDd + { + T_Ok(t_write_file(str8_lit("main.c"), str8_cstring(program))); + String8 cl_output = {0}; + t_invoke_(t_cl_path(), str8_lit("/MDd /fsanitize=address /Z7 /c /Fo:main_mdd.obj main.c"), max_U64, scratch.arena, &cl_output); + T_Ok(g_last_exit_code == 0); + T_Ok(t_radlink_validate_asan_out(str8_lit("main_mdd.obj"))); + } + + // /MT + { + T_Ok(t_write_file(str8_lit("main.c"), str8_cstring(program))); + String8 cl_output = {0}; + t_invoke_(t_cl_path(), str8_lit("/MT /fsanitize=address /Z7 /c /Fo:main_mt.obj main.c"), max_U64, scratch.arena, &cl_output); + T_Ok(g_last_exit_code == 0); + T_Ok(t_radlink_validate_asan_out(str8_lit("main_mt.obj"))); + } + + // /MTd + { + T_Ok(t_write_file(str8_lit("main.c"), str8_cstring(program))); + String8 cl_output = {0}; + t_invoke_(t_cl_path(), str8_lit("/MT /fsanitize=address /Z7 /c /Fo:main_mtd.obj main.c"), max_U64, scratch.arena, &cl_output); + T_Ok(g_last_exit_code == 0); + T_Ok(t_radlink_validate_asan_out(str8_lit("main_mtd.obj"))); + } +} +T_EndTest; +#endif + #if 0 -T_BeginTest(pdb_determ_test) +T_BeginTest(determ_test) { T_Ok(os_copy_file_path(t_make_file_path(scratch.arena, str8_lit("torture_main.obj")), str8_lit("torture_main.obj"))); + t_delete_file(str8_lit("a.exe")); + t_delete_file(str8_lit("b.exe")); t_delete_file(str8_lit("a.pdb")); t_delete_file(str8_lit("b.pdb")); String8 refs_path = t_make_file_path(scratch.arena, str8_lit("b.types")); t_invoke_linkerf("torture_main.obj /debug:full /rad_time_stamp:0 /rad_workers:1 /rad_store_types:%S /out:a.exe", refs_path); T_Ok(g_last_exit_code == 0); + + T_Ok(os_move_file_path(t_make_file_path(scratch.arena, str8_lit("b.exe")), t_make_file_path(scratch.arena, str8_lit("a.exe")))); T_Ok(os_move_file_path(t_make_file_path(scratch.arena, str8_lit("b.pdb")), t_make_file_path(scratch.arena, str8_lit("a.pdb")))); - String8 b = t_read_file(scratch.arena, str8_lit("b.pdb")); + + String8 b_exe = t_read_file(scratch.arena, str8_lit("b.exe")); + String8 b_pdb = t_read_file(scratch.arena, str8_lit("b.pdb")); for EachIndex(i, 50) { Temp temp = temp_begin(scratch.arena); @@ -4858,8 +4950,10 @@ T_BeginTest(pdb_determ_test) t_invoke_linkerf("torture_main.obj /debug:full /rad_time_stamp:0 /out:a.exe"); T_Ok(g_last_exit_code == 0); - String8 a = t_read_file(temp.arena, str8_lit("a.pdb")); - T_Ok(str8_match(a, b, 0)); + String8 a_exe = t_read_file(temp.arena, str8_lit("a.exe")); + String8 a_pdb = t_read_file(temp.arena, str8_lit("a.pdb")); + T_Ok(str8_match(a_exe, b_exe, 0)); + T_Ok(str8_match(a_pdb, b_pdb, 0)); temp_end(temp); }