1use std::sync::Mutex;
20use std::process;
21
22#[cfg(target_os = "linux")]
23use std::{fs, env};
24
25#[cfg(target_os = "windows")]
26use winapi::um::debugapi;
27#[cfg(target_os = "windows")]
28use std::{path, thread};
29#[cfg(target_os = "windows")]
30use std::time::Duration;
31
32#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
33compile_error!("debug-here: this crate currently only builds on linux, macos, and windows");
34
35fn already_entered() -> bool {
36 lazy_static! {
37 static ref GUARD: Mutex<bool> = Mutex::new(false);
38 }
39
40 let mut entered = GUARD.lock().unwrap();
42
43 let ret = *entered;
44 *entered = true;
45 return ret;
46}
47
48#[cfg(not(target_os = "windows"))]
60pub fn debug_here_unixy_impl(debugger: Option<&str>) {
61 if already_entered() {
62 return;
63 }
64
65
66 #[cfg(target_os = "linux")]
67 let sane_env = linux_check();
68 #[cfg(target_os = "macos")]
69 let sane_env = macos_check();
70
71 if let Err(e) = sane_env {
72 eprintln!("debug-here: {}", e);
73 return
74 }
75
76 #[cfg(target_os = "linux")]
77 let debugger = debugger.unwrap_or("rust-gdb");
78 #[cfg(target_os = "macos")]
79 let debugger = debugger.unwrap_or("rust-lldb");
80
81 if which::which(debugger).is_err() {
82 eprintln!("debug-here: can't find {} on your path. Bailing.", debugger);
83 return;
84 }
85
86 if which::which("debug-here-gdb-wrapper").is_err() {
87 eprintln!(r#"debug-here:
88 Can't find debug-here-gdb-wrapper on your path. To get it
89 you can run `cargo install debug-here-gdb-wrapper`
90 "#);
91 return;
92 }
93
94 let looping = true;
98
99 #[cfg(target_os = "linux")]
100 let launch_stat = linux_launch_term(debugger);
101 #[cfg(target_os = "macos")]
102 let launch_stat = macos_launch_term(debugger);
103
104 if let Err(e) = launch_stat {
105 eprintln!("debug-here: {}", e);
106 return;
107 }
108
109 while looping {}
112}
113
114#[cfg(target_os = "windows")]
127pub fn debug_here_win_impl() {
128 if already_entered() {
129 return;
130 }
131
132 let jitdbg_exe = r#"c:\windows\system32\vsjitdebugger.exe"#;
133 if !path::Path::new(jitdbg_exe).exists() {
134 eprintln!("debug-here: could not find '{}'.", jitdbg_exe);
135 return;
136 }
137
138
139 let pid = process::id();
140
141 let mut cmd = process::Command::new(jitdbg_exe);
142 cmd.stdin(process::Stdio::null())
143 .stdout(process::Stdio::null())
144 .stderr(process::Stdio::null());
145 cmd.arg("-p").arg(pid.to_string());
146
147 if let Err(e) = cmd.spawn() {
148 eprintln!("debug-here: failed to launch '{}': {}", jitdbg_exe, e);
149 return;
150 }
151
152 while unsafe { debugapi::IsDebuggerPresent() } == 0 {
155 thread::sleep(Duration::from_millis(100));
156 }
157
158 unsafe { debugapi::DebugBreak(); }
160}
161
162
163#[cfg(not(target_os = "windows"))]
166fn debugger_args(debugger: &str) -> Vec<String> {
167 if debugger == "rust-lldb" {
168 vec!["-p".to_string(),
169 process::id().to_string(),
170 "-o".to_string(),
171 "expression looping = 0".to_string(),
172 "-o".to_string(),
173 "finish".to_string()]
174 } else if debugger == "rust-gdb" {
175 vec!["-pid".to_string(),
176 process::id().to_string(),
177 "-ex".to_string(),
178 "set variable looping = 0".to_string(),
179 "-ex".to_string(),
180 "finish".to_string()]
181 } else {
182 panic!("unknown debugger: {}", debugger);
183 }
184}
185
186#[cfg(target_os = "linux")]
190fn linux_check() -> Result<(), String> {
191 let the_kids_are_ok =
192 fs::read("/proc/sys/kernel/yama/ptrace_scope")
193 .map(|contents|
194 std::str::from_utf8(&contents[..1]).unwrap_or("1") == "0")
195 .unwrap_or(false);
196 if !the_kids_are_ok {
197 return Err(format!(r#"
198 ptrace_scope must be set to 0 for debug-here to work.
199 This will allow any process with a given uid to rummage around
200 in the memory of any other process with the same uid, so there
201 are some security risks. To set ptrace_scope for just this
202 session you can do:
203
204 echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
205
206 Giving up on debugging for now.
207 "#));
208 }
209
210 Ok(())
211}
212
213#[cfg(target_os = "linux")]
215fn linux_launch_term(debugger: &str) -> Result<(), String> {
216 if debugger == "rust-gdb" {
229 env::set_var("RUST_DEBUG_HERE_LIFELINE",
232 format!("1,{}", process::id()));
233 } else {
234 env::set_var("RUST_DEBUG_HERE_LIFELINE",
235 format!("2,{},{}", process::id(), debugger));
236 }
237
238 let term = match which::which("alacritty").or(which::which("xterm")) {
239 Ok(t) => t,
240 Err(_) => {
241 return Err(format!(r#"
242 can't find alacritty or xterm on your path. Those are the
243 only terminal emulators currently supported on linux.
244 "#));
245 }
246 };
247 let term_cmd = term.clone();
248
249 let mut cmd = process::Command::new(term_cmd);
250 cmd.stdin(process::Stdio::null())
251 .stdout(process::Stdio::null())
252 .stderr(process::Stdio::null());
253
254 if term.ends_with("alacritty") {
256 cmd.arg("-e");
257 cmd.arg(debugger);
258 cmd.args(debugger_args(debugger));
259 } else {
260 cmd.arg("debug-here-gdb-wrapper");
261 }
262
263 match cmd.spawn() {
264 Ok(_) => Ok(()),
265 Err(e) => Err(
266 format!("failed to launch rust-gdb in {:?}: {}", term, e))
267 }
268}
269
270#[cfg(target_os = "macos")]
272fn macos_check() -> Result<(), String> {
273 if which::which("osascript").is_err() {
274 return Err(format!("debug-here: can't find osascript. Bailing."));
275 }
276
277 Ok(())
278}
279
280#[cfg(target_os = "macos")]
282fn macos_launch_term(debugger: &str) -> Result<(), String> {
283 let launch_script =
284 format!(r#"tell app "Terminal"
285 do script "exec {} {}"
286 end tell"#, debugger,
287 debugger_args(debugger).into_iter()
288 .map(|a| if a.contains(" ") { format!("'{}'", a) } else { a } )
289 .collect::<Vec<_>>().join(" "));
290
291 let mut cmd = process::Command::new("osascript");
292 cmd.arg("-e")
293 .arg(launch_script)
294 .stdin(process::Stdio::null())
295 .stdout(process::Stdio::null())
296 .stderr(process::Stdio::null());
297
298 match cmd.spawn() {
299 Ok(_) => Ok(()),
300 Err(e) => Err(
301 format!("failed to launch {} in Terminal.app: {}", debugger, e))
302 }
303}