From 3b7591248d6e643d95196ca9b8a90b47413504e8 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Thu, 24 Mar 2022 16:06:41 +0000 Subject: [PATCH] Add Rust wrapper for tombstoned client using cxx. Bug: 226162295 Test: atest libtombstoned_client_rust_test Change-Id: Ibe7c41e2381f0f369a76175d6f71fc60b71cc7d5 --- debuggerd/TEST_MAPPING | 3 + debuggerd/rust/tombstoned_client/Android.bp | 58 +++++++ debuggerd/rust/tombstoned_client/src/lib.rs | 153 +++++++++++++++++++ debuggerd/rust/tombstoned_client/wrapper.cpp | 38 +++++ debuggerd/rust/tombstoned_client/wrapper.hpp | 23 +++ 5 files changed, 275 insertions(+) create mode 100644 debuggerd/rust/tombstoned_client/Android.bp create mode 100644 debuggerd/rust/tombstoned_client/src/lib.rs create mode 100644 debuggerd/rust/tombstoned_client/wrapper.cpp create mode 100644 debuggerd/rust/tombstoned_client/wrapper.hpp diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING index d5327db35..efc13df72 100644 --- a/debuggerd/TEST_MAPPING +++ b/debuggerd/TEST_MAPPING @@ -2,6 +2,9 @@ "presubmit": [ { "name": "debuggerd_test" + }, + { + "name": "libtombstoned_client_rust_test" } ] } diff --git a/debuggerd/rust/tombstoned_client/Android.bp b/debuggerd/rust/tombstoned_client/Android.bp new file mode 100644 index 000000000..981f8a451 --- /dev/null +++ b/debuggerd/rust/tombstoned_client/Android.bp @@ -0,0 +1,58 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libtombstoned_client_wrapper", + srcs: [ + "wrapper.cpp", + ], + generated_sources: [ + "libtombstoned_client_rust_bridge_code" + ], + header_libs: [ + "libbase_headers", + "libdebuggerd_common_headers", + ], + shared_libs: [ + "libtombstoned_client", + ], +} + +rust_defaults { + name: "libtombstoned_client_rust_defaults", + crate_name: "tombstoned_client", + srcs: ["src/lib.rs"], + edition: "2021", + rustlibs: [ + "libcxx", + "libthiserror", + ], + static_libs: [ + "libtombstoned_client_wrapper", + ], + shared_libs: [ + "libtombstoned_client", + ], +} + +rust_library { + name: "libtombstoned_client_rust", + defaults: ["libtombstoned_client_rust_defaults"], + apex_available: ["com.android.virt"], +} + +rust_test { + name: "libtombstoned_client_rust_test", + defaults: ["libtombstoned_client_rust_defaults"], + require_root: true, + test_suites: ["device-tests"], +} + +genrule { + name: "libtombstoned_client_rust_bridge_code", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) >> $(out)", + srcs: ["src/lib.rs"], + out: ["libtombstoned_client_cxx_generated.cc"], +} diff --git a/debuggerd/rust/tombstoned_client/src/lib.rs b/debuggerd/rust/tombstoned_client/src/lib.rs new file mode 100644 index 000000000..5c8abef2c --- /dev/null +++ b/debuggerd/rust/tombstoned_client/src/lib.rs @@ -0,0 +1,153 @@ +// Copyright 2022, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Rust wrapper for tombstoned client. + +pub use ffi::DebuggerdDumpType; +use std::fs::File; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use thiserror::Error; + +/// Error communicating with tombstoned. +#[derive(Clone, Debug, Error, Eq, PartialEq)] +#[error("Error communicating with tombstoned")] +pub struct Error; + +/// File descriptors for communicating with tombstoned. +pub struct TombstonedConnection { + /// The socket connection to tombstoned. + /// + /// This is actually a Unix SOCK_SEQPACKET socket not a file, but the Rust standard library + /// doesn't have an appropriate type and it's not really worth bringing in a dependency on `uds` + /// or something when all we do is pass it back to C++ or close it. + tombstoned_socket: File, + /// The file descriptor for text output. + pub text_output: Option, + /// The file descriptor for proto output. + pub proto_output: Option, +} + +impl TombstonedConnection { + unsafe fn from_raw_fds( + tombstoned_socket: RawFd, + text_output_fd: RawFd, + proto_output_fd: RawFd, + ) -> Self { + Self { + tombstoned_socket: File::from_raw_fd(tombstoned_socket), + text_output: if text_output_fd >= 0 { + Some(File::from_raw_fd(text_output_fd)) + } else { + None + }, + proto_output: if proto_output_fd >= 0 { + Some(File::from_raw_fd(proto_output_fd)) + } else { + None + }, + } + } + + /// Connects to tombstoned. + pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result { + let mut tombstoned_socket = -1; + let mut text_output_fd = -1; + let mut proto_output_fd = -1; + if ffi::tombstoned_connect_files( + pid, + &mut tombstoned_socket, + &mut text_output_fd, + &mut proto_output_fd, + dump_type, + ) { + Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) }) + } else { + Err(Error) + } + } + + /// Notifies tombstoned that the dump is complete. + pub fn notify_completion(&self) -> Result<(), Error> { + if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) { + Ok(()) + } else { + Err(Error) + } + } +} + +#[cxx::bridge] +mod ffi { + /// The type of dump. + enum DebuggerdDumpType { + /// A native backtrace. + #[cxx_name = "kDebuggerdNativeBacktrace"] + NativeBacktrace, + /// A tombstone. + #[cxx_name = "kDebuggerdTombstone"] + Tombstone, + /// A Java backtrace. + #[cxx_name = "kDebuggerdJavaBacktrace"] + JavaBacktrace, + /// Any intercept. + #[cxx_name = "kDebuggerdAnyIntercept"] + AnyIntercept, + /// A tombstone proto. + #[cxx_name = "kDebuggerdTombstoneProto"] + TombstoneProto, + } + + unsafe extern "C++" { + include!("wrapper.hpp"); + + type DebuggerdDumpType; + + fn tombstoned_connect_files( + pid: i32, + tombstoned_socket: &mut i32, + text_output_fd: &mut i32, + proto_output_fd: &mut i32, + dump_type: DebuggerdDumpType, + ) -> bool; + + fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{io::Write, process}; + + // Verify that we can connect to tombstoned, write something to the file descriptor it returns, + // and notify completion, without any errors. + #[test] + fn test() { + let connection = + TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone) + .expect("Failed to connect to tombstoned."); + + assert!(connection.proto_output.is_none()); + connection + .text_output + .as_ref() + .expect("No text output FD returned.") + .write_all(b"test data") + .expect("Failed to write to text output FD."); + + connection + .notify_completion() + .expect("Failed to notify completion."); + } +} diff --git a/debuggerd/rust/tombstoned_client/wrapper.cpp b/debuggerd/rust/tombstoned_client/wrapper.cpp new file mode 100644 index 000000000..749232973 --- /dev/null +++ b/debuggerd/rust/tombstoned_client/wrapper.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wrapper.hpp" + +#include + +#include "tombstoned/tombstoned.h" + +using android::base::unique_fd; + +bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd, + int& proto_output_fd, DebuggerdDumpType dump_type) { + unique_fd tombstoned_socket_unique, text_output_unique, proto_output_unique; + + bool result = tombstoned_connect(pid, &tombstoned_socket_unique, &text_output_unique, + &proto_output_unique, dump_type); + if (result) { + tombstoned_socket = tombstoned_socket_unique.release(); + text_output_fd = text_output_unique.release(); + proto_output_fd = proto_output_unique.release(); + } + + return result; +} diff --git a/debuggerd/rust/tombstoned_client/wrapper.hpp b/debuggerd/rust/tombstoned_client/wrapper.hpp new file mode 100644 index 000000000..95d386549 --- /dev/null +++ b/debuggerd/rust/tombstoned_client/wrapper.hpp @@ -0,0 +1,23 @@ +/* + * Copyright 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "tombstoned/tombstoned.h" + +bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd, + int& proto_output_fd, DebuggerdDumpType dump_type);