1use crate::constants::TEST_ROOT_REALM_NAME;
6use crate::error::DebugAgentError;
7use crate::run_events::SuiteEvents;
8use ftest_manager::LaunchError;
9use fuchsia_component::client::connect_to_protocol;
10use futures::channel::mpsc;
11use futures::future::RemoteHandle;
12use futures::prelude::*;
13use {
14 fidl_fuchsia_debugger as fdbg, fidl_fuchsia_test_manager as ftest_manager,
15 fuchsia_async as fasync,
16};
17
18pub(crate) struct ThreadBacktraceInfo {
19 pub thread: u64,
20 pub backtrace: String,
21}
22
23pub(crate) struct DebugAgent {
24 proxy: fdbg::DebugAgentProxy,
25 event_stream_processor_handle: Option<futures::future::RemoteHandle<()>>,
26 takeable_fatal_exception_recevier: Option<mpsc::Receiver<ThreadBacktraceInfo>>,
27}
28
29impl DebugAgent {
30 pub(crate) async fn new() -> Result<Self, DebugAgentError> {
33 Self::from_launcher_proxy(
34 connect_to_protocol::<fdbg::LauncherMarker>()
35 .map_err(|e| DebugAgentError::ConnectToLauncher(e))?,
36 )
37 .await
38 }
39
40 async fn from_launcher_proxy(
43 launcher_proxy: fdbg::LauncherProxy,
44 ) -> Result<Self, DebugAgentError> {
45 let (proxy, agent_server_end) = fidl::endpoints::create_proxy();
46
47 launcher_proxy
48 .launch(agent_server_end)
49 .await
50 .map_err(|e| DebugAgentError::LaunchLocal(e))?
51 .map_err(|zx_status| DebugAgentError::LaunchResponse(zx_status))?;
52
53 let _ = proxy
60 .attach_to(
61 TEST_ROOT_REALM_NAME,
62 fdbg::FilterType::MonikerSuffix,
63 &fdbg::FilterOptions {
64 job_only: Some(true),
65 recursive: Some(true),
66 ..Default::default()
67 },
68 )
69 .await
70 .map_err(|e| DebugAgentError::AttachToTestsLocal(e))?
71 .map_err(|e| DebugAgentError::AttachToTestsResponse(e))?;
72
73 let (fatal_exception_sender, fatal_exception_receiver) = mpsc::channel(1024);
74 let event_stream_processor_handle =
75 Self::begin_process_event_stream(proxy.take_event_stream(), fatal_exception_sender);
76
77 Ok(Self {
78 proxy,
79 event_stream_processor_handle: Some(event_stream_processor_handle),
80 takeable_fatal_exception_recevier: Some(fatal_exception_receiver),
81 })
82 }
83
84 pub(crate) fn take_fatal_exception_receiver(&mut self) -> mpsc::Receiver<ThreadBacktraceInfo> {
87 self.takeable_fatal_exception_recevier.take().unwrap()
88 }
89
90 pub(crate) fn stop_sending_fatal_exceptions(&mut self) {
92 self.event_stream_processor_handle = None;
93 }
94
95 pub(crate) async fn report_all_backtraces(
98 &self,
99 test_url: Option<&str>,
100 mut event_sender: mpsc::Sender<Result<SuiteEvents, LaunchError>>,
101 ) {
102 let (client_end, mut server_end) = fidl::handle::fuchsia_handles::Socket::create_stream();
103 event_sender.send(Ok(SuiteEvents::suite_stderr(client_end).into())).await.unwrap();
104
105 let mut maybe_filter: Option<fdbg::Filter> = None;
106 if let Some(test_url) = test_url {
107 maybe_filter = Some(fdbg::Filter {
114 pattern: test_url.to_string(),
115 type_: fdbg::FilterType::Url,
116 options: fdbg::FilterOptions { recursive: Some(true), ..Default::default() },
117 });
118 }
119
120 let (iterator_proxy, iterator_server_end) = fidl::endpoints::create_proxy();
121 if let Err(_) = self
122 .proxy
123 .get_process_info(
124 &fdbg::GetProcessInfoOptions {
125 filter: maybe_filter,
126 interest: Some(fdbg::ThreadDetailsInterest {
127 backtrace: Some(true),
128 ..Default::default()
129 }),
130 ..Default::default()
131 },
132 iterator_server_end,
133 )
134 .await
135 {
136 return;
138 }
139
140 loop {
141 match iterator_proxy.get_next().await {
142 Ok(result) => match result {
143 Ok(infos) => {
144 if infos.len() == 0 {
145 break;
146 }
147
148 for info in infos {
149 let _ = server_end.write(
150 format!(
151 "Component: {}\nProcess: {} Thread: {}\n",
152 info.moniker, info.process, info.thread
153 )
154 .as_bytes(),
155 );
156 DebugAgent::write_backtrace_info(
157 &ThreadBacktraceInfo {
158 thread: info.thread,
159 backtrace: info.details.backtrace.unwrap(),
160 },
161 &mut server_end,
162 )
163 .await;
164
165 let _ = server_end.write("\n".as_bytes());
166 }
167 }
168 Err(_e) => {
169 break;
170 }
171 },
172 Err(_e) => {
173 break;
174 }
175 }
176 }
177 }
178
179 fn begin_process_event_stream(
182 mut event_stream: fdbg::DebugAgentEventStream,
183 mut sender: mpsc::Sender<ThreadBacktraceInfo>,
184 ) -> RemoteHandle<()> {
185 let (event_stream_receiver, event_stream_receiver_handle) = async move {
186 loop {
187 match event_stream.next().await {
188 Some(Ok(fdbg::DebugAgentEvent::OnFatalException {
189 payload:
190 fdbg::DebugAgentOnFatalExceptionRequest {
191 thread: Some(thread),
192 backtrace: Some(backtrace),
193 ..
194 },
195 })) => {
196 sender.send(ThreadBacktraceInfo { thread, backtrace }).await.unwrap();
197 }
198 Some(_) => {}
199 None => break,
200 }
201 }
202 }
203 .remote_handle();
204
205 fasync::Task::spawn(event_stream_receiver).detach();
206
207 event_stream_receiver_handle
208 }
209
210 pub(crate) async fn write_backtrace_info(
211 backtrace_info: &ThreadBacktraceInfo,
212 socket_server_end: &mut fidl::Socket,
213 ) {
214 for line in backtrace_info.backtrace.split('\n') {
215 if line.starts_with("{{{reset:begin}}}") {
216 let _ = socket_server_end.write(line[..17].as_bytes());
217 let _ = socket_server_end.write(b"\n");
218 let _ = socket_server_end.write(line[17..].as_bytes());
219 } else {
220 let _ = socket_server_end.write(line.as_bytes());
221 }
222
223 let _ = socket_server_end.write(b"\n");
224 }
225 }
226}
227
228#[cfg(test)]
229mod test {
230 use crate::run_events::SuiteEventPayload;
231
232 use super::*;
233
234 const TEST_THREAD_1: u64 = 1234;
235 const TEST_THREAD_2: u64 = 5678;
236 const TEST_PROCESS_1: u64 = 4321;
237 const TEST_PROCESS_2: u64 = 8765;
238 const TEST_BACKTRACE_1: &str = "{{{reset:begin}}}test backtrace 1, with reset:begin prefix";
239 const TEST_BACKTRACE_2: &str = "test backtrace 2, no special prefix";
240 const TEST_MONIKER_1: &str = "test moniker 1";
241 const TEST_MONIKER_2: &str = "test moniker 2";
242 const TEST_STDERR_TEXT: &str = "Component: test moniker 1\nProcess: 4321 Thread: 1234\n\
243 {{{reset:begin}}}\ntest backtrace 1, with reset:begin prefix\n\nComponent: test moniker 2\n\
244 Process: 8765 Thread: 5678\ntest backtrace 2, no special prefix\n\n";
245 const TEST_STDERR_SIZE: usize = 204;
246
247 #[fuchsia::test]
248 async fn fatal_exceptions() {
249 let (mut under_test, mut debug_agent_service) = start_agent().await;
250
251 debug_agent_service.send_on_fatal_exception_event(TEST_THREAD_1, TEST_BACKTRACE_1);
253
254 let mut receiver = under_test.take_fatal_exception_receiver();
256 let thread_backtrace_info = receiver.next().await.expect("receive fatal exception");
257 assert_eq!(TEST_THREAD_1, thread_backtrace_info.thread);
258 assert_eq!(TEST_BACKTRACE_1, thread_backtrace_info.backtrace);
259
260 debug_agent_service.expect_nothing_more();
261 }
262
263 #[fuchsia::test]
264 async fn all_backtraces() {
265 let (under_test, mut debug_agent_service) = start_agent().await;
266
267 let (event_sender, mut event_receiver) =
268 mpsc::channel::<Result<SuiteEvents, LaunchError>>(16);
269
270 futures::future::join(under_test.report_all_backtraces(None, event_sender), async {
273 let iterator_server_end = debug_agent_service
274 .expect_get_process_info(&fdbg::GetProcessInfoOptions {
275 filter: None,
276 interest: Some(fdbg::ThreadDetailsInterest {
277 backtrace: Some(true),
278 ..Default::default()
279 }),
280 ..Default::default()
281 })
282 .await;
283 serve_process_info_iterator(iterator_server_end).await;
284 })
285 .await;
286
287 match event_receiver.try_next() {
289 Ok(Some(Ok(SuiteEvents {
290 timestamp: _,
291 payload: SuiteEventPayload::SuiteStderr(socket),
292 }))) => {
293 let mut buffer: [u8; 256] = [0; 256];
294 let size = socket.read(&mut buffer).expect("read from socket");
295 assert_eq!(TEST_STDERR_SIZE, size);
296 let stderr_string =
297 std::str::from_utf8(&buffer[0..TEST_STDERR_SIZE]).expect("convert [u8] to str");
298 assert_eq!(TEST_STDERR_TEXT, stderr_string);
299 }
300 Ok(Some(Ok(_))) => {
301 assert!(false, "Expected SuiteStderr event, got other event");
302 }
303 Ok(Some(Err(e))) => {
304 assert!(false, "Expected event, got error {:?}", e);
305 }
306 Ok(None) => {
307 assert!(false, "Expected event, got channel closed");
308 }
309 Err(e) => {
310 assert!(false, "Expected event, got channel error {:?}", e);
311 }
312 }
313 }
314
315 struct FakeLauncherService {
317 request_stream: fdbg::LauncherRequestStream,
318 }
319
320 impl FakeLauncherService {
321 fn new() -> (fdbg::LauncherProxy, Self) {
323 let (proxy, request_stream) =
324 fidl::endpoints::create_proxy_and_stream::<fdbg::LauncherMarker>();
325
326 (proxy, Self { request_stream })
327 }
328
329 async fn expect_launch_request(&mut self) -> FakeDebugAgentService {
331 match self.next_request().await {
332 fdbg::LauncherRequest::Launch { agent, responder } => {
333 responder.send(Ok(())).expect("send response to Launcher::Launch");
334 return FakeDebugAgentService::new(agent);
335 }
336 _ => panic!("Call to unexpected method."),
337 }
338 }
339
340 async fn expect_connection_closed(&mut self) {
341 if let Some(request) = self.request_stream.try_next().await.expect("get request") {
342 assert!(false, "Expected connection closed, got request {:?}", request);
343 }
344 }
345
346 fn expect_nothing_more(&mut self) {
348 match self.request_stream.try_next().now_or_never() {
349 Some(Ok(None)) => {
350 assert!(false, "Expected nothing more, got connection closed");
351 }
352 Some(Ok(v)) => {
353 assert!(false, "Expected nothing more, got request {:?}", v);
354 }
355 Some(Err(e)) => {
356 assert!(false, "Expected nothing more, got stream error {:?}", e);
357 }
358 None => {}
359 }
360 }
361
362 async fn next_request(&mut self) -> fdbg::LauncherRequest {
364 self.request_stream
365 .try_next()
366 .await
367 .expect("get request")
368 .expect("connection not closed")
369 }
370 }
371
372 struct FakeDebugAgentService {
374 request_stream: fdbg::DebugAgentRequestStream,
375 control_handle: fdbg::DebugAgentControlHandle,
376 }
377
378 impl FakeDebugAgentService {
379 fn new(server_end: fidl::endpoints::ServerEnd<fdbg::DebugAgentMarker>) -> Self {
381 let (request_stream, control_handle) = server_end.into_stream_and_control_handle();
382 FakeDebugAgentService { request_stream, control_handle }
383 }
384
385 async fn expect_attach_to(
387 &mut self,
388 expected_pattern: &str,
389 expected_type: fdbg::FilterType,
390 expected_options: fdbg::FilterOptions,
391 num_matches: u32,
392 ) {
393 match self.next_request().await {
394 fdbg::DebugAgentRequest::AttachTo { pattern, type_, options, responder } => {
395 responder.send(Ok(num_matches)).expect("send response to DebugAgent::AttachTo");
396 assert_eq!(expected_pattern, pattern);
397 assert_eq!(expected_type, type_);
398 assert_eq!(expected_options, options);
399 }
400 _ => panic!("Call to unexpected method."),
401 }
402 }
403
404 async fn expect_get_process_info(
407 &mut self,
408 expected_options: &fdbg::GetProcessInfoOptions,
409 ) -> fidl::endpoints::ServerEnd<fdbg::ProcessInfoIteratorMarker> {
410 match self.next_request().await {
411 fdbg::DebugAgentRequest::GetProcessInfo { options, iterator, responder } => {
412 responder.send(Ok(())).expect("send response to DebugAgent::GetProcessInfo");
413 assert_eq!(expected_options, &options);
414 iterator
415 }
416 _ => panic!("Call to unexpected method."),
417 }
418 }
419
420 fn send_on_fatal_exception_event(&mut self, thread: u64, backtrace: &str) {
422 self.control_handle
423 .send_on_fatal_exception(&fdbg::DebugAgentOnFatalExceptionRequest {
424 thread: Some(thread),
425 backtrace: Some(backtrace.to_string()),
426 ..Default::default()
427 })
428 .expect("send OnFatalException event");
429 }
430
431 fn expect_nothing_more(&mut self) {
433 match self.request_stream.try_next().now_or_never() {
434 Some(Ok(None)) => {
435 assert!(false, "Expected nothing more, got connection closed");
436 }
437 Some(Ok(v)) => {
438 assert!(false, "Expected nothing more, got request {:?}", v);
439 }
440 Some(Err(e)) => {
441 assert!(false, "Expected nothing more, got stream error {:?}", e);
442 }
443 None => {}
444 }
445 }
446
447 async fn next_request(&mut self) -> fdbg::DebugAgentRequest {
449 self.request_stream
450 .try_next()
451 .await
452 .expect("get request")
453 .expect("connection not closed")
454 }
455 }
456
457 async fn start_agent() -> (DebugAgent, FakeDebugAgentService) {
458 let (launcher_proxy, mut launcher_service) = FakeLauncherService::new();
460 launcher_service.expect_nothing_more();
461
462 let (under_test, mut debug_agent_service) =
464 futures::future::join(DebugAgent::from_launcher_proxy(launcher_proxy), async {
465 let mut debug_agent_service = launcher_service.expect_launch_request().await;
466 debug_agent_service
467 .expect_attach_to(
468 TEST_ROOT_REALM_NAME,
469 fdbg::FilterType::MonikerSuffix,
470 fdbg::FilterOptions {
471 recursive: Some(true),
472 job_only: Some(true),
473 ..Default::default()
474 },
475 0,
476 )
477 .await;
478 debug_agent_service
479 })
480 .await;
481
482 let under_test = under_test.expect("create launcher from proxy");
483
484 launcher_service.expect_connection_closed().await;
486 debug_agent_service.expect_nothing_more();
487
488 (under_test, debug_agent_service)
489 }
490
491 async fn serve_process_info_iterator(
493 server_end: fidl::endpoints::ServerEnd<fdbg::ProcessInfoIteratorMarker>,
494 ) {
495 let mut request_stream = server_end.into_stream();
496
497 let request =
499 request_stream.try_next().await.expect("get request").expect("connection not closed");
500 match request {
501 fdbg::ProcessInfoIteratorRequest::GetNext { responder } => {
502 responder
503 .send(Ok(&vec![
504 fdbg::ProcessInfo {
505 process: TEST_PROCESS_1,
506 moniker: TEST_MONIKER_1.to_string(),
507 thread: TEST_THREAD_1,
508 details: fdbg::ThreadDetails {
509 backtrace: Some(TEST_BACKTRACE_1.to_string()),
510 ..Default::default()
511 },
512 },
513 fdbg::ProcessInfo {
514 process: TEST_PROCESS_2,
515 moniker: TEST_MONIKER_2.to_string(),
516 thread: TEST_THREAD_2,
517 details: fdbg::ThreadDetails {
518 backtrace: Some(TEST_BACKTRACE_2.to_string()),
519 ..Default::default()
520 },
521 },
522 ]))
523 .expect("send response");
524 }
525 }
526
527 let request =
529 request_stream.try_next().await.expect("get request").expect("connection not closed");
530 match request {
531 fdbg::ProcessInfoIteratorRequest::GetNext { responder } => {
532 responder.send(Ok(&vec![])).expect("send response");
533 }
534 }
535
536 if let Some(request) = request_stream.try_next().await.expect("get request") {
538 assert!(false, "Expected connection closed, got request {:?}", request);
539 }
540 }
541}