compiletest/
debuggers.rs

1use std::env;
2use std::process::Command;
3use std::sync::Arc;
4
5use camino::{Utf8Path, Utf8PathBuf};
6
7use crate::common::{Config, Debugger};
8
9pub(crate) fn configure_cdb(config: &Config) -> Option<Arc<Config>> {
10    config.cdb.as_ref()?;
11
12    Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() }))
13}
14
15pub(crate) fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
16    config.gdb_version?;
17
18    if config.matches_env("msvc") {
19        return None;
20    }
21
22    if config.remote_test_client.is_some() && !config.target.contains("android") {
23        println!(
24            "WARNING: debuginfo tests are not available when \
25             testing with remote"
26        );
27        return None;
28    }
29
30    if config.target.contains("android") {
31        println!(
32            "{} debug-info test uses tcp 5039 port.\
33             please reserve it",
34            config.target
35        );
36
37        // android debug-info test uses remote debugger so, we test 1 thread
38        // at once as they're all sharing the same TCP port to communicate
39        // over.
40        //
41        // we should figure out how to lift this restriction! (run them all
42        // on different ports allocated dynamically).
43        //
44        // SAFETY: at this point we are still single-threaded.
45        unsafe { env::set_var("RUST_TEST_THREADS", "1") };
46    }
47
48    Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() }))
49}
50
51pub(crate) fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
52    config.lldb_python_dir.as_ref()?;
53
54    // FIXME: this is super old
55    if let Some(350) = config.lldb_version {
56        println!(
57            "WARNING: The used version of LLDB (350) has a \
58             known issue that breaks debuginfo tests. See \
59             issue #32520 for more information. Skipping all \
60             LLDB-based tests!",
61        );
62        return None;
63    }
64
65    Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
66}
67
68/// Returns `true` if the given target is an Android target for the
69/// purposes of GDB testing.
70pub(crate) fn is_android_gdb_target(target: &str) -> bool {
71    matches!(
72        &target[..],
73        "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
74    )
75}
76
77/// Returns `true` if the given target is a MSVC target for the purposes of CDB testing.
78fn is_pc_windows_msvc_target(target: &str) -> bool {
79    target.ends_with("-pc-windows-msvc")
80}
81
82/// FIXME: this is very questionable...
83fn find_cdb(target: &str) -> Option<Utf8PathBuf> {
84    if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
85        return None;
86    }
87
88    let pf86 = Utf8PathBuf::from_path_buf(
89        env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?.into(),
90    )
91    .unwrap();
92    let cdb_arch = if cfg!(target_arch = "x86") {
93        "x86"
94    } else if cfg!(target_arch = "x86_64") {
95        "x64"
96    } else if cfg!(target_arch = "aarch64") {
97        "arm64"
98    } else if cfg!(target_arch = "arm") {
99        "arm"
100    } else {
101        return None; // No compatible CDB.exe in the Windows 10 SDK
102    };
103
104    let mut path = pf86;
105    path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
106    path.push(cdb_arch);
107    path.push(r"cdb.exe");
108
109    if !path.exists() {
110        return None;
111    }
112
113    Some(path)
114}
115
116/// Returns Path to CDB
117pub(crate) fn analyze_cdb(
118    cdb: Option<String>,
119    target: &str,
120) -> (Option<Utf8PathBuf>, Option<[u16; 4]>) {
121    let cdb = cdb.map(Utf8PathBuf::from).or_else(|| find_cdb(target));
122
123    let mut version = None;
124    if let Some(cdb) = cdb.as_ref() {
125        if let Ok(output) = Command::new(cdb).arg("/version").output() {
126            if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
127                version = extract_cdb_version(&first_line);
128            }
129        }
130    }
131
132    (cdb, version)
133}
134
135pub(crate) fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
136    // Example full_version_line: "cdb version 10.0.18362.1"
137    let version = full_version_line.rsplit(' ').next()?;
138    let mut components = version.split('.');
139    let major: u16 = components.next().unwrap().parse().unwrap();
140    let minor: u16 = components.next().unwrap().parse().unwrap();
141    let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
142    let build: u16 = components.next().unwrap_or("0").parse().unwrap();
143    Some([major, minor, patch, build])
144}
145
146/// Returns (Path to GDB, GDB Version)
147pub(crate) fn analyze_gdb(
148    gdb: Option<String>,
149    target: &str,
150    android_cross_path: &Utf8Path,
151) -> (Option<String>, Option<u32>) {
152    #[cfg(not(windows))]
153    const GDB_FALLBACK: &str = "gdb";
154    #[cfg(windows)]
155    const GDB_FALLBACK: &str = "gdb.exe";
156
157    let fallback_gdb = || {
158        if is_android_gdb_target(target) {
159            let mut gdb_path = android_cross_path.to_string();
160            gdb_path.push_str("/bin/gdb");
161            gdb_path
162        } else {
163            GDB_FALLBACK.to_owned()
164        }
165    };
166
167    let gdb = match gdb {
168        None => fallback_gdb(),
169        Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
170        Some(ref s) => s.to_owned(),
171    };
172
173    let mut version_line = None;
174    if let Ok(output) = Command::new(&gdb).arg("--version").output() {
175        if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
176            version_line = Some(first_line.to_string());
177        }
178    }
179
180    let version = match version_line {
181        Some(line) => extract_gdb_version(&line),
182        None => return (None, None),
183    };
184
185    (Some(gdb), version)
186}
187
188pub(crate) fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
189    let full_version_line = full_version_line.trim();
190
191    // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
192    // of the ? sections being optional
193
194    // We will parse up to 3 digits for each component, ignoring the date
195
196    // We skip text in parentheses.  This avoids accidentally parsing
197    // the openSUSE version, which looks like:
198    //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
199    // This particular form is documented in the GNU coding standards:
200    // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
201
202    let unbracketed_part = full_version_line.split('[').next().unwrap();
203    let mut splits = unbracketed_part.trim_end().rsplit(' ');
204    let version_string = splits.next().unwrap();
205
206    let mut splits = version_string.split('.');
207    let major = splits.next().unwrap();
208    let minor = splits.next().unwrap();
209    let patch = splits.next();
210
211    let major: u32 = major.parse().unwrap();
212    let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
213        None => {
214            let minor = minor.parse().unwrap();
215            let patch: u32 = match patch {
216                Some(patch) => match patch.find(not_a_digit) {
217                    None => patch.parse().unwrap(),
218                    Some(idx) if idx > 3 => 0,
219                    Some(idx) => patch[..idx].parse().unwrap(),
220                },
221                None => 0,
222            };
223            (minor, patch)
224        }
225        // There is no patch version after minor-date (e.g. "4-2012").
226        Some(idx) => {
227            let minor = minor[..idx].parse().unwrap();
228            (minor, 0)
229        }
230    };
231
232    Some(((major * 1000) + minor) * 1000 + patch)
233}
234
235/// Returns LLDB version
236pub(crate) fn extract_lldb_version(full_version_line: &str) -> Option<u32> {
237    // Extract the major LLDB version from the given version string.
238    // LLDB version strings are different for Apple and non-Apple platforms.
239    // The Apple variant looks like this:
240    //
241    // LLDB-179.5 (older versions)
242    // lldb-300.2.51 (new versions)
243    //
244    // We are only interested in the major version number, so this function
245    // will return `Some(179)` and `Some(300)` respectively.
246    //
247    // Upstream versions look like:
248    // lldb version 6.0.1
249    //
250    // There doesn't seem to be a way to correlate the Apple version
251    // with the upstream version, and since the tests were originally
252    // written against Apple versions, we make a fake Apple version by
253    // multiplying the first number by 100. This is a hack.
254
255    let full_version_line = full_version_line.trim();
256
257    if let Some(apple_ver) =
258        full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
259    {
260        if let Some(idx) = apple_ver.find(not_a_digit) {
261            let version: u32 = apple_ver[..idx].parse().unwrap();
262            return Some(version);
263        }
264    } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
265        if let Some(idx) = lldb_ver.find(not_a_digit) {
266            let version: u32 = lldb_ver[..idx].parse().ok()?;
267            return Some(version * 100);
268        }
269    }
270    None
271}
272
273fn not_a_digit(c: char) -> bool {
274    !c.is_ascii_digit()
275}