use std::{any::Any, process::Stdio, sync::Arc, time::Duration};
use futures::Future;
use tokio::{io::AsyncWriteExt, process::Command};
use crate::{
error::Error,
helpers::Helper,
program::{
DummyProgramEventListener, GlobalEnv, PreemptionEnabled, ProgramLoader, ThreadEnv,
TimesliceConfig, Timeslicer,
},
};
pub struct TokioTimeslicer;
impl Timeslicer for TokioTimeslicer {
fn sleep(&self, duration: Duration) -> impl Future<Output = ()> {
tokio::time::sleep(duration)
}
fn yield_now(&self) -> impl Future<Output = ()> {
tokio::task::yield_now()
}
}
pub async fn compile_ebpf(src: Vec<u8>) -> anyhow::Result<Vec<u8>> {
let mut clang = Command::new("clang")
.arg("-target")
.arg("bpf")
.arg("-emit-llvm")
.arg("-c")
.arg("-x")
.arg("c")
.arg("-")
.arg("-o")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let mut clang_stdin = clang.stdin.take().unwrap();
tokio::spawn(async move {
let _ = clang_stdin.write_all(&src).await;
});
let mut llvm_link = Command::new("llvm-link")
.arg("-o")
.arg("-")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let mut clang_stdout = clang.stdout.take().unwrap();
let mut llvm_link_stdin = llvm_link.stdin.take().unwrap();
tokio::spawn(async move {
let _ = tokio::io::copy(&mut clang_stdout, &mut llvm_link_stdin).await;
});
let mut opt = Command::new("opt")
.arg("-O2")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let mut llvm_link_stdout = llvm_link.stdout.take().unwrap();
let mut opt_stdin = opt.stdin.take().unwrap();
tokio::spawn(async move {
let _ = tokio::io::copy(&mut llvm_link_stdout, &mut opt_stdin).await;
});
let mut llc = Command::new("llc")
.arg("-march=bpf")
.arg("-bpf-stack-size=4096")
.arg("-mcpu=v3")
.arg("-filetype=obj")
.arg("-o")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let mut opt_stdout = opt.stdout.take().unwrap();
let mut llc_stdin = llc.stdin.take().unwrap();
tokio::spawn(async move {
let _ = tokio::io::copy(&mut opt_stdout, &mut llc_stdin).await;
});
let mut llvm_objcopy = Command::new("llvm-objcopy")
.arg("--remove-section")
.arg(".text")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let mut llc_stdout = llc.stdout.take().unwrap();
let mut llvm_objcopy_stdin = llvm_objcopy.stdin.take().unwrap();
tokio::spawn(async move {
let _ = tokio::io::copy(&mut llc_stdout, &mut llvm_objcopy_stdin).await;
});
let (clang_out, llvm_link_out, opt_out, llc_out, output) = tokio::join!(
clang.wait(),
llvm_link.wait(),
opt.wait(),
llc.wait(),
llvm_objcopy.wait_with_output()
);
let output = output?;
let exit_status_list = [
clang_out?,
llvm_link_out?,
opt_out?,
llc_out?,
output.status,
];
if exit_status_list.iter().any(|x| !x.success()) {
anyhow::bail!("one or more commands failed");
}
Ok(output.stdout)
}
pub fn gt_env() -> (GlobalEnv, ThreadEnv) {
let g = unsafe { GlobalEnv::new() };
let t = g.init_thread(Duration::from_millis(10));
(g, t)
}
pub fn timeslice_config() -> TimesliceConfig {
TimesliceConfig {
max_run_time_before_throttle: Duration::from_secs(10),
max_run_time_before_yield: Duration::from_millis(5),
throttle_duration: Duration::from_millis(1),
}
}
pub struct RunOpts<'a, 'b> {
pub helpers: Vec<&'static [(&'static str, Helper)]>,
pub entrypoint: &'a str,
pub calldata: &'a [u8],
pub resources: &'a mut [&'b mut dyn Any],
pub allow_dynamic_regions: bool,
}
impl<'a, 'b> RunOpts<'a, 'b> {
pub fn simple(helpers: Vec<&'static [(&'static str, Helper)]>, entrypoint: &'a str) -> Self {
Self {
helpers,
entrypoint,
calldata: &[],
resources: &mut [],
allow_dynamic_regions: false,
}
}
}
pub async fn run_one_program(opts: RunOpts<'_, '_>, code: &str) -> Result<i64, Error> {
let (_, t_env) = gt_env();
let binary = compile_ebpf(code.as_bytes().to_vec()).await.unwrap();
let helpers = opts.helpers;
let require_static_regions = !opts.allow_dynamic_regions;
let prog = tokio::task::spawn_blocking(move || {
let loader = ProgramLoader::new(
&mut rand::thread_rng(),
Arc::new(DummyProgramEventListener),
&helpers,
)
.require_static_region_analysis(require_static_regions);
loader.load(&mut rand::thread_rng(), &binary)
})
.await
.unwrap()?;
let prog = prog.pin_to_current_thread(t_env);
prog
.run(
×lice_config(),
&TokioTimeslicer,
opts.entrypoint,
opts.resources,
opts.calldata,
&PreemptionEnabled::new(t_env),
)
.await
}
pub mod region_analysis {
use crate::region_analysis::{
self as analyzer, PointerSignature, RegKind, StackKind, REGION_DATA, REGION_STACK,
REGION_UNKNOWN,
};
pub const NUM_REGS: usize = 11;
pub const UNKNOWN: u8 = REGION_UNKNOWN;
pub const STACK: u8 = REGION_STACK;
pub const DATA: u8 = REGION_DATA;
#[derive(Clone, Copy, Debug)]
pub enum PointerTag {
Uninit,
CurrentStack(i32),
CurrentStackUnknown,
ForeignStack,
Data,
Scalar,
Unknown,
}
#[derive(Clone, Copy)]
pub struct FunctionSignature(PointerSignature);
pub struct SectionAnalysis {
pub hints: Vec<u8>,
pub unresolved: Vec<usize>,
}
pub struct FunctionAnalysis {
pub hints: Vec<u8>,
pub unresolved: Vec<usize>,
pub call_signatures: Vec<(usize, FunctionSignature)>,
}
pub fn entry_signature() -> FunctionSignature {
FunctionSignature(PointerSignature::entry())
}
pub fn signature_from_tags(tags: [PointerTag; NUM_REGS]) -> FunctionSignature {
let mut regs = [RegKind::Uninit; NUM_REGS];
for (index, tag) in tags.into_iter().enumerate() {
regs[index] = match tag {
PointerTag::Uninit => RegKind::Uninit,
PointerTag::CurrentStack(offset) => RegKind::Stack(StackKind::Current(Some(offset))),
PointerTag::CurrentStackUnknown => RegKind::Stack(StackKind::Current(None)),
PointerTag::ForeignStack => RegKind::Stack(StackKind::Foreign),
PointerTag::Data => RegKind::Data,
PointerTag::Scalar => RegKind::Scalar,
PointerTag::Unknown => RegKind::Unknown,
};
}
FunctionSignature(PointerSignature::from_regs_for_testing(regs))
}
pub fn analyze_section(code: &[u8], data_lo: u64, data_hi: u64) -> SectionAnalysis {
let result = analyzer::analyze(code, data_lo, data_hi);
SectionAnalysis {
hints: result.hints,
unresolved: result.unresolved,
}
}
pub fn analyze_function(
code: &[u8],
start_pc: usize,
end_pc: usize,
signature: FunctionSignature,
data_lo: u64,
data_hi: u64,
) -> FunctionAnalysis {
let result = analyzer::analyze_function(code, start_pc, end_pc, signature.0, data_lo, data_hi);
let mut call_signatures: Vec<_> = result
.call_signatures
.into_iter()
.map(|(pc, sig)| (pc, FunctionSignature(sig)))
.collect();
call_signatures.sort_by_key(|(pc, _)| *pc);
FunctionAnalysis {
hints: result.hints,
unresolved: result.unresolved,
call_signatures,
}
}
}