1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
use super::{Header, SocketName, Stream};
use crate::Error;
use std::io::IoSlice;
/// Client side of the connection, which runs in the process that may (or has)
/// crashed to communicate with an external monitor process.
pub struct Client {
socket: Stream,
/// On Macos we need this additional mach port based client to send crash
/// contexts, as, unfortunately, it's the best (though hopefully not only?)
/// way to get the real info needed by the minidump writer to write the
/// minidump
#[cfg(target_os = "macos")]
port: crash_context::ipc::Client,
}
impl Client {
/// Creates a new client with the given name.
///
/// # Errors
///
/// The specified socket name is invalid, or a connection cannot be made
/// with a server
pub fn with_name<'scope>(sn: SocketName<'scope>) -> Result<Self, Error> {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "android"))] {
let socket_addr = match sn {
SocketName::Path(path) => {
uds::UnixSocketAddr::from_path(path).map_err(|_err| Error::InvalidName)?
}
SocketName::Abstract(name) => {
uds::UnixSocketAddr::from_abstract(name).map_err(|_err| Error::InvalidName)?
}
};
let socket = Stream::connect_unix_addr(&socket_addr)?;
} else if #[cfg(target_os = "windows")] {
let SocketName::Path(path) = sn;
let socket = Stream::connect(path)?;
} else if #[cfg(target_os = "macos")] {
let SocketName::Path(path) = sn;
let socket = Stream::connect(path)?;
// Note that sun_path is limited to 108 characters including null,
// while a mach port name is limited to 128 including null, so
// the length is already effectively checked here
let port_name = std::ffi::CString::new(path.to_str().ok_or(Error::InvalidPortName)?).map_err(|_err| Error::InvalidPortName)?;
let port = crash_context::ipc::Client::create(&port_name)?;
} else {
compile_error!("unimplemented target platform");
}
}
let s = Self {
socket,
#[cfg(target_os = "macos")]
port,
};
#[cfg(target_os = "macos")]
{
// Since we aren't sending crash requests as id 0 like for other
// platforms, we instead abuse it to send the pid of this process
// so that the server can pair the port and the socket together
let id_buf = std::process::id().to_ne_bytes();
s.send_message_impl(0, &id_buf)?;
let mut ack = [0u8; 1];
s.socket.recv(&mut ack)?;
}
Ok(s)
}
/// Requests that the server generate a minidump for the specified crash
/// context. This blocks until the server has finished writing the minidump.
///
/// # Linux
///
/// This uses a [`crash_context::CrashContext`] by reference as the size of
/// it can be larger than one would want in an alternate stack handler, the
/// use of a reference allows the context to be stored outside of the stack
/// and heap to avoid that complication, though you may of course generate
/// one however you like.
///
/// # Windows
///
/// This uses a [`crash_context::CrashContext`] by reference, as
/// the crash context internally contains pointers into this process'
/// memory that need to stay valid for the duration of the mindump creation.
///
/// # Macos
///
/// It is _highly_ recommended that you suspend all threads in the current
/// process (other than the thread that executes this method) via
/// [`thread_suspend`](https://developer.apple.com/documentation/kernel/1418833-thread_suspend)
/// (apologies for the terrible documentation, blame Apple) before calling
/// this method
pub fn request_dump(&self, crash_context: &crash_context::CrashContext) -> Result<(), Error> {
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "android"))] {
let crash_ctx_buffer = crash_context.as_bytes();
} else if #[cfg(target_os = "windows")] {
use scroll::Pwrite;
let mut buf = [0u8; 24];
let written = buf.pwrite(
super::DumpRequest {
exception_pointers: crash_context.exception_pointers as _,
process_id: crash_context.process_id,
thread_id: crash_context.thread_id,
exception_code: crash_context.exception_code,
},
0,
)?;
let crash_ctx_buffer = &buf[..written];
} else if #[cfg(target_os = "macos")] {
self.port.send_crash_context(
crash_context,
Some(std::time::Duration::from_secs(2)),
Some(std::time::Duration::from_secs(5))
)?;
Ok(())
}
}
#[cfg(not(target_os = "macos"))]
{
self.send_message_impl(0, crash_ctx_buffer)?;
// Wait for the server to send back an ack that it has finished
// with the crash context
let mut ack = [0u8; std::mem::size_of::<Header>()];
self.socket.recv(&mut ack)?;
let header = Header::from_bytes(&ack);
if header.filter(|hdr| hdr.kind == super::CRASH_ACK).is_none() {
return Err(Error::ProtocolError("received invalid response to crash"));
}
Ok(())
}
}
/// Sends a message to the server.
///
/// This method is provided so that users can send their own application
/// specific messages to the monitor process.
///
/// There are no limits imposed by this method itself, but it is recommended
/// to keep the message reasonably sized, eg. below 64KiB, as different
/// targets will have different limits for the maximum payload that can be
/// delivered.
///
/// It is also important to note that this method can be called from multiple
/// threads if you so choose. Each message is sent vectored and thus won't
/// be split, but if you care about ordering you will need to handle that
/// yourself.
///
/// # Errors
///
/// The send to the server fails
#[inline]
pub fn send_message(&self, kind: u32, buf: impl AsRef<[u8]>) -> Result<(), Error> {
debug_assert!(kind < u32::MAX - super::USER);
self.send_message_impl(kind + super::USER, buf.as_ref())
// TODO: should we have an ACK? IPC is a (relatively) reliable communication
// method, and reserving receives from the server for the exclusive
// use of crash dumping, the main thing that users will care about, means
// we reduce complication
// let mut ack = [0u8; 1];
// self.socket.recv(&mut ack)?;
}
/// Sends a ping to the server, to keep it from reaping connections that haven't
/// sent a message within its keep alive window
///
/// # Errors
///
/// The send to the server fails
#[inline]
pub fn ping(&self) -> Result<(), Error> {
self.send_message_impl(super::PING, &[])?;
let mut pong = [0u8; std::mem::size_of::<Header>()];
self.socket.recv(&mut pong)?;
let header = Header::from_bytes(&pong);
if header.filter(|hdr| hdr.kind == super::PONG).is_none() {
Err(Error::ProtocolError("received invalid response to ping"))
} else {
Ok(())
}
}
fn send_message_impl(&self, kind: u32, buf: &[u8]) -> Result<(), Error> {
let header = Header {
kind,
size: buf.len() as u32,
};
let io_bufs = [IoSlice::new(header.as_bytes()), IoSlice::new(buf)];
self.socket.send_vectored(&io_bufs)?;
Ok(())
}
}