| ... | ... | @@ -1,9 +1,11 @@ |
| 1 | const builtin = @import("builtin"); |
| 1 | 2 | const std = @import("../std.zig"); |
| 2 | 3 | const debug = std.debug; |
| 3 | 4 | const leb = @import("../leb128.zig"); |
| 4 | 5 | const abi = @import("abi.zig"); |
| 5 | 6 | const dwarf = @import("../dwarf.zig"); |
| 6 | 7 | const expressions = @import("expressions.zig"); |
| 8 | const assert = std.debug.assert; |
| 7 | 9 | |
| 8 | 10 | const Opcode = enum(u8) { |
| 9 | 11 | advance_loc = 0x1 << 6, |
| ... | ... | @@ -36,7 +38,7 @@ const Opcode = enum(u8) { |
| 36 | 38 | |
| 37 | 39 | // These opcodes encode an operand in the lower 6 bits of the opcode itself |
| 38 | 40 | pub const lo_inline = Opcode.advance_loc; |
| 39 | | pub const hi_inline = Opcode.restore; |
| 41 | pub const hi_inline = @enumToInt(Opcode.restore) | 0b111111; |
| 40 | 42 | |
| 41 | 43 | // These opcodes are trailed by zero or more operands |
| 42 | 44 | pub const lo_reserved = Opcode.nop; |
| ... | ... | @@ -61,7 +63,7 @@ const Operand = enum { |
| 61 | 63 | |
| 62 | 64 | fn Storage(comptime self: Operand) type { |
| 63 | 65 | return switch (self) { |
| 64 | | .opcode_delta, .opcode_register => u6, |
| 66 | .opcode_delta, .opcode_register => u8, |
| 65 | 67 | .uleb128_register => u8, |
| 66 | 68 | .uleb128_offset => u64, |
| 67 | 69 | .sleb128_offset => i64, |
| ... | ... | @@ -110,7 +112,7 @@ const Operand = enum { |
| 110 | 112 | |
| 111 | 113 | fn InstructionType(comptime definition: anytype) type { |
| 112 | 114 | const definition_type = @typeInfo(@TypeOf(definition)); |
| 113 | | debug.assert(definition_type == .Struct); |
| 115 | assert(definition_type == .Struct); |
| 114 | 116 | |
| 115 | 117 | const definition_len = definition_type.Struct.fields.len; |
| 116 | 118 | comptime var fields: [definition_len]std.builtin.Type.StructField = undefined; |
| ... | ... | @@ -159,14 +161,14 @@ fn InstructionType(comptime definition: anytype) type { |
| 159 | 161 | pub const Instruction = union(Opcode) { |
| 160 | 162 | advance_loc: InstructionType(.{ .delta = .opcode_delta }), |
| 161 | 163 | offset: InstructionType(.{ .register = .opcode_register, .offset = .uleb128_offset }), |
| 164 | offset_extended: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }), |
| 162 | 165 | restore: InstructionType(.{ .register = .opcode_register }), |
| 166 | restore_extended: InstructionType(.{ .register = .uleb128_register }), |
| 163 | 167 | nop: InstructionType(.{}), |
| 164 | 168 | set_loc: InstructionType(.{ .address = .address }), |
| 165 | 169 | advance_loc1: InstructionType(.{ .delta = .u8_delta }), |
| 166 | 170 | advance_loc2: InstructionType(.{ .delta = .u16_delta }), |
| 167 | 171 | advance_loc4: InstructionType(.{ .delta = .u32_delta }), |
| 168 | | offset_extended: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }), |
| 169 | | restore_extended: InstructionType(.{ .register = .uleb128_register }), |
| 170 | 172 | undefined: InstructionType(.{ .register = .uleb128_register }), |
| 171 | 173 | same_value: InstructionType(.{ .register = .uleb128_register }), |
| 172 | 174 | register: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }), |
| ... | ... | @@ -192,7 +194,7 @@ pub const Instruction = union(Opcode) { |
| 192 | 194 | @setEvalBranchQuota(1800); |
| 193 | 195 | |
| 194 | 196 | return switch (try stream.reader().readByte()) { |
| 195 | | inline @enumToInt(Opcode.lo_inline)...@enumToInt(Opcode.hi_inline) => |opcode| blk: { |
| 197 | inline @enumToInt(Opcode.lo_inline)...Opcode.hi_inline => |opcode| blk: { |
| 196 | 198 | const e = @intToEnum(Opcode, opcode & 0b11000000); |
| 197 | 199 | const payload_type = std.meta.TagPayload(Instruction, e); |
| 198 | 200 | const value = try payload_type.read(stream, @intCast(u6, opcode & 0b111111), addr_size_bytes, endian); |
| ... | ... | @@ -205,112 +207,15 @@ pub const Instruction = union(Opcode) { |
| 205 | 207 | break :blk @unionInit(Instruction, @tagName(e), value); |
| 206 | 208 | }, |
| 207 | 209 | Opcode.lo_user...Opcode.hi_user => error.UnimplementedUserOpcode, |
| 208 | | else => error.InvalidOpcode, |
| 209 | | }; |
| 210 | | } |
| 210 | else => |opcode| blk: { |
| 211 | std.debug.print("Opcode {x}\n", .{opcode}); |
| 211 | 212 | |
| 212 | | pub fn writeOperands( |
| 213 | | self: Instruction, |
| 214 | | writer: anytype, |
| 215 | | cie: dwarf.CommonInformationEntry, |
| 216 | | arch: ?std.Target.Cpu.Arch, |
| 217 | | addr_size_bytes: u8, |
| 218 | | endian: std.builtin.Endian, |
| 219 | | ) !void { |
| 220 | | switch (self) { |
| 221 | | inline .advance_loc, .advance_loc1, .advance_loc2, .advance_loc4 => |i| try writer.print("{}", .{i.operands.delta * cie.code_alignment_factor}), |
| 222 | | .offset => |i| { |
| 223 | | try abi.writeRegisterName(writer, arch, i.operands.register); |
| 224 | | try writer.print(" {}", .{@intCast(i64, i.operands.offset) * cie.data_alignment_factor}); |
| 213 | break :blk error.InvalidOpcode; |
| 225 | 214 | }, |
| 226 | | .restore => {}, |
| 227 | | .nop => {}, |
| 228 | | .set_loc => {}, |
| 229 | | .offset_extended => {}, |
| 230 | | .restore_extended => {}, |
| 231 | | .undefined => {}, |
| 232 | | .same_value => {}, |
| 233 | | .register => {}, |
| 234 | | .remember_state => {}, |
| 235 | | .restore_state => {}, |
| 236 | | .def_cfa => |i| try writer.print("{} {d:<1}", .{ abi.fmtRegister(i.operands.register, arch), @intCast(i64, i.operands.offset)}), |
| 237 | | .def_cfa_register => |i| try abi.writeRegisterName(writer, arch, i.operands.register), |
| 238 | | .def_cfa_offset => |i| try writer.print("{d:<1}", .{@intCast(i64, i.operands.offset)}), |
| 239 | | .def_cfa_expression => |i| try writeExpression(writer, i.operands.block, arch, addr_size_bytes, endian), |
| 240 | | .expression => {}, |
| 241 | | .offset_extended_sf => {}, |
| 242 | | .def_cfa_sf => {}, |
| 243 | | .def_cfa_offset_sf => {}, |
| 244 | | .val_offset => {}, |
| 245 | | .val_offset_sf => {}, |
| 246 | | .val_expression => {}, |
| 247 | | } |
| 215 | }; |
| 248 | 216 | } |
| 249 | 217 | }; |
| 250 | 218 | |
| 251 | | fn writeExpression( |
| 252 | | writer: anytype, |
| 253 | | block: []const u8, |
| 254 | | arch: ?std.Target.Cpu.Arch, |
| 255 | | addr_size_bytes: u8, |
| 256 | | endian: std.builtin.Endian, |
| 257 | | ) !void { |
| 258 | | var stream = std.io.fixedBufferStream(block); |
| 259 | | |
| 260 | | // Generate a lookup table from opcode value to name |
| 261 | | const opcode_lut_len = 256; |
| 262 | | const opcode_lut: [opcode_lut_len]?[]const u8 = comptime blk: { |
| 263 | | var lut: [opcode_lut_len]?[]const u8 = [_]?[]const u8{null} ** opcode_lut_len; |
| 264 | | for (@typeInfo(dwarf.OP).Struct.decls) |decl| { |
| 265 | | lut[@as(u8, @field(dwarf.OP, decl.name))] = decl.name; |
| 266 | | } |
| 267 | | |
| 268 | | break :blk lut; |
| 269 | | }; |
| 270 | | |
| 271 | | switch (endian) { |
| 272 | | inline .Little, .Big => |e| { |
| 273 | | switch (addr_size_bytes) { |
| 274 | | inline 2, 4, 8 => |size| { |
| 275 | | const StackMachine = expressions.StackMachine(.{ |
| 276 | | .addr_size = size, |
| 277 | | .endian = e, |
| 278 | | .call_frame_mode = true, |
| 279 | | }); |
| 280 | | |
| 281 | | const reader = stream.reader(); |
| 282 | | while (stream.pos < stream.buffer.len) { |
| 283 | | if (stream.pos > 0) try writer.writeAll(", "); |
| 284 | | |
| 285 | | const opcode = try reader.readByte(); |
| 286 | | if (opcode_lut[opcode]) |opcode_name| { |
| 287 | | try writer.print("DW_OP_{s}", .{opcode_name}); |
| 288 | | } else { |
| 289 | | // TODO: See how llvm-dwarfdump prints these? |
| 290 | | if (opcode >= dwarf.OP.lo_user and opcode <= dwarf.OP.lo_user) { |
| 291 | | try writer.print("<unknown vendor opcode: 0x{x}>", .{opcode}); |
| 292 | | } else { |
| 293 | | try writer.print("<invalid opcode: 0x{x}>", .{opcode}); |
| 294 | | } |
| 295 | | } |
| 296 | | |
| 297 | | if (try StackMachine.readOperand(&stream, opcode)) |value| { |
| 298 | | switch (value) { |
| 299 | | //.generic => |v| try writer.print("{d}", .{v}), |
| 300 | | .generic => {}, // Constant values are implied by the opcode name |
| 301 | | .register => |v| try writer.print(" {}", .{ abi.fmtRegister(v, arch) }), |
| 302 | | .base_register => |v| try writer.print(" {}{d:<1}", .{ abi.fmtRegister(v.base_register, arch), v.offset }), |
| 303 | | else => try writer.print(" TODO({s})", .{@tagName(value)}), |
| 304 | | } |
| 305 | | } |
| 306 | | } |
| 307 | | }, |
| 308 | | else => return error.InvalidAddrSize, |
| 309 | | } |
| 310 | | }, |
| 311 | | } |
| 312 | | } |
| 313 | | |
| 314 | 219 | /// See section 6.4.1 of the DWARF5 specification |
| 315 | 220 | pub const VirtualMachine = struct { |
| 316 | 221 | const RegisterRule = union(enum) { |
| ... | ... | @@ -324,52 +229,6 @@ pub const VirtualMachine = struct { |
| 324 | 229 | architectural: void, |
| 325 | 230 | }; |
| 326 | 231 | |
| 327 | | pub const Column = struct { |
| 328 | | register: u8 = undefined, |
| 329 | | rule: RegisterRule = .{ .undefined = {} }, |
| 330 | | |
| 331 | | pub fn writeRule( |
| 332 | | self: Column, |
| 333 | | writer: anytype, |
| 334 | | is_cfa: bool, |
| 335 | | arch: ?std.Target.Cpu.Arch, |
| 336 | | addr_size_bytes: u8, |
| 337 | | endian: std.builtin.Endian, |
| 338 | | ) !void { |
| 339 | | if (is_cfa) { |
| 340 | | try writer.writeAll("CFA"); |
| 341 | | } else { |
| 342 | | try abi.writeRegisterName(writer, arch, self.register); |
| 343 | | } |
| 344 | | |
| 345 | | try writer.writeByte('='); |
| 346 | | switch (self.rule) { |
| 347 | | .undefined => {}, |
| 348 | | .same_value => try writer.writeAll("S"), |
| 349 | | .offset => |offset| { |
| 350 | | if (is_cfa) { |
| 351 | | try abi.writeRegisterName(writer, arch, self.register); |
| 352 | | try writer.print("{d:<1}", .{offset}); |
| 353 | | } else { |
| 354 | | try writer.print("[CFA{d:<1}]", .{offset}); |
| 355 | | } |
| 356 | | }, |
| 357 | | .val_offset => |offset| { |
| 358 | | if (is_cfa) { |
| 359 | | try abi.writeRegisterName(writer, arch, self.register); |
| 360 | | try writer.print("{d:<1}", .{offset}); |
| 361 | | } else { |
| 362 | | try writer.print("CFA{d:<1}", .{offset}); |
| 363 | | } |
| 364 | | }, |
| 365 | | .register => |register| try abi.writeRegisterName(writer, arch, register), |
| 366 | | .expression => |expression| try writeExpression(writer, expression, arch, addr_size_bytes, endian), |
| 367 | | .val_expression => try writer.writeAll("TODO(val_expression)"), |
| 368 | | .architectural => try writer.writeAll("TODO(architectural)"), |
| 369 | | } |
| 370 | | } |
| 371 | | }; |
| 372 | | |
| 373 | 232 | /// Each row contains unwinding rules for a set of registers at a specific location in the |
| 374 | 233 | pub const Row = struct { |
| 375 | 234 | /// Offset from pc_begin |
| ... | ... | @@ -381,6 +240,12 @@ pub const VirtualMachine = struct { |
| 381 | 240 | columns: ColumnRange = .{}, |
| 382 | 241 | }; |
| 383 | 242 | |
| 243 | pub const Column = struct { |
| 244 | /// Register can only null in the case of the CFA column |
| 245 | register: ?u8 = null, |
| 246 | rule: RegisterRule = .{ .undefined = {} }, |
| 247 | }; |
| 248 | |
| 384 | 249 | const ColumnRange = struct { |
| 385 | 250 | /// Index into `columns` of the first column in this row. |
| 386 | 251 | start: usize = undefined, |
| ... | ... | @@ -391,10 +256,14 @@ pub const VirtualMachine = struct { |
| 391 | 256 | stack: std.ArrayListUnmanaged(ColumnRange) = .{}, |
| 392 | 257 | current_row: Row = .{}, |
| 393 | 258 | |
| 259 | /// The result of executing the CIE's initial_instructions |
| 260 | cie_row: ?Row = null, |
| 261 | |
| 394 | 262 | pub fn reset(self: *VirtualMachine) void { |
| 395 | 263 | self.stack.clearRetainingCapacity(); |
| 396 | 264 | self.columns.clearRetainingCapacity(); |
| 397 | 265 | self.current_row = .{}; |
| 266 | self.cie_row = null; |
| 398 | 267 | } |
| 399 | 268 | |
| 400 | 269 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { |
| ... | ... | @@ -403,12 +272,14 @@ pub const VirtualMachine = struct { |
| 403 | 272 | self.* = undefined; |
| 404 | 273 | } |
| 405 | 274 | |
| 406 | | pub fn getColumns(self: VirtualMachine, row: Row) []Column { |
| 275 | /// Return a slice backed by the row's non-CFA columns |
| 276 | pub fn rowColumns(self: VirtualMachine, row: Row) []Column { |
| 407 | 277 | return self.columns.items[row.columns.start..][0..row.columns.len]; |
| 408 | 278 | } |
| 409 | 279 | |
| 280 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row |
| 410 | 281 | fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { |
| 411 | | for (self.getColumns(self.current_row)) |*c| { |
| 282 | for (self.rowColumns(self.current_row)) |*c| { |
| 412 | 283 | if (c.register == register) return c; |
| 413 | 284 | } |
| 414 | 285 | |
| ... | ... | @@ -425,20 +296,82 @@ pub const VirtualMachine = struct { |
| 425 | 296 | return column; |
| 426 | 297 | } |
| 427 | 298 | |
| 428 | | pub fn step(self: *VirtualMachine, allocator: std.mem.Allocator, cie: dwarf.CommonInformationEntry, instruction: Instruction) !void { |
| 299 | /// Runs the CIE instructions, then the FDE instructions. Execution halts |
| 300 | /// once the row that corresponds to `pc` is known, and it is returned. |
| 301 | pub fn unwindTo( |
| 302 | self: *VirtualMachine, |
| 303 | allocator: std.mem.Allocator, |
| 304 | pc: u64, |
| 305 | cie: dwarf.CommonInformationEntry, |
| 306 | fde: dwarf.FrameDescriptionEntry, |
| 307 | addr_size_bytes: u8, |
| 308 | endian: std.builtin.Endian, |
| 309 | ) !Row { |
| 310 | assert(self.cie_row == null); |
| 311 | if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; |
| 312 | |
| 313 | var prev_row: Row = self.current_row; |
| 314 | const streams = .{ |
| 315 | std.io.fixedBufferStream(cie.initial_instructions), |
| 316 | std.io.fixedBufferStream(fde.instructions), |
| 317 | }; |
| 318 | |
| 319 | outer: for (streams, 0..) |*stream, i| { |
| 320 | while (stream.pos < stream.buffer.len) { |
| 321 | const instruction = try dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); |
| 322 | prev_row = try self.step(allocator, cie, i == 0, instruction); |
| 323 | if (pc < fde.pc_begin + self.current_row.offset) { |
| 324 | break :outer; |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | return prev_row; |
| 330 | } |
| 331 | |
| 332 | pub fn unwindToNative( |
| 333 | self: *VirtualMachine, |
| 334 | allocator: std.mem.Allocator, |
| 335 | pc: u64, |
| 336 | cie: dwarf.CommonInformationEntry, |
| 337 | fde: dwarf.FrameDescriptionEntry, |
| 338 | ) void { |
| 339 | self.stepTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian()); |
| 340 | } |
| 341 | |
| 342 | /// Executes a single instruction. |
| 343 | /// If this instruction is from the CIE, `is_initial` should be set. |
| 344 | /// Returns the value of `current_row` before executing this instruction |
| 345 | pub fn step( |
| 346 | self: *VirtualMachine, |
| 347 | allocator: std.mem.Allocator, |
| 348 | cie: dwarf.CommonInformationEntry, |
| 349 | is_initial: bool, |
| 350 | instruction: Instruction, |
| 351 | ) !Row { |
| 352 | // CIE instructions must be run before FDE instructions |
| 353 | assert(!is_initial or self.cie_row == null); |
| 354 | if (!is_initial and self.cie_row == null) self.cie_row = self.current_row; |
| 355 | |
| 356 | const prev_row = self.current_row; |
| 429 | 357 | switch (instruction) { |
| 430 | 358 | inline .advance_loc, .advance_loc1, .advance_loc2, .advance_loc4 => |i| { |
| 431 | 359 | self.current_row.offset += i.operands.delta * cie.code_alignment_factor; |
| 432 | 360 | }, |
| 433 | | .offset => |i| { |
| 361 | inline .offset, .offset_extended => |i| { |
| 434 | 362 | const column = try self.getOrAddColumn(allocator, i.operands.register); |
| 435 | 363 | column.rule = .{ .offset = @intCast(i64, i.operands.offset) * cie.data_alignment_factor }; |
| 436 | 364 | }, |
| 437 | | .restore => {}, |
| 365 | inline .restore, .restore_extended => |i| { |
| 366 | if (self.cie_row) |cie_row| { |
| 367 | const column = try self.getOrAddColumn(allocator, i.operands.register); |
| 368 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { |
| 369 | if (cie_column.register == i.operands.register) break cie_column.rule; |
| 370 | } else .{ .undefined = {} }; |
| 371 | } else return error.InvalidOperation; |
| 372 | }, |
| 438 | 373 | .nop => {}, |
| 439 | 374 | .set_loc => {}, |
| 440 | | .offset_extended => {}, |
| 441 | | .restore_extended => {}, |
| 442 | 375 | .undefined => {}, |
| 443 | 376 | .same_value => {}, |
| 444 | 377 | .register => {}, |
| ... | ... | @@ -448,17 +381,19 @@ pub const VirtualMachine = struct { |
| 448 | 381 | |
| 449 | 382 | const new_start = self.columns.items.len; |
| 450 | 383 | if (self.current_row.columns.len > 0) { |
| 451 | | // Since we're copying from the same backing array, ensure it won't be reallocated |
| 452 | 384 | try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); |
| 453 | | self.columns.appendSliceAssumeCapacity(self.getColumns(self.current_row)); |
| 385 | self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); |
| 454 | 386 | self.current_row.columns.start = new_start; |
| 455 | 387 | } |
| 456 | 388 | }, |
| 457 | 389 | .restore_state => { |
| 458 | | // TODO: Is it possible to remove the duplicate from above? Other instructions may have added columns since then though |
| 459 | | const columns = self.stack.popOrNull() orelse return error.InvalidOperation; |
| 460 | | self.current_row.columns.len = columns.len; |
| 461 | | self.current_row.columns.start = columns.start; |
| 390 | const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; |
| 391 | self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); |
| 392 | try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); |
| 393 | |
| 394 | self.current_row.columns.start = self.columns.items.len; |
| 395 | self.current_row.columns.len = restored_columns.len; |
| 396 | self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); |
| 462 | 397 | }, |
| 463 | 398 | .def_cfa => |i| { |
| 464 | 399 | self.current_row.cfa = .{ |
| ... | ... | @@ -488,5 +423,7 @@ pub const VirtualMachine = struct { |
| 488 | 423 | .val_offset_sf => {}, |
| 489 | 424 | .val_expression => {}, |
| 490 | 425 | } |
| 426 | |
| 427 | return prev_row; |
| 491 | 428 | } |
| 492 | 429 | }; |