1use 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#[derive(Debug, Clone)]
65pub enum ExecType<'a> {
66 Local(&'a [&'a str]),
68 Remote(&'a str),
70 Core { target: &'a str, core: &'a str },
72}
73
74#[derive(Debug)]
76pub struct GdbCommand<'a> {
77 exec_type: ExecType<'a>,
79 args: Vec<String>,
81 stdin: Option<&'a PathBuf>,
83 commands_cnt: usize,
85 timeout: u64,
87}
88
89impl<'a> GdbCommand<'a> {
90 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 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 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 pub fn raw(&self) -> error::Result<Vec<u8>> {
133 let mut gdb = Command::new("gdb");
134 let mut gdb_args = Vec::new();
135
136 match &self.exec_type {
138 ExecType::Local(args) => {
139 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 if !Path::new(target).exists() {
153 return Err(error::Error::NoFile(target.to_string()));
154 }
155
156 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 let output =
179 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 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 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 pub fn bt(&mut self) -> &'a mut GdbCommand {
238 self.ex("bt")
239 }
240
241 pub fn disassembly(&mut self) -> &'a mut GdbCommand {
243 self.ex("x/16i $pc")
244 }
245
246 pub fn regs(&mut self) -> &'a mut GdbCommand {
248 self.ex("i r")
249 }
250
251 pub fn mappings(&mut self) -> &'a mut GdbCommand {
253 self.ex("info proc mappings")
254 }
255
256 pub fn cmdline(&mut self) -> &'a mut GdbCommand {
258 self.ex("info proc cmdline")
259 }
260
261 pub fn env(&mut self) -> &'a mut GdbCommand {
263 self.ex("show environment")
264 }
265
266 pub fn status(&mut self) -> &'a mut GdbCommand {
268 self.ex("info proc status")
269 }
270
271 pub fn sources(&mut self) -> &'a mut GdbCommand {
273 self.ex("info sources")
274 }
275
276 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 pub fn timeout(&mut self, timeout: u64) -> &'a mut GdbCommand {
285 self.timeout = timeout;
286 self
287 }
288
289 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 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 pub fn siginfo(&mut self) -> &'a mut GdbCommand {
316 self.ex("p/x $_siginfo")
317 }
318
319 pub fn launch(&self) -> error::Result<Vec<String>> {
324 let stdout = self.raw()?;
326
327 let output = String::from_utf8_lossy(&stdout);
329
330 self.parse(output)
331 }
332
333 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 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 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 if let Some(caps) = re_end.captures(line) {
357 let end_idx = caps.get(1).unwrap().as_str().parse::<usize>()?;
358 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}