xref: /aosp_15_r20/external/google-breakpad/src/client/windows/crash_generation/crash_generation_client.cc (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
1 // Copyright 2008 Google LLC
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //     * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 //     * Neither the name of Google LLC nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>  // Must come first
31 #endif
32 
33 #include "client/windows/crash_generation/crash_generation_client.h"
34 #include <cassert>
35 #include <utility>
36 #include "client/windows/common/ipc_protocol.h"
37 
38 namespace google_breakpad {
39 
40 const int kPipeBusyWaitTimeoutMs = 2000;
41 
42 #ifdef _DEBUG
43 const DWORD kWaitForServerTimeoutMs = INFINITE;
44 #else
45 const DWORD kWaitForServerTimeoutMs = 15000;
46 #endif
47 
48 const int kPipeConnectMaxAttempts = 2;
49 
50 const DWORD kPipeDesiredAccess = FILE_READ_DATA |
51                                  FILE_WRITE_DATA |
52                                  FILE_WRITE_ATTRIBUTES;
53 
54 const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION |
55                                       SECURITY_SQOS_PRESENT;
56 
57 const DWORD kPipeMode = PIPE_READMODE_MESSAGE;
58 
59 const size_t kWaitEventCount = 2;
60 
61 // This function is orphan for production code. It can be used
62 // for debugging to help repro some scenarios like the client
63 // is slow in writing to the pipe after connecting, the client
64 // is slow in reading from the pipe after writing, etc. The parameter
65 // overlapped below is not used and it is present to match the signature
66 // of this function to TransactNamedPipe Win32 API. Uncomment if needed
67 // for debugging.
68 /**
69 static bool TransactNamedPipeDebugHelper(HANDLE pipe,
70                                          const void* in_buffer,
71                                          DWORD in_size,
72                                          void* out_buffer,
73                                          DWORD out_size,
74                                          DWORD* bytes_count,
75                                          LPOVERLAPPED) {
76   // Uncomment the next sleep to create a gap before writing
77   // to pipe.
78   // Sleep(5000);
79 
80   if (!WriteFile(pipe,
81                  in_buffer,
82                  in_size,
83                  bytes_count,
84                  NULL)) {
85     return false;
86   }
87 
88   // Uncomment the next sleep to create a gap between write
89   // and read.
90   // Sleep(5000);
91 
92   return ReadFile(pipe, out_buffer, out_size, bytes_count, NULL) != FALSE;
93 }
94 **/
95 
CrashGenerationClient(const wchar_t * pipe_name,MINIDUMP_TYPE dump_type,const CustomClientInfo * custom_info)96 CrashGenerationClient::CrashGenerationClient(
97     const wchar_t* pipe_name,
98     MINIDUMP_TYPE dump_type,
99     const CustomClientInfo* custom_info)
100         : pipe_name_(pipe_name),
101           pipe_handle_(NULL),
102           custom_info_(),
103           dump_type_(dump_type),
104           crash_event_(NULL),
105           crash_generated_(NULL),
106           server_alive_(NULL),
107           server_process_id_(0),
108           thread_id_(0),
109           exception_pointers_(NULL) {
110   memset(&assert_info_, 0, sizeof(assert_info_));
111   if (custom_info) {
112     custom_info_ = *custom_info;
113   }
114 }
115 
CrashGenerationClient(HANDLE pipe_handle,MINIDUMP_TYPE dump_type,const CustomClientInfo * custom_info)116 CrashGenerationClient::CrashGenerationClient(
117     HANDLE pipe_handle,
118     MINIDUMP_TYPE dump_type,
119     const CustomClientInfo* custom_info)
120         : pipe_name_(),
121           pipe_handle_(pipe_handle),
122           custom_info_(),
123           dump_type_(dump_type),
124           crash_event_(NULL),
125           crash_generated_(NULL),
126           server_alive_(NULL),
127           server_process_id_(0),
128           thread_id_(0),
129           exception_pointers_(NULL) {
130   memset(&assert_info_, 0, sizeof(assert_info_));
131   if (custom_info) {
132     custom_info_ = *custom_info;
133   }
134 }
135 
~CrashGenerationClient()136 CrashGenerationClient::~CrashGenerationClient() {
137   if (crash_event_) {
138     CloseHandle(crash_event_);
139   }
140 
141   if (crash_generated_) {
142     CloseHandle(crash_generated_);
143   }
144 
145   if (server_alive_) {
146     CloseHandle(server_alive_);
147   }
148 }
149 
150 // Performs the registration step with the server process.
151 // The registration step involves communicating with the server
152 // via a named pipe. The client sends the following pieces of
153 // data to the server:
154 //
155 // * Message tag indicating the client is requesting registration.
156 // * Process id of the client process.
157 // * Address of a DWORD variable in the client address space
158 //   that will contain the thread id of the client thread that
159 //   caused the crash.
160 // * Address of a EXCEPTION_POINTERS* variable in the client
161 //   address space that will point to an instance of EXCEPTION_POINTERS
162 //   when the crash happens.
163 // * Address of an instance of MDRawAssertionInfo that will contain
164 //   relevant information in case of non-exception crashes like assertion
165 //   failures and pure calls.
166 //
167 // In return the client expects the following information from the server:
168 //
169 // * Message tag indicating successful registration.
170 // * Server process id.
171 // * Handle to an object that client can signal to request dump
172 //   generation from the server.
173 // * Handle to an object that client can wait on after requesting
174 //   dump generation for the server to finish dump generation.
175 // * Handle to a mutex object that client can wait on to make sure
176 //   server is still alive.
177 //
178 // If any step of the expected behavior mentioned above fails, the
179 // registration step is not considered successful and hence out-of-process
180 // dump generation service is not available.
181 //
182 // Returns true if the registration is successful; false otherwise.
Register()183 bool CrashGenerationClient::Register() {
184   if (IsRegistered()) {
185     return true;
186   }
187 
188   HANDLE pipe = ConnectToServer();
189   if (!pipe) {
190     return false;
191   }
192 
193   bool success = RegisterClient(pipe);
194   CloseHandle(pipe);
195   return success;
196 }
197 
RequestUpload(DWORD crash_id)198 bool CrashGenerationClient::RequestUpload(DWORD crash_id) {
199   HANDLE pipe = ConnectToServer();
200   if (!pipe) {
201     return false;
202   }
203 
204   CustomClientInfo custom_info = {NULL, 0};
205   ProtocolMessage msg(MESSAGE_TAG_UPLOAD_REQUEST, crash_id,
206                       static_cast<MINIDUMP_TYPE>(NULL), NULL, NULL, NULL,
207                       custom_info, NULL, NULL, NULL);
208   DWORD bytes_count = 0;
209   bool success = WriteFile(pipe, &msg, sizeof(msg), &bytes_count, NULL) != 0;
210 
211   CloseHandle(pipe);
212   return success;
213 }
214 
ConnectToServer()215 HANDLE CrashGenerationClient::ConnectToServer() {
216   HANDLE pipe = ConnectToPipe(pipe_name_.c_str(),
217                               kPipeDesiredAccess,
218                               kPipeFlagsAndAttributes);
219   if (!pipe) {
220     return NULL;
221   }
222 
223   DWORD mode = kPipeMode;
224   if (!SetNamedPipeHandleState(pipe, &mode, NULL, NULL)) {
225     CloseHandle(pipe);
226     pipe = NULL;
227   }
228 
229   return pipe;
230 }
231 
RegisterClient(HANDLE pipe)232 bool CrashGenerationClient::RegisterClient(HANDLE pipe) {
233   ProtocolMessage msg(MESSAGE_TAG_REGISTRATION_REQUEST,
234                       GetCurrentProcessId(),
235                       dump_type_,
236                       &thread_id_,
237                       &exception_pointers_,
238                       &assert_info_,
239                       custom_info_,
240                       NULL,
241                       NULL,
242                       NULL);
243   ProtocolMessage reply;
244   DWORD bytes_count = 0;
245   // The call to TransactNamedPipe below can be changed to a call
246   // to TransactNamedPipeDebugHelper to help repro some scenarios.
247   // For details see comments for TransactNamedPipeDebugHelper.
248   if (!TransactNamedPipe(pipe,
249                          &msg,
250                          sizeof(msg),
251                          &reply,
252                          sizeof(ProtocolMessage),
253                          &bytes_count,
254                          NULL)) {
255     return false;
256   }
257 
258   if (!ValidateResponse(reply)) {
259     return false;
260   }
261 
262   ProtocolMessage ack_msg;
263   ack_msg.tag = MESSAGE_TAG_REGISTRATION_ACK;
264 
265   if (!WriteFile(pipe, &ack_msg, sizeof(ack_msg), &bytes_count, NULL)) {
266     return false;
267   }
268   crash_event_ = reply.dump_request_handle;
269   crash_generated_ = reply.dump_generated_handle;
270   server_alive_ = reply.server_alive_handle;
271   server_process_id_ = reply.id;
272 
273   return true;
274 }
275 
ConnectToPipe(const wchar_t * pipe_name,DWORD pipe_access,DWORD flags_attrs)276 HANDLE CrashGenerationClient::ConnectToPipe(const wchar_t* pipe_name,
277                                             DWORD pipe_access,
278                                             DWORD flags_attrs) {
279   if (pipe_handle_) {
280     HANDLE t = pipe_handle_;
281     pipe_handle_ = NULL;
282     return t;
283   }
284 
285   for (int i = 0; i < kPipeConnectMaxAttempts; ++i) {
286     HANDLE pipe = CreateFile(pipe_name,
287                              pipe_access,
288                              0,
289                              NULL,
290                              OPEN_EXISTING,
291                              flags_attrs,
292                              NULL);
293     if (pipe != INVALID_HANDLE_VALUE) {
294       return pipe;
295     }
296 
297     // Cannot continue retrying if error is something other than
298     // ERROR_PIPE_BUSY.
299     if (GetLastError() != ERROR_PIPE_BUSY) {
300       break;
301     }
302 
303     // Cannot continue retrying if wait on pipe fails.
304     if (!WaitNamedPipe(pipe_name, kPipeBusyWaitTimeoutMs)) {
305       break;
306     }
307   }
308 
309   return NULL;
310 }
311 
ValidateResponse(const ProtocolMessage & msg) const312 bool CrashGenerationClient::ValidateResponse(
313     const ProtocolMessage& msg) const {
314   return (msg.tag == MESSAGE_TAG_REGISTRATION_RESPONSE) &&
315          (msg.id != 0) &&
316          (msg.dump_request_handle != NULL) &&
317          (msg.dump_generated_handle != NULL) &&
318          (msg.server_alive_handle != NULL);
319 }
320 
IsRegistered() const321 bool CrashGenerationClient::IsRegistered() const {
322   return crash_event_ != NULL;
323 }
324 
RequestDump(EXCEPTION_POINTERS * ex_info,MDRawAssertionInfo * assert_info)325 bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info,
326                                         MDRawAssertionInfo* assert_info) {
327   if (!IsRegistered()) {
328     return false;
329   }
330 
331   exception_pointers_ = ex_info;
332   thread_id_ = GetCurrentThreadId();
333 
334   if (assert_info) {
335     memcpy(&assert_info_, assert_info, sizeof(assert_info_));
336   } else {
337     memset(&assert_info_, 0, sizeof(assert_info_));
338   }
339 
340   return SignalCrashEventAndWait();
341 }
342 
RequestDump(EXCEPTION_POINTERS * ex_info)343 bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info) {
344   return RequestDump(ex_info, NULL);
345 }
346 
RequestDump(MDRawAssertionInfo * assert_info)347 bool CrashGenerationClient::RequestDump(MDRawAssertionInfo* assert_info) {
348   return RequestDump(NULL, assert_info);
349 }
350 
SignalCrashEventAndWait()351 bool CrashGenerationClient::SignalCrashEventAndWait() {
352   assert(crash_event_);
353   assert(crash_generated_);
354   assert(server_alive_);
355 
356   // Reset the dump generated event before signaling the crash
357   // event so that the server can set the dump generated event
358   // once it is done generating the event.
359   if (!ResetEvent(crash_generated_)) {
360     return false;
361   }
362 
363   if (!SetEvent(crash_event_)) {
364     return false;
365   }
366 
367   HANDLE wait_handles[kWaitEventCount] = {crash_generated_, server_alive_};
368 
369   DWORD result = WaitForMultipleObjects(kWaitEventCount,
370                                         wait_handles,
371                                         FALSE,
372                                         kWaitForServerTimeoutMs);
373 
374   // Crash dump was successfully generated only if the server
375   // signaled the crash generated event.
376   return result == WAIT_OBJECT_0;
377 }
378 
DuplicatePipeToClientProcess(const wchar_t * pipe_name,HANDLE hProcess)379 HANDLE CrashGenerationClient::DuplicatePipeToClientProcess(const wchar_t* pipe_name,
380                                                            HANDLE hProcess) {
381   for (int i = 0; i < kPipeConnectMaxAttempts; ++i) {
382     HANDLE local_pipe = CreateFile(pipe_name, kPipeDesiredAccess,
383                                    0, NULL, OPEN_EXISTING,
384                                    kPipeFlagsAndAttributes, NULL);
385     if (local_pipe != INVALID_HANDLE_VALUE) {
386       HANDLE remotePipe = INVALID_HANDLE_VALUE;
387       if (DuplicateHandle(GetCurrentProcess(), local_pipe,
388                           hProcess, &remotePipe, 0, FALSE,
389                           DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
390         return remotePipe;
391       } else {
392         return INVALID_HANDLE_VALUE;
393       }
394     }
395 
396     // Cannot continue retrying if the error wasn't a busy pipe.
397     if (GetLastError() != ERROR_PIPE_BUSY) {
398       return INVALID_HANDLE_VALUE;
399     }
400 
401     if (!WaitNamedPipe(pipe_name, kPipeBusyWaitTimeoutMs)) {
402       return INVALID_HANDLE_VALUE;
403     }
404   }
405   return INVALID_HANDLE_VALUE;
406 }
407 
408 }  // namespace google_breakpad
409