qemu: Add QemuConfig to set qemu args via a struct (#2339)
* Add QemuConfig to set qemu args via a struct * Add derive macro to automate the qemu string args generation * fix tests
This commit is contained in:
parent
6979032ad9
commit
21051dc26f
@ -5,7 +5,7 @@ macro_rules! assert_unique_feature {
|
|||||||
() => {};
|
() => {};
|
||||||
($first:tt $(,$rest:tt)*) => {
|
($first:tt $(,$rest:tt)*) => {
|
||||||
$(
|
$(
|
||||||
#[cfg(all(not(doc), feature = $first, feature = $rest))]
|
#[cfg(all(not(any(doc, clippy)), feature = $first, feature = $rest))]
|
||||||
compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together"));
|
compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together"));
|
||||||
)*
|
)*
|
||||||
assert_unique_feature!($($rest),*);
|
assert_unique_feature!($($rest),*);
|
||||||
|
@ -36,8 +36,8 @@ use libafl_qemu::{
|
|||||||
modules::edges::{
|
modules::edges::{
|
||||||
edges_map_mut_ptr, EdgeCoverageModule, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND,
|
edges_map_mut_ptr, EdgeCoverageModule, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND,
|
||||||
},
|
},
|
||||||
Emulator, NopEmulatorExitHandler, QemuExitError, QemuExitReason, QemuRWError,
|
qemu_config, Emulator, NopEmulatorExitHandler, Qemu, QemuExitError, QemuExitReason,
|
||||||
QemuShutdownCause, Regs,
|
QemuRWError, QemuShutdownCause, Regs,
|
||||||
};
|
};
|
||||||
use libafl_qemu_sys::GuestPhysAddr;
|
use libafl_qemu_sys::GuestPhysAddr;
|
||||||
|
|
||||||
@ -87,23 +87,34 @@ pub fn fuzz() {
|
|||||||
println!("Breakpoint address = {breakpoint:#x}");
|
println!("Breakpoint address = {breakpoint:#x}");
|
||||||
|
|
||||||
let mut run_client = |state: Option<_>, mut mgr, _core_id| {
|
let mut run_client = |state: Option<_>, mut mgr, _core_id| {
|
||||||
|
let target_dir = env::var("TARGET_DIR").expect("TARGET_DIR env not set");
|
||||||
// Initialize QEMU
|
// Initialize QEMU
|
||||||
let args: Vec<String> = env::args().collect();
|
let qemu = Qemu::builder()
|
||||||
let env: Vec<(String, String)> = env::vars().collect();
|
.machine("mps2-an385")
|
||||||
|
.monitor(qemu_config::Monitor::Null)
|
||||||
|
.kernel(format!("{target_dir}/example.elf"))
|
||||||
|
.serial(qemu_config::Serial::Null)
|
||||||
|
.no_graphic(true)
|
||||||
|
.snapshot(true)
|
||||||
|
.drives([qemu_config::Drive::builder()
|
||||||
|
.interface(qemu_config::DriveInterface::None)
|
||||||
|
.format(qemu_config::DiskImageFileFormat::Qcow2)
|
||||||
|
.file(format!("{target_dir}/dummy.qcow2"))
|
||||||
|
.build()])
|
||||||
|
.start_cpu(false)
|
||||||
|
.build()
|
||||||
|
.expect("Failed to initialized QEMU");
|
||||||
|
|
||||||
let emulator_modules = tuple_list!(EdgeCoverageModule::default());
|
let emulator_modules = tuple_list!(EdgeCoverageModule::default());
|
||||||
|
|
||||||
let emulator = Emulator::new(
|
let mut emulator = Emulator::new_with_qemu(
|
||||||
args.as_slice(),
|
qemu,
|
||||||
env.as_slice(),
|
|
||||||
emulator_modules,
|
emulator_modules,
|
||||||
NopEmulatorExitHandler,
|
NopEmulatorExitHandler,
|
||||||
NopCommandManager,
|
NopCommandManager,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let qemu = emulator.qemu();
|
|
||||||
|
|
||||||
qemu.set_breakpoint(main_addr);
|
qemu.set_breakpoint(main_addr);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -23,3 +23,4 @@ proc-macro = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||||
quote = "1"
|
quote = "1"
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{parse_macro_input, Data::Struct, DeriveInput, Field, Fields::Named, Type};
|
||||||
|
|
||||||
/// Derive macro to implement `SerdeAny`, to use a type in a `SerdeAnyMap`
|
/// Derive macro to implement `SerdeAny`, to use a type in a `SerdeAnyMap`
|
||||||
#[proc_macro_derive(SerdeAny)]
|
#[proc_macro_derive(SerdeAny)]
|
||||||
@ -69,3 +69,89 @@ pub fn libafl_serdeany_derive(input: TokenStream) -> TokenStream {
|
|||||||
libafl_bolts::impl_serdeany!(#name);
|
libafl_bolts::impl_serdeany!(#name);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derive macro to implement `Display` for a struct where all fields implement `Display`.
|
||||||
|
/// The result is the space separated concatenation of all fields' display.
|
||||||
|
/// Order of declaration is preserved.
|
||||||
|
/// Specifically handled cases:
|
||||||
|
/// Options: Some => inner type display None => "".
|
||||||
|
/// Vec: inner type display space separated concatenation.
|
||||||
|
/// Generics and other more or less exotic stuff are not supported.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use libafl_derive;
|
||||||
|
///
|
||||||
|
/// #[derive(libafl_derive::Display)]
|
||||||
|
/// struct MyStruct {
|
||||||
|
/// foo: String,
|
||||||
|
/// bar: Option<u32>,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The above code will expand to:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// struct MyStruct {
|
||||||
|
/// foo: String,
|
||||||
|
/// bar: Option<u32>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl core::fmt::Display for MyStruct {
|
||||||
|
/// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
/// f.write_fmt(format_args!(" {0}", self.foo))?;
|
||||||
|
/// if let Some(opt) = &self.bar {
|
||||||
|
/// f.write_fmt(format_args!(" {0}", opt))?;
|
||||||
|
/// }
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_derive(Display)]
|
||||||
|
pub fn libafl_display(input: TokenStream) -> TokenStream {
|
||||||
|
let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
|
if let Struct(s) = data {
|
||||||
|
if let Named(fields) = s.fields {
|
||||||
|
let fields_fmt = fields.named.iter().map(libafl_display_field_by_type);
|
||||||
|
|
||||||
|
return quote! {
|
||||||
|
impl core::fmt::Display for #ident {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
#(#fields_fmt)*
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("Only structs are supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn libafl_display_field_by_type(it: &Field) -> proc_macro2::TokenStream {
|
||||||
|
let fmt = " {}";
|
||||||
|
let ident = &it.ident;
|
||||||
|
if let Type::Path(type_path) = &it.ty {
|
||||||
|
if type_path.qself.is_none() && type_path.path.segments.len() == 1 {
|
||||||
|
let segment = &type_path.path.segments[0];
|
||||||
|
if segment.ident == "Option" {
|
||||||
|
return quote! {
|
||||||
|
if let Some(opt) = &self.#ident {
|
||||||
|
write!(f, #fmt, opt)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if segment.ident == "Vec" {
|
||||||
|
return quote! {
|
||||||
|
for e in &self.#ident {
|
||||||
|
write!(f, #fmt, e)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quote! {
|
||||||
|
write!(f, #fmt, self.#ident)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -96,6 +96,7 @@ libafl_bolts = { path = "../libafl_bolts", version = "0.13.2", default-features
|
|||||||
] }
|
] }
|
||||||
libafl_targets = { path = "../libafl_targets", version = "0.13.2" }
|
libafl_targets = { path = "../libafl_targets", version = "0.13.2" }
|
||||||
libafl_qemu_sys = { path = "./libafl_qemu_sys", version = "0.13.2" }
|
libafl_qemu_sys = { path = "./libafl_qemu_sys", version = "0.13.2" }
|
||||||
|
libafl_derive = { path = "../libafl_derive", version = "0.13.2" }
|
||||||
|
|
||||||
serde = { version = "1.0", default-features = false, features = [
|
serde = { version = "1.0", default-features = false, features = [
|
||||||
"alloc",
|
"alloc",
|
||||||
@ -127,6 +128,7 @@ pyo3 = { version = "0.22", optional = true, features = ["multiple-pymethods"] }
|
|||||||
bytes-utils = "0.1"
|
bytes-utils = "0.1"
|
||||||
typed-builder = "0.19"
|
typed-builder = "0.19"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
|
getset = "0.1"
|
||||||
# Document all features of this crate (for `cargo doc`)
|
# Document all features of this crate (for `cargo doc`)
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ static LIBAFL_QEMU_RUNTIME_TEST: &str = r#"
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "libafl_qemu.h"
|
#include "libafl_qemu.h"
|
||||||
|
|
||||||
int main() {}
|
void __libafl_qemu_testfile() {}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
|
@ -3,7 +3,7 @@ use paste::paste;
|
|||||||
use crate::extern_c_checked;
|
use crate::extern_c_checked;
|
||||||
|
|
||||||
extern_c_checked! {
|
extern_c_checked! {
|
||||||
pub fn qemu_init(argc: i32, argv: *const *const u8, envp: *const *const u8);
|
pub fn qemu_init(argc: i32, argv: *const *const u8);
|
||||||
|
|
||||||
pub fn vm_start();
|
pub fn vm_start();
|
||||||
pub fn qemu_main_loop();
|
pub fn qemu_main_loop();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Low-level QEMU library
|
//! Low-level QEMU library
|
||||||
//!
|
//!
|
||||||
//! This module exposes the low-level QEMU library through [`Qemu`].
|
//! This module exposes the low-level QEMU library through [`Qemu`].
|
||||||
//! To access higher-level features of QEMU, it is recommanded to use [`crate::Emulator`] instead.
|
//! To access higher-level features of QEMU, it is recommended to use [`crate::Emulator`] instead.
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::{
|
use std::{
|
||||||
@ -33,6 +33,9 @@ use strum::IntoEnumIterator;
|
|||||||
|
|
||||||
use crate::{GuestAddrKind, GuestReg, Regs};
|
use crate::{GuestAddrKind, GuestReg, Regs};
|
||||||
|
|
||||||
|
pub mod qemu_config;
|
||||||
|
use qemu_config::{QemuConfig, QemuConfigBuilder, QEMU_CONFIG};
|
||||||
|
|
||||||
#[cfg(emulation_mode = "usermode")]
|
#[cfg(emulation_mode = "usermode")]
|
||||||
mod usermode;
|
mod usermode;
|
||||||
#[cfg(emulation_mode = "usermode")]
|
#[cfg(emulation_mode = "usermode")]
|
||||||
@ -519,6 +522,12 @@ impl From<u8> for HookData {
|
|||||||
|
|
||||||
#[allow(clippy::unused_self)]
|
#[allow(clippy::unused_self)]
|
||||||
impl Qemu {
|
impl Qemu {
|
||||||
|
/// For more details about the parameters check
|
||||||
|
/// [the QEMU documentation](https://www.qemu.org/docs/master/about/).
|
||||||
|
pub fn builder() -> QemuConfigBuilder {
|
||||||
|
QemuConfig::builder()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::must_use_candidate, clippy::similar_names)]
|
#[allow(clippy::must_use_candidate, clippy::similar_names)]
|
||||||
pub fn init(args: &[String], env: &[(String, String)]) -> Result<Self, QemuInitError> {
|
pub fn init(args: &[String], env: &[(String, String)]) -> Result<Self, QemuInitError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
@ -557,7 +566,7 @@ impl Qemu {
|
|||||||
qemu_user_init(argc, argv.as_ptr(), envp.as_ptr());
|
qemu_user_init(argc, argv.as_ptr(), envp.as_ptr());
|
||||||
#[cfg(emulation_mode = "systemmode")]
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
{
|
{
|
||||||
qemu_init(argc, argv.as_ptr(), envp.as_ptr());
|
qemu_init(argc, argv.as_ptr());
|
||||||
libc::atexit(qemu_cleanup_atexit);
|
libc::atexit(qemu_cleanup_atexit);
|
||||||
libafl_qemu_sys::syx_snapshot_init(true);
|
libafl_qemu_sys::syx_snapshot_init(true);
|
||||||
}
|
}
|
||||||
@ -595,6 +604,14 @@ impl Qemu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get QEMU configuration.
|
||||||
|
/// Returns `Some` only if QEMU was initialized with the builder.
|
||||||
|
/// Returns `None` if QEMU was initialized with `init` and raw string args.
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_config(&self) -> Option<&'static QemuConfig> {
|
||||||
|
QEMU_CONFIG.get()
|
||||||
|
}
|
||||||
|
|
||||||
/// This function will run the emulator until the next breakpoint / sync exit, or until finish.
|
/// This function will run the emulator until the next breakpoint / sync exit, or until finish.
|
||||||
/// It is a low-level function and simply kicks QEMU.
|
/// It is a low-level function and simply kicks QEMU.
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
406
libafl_qemu/src/qemu/qemu_config.rs
Normal file
406
libafl_qemu/src/qemu/qemu_config.rs
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
use core::{
|
||||||
|
fmt,
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::OnceLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
use getset::Getters;
|
||||||
|
use libafl_derive;
|
||||||
|
use strum_macros;
|
||||||
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
|
use crate::{Qemu, QemuInitError};
|
||||||
|
|
||||||
|
pub(super) static QEMU_CONFIG: OnceLock<QemuConfig> = OnceLock::new();
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
#[derive(Debug, strum_macros::Display, Clone)]
|
||||||
|
#[strum(prefix = "-accel ", serialize_all = "lowercase")]
|
||||||
|
pub enum Accelerator {
|
||||||
|
Kvm,
|
||||||
|
Tcg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, strum_macros::Display, Clone)]
|
||||||
|
#[strum(prefix = "if=", serialize_all = "lowercase")]
|
||||||
|
pub enum DriveInterface {
|
||||||
|
Floppy,
|
||||||
|
Ide,
|
||||||
|
Mtd,
|
||||||
|
None,
|
||||||
|
Pflash,
|
||||||
|
Scsi,
|
||||||
|
Sd,
|
||||||
|
Virtio,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, strum_macros::Display, Clone)]
|
||||||
|
#[strum(prefix = "format=", serialize_all = "lowercase")]
|
||||||
|
pub enum DiskImageFileFormat {
|
||||||
|
Qcow2,
|
||||||
|
Raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, TypedBuilder)]
|
||||||
|
pub struct Drive {
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
file: Option<PathBuf>,
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
|
format: Option<DiskImageFileFormat>,
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
|
interface: Option<DriveInterface>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Drive {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "-drive")?;
|
||||||
|
|
||||||
|
let mut is_first_option = true;
|
||||||
|
let mut separator = || {
|
||||||
|
if is_first_option {
|
||||||
|
is_first_option = false;
|
||||||
|
" "
|
||||||
|
} else {
|
||||||
|
","
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(file) = &self.file {
|
||||||
|
write!(f, "{}file={}", separator(), file.to_str().unwrap())?;
|
||||||
|
}
|
||||||
|
if let Some(format) = &self.format {
|
||||||
|
write!(f, "{}{format}", separator())?;
|
||||||
|
}
|
||||||
|
if let Some(interface) = &self.interface {
|
||||||
|
write!(f, "{}{interface}", separator())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, strum_macros::Display, Clone)]
|
||||||
|
#[strum(prefix = "-serial ", serialize_all = "lowercase")]
|
||||||
|
pub enum Serial {
|
||||||
|
None,
|
||||||
|
Null,
|
||||||
|
Stdio,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, strum_macros::Display, Clone)]
|
||||||
|
#[strum(prefix = "-monitor ", serialize_all = "lowercase")]
|
||||||
|
pub enum Monitor {
|
||||||
|
None,
|
||||||
|
Null,
|
||||||
|
Stdio,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the directory for the BIOS, VGA BIOS and keymaps.
|
||||||
|
/// Corresponds to the `-L` option of QEMU.
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Bios {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
impl Display for Bios {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "-L {}", self.path.to_str().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
impl<R: AsRef<Path>> From<R> for Bios {
|
||||||
|
fn from(path: R) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.as_ref().to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Kernel {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
impl Display for Kernel {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "-kernel {}", self.path.to_str().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
impl<R: AsRef<Path>> From<R> for Kernel {
|
||||||
|
fn from(path: R) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.as_ref().to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LoadVM {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LoadVM {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "-loadvm {}", self.path.to_str().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: AsRef<Path>> From<R> for LoadVM {
|
||||||
|
fn from(path: R) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.as_ref().to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Machine {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Machine {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "-machine {}", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: AsRef<str>> From<R> for Machine {
|
||||||
|
fn from(name: R) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.as_ref().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, strum_macros::Display)]
|
||||||
|
pub enum Snapshot {
|
||||||
|
#[strum(serialize = "-snapshot")]
|
||||||
|
ENABLE,
|
||||||
|
#[strum(serialize = "")]
|
||||||
|
DISABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Snapshot {
|
||||||
|
fn from(snapshot: bool) -> Self {
|
||||||
|
if snapshot {
|
||||||
|
Snapshot::ENABLE
|
||||||
|
} else {
|
||||||
|
Snapshot::DISABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When set to DISABLE, corresponds to the `-S` option of QEMU.
|
||||||
|
#[derive(Debug, Clone, strum_macros::Display)]
|
||||||
|
pub enum StartCPU {
|
||||||
|
#[strum(serialize = "")]
|
||||||
|
ENABLE,
|
||||||
|
#[strum(serialize = "-S")]
|
||||||
|
DISABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for StartCPU {
|
||||||
|
fn from(start_cpu: bool) -> Self {
|
||||||
|
if start_cpu {
|
||||||
|
StartCPU::ENABLE
|
||||||
|
} else {
|
||||||
|
StartCPU::DISABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, strum_macros::Display)]
|
||||||
|
pub enum NoGraphic {
|
||||||
|
#[strum(serialize = "-nographic")]
|
||||||
|
ENABLE,
|
||||||
|
#[strum(serialize = "")]
|
||||||
|
DISABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for NoGraphic {
|
||||||
|
fn from(no_graphic: bool) -> Self {
|
||||||
|
if no_graphic {
|
||||||
|
NoGraphic::ENABLE
|
||||||
|
} else {
|
||||||
|
NoGraphic::DISABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum RamSize {
|
||||||
|
MB(u32),
|
||||||
|
GB(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RamSize {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
RamSize::MB(mb) => write!(f, "-m {mb}M"),
|
||||||
|
RamSize::GB(gb) => write!(f, "-m {gb}G"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SmpCpus {
|
||||||
|
pub cpus: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SmpCpus {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "-smp {}", self.cpus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, strum_macros::Display)]
|
||||||
|
pub enum VgaPci {
|
||||||
|
#[strum(serialize = "-device VGA")]
|
||||||
|
ENABLE,
|
||||||
|
#[strum(serialize = "")]
|
||||||
|
DISABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for VgaPci {
|
||||||
|
fn from(vga_pci: bool) -> Self {
|
||||||
|
if vga_pci {
|
||||||
|
VgaPci::ENABLE
|
||||||
|
} else {
|
||||||
|
VgaPci::DISABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "usermode")]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Program {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "usermode")]
|
||||||
|
impl Display for Program {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.path.to_str().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "usermode")]
|
||||||
|
impl<R: AsRef<Path>> From<R> for Program {
|
||||||
|
fn from(path: R) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.as_ref().to_path_buf(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, libafl_derive::Display, TypedBuilder, Getters)]
|
||||||
|
#[builder(build_method(into = Result<Qemu, QemuInitError>), builder_method(vis = "pub(crate)",
|
||||||
|
doc = "Since Qemu is a zero sized struct, this is not a completely standard builder pattern. \
|
||||||
|
The Qemu configuration is not stored in the Qemu struct after build() but in QEMU_CONFIG \
|
||||||
|
Therefore, to use the derived builder and avoid boilerplate a builder for QemuConfig is \
|
||||||
|
derived. \
|
||||||
|
The QemuConfig::builder is called in Qemu::builder() which is the only place where it should \
|
||||||
|
be called, in this way the one to one matching of Qemu and QemuConfig is enforced. Therefore \
|
||||||
|
its visibility is pub(crate)"))]
|
||||||
|
#[getset(get = "pub")]
|
||||||
|
pub struct QemuConfig {
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
|
accelerator: Option<Accelerator>,
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
bios: Option<Bios>,
|
||||||
|
#[builder(default, setter(into))]
|
||||||
|
drives: Vec<Drive>,
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
kernel: Option<Kernel>,
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
load_vm: Option<LoadVM>,
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
machine: Option<Machine>,
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
|
monitor: Option<Monitor>,
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
no_graphic: Option<NoGraphic>,
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
|
ram_size: Option<RamSize>,
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
|
serial: Option<Serial>,
|
||||||
|
#[builder(default, setter(strip_option))]
|
||||||
|
smp_cpus: Option<SmpCpus>,
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
snapshot: Option<Snapshot>,
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
vga_pci: Option<VgaPci>,
|
||||||
|
#[builder(default, setter(strip_option, into))]
|
||||||
|
start_cpu: Option<StartCPU>,
|
||||||
|
#[cfg(emulation_mode = "usermode")]
|
||||||
|
#[builder(setter(into))]
|
||||||
|
program: Program,
|
||||||
|
} // Adding something here? Please leave Program as the last field
|
||||||
|
|
||||||
|
impl From<QemuConfig> for Result<Qemu, QemuInitError> {
|
||||||
|
/// This method is necessary to make the API resemble a typical builder pattern, i.e.
|
||||||
|
/// `Qemu::builder().foo(bar).build()`, while still leveraging `TypedBuilder` for this
|
||||||
|
/// non-standard use case where `Qemu` doesn't store the configuration.
|
||||||
|
/// Internally, `TypedBuilder` is used to generate a builder for `QemuConfig`.
|
||||||
|
/// This `QemuConfig.into()` method is used by the derived `QemuConfigBuilder.build()`
|
||||||
|
/// to go from `QemuConfigBuilder` to `QemuConfig`, and finally to `Qemu` in one fn.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// returns `QemuInitError` if the Qemu initialization fails, including cases where Qemu has
|
||||||
|
/// already been initialized.
|
||||||
|
fn from(config: QemuConfig) -> Self {
|
||||||
|
let args = config
|
||||||
|
.to_string()
|
||||||
|
.split(' ')
|
||||||
|
.map(std::string::ToString::to_string)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
let qemu = Qemu::init(&args, &[])?;
|
||||||
|
QEMU_CONFIG
|
||||||
|
.set(config)
|
||||||
|
.map_err(|_| unreachable!("BUG: QEMU_CONFIG was already set but Qemu was not init!"))?;
|
||||||
|
Ok(qemu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(emulation_mode = "usermode")]
|
||||||
|
fn usermode() {
|
||||||
|
let program = "/bin/pwd";
|
||||||
|
let qemu = Qemu::builder().program("/bin/pwd").build().unwrap();
|
||||||
|
let config = qemu.get_config().unwrap();
|
||||||
|
assert_eq!(config.to_string().trim(), program.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn drive_no_file_fmt() {
|
||||||
|
let drive = Drive::builder()
|
||||||
|
.format(DiskImageFileFormat::Raw)
|
||||||
|
.interface(DriveInterface::Ide)
|
||||||
|
.build();
|
||||||
|
assert_eq!(drive.to_string(), "-drive format=raw,if=ide");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
fn accelerator_kvm_to_string() {
|
||||||
|
let accel = Accelerator::Kvm;
|
||||||
|
assert_eq!(accel.to_string(), "-accel kvm");
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user