test_manager_lib/
debug_agent.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    // Creates a new `DebugAgent` and starts processing events received via the connection.
31    // The new `DebugAgent` maintains the connection until dropped.
32    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    // Creates a new `DebugAgent` from a launcher proxy and starts processing events received
41    // via the connection. The new `DebugAgent` maintains the connection until dropped.
42    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
54            .attach_to(
55                TEST_ROOT_REALM_NAME,
56                fdbg::FilterType::MonikerSuffix,
57                &fdbg::FilterOptions { job_only: Some(true), ..Default::default() },
58            )
59            .await
60            .map_err(|e| DebugAgentError::AttachToTestsLocal(e))?
61            .map_err(|e| DebugAgentError::AttachToTestsResponse(e))?;
62
63        let (fatal_exception_sender, fatal_exception_receiver) = mpsc::channel(1024);
64        let event_stream_processor_handle =
65            Self::begin_process_event_stream(proxy.take_event_stream(), fatal_exception_sender);
66
67        Ok(Self {
68            proxy,
69            event_stream_processor_handle: Some(event_stream_processor_handle),
70            takeable_fatal_exception_recevier: Some(fatal_exception_receiver),
71        })
72    }
73
74    // Takes the fatal exception receiver. This function panics if the receiver has already
75    // been taken.
76    pub(crate) fn take_fatal_exception_receiver(&mut self) -> mpsc::Receiver<ThreadBacktraceInfo> {
77        self.takeable_fatal_exception_recevier.take().unwrap()
78    }
79
80    // Stops sending fatal exceptions and closes the channel of fatal exceptions.
81    pub(crate) fn stop_sending_fatal_exceptions(&mut self) {
82        self.event_stream_processor_handle = None;
83    }
84
85    // Reports backtraces from all attached processes. This function executes in non-trivial
86    // time, but won't block long-term if the debug agent server is working properly.
87    pub(crate) async fn report_all_backtraces(
88        &self,
89        mut event_sender: mpsc::Sender<Result<SuiteEvents, LaunchError>>,
90    ) {
91        let (client_end, mut server_end) = fidl::handle::fuchsia_handles::Socket::create_stream();
92        event_sender.send(Ok(SuiteEvents::suite_stderr(client_end).into())).await.unwrap();
93
94        let (iterator_proxy, iterator_server_end) = fidl::endpoints::create_proxy();
95        if let Err(_) = self
96            .proxy
97            .get_process_info(
98                &fdbg::GetProcessInfoOptions {
99                    filter: Some(fdbg::Filter {
100                        pattern: TEST_ROOT_REALM_NAME.to_string(),
101                        type_: fdbg::FilterType::MonikerSuffix,
102                        options: fdbg::FilterOptions { ..Default::default() },
103                    }),
104                    interest: Some(fdbg::ThreadDetailsInterest {
105                        backtrace: Some(true),
106                        ..Default::default()
107                    }),
108                    ..Default::default()
109                },
110                iterator_server_end,
111            )
112            .await
113        {
114            // report error?
115            return;
116        }
117
118        loop {
119            match iterator_proxy.get_next().await {
120                Ok(result) => match result {
121                    Ok(infos) => {
122                        if infos.len() == 0 {
123                            break;
124                        }
125
126                        for info in infos {
127                            let _ =
128                                server_end.write(format!("thread {:?}\n", info.thread).as_bytes());
129                            DebugAgent::write_backtrace_info(
130                                &ThreadBacktraceInfo {
131                                    thread: info.thread,
132                                    backtrace: info.details.backtrace.unwrap(),
133                                },
134                                &mut server_end,
135                            )
136                            .await;
137                        }
138                    }
139                    Err(_e) => {
140                        break;
141                    }
142                },
143                Err(_e) => {
144                    break;
145                }
146            }
147        }
148    }
149
150    // Begins processing events sent by the debug agent server. Event processing runs in a
151    // spawned task and termines when the returned `RemoteHandle` is dropped.
152    fn begin_process_event_stream(
153        mut event_stream: fdbg::DebugAgentEventStream,
154        mut sender: mpsc::Sender<ThreadBacktraceInfo>,
155    ) -> RemoteHandle<()> {
156        let (event_stream_receiver, event_stream_receiver_handle) = async move {
157            loop {
158                match event_stream.next().await {
159                    Some(Ok(fdbg::DebugAgentEvent::OnFatalException {
160                        payload:
161                            fdbg::DebugAgentOnFatalExceptionRequest {
162                                thread: Some(thread),
163                                backtrace: Some(backtrace),
164                                ..
165                            },
166                    })) => {
167                        sender.send(ThreadBacktraceInfo { thread, backtrace }).await.unwrap();
168                    }
169                    Some(_) => {}
170                    None => break,
171                }
172            }
173        }
174        .remote_handle();
175
176        fasync::Task::spawn(event_stream_receiver).detach();
177
178        event_stream_receiver_handle
179    }
180
181    pub(crate) async fn write_backtrace_info(
182        backtrace_info: &ThreadBacktraceInfo,
183        socket_server_end: &mut fidl::Socket,
184    ) {
185        for line in backtrace_info.backtrace.split('\n') {
186            if line.starts_with("{{{reset:begin}}}") {
187                let _ = socket_server_end.write(line[..17].as_bytes());
188                let _ = socket_server_end.write(b"\n");
189                let _ = socket_server_end.write(line[17..].as_bytes());
190            } else {
191                let _ = socket_server_end.write(line.as_bytes());
192            }
193
194            let _ = socket_server_end.write(b"\n");
195        }
196    }
197}
198
199#[cfg(test)]
200mod test {
201    use crate::run_events::SuiteEventPayload;
202
203    use super::*;
204
205    const TEST_THREAD_1: u64 = 1234;
206    const TEST_THREAD_2: u64 = 5678;
207    const TEST_PROCESS_1: u64 = 4321;
208    const TEST_PROCESS_2: u64 = 8765;
209    const TEST_BACKTRACE_1: &str = "{{{reset:begin}}}test backtrace 1, with reset:begin prefix";
210    const TEST_BACKTRACE_2: &str = "test backtrace 2, no special prefix";
211    const TEST_MONIKER_1: &str = "test moniker 1";
212    const TEST_MONIKER_2: &str = "test moniker 2";
213    const TEST_STDERR_TEXT: &str = "thread 1234\n{{{reset:begin}}}\ntest backtrace 1, with \
214        reset:begin prefix\nthread 5678\ntest backtrace 2, no special prefix\n";
215    const TEST_STDERR_SIZE: usize = 120;
216
217    #[fuchsia::test]
218    async fn fatal_exceptions() {
219        let (mut under_test, mut debug_agent_service) = start_agent().await;
220
221        // Pretend a fatal exception has occurred.
222        debug_agent_service.send_on_fatal_exception_event(TEST_THREAD_1, TEST_BACKTRACE_1);
223
224        // Expect to receive the fatal exception.
225        let mut receiver = under_test.take_fatal_exception_receiver();
226        let thread_backtrace_info = receiver.next().await.expect("receive fatal exception");
227        assert_eq!(TEST_THREAD_1, thread_backtrace_info.thread);
228        assert_eq!(TEST_BACKTRACE_1, thread_backtrace_info.backtrace);
229
230        debug_agent_service.expect_nothing_more();
231    }
232
233    #[fuchsia::test]
234    async fn all_backtraces() {
235        let (under_test, mut debug_agent_service) = start_agent().await;
236
237        let (event_sender, mut event_receiver) =
238            mpsc::channel::<Result<SuiteEvents, LaunchError>>(16);
239
240        // Call `report_all_backtraces` while expecting a `GetProcessInfo` request and serving
241        // the process info iterator that is returned.
242        futures::future::join(under_test.report_all_backtraces(event_sender), async {
243            let iterator_server_end = debug_agent_service
244                .expect_get_process_info(&fdbg::GetProcessInfoOptions {
245                    filter: Some(fdbg::Filter {
246                        pattern: TEST_ROOT_REALM_NAME.to_string(),
247                        type_: fdbg::FilterType::MonikerSuffix,
248                        options: fdbg::FilterOptions { ..Default::default() },
249                    }),
250                    interest: Some(fdbg::ThreadDetailsInterest {
251                        backtrace: Some(true),
252                        ..Default::default()
253                    }),
254                    ..Default::default()
255                })
256                .await;
257            serve_process_info_iterator(iterator_server_end).await;
258        })
259        .await;
260
261        // Expect to receive a stderr event.
262        match event_receiver.try_next() {
263            Ok(Some(Ok(SuiteEvents {
264                timestamp: _,
265                payload: SuiteEventPayload::SuiteStderr(socket),
266            }))) => {
267                let mut buffer: [u8; 128] = [0; 128];
268                let size = socket.read(&mut buffer).expect("read from socket");
269                assert_eq!(TEST_STDERR_SIZE, size);
270                let stderr_string =
271                    std::str::from_utf8(&buffer[0..TEST_STDERR_SIZE]).expect("convert [u8] to str");
272                assert_eq!(TEST_STDERR_TEXT, stderr_string);
273            }
274            Ok(Some(Ok(_))) => {
275                assert!(false, "Expected SuiteStderr event, got other event");
276            }
277            Ok(Some(Err(e))) => {
278                assert!(false, "Expected event, got error {:?}", e);
279            }
280            Ok(None) => {
281                assert!(false, "Expected event, got channel closed");
282            }
283            Err(e) => {
284                assert!(false, "Expected event, got channel error {:?}", e);
285            }
286        }
287    }
288
289    // Fake fuchsia.debugger.Launcher service.
290    struct FakeLauncherService {
291        request_stream: fdbg::LauncherRequestStream,
292    }
293
294    impl FakeLauncherService {
295        // Creates a new FakeLauncherService.
296        fn new() -> (fdbg::LauncherProxy, Self) {
297            let (proxy, request_stream) =
298                fidl::endpoints::create_proxy_and_stream::<fdbg::LauncherMarker>();
299
300            (proxy, Self { request_stream })
301        }
302
303        // Expects a launch request and returns the resulting FakeDebugAgentService.
304        async fn expect_launch_request(&mut self) -> FakeDebugAgentService {
305            match self.next_request().await {
306                fdbg::LauncherRequest::Launch { agent, responder } => {
307                    responder.send(Ok(())).expect("send response to Launcher::Launch");
308                    return FakeDebugAgentService::new(agent);
309                }
310                _ => panic!("Call to unexpected method."),
311            }
312        }
313
314        async fn expect_connection_closed(&mut self) {
315            if let Some(request) = self.request_stream.try_next().await.expect("get request") {
316                assert!(false, "Expected connection closed, got request {:?}", request);
317            }
318        }
319
320        // Expects that no more requests have arrived and the client connection has not closed.
321        fn expect_nothing_more(&mut self) {
322            match self.request_stream.try_next().now_or_never() {
323                Some(Ok(None)) => {
324                    assert!(false, "Expected nothing more, got connection closed");
325                }
326                Some(Ok(v)) => {
327                    assert!(false, "Expected nothing more, got request {:?}", v);
328                }
329                Some(Err(e)) => {
330                    assert!(false, "Expected nothing more, got stream error {:?}", e);
331                }
332                None => {}
333            }
334        }
335
336        // Returns the next request.
337        async fn next_request(&mut self) -> fdbg::LauncherRequest {
338            self.request_stream
339                .try_next()
340                .await
341                .expect("get request")
342                .expect("connection not closed")
343        }
344    }
345
346    // Fake fuchsia.debugger.DebugAgent service.
347    struct FakeDebugAgentService {
348        request_stream: fdbg::DebugAgentRequestStream,
349        control_handle: fdbg::DebugAgentControlHandle,
350    }
351
352    impl FakeDebugAgentService {
353        // Creates a new FakeDebugAgentService.
354        fn new(server_end: fidl::endpoints::ServerEnd<fdbg::DebugAgentMarker>) -> Self {
355            let (request_stream, control_handle) = server_end.into_stream_and_control_handle();
356            FakeDebugAgentService { request_stream, control_handle }
357        }
358
359        // Expects an AttachTo request with the expected parameters and responds with `num_matches`.
360        async fn expect_attach_to(
361            &mut self,
362            expected_pattern: &str,
363            expected_type: fdbg::FilterType,
364            expected_options: fdbg::FilterOptions,
365            num_matches: u32,
366        ) {
367            match self.next_request().await {
368                fdbg::DebugAgentRequest::AttachTo { pattern, type_, options, responder } => {
369                    responder.send(Ok(num_matches)).expect("send response to DebugAgent::AttachTo");
370                    assert_eq!(expected_pattern, pattern);
371                    assert_eq!(expected_type, type_);
372                    assert_eq!(expected_options, options);
373                }
374                _ => panic!("Call to unexpected method."),
375            }
376        }
377
378        // Expects a GetProcessInfo request with the expected parameters and returns the server
379        // end of the process info iterator.
380        async fn expect_get_process_info(
381            &mut self,
382            expected_options: &fdbg::GetProcessInfoOptions,
383        ) -> fidl::endpoints::ServerEnd<fdbg::ProcessInfoIteratorMarker> {
384            match self.next_request().await {
385                fdbg::DebugAgentRequest::GetProcessInfo { options, iterator, responder } => {
386                    responder.send(Ok(())).expect("send response to DebugAgent::GetProcessInfo");
387                    assert_eq!(expected_options, &options);
388                    iterator
389                }
390                _ => panic!("Call to unexpected method."),
391            }
392        }
393
394        // Sends an OnFatalException event to the client.
395        fn send_on_fatal_exception_event(&mut self, thread: u64, backtrace: &str) {
396            self.control_handle
397                .send_on_fatal_exception(&fdbg::DebugAgentOnFatalExceptionRequest {
398                    thread: Some(thread),
399                    backtrace: Some(backtrace.to_string()),
400                    ..Default::default()
401                })
402                .expect("send OnFatalException event");
403        }
404
405        // Expects that no more requests have arrived and the client connection has not closed.
406        fn expect_nothing_more(&mut self) {
407            match self.request_stream.try_next().now_or_never() {
408                Some(Ok(None)) => {
409                    assert!(false, "Expected nothing more, got connection closed");
410                }
411                Some(Ok(v)) => {
412                    assert!(false, "Expected nothing more, got request {:?}", v);
413                }
414                Some(Err(e)) => {
415                    assert!(false, "Expected nothing more, got stream error {:?}", e);
416                }
417                None => {}
418            }
419        }
420
421        // Returns the next request.
422        async fn next_request(&mut self) -> fdbg::DebugAgentRequest {
423            self.request_stream
424                .try_next()
425                .await
426                .expect("get request")
427                .expect("connection not closed")
428        }
429    }
430
431    async fn start_agent() -> (DebugAgent, FakeDebugAgentService) {
432        // Create the fake launcher service.
433        let (launcher_proxy, mut launcher_service) = FakeLauncherService::new();
434        launcher_service.expect_nothing_more();
435
436        // Create a DebugAgent and the fake debug agent service.
437        let (under_test, mut debug_agent_service) =
438            futures::future::join(DebugAgent::from_launcher_proxy(launcher_proxy), async {
439                let mut debug_agent_service = launcher_service.expect_launch_request().await;
440                debug_agent_service
441                    .expect_attach_to(
442                        TEST_ROOT_REALM_NAME,
443                        fdbg::FilterType::MonikerSuffix,
444                        fdbg::FilterOptions { job_only: Some(true), ..Default::default() },
445                        0,
446                    )
447                    .await;
448                debug_agent_service
449            })
450            .await;
451
452        let under_test = under_test.expect("create launcher from proxy");
453
454        // from_launcher_proxy consumes the proxy, so the launcher connection should close.
455        launcher_service.expect_connection_closed().await;
456        debug_agent_service.expect_nothing_more();
457
458        (under_test, debug_agent_service)
459    }
460
461    // Serves a `ProcessInfoIterator` producing two process infos.
462    async fn serve_process_info_iterator(
463        server_end: fidl::endpoints::ServerEnd<fdbg::ProcessInfoIteratorMarker>,
464    ) {
465        let mut request_stream = server_end.into_stream();
466
467        // Expect a request and respond with two process infos.
468        let request =
469            request_stream.try_next().await.expect("get request").expect("connection not closed");
470        match request {
471            fdbg::ProcessInfoIteratorRequest::GetNext { responder } => {
472                responder
473                    .send(Ok(&vec![
474                        fdbg::ProcessInfo {
475                            process: TEST_PROCESS_1,
476                            moniker: TEST_MONIKER_1.to_string(),
477                            thread: TEST_THREAD_1,
478                            details: fdbg::ThreadDetails {
479                                backtrace: Some(TEST_BACKTRACE_1.to_string()),
480                                ..Default::default()
481                            },
482                        },
483                        fdbg::ProcessInfo {
484                            process: TEST_PROCESS_2,
485                            moniker: TEST_MONIKER_2.to_string(),
486                            thread: TEST_THREAD_2,
487                            details: fdbg::ThreadDetails {
488                                backtrace: Some(TEST_BACKTRACE_2.to_string()),
489                                ..Default::default()
490                            },
491                        },
492                    ]))
493                    .expect("send response");
494            }
495        }
496
497        // Expect another request and respond with an empty vector.
498        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.send(Ok(&vec![])).expect("send response");
503            }
504        }
505
506        // Expect the client to close the connection.
507        if let Some(request) = request_stream.try_next().await.expect("get request") {
508            assert!(false, "Expected connection closed, got request {:?}", request);
509        }
510    }
511}