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 { 42 /// # Safety 43 /// 44 /// The file descriptors must be valid and open. from_raw_fds( tombstoned_socket: RawFd, text_output_fd: RawFd, proto_output_fd: RawFd, ) -> Self45 unsafe fn from_raw_fds( 46 tombstoned_socket: RawFd, 47 text_output_fd: RawFd, 48 proto_output_fd: RawFd, 49 ) -> Self { 50 Self { 51 // SAFETY: The caller guarantees that the file descriptor is valid and open. 52 tombstoned_socket: unsafe { File::from_raw_fd(tombstoned_socket) }, 53 text_output: if text_output_fd >= 0 { 54 // SAFETY: The caller guarantees that the file descriptor is valid and open. 55 Some(unsafe { File::from_raw_fd(text_output_fd) }) 56 } else { 57 None 58 }, 59 proto_output: if proto_output_fd >= 0 { 60 // SAFETY: The caller guarantees that the file descriptor is valid and open. 61 Some(unsafe { File::from_raw_fd(proto_output_fd) }) 62 } else { 63 None 64 }, 65 } 66 } 67 68 /// Connects to tombstoned. connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error>69 pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error> { 70 let mut tombstoned_socket = -1; 71 let mut text_output_fd = -1; 72 let mut proto_output_fd = -1; 73 if ffi::tombstoned_connect_files( 74 pid, 75 &mut tombstoned_socket, 76 &mut text_output_fd, 77 &mut proto_output_fd, 78 dump_type, 79 ) { 80 // SAFETY: If tombstoned_connect_files returns successfully then they file descriptors 81 // are valid and open. 82 Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) }) 83 } else { 84 Err(Error) 85 } 86 } 87 88 /// Notifies tombstoned that the dump is complete. notify_completion(&self) -> Result<(), Error>89 pub fn notify_completion(&self) -> Result<(), Error> { 90 if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) { 91 Ok(()) 92 } else { 93 Err(Error) 94 } 95 } 96 } 97 98 #[cxx::bridge] 99 mod ffi { 100 /// The type of dump. 101 enum DebuggerdDumpType { 102 /// A native backtrace. 103 #[cxx_name = "kDebuggerdNativeBacktrace"] 104 NativeBacktrace, 105 /// A tombstone. 106 #[cxx_name = "kDebuggerdTombstone"] 107 Tombstone, 108 /// A Java backtrace. 109 #[cxx_name = "kDebuggerdJavaBacktrace"] 110 JavaBacktrace, 111 /// Any intercept. 112 #[cxx_name = "kDebuggerdAnyIntercept"] 113 AnyIntercept, 114 /// A tombstone proto. 115 #[cxx_name = "kDebuggerdTombstoneProto"] 116 TombstoneProto, 117 } 118 119 unsafe extern "C++" { 120 include!("wrapper.hpp"); 121 122 type DebuggerdDumpType; 123 tombstoned_connect_files( pid: i32, tombstoned_socket: &mut i32, text_output_fd: &mut i32, proto_output_fd: &mut i32, dump_type: DebuggerdDumpType, ) -> bool124 fn tombstoned_connect_files( 125 pid: i32, 126 tombstoned_socket: &mut i32, 127 text_output_fd: &mut i32, 128 proto_output_fd: &mut i32, 129 dump_type: DebuggerdDumpType, 130 ) -> bool; 131 tombstoned_notify_completion(tombstoned_socket: i32) -> bool132 fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool; 133 } 134 } 135 136 #[cfg(test)] 137 mod tests { 138 use super::*; 139 use std::{io::Write, process}; 140 141 // Verify that we can connect to tombstoned, write something to the file descriptor it returns, 142 // and notify completion, without any errors. 143 #[test] test()144 fn test() { 145 let connection = 146 TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone) 147 .expect("Failed to connect to tombstoned."); 148 149 assert!(connection.proto_output.is_none()); 150 connection 151 .text_output 152 .as_ref() 153 .expect("No text output FD returned.") 154 .write_all(b"test data") 155 .expect("Failed to write to text output FD."); 156 157 connection.notify_completion().expect("Failed to notify completion."); 158 } 159 } 160