1use std::ffi::{OsStr, OsString};
2use std::io::{BufRead, BufReader};
3use std::process::{Command, Output, Stdio};
4
5use camino::Utf8Path;
6use tracing::debug;
7
8use super::debugger::DebuggerCommands;
9use super::{Debugger, Emit, ProcRes, TestCx, Truncated, WillExecute};
10use crate::debuggers::extract_gdb_version;
11
12impl TestCx<'_> {
13 pub(super) fn run_debuginfo_test(&self) {
14 match self.config.debugger.unwrap() {
15 Debugger::Cdb => self.run_debuginfo_cdb_test(),
16 Debugger::Gdb => self.run_debuginfo_gdb_test(),
17 Debugger::Lldb => self.run_debuginfo_lldb_test(),
18 }
19 }
20
21 fn run_debuginfo_cdb_test(&self) {
22 let exe_file = self.make_exe_name();
23
24 let pdb_file = exe_file.with_extension(".pdb");
33 if pdb_file.exists() {
34 std::fs::remove_file(pdb_file).unwrap();
35 }
36
37 let should_run = self.run_if_enabled();
39 let compile_result = self.compile_test(should_run, Emit::None);
40 if !compile_result.status.success() {
41 self.fatal_proc_rec("compilation failed!", &compile_result);
42 }
43 if let WillExecute::Disabled = should_run {
44 return;
45 }
46
47 let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, "cdb", self.revision)
49 .unwrap_or_else(|e| self.fatal(&e));
50
51 let mut script_str = String::with_capacity(2048);
53 script_str.push_str("version\n"); script_str.push_str(".nvlist\n"); let mut js_extension = self.testpaths.file.clone();
59 js_extension.set_extension("cdb.js");
60 if js_extension.exists() {
61 script_str.push_str(&format!(".scriptload \"{}\"\n", js_extension));
62 }
63
64 let source_file_name = self.testpaths.file.file_name().unwrap();
66 for line in &dbg_cmds.breakpoint_lines {
67 script_str.push_str(&format!("bp `{}:{}`\n", source_file_name, line));
68 }
69
70 for line in &dbg_cmds.commands {
72 script_str.push_str(line);
73 script_str.push('\n');
74 }
75
76 script_str.push_str("qq\n"); debug!("script_str = {}", script_str);
80 self.dump_output_file(&script_str, "debugger.script");
81 let debugger_script = self.make_out_name("debugger.script");
82
83 let cdb_path = &self.config.cdb.as_ref().unwrap();
84 let mut cdb = Command::new(cdb_path);
85 cdb.arg("-lines") .arg("-cf")
87 .arg(&debugger_script)
88 .arg(&exe_file);
89
90 let debugger_run_result = self.compose_and_run(
91 cdb,
92 self.config.target_run_lib_path.as_path(),
93 None, None, );
96
97 if !debugger_run_result.status.success() {
98 self.fatal_proc_rec("Error while running CDB", &debugger_run_result);
99 }
100
101 if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
102 self.fatal_proc_rec(&e, &debugger_run_result);
103 }
104 }
105
106 fn run_debuginfo_gdb_test(&self) {
107 let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, "gdb", self.revision)
108 .unwrap_or_else(|e| self.fatal(&e));
109 let mut cmds = dbg_cmds.commands.join("\n");
110
111 let should_run = self.run_if_enabled();
113 let compiler_run_result = self.compile_test(should_run, Emit::None);
114 if !compiler_run_result.status.success() {
115 self.fatal_proc_rec("compilation failed!", &compiler_run_result);
116 }
117 if let WillExecute::Disabled = should_run {
118 return;
119 }
120
121 let exe_file = self.make_exe_name();
122
123 let debugger_run_result;
124 if let Some(android_cross_path) = self.config.android_cross_path.as_deref() {
127 cmds = cmds.replace("run", "continue");
128
129 let mut script_str = String::with_capacity(2048);
131 script_str.push_str(&format!("set charset {}\n", Self::charset()));
132 script_str.push_str(&format!("set sysroot {android_cross_path}\n"));
133 script_str.push_str(&format!("file {}\n", exe_file));
134 script_str.push_str("target remote :5039\n");
135 script_str.push_str(&format!(
136 "set solib-search-path \
137 ./{}/stage2/lib/rustlib/{}/lib/\n",
138 self.config.host, self.config.target
139 ));
140 for line in &dbg_cmds.breakpoint_lines {
141 script_str.push_str(
142 format!("break {}:{}\n", self.testpaths.file.file_name().unwrap(), *line)
143 .as_str(),
144 );
145 }
146 script_str.push_str(&cmds);
147 script_str.push_str("\nquit\n");
148
149 debug!("script_str = {}", script_str);
150 self.dump_output_file(&script_str, "debugger.script");
151
152 let adb_path = self.config.adb_path.as_ref().expect("`adb_path` must be specified");
155 let adb_test_dir =
156 self.config.adb_test_dir.as_ref().expect("`adb_test_dir` must be specified");
157
158 Command::new(adb_path)
159 .arg("push")
160 .arg(&exe_file)
161 .arg(adb_test_dir)
162 .status()
163 .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
164
165 Command::new(adb_path)
166 .args(&["forward", "tcp:5039", "tcp:5039"])
167 .status()
168 .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
169
170 let adb_arg = format!(
171 "export LD_LIBRARY_PATH={}; \
172 gdbserver{} :5039 {}/{}",
173 adb_test_dir,
174 if self.config.target.contains("aarch64") { "64" } else { "" },
175 adb_test_dir,
176 exe_file.file_name().unwrap()
177 );
178
179 debug!("adb arg: {}", adb_arg);
180 let mut adb = Command::new(adb_path)
181 .args(&["shell", &adb_arg])
182 .stdout(Stdio::piped())
183 .stderr(Stdio::inherit())
184 .spawn()
185 .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
186
187 let mut stdout = BufReader::new(adb.stdout.take().unwrap());
191 let mut line = String::new();
192 loop {
193 line.clear();
194 stdout.read_line(&mut line).unwrap();
195 if line.starts_with("Listening on port 5039") {
196 break;
197 }
198 }
199 drop(stdout);
200
201 let mut debugger_script = OsString::from("-command=");
202 debugger_script.push(self.make_out_name("debugger.script"));
203 let debugger_opts: &[&OsStr] =
204 &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
205
206 let gdb_path = self.config.gdb.as_ref().unwrap();
207 let Output { status, stdout, stderr } = Command::new(&gdb_path)
208 .args(debugger_opts)
209 .output()
210 .unwrap_or_else(|e| panic!("failed to exec `{gdb_path:?}`: {e:?}"));
211 let cmdline = {
212 let mut gdb = Command::new(&format!("{}-gdb", self.config.target));
213 gdb.args(debugger_opts);
214 let cmdline = self.make_cmdline(&gdb, Utf8Path::new(""));
216 self.logv(format_args!("executing {cmdline}"));
217 cmdline
218 };
219
220 debugger_run_result = ProcRes {
221 status,
222 stdout: String::from_utf8(stdout).unwrap(),
223 stderr: String::from_utf8(stderr).unwrap(),
224 truncated: Truncated::No,
225 cmdline,
226 };
227 if adb.kill().is_err() {
228 writeln!(self.stdout, "Adb process is already finished.");
229 }
230 } else {
231 let rust_pp_module_abs_path = self.config.src_root.join("src").join("etc");
232 let mut script_str = String::with_capacity(2048);
234 script_str.push_str(&format!("set charset {}\n", Self::charset()));
235 script_str.push_str("show version\n");
236
237 match self.config.gdb_version {
238 Some(version) => {
239 writeln!(
240 self.stdout,
241 "NOTE: compiletest thinks it is using GDB version {}",
242 version
243 );
244
245 if !self.props.disable_gdb_pretty_printers
246 && version > extract_gdb_version("7.4").unwrap()
247 {
248 script_str.push_str(&format!(
251 "add-auto-load-safe-path {}\n",
252 rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
253 ));
254
255 script_str.push_str(&format!(
259 "add-auto-load-safe-path {}\n",
260 self.output_base_dir().as_str().replace(r"\", r"\\")
261 ));
262
263 #[cfg(target_os = "windows")]
267 {
268 script_str.push_str(&format!(
269 "source {}\n",
270 self.config
271 .src_root
272 .join("src/etc/gdb_load_rust_pretty_printers.py")
273 ));
274 }
275 }
276 }
277 _ => {
278 writeln!(
279 self.stdout,
280 "NOTE: compiletest does not know which version of \
281 GDB it is using"
282 );
283 }
284 }
285
286 script_str.push_str("set print pretty off\n");
289
290 script_str.push_str(&format!(
292 "directory {}\n",
293 rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
294 ));
295
296 script_str.push_str(&format!("file {}\n", exe_file.as_str().replace(r"\", r"\\")));
298
299 script_str.push_str("set language rust\n");
301
302 for line in &dbg_cmds.breakpoint_lines {
304 script_str.push_str(&format!(
305 "break '{}':{}\n",
306 self.testpaths.file.file_name().unwrap(),
307 *line
308 ));
309 }
310
311 script_str.push_str(&cmds);
312 script_str.push_str("\nquit\n");
313
314 debug!("script_str = {}", script_str);
315 self.dump_output_file(&script_str, "debugger.script");
316
317 let mut debugger_script = OsString::from("-command=");
318 debugger_script.push(self.make_out_name("debugger.script"));
319
320 let debugger_opts: &[&OsStr] =
321 &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
322
323 let mut gdb = Command::new(self.config.gdb.as_ref().unwrap());
324
325 let pythonpath = with_pythonpath_prepended(&rust_pp_module_abs_path);
326 gdb.args(debugger_opts).env("PYTHONPATH", pythonpath);
327
328 debugger_run_result =
329 self.compose_and_run(gdb, self.config.target_run_lib_path.as_path(), None, None);
330 }
331
332 if !debugger_run_result.status.success() {
333 self.fatal_proc_rec("gdb failed to execute", &debugger_run_result);
334 }
335
336 if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
337 self.fatal_proc_rec(&e, &debugger_run_result);
338 }
339 }
340
341 fn run_debuginfo_lldb_test(&self) {
342 let Some(ref lldb) = self.config.lldb else {
343 self.fatal("Can't run LLDB test because LLDB's path is not set.");
344 };
345
346 let should_run = self.run_if_enabled();
348 let compile_result = self.compile_test(should_run, Emit::None);
349 if !compile_result.status.success() {
350 self.fatal_proc_rec("compilation failed!", &compile_result);
351 }
352 if let WillExecute::Disabled = should_run {
353 return;
354 }
355
356 let exe_file = self.make_exe_name();
357
358 match self.config.lldb_version {
359 Some(ref version) => {
360 writeln!(
361 self.stdout,
362 "NOTE: compiletest thinks it is using LLDB version {}",
363 version
364 );
365 }
366 _ => {
367 writeln!(
368 self.stdout,
369 "NOTE: compiletest does not know which version of \
370 LLDB it is using"
371 );
372 }
373 }
374
375 let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, "lldb", self.revision)
377 .unwrap_or_else(|e| self.fatal(&e));
378
379 let mut script_str = String::from("settings set auto-confirm true\n");
382
383 if self.config.host.contains("darwin") {
409 script_str.push_str("settings set target.inherit-tcc true\n");
410 }
411
412 script_str.push_str("version\n");
414
415 let rust_pp_module_abs_path = self.config.src_root.join("src/etc");
417
418 script_str.push_str(&format!(
419 "command script import {}/lldb_lookup.py\n",
420 rust_pp_module_abs_path
421 ));
422 script_str.push_str("script print(lldb_lookup.FEATURE_FLAGS)\n");
423
424 let source_file_name = self.testpaths.file.file_name().unwrap();
426 for line in &dbg_cmds.breakpoint_lines {
427 script_str.push_str(&format!(
428 "breakpoint set --file '{}' --line {}\n",
429 source_file_name, line
430 ));
431 }
432
433 for line in &dbg_cmds.commands {
435 script_str.push_str(line);
436 script_str.push('\n');
437 }
438
439 script_str.push_str("\nquit\n");
441
442 debug!("script_str = {}", script_str);
444 self.dump_output_file(&script_str, "debugger.script");
445 let debugger_script = self.make_out_name("debugger.script");
446
447 let debugger_run_result = self.run_lldb(lldb, &exe_file, &debugger_script);
449
450 if !debugger_run_result.status.success() {
451 self.fatal_proc_rec("Error while running LLDB", &debugger_run_result);
452 }
453
454 if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
455 self.fatal_proc_rec(&e, &debugger_run_result);
456 }
457 }
458
459 fn run_lldb(
460 &self,
461 lldb: &Utf8Path,
462 test_executable: &Utf8Path,
463 debugger_script: &Utf8Path,
464 ) -> ProcRes {
465 let rust_pp_module_abs_path = self.config.src_root.join("src/etc");
467 let pythonpath = with_pythonpath_prepended(&rust_pp_module_abs_path);
468 let path = prepend_to_path(&self.config.target_run_lib_path);
470
471 let mut cmd = Command::new(lldb);
472 cmd.arg("--one-line")
473 .arg("script --language python -- import lldb_batchmode; lldb_batchmode.main()")
474 .env("LLDB_BATCHMODE_TARGET_PATH", test_executable)
475 .env("LLDB_BATCHMODE_SCRIPT_PATH", debugger_script)
476 .env("PYTHONUNBUFFERED", "1") .env("PYTHONPATH", pythonpath)
478 .env("PATH", path);
479
480 self.run_command_to_procres(&mut cmd)
481 }
482}
483
484fn with_pythonpath_prepended(some_path: &Utf8Path) -> String {
485 if let Ok(pp) = std::env::var("PYTHONPATH") {
487 #[cfg(target_os = "windows")]
488 {
489 format!("{pp};{some_path}")
490 }
491 #[cfg(not(target_os = "windows"))]
492 {
493 format!("{pp}:{some_path}")
494 }
495 } else {
496 some_path.to_string()
497 }
498}
499
500fn prepend_to_path(some_path: &Utf8Path) -> String {
501 if let Ok(path) = std::env::var("PATH") {
502 #[cfg(target_os = "windows")]
503 {
504 format!("{some_path};{path}")
505 }
506 #[cfg(not(target_os = "windows"))]
507 {
508 format!("{some_path}:{path}")
509 }
510 } else {
511 some_path.to_string()
512 }
513}