Skip to content

Commit f3b07bf

Browse files
committed
Add Gdb::{break_insert,break_disable,break_delete}
1 parent 188d2a4 commit f3b07bf

File tree

5 files changed

+203
-47
lines changed

5 files changed

+203
-47
lines changed

src/breakpoint.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use camino::Utf8PathBuf;
2+
3+
use crate::{raw, Error};
4+
5+
#[derive(Debug, Clone, Eq, PartialEq)]
6+
pub enum LineSpec {
7+
Line { file: Utf8PathBuf, num: u32 },
8+
Function(String),
9+
FunctionExplicitFile { file: Utf8PathBuf, name: String },
10+
}
11+
12+
impl LineSpec {
13+
pub fn line(file: impl Into<Utf8PathBuf>, num: u32) -> Self {
14+
Self::Line {
15+
file: file.into(),
16+
num,
17+
}
18+
}
19+
20+
pub fn function(name: impl Into<String>) -> Self {
21+
Self::Function(name.into())
22+
}
23+
24+
pub fn function_with_explicit_file(
25+
file: impl Into<Utf8PathBuf>,
26+
name: impl Into<String>,
27+
) -> Self {
28+
Self::FunctionExplicitFile {
29+
file: file.into(),
30+
name: name.into(),
31+
}
32+
}
33+
34+
pub fn serialize(self) -> String {
35+
match self {
36+
Self::Line { file, num } => format!("{}:{}", file, num),
37+
Self::Function(name) => name,
38+
Self::FunctionExplicitFile { file, name } => format!("{}:{}", file, name),
39+
}
40+
}
41+
}
42+
43+
#[derive(Debug, Clone, Eq, PartialEq)]
44+
pub struct Breakpoint {
45+
pub number: u32,
46+
pub addr: u64,
47+
pub file: Utf8PathBuf,
48+
pub line: u32,
49+
pub thread_groups: Vec<String>,
50+
pub times: u32,
51+
}
52+
53+
impl Breakpoint {
54+
pub fn from_raw(mut raw: raw::Dict) -> Result<Self, Error> {
55+
let number = raw.remove_expect("number")?.expect_number()?;
56+
let addr = raw.remove_expect("addr")?.expect_hex()?;
57+
let file = raw.remove_expect("fullname")?.expect_path()?;
58+
let line = raw.remove_expect("line")?.expect_number()?;
59+
let times = raw.remove_expect("times")?.expect_number()?;
60+
61+
let thread_groups = raw
62+
.remove_expect("thread-groups")?
63+
.expect_list()?
64+
.into_iter()
65+
.map(raw::Value::expect_string)
66+
.collect::<Result<_, _>>()?;
67+
68+
Ok(Self {
69+
number,
70+
addr,
71+
file,
72+
line,
73+
thread_groups,
74+
times,
75+
})
76+
}
77+
}

src/inner.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{collections::HashMap, time::Duration};
33
use crate::{
44
parser::{self, parse_message},
55
raw::{GeneralMessage, Response},
6-
ResponseError, Token,
6+
Error, Token,
77
};
88

99
use tokio::{
@@ -34,11 +34,7 @@ impl Inner {
3434
}
3535
}
3636

37-
pub(super) async fn execute(
38-
&self,
39-
msg: String,
40-
timeout: Duration,
41-
) -> Result<Response, ResponseError> {
37+
pub(super) async fn execute(&self, msg: String, timeout: Duration) -> Result<Response, Error> {
4238
let token = Token::generate();
4339
let (out_tx, mut out_rx) = mpsc::channel(1);
4440

@@ -48,7 +44,7 @@ impl Inner {
4844

4945
time::timeout(timeout, out_rx.recv())
5046
.await
51-
.map_err(|_| ResponseError::Timeout)?
47+
.map_err(|_| Error::Timeout)?
5248
.expect("out chan not closed")
5349
}
5450

@@ -59,7 +55,7 @@ impl Inner {
5955
}
6056
}
6157

62-
type Request = (Token, String, mpsc::Sender<Result<Response, ResponseError>>);
58+
type Request = (Token, String, mpsc::Sender<Result<Response, Error>>);
6359

6460
async fn mainloop(
6561
mut cmd: process::Child,

src/lib.rs

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use std::{collections::HashMap, fmt, process::Stdio, time::Duration};
22

3+
use breakpoint::{Breakpoint, LineSpec};
34
use camino::{Utf8Path, Utf8PathBuf};
45
use rand::Rng;
56
use tokio::{io, process};
67
use tracing::{debug, error};
78

9+
pub mod breakpoint;
810
mod inner;
911
pub mod parser;
1012
pub mod raw;
@@ -19,7 +21,7 @@ use crate::symbol::Symbol;
1921
mod test_common;
2022

2123
#[derive(Debug, Clone, thiserror::Error, Eq, PartialEq)]
22-
pub enum ResponseError {
24+
pub enum Error {
2325
#[error(transparent)]
2426
Gdb(#[from] GdbError),
2527

@@ -33,9 +35,12 @@ pub enum ResponseError {
3335
#[error("Expected response to have a payload")]
3436
ExpectedPayload,
3537

36-
#[error("Failed to parse payload value as u32, got: {0}")]
38+
#[error("Failed to parse payload value as u32")]
3739
ParseU32(#[from] std::num::ParseIntError),
3840

41+
#[error("Failed to parse payload value as hex")]
42+
ParseHex(#[from] ParseHexError),
43+
3944
#[error("Expected response to have message {expected}, got {actual}")]
4045
UnexpectedResponseMessage { expected: String, actual: String },
4146

@@ -50,6 +55,14 @@ pub struct GdbError {
5055
msg: String,
5156
}
5257

58+
#[derive(Debug, Clone, thiserror::Error, Eq, PartialEq)]
59+
pub enum ParseHexError {
60+
#[error("Expected to start with 0x")]
61+
InvalidPrefix,
62+
#[error(transparent)]
63+
ParseInt(#[from] std::num::ParseIntError),
64+
}
65+
5366
pub struct Gdb {
5467
inner: Inner,
5568
timeout: Duration,
@@ -91,16 +104,56 @@ impl Gdb {
91104
Ok(Self { inner, timeout })
92105
}
93106

94-
pub async fn run(&self) -> Result<(), ResponseError> {
107+
pub async fn run(&self) -> Result<(), Error> {
95108
self.execute_raw("-exec-run")
96109
.await?
97110
.expect_result()?
98111
.expect_msg_is("running")
99112
}
100113

101-
pub async fn symbol_info_functions(
102-
&self,
103-
) -> Result<HashMap<Utf8PathBuf, Vec<Symbol>>, ResponseError> {
114+
pub async fn break_insert(&self, at: LineSpec) -> Result<Breakpoint, Error> {
115+
let raw = self
116+
.execute_raw(format!("-break-insert {}", at.serialize()))
117+
.await?
118+
.expect_result()?
119+
.expect_payload()?
120+
.remove_expect("bkpt")?
121+
.expect_dict()?;
122+
123+
Breakpoint::from_raw(raw)
124+
}
125+
126+
pub async fn break_disable<'a, I>(&self, breakpoints: I) -> Result<(), Error>
127+
where
128+
I: IntoIterator<Item = &'a Breakpoint>,
129+
{
130+
let mut raw = String::new();
131+
for bp in breakpoints {
132+
raw.push_str(&format!("{} ", bp.number));
133+
}
134+
135+
self.execute_raw(format!("-break-disable {}", raw))
136+
.await?
137+
.expect_result()?
138+
.expect_msg_is("done")
139+
}
140+
141+
pub async fn break_delete<'a, I>(&self, breakpoints: I) -> Result<(), Error>
142+
where
143+
I: IntoIterator<Item = &'a Breakpoint>,
144+
{
145+
let mut raw = String::new();
146+
for bp in breakpoints {
147+
raw.push_str(&format!("{} ", bp.number));
148+
}
149+
150+
self.execute_raw(format!("-break-delete {}", raw))
151+
.await?
152+
.expect_result()?
153+
.expect_msg_is("done")
154+
}
155+
156+
pub async fn symbol_info_functions(&self) -> Result<HashMap<Utf8PathBuf, Vec<Symbol>>, Error> {
104157
let payload = self
105158
.execute_raw("-symbol-info-functions")
106159
.await?
@@ -112,17 +165,14 @@ impl Gdb {
112165
/// Execute a command for a response.
113166
///
114167
/// Your command will be prefixed with a token and suffixed with a newline.
115-
pub async fn execute_raw(
116-
&self,
117-
msg: impl Into<String>,
118-
) -> Result<raw::Response, ResponseError> {
168+
pub async fn execute_raw(&self, msg: impl Into<String>) -> Result<raw::Response, Error> {
119169
self.inner.execute(msg.into(), self.timeout).await
120170
}
121171

122172
/// Waits until gdb is responsive to commands.
123173
///
124174
/// You do not need to call this before sending commands yourself.
125-
pub async fn await_ready(&self) -> Result<(), ResponseError> {
175+
pub async fn await_ready(&self) -> Result<(), Error> {
126176
self.execute_raw("-list-target-features").await?;
127177
Ok(())
128178
}
@@ -155,7 +205,7 @@ impl Token {
155205

156206
#[cfg(test)]
157207
mod tests {
158-
use std::collections::BTreeMap;
208+
use std::{collections::BTreeMap, iter};
159209

160210
use super::*;
161211
use insta::assert_debug_snapshot;
@@ -167,6 +217,24 @@ mod tests {
167217
Ok(Gdb::spawn(bin, TIMEOUT)?)
168218
}
169219

220+
#[tokio::test]
221+
async fn test_break() -> Result {
222+
let subject = fixture()?;
223+
224+
let bp = subject
225+
.break_insert(LineSpec::line("samples/hello_world/src/main.rs", 13))
226+
.await?;
227+
assert_eq!(1, bp.number);
228+
assert!(bp.file.ends_with("samples/hello_world/src/main.rs"));
229+
assert_eq!(13, bp.line);
230+
assert_eq!(0, bp.times);
231+
232+
subject.break_disable(iter::once(&bp)).await?;
233+
subject.break_delete(iter::once(&bp)).await?;
234+
235+
Ok(())
236+
}
237+
170238
#[tokio::test]
171239
async fn test_run() -> Result {
172240
let subject = fixture()?;
@@ -206,7 +274,7 @@ mod tests {
206274
let err = subject.execute_raw("-invalid-command").await.unwrap_err();
207275

208276
assert_eq!(
209-
ResponseError::Gdb(GdbError {
277+
Error::Gdb(GdbError {
210278
code: "undefined-command".into(),
211279
msg: "Undefined MI command: invalid-command".into(),
212280
}),

0 commit comments

Comments
 (0)