dwarf: don't dupe function names, as they are backed by the memory mapped sections

dwarf: const-correctness fixups
dwarf: implement the remaining register rules
dwarf: start implmenting the DWARF expression stack machine
This commit is contained in:
kcbanner 2023-07-03 03:45:06 -04:00
parent 62598c2187
commit b85f84061a
4 changed files with 291 additions and 87 deletions

View File

@ -483,16 +483,14 @@ pub const StackIterator = struct {
pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const os.ucontext_t) !StackIterator {
var iterator = init(first_address, null);
iterator.debug_info = debug_info;
iterator.dwarf_context = try DW.UnwindContext.init(context, &isValidMemory);
iterator.dwarf_context = try DW.UnwindContext.init(debug_info.allocator, context, &isValidMemory);
iterator.last_error = null;
return iterator;
}
pub fn deinit(self: *StackIterator) void {
if (have_ucontext) {
if (self.debug_info) |debug_info| {
self.dwarf_context.deinit(debug_info.allocator);
}
if (have_ucontext and self.debug_info != null) {
self.dwarf_context.deinit();
}
}
@ -599,7 +597,7 @@ pub const StackIterator = struct {
if (try module.getDwarfInfoForAddress(self.debug_info.?.allocator, self.dwarf_context.pc)) |di| {
self.dwarf_context.reg_ctx.eh_frame = true;
self.dwarf_context.reg_ctx.is_macho = di.is_macho;
return di.unwindFrame(self.debug_info.?.allocator, &self.dwarf_context, module.base_address);
return di.unwindFrame(&self.dwarf_context, module.base_address);
} else return error.MissingDebugInfo;
}

View File

@ -163,15 +163,9 @@ const PcRange = struct {
const Func = struct {
pc_range: ?PcRange,
name: ?[]const u8,
fn deinit(func: *Func, allocator: mem.Allocator) void {
if (func.name) |name| {
allocator.free(name);
}
}
};
const CompileUnit = struct {
pub const CompileUnit = struct {
version: u16,
is_64: bool,
die: *Die,
@ -181,6 +175,7 @@ const CompileUnit = struct {
addr_base: usize,
rnglists_base: usize,
loclists_base: usize,
frame_base: ?*const FormValue,
};
const AbbrevTable = std.ArrayList(AbbrevTableEntry);
@ -216,7 +211,7 @@ const AbbrevAttr = struct {
payload: i64,
};
const FormValue = union(enum) {
pub const FormValue = union(enum) {
Address: u64,
AddrOffset: usize,
Block: []u8,
@ -298,7 +293,7 @@ const Die = struct {
fn getAttrAddr(
self: *const Die,
di: *DwarfInfo,
di: *const DwarfInfo,
id: u64,
compile_unit: CompileUnit,
) error{ InvalidDebugInfo, MissingDebugInfo }!u64 {
@ -708,9 +703,6 @@ pub const DwarfInfo = struct {
allocator.destroy(cu.die);
}
di.compile_unit_list.deinit(allocator);
for (di.func_list.items) |*func| {
func.deinit(allocator);
}
di.func_list.deinit(allocator);
di.cie_map.deinit(allocator);
di.fde_list.deinit(allocator);
@ -793,6 +785,7 @@ pub const DwarfInfo = struct {
.addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0,
.rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0,
.loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0,
.frame_base = die_obj.getAttr(AT.frame_base),
};
},
TAG.subprogram, TAG.inlined_subroutine, TAG.subroutine, TAG.entry_point => {
@ -802,8 +795,7 @@ pub const DwarfInfo = struct {
// Prevent endless loops
while (depth > 0) : (depth -= 1) {
if (this_die_obj.getAttr(AT.name)) |_| {
const name = try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit);
break :x try allocator.dupe(u8, name);
break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit);
} else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
// Follow the DIE it points to and repeat
const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin);
@ -834,7 +826,7 @@ pub const DwarfInfo = struct {
break :x null;
};
var found_range = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: {
var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: {
if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
const pc_end = switch (high_pc_value.*) {
FormValue.Address => |value| value,
@ -852,9 +844,11 @@ pub const DwarfInfo = struct {
.end = pc_end,
},
});
break :blk true;
}
break :blk true;
break :blk false;
} else |err| blk: {
if (err != error.MissingDebugInfo) return err;
break :blk false;
@ -867,7 +861,7 @@ pub const DwarfInfo = struct {
};
while (try iter.next()) |range| {
found_range = true;
range_added = true;
try di.func_list.append(allocator, Func{
.name = fn_name,
.pc_range = .{
@ -878,7 +872,7 @@ pub const DwarfInfo = struct {
}
}
if (!found_range) {
if (fn_name != null and !range_added) {
try di.func_list.append(allocator, Func{
.name = fn_name,
.pc_range = null,
@ -952,6 +946,7 @@ pub const DwarfInfo = struct {
.addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0,
.rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0,
.loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0,
.frame_base = compile_unit_die.getAttr(AT.frame_base),
};
compile_unit.pc_range = x: {
@ -987,11 +982,11 @@ pub const DwarfInfo = struct {
const DebugRangeIterator = struct {
base_address: u64,
section_type: DwarfSection,
di: *DwarfInfo,
di: *const DwarfInfo,
compile_unit: *const CompileUnit,
stream: io.FixedBufferStream([]const u8),
pub fn init(ranges_value: *const FormValue, di: *DwarfInfo, compile_unit: *const CompileUnit) !@This() {
pub fn init(ranges_value: *const FormValue, di: *const DwarfInfo, compile_unit: *const CompileUnit) !@This() {
const section_type = if (compile_unit.version >= 5) DwarfSection.debug_rnglists else DwarfSection.debug_ranges;
const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo;
@ -1129,7 +1124,7 @@ pub const DwarfInfo = struct {
}
};
pub fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit {
pub fn findCompileUnit(di: *const DwarfInfo, target_address: u64) !*const CompileUnit {
for (di.compile_unit_list.items) |*compile_unit| {
if (compile_unit.pc_range) |range| {
if (target_address >= range.start and target_address < range.end) return compile_unit;
@ -1630,7 +1625,7 @@ pub const DwarfInfo = struct {
}
}
pub fn unwindFrame(di: *const DwarfInfo, allocator: mem.Allocator, context: *UnwindContext, module_base_address: usize) !usize {
pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, module_base_address: usize) !usize {
if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture;
if (context.pc == 0) return 0;
@ -1678,10 +1673,11 @@ pub const DwarfInfo = struct {
cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
}
const compile_unit: ?*const CompileUnit = di.findCompileUnit(fde.pc_begin) catch null;
context.vm.reset();
context.reg_ctx.eh_frame = cie.version != 4;
_ = try context.vm.runToNative(allocator, mapped_pc, cie, fde);
_ = try context.vm.runToNative(context.allocator, mapped_pc, cie, fde);
const row = &context.vm.current_row;
context.cfa = switch (row.cfa.rule) {
@ -1690,12 +1686,19 @@ pub const DwarfInfo = struct {
const value = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, register, context.reg_ctx));
break :blk try call_frame.applyOffset(value, offset);
},
.expression => |expression| {
.expression => |expression| blk: {
context.stack_machine.reset();
const value = try context.stack_machine.run(
expression,
context.allocator,
compile_unit,
&context.ucontext,
context.reg_ctx,
context.cfa orelse 0,
);
// TODO: Evaluate expression
_ = expression;
return error.UnimplementedTODO;
if (value != .generic) return error.InvalidExpressionValue;
break :blk value.generic;
},
else => return error.InvalidCFARule,
};
@ -1713,7 +1716,13 @@ pub const DwarfInfo = struct {
has_next_ip = column.rule != .undefined;
}
try column.resolveValue(context.*, dest);
try column.resolveValue(
context,
compile_unit,
&context.ucontext,
context.reg_ctx,
dest,
);
}
}
@ -1738,16 +1747,19 @@ pub const DwarfInfo = struct {
};
pub const UnwindContext = struct {
allocator: mem.Allocator,
cfa: ?usize,
pc: usize,
ucontext: os.ucontext_t,
reg_ctx: abi.RegisterContext,
isValidMemory: *const fn (address: usize) bool,
vm: call_frame.VirtualMachine = .{},
stack_machine: expressions.StackMachine(.{ .call_frame_mode = true }) = .{},
pub fn init(ucontext: *const os.ucontext_t, isValidMemory: *const fn (address: usize) bool) !UnwindContext {
pub fn init(allocator: mem.Allocator, ucontext: *const os.ucontext_t, isValidMemory: *const fn (address: usize) bool) !UnwindContext {
const pc = mem.readIntSliceNative(usize, try abi.regBytes(ucontext, abi.ipRegNum(), null));
return .{
.allocator = allocator,
.cfa = null,
.pc = pc,
.ucontext = ucontext.*,
@ -1756,8 +1768,9 @@ pub const UnwindContext = struct {
};
}
pub fn deinit(self: *UnwindContext, allocator: mem.Allocator) void {
self.vm.deinit(allocator);
pub fn deinit(self: *UnwindContext) void {
self.vm.deinit(self.allocator);
self.stack_machine.deinit(self.allocator);
}
pub fn getFp(self: *const UnwindContext) !usize {

View File

@ -183,9 +183,9 @@ pub const Instruction = union(Opcode) {
offset_extended_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
def_cfa_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
def_cfa_offset_sf: InstructionType(.{ .offset = .sleb128_offset }),
val_offset: InstructionType(.{ .a = .uleb128_offset, .b = .uleb128_offset }),
val_offset_sf: InstructionType(.{ .a = .uleb128_offset, .b = .sleb128_offset }),
val_expression: InstructionType(.{ .a = .uleb128_offset, .block = .block }),
val_offset: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }),
val_offset_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
val_expression: InstructionType(.{ .register = .uleb128_register, .block = .block }),
fn readOperands(
self: *Instruction,
@ -292,7 +292,14 @@ pub const VirtualMachine = struct {
rule: RegisterRule = .{ .default = {} },
/// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes)
pub fn resolveValue(self: Column, context: dwarf.UnwindContext, out: []u8) !void {
pub fn resolveValue(
self: Column,
context: *dwarf.UnwindContext,
compile_unit: ?*const dwarf.CompileUnit,
ucontext: *const std.os.ucontext_t,
reg_ctx: abi.RegisterContext,
out: []u8,
) !void {
switch (self.rule) {
.default => {
const register = self.register orelse return error.InvalidRegister;
@ -321,14 +328,21 @@ pub const VirtualMachine = struct {
@memcpy(out, try abi.regBytes(&context.ucontext, register, context.reg_ctx));
},
.expression => |expression| {
// TODO
_ = expression;
unreachable;
context.stack_machine.reset();
const value = try context.stack_machine.run(expression, context.allocator, compile_unit, ucontext, reg_ctx, context.cfa.?);
if (value != .generic) return error.InvalidExpressionValue;
if (!context.isValidMemory(value.generic)) return error.InvalidExpressionAddress;
const ptr: *usize = @ptrFromInt(value.generic);
mem.writeIntSliceNative(usize, out, ptr.*);
},
.val_expression => |expression| {
// TODO
_ = expression;
unreachable;
context.stack_machine.reset();
const value = try context.stack_machine.run(expression, context.allocator, compile_unit, ucontext, reg_ctx, context.cfa.?);
if (value != .generic) return error.InvalidExpressionValue;
mem.writeIntSliceNative(usize, out, value.generic);
},
.architectural => return error.UnimplementedRule,
}
@ -546,12 +560,16 @@ pub const VirtualMachine = struct {
.def_cfa_offset => |i| {
try self.resolveCopyOnWrite(allocator);
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.operands.offset) };
self.current_row.cfa.rule = .{
.val_offset = @intCast(i.operands.offset),
};
},
.def_cfa_offset_sf => |i| {
try self.resolveCopyOnWrite(allocator);
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.operands.offset * cie.data_alignment_factor };
self.current_row.cfa.rule = .{
.val_offset = i.operands.offset * cie.data_alignment_factor,
};
},
.def_cfa_expression => |i| {
try self.resolveCopyOnWrite(allocator);
@ -567,17 +585,26 @@ pub const VirtualMachine = struct {
.expression = i.operands.block,
};
},
.val_offset => {
// TODO: Implement
unreachable;
.val_offset => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.operands.register);
column.rule = .{
.val_offset = @as(i64, @intCast(i.operands.offset)) * cie.data_alignment_factor,
};
},
.val_offset_sf => {
// TODO: Implement
unreachable;
.val_offset_sf => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.operands.register);
column.rule = .{
.val_offset = i.operands.offset * cie.data_alignment_factor,
};
},
.val_expression => {
// TODO: Implement
unreachable;
.val_expression => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.operands.register);
column.rule = .{
.val_expression = i.operands.block,
};
},
}

View File

@ -2,6 +2,9 @@ const std = @import("std");
const builtin = @import("builtin");
const OP = @import("OP.zig");
const leb = @import("../leb128.zig");
const dwarf = @import("../dwarf.zig");
const abi = dwarf.abi;
const mem = std.mem;
pub const StackMachineOptions = struct {
/// The address size of the target architecture
@ -33,9 +36,10 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
};
return struct {
const Value = union(enum) {
const Self = @This();
const Operand = union(enum) {
generic: addr_type,
const_type: []const u8,
register: u8,
base_register: struct {
base_register: u8,
@ -46,7 +50,11 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
offset: i64,
},
block: []const u8,
base_type: struct {
register_type: struct {
register: u8,
type_offset: u64,
},
const_type: struct {
type_offset: u64,
value_bytes: []const u8,
},
@ -56,9 +64,31 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
},
};
const Value = union(enum) {
generic: addr_type,
regval_type: struct {
// Offset of DW_TAG_base_type DIE
type_offset: u64,
value: addr_type,
},
const_type: struct {
// Offset of DW_TAG_base_type DIE
type_offset: u64,
value_bytes: []const u8,
},
};
stack: std.ArrayListUnmanaged(Value) = .{},
fn generic(value: anytype) Value {
pub fn reset(self: *Self) void {
self.stack.clearRetainingCapacity();
}
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
self.stack.deinit(allocator);
}
fn generic(value: anytype) Operand {
const int_info = @typeInfo(@TypeOf(value)).Int;
if (@sizeOf(@TypeOf(value)) > options.addr_size) {
return .{ .generic = switch (int_info.signedness) {
@ -73,7 +103,7 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
}
}
pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8) !?Value {
pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8) !?Operand {
const reader = stream.reader();
return switch (opcode) {
OP.addr,
@ -87,8 +117,8 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
OP.const1s => generic(try reader.readByteSigned()),
OP.const2u,
OP.call2,
OP.call4,
=> generic(try reader.readInt(u16, options.endian)),
OP.call4 => generic(try reader.readInt(u32, options.endian)),
OP.const2s,
OP.bra,
OP.skip,
@ -114,21 +144,35 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
.offset = try leb.readILEB128(i64, reader),
} },
OP.regx => .{ .register = try leb.readULEB128(u8, reader) },
OP.bregx, OP.regval_type => .{ .base_register = .{
.base_register = try leb.readULEB128(u8, reader),
.offset = try leb.readILEB128(i64, reader),
} },
OP.bregx => blk: {
const base_register = try leb.readULEB128(u8, reader);
const offset = try leb.readILEB128(i64, reader);
break :blk .{ .base_register = .{
.base_register = base_register,
.offset = offset,
} };
},
OP.regval_type => blk: {
const register = try leb.readULEB128(u8, reader);
const type_offset = try leb.readULEB128(u64, reader);
break :blk .{ .register_type = .{
.register = register,
.type_offset = type_offset,
} };
},
OP.piece => .{
.composite_location = .{
.size = try leb.readULEB128(u8, reader),
.offset = 0,
},
},
OP.bit_piece => .{
.composite_location = .{
.size = try leb.readULEB128(u8, reader),
.offset = try leb.readILEB128(i64, reader),
},
OP.bit_piece => blk: {
const size = try leb.readULEB128(u8, reader);
const offset = try leb.readILEB128(i64, reader);
break :blk .{ .composite_location = .{
.size = size,
.offset = offset,
} };
},
OP.implicit_value, OP.entry_value => blk: {
const size = try leb.readULEB128(u8, reader);
@ -145,7 +189,7 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
if (stream.pos + size > stream.buffer.len) return error.InvalidExpression;
const value_bytes = stream.buffer[stream.pos..][0..size];
stream.pos += size;
break :blk .{ .base_type = .{
break :blk .{ .const_type = .{
.type_offset = type_offset,
.value_bytes = value_bytes,
} };
@ -163,22 +207,144 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
};
}
pub fn step(
self: *StackMachine,
stream: std.io.FixedBufferStream([]const u8),
pub fn run(
self: *Self,
expression: []const u8,
allocator: std.mem.Allocator,
) !void {
if (@sizeOf(usize) != addr_type or options.endian != builtin.target.cpu.arch.endian())
compile_unit: ?*const dwarf.CompileUnit,
ucontext: *const std.os.ucontext_t,
reg_ctx: abi.RegisterContext,
initial_value: usize,
) !Value {
try self.stack.append(allocator, .{ .generic = initial_value });
var stream = std.io.fixedBufferStream(expression);
while (try self.step(&stream, allocator, compile_unit, ucontext, reg_ctx)) {}
if (self.stack.items.len == 0) return error.InvalidExpression;
return self.stack.items[self.stack.items.len - 1];
}
/// Reads an opcode and its operands from the stream and executes it
pub fn step(
self: *Self,
stream: *std.io.FixedBufferStream([]const u8),
allocator: std.mem.Allocator,
compile_unit: ?*const dwarf.CompileUnit,
ucontext: *const std.os.ucontext_t,
reg_ctx: dwarf.abi.RegisterContext,
) !bool {
if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian())
@compileError("Execution of non-native address sizees / endianness is not supported");
const opcode = try stream.reader.readByte();
_ = opcode;
_ = self;
_ = allocator;
const opcode = try stream.reader().readByte();
if (options.call_frame_mode) {
// Certain opcodes are not allowed in a CFA context, see 6.4.2
switch (opcode) {
OP.addrx,
OP.call2,
OP.call4,
OP.call_ref,
OP.const_type,
OP.constx,
OP.convert,
OP.deref_type,
OP.regval_type,
OP.reinterpret,
OP.push_object_address,
OP.call_frame_cfa,
=> return error.InvalidCFAExpression,
else => {},
}
}
// switch (opcode) {
// OP.addr => try self.stack.append(allocator, try readOperand(stream, opcode)),
// }
switch (opcode) {
// 2.5.1.1: Literal Encodings
OP.lit0...OP.lit31,
OP.addr,
OP.const1u,
OP.const2u,
OP.const4u,
OP.const8u,
OP.const1s,
OP.const2s,
OP.const4s,
OP.const8s,
OP.constu,
OP.consts,
=> try self.stack.append(allocator, .{ .generic = (try readOperand(stream, opcode)).?.generic }),
OP.const_type => {
const const_type = (try readOperand(stream, opcode)).?.const_type;
try self.stack.append(allocator, .{ .const_type = .{
.type_offset = const_type.type_offset,
.value_bytes = const_type.value_bytes,
} });
},
OP.addrx, OP.constx => {
const debug_addr_index = (try readOperand(stream, opcode)).?.generic;
// TODO: Read item from .debug_addr, this requires need DW_AT_addr_base of the compile unit, push onto stack as generic
_ = debug_addr_index;
unreachable;
},
// 2.5.1.2: Register Values
OP.fbreg => {
if (compile_unit == null) return error.ExpressionRequiresCompileUnit;
if (compile_unit.?.frame_base == null) return error.ExpressionRequiresFrameBase;
const offset: i64 = @intCast((try readOperand(stream, opcode)).?.generic);
_ = offset;
switch (compile_unit.?.frame_base.?.*) {
.ExprLoc => {
// TODO: Run this expression in a nested stack machine
return error.UnimplementedOpcode;
},
.LocListOffset => {
// TODO: Read value from .debug_loclists
return error.UnimplementedOpcode;
},
.SecOffset => {
// TODO: Read value from .debug_loclists
return error.UnimplementedOpcode;
},
else => return error.InvalidFrameBase,
}
},
OP.breg0...OP.breg31, OP.bregx => {
const base_register = (try readOperand(stream, opcode)).?.base_register;
var value: i64 = @intCast(mem.readIntSliceNative(usize, try abi.regBytes(ucontext, base_register.base_register, reg_ctx)));
value += base_register.offset;
try self.stack.append(allocator, .{ .generic = @intCast(value) });
},
OP.regval_type => {
const register_type = (try readOperand(stream, opcode)).?.register_type;
const value = mem.readIntSliceNative(usize, try abi.regBytes(ucontext, register_type.register, reg_ctx));
try self.stack.append(allocator, .{
.regval_type = .{
.value = value,
.type_offset = register_type.type_offset,
},
});
},
// 2.5.1.3: Stack Operations
OP.dup => {},
else => {
std.debug.print("Unimplemented DWARF expression opcode: {x}\n", .{opcode});
unreachable;
},
// These have already been handled by readOperand
OP.lo_user...OP.hi_user => unreachable,
}
return stream.pos < stream.buffer.len;
}
};
}