implement /INFERASANLIBS

This commit is contained in:
Nikita Smith
2026-04-06 22:18:26 -07:00
parent 3bb8983461
commit e54bef58df
10 changed files with 233 additions and 17 deletions
+93 -8
View File
@@ -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
+1 -1
View File
@@ -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
+13
View File
@@ -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;
+3
View File
@@ -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);
+1
View File
@@ -107,6 +107,7 @@ typedef enum
LNK_Warning_DirectiveSectionWithRelocs,
LNK_Warning_NoLargeAddressAwarenessForDll,
LNK_Warning_TryingToExportEntryPoint,
LNK_Warning_InferAsanFail,
LNK_Warning_Last,
LNK_Error_Count
+1 -1
View File
@@ -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;
}
+1 -3
View File
@@ -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
+18
View File
@@ -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_(&params);
#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)
{
+4
View File
@@ -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);
+98 -4
View File
@@ -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 <stdlib.h>\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);
}