Add gdb_qemu utility (#1331)
This commit is contained in:
parent
c6062889d5
commit
97b3d3c7c7
6
utils/gdb_qemu/.cargo/config.toml
Normal file
6
utils/gdb_qemu/.cargo/config.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[build]
|
||||
target = "x86_64-unknown-linux-gnu"
|
||||
|
||||
[target.powerpc-unknown-linux-gnu]
|
||||
linker = "powerpc-linux-gnu-gcc"
|
||||
runner = "qemu-ppc -L /usr/powerpc-linux-gnu"
|
2
utils/gdb_qemu/.gitignore
vendored
Normal file
2
utils/gdb_qemu/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
gdb_qemu.log
|
6
utils/gdb_qemu/Cargo.toml
Normal file
6
utils/gdb_qemu/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"gdb_qemu",
|
||||
"demo",
|
||||
]
|
82
utils/gdb_qemu/Makefile.toml
Normal file
82
utils/gdb_qemu/Makefile.toml
Normal file
@ -0,0 +1,82 @@
|
||||
[config]
|
||||
default_to_workspace = false
|
||||
|
||||
[env]
|
||||
DEMO_TARGET="powerpc-unknown-linux-gnu"
|
||||
HOST_TARGET="x86_64-unknown-linux-gnu"
|
||||
PROFILE="dev"
|
||||
DEMO_DIR="${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${DEMO_TARGET}/debug"
|
||||
TARGET_DIR="${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${HOST_TARGET}/debug"
|
||||
|
||||
[env.release]
|
||||
PROFILE="release"
|
||||
DEMO_DIR="${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${DEMO_TARGET}/release"
|
||||
TARGET_DIR="${CARGO_MAKE_CRATE_TARGET_DIRECTORY}/${HOST_TARGET}/release"
|
||||
|
||||
[tasks.clean]
|
||||
command = "cargo"
|
||||
args = ["clean"]
|
||||
|
||||
[tasks.format]
|
||||
install_crate = "rustfmt"
|
||||
command = "cargo"
|
||||
args = ["fmt", "--", "--emit=files"]
|
||||
|
||||
[tasks.demo]
|
||||
dependencies = ["format", "clippy"]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"build",
|
||||
"-p", "demo",
|
||||
"--profile", "${PROFILE}",
|
||||
"--target", "powerpc-unknown-linux-gnu",
|
||||
]
|
||||
|
||||
[tasks.run_demo]
|
||||
dependencies = ["demo"]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"run",
|
||||
"-p", "demo",
|
||||
"--target", "powerpc-unknown-linux-gnu",
|
||||
]
|
||||
|
||||
[tasks.build]
|
||||
dependencies = ["format", "clippy"]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"build",
|
||||
"-p", "gdb_qemu",
|
||||
"--profile", "${PROFILE}",
|
||||
]
|
||||
|
||||
[tasks.run]
|
||||
command = "cargo"
|
||||
dependencies = [ "demo" ]
|
||||
args = [
|
||||
"run",
|
||||
"-p", "gdb_qemu",
|
||||
"--profile", "${PROFILE}",
|
||||
"--",
|
||||
"-p", "1234",
|
||||
"-L", "trace",
|
||||
"--",
|
||||
"qemu-ppc",
|
||||
"-L", "/usr/powerpc-linux-gnu",
|
||||
"-g", "1234",
|
||||
"${DEMO_DIR}/demo"
|
||||
]
|
||||
|
||||
[tasks.gdb]
|
||||
command = "gdb-multiarch"
|
||||
dependencies = ["demo", "build"]
|
||||
args = [
|
||||
"-ex", "set architecture powerpc:MPC8XX",
|
||||
"-ex", "set pagination off",
|
||||
"-ex", "set confirm off",
|
||||
"-ex", "file ${DEMO_DIR}/demo",
|
||||
"-ex", "target remote | ${TARGET_DIR}/gdb_qemu -p 1234 -L trace qemu-ppc -- -L /usr/powerpc-linux-gnu -g 1234 ${DEMO_DIR}/demo"
|
||||
]
|
||||
|
||||
[tasks.all]
|
||||
dependencies = ["demo", "build"]
|
69
utils/gdb_qemu/README.md
Normal file
69
utils/gdb_qemu/README.md
Normal file
@ -0,0 +1,69 @@
|
||||
# GDB-QEMU
|
||||
`gdb-qemu` is a launcher for running `qemu-user` within `gdb`.
|
||||
|
||||
# Test
|
||||
```
|
||||
rustup target add powerpc-unknown-linux-gnu
|
||||
$ cargo make gdb
|
||||
```
|
||||
|
||||
# Example
|
||||
```
|
||||
gdb-multiarch \
|
||||
-ex "set architecture powerpc:MPC8XX" \
|
||||
-ex "set pagination off" \
|
||||
-ex "set confirm off" \
|
||||
-ex "file demo" \
|
||||
-ex "target remote | gdb-qemu -p 1234 qemu-ppc -- -L /usr/powerpc-linux-gnu -g 1234 demo
|
||||
```
|
||||
|
||||
# About
|
||||
`qemu-gdb` does the following:
|
||||
* Creates two pipes for the target program to send its `stdout`, `stderr`.
|
||||
* Forks a child process and sets the `stdout` and `stderr` using `dup2`.
|
||||
* Exec's the target program (passing the provided arguments).
|
||||
* Connects to the specified TCP debug port on the target program.
|
||||
* Forwards data from `gdb-qemu`'s `stdin` and `stdout` to the TCP port.
|
||||
* Forwards data from the target program's `stdout` and `stderr` to `gdb-qemu`s `stderr`.
|
||||
* Optionally logs to the specified log file.
|
||||
* Optionally logs trace information of the data transferred by the message pumps.
|
||||
|
||||
# Usage
|
||||
```
|
||||
Tool launching qemu-user for debugging
|
||||
|
||||
Usage: gdb-qemu [OPTIONS] --port <PORT> <PROGRAM> [-- <ARGS>...]
|
||||
|
||||
Arguments:
|
||||
<PROGRAM>
|
||||
Name of the qemu-user binary to launch
|
||||
|
||||
[ARGS]...
|
||||
Arguments passed to the target
|
||||
|
||||
Options:
|
||||
-p, --port <PORT>
|
||||
Port
|
||||
|
||||
-t, --timeout <TIMEOUT>
|
||||
Timeout Ms
|
||||
|
||||
[default: 2000]
|
||||
|
||||
-l, --log-file <LOG_FILE>
|
||||
Log file (Requires --log-level)
|
||||
|
||||
[default: gdb_qemu.log]
|
||||
|
||||
-L, --log-level <LOG_LEVEL>
|
||||
Log level
|
||||
|
||||
[default: off]
|
||||
[possible values: off, error, warn, info, debug, trace]
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
11
utils/gdb_qemu/demo/Cargo.toml
Normal file
11
utils/gdb_qemu/demo/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "8.1.1", features = ["build", "cargo", "git", "gitcl", "rustc", "si"] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.71", default-features = false }
|
||||
clap = { version = "4.2.0", default-features = false, features = ["derive", "string", "std", "help"] }
|
13
utils/gdb_qemu/demo/build.rs
Normal file
13
utils/gdb_qemu/demo/build.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use {std::error::Error, vergen::EmitBuilder};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
EmitBuilder::builder()
|
||||
.all_build()
|
||||
.all_cargo()
|
||||
.all_git()
|
||||
.all_rustc()
|
||||
.all_sysinfo()
|
||||
.emit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
35
utils/gdb_qemu/demo/src/args.rs
Normal file
35
utils/gdb_qemu/demo/src/args.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use clap::{builder::Str, Parser};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl From<Version> for Str {
|
||||
fn from(_: Version) -> Str {
|
||||
let version = [
|
||||
("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()
|
||||
.map(|(k, v)| format!("{k:25}: {v}\n"))
|
||||
.collect::<String>();
|
||||
|
||||
format!("\n{version:}").into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
#[command(
|
||||
version = Version::default(),
|
||||
about = "gdb-qemu",
|
||||
long_about = "Tool launching qemu-user for debugging"
|
||||
)]
|
||||
pub struct Args;
|
30
utils/gdb_qemu/demo/src/main.rs
Normal file
30
utils/gdb_qemu/demo/src/main.rs
Normal file
@ -0,0 +1,30 @@
|
||||
mod args;
|
||||
|
||||
use {
|
||||
crate::args::Args,
|
||||
clap::Parser,
|
||||
std::{thread::sleep, time::Duration},
|
||||
};
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn run_test(num: usize) {
|
||||
println!("OUT - test: {num:}");
|
||||
if num & 1 == 0 {
|
||||
eprintln!("ERR - test: {num:}");
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn test(num: usize) {
|
||||
for i in 0..num {
|
||||
run_test(i);
|
||||
sleep(Duration::from_millis(250));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Hello demo!");
|
||||
let args = Args::parse();
|
||||
println!("Args: {args:#?}");
|
||||
test(10);
|
||||
}
|
16
utils/gdb_qemu/gdb_qemu/Cargo.toml
Normal file
16
utils/gdb_qemu/gdb_qemu/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "gdb_qemu"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "8.1.1", features = ["build", "cargo", "git", "gitcl", "rustc", "si"] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.71", default-features = false }
|
||||
clap = { version = "4.2.0", default-features = false, features = ["derive", "string", "std", "help", "derive", "error-context", "usage"] }
|
||||
libc = {version = "0.2.146", default-features = false }
|
||||
log = { version = "0.4.19", default-features = false }
|
||||
nix = { version = "0.26.2", default-features = false, features = ["signal", "fs"] }
|
||||
readonly = { version = "0.2.8", default-features = false }
|
||||
simplelog = { version = "0.12.1", default-features = false }
|
13
utils/gdb_qemu/gdb_qemu/build.rs
Normal file
13
utils/gdb_qemu/gdb_qemu/build.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use {std::error::Error, vergen::EmitBuilder};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
EmitBuilder::builder()
|
||||
.all_build()
|
||||
.all_cargo()
|
||||
.all_git()
|
||||
.all_rustc()
|
||||
.all_sysinfo()
|
||||
.emit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
24
utils/gdb_qemu/gdb_qemu/src/args/level.rs
Normal file
24
utils/gdb_qemu/gdb_qemu/src/args/level.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use {clap::ValueEnum, simplelog::LevelFilter};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, Copy)]
|
||||
pub enum Level {
|
||||
Off,
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
impl From<Level> for LevelFilter {
|
||||
fn from(level: Level) -> LevelFilter {
|
||||
match level {
|
||||
Level::Off => LevelFilter::Off,
|
||||
Level::Error => LevelFilter::Error,
|
||||
Level::Warn => LevelFilter::Warn,
|
||||
Level::Info => LevelFilter::Info,
|
||||
Level::Debug => LevelFilter::Debug,
|
||||
Level::Trace => LevelFilter::Trace,
|
||||
}
|
||||
}
|
||||
}
|
83
utils/gdb_qemu/gdb_qemu/src/args/mod.rs
Normal file
83
utils/gdb_qemu/gdb_qemu/src/args/mod.rs
Normal file
@ -0,0 +1,83 @@
|
||||
pub mod level;
|
||||
mod version;
|
||||
|
||||
use {
|
||||
crate::args::{level::Level, version::Version},
|
||||
clap::Parser,
|
||||
std::iter,
|
||||
};
|
||||
|
||||
pub trait ParentArgs {
|
||||
fn port(&self) -> u16;
|
||||
fn timeout(&self) -> u64;
|
||||
}
|
||||
|
||||
impl ParentArgs for Args {
|
||||
fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
fn timeout(&self) -> u64 {
|
||||
self.timeout
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ChildArgs {
|
||||
fn argv(&self) -> Vec<String>;
|
||||
}
|
||||
|
||||
impl ChildArgs for Args {
|
||||
fn argv(&self) -> Vec<String> {
|
||||
iter::once(&self.program)
|
||||
.chain(self.args.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LogArgs {
|
||||
fn log_file(&self) -> String;
|
||||
fn log_level(&self) -> Level;
|
||||
}
|
||||
|
||||
impl LogArgs for Args {
|
||||
fn log_file(&self) -> String {
|
||||
self.log_file.clone()
|
||||
}
|
||||
fn log_level(&self) -> Level {
|
||||
self.log_level
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
#[command(
|
||||
version = Version::default(),
|
||||
about = "gdb-qemu",
|
||||
long_about = "Tool launching qemu-user for debugging"
|
||||
)]
|
||||
#[readonly::make]
|
||||
pub struct Args {
|
||||
#[arg(short, long, help = "Port", value_parser = clap::value_parser!(u16).range(1..))]
|
||||
port: u16,
|
||||
|
||||
#[arg(short, long, help = "Timeout Ms", default_value_t = 2000)]
|
||||
timeout: u64,
|
||||
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Log file (Requires --log-level)",
|
||||
default_value = "gdb_qemu.log",
|
||||
requires = "log_level"
|
||||
)]
|
||||
log_file: String,
|
||||
|
||||
#[arg(short='L', long, help = "Log level", value_enum, default_value_t = Level::Off)]
|
||||
log_level: Level,
|
||||
|
||||
#[arg(help = "Name of the qemu-user binary to launch")]
|
||||
program: String,
|
||||
|
||||
#[arg(last = true, value_parser, value_delimiter = ' ', num_args = 1.., help = "Arguments passed to the target")]
|
||||
args: Vec<String>,
|
||||
}
|
26
utils/gdb_qemu/gdb_qemu/src/args/version.rs
Normal file
26
utils/gdb_qemu/gdb_qemu/src/args/version.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use clap::builder::Str;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl From<Version> for Str {
|
||||
fn from(_: Version) -> Str {
|
||||
let version = [
|
||||
("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()
|
||||
.map(|(k, v)| format!("{k:25}: {v}\n"))
|
||||
.collect::<String>();
|
||||
|
||||
format!("\n{version:}").into()
|
||||
}
|
||||
}
|
55
utils/gdb_qemu/gdb_qemu/src/child.rs
Normal file
55
utils/gdb_qemu/gdb_qemu/src/child.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use {
|
||||
crate::{args::ChildArgs, exit::Exit},
|
||||
anyhow::{anyhow, Result},
|
||||
nix::unistd::{dup2, execvp},
|
||||
std::ffi::CString,
|
||||
std::os::fd::{AsRawFd, RawFd},
|
||||
};
|
||||
|
||||
pub struct Child {
|
||||
argv: Vec<String>,
|
||||
fd1: RawFd,
|
||||
fd2: RawFd,
|
||||
}
|
||||
|
||||
impl Child {
|
||||
fn launch(&self) -> Result<()> {
|
||||
let cargs = self
|
||||
.argv
|
||||
.iter()
|
||||
.map(|x| CString::new(x.clone()).map_err(|e| anyhow!("Failed to read argument: {e:}")))
|
||||
.collect::<Result<Vec<CString>>>()?;
|
||||
|
||||
info!("cargs: {cargs:#?}");
|
||||
|
||||
execvp(&cargs[0], &cargs).map_err(|e| anyhow!("Failed to exceve: {e:}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn redirect(&self) -> Result<()> {
|
||||
let stdout = std::io::stdout();
|
||||
let stderr = std::io::stderr();
|
||||
dup2(self.fd1, stdout.as_raw_fd())
|
||||
.map_err(|e| anyhow!("Failed to redirect stdout: {e:}"))?;
|
||||
|
||||
dup2(self.fd2, stderr.as_raw_fd())
|
||||
.map_err(|e| anyhow!("Failed to redirect stderr: {e:}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<()> {
|
||||
Exit::die_on_parent_exit()?;
|
||||
self.redirect()?;
|
||||
self.launch()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new(args: &impl ChildArgs, fd1: RawFd, fd2: RawFd) -> Child {
|
||||
Child {
|
||||
argv: args.argv().to_vec(),
|
||||
fd1,
|
||||
fd2,
|
||||
}
|
||||
}
|
||||
}
|
5
utils/gdb_qemu/gdb_qemu/src/errno.rs
Normal file
5
utils/gdb_qemu/gdb_qemu/src/errno.rs
Normal file
@ -0,0 +1,5 @@
|
||||
use libc::__errno_location;
|
||||
|
||||
pub fn errno() -> i32 {
|
||||
unsafe { *__errno_location() }
|
||||
}
|
57
utils/gdb_qemu/gdb_qemu/src/exit.rs
Normal file
57
utils/gdb_qemu/gdb_qemu/src/exit.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use {
|
||||
crate::errno::errno,
|
||||
anyhow::{anyhow, Result},
|
||||
libc::{_exit, prctl, PR_SET_PDEATHSIG},
|
||||
nix::sys::signal::SIGKILL,
|
||||
nix::{
|
||||
sys::{
|
||||
signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGCHLD},
|
||||
wait::{waitpid, WaitStatus::Exited},
|
||||
},
|
||||
unistd::Pid,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Exit;
|
||||
|
||||
impl Exit {
|
||||
pub fn die_on_parent_exit() -> Result<()> {
|
||||
if unsafe { prctl(PR_SET_PDEATHSIG, SIGKILL) } != 0 {
|
||||
Err(anyhow!("Failed to prctl(PR_SET_PDEATHSIG): {}", errno()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn die_on_child_exit() -> Result<()> {
|
||||
let sig_action = SigAction::new(
|
||||
SigHandler::Handler(Self::handle_sigchld),
|
||||
SaFlags::empty(),
|
||||
SigSet::empty(),
|
||||
);
|
||||
|
||||
unsafe { sigaction(SIGCHLD, &sig_action) }
|
||||
.map_err(|e| anyhow!("Failed to sigaction: {e:}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigchld(sig: libc::c_int) {
|
||||
info!("handle_sigchld: {sig:}");
|
||||
let status = waitpid(Pid::from_raw(-1), None).expect("Failed to wait for child");
|
||||
match status {
|
||||
Exited(pid, exit) => {
|
||||
info!("Exited: {pid:}");
|
||||
unsafe { _exit(exit) };
|
||||
}
|
||||
_ => {
|
||||
panic!("Invalid exit status: {status:#?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_for_child() -> Result<()> {
|
||||
let status =
|
||||
waitpid(Pid::from_raw(-1), None).map_err(|e| anyhow!("Failed to waitpid: {e:}"))?;
|
||||
info!("STATUS: {status:#?}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
21
utils/gdb_qemu/gdb_qemu/src/logger.rs
Normal file
21
utils/gdb_qemu/gdb_qemu/src/logger.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use {
|
||||
crate::args::LogArgs,
|
||||
anyhow::{anyhow, Result},
|
||||
simplelog::{Config, LevelFilter, WriteLogger},
|
||||
std::fs::File,
|
||||
};
|
||||
|
||||
pub struct Logger;
|
||||
|
||||
impl Logger {
|
||||
pub fn init(args: &impl LogArgs) -> Result<()> {
|
||||
let filter: LevelFilter = args.log_level().into();
|
||||
if filter != LevelFilter::Off {
|
||||
let logfile = File::create(args.log_file())
|
||||
.map_err(|e| anyhow!("Failed to open log file: {e:}"))?;
|
||||
WriteLogger::init(filter, Config::default(), logfile)
|
||||
.map_err(|e| anyhow!("Failed to initalize logger: {e:}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
38
utils/gdb_qemu/gdb_qemu/src/main.rs
Normal file
38
utils/gdb_qemu/gdb_qemu/src/main.rs
Normal file
@ -0,0 +1,38 @@
|
||||
mod args;
|
||||
mod child;
|
||||
mod errno;
|
||||
mod exit;
|
||||
mod logger;
|
||||
mod parent;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate simplelog;
|
||||
|
||||
use {
|
||||
crate::{args::Args, child::Child, exit::Exit, logger::Logger, parent::Parent},
|
||||
anyhow::{anyhow, Result},
|
||||
clap::Parser,
|
||||
nix::unistd::{fork, pipe, ForkResult},
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
Logger::init(&args)?;
|
||||
|
||||
info!("Started gdb-qemu...");
|
||||
|
||||
info!("Args: {args:#?}");
|
||||
|
||||
Exit::die_on_child_exit()?;
|
||||
|
||||
let (a1, b1) = pipe().map_err(|e| anyhow!("Failed to create pipe #1: {e:}"))?;
|
||||
let (a2, b2) = pipe().map_err(|e| anyhow!("Failed to create pipe #2: {e:}"))?;
|
||||
|
||||
match unsafe { fork() } {
|
||||
Ok(ForkResult::Parent { child: _, .. }) => Parent::new(&args, a1, a2).run()?,
|
||||
Ok(ForkResult::Child) => Child::new(&args, b1, b2).run()?,
|
||||
Err(e) => Err(anyhow!("main: fork failed: {e:}"))?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
194
utils/gdb_qemu/gdb_qemu/src/parent.rs
Normal file
194
utils/gdb_qemu/gdb_qemu/src/parent.rs
Normal file
@ -0,0 +1,194 @@
|
||||
use {
|
||||
crate::{args::ParentArgs, exit::Exit},
|
||||
anyhow::{anyhow, Result},
|
||||
nix::unistd::read,
|
||||
std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
net::{SocketAddr, TcpStream},
|
||||
os::fd::RawFd,
|
||||
str::from_utf8,
|
||||
thread::spawn,
|
||||
time::{Duration, SystemTime},
|
||||
},
|
||||
};
|
||||
|
||||
enum Direction {
|
||||
GdbToTarget,
|
||||
TargetToGdb,
|
||||
}
|
||||
|
||||
impl fmt::Display for Direction {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Direction::GdbToTarget => write!(fmt, "GDB --> TGT"),
|
||||
Direction::TargetToGdb => write!(fmt, "GDB <-- TGT"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Channel {
|
||||
Stdout,
|
||||
StdErr,
|
||||
}
|
||||
|
||||
impl fmt::Display for Channel {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Channel::Stdout => write!(fmt, "[STDOUT]"),
|
||||
Channel::StdErr => write!(fmt, "[STDERR]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Parent {
|
||||
port: u16,
|
||||
timeout: u64,
|
||||
fd1: RawFd,
|
||||
fd2: RawFd,
|
||||
}
|
||||
|
||||
impl Parent {
|
||||
const BUFFER_SIZE: usize = 16 << 10;
|
||||
|
||||
pub fn new(args: &impl ParentArgs, fd1: RawFd, fd2: RawFd) -> Parent {
|
||||
Parent {
|
||||
port: args.port(),
|
||||
timeout: args.timeout(),
|
||||
fd1,
|
||||
fd2,
|
||||
}
|
||||
}
|
||||
|
||||
fn log_packets(direction: &Direction, buffer: &[u8]) -> Result<()> {
|
||||
for pkt in from_utf8(buffer)
|
||||
.map_err(|e| anyhow!("Failed to read buffer: {e:}"))?
|
||||
.split('$')
|
||||
.filter(|x| !x.is_empty())
|
||||
.filter(|x| x != &"+")
|
||||
{
|
||||
trace!("{direction:} - ${pkt:}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn log_io(channel: &Channel, buffer: &[u8]) -> Result<()> {
|
||||
for line in from_utf8(buffer)
|
||||
.map_err(|e| anyhow!("Failed to read buffer: {e:}"))?
|
||||
.lines()
|
||||
.filter(|x| !x.is_empty())
|
||||
{
|
||||
trace!("{channel:} - {line:}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pump(input: &mut impl Read, output: &mut impl Write, direction: Direction) -> Result<()> {
|
||||
let mut buffer = [0u8; Parent::BUFFER_SIZE];
|
||||
loop {
|
||||
let n = input
|
||||
.read(&mut buffer)
|
||||
.map_err(|e| anyhow!("Failed to read input: {e:}"))?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
Parent::log_packets(&direction, &buffer[..n])?;
|
||||
|
||||
output
|
||||
.write_all(&buffer[..n])
|
||||
.map_err(|e| anyhow!("Failed to write output: {e:}"))?;
|
||||
output
|
||||
.flush()
|
||||
.map_err(|e| anyhow!("Failed to flush output: {e:}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pumpfd(input: RawFd, output: &mut impl Write, channel: Channel) -> Result<()> {
|
||||
let mut buffer = [0u8; Parent::BUFFER_SIZE];
|
||||
loop {
|
||||
let n = read(input, &mut buffer).map_err(|e| anyhow!("Failed to read input: {e:}"))?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
Parent::log_io(&channel, &buffer[..n])?;
|
||||
|
||||
output
|
||||
.write_all(&buffer[..n])
|
||||
.map_err(|e| anyhow!("Failed to write output: {e:}"))?;
|
||||
output
|
||||
.flush()
|
||||
.map_err(|e| anyhow!("Failed to flush output: {e:}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connect(&self) -> Result<TcpStream> {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], self.port));
|
||||
let timeout = Duration::from_millis(self.timeout);
|
||||
|
||||
let now = SystemTime::now();
|
||||
|
||||
loop {
|
||||
let result = TcpStream::connect(addr);
|
||||
if let Ok(stream) = result {
|
||||
return Ok(stream);
|
||||
}
|
||||
|
||||
let elapsed = now
|
||||
.elapsed()
|
||||
.map_err(|e| anyhow!("Failed to measure elapsed time: {e:}"))?;
|
||||
|
||||
if elapsed > timeout {
|
||||
return result.map_err(|e| anyhow!("Failed to connect: {e:}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<()> {
|
||||
let stream = self.connect()?;
|
||||
info!("Connected to client: {stream:#?}");
|
||||
|
||||
let mut read_stream = stream
|
||||
.try_clone()
|
||||
.map_err(|e| anyhow!("Failed to clone read_stream: {e:}"))?;
|
||||
let mut stdout = std::io::stdout();
|
||||
let reader = spawn(move || {
|
||||
Self::pump(&mut read_stream, &mut stdout, Direction::TargetToGdb).unwrap()
|
||||
});
|
||||
|
||||
let mut stdin = std::io::stdin();
|
||||
let mut write_stream = stream
|
||||
.try_clone()
|
||||
.map_err(|e| anyhow!("Failed to clone write_stream: {e:}"))?;
|
||||
let writer = spawn(move || {
|
||||
Self::pump(&mut stdin, &mut write_stream, Direction::GdbToTarget).unwrap()
|
||||
});
|
||||
|
||||
let mut stderr1 = std::io::stderr();
|
||||
let fd1 = self.fd1;
|
||||
let stdout_pump = spawn(move || Self::pumpfd(fd1, &mut stderr1, Channel::Stdout).unwrap());
|
||||
|
||||
let mut stderr2 = std::io::stderr();
|
||||
let fd2 = self.fd2;
|
||||
let stderr_pump = spawn(move || Self::pumpfd(fd2, &mut stderr2, Channel::StdErr).unwrap());
|
||||
|
||||
reader
|
||||
.join()
|
||||
.map_err(|e| anyhow!("Failed to join reader: {e:#?}"))?;
|
||||
writer
|
||||
.join()
|
||||
.map_err(|e| anyhow!("Failed to join writer: {e:#?}"))?;
|
||||
stdout_pump
|
||||
.join()
|
||||
.map_err(|e| anyhow!("Failed to join stdout_pump: {e:#?}"))?;
|
||||
stderr_pump
|
||||
.join()
|
||||
.map_err(|e| anyhow!("Failed to join stderr_pump: {e:#?}"))?;
|
||||
|
||||
Exit::wait_for_child()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user