1 // Copyright 2022, The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! Rust wrapper for tombstoned client. 16 17 pub use ffi::DebuggerdDumpType; 18 use std::fs::File; 19 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; 20 use thiserror::Error; 21 22 /// Error communicating with tombstoned. 23 #[derive(Clone, Debug, Error, Eq, PartialEq)] 24 #[error("Error communicating with tombstoned")] 25 pub struct Error; 26 27 /// File descriptors for communicating with tombstoned. 28 pub struct TombstonedConnection { 29 /// The socket connection to tombstoned. 30 /// 31 /// This is actually a Unix SOCK_SEQPACKET socket not a file, but the Rust standard library 32 /// doesn't have an appropriate type and it's not really worth bringing in a dependency on `uds` 33 /// or something when all we do is pass it back to C++ or close it. 34 tombstoned_socket: File, 35 /// The file descriptor for text output. 36 pub text_output: Option<File>, 37 /// The file descriptor for proto output. 38 pub proto_output: Option<File>, 39 } 40 41 impl TombstonedConnection { from_raw_fds( tombstoned_socket: RawFd, text_output_fd: RawFd, proto_output_fd: RawFd, ) -> Self42 unsafe fn from_raw_fds( 43 tombstoned_socket: RawFd, 44 text_output_fd: RawFd, 45 proto_output_fd: RawFd, 46 ) -> Self { 47 Self { 48 tombstoned_socket: File::from_raw_fd(tombstoned_socket), 49 text_output: if text_output_fd >= 0 { 50 Some(File::from_raw_fd(text_output_fd)) 51 } else { 52 None 53 }, 54 proto_output: if proto_output_fd >= 0 { 55 Some(File::from_raw_fd(proto_output_fd)) 56 } else { 57 None 58 }, 59 } 60 } 61 62 /// Connects to tombstoned. connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error>63 pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error> { 64 let mut tombstoned_socket = -1; 65 let mut text_output_fd = -1; 66 let mut proto_output_fd = -1; 67 if ffi::tombstoned_connect_files( 68 pid, 69 &mut tombstoned_socket, 70 &mut text_output_fd, 71 &mut proto_output_fd, 72 dump_type, 73 ) { 74 Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) }) 75 } else { 76 Err(Error) 77 } 78 } 79 80 /// Notifies tombstoned that the dump is complete. notify_completion(&self) -> Result<(), Error>81 pub fn notify_completion(&self) -> Result<(), Error> { 82 if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) { 83 Ok(()) 84 } else { 85 Err(Error) 86 } 87 } 88 } 89 90 #[cxx::bridge] 91 mod ffi { 92 /// The type of dump. 93 enum DebuggerdDumpType { 94 /// A native backtrace. 95 #[cxx_name = "kDebuggerdNativeBacktrace"] 96 NativeBacktrace, 97 /// A tombstone. 98 #[cxx_name = "kDebuggerdTombstone"] 99 Tombstone, 100 /// A Java backtrace. 101 #[cxx_name = "kDebuggerdJavaBacktrace"] 102 JavaBacktrace, 103 /// Any intercept. 104 #[cxx_name = "kDebuggerdAnyIntercept"] 105 AnyIntercept, 106 /// A tombstone proto. 107 #[cxx_name = "kDebuggerdTombstoneProto"] 108 TombstoneProto, 109 } 110 111 unsafe extern "C++" { 112 include!("wrapper.hpp"); 113 114 type DebuggerdDumpType; 115 tombstoned_connect_files( pid: i32, tombstoned_socket: &mut i32, text_output_fd: &mut i32, proto_output_fd: &mut i32, dump_type: DebuggerdDumpType, ) -> bool116 fn tombstoned_connect_files( 117 pid: i32, 118 tombstoned_socket: &mut i32, 119 text_output_fd: &mut i32, 120 proto_output_fd: &mut i32, 121 dump_type: DebuggerdDumpType, 122 ) -> bool; 123 tombstoned_notify_completion(tombstoned_socket: i32) -> bool124 fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool; 125 } 126 } 127 128 #[cfg(test)] 129 mod tests { 130 use super::*; 131 use std::{io::Write, process}; 132 133 // Verify that we can connect to tombstoned, write something to the file descriptor it returns, 134 // and notify completion, without any errors. 135 #[test] test()136 fn test() { 137 let connection = 138 TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone) 139 .expect("Failed to connect to tombstoned."); 140 141 assert!(connection.proto_output.is_none()); 142 connection 143 .text_output 144 .as_ref() 145 .expect("No text output FD returned.") 146 .write_all(b"test data") 147 .expect("Failed to write to text output FD."); 148 149 connection 150 .notify_completion() 151 .expect("Failed to notify completion."); 152 } 153 } 154