fix(dwarf): non-empty comp_dir so ld keeps the debug map (issue 0058)
A source path with no directory component (`sx build main.sx` from the project dir — what the chess app does) made `diFileFor` emit a `DIFile` with an empty `directory:`, so the compile unit's `DW_AT_comp_dir` was "". Apple's ld then silently drops the *entire* object's debug map (0 N_OSO) and the binary is undebuggable — lldb resolves no sx source. Builds whose path had any directory (`.sx-tmp/x.sx`, `examples/x.sx`) were unaffected, which is why small repros + the stepping smoke passed and only the bundled chess app hit it. Fix: diFileFor falls back to "." (and "/" for a root-level file) when the path has no directory component, so comp_dir is never empty. Verified: chess (`sx build --target macos --emit-obj main.sx`) now links with OSO=1 and lldb resolves `frame at main.sx:82:8`. Regression guard added to the DWARF unit test (asserts `DIFile(... directory: ".")` for a bare filename). Gates: zig build, zig build test, run_examples.sh -> 291 passed, debug-stepping smoke ok.
This commit is contained in:
@@ -989,7 +989,10 @@ test "emit: ERR E3.0 — DWARF debug info (compile unit + subprogram + per-inst
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "\"Debug Info Version\"") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "\"Dwarf Version\"") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "DICompileUnit") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "DIFile(filename: \"probe.sx\"") != null);
|
||||
// Regression (issue 0058): a bare filename (no directory component) must
|
||||
// still get a NON-EMPTY `directory:` — an empty `DW_AT_comp_dir` makes ld
|
||||
// silently drop the whole debug map, so the binary becomes undebuggable.
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "DIFile(filename: \"probe.sx\", directory: \".\")") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "DISubprogram(name: \"main\"") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "DILocation(line: 3") != null);
|
||||
}
|
||||
|
||||
@@ -394,11 +394,15 @@ pub const LLVMEmitter = struct {
|
||||
}
|
||||
|
||||
/// The `DIFile` for `path`, created once and cached. Splits the path
|
||||
/// into basename + directory as DWARF expects.
|
||||
/// into basename + directory as DWARF expects. The directory MUST be
|
||||
/// non-empty: an empty `DW_AT_comp_dir` makes Apple's `ld` silently drop
|
||||
/// the whole object's debug map (no `N_OSO`), so a binary built from a
|
||||
/// bare filename (e.g. `sx build main.sx`) becomes undebuggable. Fall back
|
||||
/// to "." when the path has no directory component.
|
||||
fn diFileFor(self: *LLVMEmitter, path: []const u8) c.LLVMMetadataRef {
|
||||
if (self.di_files.get(path)) |f| return f;
|
||||
const slash = std.mem.lastIndexOfScalar(u8, path, '/');
|
||||
const dir = if (slash) |s| path[0..s] else "";
|
||||
const dir = if (slash) |s| (if (s == 0) "/" else path[0..s]) else ".";
|
||||
const base = if (slash) |s| path[s + 1 ..] else path;
|
||||
const f = c.LLVMDIBuilderCreateFile(self.di_builder, base.ptr, base.len, dir.ptr, dir.len);
|
||||
self.di_files.put(path, f) catch {};
|
||||
|
||||
Reference in New Issue
Block a user