Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/backgroundhangmonitor/HangDetails.cpp
Line
Count
Source (jump to first uncovered line)
1
#include "HangDetails.h"
2
#include "nsIHangDetails.h"
3
#include "nsPrintfCString.h"
4
#include "mozilla/gfx/GPUParent.h"
5
#include "mozilla/dom/ContentChild.h"
6
#include "mozilla/Unused.h"
7
#include "mozilla/GfxMessageUtils.h" // For ParamTraits<GeckoProcessType>
8
9
#ifdef MOZ_GECKO_PROFILER
10
#include "shared-libraries.h"
11
#endif
12
13
namespace mozilla {
14
15
NS_IMETHODIMP
16
nsHangDetails::GetDuration(double* aDuration)
17
0
{
18
0
  *aDuration = mDetails.duration().ToMilliseconds();
19
0
  return NS_OK;
20
0
}
21
22
NS_IMETHODIMP
23
nsHangDetails::GetThread(nsACString& aName)
24
0
{
25
0
  aName.Assign(mDetails.threadName());
26
0
  return NS_OK;
27
0
}
28
29
NS_IMETHODIMP
30
nsHangDetails::GetRunnableName(nsACString& aRunnableName)
31
0
{
32
0
  aRunnableName.Assign(mDetails.runnableName());
33
0
  return NS_OK;
34
0
}
35
36
NS_IMETHODIMP
37
nsHangDetails::GetProcess(nsACString& aName)
38
0
{
39
0
  aName.Assign(mDetails.process());
40
0
  return NS_OK;
41
0
}
42
43
NS_IMETHODIMP
44
nsHangDetails::GetRemoteType(nsAString& aName)
45
0
{
46
0
  aName.Assign(mDetails.remoteType());
47
0
  return NS_OK;
48
0
}
49
50
NS_IMETHODIMP
51
nsHangDetails::GetAnnotations(JSContext* aCx, JS::MutableHandleValue aVal)
52
0
{
53
0
  // We create an object with { "key" : "value" } string pairs for each item in
54
0
  // our annotations object.
55
0
  JS::RootedObject jsAnnotation(aCx, JS_NewPlainObject(aCx));
56
0
  if (!jsAnnotation) {
57
0
    return NS_ERROR_OUT_OF_MEMORY;
58
0
  }
59
0
60
0
  for (auto& annot : mDetails.annotations()) {
61
0
    JSString* jsString = JS_NewUCStringCopyN(aCx, annot.value().get(), annot.value().Length());
62
0
    if (!jsString) {
63
0
      return NS_ERROR_OUT_OF_MEMORY;
64
0
    }
65
0
    JS::RootedValue jsValue(aCx);
66
0
    jsValue.setString(jsString);
67
0
    if (!JS_DefineUCProperty(aCx, jsAnnotation, annot.name().get(), annot.name().Length(),
68
0
                             jsValue, JSPROP_ENUMERATE)) {
69
0
      return NS_ERROR_OUT_OF_MEMORY;
70
0
    }
71
0
  }
72
0
73
0
  aVal.setObject(*jsAnnotation);
74
0
  return NS_OK;
75
0
}
76
77
namespace  {
78
79
nsresult
80
StringFrame(JSContext* aCx,
81
            JS::RootedObject& aTarget,
82
            size_t aIndex,
83
            const char* aString)
84
0
{
85
0
  JSString* jsString = JS_NewStringCopyZ(aCx, aString);
86
0
  if (!jsString) {
87
0
    return NS_ERROR_OUT_OF_MEMORY;
88
0
  }
89
0
  JS::RootedString string(aCx, jsString);
90
0
  if (!string) {
91
0
    return NS_ERROR_OUT_OF_MEMORY;
92
0
  }
93
0
  if (!JS_DefineElement(aCx, aTarget, aIndex, string, JSPROP_ENUMERATE)) {
94
0
    return NS_ERROR_OUT_OF_MEMORY;
95
0
  }
96
0
  return NS_OK;
97
0
}
98
99
} // anonymous namespace
100
101
NS_IMETHODIMP
102
nsHangDetails::GetStack(JSContext* aCx, JS::MutableHandleValue aStack)
103
0
{
104
0
  auto& stack = mDetails.stack();
105
0
  uint32_t length = stack.stack().Length();
106
0
  JS::RootedObject ret(aCx, JS_NewArrayObject(aCx, length));
107
0
  if (!ret) {
108
0
    return NS_ERROR_OUT_OF_MEMORY;
109
0
  }
110
0
111
0
  for (uint32_t i = 0; i < length; ++i) {
112
0
    auto& entry = stack.stack()[i];
113
0
    switch (entry.type()) {
114
0
      case HangEntry::TnsCString: {
115
0
        nsresult rv = StringFrame(aCx, ret, i, entry.get_nsCString().get());
116
0
        NS_ENSURE_SUCCESS(rv, rv);
117
0
        break;
118
0
      }
119
0
      case HangEntry::THangEntryBufOffset: {
120
0
        uint32_t offset = entry.get_HangEntryBufOffset().index();
121
0
122
0
        // NOTE: We can't trust the offset we got, as we might have gotten it
123
0
        // from a compromised content process. Validate that it is in bounds.
124
0
        if (NS_WARN_IF(stack.strbuffer().IsEmpty() ||
125
0
                       offset >= stack.strbuffer().Length())) {
126
0
          MOZ_ASSERT_UNREACHABLE("Corrupted offset data");
127
0
          return NS_ERROR_FAILURE;
128
0
        }
129
0
130
0
        // NOTE: If our content process is compromised, it could send us back a
131
0
        // strbuffer() which didn't have a null terminator. If the last byte in
132
0
        // the buffer is not '\0', we abort, to make sure we don't read out of
133
0
        // bounds.
134
0
        if (stack.strbuffer().LastElement() != '\0') {
135
0
          MOZ_ASSERT_UNREACHABLE("Corrupted strbuffer data");
136
0
          return NS_ERROR_FAILURE;
137
0
        }
138
0
139
0
        // We know this offset is safe because of the previous checks.
140
0
        const int8_t* start = stack.strbuffer().Elements() + offset;
141
0
        nsresult rv = StringFrame(aCx, ret, i,
142
0
                                  reinterpret_cast<const char*>(start));
143
0
        NS_ENSURE_SUCCESS(rv, rv);
144
0
        break;
145
0
      }
146
0
      case HangEntry::THangEntryModOffset: {
147
0
        const HangEntryModOffset& mo = entry.get_HangEntryModOffset();
148
0
149
0
        JS::RootedObject jsFrame(aCx, JS_NewArrayObject(aCx, 2));
150
0
        if (!jsFrame) {
151
0
          return NS_ERROR_OUT_OF_MEMORY;
152
0
        }
153
0
154
0
        if (!JS_DefineElement(aCx, jsFrame, 0, mo.module(), JSPROP_ENUMERATE)) {
155
0
          return NS_ERROR_OUT_OF_MEMORY;
156
0
        }
157
0
158
0
        nsPrintfCString hexString("%" PRIxPTR, (uintptr_t)mo.offset());
159
0
        JS::RootedString hex(aCx, JS_NewStringCopyZ(aCx, hexString.get()));
160
0
        if (!hex || !JS_DefineElement(aCx, jsFrame, 1, hex, JSPROP_ENUMERATE)) {
161
0
          return NS_ERROR_OUT_OF_MEMORY;
162
0
        }
163
0
164
0
        if (!JS_DefineElement(aCx, ret, i, jsFrame, JSPROP_ENUMERATE)) {
165
0
          return NS_ERROR_OUT_OF_MEMORY;
166
0
        }
167
0
        break;
168
0
      }
169
0
      case HangEntry::THangEntryProgCounter: {
170
0
        // Don't bother recording fixed program counters to JS
171
0
        nsresult rv = StringFrame(aCx, ret, i, "(unresolved)");
172
0
        NS_ENSURE_SUCCESS(rv, rv);
173
0
        break;
174
0
      }
175
0
      case HangEntry::THangEntryContent: {
176
0
        nsresult rv = StringFrame(aCx, ret, i, "(content script)");
177
0
        NS_ENSURE_SUCCESS(rv, rv);
178
0
        break;
179
0
      }
180
0
      case HangEntry::THangEntryJit: {
181
0
        nsresult rv = StringFrame(aCx, ret, i, "(jit frame)");
182
0
        NS_ENSURE_SUCCESS(rv, rv);
183
0
        break;
184
0
      }
185
0
      case HangEntry::THangEntryWasm: {
186
0
        nsresult rv = StringFrame(aCx, ret, i, "(wasm)");
187
0
        NS_ENSURE_SUCCESS(rv, rv);
188
0
        break;
189
0
      }
190
0
      case HangEntry::THangEntryChromeScript: {
191
0
        nsresult rv = StringFrame(aCx, ret, i, "(chrome script)");
192
0
        NS_ENSURE_SUCCESS(rv, rv);
193
0
        break;
194
0
      }
195
0
      case HangEntry::THangEntrySuppressed: {
196
0
        nsresult rv = StringFrame(aCx, ret, i, "(profiling suppressed)");
197
0
        NS_ENSURE_SUCCESS(rv, rv);
198
0
        break;
199
0
      }
200
0
      default: MOZ_CRASH("Unsupported HangEntry type?");
201
0
    }
202
0
  }
203
0
204
0
  aStack.setObject(*ret);
205
0
  return NS_OK;
206
0
}
207
208
NS_IMETHODIMP
209
nsHangDetails::GetModules(JSContext* aCx, JS::MutableHandleValue aVal)
210
0
{
211
0
  auto& modules = mDetails.stack().modules();
212
0
  size_t length = modules.Length();
213
0
  JS::RootedObject retObj(aCx, JS_NewArrayObject(aCx, length));
214
0
  if (!retObj) {
215
0
    return NS_ERROR_OUT_OF_MEMORY;
216
0
  }
217
0
218
0
  for (size_t i = 0; i < length; ++i) {
219
0
    const HangModule& module = modules[i];
220
0
    JS::RootedObject jsModule(aCx, JS_NewArrayObject(aCx, 2));
221
0
    if (!jsModule) {
222
0
      return NS_ERROR_OUT_OF_MEMORY;
223
0
    }
224
0
225
0
    JS::RootedString name(aCx, JS_NewUCStringCopyN(aCx,
226
0
                                                   module.name().BeginReading(),
227
0
                                                   module.name().Length()));
228
0
    if (!JS_DefineElement(aCx, jsModule, 0, name, JSPROP_ENUMERATE)) {
229
0
      return NS_ERROR_OUT_OF_MEMORY;
230
0
    }
231
0
232
0
    JS::RootedString breakpadId(aCx, JS_NewStringCopyN(aCx,
233
0
                                                       module.breakpadId().BeginReading(),
234
0
                                                       module.breakpadId().Length()));
235
0
    if (!JS_DefineElement(aCx, jsModule, 1, breakpadId, JSPROP_ENUMERATE)) {
236
0
      return NS_ERROR_OUT_OF_MEMORY;
237
0
    }
238
0
239
0
    if (!JS_DefineElement(aCx, retObj, i, jsModule, JSPROP_ENUMERATE)) {
240
0
      return NS_ERROR_OUT_OF_MEMORY;
241
0
    }
242
0
  }
243
0
244
0
  aVal.setObject(*retObj);
245
0
  return NS_OK;
246
0
}
247
248
// Processing and submitting the stack as an observer notification.
249
250
void
251
nsHangDetails::Submit()
252
0
{
253
0
  if (NS_WARN_IF(!SystemGroup::Initialized())) {
254
0
    return;
255
0
  }
256
0
257
0
  RefPtr<nsHangDetails> hangDetails = this;
258
0
  nsCOMPtr<nsIRunnable> notifyObservers = NS_NewRunnableFunction("NotifyBHRHangObservers", [hangDetails] {
259
0
    // The place we need to report the hang to varies depending on process.
260
0
    //
261
0
    // In child processes, we report the hang to our parent process, while if
262
0
    // we're in the parent process, we report a bhr-thread-hang observer
263
0
    // notification.
264
0
    switch (XRE_GetProcessType()) {
265
0
    case GeckoProcessType_Content: {
266
0
      auto cc = dom::ContentChild::GetSingleton();
267
0
      if (cc) {
268
0
        hangDetails->mDetails.remoteType().Assign(cc->GetRemoteType());
269
0
        Unused << cc->SendBHRThreadHang(hangDetails->mDetails);
270
0
      }
271
0
      break;
272
0
    }
273
0
    case GeckoProcessType_GPU: {
274
0
      auto gp = gfx::GPUParent::GetSingleton();
275
0
      if (gp) {
276
0
        Unused << gp->SendBHRThreadHang(hangDetails->mDetails);
277
0
      }
278
0
      break;
279
0
    }
280
0
    case GeckoProcessType_Default: {
281
0
      nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
282
0
      if (os) {
283
0
        os->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
284
0
      }
285
0
      break;
286
0
    }
287
0
    default:
288
0
      // XXX: Consider handling GeckoProcessType_GMPlugin and
289
0
      // GeckoProcessType_Plugin?
290
0
      NS_WARNING("Unsupported BHR process type - discarding hang.");
291
0
      break;
292
0
    }
293
0
  });
294
0
295
0
  nsresult rv = SystemGroup::Dispatch(TaskCategory::Other,
296
0
                                      notifyObservers.forget());
297
0
  MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
298
0
}
299
300
NS_IMPL_ISUPPORTS(nsHangDetails, nsIHangDetails)
301
302
namespace {
303
304
// Sorting comparator used by ReadModuleInformation. Sorts PC Frames by their
305
// PC.
306
struct PCFrameComparator {
307
0
  bool LessThan(HangEntry* const& a, HangEntry* const& b) const {
308
0
    return a->get_HangEntryProgCounter().pc() < b->get_HangEntryProgCounter().pc();
309
0
  }
310
0
  bool Equals(HangEntry* const& a, HangEntry* const& b) const {
311
0
    return a->get_HangEntryProgCounter().pc() == b->get_HangEntryProgCounter().pc();
312
0
  }
313
};
314
315
} // anonymous namespace
316
317
void
318
ReadModuleInformation(HangStack& stack)
319
0
{
320
0
  // modules() should be empty when we start filling it.
321
0
  stack.modules().Clear();
322
0
323
0
#ifdef MOZ_GECKO_PROFILER
324
0
  // Create a sorted list of the PCs in the current stack.
325
0
  AutoTArray<HangEntry*, 100> frames;
326
0
  for (auto& frame : stack.stack()) {
327
0
    if (frame.type() == HangEntry::THangEntryProgCounter) {
328
0
      frames.AppendElement(&frame);
329
0
    }
330
0
  }
331
0
  PCFrameComparator comparator;
332
0
  frames.Sort(comparator);
333
0
334
0
  SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf();
335
0
  rawModules.SortByAddress();
336
0
337
0
  size_t frameIdx = 0;
338
0
  for (size_t i = 0; i < rawModules.GetSize(); ++i) {
339
0
    const SharedLibrary& info = rawModules.GetEntry(i);
340
0
    uintptr_t moduleStart = info.GetStart();
341
0
    uintptr_t moduleEnd = info.GetEnd() - 1;
342
0
    // the interval is [moduleStart, moduleEnd)
343
0
344
0
    bool moduleReferenced = false;
345
0
    for (; frameIdx < frames.Length(); ++frameIdx) {
346
0
      auto& frame = frames[frameIdx];
347
0
      uint64_t pc = frame->get_HangEntryProgCounter().pc();
348
0
      // We've moved past this frame, let's go to the next one.
349
0
      if (pc >= moduleEnd) {
350
0
        break;
351
0
      }
352
0
      if (pc >= moduleStart) {
353
0
        uint64_t offset = pc - moduleStart;
354
0
        if (NS_WARN_IF(offset > UINT32_MAX)) {
355
0
          continue; // module/offset can only hold 32-bit offsets into shared libraries.
356
0
        }
357
0
358
0
        // If we found the module, rewrite the Frame entry to instead be a
359
0
        // ModOffset one. mModules.Length() will be the index of the module when
360
0
        // we append it below, and we set moduleReferenced to true to ensure
361
0
        // that we do.
362
0
        moduleReferenced = true;
363
0
        uint32_t module = stack.modules().Length();
364
0
        HangEntryModOffset modOffset(module, static_cast<uint32_t>(offset));
365
0
        *frame = modOffset;
366
0
      }
367
0
    }
368
0
369
0
    if (moduleReferenced) {
370
0
      HangModule module(info.GetDebugName(), info.GetBreakpadId());
371
0
      stack.modules().AppendElement(module);
372
0
    }
373
0
  }
374
0
#endif
375
0
}
376
377
NS_IMETHODIMP
378
ProcessHangStackRunnable::Run()
379
0
{
380
0
  // NOTE: Reading module information can take a long time, which is why we do
381
0
  // it off-main-thread.
382
0
  ReadModuleInformation(mHangDetails.stack());
383
0
384
0
  RefPtr<nsHangDetails> hangDetails = new nsHangDetails(std::move(mHangDetails));
385
0
  hangDetails->Submit();
386
0
387
0
  return NS_OK;
388
0
}
389
390
} // namespace mozilla