gdb_command/
lib.rs

1//! # gdb-command
2//!
3//! `gdb-command` is a library providing API for manipulating gdb in batch mode. It supports:
4//!
5//! * Execution of target program (Local type).
6//! * Opening core of target program (Core type).
7//! * Attaching to remote process (Remote type).
8//!
9//! # Example
10//!
11//! ```rust
12//! use std::process::Command;
13//! use std::thread;
14//! use std::time::Duration;
15//! use gdb_command::*;
16//!
17//! fn main () -> error::Result<()> {
18//!     // Get stack trace from running program (stopped at crash)
19//!     let result = GdbCommand::new(&ExecType::Local(&["tests/bins/test_abort", "A"])).r().bt().launch()?;
20//!
21//!     // Get stack trace from core
22//!     let result = GdbCommand::new(
23//!             &ExecType::Core {target: "tests/bins/test_canary",
24//!                 core: "tests/bins/core.test_canary"})
25//!         .bt().launch()?;
26//!
27//!     // Get info from remote attach to process
28//!     let mut child = Command::new("tests/bins/test_callstack_remote")
29//!        .spawn()
30//!        .expect("failed to execute child");
31//!
32//!     thread::sleep(Duration::from_millis(10));
33//!
34//!     // To run this test: echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
35//!     let result = GdbCommand::new(&ExecType::Remote(&child.id().to_string()))
36//!         .bt()
37//!         .regs()
38//!         .disassembly()
39//!         .launch();
40//!     child.kill().unwrap();
41//!
42//!     Ok(())
43//! }
44//!
45//! ```
46
47use regex::Regex;
48use std::path::Path;
49use std::path::PathBuf;
50use std::process::Command;
51use std::process::Stdio;
52use std::time::Duration;
53
54use wait_timeout::ChildExt;
55
56pub mod error;
57pub mod mappings;
58pub mod memory;
59pub mod registers;
60pub mod siginfo;
61pub mod stacktrace;
62
63/// Type of `gdb` execution: Remote attach to process, local run with args, core.
64#[derive(Debug, Clone)]
65pub enum ExecType<'a> {
66    /// Run target program via `gdb` (--args) option.
67    Local(&'a [&'a str]),
68    /// Attach to process via `gdb` (-p) option.
69    Remote(&'a str),
70    /// Run target via `gdb` with coredump.
71    Core { target: &'a str, core: &'a str },
72}
73
74/// Struct contains information about arguments for `gdb` to run.
75#[derive(Debug)]
76pub struct GdbCommand<'a> {
77    /// Gdb execution type.
78    exec_type: ExecType<'a>,
79    /// Execution parameters (-ex).
80    args: Vec<String>,
81    /// Stdin file
82    stdin: Option<&'a PathBuf>,
83    /// Commands to execute for result.
84    commands_cnt: usize,
85    /// Target program timeout [sec] (disabled if equal to 0).
86    timeout: u64,
87}
88
89impl<'a> GdbCommand<'a> {
90    /// Construct `GdbCommand` from given ExecType.
91    /// # Arguments
92    ///
93    /// * `type` - execution type to run gdb.
94    pub fn new(exec_type: &'a ExecType) -> GdbCommand<'a> {
95        GdbCommand {
96            exec_type: exec_type.clone(),
97            args: Vec::new(),
98            stdin: None,
99            commands_cnt: 0,
100            timeout: 0,
101        }
102    }
103
104    /// Add stdin for executable.
105    /// You should call this method before using `r` method.
106    /// # Arguments
107    ///
108    /// * `file` - path to stdin file
109    pub fn stdin<T: Into<Option<&'a PathBuf>>>(&mut self, file: T) -> &'a mut GdbCommand {
110        self.stdin = file.into();
111        self
112    }
113
114    /// Add new gdb command to execute.
115    /// # Arguments
116    ///
117    /// * `cmd` - gdb command parameter (-ex).
118    pub fn ex<T: Into<String>>(&mut self, cmd: T) -> &'a mut GdbCommand {
119        self.args.push("-ex".to_string());
120        self.args
121            .push(format!("p \"gdb-command-start-{}\"", self.commands_cnt));
122        self.args.push("-ex".to_string());
123        self.args.push(cmd.into());
124        self.args.push("-ex".to_string());
125        self.args
126            .push(format!("p \"gdb-command-end-{}\"", self.commands_cnt));
127        self.commands_cnt += 1;
128        self
129    }
130
131    /// Run gdb with provided commands and return raw stdout.
132    pub fn raw(&self) -> error::Result<Vec<u8>> {
133        let mut gdb = Command::new("gdb");
134        let mut gdb_args = Vec::new();
135
136        // Add parameters according to execution
137        match &self.exec_type {
138            ExecType::Local(args) => {
139                // Check if binary exists (first element.)
140                if !Path::new(args[0]).exists() {
141                    return Err(error::Error::NoFile(args[0].to_string()));
142                }
143
144                gdb_args.push(args[0].to_string())
145            }
146            ExecType::Remote(pid) => {
147                gdb_args.push("-p".to_string());
148                gdb_args.push(pid.to_string());
149            }
150            ExecType::Core { target, core } => {
151                // Check if binary exists
152                if !Path::new(target).exists() {
153                    return Err(error::Error::NoFile(target.to_string()));
154                }
155
156                // Check if core exists
157                if !Path::new(core).exists() {
158                    return Err(error::Error::NoFile(core.to_string()));
159                }
160                gdb_args.push(target.to_string());
161                gdb_args.push(core.to_string());
162            }
163        }
164
165        gdb_args.append(&mut vec![
166            "--batch".to_string(),
167            "-ex".to_string(),
168            "set backtrace limit 2000".to_string(),
169            "-ex".to_string(),
170            "set disassembly-flavor intel".to_string(),
171            "-ex".to_string(),
172            "set filename-display absolute".to_string(),
173        ]);
174        gdb_args.append(&mut self.args.clone());
175        gdb.args(&gdb_args);
176
177        // Run gdb and get output
178        let output =
179        // If timeout is specified, spawn and check timeout
180        // Else get output
181        if self.timeout != 0 {
182            let mut child = gdb
183                .stderr(Stdio::piped())
184                .stdout(Stdio::piped())
185                .spawn()?;
186            if child
187                .wait_timeout(Duration::from_secs(self.timeout))
188                .unwrap()
189                .is_none()
190            {
191                let _ = child.kill();
192                return Err(error::Error::Gdb(format!(
193                    "Timeout error: {} sec exceeded",
194                    self.timeout,
195                )));
196            }
197            child.wait_with_output()
198        } else {
199            gdb.output()
200        };
201        if let Err(e) = output {
202            return Err(error::Error::Gdb(e.to_string()));
203        }
204        let mut output = output.unwrap();
205        output.stdout.append(&mut output.stderr.clone());
206        Ok(output.stdout)
207    }
208
209    /// Add command to run program
210    /// # Arguments
211    ///
212    /// * `file` - path to stdin file
213    pub fn r(&mut self) -> &'a mut GdbCommand {
214        self.args.push("-ex".to_string());
215        let mut run_command = "r".to_string();
216        if let ExecType::Local(args) = self.exec_type {
217            if args.len() > 1 {
218                run_command.push(' ');
219                run_command += &args[1..].join(" ");
220            }
221        }
222        if let Some(stdin) = self.stdin {
223            run_command += &format!(" < {}", stdin.display());
224        }
225        self.args.push(run_command);
226        self
227    }
228
229    /// Add command to continue execution
230    pub fn c(&mut self) -> &'a mut GdbCommand {
231        self.args.push("-ex".to_string());
232        self.args.push("c".to_string());
233        self
234    }
235
236    /// Add command to get backtrace (-ex bt)
237    pub fn bt(&mut self) -> &'a mut GdbCommand {
238        self.ex("bt")
239    }
240
241    /// Add command to get disassembly (-ex 'x/16i $pc')
242    pub fn disassembly(&mut self) -> &'a mut GdbCommand {
243        self.ex("x/16i $pc")
244    }
245
246    /// Add command to get registers (-ex 'i r')
247    pub fn regs(&mut self) -> &'a mut GdbCommand {
248        self.ex("i r")
249    }
250
251    /// Add command to get mappings (-ex 'info proc mappings')
252    pub fn mappings(&mut self) -> &'a mut GdbCommand {
253        self.ex("info proc mappings")
254    }
255
256    /// Add command to get cmd line.
257    pub fn cmdline(&mut self) -> &'a mut GdbCommand {
258        self.ex("info proc cmdline")
259    }
260
261    /// Add command to get environment variables
262    pub fn env(&mut self) -> &'a mut GdbCommand {
263        self.ex("show environment")
264    }
265
266    /// Add command to get process status
267    pub fn status(&mut self) -> &'a mut GdbCommand {
268        self.ex("info proc status")
269    }
270
271    /// Add command to get info
272    pub fn sources(&mut self) -> &'a mut GdbCommand {
273        self.ex("info sources")
274    }
275
276    /// Break at main
277    pub fn bmain(&mut self) -> &'a mut GdbCommand {
278        self.args.push("-ex".to_string());
279        self.args.push("b main".to_string());
280        self
281    }
282
283    /// Add timeout [sec]
284    pub fn timeout(&mut self, timeout: u64) -> &'a mut GdbCommand {
285        self.timeout = timeout;
286        self
287    }
288
289    /// Print lines from source file
290    ///
291    /// # Arguments
292    ///
293    /// * `location` - lines centered around the line specified by location.
294    /// If None then location is current line.
295    pub fn list<T: Into<Option<&'a str>>>(&mut self, location: T) -> &'a mut GdbCommand {
296        if let Some(loc) = location.into() {
297            self.ex(format!("list {loc}"))
298        } else {
299            self.ex("list")
300        }
301    }
302
303    /// Get memory contents (string of hex bytes)
304    ///
305    /// # Arguments
306    ///
307    /// * `expr` - expression that represents the start memory address.
308    ///
309    /// * `size` - size of memory in bytes to get.
310    pub fn mem<T: AsRef<str>>(&mut self, expr: T, size: usize) -> &'a mut GdbCommand {
311        self.ex(format!("x/{}bx {}", size, expr.as_ref()))
312    }
313
314    /// Add command to get siginfo
315    pub fn siginfo(&mut self) -> &'a mut GdbCommand {
316        self.ex("p/x $_siginfo")
317    }
318
319    /// Execute gdb and get result for each command.
320    /// # Return value.
321    ///
322    /// The return value is a vector of strings for each command executed.
323    pub fn launch(&self) -> error::Result<Vec<String>> {
324        // Get raw output from Gdb.
325        let stdout = self.raw()?;
326
327        // Split stdout into lines.
328        let output = String::from_utf8_lossy(&stdout);
329
330        self.parse(output)
331    }
332
333    /// Parse raw gdb output.
334    /// # Return value.
335    ///
336    /// The return value is a vector of strings for each command executed.
337    pub fn parse<T: AsRef<str>>(&self, output: T) -> error::Result<Vec<String>> {
338        let lines: Vec<String> = output.as_ref().lines().map(|l| l.to_string()).collect();
339
340        // Create empty results for each command.
341        let mut results = Vec::new();
342        (0..self.commands_cnt).for_each(|_| results.push(String::new()));
343
344        let re_start = Regex::new(r#"^\$\d+\s*=\s*"gdb-command-start-(\d+)"$"#).unwrap();
345        let re_end = Regex::new(r#"^\$\d+\s*=\s*"gdb-command-end-(\d+)"$"#).unwrap();
346        let mut start = 0;
347        let mut cmd_idx = 0;
348        for (i, line) in lines.iter().enumerate() {
349            // Find gdb-commnad-start guard and save command index.
350            if let Some(caps) = re_start.captures(line) {
351                cmd_idx = caps.get(1).unwrap().as_str().parse::<usize>()?;
352                start = i;
353            }
354
355            // Find gdb-commnad-end guard.
356            if let Some(caps) = re_end.captures(line) {
357                let end_idx = caps.get(1).unwrap().as_str().parse::<usize>()?;
358                // Check if gdb-commnad-end guard matches start guard.
359                if end_idx == cmd_idx && cmd_idx < self.commands_cnt {
360                    results[cmd_idx] = lines[start + 1..i].join("\n");
361                }
362            }
363        }
364        Ok(results)
365    }
366}