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