From c4228e08c5fc016f53c4e58866c5cceb84448998 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Thu, 21 Sep 2023 16:11:56 +0200 Subject: [PATCH] MacOS uses regular stacktrace for errors. --- lib/std/core/builtin.c3 | 50 ++++++- lib/std/core/string.c3 | 2 +- lib/std/os/backtrace.c3 | 73 ++++++++++ lib/std/os/cpu.c3 | 4 +- lib/std/os/env.c3 | 9 ++ lib/std/os/macos/darwin.c3 | 130 +++++++++++++++++- src/build/builder.c | 1 - src/compiler/compiler.c | 4 +- src/compiler/linker.c | 11 ++ src/compiler/llvm_codegen.c | 2 + src/compiler/llvm_codegen_builtins.c | 2 +- src/compiler/llvm_codegen_expr.c | 2 +- src/compiler/llvm_codegen_function.c | 6 +- src/compiler/llvm_codegen_internal.h | 1 + src/compiler/llvm_codegen_module.c | 1 + src/version.h | 2 +- test/test_suite/abi/wasm_extern.c3t | 4 +- .../attributes/user_defined_attributes.c3t | 6 +- test/test_suite/functions/naked_function.c3t | 2 +- test/test_suite/statements/ranged_switch.c3t | 1 - test/test_suite/statements/while_switch.c3t | 2 - 21 files changed, 291 insertions(+), 24 deletions(-) create mode 100644 lib/std/os/backtrace.c3 diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index e5d8c1c60..3f52d6837 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -89,7 +89,47 @@ struct CallstackElement uint line; } -fn void default_panic(String message, String file, String function, uint line) +fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::DARWIN) +{ + @stack_mem(4096; Allocator *mem) + { + BacktraceList! backtrace = darwin::backtrace_load(mem); + if (catch backtrace) return false; + if (backtrace.len() <= backtraces_to_ignore) return false; + (void)io::stderr().print("\nERROR: '"); + (void)io::stderr().print(message); + io::printn("'"); + foreach (i, &trace : backtrace) + { + if (i < backtraces_to_ignore) continue; + if (trace.is_unknown()) + { + (void)io::stderr().printn(" in ???"); + continue; + } + if (trace.has_file()) + { + (void)io::stderr().printfn(" in %s (%s:%d) [%s]", trace.function, trace.file, trace.line, trace.object_file); + continue; + } + (void)io::stderr().printfn(" in %s (source unavailable) [%s]", trace.function, trace.object_file); + } + return true; + }; +} +fn void default_panic(String message, String file, String function, uint line) @if(env::DARWIN) +{ + $if $defined(io::stderr) && $defined(Stream.printf): + if (!print_backtrace(message, 2)) + { + (void)io::stderr().printfn("\nERROR: '%s', in %s (%s:%d)", message, function, file, line); + return; + } + $endif + $$trap(); + +} +fn void default_panic(String message, String file, String function, uint line) @if(!env::DARWIN) { CallstackElement* stack = $$stacktrace(); $if $defined(io::stderr) && $defined(Stream.printf): @@ -323,7 +363,12 @@ macro uint char[].hash(char[] c) => (uint)fnv32a::encode(c); module std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS); import libc; -fn void sig_panic(String message) +fn void sig_panic(String message) @if(env::DARWIN) +{ + default_panic(message, "???", "???", 0); +} + +fn void sig_panic(String message) @if(!env::DARWIN) { $if $defined(io::stderr) && $defined(File.printf): CallstackElement* stack = $$stacktrace(); @@ -353,7 +398,6 @@ fn void sig_bus_error(CInt i) fn void sig_segmentation_fault(CInt i) { sig_panic("Out of bounds memory access."); - $$trap(); } fn void install_signal_handler(CInt signal, SignalFunction func) @local diff --git a/lib/std/core/string.c3 b/lib/std/core/string.c3 index 6ca1a07c5..1fe51ec95 100644 --- a/lib/std/core/string.c3 +++ b/lib/std/core/string.c3 @@ -290,7 +290,7 @@ fn usz! String.rindex_of(s, String needle) fn String ZString.as_str(str) { - return (String)((char*)str)[:str.len()]; + return (String)(str[:str.len()]); } fn usz ZString.char_len(str) diff --git a/lib/std/os/backtrace.c3 b/lib/std/os/backtrace.c3 new file mode 100644 index 000000000..957181ec6 --- /dev/null +++ b/lib/std/os/backtrace.c3 @@ -0,0 +1,73 @@ +module std::os::backtrace; + +fault BacktraceFault +{ + SEGMENT_NOT_FOUND, + EXECUTABLE_PATH_NOT_FOUND, + IMAGE_NOT_FOUND, + NO_BACKTRACE_SYMBOLS, +} + +const Backtrace BACKTRACE_UNKNOWN = { 0, "", "", "", 0, null }; + +struct Backtrace +{ + uptr offset; + String function; + String object_file; + String file; + uint line; + Allocator* allocator; +} + +fn bool Backtrace.has_file(&self) +{ + return self.file.len > 0; +} + +fn bool Backtrace.is_unknown(&self) +{ + return !self.object_file.len; +} + +fn usz! Backtrace.to_format(&self, Formatter* formatter) @dynamic +{ + if (self.has_file()) + { + return formatter.printf("%s (in %s) (%s:%d)", self.function, self.object_file, self.file, self.line); + } + if (self.is_unknown()) + { + return formatter.printf("??? (in unknown)"); + } + return formatter.printf("%s (in %s) (source unavailable)", self.function, self.object_file); +} +fn void Backtrace.free(&self) +{ + if (!self.allocator) return; + free(self.function, .using = self.allocator); + free(self.object_file, .using = self.allocator); + free(self.file, .using = self.allocator); +} + +fn Backtrace* Backtrace.init(&self, uptr offset, String function, String object_file, String file = "", uint line = 0, Allocator* using = mem::heap()) +{ + if (!using) + { + self.offset = offset; + self.function = function; + self.object_file = object_file; + self.file = file; + self.line = 0; + self.allocator = null; + return self; + } + self.offset = offset; + self.function = function.copy(using); + self.object_file = object_file.copy(using); + self.file = file.copy(using); + self.allocator = using; + self.line = line; + return self; +} + diff --git a/lib/std/os/cpu.c3 b/lib/std/os/cpu.c3 index e0d3e6bbc..22cd0339c 100644 --- a/lib/std/os/cpu.c3 +++ b/lib/std/os/cpu.c3 @@ -9,8 +9,8 @@ fn uint num_cpu() usz len = 4; uint count; - nm = { macos::CTL_HW, macos::HW_NCPU }; - macos::sysctl(&nm, 2, &count, &len, null, 0); + nm = { darwin::CTL_HW, darwin::HW_NCPU }; + darwin::sysctl(&nm, 2, &count, &len, null, 0); if (count < 1) count = 1; return count; } diff --git a/lib/std/os/env.c3 b/lib/std/os/env.c3 index d4ab784b7..e7a864253 100644 --- a/lib/std/os/env.c3 +++ b/lib/std/os/env.c3 @@ -55,3 +55,12 @@ fn void clear_var(String name) }; $endif } + +fn String! executable_path(Allocator *using = mem::heap()) +{ + $if env::DARWIN: + return darwin::executable_path(); + $else + return ""; + $endif +} \ No newline at end of file diff --git a/lib/std/os/macos/darwin.c3 b/lib/std/os/macos/darwin.c3 index 40f7374c1..c00634dd3 100644 --- a/lib/std/os/macos/darwin.c3 +++ b/lib/std/os/macos/darwin.c3 @@ -1,4 +1,5 @@ -module std::os::macos @if(env::DARWIN); +module std::os::darwin @if(env::DARWIN); +import std::collections::list; const CTL_UNSPEC = 0; /* unused */ const CTL_KERN = 1; /* "high kernel": proc, limits */ @@ -36,3 +37,130 @@ const HW_L3CACHESIZE = 22; /* int: L3 Cache Size in Bytes */ const HW_MAXID = 23; /* number of valid hw ids */ extern fn CInt sysctl(CInt *name, CUInt namelen, void *oldp, usz *oldlenp, void *newp, usz newlen); +extern fn CInt darwin_NSGetExecutablePath(char* buffer, uint *size) @extern("_NSGetExecutablePath") @builtin; +extern fn Darwin_segment_command_64* getsegbyname(ZString segname); +extern fn uint _dyld_image_count(); +extern fn ZString _dyld_get_image_name(uint image_index); +extern fn iptr _dyld_get_image_vmaddr_slide(uint image_index); +extern fn CInt dladdr(void* addr, Darwin_Dl_info* info); + +struct Darwin_Dl_info +{ + ZString dli_fname; /* Pathname of shared object */ + void* dli_fbase; /* Base address of shared object */ + ZString dli_sname; /* Name of nearest symbol */ + void* dli_saddr; /* Address of nearest symbol */ +} + +struct Darwin_segment_command_64 +{ + uint cmd; /* LC_SEGMENT_64 */ + uint cmdsize; /* includes sizeof section_64 structs */ + char[16] segname; /* segment name */ + ulong vmaddr; /* memory address of this segment */ + ulong vmsize; /* memory size of this segment */ + ulong fileoff; /* file offset of this segment */ + ulong filesize; /* amount to map from the file */ + int maxprot; /* maximum VM protection */ + int initprot; /* initial VM protection */ + uint nsects; /* number of sections in segment */ + uint flags; /* flags */ +} + + +fn String! executable_path(Allocator *using = mem::heap()) +{ + char[4096] path; + uint len = path.len; + if (darwin_NSGetExecutablePath(&path, &len) < 0) return SearchResult.MISSING?; + return ((ZString)&path).copy(.using = using); +} + +def BacktraceList = List(); + +fn uptr! load_address() @local +{ + Darwin_segment_command_64* cmd = darwin::getsegbyname("__TEXT"); + if (!cmd) return BacktraceFault.SEGMENT_NOT_FOUND?; + String path = env::executable_path(mem::temp()) ?? BacktraceFault.EXECUTABLE_PATH_NOT_FOUND?!; + uint dyld_count = darwin::_dyld_image_count(); + for (uint i = 0; i < dyld_count; i++) + { + ZString image_name = darwin::_dyld_get_image_name(i); + if (!image_name) continue; + if (image_name.as_str() != path) continue; + return cmd.vmaddr + darwin::_dyld_get_image_vmaddr_slide(i); + } + return BacktraceFault.IMAGE_NOT_FOUND?; +} + + +fn Backtrace! backtrace_load_element(String execpath, void* buffer, void* load_address, Allocator* using = mem::heap()) @local +{ + @stack_mem(1024; Allocator* mem) + { + if (buffer) + { + SubProcess process = process::create({ "atos", + "-o", execpath, "-arch", env::AARCH64 ? "arm64" : "x86_64", "-l", + string::printf("%p", load_address, .using = mem), + string::printf("%p", buffer, .using = mem), + "-fullPath" })!; + process.join()!; + char[4096] buf; + usz len = process.read_stdout(&buf, buf.len)!; + String s = (String)buf[:len - 1]; + String[] parts = s.split(" ", .using = mem); + if (parts.len == 4) + { + String[] path_parts = parts[3].split(":", .using = mem); + return { + .offset = (uptr)buffer, + .function = parts[0].copy(using), + .object_file = parts[2][..^2].copy(using), + .file = path_parts[0][1..].copy(using), + .line = path_parts[1][..^2].to_uint()!, + .allocator = using + }; + } + } + Darwin_Dl_info info; + if (!buffer || !darwin::dladdr(buffer, &info)) return backtrace::BACKTRACE_UNKNOWN; + return { + .offset = (uptr)buffer, + .function = info.dli_sname ? info.dli_sname.copy(using) : "???".copy(using), + .object_file = info.dli_fname.copy(using), + .file = "".copy(using), + .line = 0 + }; + }; +} + +fn BacktraceList! backtrace_load(Allocator* using = mem::heap()) +{ + void*[256] bt_buffer; + CInt size = posix::backtrace(&bt_buffer, 256); + void *load_addr = (void *)load_address()!; + BacktraceList list; + list.init(size, .using = using); + defer catch + { + foreach (trace : list) + { + trace.free(); + } + list.free(); + } + @stack_mem(256; Allocator* mem) + { + String execpath = executable_path(mem)!; + for (usz i = 1; i < size; i++) + { + char[1024] fname; + void* buffer = bt_buffer[i]; + Backtrace trace = backtrace_load_element(execpath, buffer, load_addr, .using = using)!; + list.append(trace); + } + }; + return list; +} diff --git a/src/build/builder.c b/src/build/builder.c index 2041ace18..e527d2a2a 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -124,7 +124,6 @@ void update_build_target_with_opt_level(BuildTarget *target, OptimizationSetting single_module = true; break; case OPT_SETTING_O5: - single_module = true; optlevel = OPTIMIZATION_AGGRESSIVE; safety_level = SAFETY_OFF; fp_opt = FP_FAST; diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 35bc00a4e..77920cc50 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -23,6 +23,7 @@ double compiler_ir_gen_time; double compiler_codegen_time; double compiler_link_time; + const char* c3_suffix_list[3] = { ".c3", ".c3t", ".c3i" }; extern int llvm_version_major; @@ -1023,4 +1024,5 @@ File *compile_and_invoke(const char *file, const char *args) error_exit("Error invoking script %s with arguments %s.", file, args); } return source_file_text_load(file, out); -} \ No newline at end of file +} + diff --git a/src/compiler/linker.c b/src/compiler/linker.c index 9b89264c7..6f50729e7 100644 --- a/src/compiler/linker.c +++ b/src/compiler/linker.c @@ -747,6 +747,7 @@ const char *concat_string_parts(const char **args) return output; } + void platform_linker(const char *output_file, const char **files, unsigned file_count) { DEBUG_LOG("Using cc linker."); @@ -763,6 +764,16 @@ void platform_linker(const char *output_file, const char **files, unsigned file_ { error_exit("Failed to link executable '%s' using command '%s'.\n", output_file, output); } + if (os_is_apple(platform_target.os) && active_target.debug_info == DEBUG_INFO_FULL) + { + // Create .dSYM + scratch_buffer_clear(); + scratch_buffer_printf("dsymutil %s", output_file); + if (system(scratch_buffer_to_string()) != 0) + { + puts("Failed to create .dSYM files, debugging will be impacted."); + } + } printf("Program linked to executable '%s'.\n", output_file); } diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index 1cbc8d38e..c7df22529 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -1006,7 +1006,9 @@ void llvm_append_function_attributes(GenContext *c, Decl *decl) { llvm_attribute_add_string(c, function, "frame-pointer", "all", -1); } + llvm_attribute_add_string(c, function, "stack-protector-buffer-size", "8", -1); llvm_attribute_add_string(c, function, "no-trapping-math", "true", -1); + if (prototype->ret_by_ref) { ABIArgInfo *info = prototype->ret_by_ref_abi_info; diff --git a/src/compiler/llvm_codegen_builtins.c b/src/compiler/llvm_codegen_builtins.c index 3539f0873..ab99dafe4 100644 --- a/src/compiler/llvm_codegen_builtins.c +++ b/src/compiler/llvm_codegen_builtins.c @@ -138,7 +138,7 @@ INLINE void llvm_emit_unreachable_stmt(GenContext *c, BEValue *result_value, Exp INLINE void llvm_emit_stacktrace(GenContext *c, BEValue *result_value, Expr *expr) { - if (!c->debug.enable_stacktrace) + if (!c->debug.emulated_stacktrace) { llvm_value_set(result_value, llvm_get_zero(c, type_voidptr), type_voidptr); return; diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 604c87f75..b39ea17c3 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -6093,7 +6093,7 @@ static inline void llvm_emit_macro_block(GenContext *c, BEValue *be_value, Expr if (llvm_use_debug(c)) { llvm_debug_push_lexical_scope(c, astptr(expr->macro_block.first_stmt)->span); - if (c->debug.enable_stacktrace) + if (c->debug.emulated_stacktrace) { restore_at_exit = true; llvm_emit_update_stack_row(c, expr->span.row); diff --git a/src/compiler/llvm_codegen_function.c b/src/compiler/llvm_codegen_function.c index c83f8c3b1..825c578e6 100644 --- a/src/compiler/llvm_codegen_function.c +++ b/src/compiler/llvm_codegen_function.c @@ -466,7 +466,7 @@ static void llvm_emit_stacktrace_definitions(GenContext *c) void llvm_emit_pop_stacktrace(GenContext *c, Stacktrace *slot) { - if (!c->debug.enable_stacktrace) return; + if (!c->debug.emulated_stacktrace) return; if (slot) { c->debug.stacktrace = *slot; @@ -475,7 +475,7 @@ void llvm_emit_pop_stacktrace(GenContext *c, Stacktrace *slot) } void llvm_emit_update_stack_row(GenContext *c, uint32_t row) { - if (!c->debug.enable_stacktrace) return; + if (!c->debug.emulated_stacktrace) return; if (row == 0) row = 1; if (c->debug.stacktrace.last_row == row) return; c->debug.stacktrace.last_row = row; @@ -520,7 +520,7 @@ void llvm_emit_body(GenContext *c, LLVMValueRef function, FunctionPrototype *pro LLVMValueRef prev_function = c->function; LLVMBuilderRef prev_builder = c->builder; - bool use_stacktrace = emit_debug && c->debug.enable_stacktrace; + bool use_stacktrace = emit_debug && c->debug.emulated_stacktrace; if (use_stacktrace && !c->debug.stack_init_fn) { llvm_emit_stacktrace_definitions(c); diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index 4c87f489c..69ac4c848 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -73,6 +73,7 @@ typedef struct { unsigned runtime_version : 8; bool enable_stacktrace : 1; + bool emulated_stacktrace : 1; LLVMDIBuilderRef builder; DebugFile *debug_files; DebugFile file; diff --git a/src/compiler/llvm_codegen_module.c b/src/compiler/llvm_codegen_module.c index 0a3fd49b5..44cc3e233 100644 --- a/src/compiler/llvm_codegen_module.c +++ b/src/compiler/llvm_codegen_module.c @@ -168,6 +168,7 @@ void gencontext_begin_module(GenContext *c) LLVMStructSetBody(c->debug.stack_type, types, 5, false); c->debug.current_stack_ptr = NULL; c->debug.enable_stacktrace = true; + c->debug.emulated_stacktrace = !os_is_apple(platform_target.os); } } c->global_builder = LLVMCreateBuilder(); diff --git a/src/version.h b/src/version.h index 318563feb..46c425f32 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.653" \ No newline at end of file +#define COMPILER_VERSION "0.4.654" \ No newline at end of file diff --git a/test/test_suite/abi/wasm_extern.c3t b/test/test_suite/abi/wasm_extern.c3t index fcfb0bd10..b79143320 100644 --- a/test/test_suite/abi/wasm_extern.c3t +++ b/test/test_suite/abi/wasm_extern.c3t @@ -13,5 +13,5 @@ target triple = "wasm32-unknown-unknown" declare i32 @get_abc() #0 define i32 @fgh() #1 -attributes #0 = { nounwind "no-trapping-math"="true" "wasm-import-name"="get_abc" } -attributes #1 = { nounwind "no-trapping-math"="true" "wasm-export-name"="fgh" } +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "wasm-import-name"="get_abc" } +attributes #1 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "wasm-export-name"="fgh" } diff --git a/test/test_suite/attributes/user_defined_attributes.c3t b/test/test_suite/attributes/user_defined_attributes.c3t index 0ab5e2986..c3acb59a4 100644 --- a/test/test_suite/attributes/user_defined_attributes.c3t +++ b/test/test_suite/attributes/user_defined_attributes.c3t @@ -53,6 +53,6 @@ entry: call void @test.testme() ret void } -attributes #0 = { noreturn nounwind "no-trapping-math"="true" } -attributes #1 = { noinline nounwind "no-trapping-math"="true" } -attributes #2 = { nounwind "no-trapping-math"="true" } \ No newline at end of file +attributes #0 = { noreturn nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { noinline nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } \ No newline at end of file diff --git a/test/test_suite/functions/naked_function.c3t b/test/test_suite/functions/naked_function.c3t index 385d3bf62..36dfe4383 100644 --- a/test/test_suite/functions/naked_function.c3t +++ b/test/test_suite/functions/naked_function.c3t @@ -12,4 +12,4 @@ entry: ret void } -attributes #0 = { naked nounwind "no-trapping-math"="true" } \ No newline at end of file +attributes #0 = { naked nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } \ No newline at end of file diff --git a/test/test_suite/statements/ranged_switch.c3t b/test/test_suite/statements/ranged_switch.c3t index 35c06f57c..50621a973 100644 --- a/test/test_suite/statements/ranged_switch.c3t +++ b/test/test_suite/statements/ranged_switch.c3t @@ -227,4 +227,3 @@ entry: ret i32 0 } -attributes #0 = { nounwind "no-trapping-math"="true" } diff --git a/test/test_suite/statements/while_switch.c3t b/test/test_suite/statements/while_switch.c3t index f3c27a9b2..6d297a277 100644 --- a/test/test_suite/statements/while_switch.c3t +++ b/test/test_suite/statements/while_switch.c3t @@ -88,5 +88,3 @@ switch.exit: ; preds = %switch.default loop.exit: ; preds = %loop.cond ret i32 0 } - -attributes #0 = { nounwind "no-trapping-math"="true" } \ No newline at end of file