compiletest/runtest/
debuginfo.rs

1use std::ffi::{OsStr, OsString};
2use std::fs::File;
3use std::io::{BufRead, BufReader, Read};
4use std::path::Path;
5use std::process::{Command, Output, Stdio};
6
7use tracing::debug;
8
9use super::debugger::DebuggerCommands;
10use super::{Debugger, Emit, ProcRes, TestCx, Truncated, WillExecute};
11use crate::common::Config;
12use crate::debuggers::{extract_gdb_version, is_android_gdb_target};
13use crate::util::logv;
14
15impl TestCx<'_> {
16    pub(super) fn run_debuginfo_test(&self) {
17        match self.config.debugger.unwrap() {
18            Debugger::Cdb => self.run_debuginfo_cdb_test(),
19            Debugger::Gdb => self.run_debuginfo_gdb_test(),
20            Debugger::Lldb => self.run_debuginfo_lldb_test(),
21        }
22    }
23
24    fn run_debuginfo_cdb_test(&self) {
25        let config = Config {
26            target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags),
27            host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags),
28            ..self.config.clone()
29        };
30
31        let test_cx = TestCx { config: &config, ..*self };
32
33        test_cx.run_debuginfo_cdb_test_no_opt();
34    }
35
36    fn run_debuginfo_cdb_test_no_opt(&self) {
37        let exe_file = self.make_exe_name();
38
39        // Existing PDB files are update in-place. When changing the debuginfo
40        // the compiler generates for something, this can lead to the situation
41        // where both the old and the new version of the debuginfo for the same
42        // type is present in the PDB, which is very confusing.
43        // Therefore we delete any existing PDB file before compiling the test
44        // case.
45        // FIXME: If can reliably detect that MSVC's link.exe is used, then
46        //        passing `/INCREMENTAL:NO` might be a cleaner way to do this.
47        let pdb_file = exe_file.with_extension(".pdb");
48        if pdb_file.exists() {
49            std::fs::remove_file(pdb_file).unwrap();
50        }
51
52        // compile test file (it should have 'compile-flags:-g' in the header)
53        let should_run = self.run_if_enabled();
54        let compile_result = self.compile_test(should_run, Emit::None);
55        if !compile_result.status.success() {
56            self.fatal_proc_rec("compilation failed!", &compile_result);
57        }
58        if let WillExecute::Disabled = should_run {
59            return;
60        }
61
62        // Parse debugger commands etc from test files
63        let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, "cdb")
64            .unwrap_or_else(|e| self.fatal(&e));
65
66        // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-commands
67        let mut script_str = String::with_capacity(2048);
68        script_str.push_str("version\n"); // List CDB (and more) version info in test output
69        script_str.push_str(".nvlist\n"); // List loaded `*.natvis` files, bulk of custom MSVC debug
70
71        // If a .js file exists next to the source file being tested, then this is a JavaScript
72        // debugging extension that needs to be loaded.
73        let mut js_extension = self.testpaths.file.clone();
74        js_extension.set_extension("cdb.js");
75        if js_extension.exists() {
76            script_str.push_str(&format!(".scriptload \"{}\"\n", js_extension.to_string_lossy()));
77        }
78
79        // Set breakpoints on every line that contains the string "#break"
80        let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy();
81        for line in &dbg_cmds.breakpoint_lines {
82            script_str.push_str(&format!("bp `{}:{}`\n", source_file_name, line));
83        }
84
85        // Append the other `cdb-command:`s
86        for line in &dbg_cmds.commands {
87            script_str.push_str(line);
88            script_str.push('\n');
89        }
90
91        script_str.push_str("qq\n"); // Quit the debugger (including remote debugger, if any)
92
93        // Write the script into a file
94        debug!("script_str = {}", script_str);
95        self.dump_output_file(&script_str, "debugger.script");
96        let debugger_script = self.make_out_name("debugger.script");
97
98        let cdb_path = &self.config.cdb.as_ref().unwrap();
99        let mut cdb = Command::new(cdb_path);
100        cdb.arg("-lines") // Enable source line debugging.
101            .arg("-cf")
102            .arg(&debugger_script)
103            .arg(&exe_file);
104
105        let debugger_run_result = self.compose_and_run(
106            cdb,
107            self.config.run_lib_path.to_str().unwrap(),
108            None, // aux_path
109            None, // input
110        );
111
112        if !debugger_run_result.status.success() {
113            self.fatal_proc_rec("Error while running CDB", &debugger_run_result);
114        }
115
116        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
117            self.fatal_proc_rec(&e, &debugger_run_result);
118        }
119    }
120
121    fn run_debuginfo_gdb_test(&self) {
122        let config = Config {
123            target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags),
124            host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags),
125            ..self.config.clone()
126        };
127
128        let test_cx = TestCx { config: &config, ..*self };
129
130        test_cx.run_debuginfo_gdb_test_no_opt();
131    }
132
133    fn run_debuginfo_gdb_test_no_opt(&self) {
134        let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, "gdb")
135            .unwrap_or_else(|e| self.fatal(&e));
136        let mut cmds = dbg_cmds.commands.join("\n");
137
138        // compile test file (it should have 'compile-flags:-g' in the header)
139        let should_run = self.run_if_enabled();
140        let compiler_run_result = self.compile_test(should_run, Emit::None);
141        if !compiler_run_result.status.success() {
142            self.fatal_proc_rec("compilation failed!", &compiler_run_result);
143        }
144        if let WillExecute::Disabled = should_run {
145            return;
146        }
147
148        let exe_file = self.make_exe_name();
149
150        let debugger_run_result;
151        if is_android_gdb_target(&self.config.target) {
152            cmds = cmds.replace("run", "continue");
153
154            let tool_path = match self.config.android_cross_path.to_str() {
155                Some(x) => x.to_owned(),
156                None => self.fatal("cannot find android cross path"),
157            };
158
159            // write debugger script
160            let mut script_str = String::with_capacity(2048);
161            script_str.push_str(&format!("set charset {}\n", Self::charset()));
162            script_str.push_str(&format!("set sysroot {}\n", tool_path));
163            script_str.push_str(&format!("file {}\n", exe_file.to_str().unwrap()));
164            script_str.push_str("target remote :5039\n");
165            script_str.push_str(&format!(
166                "set solib-search-path \
167                 ./{}/stage2/lib/rustlib/{}/lib/\n",
168                self.config.host, self.config.target
169            ));
170            for line in &dbg_cmds.breakpoint_lines {
171                script_str.push_str(
172                    format!(
173                        "break {:?}:{}\n",
174                        self.testpaths.file.file_name().unwrap().to_string_lossy(),
175                        *line
176                    )
177                    .as_str(),
178                );
179            }
180            script_str.push_str(&cmds);
181            script_str.push_str("\nquit\n");
182
183            debug!("script_str = {}", script_str);
184            self.dump_output_file(&script_str, "debugger.script");
185
186            let adb_path = &self.config.adb_path;
187
188            Command::new(adb_path)
189                .arg("push")
190                .arg(&exe_file)
191                .arg(&self.config.adb_test_dir)
192                .status()
193                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
194
195            Command::new(adb_path)
196                .args(&["forward", "tcp:5039", "tcp:5039"])
197                .status()
198                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
199
200            let adb_arg = format!(
201                "export LD_LIBRARY_PATH={}; \
202                 gdbserver{} :5039 {}/{}",
203                self.config.adb_test_dir.clone(),
204                if self.config.target.contains("aarch64") { "64" } else { "" },
205                self.config.adb_test_dir.clone(),
206                exe_file.file_name().unwrap().to_str().unwrap()
207            );
208
209            debug!("adb arg: {}", adb_arg);
210            let mut adb = Command::new(adb_path)
211                .args(&["shell", &adb_arg])
212                .stdout(Stdio::piped())
213                .stderr(Stdio::inherit())
214                .spawn()
215                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
216
217            // Wait for the gdbserver to print out "Listening on port ..."
218            // at which point we know that it's started and then we can
219            // execute the debugger below.
220            let mut stdout = BufReader::new(adb.stdout.take().unwrap());
221            let mut line = String::new();
222            loop {
223                line.truncate(0);
224                stdout.read_line(&mut line).unwrap();
225                if line.starts_with("Listening on port 5039") {
226                    break;
227                }
228            }
229            drop(stdout);
230
231            let mut debugger_script = OsString::from("-command=");
232            debugger_script.push(self.make_out_name("debugger.script"));
233            let debugger_opts: &[&OsStr] =
234                &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
235
236            let gdb_path = self.config.gdb.as_ref().unwrap();
237            let Output { status, stdout, stderr } = Command::new(&gdb_path)
238                .args(debugger_opts)
239                .output()
240                .unwrap_or_else(|e| panic!("failed to exec `{gdb_path:?}`: {e:?}"));
241            let cmdline = {
242                let mut gdb = Command::new(&format!("{}-gdb", self.config.target));
243                gdb.args(debugger_opts);
244                let cmdline = self.make_cmdline(&gdb, "");
245                logv(self.config, format!("executing {}", cmdline));
246                cmdline
247            };
248
249            debugger_run_result = ProcRes {
250                status,
251                stdout: String::from_utf8(stdout).unwrap(),
252                stderr: String::from_utf8(stderr).unwrap(),
253                truncated: Truncated::No,
254                cmdline,
255            };
256            if adb.kill().is_err() {
257                println!("Adb process is already finished.");
258            }
259        } else {
260            let rust_src_root =
261                self.config.find_rust_src_root().expect("Could not find Rust source root");
262            let rust_pp_module_rel_path = Path::new("./src/etc");
263            let rust_pp_module_abs_path =
264                rust_src_root.join(rust_pp_module_rel_path).to_str().unwrap().to_owned();
265            // write debugger script
266            let mut script_str = String::with_capacity(2048);
267            script_str.push_str(&format!("set charset {}\n", Self::charset()));
268            script_str.push_str("show version\n");
269
270            match self.config.gdb_version {
271                Some(version) => {
272                    println!("NOTE: compiletest thinks it is using GDB version {}", version);
273
274                    if version > extract_gdb_version("7.4").unwrap() {
275                        // Add the directory containing the pretty printers to
276                        // GDB's script auto loading safe path
277                        script_str.push_str(&format!(
278                            "add-auto-load-safe-path {}\n",
279                            rust_pp_module_abs_path.replace(r"\", r"\\")
280                        ));
281
282                        let output_base_dir = self.output_base_dir().to_str().unwrap().to_owned();
283
284                        // Add the directory containing the output binary to
285                        // include embedded pretty printers to GDB's script
286                        // auto loading safe path
287                        script_str.push_str(&format!(
288                            "add-auto-load-safe-path {}\n",
289                            output_base_dir.replace(r"\", r"\\")
290                        ));
291                    }
292                }
293                _ => {
294                    println!(
295                        "NOTE: compiletest does not know which version of \
296                         GDB it is using"
297                    );
298                }
299            }
300
301            // The following line actually doesn't have to do anything with
302            // pretty printing, it just tells GDB to print values on one line:
303            script_str.push_str("set print pretty off\n");
304
305            // Add the pretty printer directory to GDB's source-file search path
306            script_str
307                .push_str(&format!("directory {}\n", rust_pp_module_abs_path.replace(r"\", r"\\")));
308
309            // Load the target executable
310            script_str
311                .push_str(&format!("file {}\n", exe_file.to_str().unwrap().replace(r"\", r"\\")));
312
313            // Force GDB to print values in the Rust format.
314            script_str.push_str("set language rust\n");
315
316            // Add line breakpoints
317            for line in &dbg_cmds.breakpoint_lines {
318                script_str.push_str(&format!(
319                    "break '{}':{}\n",
320                    self.testpaths.file.file_name().unwrap().to_string_lossy(),
321                    *line
322                ));
323            }
324
325            script_str.push_str(&cmds);
326            script_str.push_str("\nquit\n");
327
328            debug!("script_str = {}", script_str);
329            self.dump_output_file(&script_str, "debugger.script");
330
331            let mut debugger_script = OsString::from("-command=");
332            debugger_script.push(self.make_out_name("debugger.script"));
333
334            let debugger_opts: &[&OsStr] =
335                &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
336
337            let mut gdb = Command::new(self.config.gdb.as_ref().unwrap());
338            let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
339                format!("{pp}:{rust_pp_module_abs_path}")
340            } else {
341                rust_pp_module_abs_path
342            };
343            gdb.args(debugger_opts).env("PYTHONPATH", pythonpath);
344
345            debugger_run_result =
346                self.compose_and_run(gdb, self.config.run_lib_path.to_str().unwrap(), None, None);
347        }
348
349        if !debugger_run_result.status.success() {
350            self.fatal_proc_rec("gdb failed to execute", &debugger_run_result);
351        }
352
353        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
354            self.fatal_proc_rec(&e, &debugger_run_result);
355        }
356    }
357
358    fn run_debuginfo_lldb_test(&self) {
359        if self.config.lldb_python_dir.is_none() {
360            self.fatal("Can't run LLDB test because LLDB's python path is not set.");
361        }
362
363        let config = Config {
364            target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags),
365            host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags),
366            ..self.config.clone()
367        };
368
369        let test_cx = TestCx { config: &config, ..*self };
370
371        test_cx.run_debuginfo_lldb_test_no_opt();
372    }
373
374    fn run_debuginfo_lldb_test_no_opt(&self) {
375        // compile test file (it should have 'compile-flags:-g' in the header)
376        let should_run = self.run_if_enabled();
377        let compile_result = self.compile_test(should_run, Emit::None);
378        if !compile_result.status.success() {
379            self.fatal_proc_rec("compilation failed!", &compile_result);
380        }
381        if let WillExecute::Disabled = should_run {
382            return;
383        }
384
385        let exe_file = self.make_exe_name();
386
387        match self.config.lldb_version {
388            Some(ref version) => {
389                println!("NOTE: compiletest thinks it is using LLDB version {}", version);
390            }
391            _ => {
392                println!(
393                    "NOTE: compiletest does not know which version of \
394                     LLDB it is using"
395                );
396            }
397        }
398
399        // Parse debugger commands etc from test files
400        let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, "lldb")
401            .unwrap_or_else(|e| self.fatal(&e));
402
403        // Write debugger script:
404        // We don't want to hang when calling `quit` while the process is still running
405        let mut script_str = String::from("settings set auto-confirm true\n");
406
407        // Make LLDB emit its version, so we have it documented in the test output
408        script_str.push_str("version\n");
409
410        // Switch LLDB into "Rust mode"
411        let rust_src_root =
412            self.config.find_rust_src_root().expect("Could not find Rust source root");
413        let rust_pp_module_rel_path = Path::new("./src/etc");
414        let rust_pp_module_abs_path = rust_src_root.join(rust_pp_module_rel_path);
415
416        script_str.push_str(&format!(
417            "command script import {}/lldb_lookup.py\n",
418            rust_pp_module_abs_path.to_str().unwrap()
419        ));
420        File::open(rust_pp_module_abs_path.join("lldb_commands"))
421            .and_then(|mut file| file.read_to_string(&mut script_str))
422            .expect("Failed to read lldb_commands");
423
424        // Set breakpoints on every line that contains the string "#break"
425        let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy();
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        // Append the other commands
434        for line in &dbg_cmds.commands {
435            script_str.push_str(line);
436            script_str.push('\n');
437        }
438
439        // Finally, quit the debugger
440        script_str.push_str("\nquit\n");
441
442        // Write the script into a file
443        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 LLDB execute the script via lldb_batchmode.py
448        let debugger_run_result = self.run_lldb(&exe_file, &debugger_script, &rust_src_root);
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        test_executable: &Path,
462        debugger_script: &Path,
463        rust_src_root: &Path,
464    ) -> ProcRes {
465        // Prepare the lldb_batchmode which executes the debugger script
466        let lldb_script_path = rust_src_root.join("src/etc/lldb_batchmode.py");
467        let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
468            format!("{pp}:{}", self.config.lldb_python_dir.as_ref().unwrap())
469        } else {
470            self.config.lldb_python_dir.as_ref().unwrap().to_string()
471        };
472        self.run_command_to_procres(
473            Command::new(&self.config.python)
474                .arg(&lldb_script_path)
475                .arg(test_executable)
476                .arg(debugger_script)
477                .env("PYTHONUNBUFFERED", "1") // Help debugging #78665
478                .env("PYTHONPATH", pythonpath),
479        )
480    }
481
482    fn cleanup_debug_info_options(&self, options: &Vec<String>) -> Vec<String> {
483        // Remove options that are either unwanted (-O) or may lead to duplicates due to RUSTFLAGS.
484        let options_to_remove = ["-O".to_owned(), "-g".to_owned(), "--debuginfo".to_owned()];
485
486        options.iter().filter(|x| !options_to_remove.contains(x)).cloned().collect()
487    }
488}