Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor - Store exception pc back in vm #529

Merged
merged 4 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl<'a, 'b, C: ContextObject> SingleThreadBase for Interpreter<'a, 'b, C> {
self.reg[i] = regs.r[i];
}
self.reg[ebpf::FRAME_PTR_REG] = regs.sp;
self.pc = regs.pc as usize;
self.reg[11] = regs.pc;
Ok(())
}

Expand Down Expand Up @@ -252,7 +252,7 @@ impl<'a, 'b, C: ContextObject> target::ext::base::single_register_access::Single
match reg_id {
BpfRegId::Gpr(i) => self.reg[i as usize] = r,
BpfRegId::Sp => self.reg[ebpf::FRAME_PTR_REG] = r,
BpfRegId::Pc => self.pc = r as usize,
BpfRegId::Pc => self.reg[11] = r,
BpfRegId::InstructionCountRemaining => (),
}
Ok(())
Expand Down
126 changes: 57 additions & 69 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::{
error::EbpfError,
vm::{Config, ContextObject, EbpfVm, ProgramResult},
};
use std::convert::TryInto;

/// Virtual memory operation helper.
macro_rules! translate_memory_access {
Expand All @@ -29,8 +28,7 @@ macro_rules! translate_memory_access {
) {
ProgramResult::Ok(v) => v,
ProgramResult::Err(err) => {
$self.vm.program_result = ProgramResult::Err(err);
return false;
throw_error!($self, err);
},
}
};
Expand All @@ -48,6 +46,7 @@ macro_rules! translate_memory_access {

macro_rules! throw_error {
($self:expr, $err:expr) => {{
$self.vm.registers[11] = $self.reg[11];
$self.vm.program_result = ProgramResult::Err($err);
return false;
}};
Expand All @@ -63,6 +62,20 @@ macro_rules! throw_error {
};
}

macro_rules! check_pc {
($self:expr, $next_pc:ident, $target_pc:expr) => {
if ($target_pc as usize)
.checked_mul(ebpf::INSN_SIZE)
.and_then(|offset| $self.program.get(offset..offset + ebpf::INSN_SIZE))
.is_some()
{
$next_pc = $target_pc;
} else {
throw_error!($self, EbpfError::CallOutsideTextSegment);
}
};
}

/// State of the interpreter during a debugging session
#[cfg(feature = "debugger")]
pub enum DebugState {
Expand All @@ -80,10 +93,8 @@ pub struct Interpreter<'a, 'b, C: ContextObject> {
pub(crate) program_vm_addr: u64,
pub(crate) due_insn_count: u64,

/// General purpose self.registers
pub reg: [u64; 11],
/// Program counter / instruction pointer
pub pc: usize,
/// General purpose registers and pc
pub reg: [u64; 12],

#[cfg(feature = "debugger")]
pub(crate) debug_state: DebugState,
Expand All @@ -105,32 +116,18 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
program,
program_vm_addr,
due_insn_count: 0,
reg: registers[0..11].try_into().unwrap(),
pc: registers[11] as usize,
reg: registers,
#[cfg(feature = "debugger")]
debug_state: DebugState::Continue,
#[cfg(feature = "debugger")]
breakpoints: Vec::new(),
}
}

fn check_pc(&mut self) -> bool {
if self
.pc
.checked_mul(ebpf::INSN_SIZE)
.and_then(|offset| self.program.get(offset..offset + ebpf::INSN_SIZE))
.is_some()
{
true
} else {
throw_error!(self, EbpfError::CallOutsideTextSegment);
}
}

/// Translate between the virtual machines' pc value and the pc value used by the debugger
#[cfg(feature = "debugger")]
pub fn get_dbg_pc(&self) -> u64 {
((self.pc * ebpf::INSN_SIZE) as u64) + self.executable.get_text_section_offset()
(self.reg[11] * ebpf::INSN_SIZE as u64) + self.executable.get_text_section_offset()
}

fn push_frame(&mut self, config: &Config) -> bool {
Expand All @@ -139,7 +136,7 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
&self.reg[ebpf::FIRST_SCRATCH_REG..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS],
);
frame.frame_pointer = self.reg[ebpf::FRAME_PTR_REG];
frame.target_pc = self.pc;
frame.target_pc = self.reg[11] + 1;

self.vm.call_depth += 1;
if self.vm.call_depth as usize == config.max_call_depth {
Expand All @@ -165,20 +162,16 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
let config = &self.executable.get_config();

self.due_insn_count += 1;
let pc = self.pc;
self.pc += 1;
if self.pc * ebpf::INSN_SIZE > self.program.len() {
let mut next_pc = self.reg[11] + 1;
if next_pc as usize * ebpf::INSN_SIZE > self.program.len() {
throw_error!(self, EbpfError::ExecutionOverrun);
}
let mut insn = ebpf::get_insn_unchecked(self.program, pc);
let mut insn = ebpf::get_insn_unchecked(self.program, self.reg[11] as usize);
let dst = insn.dst as usize;
let src = insn.src as usize;

if config.enable_instruction_tracing {
let mut state = [0u64; 12];
state[0..11].copy_from_slice(&self.reg);
state[11] = pc as u64;
self.vm.context_object_pointer.trace(state);
self.vm.context_object_pointer.trace(self.reg);
}

match insn.opc {
Expand All @@ -194,8 +187,9 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {

ebpf::LD_DW_IMM => {
ebpf::augment_lddw_unchecked(self.program, &mut insn);
self.pc += 1;
self.reg[dst] = insn.imm as u64;
self.reg[11] += 1;
next_pc += 1;
},

// BPF_LDX class
Expand Down Expand Up @@ -424,29 +418,29 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
},

// BPF_JMP class
ebpf::JA => { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JEQ_IMM => if self.reg[dst] == insn.imm as u64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JEQ_REG => if self.reg[dst] == self.reg[src] { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JGT_IMM => if self.reg[dst] > insn.imm as u64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JGT_REG => if self.reg[dst] > self.reg[src] { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JGE_IMM => if self.reg[dst] >= insn.imm as u64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JGE_REG => if self.reg[dst] >= self.reg[src] { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JLT_IMM => if self.reg[dst] < insn.imm as u64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JLT_REG => if self.reg[dst] < self.reg[src] { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JLE_IMM => if self.reg[dst] <= insn.imm as u64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JLE_REG => if self.reg[dst] <= self.reg[src] { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSET_IMM => if self.reg[dst] & insn.imm as u64 != 0 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSET_REG => if self.reg[dst] & self.reg[src] != 0 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JNE_IMM => if self.reg[dst] != insn.imm as u64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JNE_REG => if self.reg[dst] != self.reg[src] { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSGT_IMM => if (self.reg[dst] as i64) > insn.imm { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSGT_REG => if (self.reg[dst] as i64) > self.reg[src] as i64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSGE_IMM => if (self.reg[dst] as i64) >= insn.imm { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSGE_REG => if (self.reg[dst] as i64) >= self.reg[src] as i64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSLT_IMM => if (self.reg[dst] as i64) < insn.imm { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSLT_REG => if (self.reg[dst] as i64) < self.reg[src] as i64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSLE_IMM => if (self.reg[dst] as i64) <= insn.imm { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JSLE_REG => if (self.reg[dst] as i64) <= self.reg[src] as i64 { self.pc = (self.pc as isize + insn.off as isize) as usize; },
ebpf::JA => { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JEQ_IMM => if self.reg[dst] == insn.imm as u64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JEQ_REG => if self.reg[dst] == self.reg[src] { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JGT_IMM => if self.reg[dst] > insn.imm as u64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JGT_REG => if self.reg[dst] > self.reg[src] { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JGE_IMM => if self.reg[dst] >= insn.imm as u64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JGE_REG => if self.reg[dst] >= self.reg[src] { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JLT_IMM => if self.reg[dst] < insn.imm as u64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JLT_REG => if self.reg[dst] < self.reg[src] { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JLE_IMM => if self.reg[dst] <= insn.imm as u64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JLE_REG => if self.reg[dst] <= self.reg[src] { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSET_IMM => if self.reg[dst] & insn.imm as u64 != 0 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSET_REG => if self.reg[dst] & self.reg[src] != 0 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JNE_IMM => if self.reg[dst] != insn.imm as u64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JNE_REG => if self.reg[dst] != self.reg[src] { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSGT_IMM => if (self.reg[dst] as i64) > insn.imm { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSGT_REG => if (self.reg[dst] as i64) > self.reg[src] as i64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSGE_IMM => if (self.reg[dst] as i64) >= insn.imm { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSGE_REG => if (self.reg[dst] as i64) >= self.reg[src] as i64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSLT_IMM => if (self.reg[dst] as i64) < insn.imm { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSLT_REG => if (self.reg[dst] as i64) < self.reg[src] as i64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSLE_IMM => if (self.reg[dst] as i64) <= insn.imm { next_pc = (next_pc as i64 + insn.off as i64) as u64; },
ebpf::JSLE_REG => if (self.reg[dst] as i64) <= self.reg[src] as i64 { next_pc = (next_pc as i64 + insn.off as i64) as u64; },

ebpf::CALL_REG => {
let target_pc = if self.executable.get_sbpf_version().callx_uses_src_reg() {
Expand All @@ -460,12 +454,10 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
if target_pc < self.program_vm_addr {
throw_error!(self, EbpfError::CallOutsideTextSegment);
}
self.pc = (target_pc - self.program_vm_addr) as usize / ebpf::INSN_SIZE;
if !self.check_pc() {
return false;
}
if self.executable.get_sbpf_version().static_syscalls() && self.executable.get_function_registry().lookup_by_key(self.pc as u32).is_none() {
check_pc!(self, next_pc, (target_pc - self.program_vm_addr) / ebpf::INSN_SIZE as u64);
if self.executable.get_sbpf_version().static_syscalls() && self.executable.get_function_registry().lookup_by_key(next_pc as u32).is_none() {
self.due_insn_count += 1;
self.reg[11] = next_pc;
throw_error!(self, EbpfError::UnsupportedInstruction);
}
},
Expand Down Expand Up @@ -516,10 +508,7 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
if !self.push_frame(config) {
return false;
}
self.pc = target_pc;
if !self.check_pc() {
return false;
}
check_pc!(self, next_pc, target_pc as u64);
}
}

Expand All @@ -539,7 +528,6 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
// Return from BPF to BPF call
self.vm.call_depth -= 1;
let frame = &self.vm.call_frames[self.vm.call_depth as usize];
self.pc = frame.target_pc;
self.reg[ebpf::FRAME_PTR_REG] = frame.frame_pointer;
self.reg[ebpf::FIRST_SCRATCH_REG
..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS]
Expand All @@ -549,17 +537,17 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> {
config.stack_frame_size * if config.enable_stack_frame_gaps { 2 } else { 1 };
self.vm.stack_pointer -= stack_frame_size as u64;
}
if !self.check_pc() {
return false;
}
check_pc!(self, next_pc, frame.target_pc);
}
_ => throw_error!(self, EbpfError::UnsupportedInstruction),
}

if config.enable_instruction_meter && self.due_insn_count >= self.vm.previous_instruction_meter {
self.reg[11] += 1;
throw_error!(self, EbpfError::ExceededMaxInstructions);
}

self.reg[11] = next_pc;
true
}
}
7 changes: 5 additions & 2 deletions src/jit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,9 @@ enum RuntimeEnvironmentSlot {
PreviousInstructionMeter = 4,
StopwatchNumerator = 5,
StopwatchDenominator = 6,
ProgramResult = 7,
MemoryMapping = 15,
Registers = 7,
ProgramResult = 19,
MemoryMapping = 27,
}

/* Explaination of the Instruction Meter
Expand Down Expand Up @@ -1347,6 +1348,7 @@ impl<'a, C: ContextObject> JitCompiler<'a, C> {

// Epilogue for errors
self.set_anchor(ANCHOR_THROW_EXCEPTION_UNCHECKED);
self.emit_ins(X86Instruction::store(OperandSize::S64, R11, RBP, X86IndirectAccess::Offset(self.slot_on_environment_stack(RuntimeEnvironmentSlot::Registers) + 11 * std::mem::size_of::<u64>() as i32))); // registers[11] = pc;
self.emit_ins(X86Instruction::jump_immediate(self.relative_to_anchor(ANCHOR_EPILOGUE, 5)));

// Quit gracefully
Expand Down Expand Up @@ -1649,6 +1651,7 @@ mod tests {
check_slot!(env, previous_instruction_meter, PreviousInstructionMeter);
check_slot!(env, stopwatch_numerator, StopwatchNumerator);
check_slot!(env, stopwatch_denominator, StopwatchDenominator);
check_slot!(env, registers, Registers);
check_slot!(env, program_result, ProgramResult);
check_slot!(env, memory_mapping, MemoryMapping);
}
Expand Down
16 changes: 9 additions & 7 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ pub struct CallFrame {
/// The callers frame pointer
pub frame_pointer: u64,
/// The target_pc of the exit instruction which returns back to the caller
pub target_pc: usize,
pub target_pc: u64,
}

/// A virtual machine to run eBPF programs.
Expand Down Expand Up @@ -371,6 +371,8 @@ pub struct EbpfVm<'a, C: ContextObject> {
pub stopwatch_numerator: u64,
/// Number of times the stop watch was used
pub stopwatch_denominator: u64,
/// Registers inlined
pub registers: [u64; 12],
/// ProgramResult inlined
pub program_result: ProgramResult,
/// MemoryMapping inlined
Expand Down Expand Up @@ -410,6 +412,7 @@ impl<'a, C: ContextObject> EbpfVm<'a, C> {
previous_instruction_meter: 0,
stopwatch_numerator: 0,
stopwatch_denominator: 0,
registers: [0u64; 12],
program_result: ProgramResult::Ok(0),
memory_mapping,
call_frames: vec![CallFrame::default(); config.max_call_depth],
Expand All @@ -426,11 +429,10 @@ impl<'a, C: ContextObject> EbpfVm<'a, C> {
executable: &Executable<C>,
interpreted: bool,
) -> (u64, ProgramResult) {
let mut registers = [0u64; 12];
// R1 points to beginning of input memory, R10 to the stack of the first frame, R11 is the pc (hidden)
registers[1] = ebpf::MM_INPUT_START;
registers[ebpf::FRAME_PTR_REG] = self.stack_pointer;
registers[11] = executable.get_entrypoint_instruction_offset() as u64;
self.registers[1] = ebpf::MM_INPUT_START;
self.registers[ebpf::FRAME_PTR_REG] = self.stack_pointer;
self.registers[11] = executable.get_entrypoint_instruction_offset() as u64;
let config = executable.get_config();
let initial_insn_count = if config.enable_instruction_meter {
self.context_object_pointer.get_remaining()
Expand All @@ -442,7 +444,7 @@ impl<'a, C: ContextObject> EbpfVm<'a, C> {
let due_insn_count = if interpreted {
#[cfg(feature = "debugger")]
let debug_port = self.debug_port.clone();
let mut interpreter = Interpreter::new(self, executable, registers);
let mut interpreter = Interpreter::new(self, executable, self.registers);
#[cfg(feature = "debugger")]
if let Some(debug_port) = debug_port {
crate::debugger::execute(&mut interpreter, debug_port);
Expand All @@ -463,7 +465,7 @@ impl<'a, C: ContextObject> EbpfVm<'a, C> {
Err(error) => return (0, ProgramResult::Err(error)),
};
let instruction_meter_final =
compiled_program.invoke(config, self, registers).max(0) as u64;
compiled_program.invoke(config, self, self.registers).max(0) as u64;
self.context_object_pointer
.get_remaining()
.saturating_sub(instruction_meter_final)
Expand Down
Loading