use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::path::Path;
use std::process::{Command, Output, Stdio};
use tracing::debug;
use super::debugger::DebuggerCommands;
use super::{Debugger, Emit, ProcRes, TestCx, Truncated, WillExecute};
use crate::common::Config;
use crate::debuggers::{extract_gdb_version, is_android_gdb_target};
use crate::util::logv;
impl TestCx<'_> {
pub(super) fn run_debuginfo_test(&self) {
match self.config.debugger.unwrap() {
Debugger::Cdb => self.run_debuginfo_cdb_test(),
Debugger::Gdb => self.run_debuginfo_gdb_test(),
Debugger::Lldb => self.run_debuginfo_lldb_test(),
}
}
fn run_debuginfo_cdb_test(&self) {
let config = Config {
target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags),
host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags),
..self.config.clone()
};
let test_cx = TestCx { config: &config, ..*self };
test_cx.run_debuginfo_cdb_test_no_opt();
}
fn run_debuginfo_cdb_test_no_opt(&self) {
let exe_file = self.make_exe_name();
let pdb_file = exe_file.with_extension(".pdb");
if pdb_file.exists() {
std::fs::remove_file(pdb_file).unwrap();
}
let should_run = self.run_if_enabled();
let compile_result = self.compile_test(should_run, Emit::None);
if !compile_result.status.success() {
self.fatal_proc_rec("compilation failed!", &compile_result);
}
if let WillExecute::Disabled = should_run {
return;
}
let prefixes = {
static PREFIXES: &[&str] = &["cdb", "cdbg"];
PREFIXES
};
let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, prefixes)
.unwrap_or_else(|e| self.fatal(&e));
let mut script_str = String::with_capacity(2048);
script_str.push_str("version\n"); script_str.push_str(".nvlist\n"); let mut js_extension = self.testpaths.file.clone();
js_extension.set_extension("cdb.js");
if js_extension.exists() {
script_str.push_str(&format!(".scriptload \"{}\"\n", js_extension.to_string_lossy()));
}
let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy();
for line in &dbg_cmds.breakpoint_lines {
script_str.push_str(&format!("bp `{}:{}`\n", source_file_name, line));
}
for line in &dbg_cmds.commands {
script_str.push_str(line);
script_str.push('\n');
}
script_str.push_str("qq\n"); debug!("script_str = {}", script_str);
self.dump_output_file(&script_str, "debugger.script");
let debugger_script = self.make_out_name("debugger.script");
let cdb_path = &self.config.cdb.as_ref().unwrap();
let mut cdb = Command::new(cdb_path);
cdb.arg("-lines") .arg("-cf")
.arg(&debugger_script)
.arg(&exe_file);
let debugger_run_result = self.compose_and_run(
cdb,
self.config.run_lib_path.to_str().unwrap(),
None, None, );
if !debugger_run_result.status.success() {
self.fatal_proc_rec("Error while running CDB", &debugger_run_result);
}
if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
self.fatal_proc_rec(&e, &debugger_run_result);
}
}
fn run_debuginfo_gdb_test(&self) {
let config = Config {
target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags),
host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags),
..self.config.clone()
};
let test_cx = TestCx { config: &config, ..*self };
test_cx.run_debuginfo_gdb_test_no_opt();
}
fn run_debuginfo_gdb_test_no_opt(&self) {
let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, &["gdb"])
.unwrap_or_else(|e| self.fatal(&e));
let mut cmds = dbg_cmds.commands.join("\n");
let should_run = self.run_if_enabled();
let compiler_run_result = self.compile_test(should_run, Emit::None);
if !compiler_run_result.status.success() {
self.fatal_proc_rec("compilation failed!", &compiler_run_result);
}
if let WillExecute::Disabled = should_run {
return;
}
let exe_file = self.make_exe_name();
let debugger_run_result;
if is_android_gdb_target(&self.config.target) {
cmds = cmds.replace("run", "continue");
let tool_path = match self.config.android_cross_path.to_str() {
Some(x) => x.to_owned(),
None => self.fatal("cannot find android cross path"),
};
let mut script_str = String::with_capacity(2048);
script_str.push_str(&format!("set charset {}\n", Self::charset()));
script_str.push_str(&format!("set sysroot {}\n", tool_path));
script_str.push_str(&format!("file {}\n", exe_file.to_str().unwrap()));
script_str.push_str("target remote :5039\n");
script_str.push_str(&format!(
"set solib-search-path \
./{}/stage2/lib/rustlib/{}/lib/\n",
self.config.host, self.config.target
));
for line in &dbg_cmds.breakpoint_lines {
script_str.push_str(
format!(
"break {:?}:{}\n",
self.testpaths.file.file_name().unwrap().to_string_lossy(),
*line
)
.as_str(),
);
}
script_str.push_str(&cmds);
script_str.push_str("\nquit\n");
debug!("script_str = {}", script_str);
self.dump_output_file(&script_str, "debugger.script");
let adb_path = &self.config.adb_path;
Command::new(adb_path)
.arg("push")
.arg(&exe_file)
.arg(&self.config.adb_test_dir)
.status()
.unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
Command::new(adb_path)
.args(&["forward", "tcp:5039", "tcp:5039"])
.status()
.unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
let adb_arg = format!(
"export LD_LIBRARY_PATH={}; \
gdbserver{} :5039 {}/{}",
self.config.adb_test_dir.clone(),
if self.config.target.contains("aarch64") { "64" } else { "" },
self.config.adb_test_dir.clone(),
exe_file.file_name().unwrap().to_str().unwrap()
);
debug!("adb arg: {}", adb_arg);
let mut adb = Command::new(adb_path)
.args(&["shell", &adb_arg])
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.unwrap_or_else(|e| panic!("failed to exec `{adb_path:?}`: {e:?}"));
let mut stdout = BufReader::new(adb.stdout.take().unwrap());
let mut line = String::new();
loop {
line.truncate(0);
stdout.read_line(&mut line).unwrap();
if line.starts_with("Listening on port 5039") {
break;
}
}
drop(stdout);
let mut debugger_script = OsString::from("-command=");
debugger_script.push(self.make_out_name("debugger.script"));
let debugger_opts: &[&OsStr] =
&["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
let gdb_path = self.config.gdb.as_ref().unwrap();
let Output { status, stdout, stderr } = Command::new(&gdb_path)
.args(debugger_opts)
.output()
.unwrap_or_else(|e| panic!("failed to exec `{gdb_path:?}`: {e:?}"));
let cmdline = {
let mut gdb = Command::new(&format!("{}-gdb", self.config.target));
gdb.args(debugger_opts);
let cmdline = self.make_cmdline(&gdb, "");
logv(self.config, format!("executing {}", cmdline));
cmdline
};
debugger_run_result = ProcRes {
status,
stdout: String::from_utf8(stdout).unwrap(),
stderr: String::from_utf8(stderr).unwrap(),
truncated: Truncated::No,
cmdline,
};
if adb.kill().is_err() {
println!("Adb process is already finished.");
}
} else {
let rust_src_root =
self.config.find_rust_src_root().expect("Could not find Rust source root");
let rust_pp_module_rel_path = Path::new("./src/etc");
let rust_pp_module_abs_path =
rust_src_root.join(rust_pp_module_rel_path).to_str().unwrap().to_owned();
let mut script_str = String::with_capacity(2048);
script_str.push_str(&format!("set charset {}\n", Self::charset()));
script_str.push_str("show version\n");
match self.config.gdb_version {
Some(version) => {
println!("NOTE: compiletest thinks it is using GDB version {}", version);
if version > extract_gdb_version("7.4").unwrap() {
script_str.push_str(&format!(
"add-auto-load-safe-path {}\n",
rust_pp_module_abs_path.replace(r"\", r"\\")
));
let output_base_dir = self.output_base_dir().to_str().unwrap().to_owned();
script_str.push_str(&format!(
"add-auto-load-safe-path {}\n",
output_base_dir.replace(r"\", r"\\")
));
}
}
_ => {
println!(
"NOTE: compiletest does not know which version of \
GDB it is using"
);
}
}
script_str.push_str("set print pretty off\n");
script_str
.push_str(&format!("directory {}\n", rust_pp_module_abs_path.replace(r"\", r"\\")));
script_str
.push_str(&format!("file {}\n", exe_file.to_str().unwrap().replace(r"\", r"\\")));
script_str.push_str("set language rust\n");
for line in &dbg_cmds.breakpoint_lines {
script_str.push_str(&format!(
"break '{}':{}\n",
self.testpaths.file.file_name().unwrap().to_string_lossy(),
*line
));
}
script_str.push_str(&cmds);
script_str.push_str("\nquit\n");
debug!("script_str = {}", script_str);
self.dump_output_file(&script_str, "debugger.script");
let mut debugger_script = OsString::from("-command=");
debugger_script.push(self.make_out_name("debugger.script"));
let debugger_opts: &[&OsStr] =
&["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script];
let mut gdb = Command::new(self.config.gdb.as_ref().unwrap());
let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
format!("{pp}:{rust_pp_module_abs_path}")
} else {
rust_pp_module_abs_path
};
gdb.args(debugger_opts).env("PYTHONPATH", pythonpath);
debugger_run_result =
self.compose_and_run(gdb, self.config.run_lib_path.to_str().unwrap(), None, None);
}
if !debugger_run_result.status.success() {
self.fatal_proc_rec("gdb failed to execute", &debugger_run_result);
}
if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
self.fatal_proc_rec(&e, &debugger_run_result);
}
}
fn run_debuginfo_lldb_test(&self) {
if self.config.lldb_python_dir.is_none() {
self.fatal("Can't run LLDB test because LLDB's python path is not set.");
}
let config = Config {
target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags),
host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags),
..self.config.clone()
};
let test_cx = TestCx { config: &config, ..*self };
test_cx.run_debuginfo_lldb_test_no_opt();
}
fn run_debuginfo_lldb_test_no_opt(&self) {
let should_run = self.run_if_enabled();
let compile_result = self.compile_test(should_run, Emit::None);
if !compile_result.status.success() {
self.fatal_proc_rec("compilation failed!", &compile_result);
}
if let WillExecute::Disabled = should_run {
return;
}
let exe_file = self.make_exe_name();
match self.config.lldb_version {
Some(ref version) => {
println!("NOTE: compiletest thinks it is using LLDB version {}", version);
}
_ => {
println!(
"NOTE: compiletest does not know which version of \
LLDB it is using"
);
}
}
let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, &["lldb"])
.unwrap_or_else(|e| self.fatal(&e));
let mut script_str = String::from("settings set auto-confirm true\n");
script_str.push_str("version\n");
let rust_src_root =
self.config.find_rust_src_root().expect("Could not find Rust source root");
let rust_pp_module_rel_path = Path::new("./src/etc");
let rust_pp_module_abs_path = rust_src_root.join(rust_pp_module_rel_path);
script_str.push_str(&format!(
"command script import {}/lldb_lookup.py\n",
rust_pp_module_abs_path.to_str().unwrap()
));
File::open(rust_pp_module_abs_path.join("lldb_commands"))
.and_then(|mut file| file.read_to_string(&mut script_str))
.expect("Failed to read lldb_commands");
let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy();
for line in &dbg_cmds.breakpoint_lines {
script_str.push_str(&format!(
"breakpoint set --file '{}' --line {}\n",
source_file_name, line
));
}
for line in &dbg_cmds.commands {
script_str.push_str(line);
script_str.push('\n');
}
script_str.push_str("\nquit\n");
debug!("script_str = {}", script_str);
self.dump_output_file(&script_str, "debugger.script");
let debugger_script = self.make_out_name("debugger.script");
let debugger_run_result = self.run_lldb(&exe_file, &debugger_script, &rust_src_root);
if !debugger_run_result.status.success() {
self.fatal_proc_rec("Error while running LLDB", &debugger_run_result);
}
if let Err(e) = dbg_cmds.check_output(&debugger_run_result) {
self.fatal_proc_rec(&e, &debugger_run_result);
}
}
fn run_lldb(
&self,
test_executable: &Path,
debugger_script: &Path,
rust_src_root: &Path,
) -> ProcRes {
let lldb_script_path = rust_src_root.join("src/etc/lldb_batchmode.py");
let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
format!("{pp}:{}", self.config.lldb_python_dir.as_ref().unwrap())
} else {
self.config.lldb_python_dir.as_ref().unwrap().to_string()
};
self.run_command_to_procres(
Command::new(&self.config.python)
.arg(&lldb_script_path)
.arg(test_executable)
.arg(debugger_script)
.env("PYTHONUNBUFFERED", "1") .env("PYTHONPATH", pythonpath),
)
}
fn cleanup_debug_info_options(&self, options: &Vec<String>) -> Vec<String> {
let options_to_remove = ["-O".to_owned(), "-g".to_owned(), "--debuginfo".to_owned()];
options.iter().filter(|x| !options_to_remove.contains(x)).cloned().collect()
}
}