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;
11use crate::util::ArgFileCommand;
12
13impl TestCx<'_> {
14 pub(super) fn run_debuginfo_test(&self) {
15 match self.variant.debugger.as_ref().unwrap() {
16 Debugger::Cdb => self.run_debuginfo_cdb_test(),
17 Debugger::Gdb => self.run_debuginfo_gdb_test(),
18 Debugger::Lldb => self.run_debuginfo_lldb_test(),
19 }
20 }
21
22 fn run_debuginfo_cdb_test(&self) {
23 let exe_file = self.make_exe_name();
24
25 let pdb_file = exe_file.with_extension(".pdb");
34 if pdb_file.exists() {
35 std::fs::remove_file(pdb_file).unwrap();
36 }
37
38 let should_run = self.run_if_enabled();
40 let compile_result = self.compile_test(should_run, Emit::None);
41 if !compile_result.status.success() {
42 self.fatal_proc_rec("compilation failed!", &compile_result);
43 }
44 if let WillExecute::Disabled = should_run {
45 return;
46 }
47
48 let dbg_cmds =
50 DebuggerCommands::parse_from(&self.testpaths.file, "cdb", self.variant.revision())
51 .unwrap_or_else(|e| self.fatal(&e));
52
53 let mut script_str = String::with_capacity(2048);
55 script_str.push_str("version\n"); script_str.push_str(".nvlist\n"); let mut js_extension = self.testpaths.file.clone();
61 js_extension.set_extension("cdb.js");
62 if js_extension.exists() {
63 script_str.push_str(&format!(".scriptload \"{}\"\n", js_extension));
64 }
65
66 let source_file_name = self.testpaths.file.file_name().unwrap();
68 for line in &dbg_cmds.breakpoint_lines {
69 script_str.push_str(&format!("bp `{}:{}`\n", source_file_name, line));
70 }
71
72 for line in &dbg_cmds.commands {
74 script_str.push_str(line);
75 script_str.push('\n');
76 }
77
78 script_str.push_str("qq\n"); debug!("script_str = {}", script_str);
82 self.dump_output_file(&script_str, "debugger.script");
83 let debugger_script = self.make_out_name("debugger.script");
84
85 let cdb_path = &self.config.cdb.as_ref().unwrap();
86 let mut cdb = Command::new(cdb_path);
87 cdb.arg("-lines") .arg("-cf")
89 .arg(&debugger_script)
90 .arg(&exe_file);
91
92 let debugger_run_result = self.compose_and_run(
93 cdb,
94 self.config.target_run_lib_path.as_path(),
95 None, None, );
98
99 if !debugger_run_result.status.success() {
100 self.fatal_proc_rec("Error while running CDB", &debugger_run_result);
101 }
102
103 if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
104 self.fatal_proc_rec(&e, &debugger_run_result);
105 }
106 }
107
108 fn run_debuginfo_gdb_test(&self) {
109 let dbg_cmds =
110 DebuggerCommands::parse_from(&self.testpaths.file, "gdb", self.variant.revision())
111 .unwrap_or_else(|e| self.fatal(&e));
112 let mut cmds = dbg_cmds.commands.join("\n");
113
114 let should_run = self.run_if_enabled();
116 let compiler_run_result = self.compile_test(should_run, Emit::None);
117 if !compiler_run_result.status.success() {
118 self.fatal_proc_rec("compilation failed!", &compiler_run_result);
119 }
120 if let WillExecute::Disabled = should_run {
121 return;
122 }
123
124 let exe_file = self.make_exe_name();
125
126 let debugger_run_result;
127 if let Some(android_cross_path) = self.config.android_cross_path.as_deref() {
130 cmds = cmds.replace("run", "continue");
131
132 let mut script_str = String::with_capacity(2048);
134 script_str.push_str("py import debugger_tester\n");
135 script_str.push_str(&format!("set charset {}\n", Self::charset()));
136 script_str.push_str(&format!("set sysroot {android_cross_path}\n"));
137 script_str.push_str(&format!("file {}\n", exe_file));
138 script_str.push_str("target remote :5039\n");
139 script_str.push_str(&format!(
140 "set solib-search-path \
141 ./{}/stage2/lib/rustlib/{}/lib/\n",
142 self.config.host, self.config.target
143 ));
144 for line in &dbg_cmds.breakpoint_lines {
145 script_str.push_str(
146 format!("break {}:{}\n", self.testpaths.file.file_name().unwrap(), *line)
147 .as_str(),
148 );
149 }
150 script_str.push_str(&cmds);
151 script_str.push_str("\nquit\n");
152
153 debug!("script_str = {}", script_str);
154 self.dump_output_file(&script_str, "debugger.script");
155
156 let adb_path = self.config.adb_path.as_ref().expect("`adb_path` must be specified");
159 let adb_test_dir =
160 self.config.adb_test_dir.as_ref().expect("`adb_test_dir` must be specified");
161
162 Command::new(adb_path)
163 .arg("push")
164 .arg(&exe_file)
165 .arg(adb_test_dir)
166 .status()
167 .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
168
169 Command::new(adb_path)
170 .args(&["forward", "tcp:5039", "tcp:5039"])
171 .status()
172 .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
173
174 let adb_arg = format!(
175 "export LD_LIBRARY_PATH={}; \
176 gdbserver{} :5039 {}/{}",
177 adb_test_dir,
178 if self.config.target.contains("aarch64") { "64" } else { "" },
179 adb_test_dir,
180 exe_file.file_name().unwrap()
181 );
182
183 debug!("adb arg: {}", adb_arg);
184 let mut adb = Command::new(adb_path)
185 .args(&["shell", &adb_arg])
186 .stdout(Stdio::piped())
187 .stderr(Stdio::inherit())
188 .spawn()
189 .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
190
191 let mut stdout = BufReader::new(adb.stdout.take().unwrap());
195 let mut line = String::new();
196 loop {
197 line.clear();
198 stdout.read_line(&mut line).unwrap();
199 if line.starts_with("Listening on port 5039") {
200 break;
201 }
202 }
203 drop(stdout);
204
205 let mut debugger_script = OsString::from("-command=");
206 debugger_script.push(self.make_out_name("debugger.script"));
207 let debugger_opts: &[&OsStr] =
208 &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
209
210 let gdb_path = self.config.gdb.as_ref().unwrap();
211 let Output { status, stdout, stderr } = Command::new(&gdb_path)
212 .args(debugger_opts)
213 .output()
214 .unwrap_or_else(|e| panic!("failed to exec `{gdb_path:?}`: {e:?}"));
215 let cmdline = {
216 let mut gdb = Command::new(&format!("{}-gdb", self.config.target));
217 gdb.args(debugger_opts);
218 let cmdline = self.make_cmdline(&gdb, Utf8Path::new(""));
220 self.logv(format_args!("executing {cmdline}"));
221 cmdline
222 };
223
224 debugger_run_result = ProcRes {
225 status,
226 stdout: String::from_utf8(stdout).unwrap(),
227 stderr: String::from_utf8(stderr).unwrap(),
228 truncated: Truncated::No,
229 cmdline,
230 };
231 if adb.kill().is_err() {
232 writeln!(self.stdout, "Adb process is already finished.");
233 }
234 } else {
235 let rust_pp_module_abs_path = self.config.src_root.join("src").join("etc");
236 let mut script_str = String::with_capacity(2048);
238 script_str.push_str("py import debugger_tester\n");
239 script_str.push_str(&format!("set charset {}\n", Self::charset()));
240 script_str.push_str("show version\n");
241
242 match self.config.gdb_version {
243 Some(version) => {
244 writeln!(
245 self.stdout,
246 "NOTE: compiletest thinks it is using GDB version {}",
247 version
248 );
249
250 if !self.props.disable_gdb_pretty_printers
251 && version > extract_gdb_version("7.4").unwrap()
252 {
253 script_str.push_str(&format!(
256 "add-auto-load-safe-path {}\n",
257 rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
258 ));
259
260 script_str.push_str(&format!(
264 "add-auto-load-safe-path {}\n",
265 self.output_base_dir().as_str().replace(r"\", r"\\")
266 ));
267
268 #[cfg(target_os = "windows")]
272 {
273 script_str.push_str(&format!(
274 "source {}\n",
275 self.config
276 .src_root
277 .join("src/etc/gdb_load_rust_pretty_printers.py")
278 ));
279 }
280 }
281 }
282 _ => {
283 writeln!(
284 self.stdout,
285 "NOTE: compiletest does not know which version of \
286 GDB it is using"
287 );
288 }
289 }
290
291 script_str.push_str("set print pretty off\n");
294
295 script_str.push_str(&format!(
297 "directory {}\n",
298 rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
299 ));
300
301 script_str.push_str(&format!("file {}\n", exe_file.as_str().replace(r"\", r"\\")));
303
304 script_str.push_str("set language rust\n");
306
307 for line in &dbg_cmds.breakpoint_lines {
309 script_str.push_str(&format!(
310 "break '{}':{}\n",
311 self.testpaths.file.file_name().unwrap(),
312 *line
313 ));
314 }
315
316 script_str.push_str(&cmds);
317 script_str.push_str("\nrepr_finalize\n");
319
320 script_str.push_str("\nquit\n");
321
322 debug!("script_str = {}", script_str);
323 self.dump_output_file(&script_str, "debugger.script");
324
325 let mut debugger_script = OsString::from("-command=");
326 debugger_script.push(self.make_out_name("debugger.script"));
327
328 let debugger_opts: &[&OsStr] =
329 &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
330
331 let mut gdb = Command::new(self.config.gdb.as_ref().unwrap());
332
333 let gdb_input_data_path = self.config.src_root.join(format!(
334 "{}/gdb_input/{}.json",
335 self.testpaths.file.parent().unwrap(),
336 get_target_file_name(&self.config.target)
337 ));
338
339 let pythonpath = with_pythonpath_prepended(&rust_pp_module_abs_path);
340 gdb.args(debugger_opts)
341 .env("PYTHONPATH", pythonpath)
342 .env("DEBUGGER_TESTER_DEBUGGER", "gdb")
343 .env("DEBUGGER_TESTER_BLESS_TEST_DATA", if self.config.bless { "1" } else { "0" })
344 .env("DEBUGGER_TESTER_TARGET_TRIPLE", &self.config.target)
345 .env("DEBUGGER_TESTER_INPUT_DATA_PATH", gdb_input_data_path);
346
347 debugger_run_result =
348 self.compose_and_run(gdb, self.config.target_run_lib_path.as_path(), None, None);
349 }
350
351 if !debugger_run_result.status.success() {
352 self.fatal_proc_rec("gdb failed to execute", &debugger_run_result);
353 }
354
355 if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
356 self.fatal_proc_rec(&e, &debugger_run_result);
357 }
358 }
359
360 fn run_debuginfo_lldb_test(&self) {
361 let Some(ref lldb) = self.config.lldb else {
362 self.fatal("Can't run LLDB test because LLDB's path is not set.");
363 };
364
365 let should_run = self.run_if_enabled();
367 let compile_result = self.compile_test(should_run, Emit::None);
368 if !compile_result.status.success() {
369 self.fatal_proc_rec("compilation failed!", &compile_result);
370 }
371 if let WillExecute::Disabled = should_run {
372 return;
373 }
374
375 let exe_file = self.make_exe_name();
376
377 match self.config.lldb_version {
378 Some(ref version) => {
379 writeln!(
380 self.stdout,
381 "NOTE: compiletest thinks it is using LLDB version: {:?}",
382 version
383 );
384 }
385 _ => {
386 writeln!(
387 self.stdout,
388 "NOTE: compiletest does not know which version of \
389 LLDB it is using"
390 );
391 }
392 }
393
394 let dbg_cmds =
396 DebuggerCommands::parse_from(&self.testpaths.file, "lldb", self.variant.revision())
397 .unwrap_or_else(|e| self.fatal(&e));
398
399 let mut script_str = String::from("settings set auto-confirm true\n");
402
403 if self.config.host.contains("darwin") {
429 script_str.push_str("settings set target.inherit-tcc true\n");
430 }
431
432 script_str.push_str("version\n");
434
435 let rust_pp_module_abs_path = self.config.src_root.join("src/etc");
437
438 script_str.push_str(&format!(
439 "command script import {}/lldb_lookup.py\n",
440 rust_pp_module_abs_path
441 ));
442 script_str.push_str("script print(lldb_lookup.FEATURE_FLAGS)\n");
443
444 let source_file_name = self.testpaths.file.file_name().unwrap();
446 for line in &dbg_cmds.breakpoint_lines {
447 script_str.push_str(&format!(
448 "breakpoint set --file '{}' --line {}\n",
449 source_file_name, line
450 ));
451 }
452
453 for line in &dbg_cmds.commands {
455 script_str.push_str(line);
456 script_str.push('\n');
457 }
458
459 script_str.push_str("\nquit\n");
461
462 debug!("script_str = {}", script_str);
464 self.dump_output_file(&script_str, "debugger.script");
465 let debugger_script = self.make_out_name("debugger.script");
466
467 let debugger_run_result = self.run_lldb(lldb, &exe_file, &debugger_script);
469
470 if !debugger_run_result.status.success() {
471 self.fatal_proc_rec("Error while running LLDB", &debugger_run_result);
472 }
473
474 if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
475 self.fatal_proc_rec(&e, &debugger_run_result);
476 }
477 }
478
479 fn run_lldb(
480 &self,
481 lldb: &Utf8Path,
482 test_executable: &Utf8Path,
483 debugger_script: &Utf8Path,
484 ) -> ProcRes {
485 let rust_pp_module_abs_path = self.config.src_root.join("src/etc");
487 let pythonpath = with_pythonpath_prepended(&rust_pp_module_abs_path);
488 let path = prepend_to_path(&self.config.target_run_lib_path);
490
491 let lldb_input_data_path = self.config.src_root.join(format!(
493 "{}/lldb_input/{}.json",
494 self.testpaths.file.parent().unwrap(),
495 get_target_file_name(&self.config.target)
496 ));
497
498 let mut cmd = ArgFileCommand::new(lldb);
499 cmd.arg("--batch") .arg("--one-line")
501 .arg("script --language python -- import debugger_tester; debugger_tester.main()")
502 .env("DEBUGGER_TESTER_TARGET_PATH", test_executable)
503 .env("DEBUGGER_TESTER_SCRIPT_PATH", debugger_script)
504 .env("DEBUGGER_TESTER_INPUT_DATA_PATH", lldb_input_data_path)
505 .env("DEBUGGER_TESTER_BLESS_TEST_DATA", if self.config.bless { "1" } else { "0" })
506 .env("DEBUGGER_TESTER_TARGET_TRIPLE", &self.config.target)
507 .env("DEBUGGER_TESTER_DEBUGGER", "lldb")
508 .env("PYTHONUNBUFFERED", "1") .env("PYTHONPATH", pythonpath)
510 .env("PATH", path);
511
512 self.run_command_to_procres(cmd)
513 }
514}
515
516fn with_pythonpath_prepended(some_path: &Utf8Path) -> String {
517 if let Ok(pp) = std::env::var("PYTHONPATH") {
519 #[cfg(target_os = "windows")]
520 {
521 format!("{pp};{some_path}")
522 }
523 #[cfg(not(target_os = "windows"))]
524 {
525 format!("{pp}:{some_path}")
526 }
527 } else {
528 some_path.to_string()
529 }
530}
531
532fn prepend_to_path(some_path: &Utf8Path) -> String {
533 if let Ok(path) = std::env::var("PATH") {
534 #[cfg(target_os = "windows")]
535 {
536 format!("{some_path};{path}")
537 }
538 #[cfg(not(target_os = "windows"))]
539 {
540 format!("{some_path}:{path}")
541 }
542 } else {
543 some_path.to_string()
544 }
545}
546
547fn get_target_file_name(target_name: &str) -> &'static str {
550 if target_name.ends_with("windows-msvc") {
551 "windows_msvc"
552 } else if target_name.ends_with("windows-gnu") || target_name.ends_with("windows-gnullvm") {
553 "windows_gnu"
554 } else {
555 "non_windows"
556 }
557}