compiletest/runtest/
debuginfo.rs

1use std::ffi::{OsStr, OsString};
2use std::fs::File;
3use std::io::{BufRead, BufReader, Read};
4use std::process::{Command, Output, Stdio};
5
6use camino::Utf8Path;
7use tracing::debug;
8
9use super::debugger::DebuggerCommands;
10use super::{Debugger, Emit, ProcRes, TestCx, Truncated, WillExecute};
11use crate::debuggers::{extract_gdb_version, is_android_gdb_target};
12
13impl TestCx<'_> {
14    pub(super) fn run_debuginfo_test(&self) {
15        match self.config.debugger.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        // Existing PDB files are update in-place. When changing the debuginfo
26        // the compiler generates for something, this can lead to the situation
27        // where both the old and the new version of the debuginfo for the same
28        // type is present in the PDB, which is very confusing.
29        // Therefore we delete any existing PDB file before compiling the test
30        // case.
31        // FIXME: If can reliably detect that MSVC's link.exe is used, then
32        //        passing `/INCREMENTAL:NO` might be a cleaner way to do this.
33        let pdb_file = exe_file.with_extension(".pdb");
34        if pdb_file.exists() {
35            std::fs::remove_file(pdb_file).unwrap();
36        }
37
38        // compile test file (it should have 'compile-flags:-g' in the directive)
39        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        // Parse debugger commands etc from test files
49        let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, "cdb")
50            .unwrap_or_else(|e| self.fatal(&e));
51
52        // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-commands
53        let mut script_str = String::with_capacity(2048);
54        script_str.push_str("version\n"); // List CDB (and more) version info in test output
55        script_str.push_str(".nvlist\n"); // List loaded `*.natvis` files, bulk of custom MSVC debug
56
57        // If a .js file exists next to the source file being tested, then this is a JavaScript
58        // debugging extension that needs to be loaded.
59        let mut js_extension = self.testpaths.file.clone();
60        js_extension.set_extension("cdb.js");
61        if js_extension.exists() {
62            script_str.push_str(&format!(".scriptload \"{}\"\n", js_extension));
63        }
64
65        // Set breakpoints on every line that contains the string "#break"
66        let source_file_name = self.testpaths.file.file_name().unwrap();
67        for line in &dbg_cmds.breakpoint_lines {
68            script_str.push_str(&format!("bp `{}:{}`\n", source_file_name, line));
69        }
70
71        // Append the other `cdb-command:`s
72        for line in &dbg_cmds.commands {
73            script_str.push_str(line);
74            script_str.push('\n');
75        }
76
77        script_str.push_str("qq\n"); // Quit the debugger (including remote debugger, if any)
78
79        // Write the script into a file
80        debug!("script_str = {}", script_str);
81        self.dump_output_file(&script_str, "debugger.script");
82        let debugger_script = self.make_out_name("debugger.script");
83
84        let cdb_path = &self.config.cdb.as_ref().unwrap();
85        let mut cdb = Command::new(cdb_path);
86        cdb.arg("-lines") // Enable source line debugging.
87            .arg("-cf")
88            .arg(&debugger_script)
89            .arg(&exe_file);
90
91        let debugger_run_result = self.compose_and_run(
92            cdb,
93            self.config.run_lib_path.as_path(),
94            None, // aux_path
95            None, // input
96        );
97
98        if !debugger_run_result.status.success() {
99            self.fatal_proc_rec("Error while running CDB", &debugger_run_result);
100        }
101
102        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
103            self.fatal_proc_rec(&e, &debugger_run_result);
104        }
105    }
106
107    fn run_debuginfo_gdb_test(&self) {
108        let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, "gdb")
109            .unwrap_or_else(|e| self.fatal(&e));
110        let mut cmds = dbg_cmds.commands.join("\n");
111
112        // compile test file (it should have 'compile-flags:-g' in the directive)
113        let should_run = self.run_if_enabled();
114        let compiler_run_result = self.compile_test(should_run, Emit::None);
115        if !compiler_run_result.status.success() {
116            self.fatal_proc_rec("compilation failed!", &compiler_run_result);
117        }
118        if let WillExecute::Disabled = should_run {
119            return;
120        }
121
122        let exe_file = self.make_exe_name();
123
124        let debugger_run_result;
125        if is_android_gdb_target(&self.config.target) {
126            cmds = cmds.replace("run", "continue");
127
128            // write debugger script
129            let mut script_str = String::with_capacity(2048);
130            script_str.push_str(&format!("set charset {}\n", Self::charset()));
131            script_str.push_str(&format!("set sysroot {}\n", &self.config.android_cross_path));
132            script_str.push_str(&format!("file {}\n", exe_file));
133            script_str.push_str("target remote :5039\n");
134            script_str.push_str(&format!(
135                "set solib-search-path \
136                 ./{}/stage2/lib/rustlib/{}/lib/\n",
137                self.config.host, self.config.target
138            ));
139            for line in &dbg_cmds.breakpoint_lines {
140                script_str.push_str(
141                    format!("break {}:{}\n", self.testpaths.file.file_name().unwrap(), *line)
142                        .as_str(),
143                );
144            }
145            script_str.push_str(&cmds);
146            script_str.push_str("\nquit\n");
147
148            debug!("script_str = {}", script_str);
149            self.dump_output_file(&script_str, "debugger.script");
150
151            let adb_path = &self.config.adb_path;
152
153            Command::new(adb_path)
154                .arg("push")
155                .arg(&exe_file)
156                .arg(&self.config.adb_test_dir)
157                .status()
158                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
159
160            Command::new(adb_path)
161                .args(&["forward", "tcp:5039", "tcp:5039"])
162                .status()
163                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
164
165            let adb_arg = format!(
166                "export LD_LIBRARY_PATH={}; \
167                 gdbserver{} :5039 {}/{}",
168                self.config.adb_test_dir.clone(),
169                if self.config.target.contains("aarch64") { "64" } else { "" },
170                self.config.adb_test_dir.clone(),
171                exe_file.file_name().unwrap()
172            );
173
174            debug!("adb arg: {}", adb_arg);
175            let mut adb = Command::new(adb_path)
176                .args(&["shell", &adb_arg])
177                .stdout(Stdio::piped())
178                .stderr(Stdio::inherit())
179                .spawn()
180                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
181
182            // Wait for the gdbserver to print out "Listening on port ..."
183            // at which point we know that it's started and then we can
184            // execute the debugger below.
185            let mut stdout = BufReader::new(adb.stdout.take().unwrap());
186            let mut line = String::new();
187            loop {
188                line.truncate(0);
189                stdout.read_line(&mut line).unwrap();
190                if line.starts_with("Listening on port 5039") {
191                    break;
192                }
193            }
194            drop(stdout);
195
196            let mut debugger_script = OsString::from("-command=");
197            debugger_script.push(self.make_out_name("debugger.script"));
198            let debugger_opts: &[&OsStr] =
199                &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
200
201            let gdb_path = self.config.gdb.as_ref().unwrap();
202            let Output { status, stdout, stderr } = Command::new(&gdb_path)
203                .args(debugger_opts)
204                .output()
205                .unwrap_or_else(|e| panic!("failed to exec `{gdb_path:?}`: {e:?}"));
206            let cmdline = {
207                let mut gdb = Command::new(&format!("{}-gdb", self.config.target));
208                gdb.args(debugger_opts);
209                // FIXME(jieyouxu): don't pass an empty Path
210                let cmdline = self.make_cmdline(&gdb, Utf8Path::new(""));
211                self.logv(format_args!("executing {cmdline}"));
212                cmdline
213            };
214
215            debugger_run_result = ProcRes {
216                status,
217                stdout: String::from_utf8(stdout).unwrap(),
218                stderr: String::from_utf8(stderr).unwrap(),
219                truncated: Truncated::No,
220                cmdline,
221            };
222            if adb.kill().is_err() {
223                writeln!(self.stdout, "Adb process is already finished.");
224            }
225        } else {
226            let rust_pp_module_abs_path = self.config.src_root.join("src").join("etc");
227            // write debugger script
228            let mut script_str = String::with_capacity(2048);
229            script_str.push_str(&format!("set charset {}\n", Self::charset()));
230            script_str.push_str("show version\n");
231
232            match self.config.gdb_version {
233                Some(version) => {
234                    writeln!(
235                        self.stdout,
236                        "NOTE: compiletest thinks it is using GDB version {}",
237                        version
238                    );
239
240                    if !self.props.disable_gdb_pretty_printers
241                        && version > extract_gdb_version("7.4").unwrap()
242                    {
243                        // Add the directory containing the pretty printers to
244                        // GDB's script auto loading safe path
245                        script_str.push_str(&format!(
246                            "add-auto-load-safe-path {}\n",
247                            rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
248                        ));
249
250                        // Add the directory containing the output binary to
251                        // include embedded pretty printers to GDB's script
252                        // auto loading safe path
253                        script_str.push_str(&format!(
254                            "add-auto-load-safe-path {}\n",
255                            self.output_base_dir().as_str().replace(r"\", r"\\")
256                        ));
257                    }
258                }
259                _ => {
260                    writeln!(
261                        self.stdout,
262                        "NOTE: compiletest does not know which version of \
263                         GDB it is using"
264                    );
265                }
266            }
267
268            // The following line actually doesn't have to do anything with
269            // pretty printing, it just tells GDB to print values on one line:
270            script_str.push_str("set print pretty off\n");
271
272            // Add the pretty printer directory to GDB's source-file search path
273            script_str.push_str(&format!(
274                "directory {}\n",
275                rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
276            ));
277
278            // Load the target executable
279            script_str.push_str(&format!("file {}\n", exe_file.as_str().replace(r"\", r"\\")));
280
281            // Force GDB to print values in the Rust format.
282            script_str.push_str("set language rust\n");
283
284            // Add line breakpoints
285            for line in &dbg_cmds.breakpoint_lines {
286                script_str.push_str(&format!(
287                    "break '{}':{}\n",
288                    self.testpaths.file.file_name().unwrap(),
289                    *line
290                ));
291            }
292
293            script_str.push_str(&cmds);
294            script_str.push_str("\nquit\n");
295
296            debug!("script_str = {}", script_str);
297            self.dump_output_file(&script_str, "debugger.script");
298
299            let mut debugger_script = OsString::from("-command=");
300            debugger_script.push(self.make_out_name("debugger.script"));
301
302            let debugger_opts: &[&OsStr] =
303                &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
304
305            let mut gdb = Command::new(self.config.gdb.as_ref().unwrap());
306
307            // FIXME: we are propagating `PYTHONPATH` from the environment, not a compiletest flag!
308            let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
309                format!("{pp}:{rust_pp_module_abs_path}")
310            } else {
311                rust_pp_module_abs_path.to_string()
312            };
313            gdb.args(debugger_opts).env("PYTHONPATH", pythonpath);
314
315            debugger_run_result =
316                self.compose_and_run(gdb, self.config.run_lib_path.as_path(), None, None);
317        }
318
319        if !debugger_run_result.status.success() {
320            self.fatal_proc_rec("gdb failed to execute", &debugger_run_result);
321        }
322
323        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
324            self.fatal_proc_rec(&e, &debugger_run_result);
325        }
326    }
327
328    fn run_debuginfo_lldb_test(&self) {
329        if self.config.lldb_python_dir.is_none() {
330            self.fatal("Can't run LLDB test because LLDB's python path is not set.");
331        }
332
333        // compile test file (it should have 'compile-flags:-g' in the directive)
334        let should_run = self.run_if_enabled();
335        let compile_result = self.compile_test(should_run, Emit::None);
336        if !compile_result.status.success() {
337            self.fatal_proc_rec("compilation failed!", &compile_result);
338        }
339        if let WillExecute::Disabled = should_run {
340            return;
341        }
342
343        let exe_file = self.make_exe_name();
344
345        match self.config.lldb_version {
346            Some(ref version) => {
347                writeln!(
348                    self.stdout,
349                    "NOTE: compiletest thinks it is using LLDB version {}",
350                    version
351                );
352            }
353            _ => {
354                writeln!(
355                    self.stdout,
356                    "NOTE: compiletest does not know which version of \
357                     LLDB it is using"
358                );
359            }
360        }
361
362        // Parse debugger commands etc from test files
363        let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, "lldb")
364            .unwrap_or_else(|e| self.fatal(&e));
365
366        // Write debugger script:
367        // We don't want to hang when calling `quit` while the process is still running
368        let mut script_str = String::from("settings set auto-confirm true\n");
369
370        // macOS has a system for restricting access to files and peripherals
371        // called Transparency, Consent, and Control (TCC), which can be
372        // configured using the "Security & Privacy" tab in your settings.
373        //
374        // This system is provenance-based: if Terminal.app is given access to
375        // your Desktop, and you launch a binary within Terminal.app, the new
376        // binary also has access to the files on your Desktop.
377        //
378        // By default though, LLDB launches binaries in very isolated
379        // contexts. This includes resetting any TCC grants that might
380        // otherwise have been inherited.
381        //
382        // In effect, this means that if the developer has placed the rust
383        // repository under one of the system-protected folders, they will get
384        // a pop-up _for each binary_ asking for permissions to access the
385        // folder - quite annoying.
386        //
387        // To avoid this, we tell LLDB to spawn processes with TCC grants
388        // inherited from the parent process.
389        //
390        // Setting this also avoids unnecessary overhead from XprotectService
391        // when running with the Developer Tool grant.
392        //
393        // TIP: If you want to allow launching `lldb ~/Desktop/my_binary`
394        // without being prompted, you can put this in your `~/.lldbinit` too.
395        if self.config.host.contains("darwin") {
396            script_str.push_str("settings set target.inherit-tcc true\n");
397        }
398
399        // Make LLDB emit its version, so we have it documented in the test output
400        script_str.push_str("version\n");
401
402        // Switch LLDB into "Rust mode".
403        let rust_pp_module_abs_path = self.config.src_root.join("src/etc");
404
405        script_str.push_str(&format!(
406            "command script import {}/lldb_lookup.py\n",
407            rust_pp_module_abs_path
408        ));
409        File::open(rust_pp_module_abs_path.join("lldb_commands"))
410            .and_then(|mut file| file.read_to_string(&mut script_str))
411            .expect("Failed to read lldb_commands");
412
413        // Set breakpoints on every line that contains the string "#break"
414        let source_file_name = self.testpaths.file.file_name().unwrap();
415        for line in &dbg_cmds.breakpoint_lines {
416            script_str.push_str(&format!(
417                "breakpoint set --file '{}' --line {}\n",
418                source_file_name, line
419            ));
420        }
421
422        // Append the other commands
423        for line in &dbg_cmds.commands {
424            script_str.push_str(line);
425            script_str.push('\n');
426        }
427
428        // Finally, quit the debugger
429        script_str.push_str("\nquit\n");
430
431        // Write the script into a file
432        debug!("script_str = {}", script_str);
433        self.dump_output_file(&script_str, "debugger.script");
434        let debugger_script = self.make_out_name("debugger.script");
435
436        // Let LLDB execute the script via lldb_batchmode.py
437        let debugger_run_result = self.run_lldb(&exe_file, &debugger_script);
438
439        if !debugger_run_result.status.success() {
440            self.fatal_proc_rec("Error while running LLDB", &debugger_run_result);
441        }
442
443        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
444            self.fatal_proc_rec(&e, &debugger_run_result);
445        }
446    }
447
448    fn run_lldb(&self, test_executable: &Utf8Path, debugger_script: &Utf8Path) -> ProcRes {
449        // Prepare the lldb_batchmode which executes the debugger script
450        let lldb_script_path = self.config.src_root.join("src/etc/lldb_batchmode.py");
451
452        // FIXME: `PYTHONPATH` takes precedence over the flag...?
453        let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
454            format!("{pp}:{}", self.config.lldb_python_dir.as_ref().unwrap())
455        } else {
456            self.config.lldb_python_dir.clone().unwrap()
457        };
458        self.run_command_to_procres(
459            Command::new(&self.config.python)
460                .arg(&lldb_script_path)
461                .arg(test_executable)
462                .arg(debugger_script)
463                .env("PYTHONUNBUFFERED", "1") // Help debugging #78665
464                .env("PYTHONPATH", pythonpath),
465        )
466    }
467}