diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a8c4196ad..d43a42868 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -264,6 +264,7 @@ jobs: ../build/c3c compile-run examples/load_world.c3 ../build/c3c compile-run examples/process.c3 ../build/c3c compile-run examples/ls.c3 + ../build/c3c compile-run linux_stack.c3 - name: Compile run unit tests run: | diff --git a/lib/std/os/linux/linux.c3 b/lib/std/os/linux/linux.c3 new file mode 100644 index 000000000..64d2aa5f6 --- /dev/null +++ b/lib/std/os/linux/linux.c3 @@ -0,0 +1,199 @@ +module std::os::linux @if(env::LINUX); +import libc; +import std::os::posix; +import std::io; +import std::collections::list; + +extern fn isz readlink(ZString path, char* buf, usz bufsize); + +def BacktraceList = List(); + +const PT_PHDR = 6; +const EI_NIDENT = 16; +def Elf32_Half = ushort; +def Elf32_Word = uint; +def Elf32_Addr = uint; +def Elf32_Off = uint; + +struct Elf32_Ehdr +{ + char[EI_NIDENT] e_ident; + Elf32_Half e_type; + Elf32_Half e_machine; + Elf32_Word e_version; + Elf32_Addr e_entry; + Elf32_Off e_phoff; + Elf32_Off e_shoff; + Elf32_Word e_flags; + Elf32_Half e_ehsize; + Elf32_Half e_phentsize; + Elf32_Half e_phnum; + Elf32_Half e_shentsize; + Elf32_Half e_shnum; + Elf32_Half e_shstrndx; +} + +struct Elf32_Phdr +{ + Elf32_Word p_type; + Elf32_Off p_offset; + Elf32_Addr p_vaddr; + Elf32_Addr p_paddr; + Elf32_Word p_filesz; + Elf32_Word p_memsz; + Elf32_Word p_flags; + Elf32_Word p_align; +} + +def Elf64_Addr = ulong; +def Elf64_Half = ushort; +def Elf64_Off = ulong; +def Elf64_Word = uint; +def Elf64_Sword = int; +def Elf64_Sxword = long; +def Elf64_Lword = ulong; +def Elf64_Xword = ulong; + +struct Elf64_Ehdr +{ + char[EI_NIDENT] e_ident; + Elf64_Half e_type; + Elf64_Half e_machine; + Elf64_Word e_version; + Elf64_Addr e_entry; + Elf64_Off e_phoff; + Elf64_Off e_shoff; + Elf64_Word e_flags; + Elf64_Half e_ehsize; + Elf64_Half e_phentsize; + Elf64_Half e_phnum; + Elf64_Half e_shentsize; + Elf64_Half e_shnum; + Elf64_Half e_shstrndx; +} + +struct Elf64_Phdr +{ + Elf64_Word p_type; + Elf64_Word p_flags; + Elf64_Off p_offset; + Elf64_Addr p_vaddr; + Elf64_Addr p_paddr; + Elf64_Xword p_filesz; + Elf64_Xword p_memsz; + Elf64_Xword p_align; +} + +extern fn CInt dladdr(void* addr, Linux_Dl_info* info); + +struct Linux_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 */ +} + +fn ulong! elf_module_image_base(String path) @local +{ + File file = file::open(path, "rb")!; + defer (void)file.close(); + char[4] buffer; + io::read_all(&file, &buffer)!; + if (buffer != char[4]{ 0x7f, 'E', 'L', 'F'}) return BacktraceFault.IMAGE_NOT_FOUND?; + bool is_64 = file.read_byte()! == 2; + bool is_little_endian = file.read_byte()! == 1; + // Actually, not supported. + if (!is_little_endian) return BacktraceFault.IMAGE_NOT_FOUND?; + file.seek(0)!; + if (is_64) + { + Elf64_Ehdr file_header; + io::read_any(&file, &file_header)!; + if (file_header.e_ehsize != Elf64_Ehdr.sizeof) return BacktraceFault.IMAGE_NOT_FOUND?; + for (isz i = 0; i < file_header.e_phnum; i++) + { + Elf64_Phdr header; + file.seek(file_header.e_phoff + (usz)file_header.e_phentsize * i)!; + io::read_any(&file, &header)!; + if (header.p_type == PT_PHDR) return header.p_vaddr - header.p_offset; + } + return 0; + } + Elf32_Ehdr file_header; + io::read_any(&file, &file_header)!; + if (file_header.e_ehsize != Elf32_Ehdr.sizeof) return BacktraceFault.IMAGE_NOT_FOUND?; + for (isz i = 0; i < file_header.e_phnum; i++) + { + Elf32_Phdr header; + file.seek(file_header.e_phoff + (usz)file_header.e_phentsize * i)!; + io::read_any(&file, &header)!; + if (header.p_type == PT_PHDR) return (ulong)header.p_vaddr - header.p_offset; + } + return 0; +} + +fn Backtrace! backtrace_load_element(void* addr, Allocator* allocator = mem::heap()) @local +{ + @pool(allocator) + { + if (!addr) return backtrace::BACKTRACE_UNKNOWN; + char[] buf = mem::temp_array(char, 1024); + Linux_Dl_info info; + if (dladdr(addr, &info) == 0) return backtrace::BACKTRACE_UNKNOWN; + void* obj_address = addr - (uptr)info.dli_fbase + (uptr)elf_module_image_base(info.dli_fname.str_view())!; + ZString obj_path = info.dli_fname; + String s = process::execute_stdout_to_buffer(buf, { "addr2line", "-p", "-i", "-C", "-f", "-e", obj_path.str_view(), string::tformat("0x%x", obj_address) })!; + String[] parts = s.tsplit(" at "); + if (parts.len != 2) + { + return { + .function = info.dli_sname ? info.dli_sname.copy(allocator) : "???".copy(allocator), + .object_file = info.dli_fname.copy(allocator), + .offset = (uptr)addr, + .file = "".copy(allocator), + .line = 0 + }; + } + uint line = 0; + String source = ""; + if (!parts[1].contains("?") || !parts[1].contains(":")) + { + usz index = parts[1].rindex_of_char(':')!; + source = parts[1][:index]; + line = parts[1][index + 1..].to_uint()!; + } + return { + .function = parts[0], + .object_file = info.dli_fname.copy(allocator), + .file = source, + .line = line, + .allocator = allocator + }; + }; +} + +fn BacktraceList! backtrace_load(Allocator* allocator) +{ + void*[256] bt_buffer; + CInt size = posix::backtrace(&bt_buffer, 256); + BacktraceList list; + list.init_new(size, allocator); + defer catch + { + foreach (trace : list) + { + trace.free(); + } + list.free(); + } + @pool(allocator) + { + for (usz i = 1; i < size; i++) + { + Backtrace trace = backtrace_load_element(bt_buffer[i], allocator)!; + list.append(trace); + } + }; + return list; +} diff --git a/lib/std/os/macos/darwin.c3 b/lib/std/os/macos/darwin.c3 index 28b164a08..d8f6fb302 100644 --- a/lib/std/os/macos/darwin.c3 +++ b/lib/std/os/macos/darwin.c3 @@ -101,15 +101,12 @@ fn Backtrace! backtrace_load_element(String execpath, void* buffer, void* load_a { if (buffer) { - SubProcess process = process::create({ "atos", - "-o", execpath, "-arch", env::AARCH64 ? "arm64" : "x86_64", "-l", + char* buf = tmalloc(1024); + String s = process::execute_stdout_to_buffer(buf[:1024], + { "atos", "-o", execpath, "-arch", env::AARCH64 ? "arm64" : "x86_64", "-l", string::tformat("%p", load_address), string::tformat("%p", buffer), "-fullPath" })!; - process.join()!; - char* buf = tmalloc(1024); - usz len = process.read_stdout(buf, 1024)!; - String s = (String)buf[:len - 1]; String[] parts = s.tsplit(" "); if (parts.len == 4) { diff --git a/lib/std/os/subprocess.c3 b/lib/std/os/subprocess.c3 index 0305a6856..aac929d76 100644 --- a/lib/std/os/subprocess.c3 +++ b/lib/std/os/subprocess.c3 @@ -269,6 +269,13 @@ fn ZString* tcopy_env(String[] environment) @local @inline @if(env::POSIX) return copy; } +fn String! execute_stdout_to_buffer(char[] buffer, String[] command_line, SubProcessOptions options = {}, String[] environment = {}) +{ + SubProcess process = process::create(command_line, options, environment)!; + process.join()!; + usz len = process.read_stdout(buffer.ptr, buffer.len)!; + return (String)buffer[:len - 1]; +} /** * @require !environment || !options.inherit_environment diff --git a/resources/linux_stack.c3 b/resources/linux_stack.c3 new file mode 100644 index 000000000..1582f7099 --- /dev/null +++ b/resources/linux_stack.c3 @@ -0,0 +1,14 @@ +module test; +import std::io; +import std::collections::map; +import std::os; + +fn void! main() +{ + int x = 2; + BacktraceList list = linux::backtrace_load(mem::heap())!; + foreach (Backtrace trace : list) + { + io::printn(trace); + } +} \ No newline at end of file diff --git a/src/compiler/linker.c b/src/compiler/linker.c index 8e487f82c..572789b21 100644 --- a/src/compiler/linker.c +++ b/src/compiler/linker.c @@ -386,9 +386,17 @@ static void linker_setup_linux(const char ***args_ref, LinkerType linker_type) { if (linker_type == LINKER_CC) { + if (active_target.debug_info == DEBUG_INFO_FULL) + { + add_arg("-rdynamic"); + } add_arg("-pthread"); return; } + if (active_target.debug_info == DEBUG_INFO_FULL) + { + add_arg("-export-dynamic"); + } if (is_no_pie(platform_target.reloc_model)) add_arg("-no-pie"); if (is_pie(platform_target.reloc_model)) add_arg("-pie"); if (platform_target.arch == ARCH_TYPE_X86_64) add_arg("--eh-frame-hdr"); diff --git a/src/compiler/llvm_codegen_module.c b/src/compiler/llvm_codegen_module.c index 44cc3e233..af98fd6a2 100644 --- a/src/compiler/llvm_codegen_module.c +++ b/src/compiler/llvm_codegen_module.c @@ -152,6 +152,7 @@ void gencontext_begin_module(GenContext *c) { llvm_set_module_flag(c, LLVMModuleFlagBehaviorWarning, "Dwarf Version", 4, type_uint); llvm_set_module_flag(c, LLVMModuleFlagBehaviorWarning, "Debug Info Version", 3, type_uint); + llvm_set_module_flag(c, LLVMModuleFlagBehaviorWarning, "frame-pointer", 2, type_uint); } llvm_set_module_flag(c, LLVMModuleFlagBehaviorError, "uwtable", 2, type_uint);