* Start on qemu_tmin

* WIP

* qemu_tmin working for single testcase.
Also some comment improvements.

* Add env_logger to baby_fuzzer

* Remove old reference.

* Added comment doc for qemu_tmin.

* Slight reorder for parallelisation

* Finished single-thread qemu_tmin

* Finishing touches to single thread version.

* A pre_commit.sh change I didn't notice.

* Duplicate to attempt multi-threaded version

* Fix taplo whine.
Mark "fork" mode as broken.

* Launcher for parallelisation implemented, but for one core.

* Running in parallel. Now need tidy up.

* Parallel version complete.

* Add comment

* Merged single-core/multi-core qemu_tmin into one crate

* Removed forkexecutor mode.

* Precommit fixes

* Add qemu_tmin to build_and_test.yml

* Clippy fixes

* Change tmin test cores to 0.
This commit is contained in:
forky2 2025-04-25 21:08:18 +00:00 committed by GitHub
parent 6bbff51951
commit c3475cd577
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 4736 additions and 76 deletions

View File

@ -384,6 +384,7 @@ jobs:
fuzzer:
# Binary only
- ./fuzzers/binary_only/qemu_cmin
- ./fuzzers/binary_only/qemu_tmin
- ./fuzzers/binary_only/qemu_coverage
- ./fuzzers/binary_only/qemu_launcher
arch:

View File

@ -23,6 +23,7 @@ opt-level = 3
debug = true
[dependencies]
env_logger = "0.11.7"
libafl = { path = "../../../libafl", features = ["tui_monitor"] }
libafl_bolts = { path = "../../../libafl_bolts" }
log = { version = "0.4.22", features = ["release_max_level_info"] }

View File

@ -35,6 +35,7 @@ fn signals_set(idx: usize) {
#[expect(clippy::manual_assert)]
pub fn main() {
env_logger::init();
// The closure that we want to fuzz
let mut harness = |input: &BytesInput| {
let target = input.target_bytes();

View File

@ -1,4 +1,4 @@
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
//! A binary-only corpus minimizer using qemu, similar to AFL++ afl-cmin
#[cfg(feature = "i386")]
use core::mem::size_of;
#[cfg(feature = "snapshot")]

View File

@ -1,4 +1,4 @@
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
//! A binary-only corpus minimizer using qemu, similar to AFL++ afl-cmin
#[cfg(target_os = "linux")]
mod fuzzer;

View File

@ -1,4 +1,4 @@
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
//! A qemu test case runner to generate drcov coverage outputs
#[cfg(feature = "i386")]
use core::mem::size_of;
use core::time::Duration;

View File

@ -1,4 +1,4 @@
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
//! A qemu test case runner to generate drcov coverage outputs
#[cfg(target_os = "linux")]
mod fuzzer;

View File

@ -0,0 +1,8 @@
libpng-*
libpng_harness
libpng_harness_crashing
zlib-*
crashes
target
output
corpus/

3598
fuzzers/binary_only/qemu_tmin/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
[package]
name = "qemu_tmin"
version = "0.15.1"
authors = [
"Andrea Fioraldi <andreafioraldi@gmail.com>",
"Dominik Maier <domenukk@gmail.com>",
"WorksButNotTested",
"forky2",
]
edition = "2021"
[profile.release]
#lto = true
#codegen-units = 1
#opt-level = 3
debug = true
[[bin]]
name = "tmin_single_core"
path = "src/tmin_single_core.rs"
[[bin]]
name = "tmin_multi_core"
path = "src/tmin_multi_core.rs"
[features]
default = ["std", "snapshot"]
fork = []
snapshot = []
std = []
be = ["libafl_qemu/be"]
arm = ["libafl_qemu/arm"]
x86_64 = ["libafl_qemu/x86_64"]
i386 = ["libafl_qemu/i386"]
aarch64 = ["libafl_qemu/aarch64"]
mips = ["libafl_qemu/mips"]
ppc = ["libafl_qemu/ppc", "be"]
[build-dependencies]
vergen = { version = "9.0.1", features = ["build", "cargo", "rustc", "si"] }
vergen-git2 = "1.0.1"
[dependencies]
clap = { version = "4.5.18", features = ["derive", "string"] }
env_logger = { version = "0.11.5" }
libafl = { path = "../../../libafl" }
libafl_bolts = { path = "../../../libafl_bolts" }
libafl_qemu = { path = "../../../libafl_qemu", features = ["usermode"] }
libafl_targets = { path = "../../../libafl_targets" }
log = { version = "0.4.22", features = ["release_max_level_info"] }
rangemap = { version = "1.5.1" }

View File

@ -0,0 +1,58 @@
import "../../../just/libafl-qemu-libpng.just"
FUZZER_NAME := ""
FUZZER_SINGLE := BUILD_DIR / "tmin_single_core" + FUZZER_EXTENSION
FUZZER_MULTI := BUILD_DIR / "tmin_multi_core" + FUZZER_EXTENSION
HARNESS := TARGET_DIR / ("libpng-harness-" + PROFILE)
[unix]
build:
cargo build \
--profile {{ PROFILE }} \
--features {{ ARCH }} \
--target-dir {{ TARGET_DIR }}
[unix]
harness: libpng
#!/bin/bash
source {{ DOTENV }}
$CROSS_CXX \
./harness.cc \
$CROSS_CFLAGS \
"{{TARGET_DIR}}/build-png/.libs/libpng16.a" \
"{{TARGET_DIR}}/build-zlib/libz.a" \
-I"{{TARGET_DIR}}/build-png" \
-I"{{TARGET_DIR}}/build-zlib/zlib/lib" \
-L"{{TARGET_DIR}}/build-zlib/zlib/lib" \
-o"{{ HARNESS }}" \
-lm -static
[unix]
run_single: harness build
{{ FUZZER_SINGLE }} \
--output ./output \
--input ./corpus \
--verbose \
-- {{ HARNESS }}
[unix]
run_multi: harness build
{{ FUZZER_MULTI }} \
--output ./output \
--input ./corpus \
--cores 0 \
--verbose \
-- {{ HARNESS }}
[unix]
test:
ARCH=x86_64 just run_single
ARCH=x86_64 just run_multi
ARCH=arm just run_single
ARCH=arm just run_multi
[unix]
clean:
cargo clean

View File

@ -0,0 +1,45 @@
# qemu_tmin
QEMU testcase minimizer.
This folder contains an example fuzzer which runs each entry in the input corpus
and minimizes the input, ensuring that coverage map remains the same. The output
is a new corpus that may or may not be smaller than the original inputs, but
will not be larger.
If some input files are idential, only one of each duplicate set will be kept
for minimization.
The following architectures are supported:
* arm
* aarch64
* i386
* x86_64
* mips
* ppc
## Prerequisites
```bash
sudo apt install \
gcc-arm-linux-gnueabi \
g++-arm-linux-gnueabi \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu \
gcc \
g++ \
gcc-mipsel-linux-gnu \
g++-mipsel-linux-gnu \
gcc-powerpc-linux-gnu \
g++-powerpc-linux-gnu
```
## Run
Defaults to `x86_64` architecture
```bash
just run
```
```bash
just <arch>
```

View File

@ -0,0 +1,59 @@
use vergen::{BuildBuilder, CargoBuilder, Emitter, RustcBuilder, SysinfoBuilder};
use vergen_git2::Git2Builder;
#[macro_export]
macro_rules! assert_unique_feature {
() => {};
($first:tt $(,$rest:tt)*) => {
$(
#[cfg(all(not(doc), feature = $first, feature = $rest))]
compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together"));
)*
assert_unique_feature!($($rest),*);
}
}
fn main() {
let build = BuildBuilder::all_build().unwrap();
let cargo = CargoBuilder::all_cargo().unwrap();
let git = Git2Builder::all_git().unwrap();
let rustc = RustcBuilder::all_rustc().unwrap();
let sysinfo = SysinfoBuilder::all_sysinfo().unwrap();
Emitter::default()
.add_instructions(&build)
.unwrap()
.add_instructions(&cargo)
.unwrap()
.add_instructions(&git)
.unwrap()
.add_instructions(&rustc)
.unwrap()
.add_instructions(&sysinfo)
.unwrap()
.emit()
.unwrap();
assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc");
let cpu_target = if cfg!(feature = "x86_64") {
"x86_64".to_string()
} else if cfg!(feature = "arm") {
"arm".to_string()
} else if cfg!(feature = "aarch64") {
"aarch64".to_string()
} else if cfg!(feature = "i386") {
"i386".to_string()
} else if cfg!(feature = "mips") {
"mips".to_string()
} else if cfg!(feature = "ppc") {
"ppc".to_string()
} else {
println!("cargo:warning=No architecture specified defaulting to x86_64...");
println!("cargo:rustc-cfg=feature=\"x86_64\"");
println!("cargo:rustc-cfg=feature=\"64bit\"");
"x86_64".to_string()
};
println!("cargo:rustc-env=CPU_TARGET={cpu_target}");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

View File

@ -0,0 +1,193 @@
// libpng_read_fuzzer.cc
// Copyright 2017-2018 Glenn Randers-Pehrson
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that may
// be found in the LICENSE file https://cs.chromium.org/chromium/src/LICENSE
// Last changed in libpng 1.6.35 [July 15, 2018]
// The modifications in 2017 by Glenn Randers-Pehrson include
// 1. addition of a PNG_CLEANUP macro,
// 2. setting the option to ignore ADLER32 checksums,
// 3. adding "#include <string.h>" which is needed on some platforms
// to provide memcpy().
// 4. adding read_end_info() and creating an end_info structure.
// 5. adding calls to png_set_*() transforms commonly used by browsers.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <vector>
#define PNG_INTERNAL
#include "png.h"
#define PNG_CLEANUP \
if (png_handler.png_ptr) { \
if (png_handler.row_ptr) \
png_free(png_handler.png_ptr, png_handler.row_ptr); \
if (png_handler.end_info_ptr) \
png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr, \
&png_handler.end_info_ptr); \
else if (png_handler.info_ptr) \
png_destroy_read_struct(&png_handler.png_ptr, &png_handler.info_ptr, \
nullptr); \
else \
png_destroy_read_struct(&png_handler.png_ptr, nullptr, nullptr); \
png_handler.png_ptr = nullptr; \
png_handler.row_ptr = nullptr; \
png_handler.info_ptr = nullptr; \
png_handler.end_info_ptr = nullptr; \
}
struct BufState {
const uint8_t *data;
size_t bytes_left;
};
struct PngObjectHandler {
png_infop info_ptr = nullptr;
png_structp png_ptr = nullptr;
png_infop end_info_ptr = nullptr;
png_voidp row_ptr = nullptr;
BufState *buf_state = nullptr;
~PngObjectHandler() {
if (row_ptr) { png_free(png_ptr, row_ptr); }
if (end_info_ptr)
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info_ptr);
else if (info_ptr)
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
else
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
delete buf_state;
}
};
void user_read_data(png_structp png_ptr, png_bytep data, size_t length) {
BufState *buf_state = static_cast<BufState *>(png_get_io_ptr(png_ptr));
if (length > buf_state->bytes_left) { png_error(png_ptr, "read error"); }
memcpy(data, buf_state->data, length);
buf_state->bytes_left -= length;
buf_state->data += length;
}
static const int kPngHeaderSize = 8;
// Entry point for LibFuzzer.
// Roughly follows the libpng book example:
// http://www.libpng.org/pub/png/book/chapter13.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < kPngHeaderSize) { return 0; }
std::vector<unsigned char> v(data, data + size);
if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) {
// not a PNG.
return 0;
}
PngObjectHandler png_handler;
png_handler.png_ptr = nullptr;
png_handler.row_ptr = nullptr;
png_handler.info_ptr = nullptr;
png_handler.end_info_ptr = nullptr;
png_handler.png_ptr =
png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_handler.png_ptr) { return 0; }
png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr);
if (!png_handler.info_ptr) {
PNG_CLEANUP
return 0;
}
png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr);
if (!png_handler.end_info_ptr) {
PNG_CLEANUP
return 0;
}
png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
#ifdef PNG_IGNORE_ADLER32
png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON);
#endif
// Setting up reading from buffer.
png_handler.buf_state = new BufState();
png_handler.buf_state->data = data + kPngHeaderSize;
png_handler.buf_state->bytes_left = size - kPngHeaderSize;
png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data);
png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize);
if (setjmp(png_jmpbuf(png_handler.png_ptr))) {
PNG_CLEANUP
return 0;
}
// Reading.
png_read_info(png_handler.png_ptr, png_handler.info_ptr);
// reset error handler to put png_deleter into scope.
if (setjmp(png_jmpbuf(png_handler.png_ptr))) {
PNG_CLEANUP
return 0;
}
png_uint_32 width, height;
int bit_depth, color_type, interlace_type, compression_type;
int filter_type;
if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width, &height,
&bit_depth, &color_type, &interlace_type, &compression_type,
&filter_type)) {
PNG_CLEANUP
return 0;
}
// This is going to be too slow.
if (width && height > 100000000 / width) {
PNG_CLEANUP
#ifdef HAS_DUMMY_CRASH
#if defined(__aarch64__) || defined(__arm__)
asm volatile(".word 0xf7f0a000\n");
#else
asm("ud2");
#endif
#endif
return 0;
}
// Set several transforms that browsers typically use:
png_set_gray_to_rgb(png_handler.png_ptr);
png_set_expand(png_handler.png_ptr);
png_set_packing(png_handler.png_ptr);
png_set_scale_16(png_handler.png_ptr);
png_set_tRNS_to_alpha(png_handler.png_ptr);
int passes = png_set_interlace_handling(png_handler.png_ptr);
png_read_update_info(png_handler.png_ptr, png_handler.info_ptr);
png_handler.row_ptr =
png_malloc(png_handler.png_ptr,
png_get_rowbytes(png_handler.png_ptr, png_handler.info_ptr));
for (int pass = 0; pass < passes; ++pass) {
for (png_uint_32 y = 0; y < height; ++y) {
png_read_row(png_handler.png_ptr,
static_cast<png_bytep>(png_handler.row_ptr), nullptr);
}
}
png_read_end(png_handler.png_ptr, png_handler.end_info_ptr);
PNG_CLEANUP
return 0;
}
int main() {
uint8_t buf[10] = {0};
LLVMFuzzerTestOneInput(buf, 10);
}

View File

@ -0,0 +1,372 @@
//! A binary-only testcase minimizer using qemu, similar to AFL++ afl-tmin
#[cfg(feature = "i386")]
use core::mem::size_of;
#[cfg(feature = "snapshot")]
use core::time::Duration;
use std::{env, fmt::Write, io, path::PathBuf, process, ptr::NonNull};
use clap::{builder::Str, Parser};
use libafl::{
corpus::{Corpus, CorpusId, HasCurrentCorpusId, InMemoryCorpus, InMemoryOnDiskCorpus},
events::{
launcher::Launcher, ClientDescription, EventConfig, LlmpRestartingEventManager, SendExiting,
},
executors::ExitKind,
feedbacks::MaxMapFeedback,
fuzzer::StdFuzzer,
inputs::{BytesInput, HasTargetBytes},
monitors::MultiMonitor,
mutators::{havoc_mutations, HavocScheduledMutator},
observers::{ConstMapObserver, HitcountsMapObserver},
schedulers::QueueScheduler,
stages::{ObserverEqualityFactory, StagesTuple, StdTMinMutationalStage},
state::{HasCorpus, StdState},
Error,
};
use libafl_bolts::{
core_affinity::Cores,
os::unix_signals::Signal,
rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list,
AsSlice, AsSliceMut,
};
use libafl_qemu::{
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
};
#[cfg(feature = "snapshot")]
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
use libafl_targets::{EDGES_MAP_DEFAULT_SIZE, EDGES_MAP_PTR};
#[cfg(feature = "fork")]
compile_error!("'fork' feature is currently not implemented; pending forkserver PR.");
#[cfg(all(feature = "fork", feature = "snapshot"))]
compile_error!("Cannot enable both 'fork' and 'snapshot' features at the same time.");
#[derive(Default)]
pub struct Version;
impl From<Version> for Str {
fn from(_: Version) -> Str {
let version = [
("Architecture:", env!("CPU_TARGET")),
("Build Timestamp:", env!("VERGEN_BUILD_TIMESTAMP")),
("Describe:", env!("VERGEN_GIT_DESCRIBE")),
("Commit SHA:", env!("VERGEN_GIT_SHA")),
("Commit Date:", env!("VERGEN_RUSTC_COMMIT_DATE")),
("Commit Branch:", env!("VERGEN_GIT_BRANCH")),
("Rustc Version:", env!("VERGEN_RUSTC_SEMVER")),
("Rustc Channel:", env!("VERGEN_RUSTC_CHANNEL")),
("Rustc Host Triple:", env!("VERGEN_RUSTC_HOST_TRIPLE")),
("Rustc Commit SHA:", env!("VERGEN_RUSTC_COMMIT_HASH")),
("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")),
]
.iter()
.fold(String::new(), |mut output, (k, v)| {
let _ = writeln!(output, "{k:25}: {v}");
output
});
format!("\n{version:}").into()
}
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
#[command(
name = format ! ("qemu_tmin-{}", env ! ("CPU_TARGET")),
version = Version::default(),
about,
long_about = "Module for minimizing test cases using QEMU instrumentation"
)]
pub struct FuzzerOptions {
#[arg(long, help = "Output directory")]
output: String,
#[arg(long, help = "Input directory")]
input: String,
#[arg(long, help = "Timeout in seconds", default_value_t = 1_u64)]
timeout: u64,
#[arg(long = "port", help = "Broker port", default_value_t = 1337_u16)]
port: u16,
#[arg(long, help = "Cpu cores to use", default_value = "all", value_parser = Cores::from_cmdline)]
cores: Cores,
#[arg(
long,
help = "Number of iterations for minimization",
default_value_t = 1024_usize
)]
iterations: usize,
#[clap(short, long, help = "Enable output from the fuzzer clients")]
verbose: bool,
#[arg(last = true, help = "Arguments passed to the target")]
args: Vec<String>,
}
pub const MAX_INPUT_SIZE: usize = 1048576; // 1MB
pub fn fuzz() {
// Initialise env_logger
env_logger::init();
// Parse user options
let mut options = FuzzerOptions::parse();
let program = env::args().next().unwrap();
log::info!("Program: {program:}");
options.args.insert(0, program);
log::info!("ARGS: {:#?}", options.args);
// Get all of the files supplied from the input corpus
let corpus_dir = PathBuf::from(options.input);
let files = corpus_dir
.read_dir()
.expect("Failed to read corpus dir")
.map(|x| Ok(x?.path()))
.collect::<Result<Vec<PathBuf>, io::Error>>()
.expect("Failed to read dir entry");
// To run parallelised, we work out number of files to process per core.
let num_files = files.len();
let num_cores = options.cores.ids.len();
let files_per_core = (num_files as f64 / num_cores as f64).ceil() as usize;
// Create a shared memory region for sharing coverage map between fuzzer and target
// In snapshot mode, this is only required for the SimpleRestartingEventManager.
// However, fork mode requires it to share memory between parent and child,
// so we use it in both cases.
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
// Clear LD_LIBRARY_PATH
env::remove_var("LD_LIBRARY_PATH");
// The client closure is a 'fuzzing' process for a single core.
// Because it is used inside the Launcher (below) it is a fork()ed separate
// process from our main process here.
let mut run_client = |state: Option<_>,
mut mgr: LlmpRestartingEventManager<_, _, _, _, _>,
client_description: ClientDescription| {
let core_id = client_description.core_id();
let core_idx = options
.cores
.position(core_id)
.expect("Failed to get core index");
let files: Vec<PathBuf> = files
.iter()
.skip(files_per_core * core_idx)
.take(files_per_core)
.cloned()
.collect::<Vec<PathBuf>>();
if files.is_empty() {
mgr.send_exiting()?;
Err(Error::ShuttingDown)?
}
let mut edges_shmem = shmem_provider
.clone()
.new_shmem(EDGES_MAP_DEFAULT_SIZE)
.unwrap();
let edges = edges_shmem.as_slice_mut();
unsafe { EDGES_MAP_PTR = edges.as_mut_ptr() };
// We use a HitcountsMapObserver to observe the coverage map
let mut edges_observer = unsafe {
HitcountsMapObserver::new(ConstMapObserver::from_mut_ptr(
"edges",
NonNull::new(edges.as_mut_ptr())
.expect("The edge map pointer is null.")
.cast::<[u8; EDGES_MAP_DEFAULT_SIZE]>(),
))
};
// In either fork/snapshot mode, we link the observer to QEMU
#[cfg(feature = "snapshot")]
let modules = tuple_list!(
StdEdgeCoverageChildModule::builder()
.const_map_observer(edges_observer.as_mut())
.build()?,
SnapshotModule::new()
);
// Create our QEMU emulator
let emulator = Emulator::empty()
.qemu_parameters(options.args.clone())
.modules(modules)
.build()?;
let qemu = emulator.qemu();
// Use ELF tools to get the target function
let mut elf_buffer = Vec::new();
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap();
let test_one_input_ptr = elf
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
.expect("Symbol LLVMFuzzerTestOneInput not found");
log::info!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}");
// Run target until the target function, and store important registers.
// Set a breakpoint on target function return.
qemu.entry_break(test_one_input_ptr);
let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap();
let ret_addr: GuestAddr = qemu.read_return_address().unwrap();
let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap();
log::info!("Break at {pc:#x}");
log::info!("Return address = {ret_addr:#x}");
qemu.set_breakpoint(ret_addr);
// Map a private region for input buffer
let input_addr = qemu
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
.unwrap();
log::info!("Placing input at {input_addr:#x}");
// Rust harness: this closure copies an input buffer to our private region
// for target function input and updates registers to a single iteration
// before telling QEMU to resume execution.
#[cfg(feature = "snapshot")]
let mut harness =
|_emulator: &mut Emulator<_, _, _, _, _, _, _>, _state: &mut _, input: &BytesInput| {
let target = input.target_bytes();
let mut buf = target.as_slice();
let mut len = buf.len();
if len > MAX_INPUT_SIZE {
buf = &buf[0..MAX_INPUT_SIZE];
len = MAX_INPUT_SIZE;
}
let len = len as GuestReg;
unsafe {
qemu.write_mem(input_addr, buf).expect("qemu write failed.");
qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap();
qemu.write_reg(Regs::Sp, stack_ptr).unwrap();
qemu.write_return_address(ret_addr).unwrap();
qemu.write_function_argument(0, input_addr).unwrap();
qemu.write_function_argument(1, len).unwrap();
match qemu.run() {
Ok(QemuExitReason::Breakpoint(_)) => {}
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(
Signal::SigInterrupt,
))) => process::exit(0),
Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash,
_ => panic!("Unexpected QEMU exit."),
}
}
ExitKind::Ok
};
// Our fuzzer is a simple queue scheduler (FIFO), and has no corpus feedback
// or objective feedback. This is important as we need the MaxMapFeedback
// on the observer to be constrained by ObserverEqualityFactory which will
// ensure interestingness is only true for identical coverage.
let scheduler = QueueScheduler::new();
let mut fuzzer = StdFuzzer::new(scheduler, (), ());
// We define the stages that will be performed by the fuzzer. We have one
// stage of the StdTMinMutationalStage which will run for n iterations.
// The havoc mutator will generate mutations; only those shorter than the
// current input will be tested; and the ObservreEqualityFactory will
// provide an observer that ensures additions to the corpus have the same
// coverage.
let minimizer = HavocScheduledMutator::new(havoc_mutations());
let factory = ObserverEqualityFactory::new(&edges_observer);
let mut stages = tuple_list!(StdTMinMutationalStage::new(
minimizer,
factory,
options.iterations
),);
// Create a state instance. Unlike a typical fuzzer, we start with an empty
// input corpus, and we don't care about 'solutions' so store in an
// InMemoryCorpus.
let mut feedback = MaxMapFeedback::new(&edges_observer);
let mut objective = ();
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryOnDiskCorpus::new(PathBuf::from(options.output.clone())).unwrap(),
InMemoryCorpus::new(),
&mut feedback,
&mut objective,
)
.unwrap()
});
// The executor. Nothing exciting here.
#[cfg(feature = "snapshot")]
let mut executor = QemuExecutor::new(
emulator,
&mut harness,
tuple_list!(edges_observer),
&mut fuzzer,
&mut state,
&mut mgr,
Duration::from_millis(5000),
)?;
// Load the input corpus
state.load_initial_inputs_by_filenames_forced(
&mut fuzzer,
&mut executor,
&mut mgr,
&files,
)?;
log::info!("Processed {} inputs from disk.", files.len());
// Iterate over initial corpus_ids and minimize each.
let corpus_ids: Vec<CorpusId> = state.corpus().ids().collect();
for corpus_id in corpus_ids {
state.set_corpus_id(corpus_id)?;
stages.perform_all(&mut fuzzer, &mut executor, &mut state, &mut mgr)?;
}
mgr.send_exiting()?;
Ok(())
};
// The Launcher creates forks on the specified list of cores, and for each
// one runs the run_client closure which performs the work.
// It links the forks to a MultiMonitor which just prints out the events
// it receives.
match Launcher::builder()
.shmem_provider(shmem_provider.clone())
.broker_port(options.port)
.configuration(EventConfig::from_build_id())
.monitor(MultiMonitor::new(|s| println!("{s}")))
.run_client(&mut run_client)
.cores(&options.cores)
.build()
.launch()
{
Ok(()) => (),
Err(Error::ShuttingDown) => {
println!("Run finished successfully.");
}
Err(err) => panic!("Failed to run launcher: {err:?}"),
}
// Here it would be nice to sum the number of cases processed by each fork
// to provide an exit summary (as is done for the "snapshot" version), but
// to do so would require more communication between child and parent. So
// it is left undone.
}
#[cfg(target_os = "linux")]
pub fn main() {
fuzz();
}
#[cfg(not(target_os = "linux"))]
pub fn main() {
panic!("qemu-user and libafl_qemu is only supported on linux!");
}

View File

@ -0,0 +1,319 @@
//! A binary-only testcase minimizer using qemu, similar to AFL++ afl-tmin
#[cfg(feature = "i386")]
use core::mem::size_of;
#[cfg(feature = "snapshot")]
use core::time::Duration;
use std::{env, fmt::Write, io, path::PathBuf, process, ptr::NonNull};
use clap::{builder::Str, Parser};
use libafl::{
corpus::{Corpus, CorpusId, HasCurrentCorpusId, InMemoryCorpus, InMemoryOnDiskCorpus},
events::{SendExiting, SimpleRestartingEventManager},
executors::ExitKind,
feedbacks::MaxMapFeedback,
fuzzer::StdFuzzer,
inputs::{BytesInput, HasTargetBytes},
monitors::SimpleMonitor,
mutators::{havoc_mutations, HavocScheduledMutator},
observers::{ConstMapObserver, HitcountsMapObserver},
schedulers::QueueScheduler,
stages::{ObserverEqualityFactory, StagesTuple, StdTMinMutationalStage},
state::{HasCorpus, StdState},
Error,
};
use libafl_bolts::{
os::unix_signals::Signal,
rands::StdRand,
shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list,
AsSlice, AsSliceMut,
};
use libafl_qemu::{
elf::EasyElf, modules::edges::StdEdgeCoverageChildModule, ArchExtras, Emulator, GuestAddr,
GuestReg, MmapPerms, QemuExitError, QemuExitReason, QemuShutdownCause, Regs,
};
#[cfg(feature = "snapshot")]
use libafl_qemu::{modules::SnapshotModule, QemuExecutor};
use libafl_targets::{EDGES_MAP_DEFAULT_SIZE, EDGES_MAP_PTR};
#[cfg(feature = "fork")]
compile_error!("'fork' feature is currently not implemented; pending forkserver PR.");
#[cfg(all(feature = "fork", feature = "snapshot"))]
compile_error!("Cannot enable both 'fork' and 'snapshot' features at the same time.");
#[derive(Default)]
pub struct Version;
impl From<Version> for Str {
fn from(_: Version) -> Str {
let version = [
("Architecture:", env!("CPU_TARGET")),
("Build Timestamp:", env!("VERGEN_BUILD_TIMESTAMP")),
("Describe:", env!("VERGEN_GIT_DESCRIBE")),
("Commit SHA:", env!("VERGEN_GIT_SHA")),
("Commit Date:", env!("VERGEN_RUSTC_COMMIT_DATE")),
("Commit Branch:", env!("VERGEN_GIT_BRANCH")),
("Rustc Version:", env!("VERGEN_RUSTC_SEMVER")),
("Rustc Channel:", env!("VERGEN_RUSTC_CHANNEL")),
("Rustc Host Triple:", env!("VERGEN_RUSTC_HOST_TRIPLE")),
("Rustc Commit SHA:", env!("VERGEN_RUSTC_COMMIT_HASH")),
("Cargo Target Triple", env!("VERGEN_CARGO_TARGET_TRIPLE")),
]
.iter()
.fold(String::new(), |mut output, (k, v)| {
let _ = writeln!(output, "{k:25}: {v}");
output
});
format!("\n{version:}").into()
}
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
#[command(
name = format ! ("qemu_tmin-{}", env ! ("CPU_TARGET")),
version = Version::default(),
about,
long_about = "Module for minimizing test cases using QEMU instrumentation"
)]
pub struct FuzzerOptions {
#[arg(long, help = "Output directory")]
output: String,
#[arg(long, help = "Input directory")]
input: String,
#[arg(long, help = "Timeout in seconds", default_value_t = 1_u64)]
timeout: u64,
#[arg(
long,
help = "Number of iterations for minimization",
default_value_t = 1024_usize
)]
iterations: usize,
#[clap(short, long, help = "Enable output from the fuzzer clients")]
verbose: bool,
#[arg(last = true, help = "Arguments passed to the target")]
args: Vec<String>,
}
pub const MAX_INPUT_SIZE: usize = 1048576; // 1MB
pub fn fuzz() -> Result<(), Error> {
// Initialise env_logger
env_logger::init();
// Parse user options
let mut options = FuzzerOptions::parse();
let program = env::args().next().unwrap();
log::info!("Program: {program:}");
options.args.insert(0, program);
log::info!("ARGS: {:#?}", options.args);
// Get all of the files supplied from the input corpus
let corpus_dir = PathBuf::from(options.input);
let files = corpus_dir
.read_dir()
.expect("Failed to read corpus dir")
.map(|x| Ok(x?.path()))
.collect::<Result<Vec<PathBuf>, io::Error>>()
.expect("Failed to read dir entry");
// Clear LD_LIBRARY_PATH
env::remove_var("LD_LIBRARY_PATH");
// Create a shared memory region for sharing coverage map between fuzzer and target
// In snapshot mode, this is only required for the SimpleRestartingEventManager.
// However, fork mode requires it to share memory between parent and child,
// so we use it in both cases.
let mut shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
let mut edges_shmem = shmem_provider.new_shmem(EDGES_MAP_DEFAULT_SIZE).unwrap();
let edges = edges_shmem.as_slice_mut();
unsafe { EDGES_MAP_PTR = edges.as_mut_ptr() };
// We use a HitcountsMapObserver to observe the coverage map
let mut edges_observer = unsafe {
HitcountsMapObserver::new(ConstMapObserver::from_mut_ptr(
"edges",
NonNull::new(edges.as_mut_ptr())
.expect("The edge map pointer is null.")
.cast::<[u8; EDGES_MAP_DEFAULT_SIZE]>(),
))
};
#[cfg(feature = "snapshot")]
let modules = tuple_list!(
StdEdgeCoverageChildModule::builder()
.const_map_observer(edges_observer.as_mut())
.build()?,
SnapshotModule::new()
);
// Create our QEMU emulator
let emulator = Emulator::empty()
.qemu_parameters(options.args)
.modules(modules)
.build()?;
let qemu = emulator.qemu();
// Use ELF tools to get the target function
let mut elf_buffer = Vec::new();
let elf = EasyElf::from_file(qemu.binary_path(), &mut elf_buffer).unwrap();
let test_one_input_ptr = elf
.resolve_symbol("LLVMFuzzerTestOneInput", qemu.load_addr())
.expect("Symbol LLVMFuzzerTestOneInput not found");
log::info!("LLVMFuzzerTestOneInput @ {test_one_input_ptr:#x}");
// Run target until the target function, and store important registers.
// Set a breakpoint on target function return.
qemu.entry_break(test_one_input_ptr);
let stack_ptr: GuestAddr = qemu.read_reg(Regs::Sp).unwrap();
let ret_addr: GuestAddr = qemu.read_return_address().unwrap();
let pc: GuestReg = qemu.read_reg(Regs::Pc).unwrap();
log::info!("Break at {pc:#x}");
log::info!("Return address = {ret_addr:#x}");
qemu.set_breakpoint(ret_addr);
// Map a private region for input buffer
let input_addr = qemu
.map_private(0, MAX_INPUT_SIZE, MmapPerms::ReadWrite)
.unwrap();
log::info!("Placing input at {input_addr:#x}");
// Rust harness: this closure copies an input buffer to our private region
// for target function input and updates registers to a single iteration
// before telling QEMU to resume execution.
#[cfg(feature = "snapshot")]
let mut harness =
|_emulator: &mut Emulator<_, _, _, _, _, _, _>, _state: &mut _, input: &BytesInput| {
let target = input.target_bytes();
let mut buf = target.as_slice();
let mut len = buf.len();
if len > MAX_INPUT_SIZE {
buf = &buf[0..MAX_INPUT_SIZE];
len = MAX_INPUT_SIZE;
}
let len = len as GuestReg;
unsafe {
qemu.write_mem(input_addr, buf).expect("qemu write failed.");
qemu.write_reg(Regs::Pc, test_one_input_ptr).unwrap();
qemu.write_reg(Regs::Sp, stack_ptr).unwrap();
qemu.write_return_address(ret_addr).unwrap();
qemu.write_function_argument(0, input_addr).unwrap();
qemu.write_function_argument(1, len).unwrap();
match qemu.run() {
Ok(QemuExitReason::Breakpoint(_)) => {}
Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(
Signal::SigInterrupt,
))) => process::exit(0),
Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash,
_ => panic!("Unexpected QEMU exit."),
}
}
ExitKind::Ok
};
// Set up the most basic monitor possible.
let monitor = SimpleMonitor::with_user_monitor(|s| {
println!("{s}");
});
let (state, mut mgr) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider)
{
Ok(res) => res,
Err(err) => match err {
Error::ShuttingDown => {
return Ok(());
}
_ => {
panic!("Failed to setup the restarter: {err}");
}
},
};
// Our fuzzer is a simple queue scheduler (FIFO), and has no corpus feedback
// or objective feedback. This is important as we need the MaxMapFeedback
// on the observer to be constrained by ObserverEqualityFactory which will
// ensure interestingness is only true for identical coverage.
let scheduler = QueueScheduler::new();
let mut fuzzer = StdFuzzer::new(scheduler, (), ());
// We define the stages that will be performed by the fuzzer. We have one
// stage of the StdTMinMutationalStage which will run for n iterations.
// The havoc mutator will generate mutations; only those shorter than the
// current input will be tested; and the ObservreEqualityFactory will
// provide an observer that ensures additions to the corpus have the same
// coverage.
let minimizer = HavocScheduledMutator::new(havoc_mutations());
let factory = ObserverEqualityFactory::new(&edges_observer);
let mut stages = tuple_list!(StdTMinMutationalStage::new(
minimizer,
factory,
options.iterations
),);
// Create a state instance. Unlike a typical fuzzer, we start with an empty
// input corpus, and we don't care about 'solutions' so store in an
// InMemoryCorpus.
let mut feedback = MaxMapFeedback::new(&edges_observer);
let mut objective = ();
let mut state = state.unwrap_or_else(|| {
StdState::new(
StdRand::new(),
InMemoryOnDiskCorpus::new(PathBuf::from(options.output)).unwrap(),
InMemoryCorpus::new(),
&mut feedback,
&mut objective,
)
.unwrap()
});
// The executor. Nothing exciting here.
#[cfg(feature = "snapshot")]
let mut executor = QemuExecutor::new(
emulator,
&mut harness,
tuple_list!(edges_observer),
&mut fuzzer,
&mut state,
&mut mgr,
Duration::from_millis(5000),
)?;
// Load the input corpus
state.load_initial_inputs_by_filenames_forced(&mut fuzzer, &mut executor, &mut mgr, &files)?;
log::info!("Processed {} inputs from disk.", files.len());
// Iterate over initial corpus_ids and minimize each.
let corpus_ids: Vec<CorpusId> = state.corpus().ids().collect();
for corpus_id in corpus_ids {
state.set_corpus_id(corpus_id)?;
stages.perform_all(&mut fuzzer, &mut executor, &mut state, &mut mgr)?;
}
// We end up with equivalent output corpus, hopefully smaller than the
// input, but certainly no larger.
let size = state.corpus().count();
println!("Corpus size: {size}");
mgr.send_exiting()?;
Ok(())
}
#[cfg(target_os = "linux")]
pub fn main() {
fuzz().unwrap();
}
#[cfg(not(target_os = "linux"))]
pub fn main() {
panic!("qemu-user and libafl_qemu is only supported on linux!");
}

View File

@ -1,4 +1,4 @@
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
//! A libfuzzer-like fuzzer using Nyx for binary-only coverage
#[cfg(target_os = "linux")]
mod client;
#[cfg(target_os = "linux")]

View File

@ -1,4 +1,4 @@
//! A libfuzzer-like fuzzer using qemu for binary-only coverage
//! A binary-only systemmode fuzzer using qemu for binary-only coverage
#[cfg(all(target_os = "linux", feature = "low_level"))]
mod fuzzer_low_level;

View File

@ -574,7 +574,7 @@ where
Ok(()) => (),
Err(e) => log::error!("Failed to send tcp message {e:#?}"),
}
log::debug!("Asking he broker to be disconnected");
log::debug!("Asking the broker to be disconnected");
Ok(())
}
}

View File

@ -1,4 +1,4 @@
/* 1.87.0-nightly */
/* 1.88.0-nightly */
/* qemu git hash: 97bef506eed24ee8d0eda4a07c4419c55dae4acb */
/* automatically generated by rust-bindgen 0.71.1 */
@ -305,6 +305,7 @@ impl Default for __pthread_mutex_s {
pub struct __pthread_cond_s {
pub __wseq: __atomic_wide_counter,
pub __g1_start: __atomic_wide_counter,
pub __g_refs: [::std::os::raw::c_uint; 2usize],
pub __g_size: [::std::os::raw::c_uint; 2usize],
pub __g1_orig_size: ::std::os::raw::c_uint,
pub __wrefs: ::std::os::raw::c_uint,
@ -312,20 +313,22 @@ pub struct __pthread_cond_s {
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of __pthread_cond_s"][::std::mem::size_of::<__pthread_cond_s>() - 40usize];
["Size of __pthread_cond_s"][::std::mem::size_of::<__pthread_cond_s>() - 48usize];
["Alignment of __pthread_cond_s"][::std::mem::align_of::<__pthread_cond_s>() - 8usize];
["Offset of field: __pthread_cond_s::__wseq"]
[::std::mem::offset_of!(__pthread_cond_s, __wseq) - 0usize];
["Offset of field: __pthread_cond_s::__g1_start"]
[::std::mem::offset_of!(__pthread_cond_s, __g1_start) - 8usize];
["Offset of field: __pthread_cond_s::__g_refs"]
[::std::mem::offset_of!(__pthread_cond_s, __g_refs) - 16usize];
["Offset of field: __pthread_cond_s::__g_size"]
[::std::mem::offset_of!(__pthread_cond_s, __g_size) - 16usize];
[::std::mem::offset_of!(__pthread_cond_s, __g_size) - 24usize];
["Offset of field: __pthread_cond_s::__g1_orig_size"]
[::std::mem::offset_of!(__pthread_cond_s, __g1_orig_size) - 24usize];
[::std::mem::offset_of!(__pthread_cond_s, __g1_orig_size) - 32usize];
["Offset of field: __pthread_cond_s::__wrefs"]
[::std::mem::offset_of!(__pthread_cond_s, __wrefs) - 28usize];
[::std::mem::offset_of!(__pthread_cond_s, __wrefs) - 36usize];
["Offset of field: __pthread_cond_s::__g_signals"]
[::std::mem::offset_of!(__pthread_cond_s, __g_signals) - 32usize];
[::std::mem::offset_of!(__pthread_cond_s, __g_signals) - 40usize];
};
impl Default for __pthread_cond_s {
fn default() -> Self {
@ -340,9 +343,10 @@ impl ::std::fmt::Debug for __pthread_cond_s {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(
f,
"__pthread_cond_s {{ __wseq: {:?}, __g1_start: {:?}, __g_size: {:?}, __g1_orig_size: {:?}, __wrefs: {:?}, __g_signals: {:?} }}",
"__pthread_cond_s {{ __wseq: {:?}, __g1_start: {:?}, __g_refs: {:?}, __g_size: {:?}, __g1_orig_size: {:?}, __wrefs: {:?}, __g_signals: {:?} }}",
self.__wseq,
self.__g1_start,
self.__g_refs,
self.__g_size,
self.__g1_orig_size,
self.__wrefs,
@ -450,9 +454,7 @@ pub struct _IO_FILE {
pub _markers: *mut _IO_marker,
pub _chain: *mut _IO_FILE,
pub _fileno: ::std::os::raw::c_int,
pub _bitfield_align_1: [u32; 0],
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 3usize]>,
pub _short_backupbuf: [::std::os::raw::c_char; 1usize],
pub _flags2: ::std::os::raw::c_int,
pub _old_offset: __off_t,
pub _cur_column: ::std::os::raw::c_ushort,
pub _vtable_offset: ::std::os::raw::c_schar,
@ -463,7 +465,7 @@ pub struct _IO_FILE {
pub _wide_data: *mut _IO_wide_data,
pub _freeres_list: *mut _IO_FILE,
pub _freeres_buf: *mut ::std::os::raw::c_void,
pub _prevchain: *mut *mut _IO_FILE,
pub __pad5: usize,
pub _mode: ::std::os::raw::c_int,
pub _unused2: [::std::os::raw::c_char; 20usize],
}
@ -497,8 +499,7 @@ const _: () = {
["Offset of field: _IO_FILE::_markers"][::std::mem::offset_of!(_IO_FILE, _markers) - 96usize];
["Offset of field: _IO_FILE::_chain"][::std::mem::offset_of!(_IO_FILE, _chain) - 104usize];
["Offset of field: _IO_FILE::_fileno"][::std::mem::offset_of!(_IO_FILE, _fileno) - 112usize];
["Offset of field: _IO_FILE::_short_backupbuf"]
[::std::mem::offset_of!(_IO_FILE, _short_backupbuf) - 119usize];
["Offset of field: _IO_FILE::_flags2"][::std::mem::offset_of!(_IO_FILE, _flags2) - 116usize];
["Offset of field: _IO_FILE::_old_offset"]
[::std::mem::offset_of!(_IO_FILE, _old_offset) - 120usize];
["Offset of field: _IO_FILE::_cur_column"]
@ -516,8 +517,7 @@ const _: () = {
[::std::mem::offset_of!(_IO_FILE, _freeres_list) - 168usize];
["Offset of field: _IO_FILE::_freeres_buf"]
[::std::mem::offset_of!(_IO_FILE, _freeres_buf) - 176usize];
["Offset of field: _IO_FILE::_prevchain"]
[::std::mem::offset_of!(_IO_FILE, _prevchain) - 184usize];
["Offset of field: _IO_FILE::__pad5"][::std::mem::offset_of!(_IO_FILE, __pad5) - 184usize];
["Offset of field: _IO_FILE::_mode"][::std::mem::offset_of!(_IO_FILE, _mode) - 192usize];
["Offset of field: _IO_FILE::_unused2"][::std::mem::offset_of!(_IO_FILE, _unused2) - 196usize];
};
@ -530,50 +530,6 @@ impl Default for _IO_FILE {
}
}
}
impl _IO_FILE {
#[inline]
pub fn _flags2(&self) -> ::std::os::raw::c_int {
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 24u8) as u32) }
}
#[inline]
pub fn set__flags2(&mut self, val: ::std::os::raw::c_int) {
unsafe {
let val: u32 = ::std::mem::transmute(val);
self._bitfield_1.set(0usize, 24u8, val as u64)
}
}
#[inline]
pub unsafe fn _flags2_raw(this: *const Self) -> ::std::os::raw::c_int {
unsafe {
::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_get(
::std::ptr::addr_of!((*this)._bitfield_1),
0usize,
24u8,
) as u32)
}
}
#[inline]
pub unsafe fn set__flags2_raw(this: *mut Self, val: ::std::os::raw::c_int) {
unsafe {
let val: u32 = ::std::mem::transmute(val);
<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_set(
::std::ptr::addr_of_mut!((*this)._bitfield_1),
0usize,
24u8,
val as u64,
)
}
}
#[inline]
pub fn new_bitfield_1(_flags2: ::std::os::raw::c_int) -> __BindgenBitfieldUnit<[u8; 3usize]> {
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 3usize]> = Default::default();
__bindgen_bitfield_unit.set(0usize, 24u8, {
let _flags2: u32 = unsafe { ::std::mem::transmute(_flags2) };
_flags2 as u64
});
__bindgen_bitfield_unit
}
}
pub type __jmp_buf = [::std::os::raw::c_long; 8usize];
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]

View File

@ -661,19 +661,17 @@ impl Default for _IO_FILE {
impl _IO_FILE {
#[inline]
pub fn _flags2(&self) -> ::std::os::raw::c_int {
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 24u8) as u32) }
u32::cast_signed(self._bitfield_1.get(0usize, 24u8) as u32)
}
#[inline]
pub fn set__flags2(&mut self, val: ::std::os::raw::c_int) {
unsafe {
let val: u32 = ::std::mem::transmute(val);
let val: u32 = i32::cast_unsigned(val);
self._bitfield_1.set(0usize, 24u8, val as u64)
}
}
#[inline]
pub unsafe fn _flags2_raw(this: *const Self) -> ::std::os::raw::c_int {
unsafe {
::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_get(
u32::cast_signed(<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_get(
::std::ptr::addr_of!((*this)._bitfield_1),
0usize,
24u8,
@ -683,7 +681,7 @@ impl _IO_FILE {
#[inline]
pub unsafe fn set__flags2_raw(this: *mut Self, val: ::std::os::raw::c_int) {
unsafe {
let val: u32 = ::std::mem::transmute(val);
let val: u32 = i32::cast_unsigned(val);
<__BindgenBitfieldUnit<[u8; 3usize]>>::raw_set(
::std::ptr::addr_of_mut!((*this)._bitfield_1),
0usize,
@ -696,7 +694,7 @@ impl _IO_FILE {
pub fn new_bitfield_1(_flags2: ::std::os::raw::c_int) -> __BindgenBitfieldUnit<[u8; 3usize]> {
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 3usize]> = Default::default();
__bindgen_bitfield_unit.set(0usize, 24u8, {
let _flags2: u32 = unsafe { ::std::mem::transmute(_flags2) };
let _flags2: u32 = i32::cast_unsigned(_flags2);
_flags2 as u64
});
__bindgen_bitfield_unit

View File

@ -40,7 +40,7 @@ pub fn debug_print(emu: &Unicorn<()>, thumb_mode: bool) {
let pc = emu.pc_read().unwrap();
log::debug!("PC: {:X}", pc);
log::debug!("PC: {pc:X}");
let arch = emu.get_arch();
match arch {
Arch::ARM => {
@ -132,7 +132,7 @@ pub fn debug_print(emu: &Unicorn<()>, thumb_mode: bool) {
}
for i in insns.as_ref() {
log::debug!("{}", i);
log::debug!("{i}");
}
}
}