change one million things

This commit is contained in:
mlugg 2025-09-01 16:50:39 +01:00
parent b706949736
commit b750e7cf9e
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
10 changed files with 2139 additions and 2543 deletions

View File

@ -1083,26 +1083,27 @@ pub const Coff = struct {
age: u32 = undefined,
// The lifetime of `data` must be longer than the lifetime of the returned Coff
pub fn init(data: []const u8, is_loaded: bool) !Coff {
pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff {
const pe_pointer_offset = 0x3C;
const pe_magic = "PE\x00\x00";
var reader: std.Io.Reader = .fixed(data);
reader.seek = pe_pointer_offset;
const coff_header_offset = try reader.takeInt(u32, .little);
reader.seek = coff_header_offset;
const is_image = mem.eql(u8, pe_magic, try reader.takeArray(4));
if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
if (data.len < header_offset + 4) return error.EndOfStream;
const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);
var coff = @This(){
const coff: Coff = .{
.data = data,
.is_image = is_image,
.is_loaded = is_loaded,
.coff_header_offset = coff_header_offset,
.coff_header_offset = o: {
if (is_image) break :o header_offset + 4;
break :o header_offset;
},
};
// Do some basic validation upfront
if (is_image) {
coff.coff_header_offset = coff.coff_header_offset + 4;
const coff_header = coff.getCoffHeader();
if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;
}

View File

@ -153,6 +153,7 @@ pub const SourceLocation = struct {
};
pub const Symbol = struct {
// MLUGG TODO: remove the defaults and audit everywhere. also grep for '???' across std
name: []const u8 = "???",
compile_unit_name: []const u8 = "???",
source_location: ?SourceLocation = null,
@ -232,15 +233,14 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
}
/// TODO multithreaded awareness
var self_debug_info: ?SelfInfo = null;
pub fn getSelfDebugInfo() !*SelfInfo {
if (self_debug_info) |*info| {
return info;
} else {
self_debug_info = try SelfInfo.open(getDebugInfoAllocator());
return &self_debug_info.?;
}
/// Marked `inline` to propagate a comptime-known error to callers.
pub inline fn getSelfDebugInfo() !*SelfInfo {
if (builtin.strip_debug_info) return error.MissingDebugInfo;
if (!SelfInfo.target_supported) return error.UnsupportedOperatingSystem;
const S = struct {
var self_info: SelfInfo = .init;
};
return &S.self_info;
}
/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
@ -446,10 +446,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
defer it.deinit();
// DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext
const pc_addr = if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64)
context.mcontext.ss.pc
else
it.unwind_state.?.dwarf_context.pc;
const pc_addr = it.unwind_state.?.dwarf_context.pc;
printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return;
while (it.next()) |return_address| {
@ -460,7 +457,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
// an overflow. We do not need to signal `StackIterator` as it will correctly detect this
// condition on the subsequent iteration and return `null` thus terminating the loop.
// same behaviour for x86-windows-msvc
const address = if (return_address == 0) return_address else return_address - 1;
const address = return_address -| 1;
printSourceAtAddress(debug_info, stderr, address, tty_config) catch return;
} else printLastUnwindError(&it, debug_info, stderr, tty_config);
}
@ -758,7 +755,7 @@ pub fn writeStackTrace(
frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
}) {
const return_address = stack_trace.instruction_addresses[frame_index];
try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config);
try printSourceAtAddress(debug_info, writer, return_address -| 1, tty_config);
}
if (stack_trace.index > stack_trace.instruction_addresses.len) {
@ -808,16 +805,11 @@ pub const StackIterator = struct {
}
pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator {
// The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
// the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64)
return init(first_address, @truncate(context.mcontext.ss.fp));
if (SelfInfo.supports_unwinding) {
var iterator = init(first_address, fp);
iterator.unwind_state = .{
.debug_info = debug_info,
.dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context),
.dwarf_context = try SelfInfo.UnwindContext.init(getDebugInfoAllocator(), context),
};
return iterator;
}
@ -890,7 +882,7 @@ pub const StackIterator = struct {
if (!unwind_state.failed) {
if (unwind_state.dwarf_context.pc == 0) return null;
defer it.fp = unwind_state.dwarf_context.getFp() catch 0;
if (unwind_state.debug_info.unwindFrame(&unwind_state.dwarf_context)) |return_address| {
if (unwind_state.debug_info.unwindFrame(getDebugInfoAllocator(), &unwind_state.dwarf_context)) |return_address| {
return return_address;
} else |err| {
unwind_state.last_error = err;
@ -1039,19 +1031,6 @@ pub fn writeStackTraceWindows(
}
}
fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
const module_name = debug_info.getModuleNameForAddress(address);
return printLineInfo(
writer,
null,
address,
"???",
module_name orelse "???",
tty_config,
printLineFromFileAnyOs,
);
}
fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {
if (!have_ucontext) return;
if (it.getLastError()) |unwind_error| {
@ -1059,32 +1038,48 @@ fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writ
}
}
fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: tty.Config) !void {
const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: UnwindError, tty_config: tty.Config) !void {
const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
error.Unexpected, error.OutOfMemory => |e| return e,
error.MissingDebugInfo => "???",
};
try tty_config.setColor(writer, .dim);
if (err == error.MissingDebugInfo) {
if (unwind_err == error.MissingDebugInfo) {
try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
} else {
try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err });
try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, unwind_err });
}
try tty_config.setColor(writer, .reset);
}
pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
const symbol_info = debug_info.getSymbolAtAddress(address) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
else => return err,
const gpa = getDebugInfoAllocator();
if (debug_info.getSymbolAtAddress(gpa, address)) |symbol_info| {
defer if (symbol_info.source_location) |sl| gpa.free(sl.file_name);
return printLineInfo(
writer,
symbol_info.source_location,
address,
symbol_info.name,
symbol_info.compile_unit_name,
tty_config,
);
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {},
else => |e| return e,
}
// Unknown source location, but perhaps we can at least get a module name
const compile_unit_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
error.MissingDebugInfo => "???",
error.Unexpected, error.OutOfMemory => |e| return e,
};
defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name);
return printLineInfo(
writer,
symbol_info.source_location,
null,
address,
symbol_info.name,
symbol_info.compile_unit_name,
"???",
compile_unit_name,
tty_config,
printLineFromFileAnyOs,
);
}
@ -1095,7 +1090,6 @@ fn printLineInfo(
symbol_name: []const u8,
compile_unit_name: []const u8,
tty_config: tty.Config,
comptime printLineFromFile: anytype,
) !void {
nosuspend {
try tty_config.setColor(writer, .bold);
@ -1136,7 +1130,7 @@ fn printLineInfo(
}
}
fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !void {
fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
// Need this to always block even in async I/O mode, because this could potentially
// be called from e.g. the event loop code crashing.
var f = try fs.cwd().openFile(source_location.file_name, .{});
@ -1190,7 +1184,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi
}
}
test printLineFromFileAnyOs {
test printLineFromFile {
var aw: Writer.Allocating = .init(std.testing.allocator);
defer aw.deinit();
const output_stream = &aw.writer;
@ -1212,9 +1206,9 @@ test printLineFromFileAnyOs {
defer allocator.free(path);
try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
aw.clearRetainingCapacity();
}
@ -1230,11 +1224,11 @@ test printLineFromFileAnyOs {
,
});
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings("1\n", aw.written());
aw.clearRetainingCapacity();
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
try expectEqualStrings("3\n", aw.written());
aw.clearRetainingCapacity();
}
@ -1253,7 +1247,7 @@ test printLineFromFileAnyOs {
try writer.splatByteAll('a', overlap);
try writer.flush();
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
aw.clearRetainingCapacity();
}
@ -1267,7 +1261,7 @@ test printLineFromFileAnyOs {
const writer = &file_writer.interface;
try writer.splatByteAll('a', std.heap.page_size_max);
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
aw.clearRetainingCapacity();
}
@ -1281,19 +1275,19 @@ test printLineFromFileAnyOs {
const writer = &file_writer.interface;
try writer.splatByteAll('a', 3 * std.heap.page_size_max);
try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
aw.clearRetainingCapacity();
try writer.writeAll("a\na");
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
aw.clearRetainingCapacity();
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
try expectEqualStrings("a\n", aw.written());
aw.clearRetainingCapacity();
}
@ -1309,26 +1303,23 @@ test printLineFromFileAnyOs {
try writer.splatByteAll('\n', real_file_start);
try writer.writeAll("abc\ndef");
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
try expectEqualStrings("abc\n", aw.written());
aw.clearRetainingCapacity();
try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
try expectEqualStrings("def\n", aw.written());
aw.clearRetainingCapacity();
}
}
/// TODO multithreaded awareness
var debug_info_allocator: ?mem.Allocator = null;
var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
var debug_info_arena: ?std.heap.ArenaAllocator = null;
fn getDebugInfoAllocator() mem.Allocator {
if (debug_info_allocator) |a| return a;
debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const allocator = debug_info_arena_allocator.allocator();
debug_info_allocator = allocator;
return allocator;
if (debug_info_arena == null) {
debug_info_arena = .init(std.heap.page_allocator);
}
return debug_info_arena.?.allocator();
}
/// Whether or not the current target can print useful debug information when a segfault occurs.

View File

@ -78,17 +78,6 @@ pub const Section = struct {
debug_addr,
debug_names,
};
// 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 const Abbrev = struct {
@ -342,10 +331,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 {
return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
}
pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: usize) ?i64 {
return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null;
}
pub fn deinit(di: *Dwarf, gpa: Allocator) void {
for (di.sections) |opt_section| {
if (opt_section) |s| if (s.owned) gpa.free(s.data);
@ -364,8 +349,6 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {
}
di.compile_unit_list.deinit(gpa);
di.func_list.deinit(gpa);
di.cie_map.deinit(gpa);
di.fde_list.deinit(gpa);
di.ranges.deinit(gpa);
di.* = undefined;
}
@ -983,8 +966,8 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit:
},
0,
};
_ = addr_size;
_ = seg_size;
if (seg_size != 0) return bad(); // unsupported
_ = addr_size; // TODO: ignoring this is incorrect, we should use it to decide address lengths
const prologue_length = try readAddress(&fr, unit_header.format, endian);
const prog_start_offset = fr.seek + prologue_length;
@ -1472,44 +1455,27 @@ pub const ElfModule = struct {
mapped_memory: ?[]align(std.heap.page_size_min) const u8,
external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
pub const Lookup = struct {
base_address: usize,
name: []const u8,
build_id: ?[]const u8,
gnu_eh_frame: ?[]const u8,
pub const init: ElfModule = .{
.unwind = .{
.debug_frame = null,
.eh_frame = null,
},
.dwarf = .{},
.mapped_memory = null,
.external_mapped_memory = null,
};
pub fn init(lookup: *const Lookup) ElfModule {
var em: ElfModule = .{
.unwind = .{
.sections = @splat(null),
},
.dwarf = .{},
.mapped_memory = null,
.external_mapped_memory = null,
};
if (lookup.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.
em.unwind.sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{
.data = eh_frame_hdr,
};
}
return em;
}
pub fn deinit(self: *@This(), allocator: Allocator) void {
self.dwarf.deinit(allocator);
std.posix.munmap(self.mapped_memory);
if (self.external_mapped_memory) |m| std.posix.munmap(m);
}
pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, base_address: usize, address: usize) !std.debug.Symbol {
// Translate the VA into an address into this object
const relocated_address = address - base_address;
return self.dwarf.getSymbol(allocator, endian, relocated_address);
pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, load_offset: usize, address: usize) !std.debug.Symbol {
// Translate the runtime address into a virtual address into the module
// MLUGG TODO: this clearly tells us that the logic should live near SelfInfo...
const vaddr = address - load_offset;
return self.dwarf.getSymbol(allocator, endian, vaddr);
}
pub fn getDwarfUnwindForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf.Unwind {
@ -1548,7 +1514,7 @@ pub const ElfModule = struct {
mapped_mem: []align(std.heap.page_size_min) const u8,
build_id: ?[]const u8,
expected_crc: ?u32,
parent_sections: *Dwarf.SectionArray,
parent_sections: ?*Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
elf_filename: ?[]const u8,
) LoadError!void {
@ -1577,10 +1543,12 @@ pub const ElfModule = struct {
var sections: Dwarf.SectionArray = @splat(null);
// Combine section list. This takes ownership over any owned sections from the parent scope.
for (parent_sections, &sections) |*parent, *section_elem| {
if (parent.*) |*p| {
section_elem.* = p.*;
p.owned = false;
if (parent_sections) |ps| {
for (ps, &sections) |*parent, *section_elem| {
if (parent.*) |*p| {
section_elem.* = p.*;
p.owned = false;
}
}
}
errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
@ -1647,7 +1615,6 @@ pub const ElfModule = struct {
// Attempt to load debug info from an external file
// See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
if (missing_debug_info) {
// Only allow one level of debug info nesting
if (parent_mapped_mem) |_| {
return error.MissingDebugInfo;
@ -1775,6 +1742,7 @@ pub const ElfModule = struct {
em.mapped_memory = parent_mapped_mem orelse mapped_mem;
em.external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null;
em.dwarf.sections = sections;
try em.dwarf.open(gpa, endian);
}
@ -1844,7 +1812,8 @@ pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]cons
return ptr[start..end];
}
pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
// MLUGG TODO FIX BEFORE MERGE: this function is slightly bogus. addresses have a byte width which is independent of the `dwarf.Format`!
return switch (format) {
.@"32" => try r.takeInt(u32, endian),
.@"64" => try r.takeInt(u64, endian),
@ -1852,6 +1821,8 @@ pub fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
}
fn nativeFormat() std.dwarf.Format {
// MLUGG TODO FIX BEFORE MERGE: this is nonsensical. this is neither what `dwarf.Format` is for, nor does it make sense to check the NATIVE FUCKING FORMAT
// when parsing ARBITRARY DWARF.
return switch (@sizeOf(usize)) {
4 => .@"32",
8 => .@"64",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,298 @@
//! Virtual machine that evaluates DWARF call frame instructions
/// See section 6.4.1 of the DWARF5 specification for details on each
pub const RegisterRule = union(enum) {
/// The spec says that the default rule for each column is the undefined rule.
/// However, it also allows ABI / compiler authors to specify alternate defaults, so
/// there is a distinction made here.
default: void,
undefined: void,
same_value: void,
/// offset(N)
offset: i64,
/// val_offset(N)
val_offset: i64,
/// register(R)
register: u8,
/// expression(E)
expression: []const u8,
/// val_expression(E)
val_expression: []const u8,
/// Augmenter-defined rule
architectural: void,
};
/// Each row contains unwinding rules for a set of registers.
pub const Row = struct {
/// Offset from `FrameDescriptionEntry.pc_begin`
offset: u64 = 0,
/// Special-case column that defines the CFA (Canonical Frame Address) rule.
/// The register field of this column defines the register that CFA is derived from.
cfa: Column = .{},
/// The register fields in these columns define the register the rule applies to.
columns: ColumnRange = .{},
/// Indicates that the next write to any column in this row needs to copy
/// the backing column storage first, as it may be referenced by previous rows.
copy_on_write: bool = false,
};
pub const Column = struct {
register: ?u8 = null,
rule: RegisterRule = .{ .default = {} },
};
const ColumnRange = struct {
/// Index into `columns` of the first column in this row.
start: usize = undefined,
len: u8 = 0,
};
columns: std.ArrayList(Column) = .empty,
stack: std.ArrayList(ColumnRange) = .empty,
current_row: Row = .{},
/// The result of executing the CIE's initial_instructions
cie_row: ?Row = null,
pub fn deinit(self: *VirtualMachine, gpa: Allocator) void {
self.stack.deinit(gpa);
self.columns.deinit(gpa);
self.* = undefined;
}
pub fn reset(self: *VirtualMachine) void {
self.stack.clearRetainingCapacity();
self.columns.clearRetainingCapacity();
self.current_row = .{};
self.cie_row = null;
}
/// Return a slice backed by the row's non-CFA columns
pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
if (row.columns.len == 0) return &.{};
return self.columns.items[row.columns.start..][0..row.columns.len];
}
/// Either retrieves or adds a column for `register` (non-CFA) in the current row.
fn getOrAddColumn(self: *VirtualMachine, gpa: Allocator, register: u8) !*Column {
for (self.rowColumns(self.current_row)) |*c| {
if (c.register == register) return c;
}
if (self.current_row.columns.len == 0) {
self.current_row.columns.start = self.columns.items.len;
}
self.current_row.columns.len += 1;
const column = try self.columns.addOne(gpa);
column.* = .{
.register = register,
};
return column;
}
/// Runs the CIE instructions, then the FDE instructions. Execution halts
/// once the row that corresponds to `pc` is known, and the row is returned.
pub fn runTo(
self: *VirtualMachine,
gpa: Allocator,
pc: u64,
cie: Dwarf.Unwind.CommonInformationEntry,
fde: Dwarf.Unwind.FrameDescriptionEntry,
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !Row {
assert(self.cie_row == null);
assert(pc >= fde.pc_begin);
assert(pc < fde.pc_begin + fde.pc_range);
var prev_row: Row = self.current_row;
const instruction_slices: [2][]const u8 = .{
cie.initial_instructions,
fde.instructions,
};
for (instruction_slices, [2]bool{ true, false }) |slice, is_cie_stream| {
var stream: std.Io.Reader = .fixed(slice);
while (stream.seek < slice.len) {
const instruction: Dwarf.call_frame.Instruction = try .read(&stream, addr_size_bytes, endian);
prev_row = try self.step(gpa, cie, is_cie_stream, instruction);
if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
}
}
return self.current_row;
}
fn resolveCopyOnWrite(self: *VirtualMachine, gpa: Allocator) !void {
if (!self.current_row.copy_on_write) return;
const new_start = self.columns.items.len;
if (self.current_row.columns.len > 0) {
try self.columns.ensureUnusedCapacity(gpa, self.current_row.columns.len);
self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
self.current_row.columns.start = new_start;
}
}
/// Executes a single instruction.
/// If this instruction is from the CIE, `is_initial` should be set.
/// Returns the value of `current_row` before executing this instruction.
pub fn step(
self: *VirtualMachine,
gpa: Allocator,
cie: Dwarf.Unwind.CommonInformationEntry,
is_initial: bool,
instruction: Dwarf.call_frame.Instruction,
) !Row {
// CIE instructions must be run before FDE instructions
assert(!is_initial or self.cie_row == null);
if (!is_initial and self.cie_row == null) {
self.cie_row = self.current_row;
self.current_row.copy_on_write = true;
}
const prev_row = self.current_row;
switch (instruction) {
.set_loc => |i| {
if (i.address <= self.current_row.offset) return error.InvalidOperation;
if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported
// TODO: Check cie.segment_selector_size != 0 for DWARFV4
self.current_row.offset = i.address;
},
inline .advance_loc,
.advance_loc1,
.advance_loc2,
.advance_loc4,
=> |i| {
self.current_row.offset += i.delta * cie.code_alignment_factor;
self.current_row.copy_on_write = true;
},
inline .offset,
.offset_extended,
.offset_extended_sf,
=> |i| {
try self.resolveCopyOnWrite(gpa);
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
},
inline .restore,
.restore_extended,
=> |i| {
try self.resolveCopyOnWrite(gpa);
if (self.cie_row) |cie_row| {
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = for (self.rowColumns(cie_row)) |cie_column| {
if (cie_column.register == i.register) break cie_column.rule;
} else .{ .default = {} };
} else return error.InvalidOperation;
},
.nop => {},
.undefined => |i| {
try self.resolveCopyOnWrite(gpa);
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = .{ .undefined = {} };
},
.same_value => |i| {
try self.resolveCopyOnWrite(gpa);
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = .{ .same_value = {} };
},
.register => |i| {
try self.resolveCopyOnWrite(gpa);
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = .{ .register = i.target_register };
},
.remember_state => {
try self.stack.append(gpa, self.current_row.columns);
self.current_row.copy_on_write = true;
},
.restore_state => {
const restored_columns = self.stack.pop() orelse return error.InvalidOperation;
self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
try self.columns.ensureUnusedCapacity(gpa, restored_columns.len);
self.current_row.columns.start = self.columns.items.len;
self.current_row.columns.len = restored_columns.len;
self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
},
.def_cfa => |i| {
try self.resolveCopyOnWrite(gpa);
self.current_row.cfa = .{
.register = i.register,
.rule = .{ .val_offset = @intCast(i.offset) },
};
},
.def_cfa_sf => |i| {
try self.resolveCopyOnWrite(gpa);
self.current_row.cfa = .{
.register = i.register,
.rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
};
},
.def_cfa_register => |i| {
try self.resolveCopyOnWrite(gpa);
if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
self.current_row.cfa.register = i.register;
},
.def_cfa_offset => |i| {
try self.resolveCopyOnWrite(gpa);
if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
self.current_row.cfa.rule = .{
.val_offset = @intCast(i.offset),
};
},
.def_cfa_offset_sf => |i| {
try self.resolveCopyOnWrite(gpa);
if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
self.current_row.cfa.rule = .{
.val_offset = i.offset * cie.data_alignment_factor,
};
},
.def_cfa_expression => |i| {
try self.resolveCopyOnWrite(gpa);
self.current_row.cfa.register = undefined;
self.current_row.cfa.rule = .{
.expression = i.block,
};
},
.expression => |i| {
try self.resolveCopyOnWrite(gpa);
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = .{
.expression = i.block,
};
},
.val_offset => |i| {
try self.resolveCopyOnWrite(gpa);
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = .{
.val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
};
},
.val_offset_sf => |i| {
try self.resolveCopyOnWrite(gpa);
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = .{
.val_offset = i.offset * cie.data_alignment_factor,
};
},
.val_expression => |i| {
try self.resolveCopyOnWrite(gpa);
const column = try self.getOrAddColumn(gpa, i.register);
column.rule = .{
.val_expression = i.block,
};
},
}
return prev_row;
}
const std = @import("../../../std.zig");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Dwarf = std.debug.Dwarf;
const VirtualMachine = @This();

View File

@ -1,12 +1,5 @@
const builtin = @import("builtin");
const std = @import("../../std.zig");
const mem = std.mem;
const debug = std.debug;
const leb = std.leb;
const DW = std.dwarf;
const abi = std.debug.Dwarf.abi;
const assert = std.debug.assert;
const native_endian = builtin.cpu.arch.endian();
const Reader = std.Io.Reader;
/// TODO merge with std.dwarf.CFA
const Opcode = enum(u8) {
@ -51,9 +44,13 @@ const Opcode = enum(u8) {
pub const hi_user = 0x3f;
};
fn readBlock(reader: *std.Io.Reader) ![]const u8 {
/// The returned slice points into `reader.buffer`.
fn readBlock(reader: *Reader) ![]const u8 {
const block_len = try reader.takeLeb128(usize);
return reader.take(block_len);
return reader.take(block_len) catch |err| switch (err) {
error.EndOfStream => return error.InvalidOperand,
error.ReadFailed => |e| return e,
};
}
pub const Instruction = union(Opcode) {
@ -140,8 +137,9 @@ pub const Instruction = union(Opcode) {
block: []const u8,
},
/// `reader` must be a `Reader.fixed` so that regions of its buffer are never invalidated.
pub fn read(
reader: *std.Io.Reader,
reader: *Reader,
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !Instruction {
@ -173,16 +171,14 @@ pub const Instruction = union(Opcode) {
.restore,
=> unreachable,
.nop => .{ .nop = {} },
.set_loc => .{
.set_loc = .{
.address = switch (addr_size_bytes) {
2 => try reader.takeInt(u16, endian),
4 => try reader.takeInt(u32, endian),
8 => try reader.takeInt(u64, endian),
else => return error.InvalidAddrSize,
},
.set_loc => .{ .set_loc = .{
.address = switch (addr_size_bytes) {
2 => try reader.takeInt(u16, endian),
4 => try reader.takeInt(u32, endian),
8 => try reader.takeInt(u64, endian),
else => return error.UnsupportedAddrSize,
},
},
} },
.advance_loc1 => .{
.advance_loc1 = .{ .delta = try reader.takeByte() },
},

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,32 @@
pub const PE = struct {
pub const absptr = 0x00;
pub const PE = packed struct(u8) {
type: Type,
rel: Rel,
pub const size_mask = 0x7;
pub const sign_mask = 0x8;
pub const type_mask = size_mask | sign_mask;
/// This is a special encoding which does not correspond to named `type`/`rel` values.
pub const omit: PE = @bitCast(@as(u8, 0xFF));
pub const uleb128 = 0x01;
pub const udata2 = 0x02;
pub const udata4 = 0x03;
pub const udata8 = 0x04;
pub const sleb128 = 0x09;
pub const sdata2 = 0x0A;
pub const sdata4 = 0x0B;
pub const sdata8 = 0x0C;
pub const Type = enum(u4) {
absptr = 0x0,
uleb128 = 0x1,
udata2 = 0x2,
udata4 = 0x3,
udata8 = 0x4,
sleb128 = 0x9,
sdata2 = 0xA,
sdata4 = 0xB,
sdata8 = 0xC,
_,
};
pub const rel_mask = 0x70;
pub const pcrel = 0x10;
pub const textrel = 0x20;
pub const datarel = 0x30;
pub const funcrel = 0x40;
pub const aligned = 0x50;
pub const indirect = 0x80;
pub const omit = 0xff;
pub const Rel = enum(u4) {
abs = 0x0,
pcrel = 0x1,
textrel = 0x2,
datarel = 0x3,
funcrel = 0x4,
aligned = 0x5,
/// Undocumented GCC extension
indirect = 0x8,
_,
};
};

View File

@ -839,62 +839,112 @@ pub const nlist = extern struct {
pub const nlist_64 = extern struct {
n_strx: u32,
n_type: u8,
n_type: packed union {
bits: packed struct(u8) {
ext: bool,
type: enum(u3) {
undf = 0,
abs = 1,
sect = 7,
pbud = 6,
indr = 5,
_,
},
pext: bool,
/// Any non-zero value indicates this is an stab, so the `stab` field should be used.
is_stab: u3,
},
stab: enum(u8) {
gsym = N_GSYM,
fname = N_FNAME,
fun = N_FUN,
stsym = N_STSYM,
lcsym = N_LCSYM,
bnsym = N_BNSYM,
ast = N_AST,
opt = N_OPT,
rsym = N_RSYM,
sline = N_SLINE,
ensym = N_ENSYM,
ssym = N_SSYM,
so = N_SO,
oso = N_OSO,
lsym = N_LSYM,
bincl = N_BINCL,
sol = N_SOL,
params = N_PARAMS,
version = N_VERSION,
olevel = N_OLEVEL,
psym = N_PSYM,
eincl = N_EINCL,
entry = N_ENTRY,
lbrac = N_LBRAC,
excl = N_EXCL,
rbrac = N_RBRAC,
bcomm = N_BCOMM,
ecomm = N_ECOMM,
ecoml = N_ECOML,
leng = N_LENG,
_,
},
},
n_sect: u8,
n_desc: u16,
n_desc: packed struct(u16) {
_pad0: u3 = 0,
arm_thumb_def: bool,
_pad1: u1 = 0,
/// The meaning of this bit is contextual.
/// See `N_DESC_DISCARDED` and `N_NO_DEAD_STRIP`.
discarded_or_no_dead_strip: bool,
weak_ref: bool,
/// The meaning of this bit is contextual.
/// See `N_WEAK_DEF` and `N_REF_TO_WEAK`.
weak_def_or_ref_to_weak: bool,
symbol_resolver: bool,
alt_entry: bool,
_pad2: u6 = 0,
},
n_value: u64,
// MLUGG TODO DELETE
pub fn stab(sym: nlist_64) bool {
return N_STAB & sym.n_type != 0;
return sym.n_type.bits.is_stab != 0;
}
pub fn pext(sym: nlist_64) bool {
return N_PEXT & sym.n_type != 0;
}
pub fn ext(sym: nlist_64) bool {
return N_EXT & sym.n_type != 0;
}
// MLUGG TODO DELETE
pub fn sect(sym: nlist_64) bool {
const type_ = N_TYPE & sym.n_type;
return type_ == N_SECT;
return sym.n_type.type == .sect;
}
// MLUGG TODO DELETE
pub fn undf(sym: nlist_64) bool {
const type_ = N_TYPE & sym.n_type;
return type_ == N_UNDF;
return sym.n_type.type == .undf;
}
// MLUGG TODO DELETE
pub fn indr(sym: nlist_64) bool {
const type_ = N_TYPE & sym.n_type;
return type_ == N_INDR;
return sym.n_type.type == .indr;
}
// MLUGG TODO DELETE
pub fn abs(sym: nlist_64) bool {
const type_ = N_TYPE & sym.n_type;
return type_ == N_ABS;
return sym.n_type.type == .abs;
}
// MLUGG TODO DELETE
pub fn weakDef(sym: nlist_64) bool {
return sym.n_desc & N_WEAK_DEF != 0;
return sym.n_desc.weak_def_or_ref_to_weak;
}
// MLUGG TODO DELETE
pub fn weakRef(sym: nlist_64) bool {
return sym.n_desc & N_WEAK_REF != 0;
return sym.n_desc.weak_ref;
}
// MLUGG TODO DELETE
pub fn discarded(sym: nlist_64) bool {
return sym.n_desc & N_DESC_DISCARDED != 0;
return sym.n_desc.discarded_or_no_dead_strip;
}
// MLUGG TODO DELETE
pub fn noDeadStrip(sym: nlist_64) bool {
return sym.n_desc & N_NO_DEAD_STRIP != 0;
return sym.n_desc.discarded_or_no_dead_strip;
}
pub fn tentative(sym: nlist_64) bool {
if (!sym.undf()) return false;
return sym.n_value != 0;
return sym.n_type.type == .undf and sym.n_value != 0;
}
};
@ -2046,7 +2096,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct {
// encodings array
};
pub const UnwindInfoCompressedEntry = packed struct {
pub const UnwindInfoCompressedEntry = packed struct(u32) {
funcOffset: u24,
encodingIndex: u8,
};

View File

@ -455,72 +455,23 @@ pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.El
}
pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
const comp = elf_file.base.comp;
const gpa = comp.gpa;
try writer.writeByte(1); // version
try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4);
try writer.writeByte(DW_EH_PE.udata4);
try writer.writeByte(DW_EH_PE.datarel | DW_EH_PE.sdata4);
try writer.writeByte(DW_EH_PE.pcrel | DW_EH_PE.sdata4); // eh_frame_ptr_enc
// Building the lookup table would be expensive work on every `flush` -- omit it.
try writer.writeByte(DW_EH_PE.omit); // fde_count_enc
try writer.writeByte(DW_EH_PE.omit); // table_enc
const shdrs = elf_file.sections.items(.shdr);
const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?];
const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?];
const num_fdes = @as(u32, @intCast(@divExact(eh_frame_hdr_shdr.sh_size - eh_frame_hdr_header_size, 8)));
const existing_size = existing_size: {
const zo = elf_file.zigObjectPtr() orelse break :existing_size 0;
const sym = zo.symbol(zo.eh_frame_index orelse break :existing_size 0);
break :existing_size sym.atom(elf_file).?.size;
};
try writer.writeInt(
u32,
@as(u32, @bitCast(@as(
i32,
@truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr + existing_size)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4),
@truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4),
))),
.little,
);
try writer.writeInt(u32, num_fdes, .little);
const Entry = extern struct {
init_addr: u32,
fde_addr: u32,
pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool {
_ = ctx;
return lhs.init_addr < rhs.init_addr;
}
};
var entries = std.array_list.Managed(Entry).init(gpa);
defer entries.deinit();
try entries.ensureTotalCapacityPrecise(num_fdes);
for (elf_file.objects.items) |index| {
const object = elf_file.file(index).?.object;
for (object.fdes.items) |fde| {
if (!fde.alive) continue;
const relocs = fde.relocs(object);
assert(relocs.len > 0); // Should this be an error? Things are completely broken anyhow if this trips...
const rel = relocs[0];
const ref = object.resolveSymbol(rel.r_sym(), elf_file);
const sym = elf_file.symbol(ref).?;
const P = @as(i64, @intCast(fde.address(elf_file)));
const S = @as(i64, @intCast(sym.address(.{}, elf_file)));
const A = rel.r_addend;
entries.appendAssumeCapacity(.{
.init_addr = @bitCast(@as(i32, @truncate(S + A - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
.fde_addr = @as(
u32,
@bitCast(@as(i32, @truncate(P - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
),
});
}
}
std.mem.sort(Entry, entries.items, {}, Entry.lessThan);
try writer.writeSliceEndian(Entry, entries.items, .little);
}
const eh_frame_hdr_header_size: usize = 12;