Evaluates instructions from instruction_bytes until target_addr is reached or all
instructions have been evaluated.
fn evalInstructions(
vm: *VirtualMachine,
gpa: Allocator,
cie: *const Unwind.CommonInformationEntry,
target_addr: u64,
instruction_bytes: []const u8,
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !void
fn evalInstructions(
vm: *VirtualMachine,
gpa: Allocator,
cie: *const Unwind.CommonInformationEntry,
target_addr: u64,
instruction_bytes: []const u8,
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !void {
var fr: std.Io.Reader = .fixed(instruction_bytes);
while (fr.seek < fr.buffer.len) {
switch (try Instruction.read(&fr, addr_size_bytes, endian)) {
.nop => {
// If there was one nop, there's a good chance we've reached the padding and so
// everything left is a nop, which is represented by a 0 byte.
if (std.mem.allEqual(u8, fr.buffered(), 0)) return;
},
.remember_state => {
try vm.stack.append(gpa, .{
.cfa = vm.current_row.cfa,
.columns = vm.current_row.columns,
});
const cols_len = vm.current_row.columns.len;
const copy_start = vm.columns.items.len;
assert(vm.current_row.columns.start == copy_start - cols_len);
try vm.columns.ensureUnusedCapacity(gpa, cols_len); // to prevent aliasing issues
vm.columns.appendSliceAssumeCapacity(vm.columns.items[copy_start - cols_len ..]);
vm.current_row.columns.start = copy_start;
},
.restore_state => {
const restored = vm.stack.pop() orelse return error.InvalidOperation;
vm.columns.shrinkRetainingCapacity(restored.columns.start + restored.columns.len);
vm.current_row.cfa = restored.cfa;
vm.current_row.columns = restored.columns;
},
.advance_loc => |delta| {
const new_addr = vm.current_row.offset + delta * cie.code_alignment_factor;
if (new_addr > target_addr) return;
vm.current_row.offset = new_addr;
},
.set_loc => |new_addr| {
if (new_addr <= vm.current_row.offset) return error.InvalidOperation;
if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported
// TODO: Check cie.segment_selector_size != 0 for DWARFV4
if (new_addr > target_addr) return;
vm.current_row.offset = new_addr;
},
.register => |reg| {
const column = try vm.getOrAddColumn(gpa, reg.index);
column.rule = switch (reg.rule) {
.restore => rule: {
const cie_row = &(vm.cie_row orelse return error.InvalidOperation);
for (vm.rowColumns(cie_row)) |cie_col| {
if (cie_col.register == reg.index) break :rule cie_col.rule;
}
break :rule .default;
},
.undefined => .undefined,
.same_value => .same_value,
.offset_uf => |off| .{ .offset = @as(i64, @intCast(off)) * cie.data_alignment_factor },
.offset_sf => |off| .{ .offset = off * cie.data_alignment_factor },
.val_offset_uf => |off| .{ .val_offset = @as(i64, @intCast(off)) * cie.data_alignment_factor },
.val_offset_sf => |off| .{ .val_offset = off * cie.data_alignment_factor },
.register => |callee_reg| .{ .register = callee_reg },
.expr => |len| .{ .expression = try takeExprBlock(&fr, len) },
.val_expr => |len| .{ .val_expression = try takeExprBlock(&fr, len) },
};
},
.def_cfa => |cfa| vm.current_row.cfa = .{
.reg_off = .{
.register = cfa.register,
// Unfortunately, LLVM emits negative CFI directives as their unsigned variants
// rather than the signed variants that DWARF has for exactly that purpose, hence
// `@bitCast` instead of `@intCast`.
.offset = @bitCast(cfa.offset),
},
},
.def_cfa_sf => |cfa| vm.current_row.cfa = .{ .reg_off = .{
.register = cfa.register,
.offset = cfa.offset_sf * cie.data_alignment_factor,
} },
.def_cfa_reg => |register| switch (vm.current_row.cfa) {
.none => {
// According to the DWARF specification, this is not valid, because this
// instruction can only be used to replace the register if the rule is already a
// `.reg_off`. However, this is emitted in practice by GNU toolchains for some
// targets, and so by convention is interpreted as equivalent to `.def_cfa` with
// an offset of 0.
vm.current_row.cfa = .{ .reg_off = .{
.register = register,
.offset = 0,
} };
},
.expression => return error.InvalidOperation,
.reg_off => |*ro| ro.register = register,
},
.def_cfa_offset => |offset| switch (vm.current_row.cfa) {
.none, .expression => return error.InvalidOperation,
// See the comment for `def_cfa` above.
.reg_off => |*ro| ro.offset = @bitCast(offset),
},
.def_cfa_offset_sf => |offset_sf| switch (vm.current_row.cfa) {
.none, .expression => return error.InvalidOperation,
.reg_off => |*ro| ro.offset = offset_sf * cie.data_alignment_factor,
},
.def_cfa_expr => |len| {
vm.current_row.cfa = .{ .expression = try takeExprBlock(&fr, len) };
},
}
}
}