Commit 424b1299a8
Changed files (1)
lib
std
dwarf
lib/std/dwarf/expressions.zig
@@ -5,17 +5,7 @@ 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
- addr_size: u8 = @sizeOf(usize),
-
- /// Endianess of the target architecture
- endian: std.builtin.Endian = .Little,
-
- /// Restrict the stack machine to a subset of opcodes used in call frame instructions
- call_frame_mode: bool = false,
-};
+const assert = std.debug.assert;
/// Expressions can be evaluated in different contexts, each requiring its own set of inputs.
/// Callers should specify all the fields relevant to their context. If a field is required
@@ -35,10 +25,21 @@ pub const ExpressionContext = struct {
cfa: ?usize,
};
+pub const ExpressionOptions = struct {
+ /// The address size of the target architecture
+ addr_size: u8 = @sizeOf(usize),
+
+ /// Endianess of the target architecture
+ endian: std.builtin.Endian = .Little,
+
+ /// Restrict the stack machine to a subset of opcodes used in call frame instructions
+ call_frame_mode: bool = false,
+};
+
/// A stack machine that can decode and run DWARF expressions.
/// Expressions can be decoded for non-native address size and endianness,
/// but can only be executed if the current target matches the configuration.
-pub fn StackMachine(comptime options: StackMachineOptions) type {
+pub fn StackMachine(comptime options: ExpressionOptions) type {
const addr_type = switch (options.addr_size) {
2 => u16,
4 => u32,
@@ -60,6 +61,7 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
generic: addr_type,
register: u8,
type_size: u8,
+ branch_offset: i16,
base_register: struct {
base_register: u8,
offset: i64,
@@ -114,7 +116,7 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
2 => mem.readIntSliceNative(u16, const_type.value_bytes),
4 => mem.readIntSliceNative(u32, const_type.value_bytes),
8 => mem.readIntSliceNative(u64, const_type.value_bytes),
- else => return error.InvalidIntegralTypeLength,
+ else => error.InvalidIntegralTypeLength,
};
},
};
@@ -163,10 +165,10 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
OP.call2,
=> generic(try reader.readInt(u16, options.endian)),
OP.call4 => generic(try reader.readInt(u32, options.endian)),
- OP.const2s,
+ OP.const2s => generic(try reader.readInt(i16, options.endian)),
OP.bra,
OP.skip,
- => generic(try reader.readInt(i16, options.endian)),
+ => .{ .branch_offset = try reader.readInt(i16, options.endian) },
OP.const4u => generic(try reader.readInt(u32, options.endian)),
OP.const4s => generic(try reader.readInt(i32, options.endian)),
OP.const8u => generic(try reader.readInt(u64, options.endian)),
@@ -362,13 +364,21 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
if (context.ucontext == null) return error.IncompleteExpressionContext;
const base_register = (try readOperand(stream, opcode)).?.base_register;
- var value: i64 = @intCast(mem.readIntSliceNative(usize, try abi.regBytes(context.ucontext.?, base_register.base_register, context.reg_ctx)));
+ var value: i64 = @intCast(mem.readIntSliceNative(usize, try abi.regBytes(
+ context.ucontext.?,
+ base_register.base_register,
+ context.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(context.ucontext.?, register_type.register, context.reg_ctx));
+ const value = mem.readIntSliceNative(usize, try abi.regBytes(
+ context.ucontext.?,
+ register_type.register,
+ context.reg_ctx,
+ ));
try self.stack.append(allocator, .{
.regval_type = .{
.type_offset = register_type.type_offset,
@@ -472,18 +482,242 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
OP.abs => {
if (self.stack.items.len == 0) return error.InvalidExpression;
const value: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
- self.stack.items[self.stack.items.len - 1] = .{ .generic = std.math.absCast(value) };
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = std.math.absCast(value),
+ };
},
OP.@"and" => {
if (self.stack.items.len < 2) return error.InvalidExpression;
const a = try self.stack.pop().asIntegral();
- self.stack.items[self.stack.items.len - 1] = .{ .generic = a & try self.stack.items[self.stack.items.len - 1].asIntegral() };
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = a & try self.stack.items[self.stack.items.len - 1].asIntegral(),
+ };
+ },
+ OP.div => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a: isize = @bitCast(try self.stack.pop().asIntegral());
+ const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = @bitCast(try std.math.divTrunc(isize, b, a)),
+ };
+ },
+ OP.minus => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const b = try self.stack.pop().asIntegral();
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = try std.math.sub(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b),
+ };
+ },
+ OP.mod => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a: isize = @bitCast(try self.stack.pop().asIntegral());
+ const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = @bitCast(@mod(b, a)),
+ };
+ },
+ OP.mul => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a: isize = @bitCast(try self.stack.pop().asIntegral());
+ const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = @bitCast(@mulWithOverflow(a, b)[0]),
+ };
+ },
+ OP.neg => {
+ if (self.stack.items.len == 0) return error.InvalidExpression;
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = @bitCast(
+ try std.math.negate(
+ @as(isize, @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral())),
+ ),
+ ),
+ };
+ },
+ OP.not => {
+ if (self.stack.items.len == 0) return error.InvalidExpression;
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = ~try self.stack.items[self.stack.items.len - 1].asIntegral(),
+ };
+ },
+ OP.@"or" => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a = try self.stack.pop().asIntegral();
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = a | try self.stack.items[self.stack.items.len - 1].asIntegral(),
+ };
+ },
+ OP.plus => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const b = try self.stack.pop().asIntegral();
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b),
+ };
+ },
+ OP.plus_uconst => {
+ if (self.stack.items.len == 0) return error.InvalidExpression;
+ const constant = (try readOperand(stream, opcode)).?.generic;
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), constant),
+ };
+ },
+ OP.shl => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a = try self.stack.pop().asIntegral();
+ const b = try self.stack.items[self.stack.items.len - 1].asIntegral();
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = std.math.shl(usize, b, a),
+ };
+ },
+ OP.shr => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a = try self.stack.pop().asIntegral();
+ const b = try self.stack.items[self.stack.items.len - 1].asIntegral();
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = std.math.shr(usize, b, a),
+ };
+ },
+ OP.shra => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a = try self.stack.pop().asIntegral();
+ const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = @bitCast(std.math.shr(isize, b, a)),
+ };
+ },
+ OP.xor => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a = try self.stack.pop().asIntegral();
+ self.stack.items[self.stack.items.len - 1] = .{
+ .generic = a ^ try self.stack.items[self.stack.items.len - 1].asIntegral(),
+ };
+ },
+
+ // 2.5.1.5: Control Flow Operations
+ OP.le,
+ OP.ge,
+ OP.eq,
+ OP.lt,
+ OP.gt,
+ OP.ne,
+ => {
+ if (self.stack.items.len < 2) return error.InvalidExpression;
+ const a = self.stack.pop();
+ const b = self.stack.items[self.stack.items.len - 1];
+
+ if (a == .generic and b == .generic) {
+ const a_int: isize = @bitCast(a.asIntegral() catch unreachable);
+ const b_int: isize = @bitCast(b.asIntegral() catch unreachable);
+ const result = @intFromBool(switch (opcode) {
+ OP.le => b_int < a_int,
+ OP.ge => b_int >= a_int,
+ OP.eq => b_int == a_int,
+ OP.lt => b_int < a_int,
+ OP.gt => b_int > a_int,
+ OP.ne => b_int != a_int,
+ else => unreachable,
+ });
+
+ self.stack.items[self.stack.items.len - 1] = .{ .generic = result };
+ } else {
+ // TODO: Load the types referenced by these values, find their comparison operator, and run it
+ return error.UnimplementedTypedComparison;
+ }
+ },
+ OP.skip, OP.bra => {
+ const branch_offset = (try readOperand(stream, opcode)).?.branch_offset;
+ const condition = if (opcode == OP.bra) blk: {
+ if (self.stack.items.len == 0) return error.InvalidExpression;
+ break :blk try self.stack.pop().asIntegral() != 0;
+ } else true;
+
+ if (condition) {
+ const new_pos = std.math.cast(
+ usize,
+ try std.math.add(isize, @as(isize, @intCast(stream.pos)), branch_offset),
+ ) orelse return error.InvalidExpression;
+
+ if (new_pos < 0 or new_pos >= stream.buffer.len) return error.InvalidExpression;
+ stream.pos = new_pos;
+ }
+ },
+ OP.call2,
+ OP.call4,
+ OP.call_ref,
+ => {
+ const debug_info_offset = (try readOperand(stream, opcode)).?.generic;
+ _ = debug_info_offset;
+
+ // TODO: Load a DIE entry at debug_info_offset in a .debug_info section (the spec says that it
+ // can be in a separate exe / shared object from the one containing this expression).
+ // Transfer control to the DW_AT_location attribute, with the current stack as input.
+
+ return error.UnimplementedExpressionCall;
+ },
+
+ // 2.5.1.6: Type Conversions
+ OP.convert => {
+ if (self.stack.items.len == 0) return error.InvalidExpression;
+ const type_offset = (try readOperand(stream, opcode)).?.generic;
+ _ = type_offset;
+
+ // TODO: Load the DW_TAG_base_type entry in context.compile_unit, find a conversion operator
+ // from the old type to the new type, run it.
+
+ return error.UnimplementedTypeConversion;
+ },
+ OP.reinterpret => {
+ if (self.stack.items.len == 0) return error.InvalidExpression;
+ const type_offset = (try readOperand(stream, opcode)).?.generic;
+
+ // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size
+ const value = self.stack.items[self.stack.items.len - 1];
+ if (type_offset == 0) {
+ self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() };
+ } else {
+ self.stack.items[self.stack.items.len - 1] = switch (value) {
+ .generic => |v| .{
+ .regval_type = .{
+ .type_offset = type_offset,
+ .type_size = @sizeOf(addr_type),
+ .value = v,
+ },
+ },
+ .regval_type => |r| .{
+ .regval_type = .{
+ .type_offset = type_offset,
+ .type_size = r.type_size,
+ .value = r.value,
+ },
+ },
+ .const_type => |c| .{
+ .const_type = .{
+ .type_offset = type_offset,
+ .value_bytes = c.value_bytes,
+ },
+ },
+ };
+ }
+ },
+
+ // 2.5.1.7: Special Operations
+ OP.nop => {},
+ OP.entry_value => {
+ const block = (try readOperand(stream, opcode)).?.block;
+ _ = block;
+
+ // TODO: If block is an expression, run it on a new stack. Push the resulting value onto this stack.
+ // TODO: If block is a register location, push the value that location had before running this program onto this stack.
+ // This implies capturing all register values before executing this block, in case this program modifies them.
+ // TODO: If the block contains, OP.push_object_address, treat it as OP.nop
+
+ return error.UnimplementedSubExpression;
},
// These have already been handled by readOperand
OP.lo_user...OP.hi_user => unreachable,
else => {
- //std.debug.print("Unimplemented DWARF expression opcode: {x}\n", .{opcode});
+ //std.debug.print("Unknown DWARF expression opcode: {x}\n", .{opcode});
return error.UnknownExpressionOpcode;
},
}
@@ -492,3 +726,232 @@ pub fn StackMachine(comptime options: StackMachineOptions) type {
}
};
}
+
+pub fn Writer(options: ExpressionOptions) type {
+ const addr_type = switch (options.addr_size) {
+ 2 => u16,
+ 4 => u32,
+ 8 => u64,
+ else => @compileError("Unsupported address size of " ++ options.addr_size),
+ };
+
+ return struct {
+ /// Zero-operand instructions
+ pub fn writeOpcode(writer: anytype, comptime opcode: u8) !void {
+ switch (opcode) {
+ OP.dup,
+ OP.drop,
+ OP.over,
+ OP.swap,
+ OP.rot,
+ OP.deref,
+ OP.xderef,
+ OP.push_object_address,
+ OP.form_tls_address,
+ OP.call_frame_cfa,
+ OP.abs,
+ OP.@"and",
+ OP.div,
+ OP.minus,
+ OP.mod,
+ OP.mul,
+ OP.neg,
+ OP.not,
+ OP.@"or",
+ OP.plus,
+ OP.shl,
+ OP.shr,
+ OP.shra,
+ OP.xor,
+ OP.le,
+ OP.ge,
+ OP.eq,
+ OP.lt,
+ OP.gt,
+ OP.ne,
+ OP.nop,
+ => try writer.writeByte(opcode),
+ else => @compileError("This opcode requires operands, use write<Opcode>() instead"),
+ }
+ }
+
+ // 2.5.1.1: Literal Encodings
+ pub fn writeLiteral(writer: anytype, literal: u8) !void {
+ switch (literal) {
+ 0...31 => |n| try writer.writeByte(n + OP.lit0),
+ else => return error.InvalidLiteral,
+ }
+ }
+
+ pub fn writeConst(writer: anytype, comptime T: type, value: T) !void {
+ if (@typeInfo(T) != .Int) @compileError("Constants must be integers");
+
+ switch (T) {
+ u8, i8, u16, i16, u32, i32, u64, i64 => {
+ try writer.writeByte(switch (T) {
+ u8 => OP.const1u,
+ i8 => OP.const1s,
+ u16 => OP.const2u,
+ i16 => OP.const2s,
+ u32 => OP.const4u,
+ i32 => OP.const4s,
+ u64 => OP.const8u,
+ i64 => OP.const8s,
+ });
+
+ try writer.writeInt(T, value, options.endian);
+ },
+ else => switch (@typeInfo(T).Int.signedness) {
+ .unsigned => {
+ try writer.writeByte(OP.constu);
+ try leb.writeULEB128(writer, value);
+ },
+ .signed => {
+ try writer.writeByte(OP.consts);
+ try leb.writeILEB128(writer, value);
+ },
+ },
+ }
+ }
+
+ pub fn writeConstx(writer: anytype, debug_addr_offset: anytype) !void {
+ try writer.writeByte(OP.constx);
+ try leb.writeULEB128(writer, debug_addr_offset);
+ }
+
+ pub fn writeConstType(writer: anytype, die_offset: anytype, size: u8, value_bytes: []const u8) !void {
+ if (size != value_bytes.len) return error.InvalidValueSize;
+ try writer.writeByte(OP.const_type);
+ try leb.writeULEB128(writer, die_offset);
+ try writer.writeByte(size);
+ try writer.writeAll(value_bytes);
+ }
+
+ pub fn writeAddr(writer: anytype, value: addr_type) !void {
+ try writer.writeByte(OP.addr);
+ try writer.writeInt(addr_type, value, options.endian);
+ }
+
+ pub fn writeAddrx(writer: anytype, debug_addr_offset: anytype) !void {
+ try writer.writeByte(OP.addrx);
+ try leb.writeULEB128(writer, debug_addr_offset);
+ }
+
+ // 2.5.1.2: Register Values
+ pub fn writeFbreg(writer: anytype, offset: anytype) !void {
+ try writer.writeByte(OP.fbreg);
+ try leb.writeILEB128(writer, offset);
+ }
+
+ pub fn writeBreg(writer: anytype, register: u8, offset: anytype) !void {
+ if (register > 31) return error.InvalidRegister;
+ try writer.writeByte(OP.reg0 + register);
+ try leb.writeILEB128(writer, offset);
+ }
+
+ pub fn writeBregx(writer: anytype, register: anytype, offset: anytype) !void {
+ try writer.writeByte(OP.bregx);
+ try leb.writeULEB128(writer, register);
+ try leb.writeILEB128(writer, offset);
+ }
+
+ pub fn writeRegvalType(writer: anytype, register: anytype, offset: anytype) !void {
+ try writer.writeByte(OP.bregx);
+ try leb.writeULEB128(writer, register);
+ try leb.writeULEB128(writer, offset);
+ }
+
+ // 2.5.1.3: Stack Operations
+ pub fn writePick(writer: anytype, index: u8) !void {
+ try writer.writeByte(OP.pick);
+ try writer.writeByte(index);
+ }
+
+ pub fn writeDerefSize(writer: anytype, size: u8) !void {
+ try writer.writeByte(OP.deref_size);
+ try writer.writeByte(size);
+ }
+
+ pub fn writeXDerefSize(writer: anytype, size: u8) !void {
+ try writer.writeByte(OP.xderef_size);
+ try writer.writeByte(size);
+ }
+
+ pub fn writeDerefType(writer: anytype, size: u8, die_offset: anytype) !void {
+ try writer.writeByte(OP.deref_type);
+ try writer.writeByte(size);
+ try leb.writeULEB128(writer, die_offset);
+ }
+
+ pub fn writeXDerefType(writer: anytype, size: u8, die_offset: anytype) !void {
+ try writer.writeByte(OP.xderef_type);
+ try writer.writeByte(size);
+ try leb.writeULEB128(writer, die_offset);
+ }
+
+ // 2.5.1.4: Arithmetic and Logical Operations
+
+ pub fn writePlusUconst(writer: anytype, uint_value: anytype) !void {
+ try writer.writeByte(OP.plus_uconst);
+ try leb.writeULEB128(writer, uint_value);
+ }
+
+ // 2.5.1.5: Control Flow Operations
+
+ pub fn writeSkip(writer: anytype, offset: i16) !void {
+ try writer.writeByte(OP.skip);
+ try writer.writeInt(i16, offset, options.endian);
+ }
+
+ pub fn writeBra(writer: anytype, offset: i16) !void {
+ try writer.writeByte(OP.bra);
+ try writer.writeInt(i16, offset, options.endian);
+ }
+
+ pub fn writeCall(writer: anytype, comptime T: type, offset: T) !void {
+ switch (T) {
+ u16 => try writer.writeByte(OP.call2),
+ u32 => try writer.writeByte(OP.call4),
+ else => @compileError("Call operand must be a 2 or 4 byte offset"),
+ }
+
+ try writer.writeInt(T, offset, options.endian);
+ }
+
+ pub fn writeCallRef(writer: anytype, debug_info_offset: addr_type) !void {
+ try writer.writeByte(OP.call_ref);
+ try writer.writeInt(addr_type, debug_info_offset, options.endian);
+ }
+
+ pub fn writeConvert(writer: anytype, die_offset: anytype) !void {
+ try writer.writeByte(OP.convert);
+ try leb.writeULEB128(writer, die_offset);
+ }
+
+ pub fn writeReinterpret(writer: anytype, die_offset: anytype) !void {
+ try writer.writeByte(OP.reinterpret);
+ try leb.writeULEB128(writer, die_offset);
+ }
+
+ // 2.5.1.7: Special Operations
+
+ pub fn writeEntryValue(writer: anytype, expression: []const u8) !void {
+ try writer.writeByte(OP.entry_value);
+ try leb.writeULEB128(writer, expression.len);
+ try writer.writeAll(expression);
+ }
+
+ // 2.6: Location Descriptions
+
+ // TODO
+
+ };
+}
+
+test "DWARF expressions" {
+ const allocator = std.testing.allocator;
+
+ const options = ExpressionOptions{};
+ const stack_machine = StackMachine(options){};
+ defer stack_machine.deinit(allocator);
+}