diff --git a/lib/std/dwarf/expressions.zig b/lib/std/dwarf/expressions.zig index e5f553ae29..cc6b687930 100644 --- a/lib/std/dwarf/expressions.zig +++ b/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() 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); +}