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::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 directive)
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));
77        }
78
79        // Set breakpoints on every line that contains the string "#break"
80        let source_file_name = self.testpaths.file.file_name().unwrap();
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.as_path(),
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 directive)
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            // write debugger script
155            let mut script_str = String::with_capacity(2048);
156            script_str.push_str(&format!("set charset {}\n", Self::charset()));
157            script_str.push_str(&format!("set sysroot {}\n", &self.config.android_cross_path));
158            script_str.push_str(&format!("file {}\n", exe_file));
159            script_str.push_str("target remote :5039\n");
160            script_str.push_str(&format!(
161                "set solib-search-path \
162                 ./{}/stage2/lib/rustlib/{}/lib/\n",
163                self.config.host, self.config.target
164            ));
165            for line in &dbg_cmds.breakpoint_lines {
166                script_str.push_str(
167                    format!("break {}:{}\n", self.testpaths.file.file_name().unwrap(), *line)
168                        .as_str(),
169                );
170            }
171            script_str.push_str(&cmds);
172            script_str.push_str("\nquit\n");
173
174            debug!("script_str = {}", script_str);
175            self.dump_output_file(&script_str, "debugger.script");
176
177            let adb_path = &self.config.adb_path;
178
179            Command::new(adb_path)
180                .arg("push")
181                .arg(&exe_file)
182                .arg(&self.config.adb_test_dir)
183                .status()
184                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
185
186            Command::new(adb_path)
187                .args(&["forward", "tcp:5039", "tcp:5039"])
188                .status()
189                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
190
191            let adb_arg = format!(
192                "export LD_LIBRARY_PATH={}; \
193                 gdbserver{} :5039 {}/{}",
194                self.config.adb_test_dir.clone(),
195                if self.config.target.contains("aarch64") { "64" } else { "" },
196                self.config.adb_test_dir.clone(),
197                exe_file.file_name().unwrap()
198            );
199
200            debug!("adb arg: {}", adb_arg);
201            let mut adb = Command::new(adb_path)
202                .args(&["shell", &adb_arg])
203                .stdout(Stdio::piped())
204                .stderr(Stdio::inherit())
205                .spawn()
206                .unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
207
208            // Wait for the gdbserver to print out "Listening on port ..."
209            // at which point we know that it's started and then we can
210            // execute the debugger below.
211            let mut stdout = BufReader::new(adb.stdout.take().unwrap());
212            let mut line = String::new();
213            loop {
214                line.truncate(0);
215                stdout.read_line(&mut line).unwrap();
216                if line.starts_with("Listening on port 5039") {
217                    break;
218                }
219            }
220            drop(stdout);
221
222            let mut debugger_script = OsString::from("-command=");
223            debugger_script.push(self.make_out_name("debugger.script"));
224            let debugger_opts: &[&OsStr] =
225                &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
226
227            let gdb_path = self.config.gdb.as_ref().unwrap();
228            let Output { status, stdout, stderr } = Command::new(&gdb_path)
229                .args(debugger_opts)
230                .output()
231                .unwrap_or_else(|e| panic!("failed to exec `{gdb_path:?}`: {e:?}"));
232            let cmdline = {
233                let mut gdb = Command::new(&format!("{}-gdb", self.config.target));
234                gdb.args(debugger_opts);
235                // FIXME(jieyouxu): don't pass an empty Path
236                let cmdline = self.make_cmdline(&gdb, Utf8Path::new(""));
237                logv(self.config, format!("executing {}", cmdline));
238                cmdline
239            };
240
241            debugger_run_result = ProcRes {
242                status,
243                stdout: String::from_utf8(stdout).unwrap(),
244                stderr: String::from_utf8(stderr).unwrap(),
245                truncated: Truncated::No,
246                cmdline,
247            };
248            if adb.kill().is_err() {
249                println!("Adb process is already finished.");
250            }
251        } else {
252            let rust_pp_module_abs_path = self.config.src_root.join("src").join("etc");
253            // write debugger script
254            let mut script_str = String::with_capacity(2048);
255            script_str.push_str(&format!("set charset {}\n", Self::charset()));
256            script_str.push_str("show version\n");
257
258            match self.config.gdb_version {
259                Some(version) => {
260                    println!("NOTE: compiletest thinks it is using GDB version {}", version);
261
262                    if !self.props.disable_gdb_pretty_printers
263                        && version > extract_gdb_version("7.4").unwrap()
264                    {
265                        // Add the directory containing the pretty printers to
266                        // GDB's script auto loading safe path
267                        script_str.push_str(&format!(
268                            "add-auto-load-safe-path {}\n",
269                            rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
270                        ));
271
272                        // Add the directory containing the output binary to
273                        // include embedded pretty printers to GDB's script
274                        // auto loading safe path
275                        script_str.push_str(&format!(
276                            "add-auto-load-safe-path {}\n",
277                            self.output_base_dir().as_str().replace(r"\", r"\\")
278                        ));
279                    }
280                }
281                _ => {
282                    println!(
283                        "NOTE: compiletest does not know which version of \
284                         GDB it is using"
285                    );
286                }
287            }
288
289            // The following line actually doesn't have to do anything with
290            // pretty printing, it just tells GDB to print values on one line:
291            script_str.push_str("set print pretty off\n");
292
293            // Add the pretty printer directory to GDB's source-file search path
294            script_str.push_str(&format!(
295                "directory {}\n",
296                rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
297            ));
298
299            // Load the target executable
300            script_str.push_str(&format!("file {}\n", exe_file.as_str().replace(r"\", r"\\")));
301
302            // Force GDB to print values in the Rust format.
303            script_str.push_str("set language rust\n");
304
305            // Add line breakpoints
306            for line in &dbg_cmds.breakpoint_lines {
307                script_str.push_str(&format!(
308                    "break '{}':{}\n",
309                    self.testpaths.file.file_name().unwrap(),
310                    *line
311                ));
312            }
313
314            script_str.push_str(&cmds);
315            script_str.push_str("\nquit\n");
316
317            debug!("script_str = {}", script_str);
318            self.dump_output_file(&script_str, "debugger.script");
319
320            let mut debugger_script = OsString::from("-command=");
321            debugger_script.push(self.make_out_name("debugger.script"));
322
323            let debugger_opts: &[&OsStr] =
324                &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
325
326            let mut gdb = Command::new(self.config.gdb.as_ref().unwrap());
327
328            // FIXME: we are propagating `PYTHONPATH` from the environment, not a compiletest flag!
329            let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
330                format!("{pp}:{rust_pp_module_abs_path}")
331            } else {
332                rust_pp_module_abs_path.to_string()
333            };
334            gdb.args(debugger_opts).env("PYTHONPATH", pythonpath);
335
336            debugger_run_result =
337                self.compose_and_run(gdb, self.config.run_lib_path.as_path(), None, None);
338        }
339
340        if !debugger_run_result.status.success() {
341            self.fatal_proc_rec("gdb failed to execute", &debugger_run_result);
342        }
343
344        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
345            self.fatal_proc_rec(&e, &debugger_run_result);
346        }
347    }
348
349    fn run_debuginfo_lldb_test(&self) {
350        if self.config.lldb_python_dir.is_none() {
351            self.fatal("Can't run LLDB test because LLDB's python path is not set.");
352        }
353
354        let config = Config {
355            target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags),
356            host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags),
357            ..self.config.clone()
358        };
359
360        let test_cx = TestCx { config: &config, ..*self };
361
362        test_cx.run_debuginfo_lldb_test_no_opt();
363    }
364
365    fn run_debuginfo_lldb_test_no_opt(&self) {
366        // compile test file (it should have 'compile-flags:-g' in the directive)
367        let should_run = self.run_if_enabled();
368        let compile_result = self.compile_test(should_run, Emit::None);
369        if !compile_result.status.success() {
370            self.fatal_proc_rec("compilation failed!", &compile_result);
371        }
372        if let WillExecute::Disabled = should_run {
373            return;
374        }
375
376        let exe_file = self.make_exe_name();
377
378        match self.config.lldb_version {
379            Some(ref version) => {
380                println!("NOTE: compiletest thinks it is using LLDB version {}", version);
381            }
382            _ => {
383                println!(
384                    "NOTE: compiletest does not know which version of \
385                     LLDB it is using"
386                );
387            }
388        }
389
390        // Parse debugger commands etc from test files
391        let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, "lldb")
392            .unwrap_or_else(|e| self.fatal(&e));
393
394        // Write debugger script:
395        // We don't want to hang when calling `quit` while the process is still running
396        let mut script_str = String::from("settings set auto-confirm true\n");
397
398        // Make LLDB emit its version, so we have it documented in the test output
399        script_str.push_str("version\n");
400
401        // Switch LLDB into "Rust mode".
402        let rust_pp_module_abs_path = self.config.src_root.join("src/etc");
403
404        script_str.push_str(&format!(
405            "command script import {}/lldb_lookup.py\n",
406            rust_pp_module_abs_path
407        ));
408        File::open(rust_pp_module_abs_path.join("lldb_commands"))
409            .and_then(|mut file| file.read_to_string(&mut script_str))
410            .expect("Failed to read lldb_commands");
411
412        // Set breakpoints on every line that contains the string "#break"
413        let source_file_name = self.testpaths.file.file_name().unwrap();
414        for line in &dbg_cmds.breakpoint_lines {
415            script_str.push_str(&format!(
416                "breakpoint set --file '{}' --line {}\n",
417                source_file_name, line
418            ));
419        }
420
421        // Append the other commands
422        for line in &dbg_cmds.commands {
423            script_str.push_str(line);
424            script_str.push('\n');
425        }
426
427        // Finally, quit the debugger
428        script_str.push_str("\nquit\n");
429
430        // Write the script into a file
431        debug!("script_str = {}", script_str);
432        self.dump_output_file(&script_str, "debugger.script");
433        let debugger_script = self.make_out_name("debugger.script");
434
435        // Let LLDB execute the script via lldb_batchmode.py
436        let debugger_run_result = self.run_lldb(&exe_file, &debugger_script);
437
438        if !debugger_run_result.status.success() {
439            self.fatal_proc_rec("Error while running LLDB", &debugger_run_result);
440        }
441
442        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
443            self.fatal_proc_rec(&e, &debugger_run_result);
444        }
445    }
446
447    fn run_lldb(&self, test_executable: &Utf8Path, debugger_script: &Utf8Path) -> ProcRes {
448        // Prepare the lldb_batchmode which executes the debugger script
449        let lldb_script_path = self.config.src_root.join("src/etc/lldb_batchmode.py");
450
451        // FIXME: `PYTHONPATH` takes precedence over the flag...?
452        let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
453            format!("{pp}:{}", self.config.lldb_python_dir.as_ref().unwrap())
454        } else {
455            self.config.lldb_python_dir.clone().unwrap()
456        };
457        self.run_command_to_procres(
458            Command::new(&self.config.python)
459                .arg(&lldb_script_path)
460                .arg(test_executable)
461                .arg(debugger_script)
462                .env("PYTHONUNBUFFERED", "1") // Help debugging #78665
463                .env("PYTHONPATH", pythonpath),
464        )
465    }
466
467    fn cleanup_debug_info_options(&self, options: &Vec<String>) -> Vec<String> {
468        // Remove options that are either unwanted (-O) or may lead to duplicates due to RUSTFLAGS.
469        let options_to_remove = ["-O".to_owned(), "-g".to_owned(), "--debuginfo".to_owned()];
470
471        options.iter().filter(|x| !options_to_remove.contains(x)).cloned().collect()
472    }
473}