Verified Commit e0064288 authored by jD91mZM2's avatar jD91mZM2

Add gdb-redox binary

parent ed3e5367
......@@ -13,6 +13,10 @@ log = "0.4.8"
byteorder = "1.3.4"
# f80 = { git = "https://gitlab.redox-os.org/redox-os/f80.git", rev = "d8de286" }
[[bin]]
path = "./bin/gdb-redox.rs"
name = "gdb-redox"
[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2.71"
[target.'cfg(target_os = "redox")'.dependencies]
......
use structopt::StructOpt;
use std::{
path::Path,
process::Command,
};
#[derive(Debug, StructOpt)]
struct Opt {
/// The program that should be debugged
program: String,
/// The arguments of the program
args: Vec<String>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opt = Opt::from_args();
// We're not using randomness simply to avoid the dependency lol. This hacky
// solution is for sure not race-condition proof, and trust me, I know. In
// the future, gdb might be able to communicate using gdbserver using stdio.
// Or maybe, the "chan:" scheme can generate a random name for us that
// avoids all the overhead of syscalls. It doesn't really matter: Just know
// that this is temporary.
let mut counter = 0;
let mut path;
loop {
path = format!("chan:gdb-redox-{}", counter);
counter += 1;
if !Path::new(&path).exists() {
break;
}
}
let child_result = Command::new("gdb")
.args(&["-ex", &format!("target remote {}", path)])
.spawn();
let mut child = match child_result {
Ok(child) => child,
Err(err) => {
eprintln!("Failed to launch gdb, is it installed?");
return Err(err.into());
}
};
let res = gdbserver::main(gdbserver::Opt {
addr: path,
kind: String::from("unix"),
program: opt.program,
args: opt.args,
});
let _ = child.wait();
res
}
use std::{
borrow::Cow,
cmp::min,
convert::TryFrom,
io::{self, prelude::*, BufReader, BufWriter},
net::TcpListener,
os::unix::net::UnixListener,
};
use gdb_remote_protocol::{
Error, FileSystem, Handler, Id, LibcFS, MemoryRegion, ProcessType,
StopReason, ThreadId, VCont, VContFeature,
};
use log::debug;
use structopt::StructOpt;
mod os;
use os::{Os, Registers, Target};
#[allow(unused)]
const ERROR_PARSE_STRING: u8 = std::u8::MAX;
#[allow(unused)]
const ERROR_GET_PATH: u8 = std::u8::MAX - 1;
#[derive(Debug, StructOpt)]
pub struct Opt {
/// The address which to bind the server to
#[structopt(short = "a", long = "addr", default_value = "0.0.0.0:64126")]
pub addr: String,
/// The type of address specified
#[structopt(short = "t", long = "type", default_value = "tcp", possible_values = &["tcp", "unix", "stdio"])]
pub kind: String,
/// The program that should be debugged
pub program: String,
/// The arguments of the program
pub args: Vec<String>,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct App {
tracee: Os,
fs: LibcFS,
}
impl Handler for App {
fn attached(&self, _pid: Option<u64>) -> Result<ProcessType> {
Ok(ProcessType::Created)
}
fn halt_reason(&self) -> Result<StopReason> {
Ok(self.tracee.status())
}
fn read_general_registers(&self) -> Result<Vec<u8>> {
let regs = self.tracee.getregs()?;
let mut bytes = Vec::new();
regs.encode(&mut bytes);
Ok(bytes)
}
fn write_general_registers(&self, content: &[u8]) -> Result<()> {
let regs = Registers::decode(content);
self.tracee.setregs(&regs)?;
Ok(())
}
fn read_memory(&self, region: MemoryRegion) -> Result<Vec<u8>> {
let mut buf = vec![0; region.length as usize];
self.tracee.getmem(region.address as usize, &mut buf)?;
Ok(buf)
}
fn write_memory(&self, address: u64, bytes: &[u8]) -> Result<()> {
self.tracee.setmem(bytes, address as usize)?;
Ok(())
}
fn query_supported_features(&self) -> Vec<String> {
vec![
String::from("qXfer:features:read+"),
String::from("qXfer:exec-file:read+"),
]
}
fn query_supported_vcont(&self) -> Result<Cow<'static, [VContFeature]>> {
Ok(Cow::Borrowed(&[
VContFeature::Continue,
VContFeature::ContinueWithSignal,
VContFeature::Step,
VContFeature::StepWithSignal,
VContFeature::RangeStep,
]))
}
fn thread_list(&self, reset: bool) -> Result<Vec<ThreadId>> {
if reset {
let id = Id::Id(self.tracee.pid());
Ok(vec![
ThreadId { pid: id, tid: id },
])
} else {
Ok(Vec::new())
}
}
fn vcont(&self, actions: Vec<(VCont, Option<ThreadId>)>) -> Result<StopReason> {
for (cmd, id) in &actions {
let id = id.unwrap_or(ThreadId { pid: Id::All, tid: Id::All });
debug!("Continuing thread: {:?}", id);
debug!("Continuing PID: {:?}", self.tracee.pid());
match (id.pid, id.tid) {
(Id::Id(pid), _) if pid != self.tracee.pid() => continue,
(_, Id::Id(tid)) if tid != self.tracee.pid() => continue,
(_, _) => (),
}
match *cmd {
VCont::Continue => {
self.tracee.cont(None)?;
}
VCont::ContinueWithSignal(signal) => {
self.tracee.cont(Some(signal))?;
}
VCont::Step => {
self.tracee.step(None)?;
}
VCont::StepWithSignal(signal) => {
self.tracee.step(Some(signal))?;
}
VCont::RangeStep(ref range) => {
// std::ops::Range<T: Copy> should probably also be Copy, but it isn't.
self.tracee.resume(range.clone())?;
}
_ => return Err(Error::Unimplemented),
}
break;
}
Ok(self.tracee.status())
}
fn read_bytes(&self, object: String, annex: String, offset: u64, length: u64) -> Result<(Vec<u8>, bool)> {
let transfer_bytes = |source: &[u8]| -> Result<(Vec<u8>, bool)> {
let start = usize::try_from(offset).expect("usize < u64");
let end = start.saturating_add(usize::try_from(length).expect("usize < u64"));
if start >= source.len() {
return Ok((Vec::new(), true));
}
let slice = &source[start..min(end, source.len())];
Ok((Vec::from(slice), false))
};
match (&*object, &*annex) {
("features", "target.xml") => {
let target_xml = include_bytes!("../target-desc.xml");
transfer_bytes(&target_xml[..])
},
("exec-file", pid) => {
let pid = usize::from_str_radix(pid, 16).map_err(|_| Error::Error(ERROR_PARSE_STRING))?;
let path = self.tracee.path(pid)?;
transfer_bytes(&path[..])
},
_ => Err(Error::Unimplemented),
}
}
fn fs(&self) -> Result<&dyn FileSystem, ()> {
Ok(&self.fs)
}
}
pub fn main(mut opt: Opt) -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
opt.args.insert(0, opt.program.clone());
let stdin = io::stdin();
let stdout = io::stdout();
let (mut reader, mut writer): (Box<dyn Read>, Box<dyn Write>) = if opt.kind == "unix" {
let listener = UnixListener::bind(opt.addr)?;
let (writer, _addr) = listener.accept()?;
(
Box::new(BufReader::new(writer.try_clone()?)),
Box::new(BufWriter::new(writer)),
)
} else if opt.kind == "stdio" {
(
Box::new(stdin.lock()),
Box::new(BufWriter::new(stdout.lock())),
)
} else {
assert_eq!(opt.kind, "tcp");
let listener = TcpListener::bind(opt.addr)?;
let (writer, _addr) = listener.accept()?;
(
Box::new(BufReader::new(writer.try_clone()?)),
Box::new(BufWriter::new(writer)),
)
};
let tracee = Os::new(opt.program, opt.args)?;
gdb_remote_protocol::process_packets_from(&mut reader, &mut writer, App {
tracee,
fs: LibcFS::default(),
});
Ok(())
}
use std::{
borrow::Cow,
cmp::min,
convert::TryFrom,
io::{self, prelude::*, BufReader, BufWriter},
net::TcpListener,
os::unix::net::UnixListener,
};
use gdb_remote_protocol::{
Error, FileSystem, Handler, Id, LibcFS, MemoryRegion, ProcessType,
StopReason, ThreadId, VCont, VContFeature,
};
use log::debug;
use structopt::StructOpt;
mod os;
use os::{Os, Registers, Target};
#[allow(unused)]
const ERROR_PARSE_STRING: u8 = std::u8::MAX;
#[allow(unused)]
const ERROR_GET_PATH: u8 = std::u8::MAX - 1;
#[derive(Debug, StructOpt)]
struct Opt {
/// The address which to bind the server to
#[structopt(short = "a", long = "addr", default_value = "0.0.0.0:64126")]
addr: String,
/// The type of address specified
#[structopt(short = "t", long = "type", default_value = "tcp", possible_values = &["tcp", "unix", "stdio"])]
kind: String,
/// The program that should be debugged
program: String,
/// The arguments of the program
args: Vec<String>,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct App {
tracee: Os,
fs: LibcFS,
}
impl Handler for App {
fn attached(&self, _pid: Option<u64>) -> Result<ProcessType> {
Ok(ProcessType::Created)
}
fn halt_reason(&self) -> Result<StopReason> {
Ok(self.tracee.status())
}
fn read_general_registers(&self) -> Result<Vec<u8>> {
let regs = self.tracee.getregs()?;
let mut bytes = Vec::new();
regs.encode(&mut bytes);
Ok(bytes)
}
fn write_general_registers(&self, content: &[u8]) -> Result<()> {
let regs = Registers::decode(content);
self.tracee.setregs(&regs)?;
Ok(())
}
fn read_memory(&self, region: MemoryRegion) -> Result<Vec<u8>> {
let mut buf = vec![0; region.length as usize];
self.tracee.getmem(region.address as usize, &mut buf)?;
Ok(buf)
}
fn write_memory(&self, address: u64, bytes: &[u8]) -> Result<()> {
self.tracee.setmem(bytes, address as usize)?;
Ok(())
}
fn query_supported_features(&self) -> Vec<String> {
vec![
String::from("qXfer:features:read+"),
String::from("qXfer:exec-file:read+"),
]
}
fn query_supported_vcont(&self) -> Result<Cow<'static, [VContFeature]>> {
Ok(Cow::Borrowed(&[
VContFeature::Continue,
VContFeature::ContinueWithSignal,
VContFeature::Step,
VContFeature::StepWithSignal,
VContFeature::RangeStep,
]))
}
fn thread_list(&self, reset: bool) -> Result<Vec<ThreadId>> {
if reset {
let id = Id::Id(self.tracee.pid());
Ok(vec![
ThreadId { pid: id, tid: id },
])
} else {
Ok(Vec::new())
}
}
fn vcont(&self, actions: Vec<(VCont, Option<ThreadId>)>) -> Result<StopReason> {
for (cmd, id) in &actions {
let id = id.unwrap_or(ThreadId { pid: Id::All, tid: Id::All });
debug!("Continuing thread: {:?}", id);
debug!("Continuing PID: {:?}", self.tracee.pid());
match (id.pid, id.tid) {
(Id::Id(pid), _) if pid != self.tracee.pid() => continue,
(_, Id::Id(tid)) if tid != self.tracee.pid() => continue,
(_, _) => (),
}
match *cmd {
VCont::Continue => {
self.tracee.cont(None)?;
}
VCont::ContinueWithSignal(signal) => {
self.tracee.cont(Some(signal))?;
}
VCont::Step => {
self.tracee.step(None)?;
}
VCont::StepWithSignal(signal) => {
self.tracee.step(Some(signal))?;
}
VCont::RangeStep(ref range) => {
// std::ops::Range<T: Copy> should probably also be Copy, but it isn't.
self.tracee.resume(range.clone())?;
}
_ => return Err(Error::Unimplemented),
}
break;
}
Ok(self.tracee.status())
}
fn read_bytes(&self, object: String, annex: String, offset: u64, length: u64) -> Result<(Vec<u8>, bool)> {
let transfer_bytes = |source: &[u8]| -> Result<(Vec<u8>, bool)> {
let start = usize::try_from(offset).expect("usize < u64");
let end = start.saturating_add(usize::try_from(length).expect("usize < u64"));
if start >= source.len() {
return Ok((Vec::new(), true));
}
let slice = &source[start..min(end, source.len())];
Ok((Vec::from(slice), false))
};
match (&*object, &*annex) {
("features", "target.xml") => {
let target_xml = include_bytes!("../target-desc.xml");
transfer_bytes(&target_xml[..])
},
("exec-file", pid) => {
let pid = usize::from_str_radix(pid, 16).map_err(|_| Error::Error(ERROR_PARSE_STRING))?;
let path = self.tracee.path(pid)?;
transfer_bytes(&path[..])
},
_ => Err(Error::Unimplemented),
}
}
fn fs(&self) -> Result<&dyn FileSystem, ()> {
Ok(&self.fs)
}
}
use gdbserver::Opt;
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let mut opt = Opt::from_args();
opt.args.insert(0, opt.program.clone());
let stdin = io::stdin();
let stdout = io::stdout();
let (mut reader, mut writer): (Box<dyn Read>, Box<dyn Write>) = if opt.kind == "unix" {
let listener = UnixListener::bind(opt.addr)?;
let (writer, _addr) = listener.accept()?;
(
Box::new(BufReader::new(writer.try_clone()?)),
Box::new(BufWriter::new(writer)),
)
} else if opt.kind == "stdio" {
(
Box::new(stdin.lock()),
Box::new(BufWriter::new(stdout.lock())),
)
} else {
assert_eq!(opt.kind, "tcp");
let listener = TcpListener::bind(opt.addr)?;
let (writer, _addr) = listener.accept()?;
(
Box::new(BufReader::new(writer.try_clone()?)),
Box::new(BufWriter::new(writer)),
)
};
let tracee = Os::new(opt.program, opt.args)?;
gdb_remote_protocol::process_packets_from(&mut reader, &mut writer, App {
tracee,
fs: LibcFS::default(),
});
Ok(())
gdbserver::main(Opt::from_args())
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment