diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e801352512a..07ce41b9bd71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -941,9 +941,9 @@ endif() if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") list(APPEND ZIG_BUILD_ARGS -Doptimize=Debug) elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") - list(APPEND ZIG_BUILD_ARGS -Doptimize=ReleaseFast) + list(APPEND ZIG_BUILD_ARGS -Doptimize=ReleaseFast -Ddebuginfo=dwarf32) else() - list(APPEND ZIG_BUILD_ARGS -Doptimize=ReleaseFast -Dstrip) + list(APPEND ZIG_BUILD_ARGS -Doptimize=ReleaseFast -Ddebuginfo=symbols) endif() if(ZIG_STATIC AND NOT MSVC) diff --git a/build.zig b/build.zig index 47f4bad49c28..111952c4108a 100644 --- a/build.zig +++ b/build.zig @@ -172,13 +172,13 @@ pub fn build(b: *std.Build) !void { const force_gpa = b.option(bool, "force-gpa", "Force the compiler to use GeneralPurposeAllocator") orelse false; const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse (enable_llvm or only_c); const sanitize_thread = b.option(bool, "sanitize-thread", "Enable thread-sanitization") orelse false; - const strip = b.option(bool, "strip", "Omit debug information"); + const debuginfo = b.option(std.builtin.DebugFormat, "debuginfo", "Format to use for debuginfo"); const valgrind = b.option(bool, "valgrind", "Enable valgrind integration"); const pie = b.option(bool, "pie", "Produce a Position Independent Executable"); const value_tracing = b.option(bool, "value-tracing", "Enable extra state tracking to help troubleshoot bugs in the compiler (using the std.debug.Trace API)") orelse false; const mem_leak_frames: u32 = b.option(u32, "mem-leak-frames", "How many stack frames to print when a memory leak occurs. Tests get 2x this amount.") orelse blk: { - if (strip == true) break :blk @as(u32, 0); + if (debuginfo != null and debuginfo.? == .none) break :blk @as(u32, 0); if (optimize != .Debug) break :blk 0; break :blk 4; }; @@ -186,7 +186,7 @@ pub fn build(b: *std.Build) !void { const exe = addCompilerStep(b, .{ .optimize = optimize, .target = target, - .strip = strip, + .debuginfo = debuginfo, .valgrind = valgrind, .sanitize_thread = sanitize_thread, .single_threaded = single_threaded, @@ -539,6 +539,7 @@ pub fn build(b: *std.Build) !void { })); test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, enable_ios_sdk, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filters, optimization_modes)); + test_step.dependOn(tests.addDebugFormatStackTraceTests(b, optimization_modes, skip_non_native)); test_step.dependOn(tests.addCliTests(b)); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filters, optimization_modes)); if (tests.addDebuggerTests(b, .{ @@ -629,7 +630,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { const AddCompilerStepOptions = struct { optimize: std.builtin.OptimizeMode, target: std.Build.ResolvedTarget, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, valgrind: ?bool = null, sanitize_thread: ?bool = null, single_threaded: ?bool = null, @@ -642,7 +643,7 @@ fn addCompilerStep(b: *std.Build, options: AddCompilerStepOptions) *std.Build.St .target = options.target, .optimize = options.optimize, .max_rss = 7_800_000_000, - .strip = options.strip, + .debuginfo = options.debuginfo, .sanitize_thread = options.sanitize_thread, .single_threaded = options.single_threaded, .code_model = switch (options.target.result.cpu.arch) { diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 65e2db3d1d2b..cc94ea45eb85 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -705,7 +705,7 @@ pub const ExecutableOptions = struct { link_libc: ?bool = null, single_threaded: ?bool = null, pic: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, unwind_tables: ?std.builtin.UnwindTables = null, omit_frame_pointer: ?bool = null, sanitize_thread: ?bool = null, @@ -731,7 +731,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile { .link_libc = options.link_libc, .single_threaded = options.single_threaded, .pic = options.pic, - .strip = options.strip, + .debuginfo = options.debuginfo, .unwind_tables = options.unwind_tables, .omit_frame_pointer = options.omit_frame_pointer, .sanitize_thread = options.sanitize_thread, @@ -761,7 +761,7 @@ pub const ObjectOptions = struct { link_libc: ?bool = null, single_threaded: ?bool = null, pic: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, unwind_tables: ?std.builtin.UnwindTables = null, omit_frame_pointer: ?bool = null, sanitize_thread: ?bool = null, @@ -781,7 +781,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile { .link_libc = options.link_libc, .single_threaded = options.single_threaded, .pic = options.pic, - .strip = options.strip, + .debuginfo = options.debuginfo, .unwind_tables = options.unwind_tables, .omit_frame_pointer = options.omit_frame_pointer, .sanitize_thread = options.sanitize_thread, @@ -809,7 +809,7 @@ pub const SharedLibraryOptions = struct { link_libc: ?bool = null, single_threaded: ?bool = null, pic: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, unwind_tables: ?std.builtin.UnwindTables = null, omit_frame_pointer: ?bool = null, sanitize_thread: ?bool = null, @@ -835,7 +835,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile .link_libc = options.link_libc, .single_threaded = options.single_threaded, .pic = options.pic, - .strip = options.strip, + .debuginfo = options.debuginfo, .unwind_tables = options.unwind_tables, .omit_frame_pointer = options.omit_frame_pointer, .sanitize_thread = options.sanitize_thread, @@ -866,7 +866,7 @@ pub const StaticLibraryOptions = struct { link_libc: ?bool = null, single_threaded: ?bool = null, pic: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, unwind_tables: ?std.builtin.UnwindTables = null, omit_frame_pointer: ?bool = null, sanitize_thread: ?bool = null, @@ -886,7 +886,7 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile .link_libc = options.link_libc, .single_threaded = options.single_threaded, .pic = options.pic, - .strip = options.strip, + .debuginfo = options.debuginfo, .unwind_tables = options.unwind_tables, .omit_frame_pointer = options.omit_frame_pointer, .sanitize_thread = options.sanitize_thread, @@ -918,7 +918,7 @@ pub const TestOptions = struct { link_libcpp: ?bool = null, single_threaded: ?bool = null, pic: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, unwind_tables: ?std.builtin.UnwindTables = null, omit_frame_pointer: ?bool = null, sanitize_thread: ?bool = null, @@ -948,7 +948,7 @@ pub fn addTest(b: *Build, options: TestOptions) *Step.Compile { .link_libcpp = options.link_libcpp, .single_threaded = options.single_threaded, .pic = options.pic, - .strip = options.strip, + .debuginfo = options.debuginfo, .unwind_tables = options.unwind_tables, .omit_frame_pointer = options.omit_frame_pointer, .sanitize_thread = options.sanitize_thread, diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 0e421280f095..55be262309f8 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -12,7 +12,6 @@ import_table: std.StringArrayHashMapUnmanaged(*Module), resolved_target: ?std.Build.ResolvedTarget = null, optimize: ?std.builtin.OptimizeMode = null, -dwarf_format: ?std.dwarf.Format, c_macros: std.ArrayListUnmanaged([]const u8), include_dirs: std.ArrayListUnmanaged(IncludeDir), @@ -21,7 +20,7 @@ rpaths: std.ArrayListUnmanaged(RPath), frameworks: std.StringArrayHashMapUnmanaged(LinkFrameworkOptions), link_objects: std.ArrayListUnmanaged(LinkObject), -strip: ?bool, +debuginfo: ?std.builtin.DebugFormat, unwind_tables: ?std.builtin.UnwindTables, single_threaded: ?bool, stack_protector: ?bool, @@ -217,7 +216,7 @@ pub const CreateOptions = struct { /// `null` neither requires nor prevents libc++ from being linked. link_libcpp: ?bool = null, single_threaded: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, unwind_tables: ?std.builtin.UnwindTables = null, dwarf_format: ?std.dwarf.Format = null, code_model: std.builtin.CodeModel = .default, @@ -254,14 +253,13 @@ pub fn init(m: *Module, owner: *std.Build, options: CreateOptions, compile: ?*St .optimize = options.optimize, .link_libc = options.link_libc, .link_libcpp = options.link_libcpp, - .dwarf_format = options.dwarf_format, .c_macros = .{}, .include_dirs = .{}, .lib_paths = .{}, .rpaths = .{}, .frameworks = .{}, .link_objects = .{}, - .strip = options.strip, + .debuginfo = options.debuginfo, .unwind_tables = options.unwind_tables, .single_threaded = options.single_threaded, .stack_protector = options.stack_protector, @@ -674,7 +672,6 @@ pub fn appendZigProcessFlags( ) !void { const b = m.owner; - try addFlag(zig_args, m.strip, "-fstrip", "-fno-strip"); try addFlag(zig_args, m.single_threaded, "-fsingle-threaded", "-fno-single-threaded"); try addFlag(zig_args, m.stack_check, "-fstack-check", "-fno-stack-check"); try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector"); @@ -687,10 +684,13 @@ pub fn appendZigProcessFlags( try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC"); try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone"); - if (m.dwarf_format) |dwarf_format| { - try zig_args.append(switch (dwarf_format) { - .@"32" => "-gdwarf32", - .@"64" => "-gdwarf64", + if (m.debuginfo) |debuginfo| { + try zig_args.append(switch (debuginfo) { + .none => "-fdebuginfo=none", + .symbols => "-fdebuginfo=symbols", + .dwarf32 => "-fdebuginfo=dwarf32", + .dwarf64 => "-fdebuginfo=dwarf64", + .code_view => "-fdebuginfo=code_view", }); } diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 1dd444abf2e4..6a29af0d5959 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -655,8 +655,8 @@ pub fn producesPdbFile(compile: *Compile) bool { else => return false, } if (target.ofmt == .c) return false; - if (compile.root_module.strip == true or - (compile.root_module.strip == null and compile.root_module.optimize == .ReleaseSmall)) + if (compile.root_module.debuginfo == .none or + (compile.root_module.debuginfo == null and compile.root_module.optimize == .ReleaseSmall)) { return false; } diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index eda7f0ff4f5e..36a22a0e7b46 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -133,6 +133,16 @@ pub const AtomicRmwOp = enum { Min, }; +/// This data structure is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const DebugFormat = enum { + none, + symbols, + dwarf32, + dwarf64, + code_view, +}; + /// The code model puts constraints on the location of symbols and the size of code and data. /// The selection of a code model is a trade off on speed and restrictions that needs to be selected on a per application basis to meet its requirements. /// A slightly more detailed explanation can be found in (for example) the [System V Application Binary Interface (x86_64)](https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf) 3.5.1. diff --git a/lib/std/debug.zig b/lib/std/debug.zig index e8855f5d1ad3..b1b8074bad0b 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -16,6 +16,7 @@ const native_endian = native_arch.endian(); pub const MemoryAccessor = @import("debug/MemoryAccessor.zig"); pub const FixedBufferReader = @import("debug/FixedBufferReader.zig"); pub const Dwarf = @import("debug/Dwarf.zig"); +pub const ElfSymTab = @import("debug/ElfSymTab.zig"); pub const Pdb = @import("debug/Pdb.zig"); pub const SelfInfo = @import("debug/SelfInfo.zig"); pub const Info = @import("debug/Info.zig"); @@ -732,30 +733,7 @@ pub const StackIterator = struct { fn next_unwind(it: *StackIterator) !usize { const unwind_state = &it.unwind_state.?; const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); - switch (native_os) { - .macos, .ios, .watchos, .tvos, .visionos => { - // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding - // via DWARF before attempting to use the compact unwind info will produce incorrect results. - if (module.unwind_info) |unwind_info| { - if (SelfInfo.unwindFrameMachO( - &unwind_state.dwarf_context, - &it.ma, - unwind_info, - module.eh_frame, - module.base_address, - )) |return_address| { - return return_address; - } else |err| { - if (err != error.RequiresDWARFUnwind) return err; - } - } else return error.MissingUnwindInfo; - }, - else => {}, - } - - if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| { - return SelfInfo.unwindFrameDwarf(di, &unwind_state.dwarf_context, &it.ma, null); - } else return error.MissingDebugInfo; + return module.unwindFrame(&unwind_state.dwarf_context, &it.ma) catch return error.MissingDebugInfo; } fn next_internal(it: *StackIterator) ?usize { diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 73b1871c4683..e217c575c5c0 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -2040,7 +2040,7 @@ const EhPointerContext = struct { text_rel_base: ?u64 = null, function_rel_base: ?u64 = null, }; -fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { +pub fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { if (enc == EH.PE.omit) return null; const value: union(enum) { diff --git a/lib/std/debug/ElfSymTab.zig b/lib/std/debug/ElfSymTab.zig new file mode 100644 index 000000000000..3449998e9ce1 --- /dev/null +++ b/lib/std/debug/ElfSymTab.zig @@ -0,0 +1,311 @@ +//! Similar to std.debug.Dwarf, but only using symbol info from an ELF file. + +const ElfSymTab = @This(); + +endian: std.builtin.Endian, + +base_address: usize, +mapped_memory: []align(std.mem.page_size) const u8, +sections: SectionArray, + +/// Populated by `scanAllSymbols`. +symbol_list: std.ArrayListUnmanaged(Symbol) = .empty, + +eh_frame_hdr: ?Dwarf.ExceptionFrameHeader = null, + +pub const Symbol = struct { + name: []const u8, + start: u64, + end: u64, +}; + +pub const OpenError = ScanError; + +/// Initialize DWARF info. The caller has the responsibility to initialize most +/// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the +/// main binary file (not the secondary debug info file). +pub fn open(d: *ElfSymTab, gpa: Allocator) OpenError!void { + try d.scanAllSymbols(gpa); +} + +pub const ScanError = error{ + InvalidDebugInfo, + MissingDebugInfo, +} || Allocator.Error || std.debug.FixedBufferReader.Error; + +fn scanAllSymbols(ei: *ElfSymTab, allocator: Allocator) OpenError!void { + const symtab: Section = ei.sections[@intFromEnum(Section.Id.symtab)].?; + const strtab: Section = ei.sections[@intFromEnum(Section.Id.strtab)].?; + + const num_symbols = symtab.data.len / symtab.entry_size; + const symbols = @as([*]const elf.Sym, @ptrCast(@alignCast(symtab.data.ptr)))[0..num_symbols]; + for (symbols) |symbol| { + if (symbol.st_name == 0) continue; + if (symbol.st_shndx == elf.SHN_UNDEF) continue; + + const symbol_name = getStringFromTable(strtab.data, symbol.st_name) orelse { + // If it doesn't have a symbol name, we can't really use it for debugging purposes + continue; + }; + + // TODO: Does SHN_ABS make a difference for this use case? + // if (symbol.st_shndx == elf.SHN_ABS) { + // continue; + // } + + // TODO: handle relocatable symbols in DYN type binaries + try ei.symbol_list.append(allocator, .{ + .name = symbol_name, + .start = symbol.st_value, + .end = symbol.st_value + symbol.st_size, + }); + } +} + +pub const LoadError = error{ + InvalidDebugInfo, + MissingDebugInfo, + InvalidElfMagic, + InvalidElfVersion, + InvalidElfEndian, + /// TODO: implement this and then remove this error code + UnimplementedElfForeignEndian, + /// TODO: implement this and then remove this error code + UnimplementedElfType, + /// The debug info may be valid but this implementation uses memory + /// mapping which limits things to usize. If the target debug info is + /// 64-bit and host is 32-bit, there may be debug info that is not + /// supportable using this method. + Overflow, + + PermissionDenied, + LockedMemoryLimitExceeded, + MemoryMappingNotSupported, +} || Allocator.Error || std.fs.File.OpenError || OpenError; + +/// Reads symbol info from an already mapped ELF file. +pub fn load( + gpa: Allocator, + mapped_mem: []align(std.mem.page_size) const u8, + expected_crc: ?u32, + gnu_eh_frame: ?[]const u8, +) LoadError!ElfSymTab { + if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; + + const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); + if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; + if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; + + const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { + elf.ELFDATA2LSB => .little, + elf.ELFDATA2MSB => .big, + else => return error.InvalidElfEndian, + }; + if (endian != native_endian) return error.UnimplementedElfForeignEndian; + if (hdr.e_type != .EXEC) return error.UnimplementedElfType; + + const shoff = hdr.e_shoff; + const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); + const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[cast(usize, str_section_off) orelse return error.Overflow])); + const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; + const shdrs = @as( + [*]const elf.Shdr, + @ptrCast(@alignCast(&mapped_mem[shoff])), + )[0..hdr.e_shnum]; + + var sections: ElfSymTab.SectionArray = ElfSymTab.null_section_array; + + if (gnu_eh_frame) |eh_frame_hdr| { + // This is a special case - pointer offsets inside .eh_frame_hdr + // are encoded relative to its base address, so we must use the + // version that is already memory mapped, and not the one that + // will be mapped separately from the ELF file. + sections[@intFromEnum(Section.Id.eh_frame_hdr)] = .{ + .entry_size = undefined, + .data = eh_frame_hdr, + .owned = false, + }; + } + + for (shdrs) |*shdr| { + if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; + const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); + + var section_index: ?usize = null; + inline for (@typeInfo(ElfSymTab.Section.Id).@"enum".fields, 0..) |sect, i| { + if (mem.eql(u8, "." ++ sect.name, name)) section_index = i; + } + if (section_index == null) continue; + if (sections[section_index.?] != null) continue; + + const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { + var section_stream = std.io.fixedBufferStream(section_bytes); + const section_reader = section_stream.reader(); + const chdr = section_reader.readStruct(elf.Chdr) catch continue; + if (chdr.ch_type != .ZLIB) continue; + + var zlib_stream = std.compress.zlib.decompressor(section_reader); + + const decompressed_section = try gpa.alloc(u8, chdr.ch_size); + errdefer gpa.free(decompressed_section); + + const read = zlib_stream.reader().readAll(decompressed_section) catch continue; + assert(read == decompressed_section.len); + + break :blk .{ + .entry_size = shdr.sh_entsize, + .data = decompressed_section, + .virtual_address = shdr.sh_addr, + .owned = true, + }; + } else .{ + .entry_size = shdr.sh_entsize, + .data = section_bytes, + .virtual_address = shdr.sh_addr, + .owned = false, + }; + } + + const missing_debug_info = + sections[@intFromEnum(ElfSymTab.Section.Id.strtab)] == null or + sections[@intFromEnum(ElfSymTab.Section.Id.symtab)] == null; + + if (missing_debug_info) { + return error.MissingDebugInfo; + } + + var ei: ElfSymTab = .{ + .base_address = 0, + .endian = endian, + .sections = sections, + .mapped_memory = mapped_mem, + }; + + try ElfSymTab.open(&ei, gpa); + + return ei; +} + +pub fn deinit(self: *ElfSymTab, allocator: std.mem.Allocator) void { + for (self.sections) |section_opt| { + const s = section_opt orelse continue; + allocator.free(s.data); + } + self.symbol_list.deinit(allocator); +} + +const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); +pub const SectionArray = [num_sections]?Section; +pub const null_section_array = [_]?Section{null} ** num_sections; + +pub const Section = struct { + entry_size: usize, + data: []const u8, + // Module-relative virtual address. + // Only set if the section data was loaded from disk. + virtual_address: ?usize = null, + // If `data` is owned by this Dwarf. + owned: bool, + + pub const Id = enum { + strtab, + symtab, + eh_frame_hdr, + eh_frame, + }; + + // For sections that are not memory mapped by the loader, this is an offset + // from `data.ptr` to where the section would have been mapped. Otherwise, + // `data` is directly backed by the section and the offset is zero. + pub fn virtualOffset(self: Section, base_address: usize) i64 { + return if (self.virtual_address) |va| + @as(i64, @intCast(base_address + va)) - + @as(i64, @intCast(@intFromPtr(self.data.ptr))) + else + 0; + } +}; + +pub fn section(ei: ElfSymTab, elf_section: Section.Id) ?[]const u8 { + return if (ei.sections[@intFromEnum(elf_section)]) |s| s.data else null; +} + +pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { + _ = allocator; + // Translate the VA into an address into this object + const relocated_address = address - self.base_address; + for (self.symbol_list.items) |symbol| { + if (symbol.start <= relocated_address and relocated_address <= symbol.end) { + return .{ + .name = symbol.name, + }; + } + } + return .{}; +} + +pub fn scanAllUnwindInfo(this: *@This()) !void { + const eh_frame_hdr = this.section(.eh_frame_hdr) orelse return; + + var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; + + const version = try fbr.readByte(); + if (version != 1) return; + + const eh_frame_ptr_enc = try fbr.readByte(); + if (eh_frame_ptr_enc == EH.PE.omit) return; + const fde_count_enc = try fbr.readByte(); + if (fde_count_enc == EH.PE.omit) return; + const table_enc = try fbr.readByte(); + if (table_enc == EH.PE.omit) return; + + const eh_frame_ptr = cast(usize, try Dwarf.readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ + .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), + .follow_indirect = true, + }) orelse return Dwarf.bad()) orelse return Dwarf.bad(); + + const fde_count = cast(usize, try Dwarf.readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ + .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), + .follow_indirect = true, + }) orelse return Dwarf.bad()) orelse return Dwarf.bad(); + + const entry_size = try Dwarf.ExceptionFrameHeader.entrySize(table_enc); + const entries_len = fde_count * entry_size; + if (entries_len > eh_frame_hdr.len - fbr.pos) return Dwarf.bad(); + + this.eh_frame_hdr = .{ + .eh_frame_ptr = eh_frame_ptr, + .table_enc = table_enc, + .fde_count = fde_count, + .entries = eh_frame_hdr[fbr.pos..][0..entries_len], + }; +} + +fn getStringFromTable(string_table: []const u8, pos: usize) ?[]const u8 { + if (pos == 0) return null; + const section_name_end = std.mem.indexOfScalarPos(u8, string_table, pos, '\x00') orelse return null; + return string_table[pos..section_name_end]; +} + +pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { + const start = cast(usize, offset) orelse return error.Overflow; + const end = start + (cast(usize, size) orelse return error.Overflow); + return ptr[start..end]; +} + +const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); + +const std = @import("../std.zig"); +const Allocator = std.mem.Allocator; +const elf = std.elf; +const mem = std.mem; +const assert = std.debug.assert; +const cast = std.math.cast; +const maxInt = std.math.maxInt; +const MemoryAccessor = std.debug.MemoryAccessor; +const FixedBufferReader = std.debug.FixedBufferReader; +const Dwarf = std.debug.Dwarf; +const DW = std.dwarf; +const EH = DW.EH; diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index 544cf0ac6ff4..9197b10f252c 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -17,6 +17,7 @@ const pdb = std.pdb; const assert = std.debug.assert; const posix = std.posix; const elf = std.elf; +const ElfSymTab = std.debug.ElfSymTab; const Dwarf = std.debug.Dwarf; const Pdb = std.debug.Pdb; const File = std.fs.File; @@ -462,30 +463,22 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module { return obj_di; } - const obj_di = try self.allocator.create(Module); - errdefer self.allocator.destroy(obj_di); - - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - if (ctx.gnu_eh_frame) |eh_frame_hdr| { - // This is a special case - pointer offsets inside .eh_frame_hdr - // are encoded relative to its base address, so we must use the - // version that is already memory mapped, and not the one that - // will be mapped separately from the ELF file. - sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ - .data = eh_frame_hdr, - .owned = false, - }; - } + const module = try self.allocator.create(Module); + errdefer self.allocator.destroy(module); + + module.* = try Module.readDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, ctx.gnu_eh_frame, null); - obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, §ions, null); - obj_di.base_address = ctx.base_address; + switch (module.*) { + .dwarf => |*dwarf_info| dwarf_info.base_address = ctx.base_address, + .symtab => |*symtab| symtab.base_address = ctx.base_address, + } // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding - obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; + module.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; - try self.address_map.putNoClobber(ctx.base_address, obj_di); + try self.address_map.putNoClobber(ctx.base_address, module); - return obj_di; + return module; } fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module { @@ -707,8 +700,61 @@ pub const Module = switch (native_os) { } } - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { - return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; + pub fn unwindFrame( + this: *@This(), + context: *UnwindContext, + ma: *std.debug.MemoryAccessor, + ) !usize { + // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding + // via DWARF before attempting to use the compact unwind info will produce incorrect results. + if (this.unwind_info) |unwind_info| { + if (SelfInfo.unwindFrameMachO( + context, + ma, + unwind_info, + this.eh_frame, + this.base_address, + )) |return_address| { + return return_address; + } else |err| { + if (err != error.RequiresDWARFUnwind) return err; + } + } else return error.MissingUnwindInfo; + + const o_file_info = (try this.getOFileInfoForAddress(context.allocator, context.pc)).o_file_info orelse return error.MissingOFileInfo; + const dwarf_info = &o_file_info.di; + + // Find the FDE and CIE + var cie: Dwarf.CommonInformationEntry = undefined; + var fde: Dwarf.FrameDescriptionEntry = undefined; + + if (dwarf_info.eh_frame_hdr) |header| { + const eh_frame_len = if (dwarf_info.section(.eh_frame)) |eh_frame| eh_frame.len else null; + try header.findEntry( + ma, + eh_frame_len, + @intFromPtr(dwarf_info.section(.eh_frame_hdr).?.ptr), + context.pc, + &cie, + &fde, + ); + } else { + const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, dwarf_info.fde_list.items, context.pc, struct { + pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order { + if (pc < item.pc_begin) return .lt; + + const range_end = item.pc_begin + item.pc_range; + if (pc < range_end) return .eq; + + return .gt; + } + }.compareFn); + + fde = if (index) |i| dwarf_info.fde_list.items[i] else return error.MissingFDE; + cie = dwarf_info.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; + } + + return unwindFrameDwarf(cie, fde, dwarf_info.findCompileUnit(fde.pc_begin) catch null, false, context, ma); } }, .uefi, .windows => struct { @@ -783,18 +829,162 @@ pub const Module = switch (native_os) { return .{}; } + }, + .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => union(enum) { + dwarf: Dwarf.ElfModule, + symtab: ElfSymTab, + + /// Reads debug info from an ELF file, or the current binary if none in specified. + /// If the required sections aren't present but a reference to external debug info is, + /// then this this function will recurse to attempt to load the debug sections from + /// an external file. + pub fn readDebugInfo( + allocator: Allocator, + elf_filename: ?[]const u8, + build_id: ?[]const u8, + expected_crc: ?u32, + gnu_eh_frame: ?[]const u8, + parent_mapped_mem: ?[]align(mem.page_size) const u8, + ) !@This() { + nosuspend { + const elf_file = (if (elf_filename) |filename| blk: { + break :blk fs.cwd().openFile(filename, .{}); + } else fs.openSelfExe(.{})) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return err, + }; - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { - _ = allocator; - _ = address; + const mapped_mem = try mapWholeFile(elf_file); + + const load_dwarf_error = load_dwarf: { + var sections: Dwarf.SectionArray = Dwarf.null_section_array; + if (gnu_eh_frame) |eh_frame_hdr| { + // This is a special case - pointer offsets inside .eh_frame_hdr + // are encoded relative to its base address, so we must use the + // version that is already memory mapped, and not the one that + // will be mapped separately from the ELF file. + sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ + .data = eh_frame_hdr, + .owned = false, + }; + } - return switch (self.debug_data) { - .dwarf => |*dwarf| dwarf, - else => null, + const dwarf_info = Dwarf.ElfModule.load( + allocator, + mapped_mem, + build_id, + expected_crc, + §ions, + parent_mapped_mem, + elf_filename, + ) catch |err| { + break :load_dwarf err; + }; + return @This(){ .dwarf = dwarf_info }; + }; + + load_symtab: { + const symtab = ElfSymTab.load( + allocator, + mapped_mem, + expected_crc, + // same special case here as for Dwarf + gnu_eh_frame, + ) catch { + break :load_symtab; + }; + return @This(){ .symtab = symtab }; + } + + return load_dwarf_error; + } + } + + pub fn scanAllUnwindInfo(this: *@This(), allocator: Allocator, base_address: usize) !void { + return switch (this.*) { + .dwarf => |*dwarf_info| dwarf_info.dwarf.scanAllUnwindInfo(allocator, base_address), + .symtab => |*symtab| symtab.scanAllUnwindInfo(), + }; + } + + pub fn deinit(this: *@This(), allocator: Allocator) void { + return switch (this.*) { + .dwarf => |*dwarf_info| dwarf_info.deinit(allocator), + .symtab => |*symtab| symtab.deinit(allocator), + }; + } + + pub fn getSymbolAtAddress(this: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { + return switch (this.*) { + .dwarf => |*dwarf_info| dwarf_info.getSymbolAtAddress(allocator, address), + .symtab => |*symtab| symtab.getSymbolAtAddress(allocator, address), + }; + } + + pub fn unwindFrame( + this: *const @This(), + context: *UnwindContext, + ma: *std.debug.MemoryAccessor, + ) !usize { + if (!supports_unwinding) return error.UnsupportedCpuArchitecture; + if (context.pc == 0) return 0; + + return switch (this.*) { + .dwarf => |*dwarf_info| { + // Find the FDE and CIE + var cie: Dwarf.CommonInformationEntry = undefined; + var fde: Dwarf.FrameDescriptionEntry = undefined; + + if (dwarf_info.dwarf.eh_frame_hdr) |header| { + const eh_frame_len = if (dwarf_info.dwarf.section(.eh_frame)) |eh_frame| eh_frame.len else null; + try header.findEntry( + ma, + eh_frame_len, + @intFromPtr(dwarf_info.dwarf.section(.eh_frame_hdr).?.ptr), + context.pc, + &cie, + &fde, + ); + } else { + const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, dwarf_info.dwarf.fde_list.items, context.pc, struct { + pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order { + if (pc < item.pc_begin) return .lt; + + const range_end = item.pc_begin + item.pc_range; + if (pc < range_end) return .eq; + + return .gt; + } + }.compareFn); + + fde = if (index) |i| dwarf_info.dwarf.fde_list.items[i] else return error.MissingFDE; + cie = dwarf_info.dwarf.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; + } + + return unwindFrameDwarf(cie, fde, dwarf_info.dwarf.findCompileUnit(fde.pc_begin) catch null, false, context, ma); + }, + .symtab => |*symtab| { + const eh_frame_header = symtab.eh_frame_hdr orelse return error.MissingFDE; + const eh_frame = symtab.section(.eh_frame) orelse return error.MissingFDE; + + // Find the FDE and CIE + var cie: Dwarf.CommonInformationEntry = undefined; + var fde: Dwarf.FrameDescriptionEntry = undefined; + + try eh_frame_header.findEntry( + ma, + eh_frame.len, + @intFromPtr(symtab.section(.eh_frame_hdr).?.ptr), + context.pc, + &cie, + &fde, + ); + + return unwindFrameDwarf(cie, fde, null, false, context, ma); + }, }; } }, - .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule, .wasi, .emscripten => struct { pub fn deinit(self: *@This(), allocator: Allocator) void { _ = self; @@ -807,13 +997,6 @@ pub const Module = switch (native_os) { _ = address; return .{}; } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { - _ = self; - _ = allocator; - _ = address; - return null; - } }, else => Dwarf, }; @@ -1036,39 +1219,6 @@ fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module { } } -/// Reads debug info from an ELF file, or the current binary if none in specified. -/// If the required sections aren't present but a reference to external debug info is, -/// then this this function will recurse to attempt to load the debug sections from -/// an external file. -pub fn readElfDebugInfo( - allocator: Allocator, - elf_filename: ?[]const u8, - build_id: ?[]const u8, - expected_crc: ?u32, - parent_sections: *Dwarf.SectionArray, - parent_mapped_mem: ?[]align(mem.page_size) const u8, -) !Dwarf.ElfModule { - nosuspend { - const elf_file = (if (elf_filename) |filename| blk: { - break :blk fs.cwd().openFile(filename, .{}); - } else fs.openSelfExe(.{})) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - - const mapped_mem = try mapWholeFile(elf_file); - return Dwarf.ElfModule.load( - allocator, - mapped_mem, - build_id, - expected_crc, - parent_sections, - parent_mapped_mem, - elf_filename, - ); - } -} - const MachoSymbol = struct { strx: u32, addr: u64, @@ -1561,88 +1711,20 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. pub fn unwindFrameDwarf( - di: *const Dwarf, + cie: Dwarf.CommonInformationEntry, + fde: Dwarf.FrameDescriptionEntry, + compile_unit: ?*const Dwarf.CompileUnit, + is_macho: bool, context: *UnwindContext, ma: *std.debug.MemoryAccessor, - explicit_fde_offset: ?usize, ) !usize { if (!supports_unwinding) return error.UnsupportedCpuArchitecture; if (context.pc == 0) return 0; - // Find the FDE and CIE - var cie: Dwarf.CommonInformationEntry = undefined; - var fde: Dwarf.FrameDescriptionEntry = undefined; - - if (explicit_fde_offset) |fde_offset| { - const dwarf_section: Dwarf.Section.Id = .eh_frame; - const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; - if (fde_offset >= frame_section.len) return error.MissingFDE; - - var fbr: std.debug.FixedBufferReader = .{ - .buf = frame_section, - .pos = fde_offset, - .endian = di.endian, - }; - - const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); - if (fde_entry_header.type != .fde) return error.MissingFDE; - - const cie_offset = fde_entry_header.type.fde; - try fbr.seekTo(cie_offset); - - fbr.endian = native_endian; - const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); - if (cie_entry_header.type != .cie) return Dwarf.bad(); - - cie = try Dwarf.CommonInformationEntry.parse( - cie_entry_header.entry_bytes, - 0, - true, - cie_entry_header.format, - dwarf_section, - cie_entry_header.length_offset, - @sizeOf(usize), - native_endian, - ); - - fde = try Dwarf.FrameDescriptionEntry.parse( - fde_entry_header.entry_bytes, - 0, - true, - cie, - @sizeOf(usize), - native_endian, - ); - } else if (di.eh_frame_hdr) |header| { - const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; - try header.findEntry( - ma, - eh_frame_len, - @intFromPtr(di.section(.eh_frame_hdr).?.ptr), - context.pc, - &cie, - &fde, - ); - } else { - const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, di.fde_list.items, context.pc, struct { - pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order { - if (pc < item.pc_begin) return .lt; - - const range_end = item.pc_begin + item.pc_range; - if (pc < range_end) return .eq; - - return .gt; - } - }.compareFn); - - fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; - cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; - } - var expression_context: Dwarf.expression.Context = .{ .format = cie.format, .memory_accessor = ma, - .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, + .compile_unit = compile_unit, .thread_context = context.thread_context, .reg_context = context.reg_context, .cfa = context.cfa, @@ -1650,7 +1732,7 @@ pub fn unwindFrameDwarf( context.vm.reset(); context.reg_context.eh_frame = cie.version != 4; - context.reg_context.is_macho = di.is_macho; + context.reg_context.is_macho = is_macho; const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); context.cfa = switch (row.cfa.rule) { @@ -1807,18 +1889,46 @@ fn unwindFrameMachODwarf( eh_frame: []const u8, fde_offset: usize, ) !usize { - var di: Dwarf = .{ + const dwarf_section: Dwarf.Section.Id = .eh_frame; + if (fde_offset >= eh_frame.len) return error.MissingFDE; + + var fbr: std.debug.FixedBufferReader = .{ + .buf = eh_frame, + .pos = fde_offset, .endian = native_endian, - .is_macho = true, }; - defer di.deinit(context.allocator); - di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ - .data = eh_frame, - .owned = false, - }; + const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); + if (fde_entry_header.type != .fde) return error.MissingFDE; + + const cie_offset = fde_entry_header.type.fde; + try fbr.seekTo(cie_offset); + + fbr.endian = native_endian; + const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); + if (cie_entry_header.type != .cie) return Dwarf.bad(); + + const cie = try Dwarf.CommonInformationEntry.parse( + cie_entry_header.entry_bytes, + 0, + true, + cie_entry_header.format, + dwarf_section, + cie_entry_header.length_offset, + @sizeOf(usize), + native_endian, + ); + + const fde = try Dwarf.FrameDescriptionEntry.parse( + fde_entry_header.entry_bytes, + 0, + true, + cie, + @sizeOf(usize), + native_endian, + ); - return unwindFrameDwarf(&di, context, ma, fde_offset); + return unwindFrameDwarf(cie, fde, null, false, context, ma); } /// This is a virtual machine that runs DWARF call frame instructions. diff --git a/src/Builtin.zig b/src/Builtin.zig index 1782527f699b..2641f897f425 100644 --- a/src/Builtin.zig +++ b/src/Builtin.zig @@ -14,7 +14,7 @@ sanitize_thread: bool, fuzz: bool, pic: bool, pie: bool, -strip: bool, +debug_format: std.builtin.DebugFormat, code_model: std.builtin.CodeModel, omit_frame_pointer: bool, wasi_exec_model: std.builtin.WasiExecModel, @@ -228,6 +228,7 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void { \\pub const fuzz = {}; \\pub const position_independent_code = {}; \\pub const position_independent_executable = {}; + \\pub const debug_format = std.builtin.DebugFormat.{p_}; \\pub const strip_debug_info = {}; \\pub const code_model: std.builtin.CodeModel = .{p_}; \\pub const omit_frame_pointer = {}; @@ -243,7 +244,8 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void { opts.fuzz, opts.pic, opts.pie, - opts.strip, + std.zig.fmtId(@tagName(opts.debug_format)), + opts.debug_format == .none, std.zig.fmtId(@tagName(opts.code_model)), opts.omit_frame_pointer, }); diff --git a/src/Compilation.zig b/src/Compilation.zig index 1c4c97e06fb9..5890d61c8116 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -842,7 +842,7 @@ pub const cache_helpers = struct { hh.add(mod.error_tracing); hh.add(mod.valgrind); hh.add(mod.pic); - hh.add(mod.strip); + hh.add(mod.debug_format); hh.add(mod.omit_frame_pointer); hh.add(mod.stack_check); hh.add(mod.red_zone); @@ -882,16 +882,7 @@ pub const cache_helpers = struct { pub fn addOptionalDebugFormat(hh: *Cache.HashHelper, x: ?Config.DebugFormat) void { hh.add(x != null); - addDebugFormat(hh, x orelse return); - } - - pub fn addDebugFormat(hh: *Cache.HashHelper, x: Config.DebugFormat) void { - const tag: @typeInfo(Config.DebugFormat).@"union".tag_type.? = x; - hh.add(tag); - switch (x) { - .strip, .code_view => {}, - .dwarf => |f| hh.add(f), - } + hh.add(x orelse return); } pub fn hashCSource(self: *Cache.Manifest, c_source: CSourceFile) !void { @@ -1364,7 +1355,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil cache.hash.add(options.config.link_libcpp); cache.hash.add(options.config.link_libunwind); cache.hash.add(output_mode); - cache_helpers.addDebugFormat(&cache.hash, options.config.debug_format); + cache.hash.add(options.config.debug_format); cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_bin); cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_implib); cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_docs); @@ -5325,18 +5316,22 @@ pub fn addCCArgs( try argv.ensureUnusedCapacity(2); switch (comp.config.debug_format) { - .strip => {}, + .none => {}, + .symbols => { + // TODO: make sure symbols are generated + }, .code_view => { // -g is required here because -gcodeview doesn't trigger debug info // generation, it only changes the type of information generated. argv.appendSliceAssumeCapacity(&.{ "-g", "-gcodeview" }); }, - .dwarf => |f| { + .dwarf32 => { argv.appendAssumeCapacity("-gdwarf-4"); - switch (f) { - .@"32" => argv.appendAssumeCapacity("-gdwarf32"), - .@"64" => argv.appendAssumeCapacity("-gdwarf64"), - } + argv.appendAssumeCapacity("-gdwarf32"); + }, + .dwarf64 => { + argv.appendAssumeCapacity("-gdwarf-4"); + argv.appendAssumeCapacity("-gdwarf64"); }, } @@ -6247,7 +6242,7 @@ fn buildOutputFromZig( assert(output_mode != .Exe); - const strip = comp.compilerRtStrip(); + const debug_format = comp.compilerRtDebugFormat(); const optimize_mode = comp.compilerRtOptMode(); const config = try Config.resolve(.{ @@ -6258,7 +6253,7 @@ fn buildOutputFromZig( .have_zcu = true, .emit_bin = true, .root_optimize_mode = optimize_mode, - .root_strip = strip, + .debug_format = debug_format, .link_libc = comp.config.link_libc, }); @@ -6271,7 +6266,7 @@ fn buildOutputFromZig( .fully_qualified_name = "root", .inherited = .{ .resolved_target = comp.root_mod.resolved_target, - .strip = strip, + .debug_format = debug_format, .stack_check = false, .stack_protector = 0, .red_zone = comp.root_mod.red_zone, @@ -6394,7 +6389,7 @@ pub fn build_crt_file( .have_zcu = false, .emit_bin = true, .root_optimize_mode = comp.compilerRtOptMode(), - .root_strip = comp.compilerRtStrip(), + .debug_format = comp.compilerRtDebugFormat(), .link_libc = false, .lto = switch (output_mode) { .Lib => comp.config.lto, @@ -6410,7 +6405,7 @@ pub fn build_crt_file( .fully_qualified_name = "root", .inherited = .{ .resolved_target = comp.root_mod.resolved_target, - .strip = comp.compilerRtStrip(), + .debug_format = comp.compilerRtDebugFormat(), .stack_check = false, .stack_protector = 0, .sanitize_c = false, @@ -6578,6 +6573,6 @@ pub fn compilerRtOptMode(comp: Compilation) std.builtin.OptimizeMode { /// This decides whether to strip debug info for all zig-provided libraries, including /// compiler-rt, libcxx, libc, libunwind, etc. -pub fn compilerRtStrip(comp: Compilation) bool { - return comp.root_mod.strip; +pub fn compilerRtDebugFormat(comp: Compilation) std.builtin.DebugFormat { + return comp.root_mod.debug_format; } diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index baaa6c9f4741..6a50af2fd07b 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -56,8 +56,7 @@ import_memory: bool, export_memory: bool, shared_memory: bool, is_test: bool, -debug_format: DebugFormat, -root_strip: bool, +debug_format: std.builtin.DebugFormat, root_error_tracing: bool, dll_export_fns: bool, rdynamic: bool, @@ -65,12 +64,6 @@ san_cov_trace_pc_guard: bool, pub const CFrontend = enum { clang, aro }; -pub const DebugFormat = union(enum) { - strip, - dwarf: std.dwarf.Format, - code_view, -}; - pub const Options = struct { output_mode: std.builtin.OutputMode, resolved_target: Module.ResolvedTarget, @@ -78,7 +71,6 @@ pub const Options = struct { have_zcu: bool, emit_bin: bool, root_optimize_mode: ?std.builtin.OptimizeMode = null, - root_strip: ?bool = null, root_error_tracing: ?bool = null, link_mode: ?std.builtin.LinkMode = null, ensure_libc_on_non_freestanding: bool = false, @@ -107,7 +99,7 @@ pub const Options = struct { import_memory: ?bool = null, export_memory: ?bool = null, shared_memory: ?bool = null, - debug_format: ?DebugFormat = null, + debug_format: ?std.builtin.DebugFormat = null, dll_export_fns: ?bool = null, rdynamic: ?bool = null, san_cov_trace_pc_guard: bool = false, @@ -432,24 +424,22 @@ pub fn resolve(options: Options) ResolveError!Config { break :b false; }; - const root_strip = b: { - if (options.root_strip) |x| break :b x; - if (root_optimize_mode == .ReleaseSmall) break :b true; - if (!target_util.hasDebugInfo(target)) break :b true; - break :b false; - }; - - const debug_format: DebugFormat = b: { - if (root_strip and !options.any_non_stripped) break :b .strip; + const debug_format: std.builtin.DebugFormat = b: { if (options.debug_format) |x| break :b x; + if (root_optimize_mode == .ReleaseSmall) break :b .none; + if (!target_util.hasDebugInfo(target)) break :b .none; break :b switch (target.ofmt) { - .elf, .goff, .macho, .wasm, .xcoff => .{ .dwarf = .@"32" }, + .elf, .goff, .macho, .wasm, .xcoff => switch (root_optimize_mode) { + .Debug, .ReleaseSafe => .dwarf32, + .ReleaseFast => .symbols, + .ReleaseSmall => unreachable, + }, .coff => .code_view, .c => switch (target.os.tag) { .windows, .uefi => .code_view, - else => .{ .dwarf = .@"32" }, + else => .dwarf32, }, - .spirv, .nvptx, .hex, .raw, .plan9 => .strip, + .spirv, .nvptx, .hex, .raw, .plan9 => .none, }; }; @@ -458,7 +448,7 @@ pub fn resolve(options: Options) ResolveError!Config { const root_error_tracing = b: { if (options.root_error_tracing) |x| break :b x; - if (root_strip) break :b false; + if (debug_format == .none or debug_format == .symbols) break :b false; if (!backend_supports_error_tracing) break :b false; break :b switch (root_optimize_mode) { .Debug => true, @@ -513,7 +503,6 @@ pub fn resolve(options: Options) ResolveError!Config { .use_lld = use_lld, .wasi_exec_model = wasi_exec_model, .debug_format = debug_format, - .root_strip = root_strip, .dll_export_fns = dll_export_fns, .rdynamic = rdynamic, }; diff --git a/src/Package/Module.zig b/src/Package/Module.zig index bfcef038edb7..a0414186ce1f 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -19,7 +19,7 @@ single_threaded: bool, error_tracing: bool, valgrind: bool, pic: bool, -strip: bool, +debug_format: std.builtin.DebugFormat, omit_frame_pointer: bool, stack_check: bool, stack_protector: u32, @@ -83,7 +83,7 @@ pub const CreateOptions = struct { error_tracing: ?bool = null, valgrind: ?bool = null, pic: ?bool = null, - strip: ?bool = null, + debug_format: ?std.builtin.DebugFormat = null, omit_frame_pointer: ?bool = null, stack_check: ?bool = null, /// null means default. @@ -124,10 +124,10 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { const unwind_tables = options.inherited.unwind_tables orelse if (options.parent) |p| p.unwind_tables else options.global.any_unwind_tables; - const strip = b: { - if (options.inherited.strip) |x| break :b x; - if (options.parent) |p| break :b p.strip; - break :b options.global.root_strip; + const debuginfo_format = b: { + if (options.inherited.debug_format) |x| break :b x; + if (options.parent) |p| break :b p.debug_format; + break :b options.global.debug_format; }; const valgrind = b: { @@ -138,8 +138,8 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { } if (options.inherited.valgrind) |x| break :b x; if (options.parent) |p| break :b p.valgrind; - if (strip) break :b false; - break :b optimize_mode == .Debug; + if (debuginfo_format == .none) break :b false; + break :b optimize_mode != .ReleaseSmall; }; const zig_backend = target_util.zigBackend(target, options.global.use_llvm); @@ -358,7 +358,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { .error_tracing = error_tracing, .valgrind = valgrind, .pic = pic, - .strip = strip, + .debug_format = debuginfo_format, .omit_frame_pointer = omit_frame_pointer, .stack_check = stack_check, .stack_protector = stack_protector, @@ -394,7 +394,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { .fuzz = fuzz, .pic = pic, .pie = options.global.pie, - .strip = strip, + .debug_format = debuginfo_format, .code_model = code_model, .omit_frame_pointer = omit_frame_pointer, .wasi_exec_model = options.global.wasi_exec_model, @@ -452,7 +452,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { .error_tracing = error_tracing, .valgrind = valgrind, .pic = pic, - .strip = strip, + .debug_format = debuginfo_format, .omit_frame_pointer = omit_frame_pointer, .stack_check = stack_check, .stack_protector = stack_protector, @@ -514,7 +514,7 @@ pub fn createLimited(gpa: Allocator, options: LimitedOptions) Allocator.Error!*P .error_tracing = undefined, .valgrind = undefined, .pic = undefined, - .strip = undefined, + .debug_format = undefined, .omit_frame_pointer = undefined, .stack_check = undefined, .stack_protector = undefined, diff --git a/src/Sema.zig b/src/Sema.zig index 5bee91f0474d..5311681e45ba 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2913,7 +2913,7 @@ fn zirStructDecl( try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (block.ownerModule().strip) break :codegen_type; + if (block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } @@ -3164,7 +3164,7 @@ fn zirEnumDecl( codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (block.ownerModule().strip) break :codegen_type; + if (block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } @@ -3288,7 +3288,7 @@ fn zirUnionDecl( try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (block.ownerModule().strip) break :codegen_type; + if (block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } @@ -3374,7 +3374,7 @@ fn zirOpaqueDecl( codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (block.ownerModule().strip) break :codegen_type; + if (block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } @@ -6650,7 +6650,7 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com } fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { - if (block.is_comptime or block.ownerModule().strip) return; + if (block.is_comptime or block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) return; const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt; @@ -6676,7 +6676,7 @@ fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!voi } fn zirDbgEmptyStmt(_: *Sema, block: *Block, _: Zir.Inst.Index) CompileError!void { - if (block.is_comptime or block.ownerModule().strip) return; + if (block.is_comptime or block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) return; _ = try block.addNoOp(.dbg_empty_stmt); } @@ -6699,7 +6699,7 @@ fn addDbgVar( air_tag: Air.Inst.Tag, name: []const u8, ) CompileError!void { - if (block.is_comptime or block.ownerModule().strip) return; + if (block.is_comptime or block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) return; const pt = sema.pt; const zcu = pt.zcu; @@ -7717,7 +7717,7 @@ fn analyzeCall( // set to in the `Block`. // This block instruction will be used to capture the return value from the // inlined function. - const need_debug_scope = !is_comptime_call and !block.is_typeof and !block.ownerModule().strip; + const need_debug_scope = !is_comptime_call and !block.is_typeof and !(block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols); const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len); try sema.air_instructions.append(gpa, .{ .tag = if (need_debug_scope) .dbg_inline_block else .block, @@ -8439,7 +8439,7 @@ fn instantiateGenericCall( .tag = .arg, .data = .{ .arg = .{ .ty = Air.internedToRef(arg_ty.toIntern()), - .name = if (child_block.ownerModule().strip) + .name = if (child_block.ownerModule().debug_format == .none or child_block.ownerModule().debug_format == .symbols) .none else try sema.appendAirString(fn_zir.nullTerminatedString(param_name)), @@ -21292,7 +21292,7 @@ fn structInitAnon( try zcu.comp.queueJob(.{ .resolve_type_fully = wip.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (block.ownerModule().strip) break :codegen_type; + if (block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) break :codegen_type; try zcu.comp.queueJob(.{ .codegen_type = wip.index }); } break :ty wip.finish(ip, new_cau_index.toOptional(), new_namespace_index); @@ -22659,7 +22659,7 @@ fn reifyEnum( codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (block.ownerModule().strip) break :codegen_type; + if (block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } @@ -22917,7 +22917,7 @@ fn reifyUnion( try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (block.ownerModule().strip) break :codegen_type; + if (block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } @@ -23276,7 +23276,7 @@ fn reifyStruct( try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (block.ownerModule().strip) break :codegen_type; + if (block.ownerModule().debug_format == .none or block.ownerModule().debug_format == .symbols) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index acd14135fa3e..08aa0329efcf 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1048,7 +1048,7 @@ fn createFileRootStruct( try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); codegen_type: { if (zcu.comp.config.use_llvm) break :codegen_type; - if (file.mod.strip) break :codegen_type; + if (file.mod.debug_format == .none or file.mod.debug_format == .symbols) break :codegen_type; // This job depends on any resolve_type_fully jobs queued up before it. try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } @@ -1393,7 +1393,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { if (!try decl_ty.hasRuntimeBitsSema(pt)) { if (zcu.comp.config.use_llvm) break :queue_codegen; - if (file.mod.strip) break :queue_codegen; + if (file.mod.debug_format == .none or file.mod.debug_format == .symbols) break :queue_codegen; } // This job depends on any resolve_type_fully jobs queued up before it. @@ -2215,7 +2215,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! .tag = .arg, .data = .{ .arg = .{ .ty = Air.internedToRef(param_ty), - .name = if (inner_block.ownerModule().strip) + .name = if (inner_block.ownerModule().debug_format == .none or inner_block.ownerModule().debug_format == .symbols) .none else try sema.appendAirString(sema.code.nullTerminatedString(param_name)), diff --git a/src/codegen/c.zig b/src/codegen/c.zig index c3e3c7fbdc11..936b5b614884 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -2646,9 +2646,12 @@ pub fn genTypeDecl( _ = try renderTypePrefix(.flush, global_ctype_pool, zcu, writer, global_ctype, .suffix, .{}); try writer.writeByte(';'); const file_scope = ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip); - if (!zcu.fileByIndex(file_scope).mod.strip) try writer.print(" /* {} */", .{ - ty.containerTypeName(ip).fmt(ip), - }); + switch (zcu.fileByIndex(file_scope).mod.debug_format) { + .none, .symbols => {}, + else => try writer.print(" /* {} */", .{ + ty.containerTypeName(ip).fmt(ip), + }), + } try writer.writeByte('\n'); }, }, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b94ea0799573..2935b120f1b5 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -851,7 +851,7 @@ pub const Object = struct { var builder = try Builder.init(.{ .allocator = gpa, - .strip = comp.config.debug_format == .strip, + .strip = comp.config.debug_format == .none or comp.config.debug_format == .symbols, .name = comp.root_name, .target = target, .triple = llvm_target_triple, @@ -1148,38 +1148,42 @@ pub const Object = struct { )); } - if (!o.builder.strip) { - module_flags.appendAssumeCapacity(try o.builder.metadataModuleFlag( - behavior_warning, - try o.builder.metadataString("Debug Info Version"), - try o.builder.metadataConstant(try o.builder.intConst(.i32, 3)), - )); + switch (comp.config.debug_format) { + .none, .symbols => {}, + .dwarf32, .dwarf64 => |f| { + module_flags.appendAssumeCapacity(try o.builder.metadataModuleFlag( + behavior_warning, + try o.builder.metadataString("Debug Info Version"), + try o.builder.metadataConstant(try o.builder.intConst(.i32, 3)), + )); - switch (comp.config.debug_format) { - .strip => unreachable, - .dwarf => |f| { - module_flags.appendAssumeCapacity(try o.builder.metadataModuleFlag( - behavior_max, - try o.builder.metadataString("Dwarf Version"), - try o.builder.metadataConstant(try o.builder.intConst(.i32, 4)), - )); + module_flags.appendAssumeCapacity(try o.builder.metadataModuleFlag( + behavior_max, + try o.builder.metadataString("Dwarf Version"), + try o.builder.metadataConstant(try o.builder.intConst(.i32, 4)), + )); - if (f == .@"64") { - module_flags.appendAssumeCapacity(try o.builder.metadataModuleFlag( - behavior_max, - try o.builder.metadataString("DWARF64"), - try o.builder.metadataConstant(.@"1"), - )); - } - }, - .code_view => { + if (f == .dwarf64) { module_flags.appendAssumeCapacity(try o.builder.metadataModuleFlag( - behavior_warning, - try o.builder.metadataString("CodeView"), + behavior_max, + try o.builder.metadataString("DWARF64"), try o.builder.metadataConstant(.@"1"), )); - }, - } + } + }, + .code_view => { + module_flags.appendAssumeCapacity(try o.builder.metadataModuleFlag( + behavior_warning, + try o.builder.metadataString("Debug Info Version"), + try o.builder.metadataConstant(try o.builder.intConst(.i32, 3)), + )); + + module_flags.appendAssumeCapacity(try o.builder.metadataModuleFlag( + behavior_warning, + try o.builder.metadataString("CodeView"), + try o.builder.metadataConstant(.@"1"), + )); + }, } const target = comp.root_mod.resolved_target.result; @@ -1482,7 +1486,7 @@ pub const Object = struct { var deinit_wip = true; var wip = try Builder.WipFunction.init(&o.builder, .{ .function = function_index, - .strip = owner_mod.strip, + .strip = !(owner_mod.debug_format == .dwarf32 or owner_mod.debug_format == .dwarf64 or owner_mod.debug_format == .code_view), }); defer if (deinit_wip) wip.deinit(); wip.cursor = .{ .block = try wip.block(0, "Entry") }; @@ -4829,7 +4833,7 @@ pub const NavGen = struct { const line_number = zcu.navSrcLine(nav_index) + 1; - if (!mod.strip) { + if (mod.debug_format == .dwarf32 or mod.debug_format == .dwarf64 or mod.debug_format == .code_view) { const debug_file = try o.getDebugFile(file_scope); const debug_global_var = try o.builder.debugGlobalVar( diff --git a/src/glibc.zig b/src/glibc.zig index 5bad947e5dba..e6e5cb4763e2 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -1271,7 +1271,7 @@ fn buildSharedLib( const map_file_path = try path.join(arena, &.{ bin_directory.path.?, all_map_basename }); const optimize_mode = comp.compilerRtOptMode(); - const strip = comp.compilerRtStrip(); + const debug_format = comp.compilerRtDebugFormat(); const config = try Compilation.Config.resolve(.{ .output_mode = .Lib, .link_mode = .dynamic, @@ -1280,7 +1280,7 @@ fn buildSharedLib( .have_zcu = false, .emit_bin = true, .root_optimize_mode = optimize_mode, - .root_strip = strip, + .debug_format = debug_format, .link_libc = false, }); @@ -1293,7 +1293,7 @@ fn buildSharedLib( .fully_qualified_name = "root", .inherited = .{ .resolved_target = comp.root_mod.resolved_target, - .strip = strip, + .debug_format = debug_format, .stack_check = false, .stack_protector = 0, .sanitize_c = false, diff --git a/src/libcxx.zig b/src/libcxx.zig index 6386d493a608..ff20e0dbf17f 100644 --- a/src/libcxx.zig +++ b/src/libcxx.zig @@ -153,7 +153,7 @@ pub fn buildLibCXX(comp: *Compilation, prog_node: std.Progress.Node) BuildError! }); const optimize_mode = comp.compilerRtOptMode(); - const strip = comp.compilerRtStrip(); + const debug_format = comp.compilerRtDebugFormat(); const config = Compilation.Config.resolve(.{ .output_mode = output_mode, @@ -163,7 +163,7 @@ pub fn buildLibCXX(comp: *Compilation, prog_node: std.Progress.Node) BuildError! .have_zcu = false, .emit_bin = true, .root_optimize_mode = optimize_mode, - .root_strip = strip, + .debug_format = debug_format, .link_libc = true, .lto = comp.config.lto, .any_sanitize_thread = comp.config.any_sanitize_thread, @@ -185,7 +185,7 @@ pub fn buildLibCXX(comp: *Compilation, prog_node: std.Progress.Node) BuildError! .fully_qualified_name = "root", .inherited = .{ .resolved_target = comp.root_mod.resolved_target, - .strip = strip, + .debug_format = debug_format, .stack_check = false, .stack_protector = 0, .sanitize_c = false, @@ -396,7 +396,7 @@ pub fn buildLibCXXABI(comp: *Compilation, prog_node: std.Progress.Node) BuildErr }); const optimize_mode = comp.compilerRtOptMode(); - const strip = comp.compilerRtStrip(); + const debug_format = comp.compilerRtDebugFormat(); const config = Compilation.Config.resolve(.{ .output_mode = output_mode, @@ -406,7 +406,7 @@ pub fn buildLibCXXABI(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .have_zcu = false, .emit_bin = true, .root_optimize_mode = optimize_mode, - .root_strip = strip, + .debug_format = debug_format, .link_libc = true, .lto = comp.config.lto, .any_sanitize_thread = comp.config.any_sanitize_thread, @@ -428,7 +428,7 @@ pub fn buildLibCXXABI(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .fully_qualified_name = "root", .inherited = .{ .resolved_target = comp.root_mod.resolved_target, - .strip = strip, + .debug_format = debug_format, .stack_check = false, .stack_protector = 0, .sanitize_c = false, diff --git a/src/libtsan.zig b/src/libtsan.zig index 8b2427df1ba2..74e26e6ace12 100644 --- a/src/libtsan.zig +++ b/src/libtsan.zig @@ -51,7 +51,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo }; const optimize_mode = comp.compilerRtOptMode(); - const strip = comp.compilerRtStrip(); + const debug_format = comp.compilerRtDebugFormat(); const link_libcpp = target.isDarwin(); const config = Compilation.Config.resolve(.{ @@ -62,7 +62,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo .have_zcu = false, .emit_bin = true, .root_optimize_mode = optimize_mode, - .root_strip = strip, + .debug_format = debug_format, .link_libc = true, .link_libcpp = link_libcpp, }) catch |err| { @@ -87,7 +87,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo .fully_qualified_name = "root", .inherited = .{ .resolved_target = comp.root_mod.resolved_target, - .strip = strip, + .debug_format = debug_format, .stack_check = false, .stack_protector = 0, .sanitize_c = false, diff --git a/src/libunwind.zig b/src/libunwind.zig index 4b3583e1cde4..872d2f2bdb00 100644 --- a/src/libunwind.zig +++ b/src/libunwind.zig @@ -34,7 +34,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .have_zcu = false, .emit_bin = true, .root_optimize_mode = comp.compilerRtOptMode(), - .root_strip = comp.compilerRtStrip(), + .debug_format = comp.compilerRtDebugFormat(), .link_libc = true, // Disable LTO to avoid https://github.com/llvm/llvm-project/issues/56825 .lto = false, @@ -56,7 +56,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .fully_qualified_name = "root", .inherited = .{ .resolved_target = comp.root_mod.resolved_target, - .strip = comp.compilerRtStrip(), + .debug_format = comp.compilerRtDebugFormat(), .stack_check = false, .stack_protector = 0, .red_zone = comp.root_mod.red_zone, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 41975a048d0d..49442cb463e5 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1839,7 +1839,7 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: try argv.append("-ERRORLIMIT:0"); try argv.append("-NOLOGO"); - if (comp.config.debug_format != .strip) { + if (comp.config.debug_format != .none) { try argv.append("-DEBUG"); const out_ext = std.fs.path.extension(full_out_path); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 608ff2fe3a73..92dc3ad443ea 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -768,7 +768,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { const gpa = comp.gpa; const diags = &comp.link_diags; const target = self.getTarget(); - const debug_fmt_strip = comp.config.debug_format == .strip; + const debug_fmt_strip = comp.config.debug_format == .none or comp.config.debug_format == .symbols; const default_sym_version = self.default_sym_version; const is_static_lib = self.base.isStaticLib(); @@ -1085,7 +1085,7 @@ fn dumpArgvInit(self: *Elf, arena: Allocator) !void { try argv.append(gpa, "-pie"); } - if (comp.config.debug_format == .strip) { + if (comp.config.debug_format == .none) { try argv.append(gpa, "-s"); } @@ -1123,7 +1123,7 @@ fn parseObject(self: *Elf, obj: link.Input.Object) !void { const gpa = self.base.comp.gpa; const diags = &self.base.comp.link_diags; const target = self.base.comp.root_mod.resolved_target.result; - const debug_fmt_strip = self.base.comp.config.debug_format == .strip; + const debug_fmt_strip = self.base.comp.config.debug_format == .none or self.base.comp.config.debug_format == .symbols; const default_sym_version = self.default_sym_version; const file_handles = &self.file_handles; @@ -1772,7 +1772,7 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s try argv.append("--export-dynamic"); } - if (comp.config.debug_format == .strip) { + if (comp.config.debug_format == .none) { try argv.append("-s"); } diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index effe12539ce8..b4dbf1cba3cb 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -97,9 +97,16 @@ pub fn init(self: *ZigObject, elf_file: *Elf, options: InitOptions) !void { } switch (comp.config.debug_format) { - .strip => {}, - .dwarf => |v| { - var dwarf = Dwarf.init(&elf_file.base, v); + .none => {}, + .symbols => { + // TODO: make sure symbols are generated + }, + .dwarf32, .dwarf64 => |v| { + var dwarf = Dwarf.init(&elf_file.base, switch (v) { + .dwarf32 => .@"32", + .dwarf64 => .@"64", + else => unreachable, + }); const addSectionSymbolWithAtom = struct { fn addSectionSymbolWithAtom( diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 349ee99ca430..26559610f8ad 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -1745,7 +1745,7 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) void { self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1)); } - if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo()) + if (macho_file.base.comp.config.debug_format != .none and self.hasDebugInfo()) self.calcStabsSize(macho_file); } @@ -2009,7 +2009,7 @@ pub fn writeSymtab(self: Object, macho_file: *MachO, ctx: anytype) void { n_strx += 1; } - if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo()) + if (macho_file.base.comp.config.debug_format != .none and self.hasDebugInfo()) self.writeStabs(n_strx, macho_file, ctx); } diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 097be1bc709e..493fd469d682 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -53,9 +53,14 @@ pub fn init(self: *ZigObject, macho_file: *MachO) !void { try self.strtab.buffer.append(gpa, 0); switch (comp.config.debug_format) { - .strip => {}, - .dwarf => |v| { - self.dwarf = Dwarf.init(&macho_file.base, v); + .none => {}, + .symbols => {}, + .dwarf32, .dwarf64 => |v| { + self.dwarf = Dwarf.init(&macho_file.base, switch (v) { + .dwarf32 => .@"32", + .dwarf64 => .@"64", + else => unreachable, + }); self.debug_strtab_dirty = true; self.debug_abbrev_dirty = true; self.debug_aranges_dirty = true; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 7d00aa5a64ff..fbd1b7aa93c7 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3055,11 +3055,11 @@ fn writeToFile( if (data_section_index) |data_index| { try wasm.emitDataRelocations(&binary_bytes, data_index, symbol_table); } - } else if (comp.config.debug_format != .strip) { + } else if (comp.config.debug_format != .none) { try wasm.emitNameSection(&binary_bytes, arena); } - if (comp.config.debug_format != .strip) { + if (comp.config.debug_format != .none) { // The build id must be computed on the main sections only, // so we have to do it now, before the debug sections. switch (wasm.base.build_id) { @@ -3569,7 +3569,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: try argv.append("--no-gc-sections"); } - if (comp.config.debug_format == .strip) { + if (comp.config.debug_format == .none) { try argv.append("-s"); } @@ -4119,7 +4119,7 @@ fn markReferences(wasm: *Wasm) !void { // Debug sections may require to be parsed and marked when it contains // relocations to alive symbols. - if (sym.tag == .section and comp.config.debug_format != .strip) { + if (sym.tag == .section and comp.config.debug_format != .none) { const object_id = sym_loc.file.unwrap() orelse continue; // Incremental debug info is done independently _ = try wasm.parseSymbolIntoAtom(object_id, sym_loc.index); sym.mark(); diff --git a/src/main.zig b/src/main.zig index 39fd3e6213c7..9c7f754ab77f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -520,8 +520,12 @@ const usage_build_generic = \\ -fno-error-tracing Disable error tracing in Debug and ReleaseSafe mode \\ -fsingle-threaded Code assumes there is only one thread \\ -fno-single-threaded Code may not assume there is only one thread - \\ -fstrip Omit debug symbols - \\ -fno-strip Keep debug symbols + \\ -fdebuginfo=[format] Set strip level + \\ none Don't emit any debug info + \\ symbols Emit symbol table + \\ dwarf32 Emit dwarf32 debug info + \\ dwarf64 Emit dwarf32 debug info + \\ code_view (Windows) Emit code_view debug info \\ -idirafter [dir] Add directory to AFTER include search path \\ -isystem [dir] Add directory to SYSTEM include search path \\ -I[dir] Add directory to include search path @@ -1528,14 +1532,21 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--show-builtin")) { show_builtin = true; emit_bin = .no; - } else if (mem.eql(u8, arg, "-fstrip")) { - mod_opts.strip = true; - } else if (mem.eql(u8, arg, "-fno-strip")) { - mod_opts.strip = false; - } else if (mem.eql(u8, arg, "-gdwarf32")) { - create_module.opts.debug_format = .{ .dwarf = .@"32" }; - } else if (mem.eql(u8, arg, "-gdwarf64")) { - create_module.opts.debug_format = .{ .dwarf = .@"64" }; + } else if (mem.startsWith(u8, arg, "-fdebuginfo=")) { + const debuginfo_format_str = arg["-fdebuginfo=".len..]; + if (mem.eql(u8, debuginfo_format_str, "none")) { + mod_opts.debug_format = .none; + } else if (mem.eql(u8, debuginfo_format_str, "symbols")) { + mod_opts.debug_format = .symbols; + } else if (mem.eql(u8, debuginfo_format_str, "dwarf32")) { + mod_opts.debug_format = .dwarf32; + } else if (mem.eql(u8, debuginfo_format_str, "dwarf64")) { + mod_opts.debug_format = .dwarf64; + } else if (mem.eql(u8, debuginfo_format_str, "code_view")) { + mod_opts.debug_format = .code_view; + } else { + fatal("expected [none|symbols|dwarf32|dwarf64|code_view] after -fdebuginfo=", .{}); + } } else if (mem.eql(u8, arg, "-fformatted-panics")) { // Remove this after 0.15.0 is tagged. warn("-fformatted-panics is deprecated and does nothing", .{}); @@ -2143,7 +2154,7 @@ fn buildOutputType( } }, .debug => { - mod_opts.strip = false; + mod_opts.debug_format = .dwarf32; if (mem.eql(u8, it.only_arg, "g")) { // We handled with strip = false above. } else if (mem.eql(u8, it.only_arg, "g1") or @@ -2155,14 +2166,8 @@ fn buildOutputType( try cc_argv.appendSlice(arena, it.other_args); } }, - .gdwarf32 => { - mod_opts.strip = false; - create_module.opts.debug_format = .{ .dwarf = .@"32" }; - }, - .gdwarf64 => { - mod_opts.strip = false; - create_module.opts.debug_format = .{ .dwarf = .@"64" }; - }, + .gdwarf32 => mod_opts.debug_format = .dwarf32, + .gdwarf64 => mod_opts.debug_format = .dwarf64, .sanitize => { var san_it = std.mem.splitScalar(u8, it.only_arg, ','); var recognized_any = false; @@ -2219,7 +2224,7 @@ fn buildOutputType( .framework_dir => try create_module.framework_dirs.append(arena, it.only_arg), .framework => try create_module.frameworks.put(arena, it.only_arg, .{}), .nostdlibinc => create_module.want_native_include_dirs = false, - .strip => mod_opts.strip = true, + .strip => mod_opts.debug_format = .none, .exec_model => { create_module.opts.wasi_exec_model = parseWasiExecModel(it.only_arg); }, @@ -2508,12 +2513,12 @@ fn buildOutputType( mem.eql(u8, arg, "--color-diagnostics=never")) { color = .off; - } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--strip-all") or - mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--strip-debug")) - { + } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--strip-all")) { // -s, --strip-all Strip all symbols + mod_opts.debug_format = .none; + } else if (mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--strip-debug")) { // -S, --strip-debug Strip debugging symbols - mod_opts.strip = true; + mod_opts.debug_format = .symbols; } else if (mem.eql(u8, arg, "--start-group") or mem.eql(u8, arg, "--end-group")) { @@ -2823,7 +2828,7 @@ fn buildOutputType( create_module.opts.any_unwind_tables = .@"async"; }, }; - if (mod_opts.strip == false) + if (mod_opts.debug_format != null and mod_opts.debug_format.? != .none) create_module.opts.any_non_stripped = true; if (mod_opts.error_tracing == true) create_module.opts.any_error_tracing = true; @@ -3842,7 +3847,7 @@ fn createModule( const resolved_target = cli_mod.inherited.resolved_target.?; create_module.opts.resolved_target = resolved_target; create_module.opts.root_optimize_mode = cli_mod.inherited.optimize_mode; - create_module.opts.root_strip = cli_mod.inherited.strip; + create_module.opts.debug_format = cli_mod.inherited.debug_format; create_module.opts.root_error_tracing = cli_mod.inherited.error_tracing; const target = resolved_target.result; @@ -5400,7 +5405,11 @@ fn jitCmd( .Debug else .ReleaseFast; - const strip = optimize_mode != .Debug; + const debuginfo_format: std.builtin.DebugFormat = switch (optimize_mode) { + .Debug, .ReleaseSafe => .dwarf32, + .ReleaseFast => .symbols, + .ReleaseSmall => .none, + }; const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); @@ -5447,7 +5456,7 @@ fn jitCmd( const config = try Compilation.Config.resolve(.{ .output_mode = .Exe, - .root_strip = strip, + .debug_format = debuginfo_format, .root_optimize_mode = optimize_mode, .resolved_target = resolved_target, .have_zcu = true, @@ -5463,7 +5472,7 @@ fn jitCmd( .inherited = .{ .resolved_target = resolved_target, .optimize_mode = optimize_mode, - .strip = strip, + .debug_format = debuginfo_format, }, .global = config, .parent = null, @@ -5486,7 +5495,7 @@ fn jitCmd( .inherited = .{ .resolved_target = resolved_target, .optimize_mode = optimize_mode, - .strip = strip, + .debug_format = debuginfo_format, }, .global = config, .parent = null, @@ -7479,7 +7488,7 @@ fn handleModArg( create_module.opts.any_unwind_tables = .@"async"; }, }; - if (mod_opts.strip == false) + if (mod_opts.debug_format != null and mod_opts.debug_format.? != .none) create_module.opts.any_non_stripped = true; if (mod_opts.error_tracing == true) create_module.opts.any_error_tracing = true; diff --git a/src/musl.zig b/src/musl.zig index ace72c0b0719..35e6d809228b 100644 --- a/src/musl.zig +++ b/src/musl.zig @@ -213,7 +213,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro }, .libc_so => { const optimize_mode = comp.compilerRtOptMode(); - const strip = comp.compilerRtStrip(); + const debug_format = comp.compilerRtDebugFormat(); const output_mode: std.builtin.OutputMode = .Lib; const config = try Compilation.Config.resolve(.{ .output_mode = output_mode, @@ -223,7 +223,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro .have_zcu = false, .emit_bin = true, .root_optimize_mode = optimize_mode, - .root_strip = strip, + .debug_format = debug_format, .link_libc = false, }); @@ -261,7 +261,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro .fully_qualified_name = "root", .inherited = .{ .resolved_target = comp.root_mod.resolved_target, - .strip = strip, + .debug_format = debug_format, .stack_check = false, .stack_protector = 0, .sanitize_c = false, diff --git a/test/debug_format_stack_traces.zig b/test/debug_format_stack_traces.zig new file mode 100644 index 000000000000..29fcb936d2c5 --- /dev/null +++ b/test/debug_format_stack_traces.zig @@ -0,0 +1,55 @@ +const std = @import("std"); +const os = std.os; +const tests = @import("tests.zig"); + +pub fn addCases(cases: *tests.DebugFormatStackTraceContext) void { + cases.addCase(.{ + .name = "hoyten", + .source = + \\ const std = @import("std"); + \\ + \\ noinline fn foo(x: u32) u32 { + \\ return x * x; + \\ } + \\ + \\ noinline fn bar() u32 { + \\ return foo(std.math.maxInt(u32)); + \\ } + \\ + \\ pub fn main() !void { + \\ std.debug.print("{}", .{bar()}); + \\ } + , + .symbols = .{ + .exclude_os = &.{ .macos, .windows }, + // release modes won't check for overflow, so no error occurs + .exclude_optimize_mode = &.{ .ReleaseFast, .ReleaseSmall }, + .expect_panic = true, + .expect = + \\thread [thread_id] panic: integer overflow + \\???:?:?: [address] in source.foo (???) + \\???:?:?: [address] in source.bar (???) + \\???:?:?: [address] in source.main (???) + \\ + , + }, + .dwarf32 = .{ + // release modes won't check for overflow, so no error occurs + .exclude_optimize_mode = &.{ .ReleaseFast, .ReleaseSmall }, + .expect_panic = true, + .expect = + \\thread [thread_id] panic: integer overflow + \\source.zig:4:15: [address] in foo (test) + \\ return x * x; + \\ ^ + \\source.zig:8:16: [address] in bar (test) + \\ return foo(std.math.maxInt(u32)); + \\ ^ + \\source.zig:12:33: [address] in main (test) + \\ std.debug.print("{}", .{bar()}); + \\ ^ + \\ + , + }, + }); +} diff --git a/test/link/elf.zig b/test/link/elf.zig index 5014d20c9862..c5484931a8bc 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -1798,7 +1798,7 @@ fn testImportingDataDynamic(b: *Build, opts: Options) *Step { \\ printFoo(); \\} , - .strip = true, // TODO temp hack + .debuginfo = .none, // TODO temp hack // TODO: What was that temp hack? }); main.pie = true; main.linkLibrary(dso); @@ -1845,7 +1845,7 @@ fn testImportingDataStatic(b: *Build, opts: Options) *Step { \\ @import("std").debug.print("{d}\n", .{foo}); \\} , - .strip = true, // TODO temp hack + .debuginfo = .none, // TODO temp hack // TODO: What was that temp hack? }); main.linkLibrary(lib); main.linkLibC(); @@ -2248,7 +2248,7 @@ fn testMismatchedCpuArchitectureError(b: *Build, opts: Options) *Step { }, .{ .name = "a", .c_source_bytes = "int foo;", - .strip = true, + .debuginfo = .none, }); const exe = addExecutable(b, opts, .{ .name = "main" }); @@ -2993,7 +2993,7 @@ fn testStrip(b: *Build, opts: Options) *Step { { const exe = addExecutable(b, opts, .{ .name = "main1" }); exe.addObject(obj); - exe.root_module.strip = false; + exe.root_module.debuginfo = .dwarf32; exe.linkLibC(); const check = exe.checkObject(); @@ -3006,7 +3006,7 @@ fn testStrip(b: *Build, opts: Options) *Step { { const exe = addExecutable(b, opts, .{ .name = "main2" }); exe.addObject(obj); - exe.root_module.strip = true; + exe.root_module.debuginfo = .none; exe.linkLibC(); const check = exe.checkObject(); diff --git a/test/link/link.zig b/test/link/link.zig index 4537846353e3..104d1c94cf82 100644 --- a/test/link/link.zig +++ b/test/link/link.zig @@ -9,7 +9,7 @@ pub const Options = struct { optimize: std.builtin.OptimizeMode = .Debug, use_llvm: bool = true, use_lld: bool = false, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, }; pub fn addTestStep(b: *Build, prefix: []const u8, opts: Options) *Step { @@ -17,8 +17,14 @@ pub fn addTestStep(b: *Build, prefix: []const u8, opts: Options) *Step { const optimize = @tagName(opts.optimize); const use_llvm = if (opts.use_llvm) "llvm" else "no-llvm"; const use_lld = if (opts.use_lld) "lld" else "no-lld"; - if (opts.strip) |strip| { - const s = if (strip) "strip" else "no-strip"; + if (opts.debuginfo) |debuginfo| { + const s = switch (debuginfo) { + .none => "debuginfo-none", + .symbols => "debuginfo-symbols", + .dwarf32 => "debuginfo-dwarf32", + .dwarf64 => "debuginfo-dwarf64", + .code_view => "debuginfo-code_view", + }; const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}-{s}-{s}", .{ prefix, target, optimize, use_llvm, use_lld, s, }) catch @panic("OOM"); @@ -43,7 +49,7 @@ const OverlayOptions = struct { objcpp_source_flags: []const []const u8 = &.{}, zig_source_bytes: ?[]const u8 = null, pic: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, }; pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Compile { @@ -79,7 +85,7 @@ fn addCompileStep( break :rsf b.addWriteFiles().add(name, bytes); }, .pic = overlay.pic, - .strip = if (base.strip) |s| s else overlay.strip, + .debuginfo = if (base.debuginfo) |d| d else overlay.debuginfo, }, .use_llvm = base.use_llvm, .use_lld = base.use_lld, diff --git a/test/link/macho.zig b/test/link/macho.zig index 1d790b20c69d..dea10581b2c4 100644 --- a/test/link/macho.zig +++ b/test/link/macho.zig @@ -898,7 +898,7 @@ fn testLinkingStaticLib(b: *Build, opts: Options) *Step { const obj = addObject(b, opts, .{ .name = "bobj", .zig_source_bytes = "export var bar: i32 = -42;", - .strip = true, // TODO for self-hosted, we don't really emit any valid DWARF yet since we only export a global + .debuginfo = .none, // TODO for self-hosted, we don't really emit any valid DWARF yet since we only export a global }); const lib = addStaticLibrary(b, opts, .{ diff --git a/test/link/wasm/archive/build.zig b/test/link/wasm/archive/build.zig index 34c5818ad88b..d9273fa97150 100644 --- a/test/link/wasm/archive/build.zig +++ b/test/link/wasm/archive/build.zig @@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize .root_source_file = b.path("main.zig"), .optimize = optimize, .target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }), - .strip = false, + .debuginfo = .dwarf32, }); lib.entry = .disabled; lib.use_llvm = false; diff --git a/test/link/wasm/bss/build.zig b/test/link/wasm/bss/build.zig index dc7d1bae4b9a..00a1ba880fff 100644 --- a/test/link/wasm/bss/build.zig +++ b/test/link/wasm/bss/build.zig @@ -19,7 +19,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt .root_source_file = b.path("lib.zig"), .target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }), .optimize = optimize_mode, - .strip = false, + .debuginfo = .dwarf32, }); lib.entry = .disabled; lib.use_llvm = false; @@ -67,7 +67,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt .root_source_file = b.path("lib2.zig"), .target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }), .optimize = optimize_mode, - .strip = false, + .debuginfo = .dwarf32, }); lib.entry = .disabled; lib.use_llvm = false; diff --git a/test/link/wasm/producers/build.zig b/test/link/wasm/producers/build.zig index 2dae6e3f9bd1..364beafde2e9 100644 --- a/test/link/wasm/producers/build.zig +++ b/test/link/wasm/producers/build.zig @@ -19,7 +19,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize .root_source_file = b.path("lib.zig"), .target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }), .optimize = optimize, - .strip = false, + .debuginfo = .dwarf32, }); lib.entry = .disabled; lib.use_llvm = false; diff --git a/test/link/wasm/segments/build.zig b/test/link/wasm/segments/build.zig index 3c8bac3f0730..71828fe5ec99 100644 --- a/test/link/wasm/segments/build.zig +++ b/test/link/wasm/segments/build.zig @@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize .root_source_file = b.path("lib.zig"), .target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }), .optimize = optimize, - .strip = false, + .debuginfo = .dwarf32, }); lib.entry = .disabled; lib.use_llvm = false; diff --git a/test/link/wasm/shared-memory/build.zig b/test/link/wasm/shared-memory/build.zig index 7807a95a4fc4..7b1d5551caa7 100644 --- a/test/link/wasm/shared-memory/build.zig +++ b/test/link/wasm/shared-memory/build.zig @@ -21,7 +21,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt .os_tag = .freestanding, }), .optimize = optimize_mode, - .strip = false, + .debuginfo = .dwarf32, .single_threaded = false, }); exe.entry = .disabled; diff --git a/test/link/wasm/stack_pointer/build.zig b/test/link/wasm/stack_pointer/build.zig index e42e36288001..ddc96198a36e 100644 --- a/test/link/wasm/stack_pointer/build.zig +++ b/test/link/wasm/stack_pointer/build.zig @@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize .root_source_file = b.path("lib.zig"), .target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }), .optimize = optimize, - .strip = false, + .debuginfo = .dwarf32, }); lib.entry = .disabled; lib.use_llvm = false; diff --git a/test/link/wasm/type/build.zig b/test/link/wasm/type/build.zig index 46b80dbfe5dc..c0ae14794eae 100644 --- a/test/link/wasm/type/build.zig +++ b/test/link/wasm/type/build.zig @@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize .root_source_file = b.path("lib.zig"), .target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }), .optimize = optimize, - .strip = false, + .debuginfo = .dwarf32, }); exe.entry = .disabled; exe.use_llvm = false; diff --git a/test/src/DebugFormatStackTrace.zig b/test/src/DebugFormatStackTrace.zig new file mode 100644 index 000000000000..d9c8fcf0d298 --- /dev/null +++ b/test/src/DebugFormatStackTrace.zig @@ -0,0 +1,96 @@ +b: *std.Build, +step: *Step, +test_index: usize, +targets: []const Target, +check_exe: *std.Build.Step.Compile, + +pub const Target = struct { + target: std.Target.Query, + optimize_mode: OptimizeMode, +}; + +const Config = struct { + name: []const u8, + source: []const u8, + symbols: ?PerFormat = null, + dwarf32: ?PerFormat = null, + + const PerFormat = struct { + expect_panic: bool = false, + expect: []const u8, + exclude_os: []const std.Target.Os.Tag = &.{}, + exclude_optimize_mode: []const std.builtin.OptimizeMode = &.{}, + }; +}; + +pub fn addCase(this: *@This(), config: Config) void { + if (config.symbols) |per_format| + this.addExpect(config.name, config.source, .symbols, per_format); + + if (config.dwarf32) |per_format| + this.addExpect(config.name, config.source, .dwarf32, per_format); +} + +fn addExpect( + this: *@This(), + name: []const u8, + source: []const u8, + debug_format: std.builtin.DebugFormat, + mode_config: Config.PerFormat, +) void { + const b = this.b; + const write_files = b.addWriteFiles(); + const source_zig = write_files.add("source.zig", source); + + add_target_loop: for (this.targets) |target| { + if (mem.indexOfScalar(std.builtin.OptimizeMode, mode_config.exclude_optimize_mode, target.optimize_mode)) |_| continue :add_target_loop; + + const resolved_target = b.resolveTargetQuery(target.target); + for (mode_config.exclude_os) |tag| if (tag == resolved_target.result.os.tag) continue :add_target_loop; + + const annotated_case_name = fmt.allocPrint(b.allocator, "check {s}-{s}-{s}-{s}", .{ + name, + @tagName(debug_format), + @tagName(target.optimize_mode), + resolved_target.result.linuxTriple(b.allocator) catch @panic("OOM"), + }) catch @panic("OOM"); + + const exe = b.addExecutable(.{ + .name = "test", + .root_source_file = source_zig, + .target = resolved_target, + .optimize = target.optimize_mode, + .debuginfo = debug_format, + }); + + const run = b.addRunArtifact(exe); + run.removeEnvironmentVariable("CLICOLOR_FORCE"); + run.setEnvironmentVariable("NO_COLOR", "1"); + + // make sure to add term check fist, as `expectStdOutEqual` will detect no expectation for term and make it check for exit code 0 + if (mode_config.expect_panic) { + switch (resolved_target.result.os.tag) { + // Expect exit code 3 on abort: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=msvc-170 + .windows => run.addCheck(.{ .expect_term = .{ .Exited = 3 } }), + else => run.addCheck(.{ .expect_term = .{ .Signal = 6 } }), + } + } + run.expectStdOutEqual(""); + + const check_run = b.addRunArtifact(this.check_exe); + check_run.setName(annotated_case_name); + check_run.addFileArg(run.captureStdErr()); + check_run.addArgs(&.{ + @tagName(debug_format), + }); + check_run.expectStdOutEqual(mode_config.expect); + + this.step.dependOn(&check_run.step); + } +} + +const std = @import("std"); +const OptimizeMode = std.builtin.OptimizeMode; +const Step = std.Build.Step; +const fmt = std.fmt; +const mem = std.mem; diff --git a/test/src/Debugger.zig b/test/src/Debugger.zig index d6fae4f2c939..7547bb622745 100644 --- a/test/src/Debugger.zig +++ b/test/src/Debugger.zig @@ -2422,7 +2422,7 @@ fn addTest( .link_libc = target.link_libc, .single_threaded = target.single_threaded, .pic = target.pic, - .strip = false, + .debuginfo = .dwarf32, .use_llvm = false, .use_lld = false, }); diff --git a/test/src/check-debug-format-stack-trace.zig b/test/src/check-debug-format-stack-trace.zig new file mode 100644 index 000000000000..a3772cbea09c --- /dev/null +++ b/test/src/check-debug-format-stack-trace.zig @@ -0,0 +1,108 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const mem = std.mem; +const fs = std.fs; + +pub fn main() !void { + var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + const args = try std.process.argsAlloc(arena); + + const input_path = args[1]; + const debug_format_text = args[2]; + + const input_bytes = try std.fs.cwd().readFileAlloc(arena, input_path, 5 * 1024 * 1024); + const debug_format = std.meta.stringToEnum(std.builtin.DebugFormat, debug_format_text).?; + _ = debug_format; + + var stderr = input_bytes; + + // process result + // - keep only basename of source file path + // - replace address with symbolic string + // - replace function name with symbolic string when optimize_mode != .Debug + // - skip empty lines + const got: []const u8 = got_result: { + var buf = std.ArrayList(u8).init(arena); + defer buf.deinit(); + + if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1]; + var it = mem.splitScalar(u8, stderr, '\n'); + process_lines: while (it.next()) |line| { + if (line.len == 0) continue; + + if (mem.startsWith(u8, line, "thread ")) { + const pos_after_thread = "thread ".len; + const end_of_thread_id = mem.indexOfScalarPos(u8, line, pos_after_thread, ' ') orelse { + // unexpected pattern: emit raw line and cont + try buf.appendSlice(line); + try buf.appendSlice("\n"); + continue :process_lines; + }; + + // emit substituted line + try buf.appendSlice(line[0..pos_after_thread]); + try buf.appendSlice("[thread_id]"); + try buf.appendSlice(line[end_of_thread_id..]); + try buf.appendSlice("\n"); + continue :process_lines; + } + + // offset search past `[drive]:` on windows + var pos: usize = if (builtin.os.tag == .windows) 2 else 0; + // locate delims/anchor + const delims = [_][]const u8{ ":", ":", ":", " in ", " (", ")" }; + var marks = [_]usize{0} ** delims.len; + for (delims, 0..) |delim, i| { + marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse { + // unexpected pattern: emit raw line and cont + try buf.appendSlice(line); + try buf.appendSlice("\n"); + continue :process_lines; + }; + pos = marks[i] + delim.len; + } + + const source_file = line[0..marks[0]]; + const source_line = line[marks[0] + delims[0].len .. marks[1]]; + const source_column = line[marks[1] + delims[1].len .. marks[2]]; + const trace_address = line[marks[2] + delims[2].len .. marks[3]]; + const source_symbol = line[marks[3] + delims[3].len .. marks[4]]; + const source_compile_unit = line[marks[4] + delims[4].len .. marks[5]]; + + _ = trace_address; + + const source_file_basename = std.fs.path.basename(source_file); + const is_unknown_source_file = mem.allEqual(u8, source_file, '?'); + + // stop processing once the symbols are no longer the source file + if (!is_unknown_source_file and !mem.eql(u8, source_file_basename, "source.zig")) { + break; + } else if (is_unknown_source_file and !mem.startsWith(u8, source_symbol, "source.")) { + break; + } + + // On Windows specifically, the compile unit can end with `.exe` or `.exe.obj` + const source_compile_unit_extension_stripped = if (mem.indexOfScalar(u8, source_compile_unit, '.')) |idot| + source_compile_unit[0..idot] + else + source_compile_unit; + + // emit substituted line + try buf.writer().print("{s}:{s}:{s}: [address] in {s} ({s})", .{ + source_file_basename, + source_line, + source_column, + source_symbol, + source_compile_unit_extension_stripped, + }); + + try buf.appendSlice("\n"); + } + break :got_result try buf.toOwnedSlice(); + }; + + try std.io.getStdOut().writeAll(got); +} diff --git a/test/standalone/stack_iterator/build.zig b/test/standalone/stack_iterator/build.zig index 38f66edd9f60..05f01571000c 100644 --- a/test/standalone/stack_iterator/build.zig +++ b/test/standalone/stack_iterator/build.zig @@ -68,7 +68,7 @@ pub fn build(b: *std.Build) void { .name = "c_shared_lib", .target = target, .optimize = optimize, - .strip = false, + .debuginfo = .dwarf32, }); if (target.result.os.tag == .windows) diff --git a/test/standalone/strip_empty_loop/build.zig b/test/standalone/strip_empty_loop/build.zig index d34120ed06b8..6267566264d6 100644 --- a/test/standalone/strip_empty_loop/build.zig +++ b/test/standalone/strip_empty_loop/build.zig @@ -12,7 +12,7 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("main.zig"), .optimize = optimize, .target = target, - .strip = true, + .debuginfo = .none, }); // TODO: actually check the output diff --git a/test/standalone/strip_struct_init/build.zig b/test/standalone/strip_struct_init/build.zig index ef7960d13051..6f4d6153231c 100644 --- a/test/standalone/strip_struct_init/build.zig +++ b/test/standalone/strip_struct_init/build.zig @@ -9,7 +9,7 @@ pub fn build(b: *std.Build) void { const main = b.addTest(.{ .root_source_file = b.path("main.zig"), .optimize = optimize, - .strip = true, + .debuginfo = .none, }); test_step.dependOn(&b.addRunArtifact(main).step); diff --git a/test/tests.zig b/test/tests.zig index 572838a2ea4b..94beae0369b8 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -8,6 +8,7 @@ const Step = std.Build.Step; // Cases const compare_output = @import("compare_output.zig"); const stack_traces = @import("stack_traces.zig"); +const debug_format_stack_traces = @import("debug_format_stack_traces.zig"); const assemble_and_link = @import("assemble_and_link.zig"); const translate_c = @import("translate_c.zig"); const run_translated_c = @import("run_translated_c.zig"); @@ -16,6 +17,7 @@ const run_translated_c = @import("run_translated_c.zig"); pub const TranslateCContext = @import("src/TranslateC.zig"); pub const RunTranslatedCContext = @import("src/RunTranslatedC.zig"); pub const CompareOutputContext = @import("src/CompareOutput.zig"); +pub const DebugFormatStackTraceContext = @import("src/DebugFormatStackTrace.zig"); pub const StackTracesContext = @import("src/StackTrace.zig"); pub const DebuggerContext = @import("src/Debugger.zig"); @@ -27,7 +29,7 @@ const TestTarget = struct { use_llvm: ?bool = null, use_lld: ?bool = null, pic: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, skip_modules: []const []const u8 = &.{}, // This is intended for targets that are known to be slow to compile. These are acceptable to @@ -121,7 +123,7 @@ const test_targets = blk: { }, .use_llvm = false, .use_lld = false, - .strip = true, + .debuginfo = .none, }, // Doesn't support new liveness //.{ @@ -961,7 +963,7 @@ const CAbiTarget = struct { use_llvm: ?bool = null, use_lld: ?bool = null, pic: ?bool = null, - strip: ?bool = null, + debuginfo: ?std.builtin.DebugFormat = null, c_defines: []const []const u8 = &.{}, }; @@ -993,7 +995,7 @@ const c_abi_targets = [_]CAbiTarget{ }, .use_llvm = false, .use_lld = false, - .strip = true, + .debuginfo = .none, .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"}, }, .{ @@ -1126,6 +1128,51 @@ pub fn addStackTraceTests( return cases.step; } +pub fn addDebugFormatStackTraceTests( + b: *std.Build, + optimize_modes: []const OptimizeMode, + skip_non_native: bool, +) *Step { + const check_exe = b.addExecutable(.{ + .name = "check-debug-format-stack-trace", + .root_source_file = b.path("test/src/check-debug-format-stack-trace.zig"), + .target = b.graph.host, + .optimize = .Debug, + }); + + const targets = blk: { + var targets = std.ArrayListUnmanaged(DebugFormatStackTraceContext.Target){}; + for (optimize_modes) |optimize_mode| { + targets.append(b.allocator, .{ + .optimize_mode = optimize_mode, + .target = .{}, + }) catch @panic("OOM"); + + if (skip_non_native) continue; + + targets.appendSlice(b.allocator, &.{ + .{ .optimize_mode = optimize_mode, .target = .{ .os_tag = .linux } }, + .{ .optimize_mode = optimize_mode, .target = .{ .os_tag = .windows } }, + .{ .optimize_mode = optimize_mode, .target = .{ .os_tag = .macos } }, + }) catch @panic("OOM"); + } + break :blk targets.toOwnedSlice(b.allocator) catch @panic("OOM"); + }; + + const cases = b.allocator.create(DebugFormatStackTraceContext) catch @panic("OOM"); + cases.* = .{ + .b = b, + .step = b.step("test-debug-format-stack-traces", "Run the debug format stack trace tests"), + .test_index = 0, + .check_exe = check_exe, + .targets = targets, + }; + + debug_format_stack_traces.addCases(cases); + + return cases.step; +} + fn compilerHasPackageManager(b: *std.Build) bool { // We can only use dependencies if the compiler was built with support for package management. // (zig2 doesn't support it, but we still need to construct a build graph to build stage3.) @@ -1247,11 +1294,11 @@ pub fn addCliTests(b: *std.Build) *Step { // This is intended to be the exact CLI usage used by godbolt.org. const run = b.addSystemCommand(&.{ - b.graph.zig_exe, "build-obj", - "--cache-dir", tmp_path, - "--name", "example", - "-fno-emit-bin", "-fno-emit-h", - "-fstrip", "-OReleaseFast", + b.graph.zig_exe, "build-obj", + "--cache-dir", tmp_path, + "--name", "example", + "-fno-emit-bin", "-fno-emit-h", + "-fdebuginfo=none", "-OReleaseFast", }); run.addFileArg(example_zig); const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s"); @@ -1525,7 +1572,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { .use_lld = test_target.use_lld, .zig_lib_dir = b.path("lib"), .pic = test_target.pic, - .strip = test_target.strip, + .debuginfo = test_target.debuginfo, }); if (options.no_builtin) these_tests.no_builtin = true; if (options.build_options) |build_options| { @@ -1698,7 +1745,7 @@ pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step { .use_llvm = c_abi_target.use_llvm, .use_lld = c_abi_target.use_lld, .pic = c_abi_target.pic, - .strip = c_abi_target.strip, + .debuginfo = c_abi_target.debuginfo, }); test_step.addCSourceFile(.{ .file = b.path("test/c_abi/cfuncs.c"),