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_pp_module_abs_path = self.config.src_root.join("src").join("etc");
261            let rust_pp_module_abs_path = rust_pp_module_abs_path.to_str().unwrap();
262            // write debugger script
263            let mut script_str = String::with_capacity(2048);
264            script_str.push_str(&format!("set charset {}\n", Self::charset()));
265            script_str.push_str("show version\n");
266
267            match self.config.gdb_version {
268                Some(version) => {
269                    println!("NOTE: compiletest thinks it is using GDB version {}", version);
270
271                    if version > extract_gdb_version("7.4").unwrap() {
272                        // Add the directory containing the pretty printers to
273                        // GDB's script auto loading safe path
274                        script_str.push_str(&format!(
275                            "add-auto-load-safe-path {}\n",
276                            rust_pp_module_abs_path.replace(r"\", r"\\")
277                        ));
278
279                        let output_base_dir = self.output_base_dir().to_str().unwrap().to_owned();
280
281                        // Add the directory containing the output binary to
282                        // include embedded pretty printers to GDB's script
283                        // auto loading safe path
284                        script_str.push_str(&format!(
285                            "add-auto-load-safe-path {}\n",
286                            output_base_dir.replace(r"\", r"\\")
287                        ));
288                    }
289                }
290                _ => {
291                    println!(
292                        "NOTE: compiletest does not know which version of \
293                         GDB it is using"
294                    );
295                }
296            }
297
298            // The following line actually doesn't have to do anything with
299            // pretty printing, it just tells GDB to print values on one line:
300            script_str.push_str("set print pretty off\n");
301
302            // Add the pretty printer directory to GDB's source-file search path
303            script_str
304                .push_str(&format!("directory {}\n", rust_pp_module_abs_path.replace(r"\", r"\\")));
305
306            // Load the target executable
307            script_str
308                .push_str(&format!("file {}\n", exe_file.to_str().unwrap().replace(r"\", r"\\")));
309
310            // Force GDB to print values in the Rust format.
311            script_str.push_str("set language rust\n");
312
313            // Add line breakpoints
314            for line in &dbg_cmds.breakpoint_lines {
315                script_str.push_str(&format!(
316                    "break '{}':{}\n",
317                    self.testpaths.file.file_name().unwrap().to_string_lossy(),
318                    *line
319                ));
320            }
321
322            script_str.push_str(&cmds);
323            script_str.push_str("\nquit\n");
324
325            debug!("script_str = {}", script_str);
326            self.dump_output_file(&script_str, "debugger.script");
327
328            let mut debugger_script = OsString::from("-command=");
329            debugger_script.push(self.make_out_name("debugger.script"));
330
331            let debugger_opts: &[&OsStr] =
332                &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
333
334            let mut gdb = Command::new(self.config.gdb.as_ref().unwrap());
335            let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
336                format!("{pp}:{rust_pp_module_abs_path}")
337            } else {
338                rust_pp_module_abs_path.to_string()
339            };
340            gdb.args(debugger_opts).env("PYTHONPATH", pythonpath);
341
342            debugger_run_result =
343                self.compose_and_run(gdb, self.config.run_lib_path.to_str().unwrap(), None, None);
344        }
345
346        if !debugger_run_result.status.success() {
347            self.fatal_proc_rec("gdb failed to execute", &debugger_run_result);
348        }
349
350        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
351            self.fatal_proc_rec(&e, &debugger_run_result);
352        }
353    }
354
355    fn run_debuginfo_lldb_test(&self) {
356        if self.config.lldb_python_dir.is_none() {
357            self.fatal("Can't run LLDB test because LLDB's python path is not set.");
358        }
359
360        let config = Config {
361            target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags),
362            host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags),
363            ..self.config.clone()
364        };
365
366        let test_cx = TestCx { config: &config, ..*self };
367
368        test_cx.run_debuginfo_lldb_test_no_opt();
369    }
370
371    fn run_debuginfo_lldb_test_no_opt(&self) {
372        // compile test file (it should have 'compile-flags:-g' in the header)
373        let should_run = self.run_if_enabled();
374        let compile_result = self.compile_test(should_run, Emit::None);
375        if !compile_result.status.success() {
376            self.fatal_proc_rec("compilation failed!", &compile_result);
377        }
378        if let WillExecute::Disabled = should_run {
379            return;
380        }
381
382        let exe_file = self.make_exe_name();
383
384        match self.config.lldb_version {
385            Some(ref version) => {
386                println!("NOTE: compiletest thinks it is using LLDB version {}", version);
387            }
388            _ => {
389                println!(
390                    "NOTE: compiletest does not know which version of \
391                     LLDB it is using"
392                );
393            }
394        }
395
396        // Parse debugger commands etc from test files
397        let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, "lldb")
398            .unwrap_or_else(|e| self.fatal(&e));
399
400        // Write debugger script:
401        // We don't want to hang when calling `quit` while the process is still running
402        let mut script_str = String::from("settings set auto-confirm true\n");
403
404        // Make LLDB emit its version, so we have it documented in the test output
405        script_str.push_str("version\n");
406
407        // Switch LLDB into "Rust mode".
408        let rust_pp_module_abs_path = self.config.src_root.join("src/etc");
409
410        script_str.push_str(&format!(
411            "command script import {}/lldb_lookup.py\n",
412            rust_pp_module_abs_path.to_str().unwrap()
413        ));
414        File::open(rust_pp_module_abs_path.join("lldb_commands"))
415            .and_then(|mut file| file.read_to_string(&mut script_str))
416            .expect("Failed to read lldb_commands");
417
418        // Set breakpoints on every line that contains the string "#break"
419        let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy();
420        for line in &dbg_cmds.breakpoint_lines {
421            script_str.push_str(&format!(
422                "breakpoint set --file '{}' --line {}\n",
423                source_file_name, line
424            ));
425        }
426
427        // Append the other commands
428        for line in &dbg_cmds.commands {
429            script_str.push_str(line);
430            script_str.push('\n');
431        }
432
433        // Finally, quit the debugger
434        script_str.push_str("\nquit\n");
435
436        // Write the script into a file
437        debug!("script_str = {}", script_str);
438        self.dump_output_file(&script_str, "debugger.script");
439        let debugger_script = self.make_out_name("debugger.script");
440
441        // Let LLDB execute the script via lldb_batchmode.py
442        let debugger_run_result = self.run_lldb(&exe_file, &debugger_script);
443
444        if !debugger_run_result.status.success() {
445            self.fatal_proc_rec("Error while running LLDB", &debugger_run_result);
446        }
447
448        if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
449            self.fatal_proc_rec(&e, &debugger_run_result);
450        }
451    }
452
453    fn run_lldb(&self, test_executable: &Path, debugger_script: &Path) -> ProcRes {
454        // Prepare the lldb_batchmode which executes the debugger script
455        let lldb_script_path = self.config.src_root.join("src/etc/lldb_batchmode.py");
456        let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
457            format!("{pp}:{}", self.config.lldb_python_dir.as_ref().unwrap())
458        } else {
459            self.config.lldb_python_dir.clone().unwrap()
460        };
461        self.run_command_to_procres(
462            Command::new(&self.config.python)
463                .arg(&lldb_script_path)
464                .arg(test_executable)
465                .arg(debugger_script)
466                .env("PYTHONUNBUFFERED", "1") // Help debugging #78665
467                .env("PYTHONPATH", pythonpath),
468        )
469    }
470
471    fn cleanup_debug_info_options(&self, options: &Vec<String>) -> Vec<String> {
472        // Remove options that are either unwanted (-O) or may lead to duplicates due to RUSTFLAGS.
473        let options_to_remove = ["-O".to_owned(), "-g".to_owned(), "--debuginfo".to_owned()];
474
475        options.iter().filter(|x| !options_to_remove.contains(x)).cloned().collect()
476    }
477}