tracexec_core/
breakpoint.rs1use std::{borrow::Cow, error::Error};
2
3use nix::unistd::Pid;
4use regex_cursor::engines::pikevm::{self, PikeVM};
5use strum::IntoStaticStr;
6
7use crate::{
8 event::OutputMsg,
9 primitives::regex::{ArgvCursor, SPACE},
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct BreakPointHit {
14 pub bid: u32,
15 pub pid: Pid,
16 pub stop: BreakPointStop,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)]
20pub enum BreakPointStop {
21 SyscallEnter,
22 SyscallExit,
23}
24
25impl BreakPointStop {
26 pub fn toggle(&mut self) {
27 *self = match self {
28 Self::SyscallEnter => Self::SyscallExit,
29 Self::SyscallExit => Self::SyscallEnter,
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
35pub struct BreakPointRegex {
36 regex: PikeVM,
37 editable: String,
38}
39
40#[derive(Debug, Clone)]
41pub enum BreakPointPattern {
42 ArgvRegex(BreakPointRegex),
45 InFilename(String),
47 ExactFilename(String),
48}
49
50#[derive(Debug, Clone)]
51pub enum BreakPointType {
52 Once,
54 Permanent,
56}
57
58#[derive(Debug, Clone)]
59pub struct BreakPoint {
60 pub pattern: BreakPointPattern,
61 pub ty: BreakPointType,
62 pub activated: bool,
63 pub stop: BreakPointStop,
64}
65
66impl BreakPointPattern {
67 pub fn pattern(&self) -> &str {
68 match self {
69 Self::ArgvRegex(regex) => regex.editable.as_str(),
70 Self::InFilename(filename) => filename,
71 Self::ExactFilename(filename) => filename,
73 }
74 }
75
76 pub fn to_editable(&self) -> String {
77 match self {
78 Self::ArgvRegex(regex) => format!("argv-regex:{}", regex.editable),
79 Self::InFilename(filename) => format!("in-filename:{filename}"),
80 Self::ExactFilename(filename) => {
81 format!("exact-filename:{filename}")
82 }
83 }
84 }
85
86 pub fn from_editable(editable: &str) -> Result<Self, String> {
87 if let Some((prefix, rest)) = editable.split_once(':') {
88 match prefix {
89 "in-filename" => Ok(Self::InFilename(rest.to_string())),
90 "exact-filename" => Ok(Self::ExactFilename(rest.to_string())),
91 "argv-regex" => Ok(Self::ArgvRegex(BreakPointRegex {
92 regex: PikeVM::new(rest).map_err(|e| e.to_string())?,
93 editable: rest.to_string(),
94 })),
95 _ => Err(format!("Invalid breakpoint pattern type: {prefix}!")),
96 }
97 } else {
98 Err("No valid breakpoint pattern found!".to_string())
99 }
100 }
101
102 pub fn matches(&self, argv: Option<&[OutputMsg]>, filename: &OutputMsg) -> bool {
103 match self {
104 Self::ArgvRegex(regex) => {
105 let Some(argv) = argv else {
106 return false;
107 };
108 let space = &SPACE;
109 let argv = ArgvCursor::new(argv, space);
110 pikevm::is_match(
111 ®ex.regex,
112 &mut pikevm::Cache::new(®ex.regex),
113 &mut regex_cursor::Input::new(argv),
114 )
115 }
116 Self::InFilename(pattern) => {
117 let OutputMsg::Ok(filename) = filename else {
118 return false;
119 };
120 filename.contains(pattern)
121 }
122 Self::ExactFilename(path) => {
123 let OutputMsg::Ok(filename) = filename else {
124 return false;
125 };
126 filename == path
127 }
128 }
129 }
130}
131
132impl TryFrom<&str> for BreakPoint {
133 type Error = Cow<'static, str>;
134
135 fn try_from(value: &str) -> Result<Self, Self::Error> {
136 let Some((stop, rest)) = value.split_once(':') else {
137 return Err("No valid syscall stop found! The breakpoint should start with \"sysenter:\" or \"sysexit:\".".into());
138 };
139 let stop = match stop {
140 "sysenter" => BreakPointStop::SyscallEnter,
141 "sysexit" => BreakPointStop::SyscallExit,
142 _ => {
143 return Err(
144 format!("Invalid syscall stop {stop:?}! The breakpoint should start with \"sysenter:\" or \"sysexit:\".")
145 .into(),
146 )
147 }
148 };
149 let Some((pattern_kind, pattern)) = rest.split_once(':') else {
150 return Err("No valid pattern kind found! The breakpoint pattern should start with \"argv-regex:\", \"exact-filename:\" or \"in-filename:\".".into());
151 };
152 let pattern = match pattern_kind {
153 "argv-regex" => BreakPointPattern::ArgvRegex(BreakPointRegex {
154 regex: PikeVM::new(pattern).map_err(|e| format!("\n{}", e.source().unwrap()))?,
155 editable: pattern.to_string(),
156 }),
157 "exact-filename" => BreakPointPattern::ExactFilename(pattern.to_string()),
158 "in-filename" => BreakPointPattern::InFilename(pattern.to_string()),
159 _ => {
160 return Err(
161 format!(
162 "Invalid pattern kind {pattern_kind:?}! The breakpoint pattern should start with \"argv-regex:\", \"exact-filename:\" or \"in-filename:\"."
163 )
164 .into(),
165 )
166 }
167 };
168 Ok(Self {
169 ty: BreakPointType::Permanent,
170 stop,
171 pattern,
172 activated: true,
173 })
174 }
175}