use std::sync::Mutex;
use std::process;
#[cfg(target_os = "linux")]
use std::{fs, env};
#[cfg(target_os = "windows")]
use winapi::um::debugapi;
#[cfg(target_os = "windows")]
use std::{path, thread};
#[cfg(target_os = "windows")]
use std::time::Duration;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
compile_error!("debug-here: this crate currently only builds on linux, macos, and windows");
fn already_entered() -> bool {
lazy_static! {
static ref GUARD: Mutex<bool> = Mutex::new(false);
}
let mut entered = GUARD.lock().unwrap();
let ret = *entered;
*entered = true;
return ret;
}
#[cfg(not(target_os = "windows"))]
pub fn debug_here_unixy_impl(debugger: Option<&str>) {
if already_entered() {
return;
}
#[cfg(target_os = "linux")]
let sane_env = linux_check();
#[cfg(target_os = "macos")]
let sane_env = macos_check();
if let Err(e) = sane_env {
eprintln!("debug-here: {}", e);
return
}
#[cfg(target_os = "linux")]
let debugger = debugger.unwrap_or("rust-gdb");
#[cfg(target_os = "macos")]
let debugger = debugger.unwrap_or("rust-lldb");
if which::which(debugger).is_err() {
eprintln!("debug-here: can't find {} on your path. Bailing.", debugger);
return;
}
if which::which("debug-here-gdb-wrapper").is_err() {
eprintln!(r#"debug-here:
Can't find debug-here-gdb-wrapper on your path. To get it
you can run `cargo install debug-here-gdb-wrapper`
"#);
return;
}
let looping = true;
#[cfg(target_os = "linux")]
let launch_stat = linux_launch_term(debugger);
#[cfg(target_os = "macos")]
let launch_stat = macos_launch_term(debugger);
if let Err(e) = launch_stat {
eprintln!("debug-here: {}", e);
return;
}
while looping {}
}
#[cfg(target_os = "windows")]
pub fn debug_here_win_impl() {
if already_entered() {
return;
}
let jitdbg_exe = r#"c:\windows\system32\vsjitdebugger.exe"#;
if !path::Path::new(jitdbg_exe).exists() {
eprintln!("debug-here: could not find '{}'.", jitdbg_exe);
return;
}
let pid = process::id();
let mut cmd = process::Command::new(jitdbg_exe);
cmd.stdin(process::Stdio::null())
.stdout(process::Stdio::null())
.stderr(process::Stdio::null());
cmd.arg("-p").arg(pid.to_string());
if let Err(e) = cmd.spawn() {
eprintln!("debug-here: failed to launch '{}': {}", jitdbg_exe, e);
return;
}
while unsafe { debugapi::IsDebuggerPresent() } == 0 {
thread::sleep(Duration::from_millis(100));
}
unsafe { debugapi::DebugBreak(); }
}
#[cfg(not(target_os = "windows"))]
fn debugger_args(debugger: &str) -> Vec<String> {
if debugger == "rust-lldb" {
vec!["-p".to_string(),
process::id().to_string(),
"-o".to_string(),
"expression looping = 0".to_string(),
"-o".to_string(),
"finish".to_string()]
} else if debugger == "rust-gdb" {
vec!["-pid".to_string(),
process::id().to_string(),
"-ex".to_string(),
"set variable looping = 0".to_string(),
"-ex".to_string(),
"finish".to_string()]
} else {
panic!("unknown debugger: {}", debugger);
}
}
#[cfg(target_os = "linux")]
fn linux_check() -> Result<(), String> {
let the_kids_are_ok =
fs::read("/proc/sys/kernel/yama/ptrace_scope")
.map(|contents|
std::str::from_utf8(&contents[..1]).unwrap_or("1") == "0")
.unwrap_or(false);
if !the_kids_are_ok {
return Err(format!(r#"
ptrace_scope must be set to 0 for debug-here to work.
This will allow any process with a given uid to rummage around
in the memory of any other process with the same uid, so there
are some security risks. To set ptrace_scope for just this
session you can do:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
Giving up on debugging for now.
"#));
}
Ok(())
}
#[cfg(target_os = "linux")]
fn linux_launch_term(debugger: &str) -> Result<(), String> {
if debugger == "rust-gdb" {
env::set_var("RUST_DEBUG_HERE_LIFELINE",
format!("1,{}", process::id()));
} else {
env::set_var("RUST_DEBUG_HERE_LIFELINE",
format!("2,{},{}", process::id(), debugger));
}
let term = match which::which("alacritty").or(which::which("xterm")) {
Ok(t) => t,
Err(_) => {
return Err(format!(r#"
can't find alacritty or xterm on your path. Those are the
only terminal emulators currently supported on linux.
"#));
}
};
let term_cmd = term.clone();
let mut cmd = process::Command::new(term_cmd);
cmd.stdin(process::Stdio::null())
.stdout(process::Stdio::null())
.stderr(process::Stdio::null());
if term.ends_with("alacritty") {
cmd.arg("-e");
cmd.arg(debugger);
cmd.args(debugger_args(debugger));
} else {
cmd.arg("debug-here-gdb-wrapper");
}
match cmd.spawn() {
Ok(_) => Ok(()),
Err(e) => Err(
format!("failed to launch rust-gdb in {:?}: {}", term, e))
}
}
#[cfg(target_os = "macos")]
fn macos_check() -> Result<(), String> {
if which::which("osascript").is_err() {
return Err(format!("debug-here: can't find osascript. Bailing."));
}
Ok(())
}
#[cfg(target_os = "macos")]
fn macos_launch_term(debugger: &str) -> Result<(), String> {
let launch_script =
format!(r#"tell app "Terminal"
do script "exec {} {}"
end tell"#, debugger,
debugger_args(debugger).into_iter()
.map(|a| if a.contains(" ") { format!("'{}'", a) } else { a } )
.collect::<Vec<_>>().join(" "));
let mut cmd = process::Command::new("osascript");
cmd.arg("-e")
.arg(launch_script)
.stdin(process::Stdio::null())
.stdout(process::Stdio::null())
.stderr(process::Stdio::null());
match cmd.spawn() {
Ok(_) => Ok(()),
Err(e) => Err(
format!("failed to launch {} in Terminal.app: {}", debugger, e))
}
}