| ... | ... | @@ -3,18 +3,13 @@ const debug = std.debug; |
| 3 | 3 | const leb = @import("../leb128.zig"); |
| 4 | 4 | const abi = @import("abi.zig"); |
| 5 | 5 | const dwarf = @import("../dwarf.zig"); |
| 6 | const expressions = @import("expressions.zig"); |
| 6 | 7 | |
| 7 | | // These enum values correspond to the opcode encoding itself, with |
| 8 | | // the exception of the opcodes that include data in the opcode itself. |
| 9 | | // For those, the enum value is the opcode with the lower 6 bits (the data) masked to 0. |
| 10 | 8 | const Opcode = enum(u8) { |
| 11 | | // These are placeholders that define the range of vendor-specific opcodes |
| 12 | | const lo_user = 0x1c; |
| 13 | | const hi_user = 0x3f; |
| 14 | | |
| 15 | 9 | advance_loc = 0x1 << 6, |
| 16 | 10 | offset = 0x2 << 6, |
| 17 | 11 | restore = 0x3 << 6, |
| 12 | |
| 18 | 13 | nop = 0x00, |
| 19 | 14 | set_loc = 0x01, |
| 20 | 15 | advance_loc1 = 0x02, |
| ... | ... | @@ -39,7 +34,17 @@ const Opcode = enum(u8) { |
| 39 | 34 | val_offset_sf = 0x15, |
| 40 | 35 | val_expression = 0x16, |
| 41 | 36 | |
| 42 | | _, |
| 37 | // These opcodes encode an operand in the lower 6 bits of the opcode itself |
| 38 | pub const lo_inline = Opcode.advance_loc; |
| 39 | pub const hi_inline = Opcode.restore; |
| 40 | |
| 41 | // These opcodes are trailed by zero or more operands |
| 42 | pub const lo_reserved = Opcode.nop; |
| 43 | pub const hi_reserved = Opcode.val_expression; |
| 44 | |
| 45 | // Vendor-specific opcodes |
| 46 | pub const lo_user = 0x1c; |
| 47 | pub const hi_user = 0x3f; |
| 43 | 48 | }; |
| 44 | 49 | |
| 45 | 50 | const Operand = enum { |
| ... | ... | @@ -70,11 +75,12 @@ const Operand = enum { |
| 70 | 75 | |
| 71 | 76 | fn read( |
| 72 | 77 | comptime self: Operand, |
| 73 | | reader: anytype, |
| 78 | stream: *std.io.FixedBufferStream([]const u8), |
| 74 | 79 | opcode_value: ?u6, |
| 75 | 80 | addr_size_bytes: u8, |
| 76 | 81 | endian: std.builtin.Endian, |
| 77 | 82 | ) !Storage(self) { |
| 83 | const reader = stream.reader(); |
| 78 | 84 | return switch (self) { |
| 79 | 85 | .opcode_delta, .opcode_register => opcode_value orelse return error.InvalidOperand, |
| 80 | 86 | .uleb128_register => try leb.readULEB128(u8, reader), |
| ... | ... | @@ -91,13 +97,13 @@ const Operand = enum { |
| 91 | 97 | .u32_delta => try reader.readInt(u32, endian), |
| 92 | 98 | .block => { |
| 93 | 99 | const block_len = try leb.readULEB128(u64, reader); |
| 100 | if (stream.pos + block_len > stream.buffer.len) return error.InvalidOperand; |
| 94 | 101 | |
| 95 | | // TODO: This feels like a kludge, change to FixedBufferStream param? |
| 96 | | const block = reader.context.buffer[reader.context.pos..][0..block_len]; |
| 102 | const block = stream.buffer[stream.pos..][0..block_len]; |
| 97 | 103 | reader.context.pos += block_len; |
| 98 | 104 | |
| 99 | 105 | return block; |
| 100 | | } |
| 106 | }, |
| 101 | 107 | }; |
| 102 | 108 | } |
| 103 | 109 | }; |
| ... | ... | @@ -133,11 +139,16 @@ fn InstructionType(comptime definition: anytype) type { |
| 133 | 139 | const Self = @This(); |
| 134 | 140 | operands: InstructionOperands, |
| 135 | 141 | |
| 136 | | pub fn read(reader: anytype, opcode_value: ?u6, addr_size_bytes: u8, endian: std.builtin.Endian) !Self { |
| 142 | pub fn read( |
| 143 | stream: *std.io.FixedBufferStream([]const u8), |
| 144 | opcode_value: ?u6, |
| 145 | addr_size_bytes: u8, |
| 146 | endian: std.builtin.Endian, |
| 147 | ) !Self { |
| 137 | 148 | var operands: InstructionOperands = undefined; |
| 138 | 149 | inline for (definition_type.Struct.fields) |definition_field| { |
| 139 | 150 | const operand = comptime std.enums.nameCast(Operand, @field(definition, definition_field.name)); |
| 140 | | @field(operands, definition_field.name) = try operand.read(reader, opcode_value, addr_size_bytes, endian); |
| 151 | @field(operands, definition_field.name) = try operand.read(stream, opcode_value, addr_size_bytes, endian); |
| 141 | 152 | } |
| 142 | 153 | |
| 143 | 154 | return .{ .operands = operands }; |
| ... | ... | @@ -173,37 +184,44 @@ pub const Instruction = union(Opcode) { |
| 173 | 184 | val_offset_sf: InstructionType(.{ .a = .uleb128_offset, .b = .sleb128_offset }), |
| 174 | 185 | val_expression: InstructionType(.{ .a = .uleb128_offset, .block = .block }), |
| 175 | 186 | |
| 176 | | pub fn read(reader: anytype, addr_size_bytes: u8, endian: std.builtin.Endian) !Instruction { |
| 177 | | const opcode = try reader.readByte(); |
| 178 | | const upper = opcode & 0b11000000; |
| 179 | | return switch (upper) { |
| 180 | | inline @enumToInt(Opcode.advance_loc), @enumToInt(Opcode.offset), @enumToInt(Opcode.restore) => |u| @unionInit( |
| 181 | | Instruction, |
| 182 | | @tagName(@intToEnum(Opcode, u)), |
| 183 | | try std.meta.TagPayload(Instruction, @intToEnum(Opcode, u)).read(reader, @intCast(u6, opcode & 0b111111), addr_size_bytes, endian), |
| 184 | | ), |
| 185 | | 0 => blk: { |
| 186 | | inline for (@typeInfo(Opcode).Enum.fields) |field| { |
| 187 | | if (field.value == opcode) { |
| 188 | | break :blk @unionInit( |
| 189 | | Instruction, |
| 190 | | @tagName(@intToEnum(Opcode, field.value)), |
| 191 | | try std.meta.TagPayload(Instruction, @intToEnum(Opcode, field.value)).read(reader, null, addr_size_bytes, endian), |
| 192 | | ); |
| 193 | | } |
| 194 | | } |
| 195 | | break :blk error.UnknownOpcode; |
| 187 | pub fn read( |
| 188 | stream: *std.io.FixedBufferStream([]const u8), |
| 189 | addr_size_bytes: u8, |
| 190 | endian: std.builtin.Endian, |
| 191 | ) !Instruction { |
| 192 | @setEvalBranchQuota(1800); |
| 193 | |
| 194 | return switch (try stream.reader().readByte()) { |
| 195 | inline @enumToInt(Opcode.lo_inline)...@enumToInt(Opcode.hi_inline) => |opcode| blk: { |
| 196 | const e = @intToEnum(Opcode, opcode & 0b11000000); |
| 197 | const payload_type = std.meta.TagPayload(Instruction, e); |
| 198 | const value = try payload_type.read(stream, @intCast(u6, opcode & 0b111111), addr_size_bytes, endian); |
| 199 | break :blk @unionInit(Instruction, @tagName(e), value); |
| 196 | 200 | }, |
| 197 | | else => error.UnknownOpcode, |
| 201 | inline @enumToInt(Opcode.lo_reserved)...@enumToInt(Opcode.hi_reserved) => |opcode| blk: { |
| 202 | const e = @intToEnum(Opcode, opcode); |
| 203 | const payload_type = std.meta.TagPayload(Instruction, e); |
| 204 | const value = try payload_type.read(stream, null, addr_size_bytes, endian); |
| 205 | break :blk @unionInit(Instruction, @tagName(e), value); |
| 206 | }, |
| 207 | Opcode.lo_user...Opcode.hi_user => error.UnimplementedUserOpcode, |
| 208 | else => error.InvalidOpcode, |
| 198 | 209 | }; |
| 199 | 210 | } |
| 200 | 211 | |
| 201 | | pub fn writeOperands(self: Instruction, writer: anytype, cie: dwarf.CommonInformationEntry, arch: ?std.Target.Cpu.Arch) !void { |
| 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 { |
| 202 | 220 | switch (self) { |
| 203 | | inline .advance_loc, .advance_loc1, .advance_loc2, .advance_loc4 => |i| try writer.print("{}", .{ i.operands.delta * cie.code_alignment_factor }), |
| 221 | inline .advance_loc, .advance_loc1, .advance_loc2, .advance_loc4 => |i| try writer.print("{}", .{i.operands.delta * cie.code_alignment_factor}), |
| 204 | 222 | .offset => |i| { |
| 205 | 223 | try abi.writeRegisterName(writer, arch, i.operands.register); |
| 206 | | try writer.print(" {}", .{ @intCast(i64, i.operands.offset) * cie.data_alignment_factor }); |
| 224 | try writer.print(" {}", .{@intCast(i64, i.operands.offset) * cie.data_alignment_factor}); |
| 207 | 225 | }, |
| 208 | 226 | .restore => {}, |
| 209 | 227 | .nop => {}, |
| ... | ... | @@ -217,14 +235,14 @@ pub const Instruction = union(Opcode) { |
| 217 | 235 | .restore_state => {}, |
| 218 | 236 | .def_cfa => |i| { |
| 219 | 237 | try abi.writeRegisterName(writer, arch, i.operands.register); |
| 220 | | try writer.print(" {}", .{ fmtOffset(@intCast(i64, i.operands.offset)) }); |
| 238 | try writer.print(" {d:<1}", .{@intCast(i64, i.operands.offset)}); |
| 221 | 239 | }, |
| 222 | 240 | .def_cfa_register => {}, |
| 223 | 241 | .def_cfa_offset => |i| { |
| 224 | | try writer.print("{}", .{ fmtOffset(@intCast(i64, i.operands.offset)) }); |
| 242 | try writer.print("{d:<1}", .{@intCast(i64, i.operands.offset)}); |
| 225 | 243 | }, |
| 226 | 244 | .def_cfa_expression => |i| { |
| 227 | | try writer.print("TODO(parse expressions data {x})", .{ std.fmt.fmtSliceHexLower(i.operands.block) }); |
| 245 | try writeExpression(writer, i.operands.block, arch, addr_size_bytes, endian); |
| 228 | 246 | }, |
| 229 | 247 | .expression => {}, |
| 230 | 248 | .offset_extended_sf => {}, |
| ... | ... | @@ -235,23 +253,83 @@ pub const Instruction = union(Opcode) { |
| 235 | 253 | .val_expression => {}, |
| 236 | 254 | } |
| 237 | 255 | } |
| 238 | | |
| 239 | 256 | }; |
| 240 | 257 | |
| 258 | fn writeExpression( |
| 259 | writer: anytype, |
| 260 | block: []const u8, |
| 261 | arch: ?std.Target.Cpu.Arch, |
| 262 | addr_size_bytes: u8, |
| 263 | endian: std.builtin.Endian, |
| 264 | ) !void { |
| 265 | var stream = std.io.fixedBufferStream(block); |
| 266 | |
| 267 | // Generate a lookup table from opcode value to name |
| 268 | const opcode_lut_len = 256; |
| 269 | const opcode_lut: [opcode_lut_len]?[]const u8 = comptime blk: { |
| 270 | var lut: [opcode_lut_len]?[]const u8 = [_]?[]const u8{null} ** opcode_lut_len; |
| 271 | for (@typeInfo(dwarf.OP).Struct.decls) |decl| { |
| 272 | lut[@as(u8, @field(dwarf.OP, decl.name))] = decl.name; |
| 273 | } |
| 241 | 274 | |
| 242 | | fn formatOffset(data: i64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { |
| 243 | | _ = fmt; |
| 244 | | if (data >= 0) try writer.writeByte('+'); |
| 245 | | return std.fmt.formatInt(data, 10, .lower, options, writer); |
| 246 | | } |
| 275 | break :blk lut; |
| 276 | }; |
| 247 | 277 | |
| 248 | | fn fmtOffset(offset: i64) std.fmt.Formatter(formatOffset) { |
| 249 | | return .{ .data = offset }; |
| 278 | switch (endian) { |
| 279 | inline .Little, .Big => |e| { |
| 280 | switch (addr_size_bytes) { |
| 281 | inline 2, 4, 8 => |size| { |
| 282 | const StackMachine = expressions.StackMachine(.{ |
| 283 | .addr_size = size, |
| 284 | .endian = e, |
| 285 | .call_frame_mode = true, |
| 286 | }); |
| 287 | |
| 288 | const reader = stream.reader(); |
| 289 | while (stream.pos < stream.buffer.len) { |
| 290 | if (stream.pos > 0) try writer.writeAll(", "); |
| 291 | |
| 292 | const opcode = try reader.readByte(); |
| 293 | if (opcode_lut[opcode]) |opcode_name| { |
| 294 | try writer.print("DW_OP_{s}", .{opcode_name}); |
| 295 | } else { |
| 296 | // TODO: See how llvm-dwarfdump prints these? |
| 297 | if (opcode >= dwarf.OP.lo_user and opcode <= dwarf.OP.lo_user) { |
| 298 | try writer.print("<unknown vendor opcode: 0x{x}>", .{opcode}); |
| 299 | } else { |
| 300 | try writer.print("<invalid opcode: 0x{x}>", .{opcode}); |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | if (try StackMachine.readOperand(&stream, opcode)) |value| { |
| 305 | switch (value) { |
| 306 | //.generic => |v| try writer.print("{d}", .{v}), |
| 307 | .generic => {}, // Constant values are implied by the opcode name |
| 308 | .register => |v| try writer.print(" {}", .{ abi.fmtRegister(v, arch) }), |
| 309 | .base_register => |v| try writer.print(" {}{d:<1}", .{ abi.fmtRegister(v.base_register, arch), v.offset }), |
| 310 | else => try writer.print(" TODO({s})", .{@tagName(value)}), |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | }, |
| 315 | else => return error.InvalidAddrSize, |
| 316 | } |
| 317 | }, |
| 318 | } |
| 250 | 319 | } |
| 251 | 320 | |
| 321 | // fn formatOffset(data: i64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { |
| 322 | // _ = fmt; |
| 323 | // if (data >= 0) try writer.writeByte('+'); |
| 324 | // return std.fmt.formatInt(data, 10, .lower, options, writer); |
| 325 | // } |
| 326 | |
| 327 | // fn fmtOffset(offset: i64) std.fmt.Formatter(formatOffset) { |
| 328 | // return .{ .data = offset }; |
| 329 | // } |
| 330 | |
| 252 | 331 | /// See section 6.4.1 of the DWARF5 specification |
| 253 | 332 | pub const VirtualMachine = struct { |
| 254 | | |
| 255 | 333 | const RegisterRule = union(enum) { |
| 256 | 334 | undefined: void, |
| 257 | 335 | same_value: void, |
| ... | ... | @@ -263,11 +341,18 @@ pub const VirtualMachine = struct { |
| 263 | 341 | architectural: void, |
| 264 | 342 | }; |
| 265 | 343 | |
| 266 | | const Column = struct { |
| 344 | pub const Column = struct { |
| 267 | 345 | register: u8 = undefined, |
| 268 | 346 | rule: RegisterRule = .{ .undefined = {} }, |
| 269 | 347 | |
| 270 | | pub fn writeRule(self: Column, writer: anytype, is_cfa: bool, arch: ?std.Target.Cpu.Arch) !void { |
| 348 | pub fn writeRule( |
| 349 | self: Column, |
| 350 | writer: anytype, |
| 351 | is_cfa: bool, |
| 352 | arch: ?std.Target.Cpu.Arch, |
| 353 | addr_size_bytes: u8, |
| 354 | endian: std.builtin.Endian, |
| 355 | ) !void { |
| 271 | 356 | if (is_cfa) { |
| 272 | 357 | try writer.writeAll("CFA"); |
| 273 | 358 | } else { |
| ... | ... | @@ -281,48 +366,54 @@ pub const VirtualMachine = struct { |
| 281 | 366 | .offset => |offset| { |
| 282 | 367 | if (is_cfa) { |
| 283 | 368 | try abi.writeRegisterName(writer, arch, self.register); |
| 284 | | try writer.print("{}", .{ fmtOffset(offset) }); |
| 369 | try writer.print("{d:<1}", .{offset}); |
| 285 | 370 | } else { |
| 286 | | try writer.print("[CFA{}]", .{ fmtOffset(offset) }); |
| 371 | try writer.print("[CFA{d:<1}]", .{offset}); |
| 287 | 372 | } |
| 288 | 373 | }, |
| 289 | 374 | .val_offset => |offset| { |
| 290 | 375 | if (is_cfa) { |
| 291 | 376 | try abi.writeRegisterName(writer, arch, self.register); |
| 292 | | try writer.print("{}", .{ fmtOffset(offset) }); |
| 377 | try writer.print("{d:<1}", .{offset}); |
| 293 | 378 | } else { |
| 294 | | try writer.print("CFA{}", .{ fmtOffset(offset) }); |
| 379 | try writer.print("CFA{d:<1}", .{offset}); |
| 295 | 380 | } |
| 296 | 381 | }, |
| 297 | 382 | .register => |register| try abi.writeRegisterName(writer, arch, register), |
| 298 | | .expression => try writer.writeAll("TODO(expression)"), |
| 383 | .expression => |expression| try writeExpression(writer, expression, arch, addr_size_bytes, endian), |
| 299 | 384 | .val_expression => try writer.writeAll("TODO(val_expression)"), |
| 300 | 385 | .architectural => try writer.writeAll("TODO(architectural)"), |
| 301 | 386 | } |
| 302 | 387 | } |
| 303 | 388 | }; |
| 304 | 389 | |
| 390 | /// Each row contains unwinding rules for a set of registers at a specific location in the program. |
| 305 | 391 | pub const Row = struct { |
| 306 | 392 | /// Offset from pc_begin |
| 307 | 393 | offset: u64 = 0, |
| 394 | /// Special-case column that defines the CFA (Canonical Frame Address) rule. |
| 395 | /// The register field of this column defines the register that CFA is derived |
| 396 | /// from, while other columns define registers in terms of the CFA. |
| 308 | 397 | cfa: Column = .{}, |
| 309 | | /// Index into `columns` of the first column in this row |
| 398 | /// Index into `columns` of the first column in this row. |
| 310 | 399 | columns_start: usize = undefined, |
| 311 | 400 | columns_len: u8 = 0, |
| 312 | 401 | }; |
| 313 | 402 | |
| 314 | | rows: std.ArrayListUnmanaged(Row) = .{}, |
| 315 | 403 | columns: std.ArrayListUnmanaged(Column) = .{}, |
| 404 | row_stack: std.ArrayListUnmanaged(Row) = .{}, |
| 316 | 405 | current_row: Row = .{}, |
| 317 | 406 | |
| 407 | // TODO: Add stack machine stack |
| 408 | |
| 318 | 409 | pub fn reset(self: *VirtualMachine) void { |
| 319 | | self.rows.clearRetainingCapacity(); |
| 410 | self.row_stack.clearRetainingCapacity(); |
| 320 | 411 | self.columns.clearRetainingCapacity(); |
| 321 | 412 | self.current_row = .{}; |
| 322 | 413 | } |
| 323 | 414 | |
| 324 | 415 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { |
| 325 | | self.rows.deinit(allocator); |
| 416 | self.row_stack.deinit(allocator); |
| 326 | 417 | self.columns.deinit(allocator); |
| 327 | 418 | self.* = undefined; |
| 328 | 419 | } |
| ... | ... | @@ -366,8 +457,20 @@ pub const VirtualMachine = struct { |
| 366 | 457 | .undefined => {}, |
| 367 | 458 | .same_value => {}, |
| 368 | 459 | .register => {}, |
| 369 | | .remember_state => {}, |
| 370 | | .restore_state => {}, |
| 460 | .remember_state => { |
| 461 | |
| 462 | // TODO: The row stack only actually needs the column information |
| 463 | // TODO: Also it needs to copy the columns because changes can edit the referenced columns |
| 464 | // TODO: This function could push the column range onto the stack, the copy the columns and update current row |
| 465 | |
| 466 | try self.row_stack.append(allocator, self.current_row); |
| 467 | }, |
| 468 | .restore_state => { |
| 469 | if (self.row_stack.items.len == 0) return error.InvalidOperation; |
| 470 | const row = self.row_stack.pop(); |
| 471 | self.current_row.columns_len = row.columns_len; |
| 472 | self.current_row.columns_start = row.columns_start; |
| 473 | }, |
| 371 | 474 | .def_cfa => |i| { |
| 372 | 475 | self.current_row.cfa = .{ |
| 373 | 476 | .register = i.operands.register, |
| ... | ... | @@ -376,11 +479,14 @@ pub const VirtualMachine = struct { |
| 376 | 479 | }, |
| 377 | 480 | .def_cfa_register => {}, |
| 378 | 481 | .def_cfa_offset => |i| { |
| 482 | self.current_row.cfa.rule = .{ .offset = @intCast(i64, i.operands.offset) }; |
| 483 | }, |
| 484 | .def_cfa_expression => |i| { |
| 485 | self.current_row.cfa.register = undefined; |
| 379 | 486 | self.current_row.cfa.rule = .{ |
| 380 | | .offset = @intCast(i64, i.operands.offset) |
| 487 | .expression = i.operands.block, |
| 381 | 488 | }; |
| 382 | 489 | }, |
| 383 | | .def_cfa_expression => {}, |
| 384 | 490 | .expression => {}, |
| 385 | 491 | .offset_extended_sf => {}, |
| 386 | 492 | .def_cfa_sf => {}, |
| ... | ... | @@ -390,5 +496,4 @@ pub const VirtualMachine = struct { |
| 390 | 496 | .val_expression => {}, |
| 391 | 497 | } |
| 392 | 498 | } |
| 393 | | |
| 394 | 499 | }; |