1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsX11ErrorHandler.h"
7 
8 #include "prenv.h"
9 #include "nsXULAppAPI.h"
10 #include "nsExceptionHandler.h"
11 #include "nsDebug.h"
12 
13 #include "mozilla/X11Util.h"
14 #include <X11/Xlib.h>
15 
16 #define BUFSIZE 2048  // What Xlib uses with XGetErrorDatabaseText
17 
18 extern "C" {
X11Error(Display * display,XErrorEvent * event)19 int X11Error(Display* display, XErrorEvent* event) {
20   // Get an indication of how long ago the request that caused the error was
21   // made.
22   unsigned long age = NextRequest(display) - event->serial;
23 
24   // Get a string to represent the request that caused the error.
25   nsAutoCString message;
26   if (event->request_code < 128) {
27     // Core protocol request
28     message.AppendInt(event->request_code);
29   } else {
30     // Extension request
31 
32     // man XSetErrorHandler says "the error handler should not call any
33     // functions (directly or indirectly) on the display that will generate
34     // protocol requests or that will look for input events" so we use another
35     // temporary Display to request extension information.  This assumes on
36     // the DISPLAY environment variable has been set and matches what was used
37     // to open |display|.
38     Display* tmpDisplay = XOpenDisplay(nullptr);
39     if (tmpDisplay) {
40       int nExts;
41       char** extNames = XListExtensions(tmpDisplay, &nExts);
42       int first_error;
43       if (extNames) {
44         for (int i = 0; i < nExts; ++i) {
45           int major_opcode, first_event;
46           if (XQueryExtension(tmpDisplay, extNames[i], &major_opcode,
47                               &first_event, &first_error) &&
48               major_opcode == event->request_code) {
49             message.Append(extNames[i]);
50             message.Append('.');
51             message.AppendInt(event->minor_code);
52             break;
53           }
54         }
55 
56         XFreeExtensionList(extNames);
57       }
58       XCloseDisplay(tmpDisplay);
59     }
60   }
61 
62   char buffer[BUFSIZE];
63   if (message.IsEmpty()) {
64     buffer[0] = '\0';
65   } else {
66     XGetErrorDatabaseText(display, "XRequest", message.get(), "", buffer,
67                           sizeof(buffer));
68   }
69 
70   nsAutoCString notes;
71   if (buffer[0]) {
72     notes.Append(buffer);
73   } else {
74     notes.AppendLiteral("Request ");
75     notes.AppendInt(event->request_code);
76     notes.Append('.');
77     notes.AppendInt(event->minor_code);
78   }
79 
80   notes.AppendLiteral(": ");
81 
82   // Get a string to describe the error.
83   XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
84   notes.Append(buffer);
85 
86   // For requests where Xlib gets the reply synchronously, |age| will be 1
87   // and the stack will include the function making the request.  For
88   // asynchronous requests, the current stack will often be unrelated to the
89   // point of making the request, even if |age| is 1, but sometimes this may
90   // help us count back to the point of the request.  With XSynchronize on,
91   // the stack will include the function making the request, even though
92   // |age| will be 2 for asynchronous requests because XSynchronize is
93   // implemented by an empty request from an XSync, which has not yet been
94   // processed.
95   if (age > 1) {
96     // XSynchronize returns the previous "after function".  If a second
97     // XSynchronize call returns the same function after an enable call then
98     // synchronization must have already been enabled.
99     if (XSynchronize(display, X11True) == XSynchronize(display, X11False)) {
100       notes.AppendLiteral("; sync");
101     } else {
102       notes.AppendLiteral("; ");
103       notes.AppendInt(uint32_t(age));
104       notes.AppendLiteral(" requests ago");
105     }
106   }
107 
108   switch (XRE_GetProcessType()) {
109     case GeckoProcessType_Default:
110     case GeckoProcessType_Content:
111       CrashReporter::AppendAppNotesToCrashReport(notes);
112       break;
113     default:;  // crash report notes not supported.
114   }
115 
116 #ifdef DEBUG
117   // The resource id is unlikely to be useful in a crash report without
118   // context of other ids, but add it to the debug console output.
119   notes.AppendLiteral("; id=0x");
120   notes.AppendInt(uint32_t(event->resourceid), 16);
121 #  ifdef MOZ_X11
122   // Actually, for requests where Xlib gets the reply synchronously,
123   // MOZ_X_SYNC=1 will not be necessary, but we don't have a table to tell us
124   // which requests get a synchronous reply.
125   if (!PR_GetEnv("MOZ_X_SYNC")) {
126     notes.AppendLiteral(
127         "\nRe-running with MOZ_X_SYNC=1 in the environment may give a more "
128         "helpful backtrace.");
129   }
130 #  endif
131 #endif
132 
133   MOZ_CRASH_UNSAFE(notes.get());
134 }
135 }
136 
InstallX11ErrorHandler()137 void InstallX11ErrorHandler() {
138   XSetErrorHandler(X11Error);
139 
140   Display* display = mozilla::DefaultXDisplay();
141   NS_ASSERTION(display, "No X display");
142   if (PR_GetEnv("MOZ_X_SYNC")) {
143     XSynchronize(display, X11True);
144   }
145 }
146