compiletest/
debuggers.rs

1use std::env;
2use std::process::Command;
3use std::sync::Arc;
4
5use camino::Utf8Path;
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.as_ref()?;
53
54    Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
55}
56
57pub(crate) fn query_cdb_version(cdb: &Utf8Path) -> Option<[u16; 4]> {
58    let mut version = None;
59    if let Ok(output) = Command::new(cdb).arg("/version").output() {
60        if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
61            version = extract_cdb_version(&first_line);
62        }
63    }
64    version
65}
66
67pub(crate) fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
68    // Example full_version_line: "cdb version 10.0.18362.1"
69    let version = full_version_line.rsplit(' ').next()?;
70    let mut components = version.split('.');
71    let major: u16 = components.next().unwrap().parse().unwrap();
72    let minor: u16 = components.next().unwrap().parse().unwrap();
73    let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
74    let build: u16 = components.next().unwrap_or("0").parse().unwrap();
75    Some([major, minor, patch, build])
76}
77
78pub(crate) fn query_gdb_version(gdb: &Utf8Path) -> Option<u32> {
79    let mut version_line = None;
80    if let Ok(output) = Command::new(&gdb).arg("--version").output() {
81        if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
82            version_line = Some(first_line.to_string());
83        }
84    }
85
86    let version = match version_line {
87        Some(line) => extract_gdb_version(&line),
88        None => return None,
89    };
90
91    version
92}
93
94pub(crate) fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
95    let full_version_line = full_version_line.trim();
96
97    // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
98    // of the ? sections being optional
99
100    // We will parse up to 3 digits for each component, ignoring the date
101
102    // We skip text in parentheses.  This avoids accidentally parsing
103    // the openSUSE version, which looks like:
104    //  GNU gdb (GDB; openSUSE Leap 15.0) 8.1
105    // This particular form is documented in the GNU coding standards:
106    // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
107
108    let unbracketed_part = full_version_line.split('[').next().unwrap();
109    let mut splits = unbracketed_part.trim_end().rsplit(' ');
110    let version_string = splits.next().unwrap();
111
112    let mut splits = version_string.split('.');
113    let major = splits.next().unwrap();
114    let minor = splits.next().unwrap();
115    let patch = splits.next();
116
117    let major: u32 = major.parse().unwrap();
118    let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
119        None => {
120            let minor = minor.parse().unwrap();
121            let patch: u32 = match patch {
122                Some(patch) => match patch.find(not_a_digit) {
123                    None => patch.parse().unwrap(),
124                    Some(idx) if idx > 3 => 0,
125                    Some(idx) => patch[..idx].parse().unwrap(),
126                },
127                None => 0,
128            };
129            (minor, patch)
130        }
131        // There is no patch version after minor-date (e.g. "4-2012").
132        Some(idx) => {
133            let minor = minor[..idx].parse().unwrap();
134            (minor, 0)
135        }
136    };
137
138    Some(((major * 1000) + minor) * 1000 + patch)
139}
140
141/// Returns LLDB version
142pub(crate) fn extract_lldb_version(full_version_line: &str) -> Option<u32> {
143    // Extract the major LLDB version from the given version string.
144    // LLDB version strings are different for Apple and non-Apple platforms.
145    // The Apple variant looks like this:
146    //
147    // LLDB-179.5 (older versions)
148    // lldb-300.2.51 (new versions)
149    //
150    // We are only interested in the major version number, so this function
151    // will return `Some(179)` and `Some(300)` respectively.
152    //
153    // Upstream versions look like:
154    // lldb version 6.0.1
155    //
156    // There doesn't seem to be a way to correlate the Apple version
157    // with the upstream version, and since the tests were originally
158    // written against Apple versions, we make a fake Apple version by
159    // multiplying the first number by 100. This is a hack.
160
161    let full_version_line = full_version_line.trim();
162
163    if let Some(apple_ver) =
164        full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
165    {
166        if let Some(idx) = apple_ver.find(not_a_digit) {
167            let version: u32 = apple_ver[..idx].parse().unwrap();
168            return Some(version);
169        }
170    } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
171        if let Some(idx) = lldb_ver.find(not_a_digit) {
172            let version: u32 = lldb_ver[..idx].parse().ok()?;
173            return Some(version * 100);
174        }
175    }
176    None
177}
178
179fn not_a_digit(c: char) -> bool {
180    !c.is_ascii_digit()
181}