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