opentitanlib/io/console/
coverage.rs1use std::collections::VecDeque;
6use std::fs;
7use std::sync::Mutex;
8use std::task::{Context, Poll, ready};
9
10use anyhow::{Result, bail};
11use crc::{CRC_32_ISO_HDLC, Crc};
12
13use super::{ConsoleDevice, CoverageConsole};
14use crate::io::middleware::{Middleware, UartMiddleware};
15use crate::io::uart::Uart;
16
17const COVERAGE_START_ANCHOR: &[u8] = b"== COVERAGE PROFILE START ==\r\n";
18const COVERAGE_END_ANCHOR: &[u8] = b"== COVERAGE PROFILE END ==\r\n";
19const COVERAGE_SKIP_ANCHOR: &[u8] = b"== COVERAGE PROFILE SKIP ==\r\n";
20
21#[derive(Default, PartialEq, Clone, Copy)]
22enum BufferMode {
23 #[default]
24 Normal,
25 Coverage,
26}
27
28pub struct CoverageMiddleware<T> {
29 inner: T,
30 profile_path_template: Option<String>,
31 state: Mutex<CoverageState>,
32}
33
34struct CoverageState {
35 mode: BufferMode,
36 match_buf: VecDeque<u8>,
38 output_buf: VecDeque<u8>,
40 coverage_buf: String,
42 blocks_processed: usize,
44}
45
46impl Default for CoverageState {
47 fn default() -> Self {
48 Self {
49 mode: BufferMode::Normal,
50 match_buf: VecDeque::new(),
51 output_buf: VecDeque::new(),
52 coverage_buf: String::new(),
53 blocks_processed: 0,
54 }
55 }
56}
57
58impl<T> CoverageMiddleware<T> {
59 pub fn new(inner: T) -> Self {
60 Self {
61 inner,
62 profile_path_template: None,
63 state: Mutex::new(CoverageState::default()),
64 }
65 }
66
67 #[cfg(test)]
68 pub fn new_with_template(inner: T, template: String) -> Self {
69 Self {
70 inner,
71 profile_path_template: Some(template),
72 state: Mutex::new(CoverageState::default()),
73 }
74 }
75
76 fn starts_with_any_anchor(data: &[u8]) -> Option<&'static [u8]> {
77 if data.starts_with(COVERAGE_START_ANCHOR) {
78 return Some(COVERAGE_START_ANCHOR);
79 }
80 if data.starts_with(COVERAGE_END_ANCHOR) {
81 return Some(COVERAGE_END_ANCHOR);
82 }
83 if data.starts_with(COVERAGE_SKIP_ANCHOR) {
84 return Some(COVERAGE_SKIP_ANCHOR);
85 }
86 None
87 }
88
89 fn is_any_anchor_prefix(data: &[u8]) -> bool {
90 !data.is_empty()
91 && (COVERAGE_START_ANCHOR.starts_with(data)
92 || COVERAGE_END_ANCHOR.starts_with(data)
93 || COVERAGE_SKIP_ANCHOR.starts_with(data))
94 }
95
96 fn process_coverage_data(&self, coverage_buf: &str) -> Result<()> {
97 let response = hex::decode(coverage_buf)?;
98 if response.len() < 4 {
99 bail!(
100 "Coverage from the device is too short: {} bytes",
101 response.len()
102 );
103 }
104
105 let (response, crc) = response.split_at(response.len() - 4);
106 let crc = u32::from_le_bytes(crc.try_into()?);
107 let actual = Crc::<u32>::new(&CRC_32_ISO_HDLC).checksum(response);
108 if crc != actual {
109 bail!("Coverage corrupted: crc = {crc:08x}, actual = {actual:08x}");
110 }
111
112 let template = self
113 .profile_path_template
114 .clone()
115 .or_else(|| std::env::var("LLVM_PROFILE_FILE").ok())
116 .unwrap_or_else(|| "./default.%p.profraw".to_owned());
117
118 let path = template.replace("%h", "test.on.device");
126 let path = path.replace("%p", &rand::random::<u32>().to_string());
127 let path = path.replace("%m", "0");
128 let path = path.replace(".profraw", ".xprofraw");
129
130 log::info!("Saving coverage profile to {}", path);
131 fs::write(path, response)?;
132
133 Ok(())
134 }
135
136 fn handle_anchor(
137 &self,
138 state: &mut CoverageState,
139 anchor: &[u8],
140 cx: &mut Context<'_>,
141 ) -> Option<Poll<Result<usize>>> {
142 for _ in 0..anchor.len() {
143 state.match_buf.pop_front();
144 }
145
146 if state.mode == BufferMode::Normal {
147 if anchor == COVERAGE_START_ANCHOR {
148 state.mode = BufferMode::Coverage;
149 state.coverage_buf.clear();
150 } else if anchor == COVERAGE_END_ANCHOR {
151 log::warn!("Got unexpected coverage end indicator!");
152 state.blocks_processed += 1;
153 cx.waker().wake_by_ref();
154 return Some(Poll::Pending);
155 } else if anchor == COVERAGE_SKIP_ANCHOR {
156 state.blocks_processed += 1;
157 cx.waker().wake_by_ref();
158 return Some(Poll::Pending);
159 }
160 } else {
161 if anchor == COVERAGE_START_ANCHOR {
163 log::warn!("Got unterminated coverage block:\n{}", state.coverage_buf);
164 state.coverage_buf.clear();
165 } else if anchor == COVERAGE_END_ANCHOR {
166 if let Err(e) = self.process_coverage_data(&state.coverage_buf) {
167 log::warn!("Failed to process coverage data: {:?}", e);
168 }
169 state.mode = BufferMode::Normal;
170 state.coverage_buf.clear();
171 state.blocks_processed += 1;
172 cx.waker().wake_by_ref();
173 return Some(Poll::Pending);
174 } else if anchor == COVERAGE_SKIP_ANCHOR {
175 log::warn!("Got unterminated coverage block:\n{}", state.coverage_buf);
176 state.mode = BufferMode::Normal;
177 state.coverage_buf.clear();
178 state.blocks_processed += 1;
179 cx.waker().wake_by_ref();
180 return Some(Poll::Pending);
181 }
182 }
183 None
184 }
185
186 fn flush_match_buf(&self, state: &mut CoverageState, len: usize) {
187 for _ in 0..len {
188 let ch = state.match_buf.pop_front().unwrap();
189 if state.mode == BufferMode::Normal {
190 state.output_buf.push_back(ch);
191 } else if ch.is_ascii_hexdigit() {
192 state.coverage_buf.push(ch as char);
193 } else {
194 log::warn!(
195 "Got invalid hex character '{}' in coverage block. Exiting coverage mode.",
196 (ch as char).escape_default()
197 );
198 state.mode = BufferMode::Normal;
199 state.coverage_buf.clear();
200 state.output_buf.push_back(ch);
201 state.blocks_processed += 1;
202 }
203 }
204 }
205}
206
207impl<T: ConsoleDevice> Middleware for CoverageMiddleware<T> {
208 type Inner = T;
209 fn inner(&self) -> &T {
210 &self.inner
211 }
212}
213
214impl<T: Uart> UartMiddleware for CoverageMiddleware<T> {
215 fn clear_rx_buffer_impl(&self) -> Result<()> {
216 let mut state = self.state.lock().unwrap();
217 *state = CoverageState::default();
218 self.inner.clear_rx_buffer()
219 }
220}
221
222impl<T: ConsoleDevice> ConsoleDevice for CoverageMiddleware<T> {
223 fn poll_read(&self, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<Result<usize>> {
224 if self.inner.as_coverage_console().is_some() {
226 return self.inner.poll_read(cx, buf);
227 }
228
229 if buf.is_empty() {
230 return Poll::Ready(Ok(0));
231 }
232 let mut state = self.state.lock().unwrap();
233
234 loop {
235 if !state.output_buf.is_empty() {
237 let len = std::cmp::min(buf.len(), state.output_buf.len());
238 for b in buf.iter_mut().take(len) {
239 *b = state.output_buf.pop_front().unwrap();
240 }
241 return Poll::Ready(Ok(len));
242 }
243
244 if !state.match_buf.is_empty() {
246 let match_buf_flat = state.match_buf.make_contiguous();
247
248 if let Some(anchor) = Self::starts_with_any_anchor(match_buf_flat) {
249 if let Some(p) = self.handle_anchor(&mut state, anchor, cx) {
250 return p;
251 }
252 continue;
253 }
254
255 let mut safe_len = 0;
257 while safe_len < match_buf_flat.len() {
258 if Self::is_any_anchor_prefix(&match_buf_flat[safe_len..]) {
259 break;
260 }
261 safe_len += 1;
262 }
263
264 if safe_len > 0 {
265 self.flush_match_buf(&mut state, safe_len);
266 continue;
267 }
268 }
270
271 let mut one_byte = [0u8; 1];
273 drop(state);
274 let res = ready!(self.inner.poll_read(cx, &mut one_byte));
275 state = self.state.lock().unwrap();
276
277 match res {
278 Ok(0) => {
279 if !state.match_buf.is_empty() {
281 let safe_len = state.match_buf.len();
282 self.flush_match_buf(&mut state, safe_len);
283 }
284 return Poll::Ready(Ok(0));
285 }
286 Ok(1) => {
287 state.match_buf.push_back(one_byte[0]);
288 }
289 _ => unreachable!(),
290 }
291 }
292 }
293
294 fn write(&self, buf: &[u8]) -> Result<()> {
295 self.inner.write(buf)
296 }
297
298 fn as_coverage_console(&self) -> Option<&dyn CoverageConsole> {
299 self.inner.as_coverage_console().or(Some(self))
300 }
301}
302
303impl<T: ConsoleDevice> CoverageConsole for CoverageMiddleware<T> {
304 fn coverage_blocks_processed(&self) -> usize {
305 self.state.lock().unwrap().blocks_processed
306 }
307}