mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Improve crashpad integration (issue #1995)
- Crash reporting is enabled and configured using a "crash_reporter.cfg" file. See comments in include/cef_crash_util.h and tools/crash_server.py for usage.
This commit is contained in:
235
tools/crash_server.py
Normal file
235
tools/crash_server.py
Normal file
@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2017 The Chromium Embedded Framework Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be found
|
||||
# in the LICENSE file.
|
||||
|
||||
"""
|
||||
This script implements a simple HTTP server for receiving crash report uploads
|
||||
from a Breakpad/Crashpad client (any CEF-based application). This script is
|
||||
intended for testing purposes only. An HTTPS server and a system such as Socorro
|
||||
(https://wiki.mozilla.org/Socorro) should be used when uploading crash reports
|
||||
from production applications.
|
||||
|
||||
Usage of this script is as follows:
|
||||
|
||||
1. Run this script from the command-line. The first argument is the server port
|
||||
number and the second argument is the directory where uploaded report
|
||||
information will be saved:
|
||||
|
||||
> python crash_server.py 8080 /path/to/dumps
|
||||
|
||||
2. Create a "crash_reporter.cfg" file at the required platform-specific
|
||||
location. On Windows and Linux this file must be placed next to the main
|
||||
application executable. On macOS this file must be placed in the top-level
|
||||
app bundle Resources directory (e.g. "<appname>.app/Contents/Resources"). At
|
||||
a minimum it must contain a "ServerURL=http://localhost:8080" line under the
|
||||
"[Config]" section (make sure the port number matches the value specified in
|
||||
step 1). See comments in include/cef_crash_util.h for a complete
|
||||
specification of this file.
|
||||
|
||||
Example file contents:
|
||||
|
||||
[Config]
|
||||
ServerURL=http://localhost:8080
|
||||
# Disable rate limiting so that all crashes are uploaded.
|
||||
RateLimitEnabled=false
|
||||
MaxUploadsPerDay=0
|
||||
|
||||
[CrashKeys]
|
||||
# The cefclient sample application sets these values (see step 5 below).
|
||||
testkey1=small
|
||||
testkey2=medium
|
||||
testkey3=large
|
||||
|
||||
3. Load one of the following URLs in the CEF-based application to cause a crash:
|
||||
|
||||
Main (browser) process crash: chrome://inducebrowsercrashforrealz
|
||||
Renderer process crash: chrome://crash
|
||||
GPU process crash: chrome://gpucrash
|
||||
|
||||
4. When this script successfully receives a crash report upload you will see
|
||||
console output like the following:
|
||||
|
||||
01/10/2017 12:31:23: Dump <id>
|
||||
|
||||
The "<id>" value is a 16 digit hexadecimal string that uniquely identifies
|
||||
the dump. Crash dumps and metadata (product state, command-line flags, crash
|
||||
keys, etc.) will be written to the "<id>.dmp" and "<id>.json" files
|
||||
underneath the directory specified in step 1.
|
||||
|
||||
On Linux Breakpad uses the wget utility to upload crash dumps, so make sure
|
||||
that utility is installed. If the crash is handled correctly then you should
|
||||
see console output like the following when the client uploads a crash dump:
|
||||
|
||||
--2017-01-10 12:31:22-- http://localhost:8080/
|
||||
Resolving localhost (localhost)... 127.0.0.1
|
||||
Connecting to localhost (localhost)|127.0.0.1|:8080... connected.
|
||||
HTTP request sent, awaiting response... 200 OK
|
||||
Length: unspecified [text/html]
|
||||
Saving to: '/dev/fd/3'
|
||||
Crash dump id: <id>
|
||||
|
||||
On macOS when uploading a crash report to this script over HTTP you may
|
||||
receive an error like the following:
|
||||
|
||||
"Transport security has blocked a cleartext HTTP (http://) resource load
|
||||
since it is insecure. Temporary exceptions can be configured via your app's
|
||||
Info.plist file."
|
||||
|
||||
You can work around this error by adding the following key to the Helper app
|
||||
Info.plist file (e.g. "<appname>.app/Contents/Frameworks/
|
||||
<appname> Helper.app/Contents/Info.plist"):
|
||||
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<!--Allow all connections (for testing only!)-->
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
5. The cefclient sample application sets test crash key values in the browser
|
||||
and renderer processes. To work properly these values must also be defined
|
||||
in the "[CrashKeys]" section of "crash_reporter.cfg" as shown above.
|
||||
|
||||
In tests/cefclient/browser/client_browser.cc (browser process):
|
||||
|
||||
CefSetCrashKeyValue("testkey1", "value1_browser");
|
||||
CefSetCrashKeyValue("testkey2", "value2_browser");
|
||||
CefSetCrashKeyValue("testkey3", "value3_browser");
|
||||
|
||||
In tests/cefclient/renderer/client_renderer.cc (renderer process):
|
||||
|
||||
CefSetCrashKeyValue("testkey1", "value1_renderer");
|
||||
CefSetCrashKeyValue("testkey2", "value2_renderer");
|
||||
CefSetCrashKeyValue("testkey3", "value3_renderer");
|
||||
|
||||
When crashing the browser or renderer processes with cefclient you should
|
||||
verify that the test crash key values are included in the metadata
|
||||
("<id>.json") file. Some values may be chunked as described in
|
||||
include/cef_crash_util.h.
|
||||
"""
|
||||
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
import cgi
|
||||
import cStringIO
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import uuid
|
||||
import zlib
|
||||
|
||||
def print_msg(msg):
|
||||
""" Write |msg| to stdout and flush. """
|
||||
timestr = datetime.datetime.now().strftime("%m/%d/%Y %H:%M:%S")
|
||||
sys.stdout.write("%s: %s\n" % (timestr, msg))
|
||||
sys.stdout.flush()
|
||||
|
||||
# Key identifying the minidump file.
|
||||
minidump_key = 'upload_file_minidump'
|
||||
|
||||
class CrashHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
def __init__(self, dump_directory, *args):
|
||||
self._dump_directory = dump_directory
|
||||
BaseHTTPRequestHandler.__init__(self, *args)
|
||||
|
||||
def _send_default_response_headers(self):
|
||||
""" Send default response headers. """
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
|
||||
def _parse_post_data(self):
|
||||
""" Returns a cgi.FieldStorage object for this request or None if this is
|
||||
not a POST request. """
|
||||
if self.command != 'POST':
|
||||
return None
|
||||
return cgi.FieldStorage(
|
||||
fp = self.rfile,
|
||||
headers = self.headers,
|
||||
environ = {
|
||||
'REQUEST_METHOD': 'POST',
|
||||
'CONTENT_TYPE': self.headers['Content-Type'],
|
||||
})
|
||||
|
||||
def _create_new_dump_id(self):
|
||||
""" Breakpad requires a 16 digit hexadecimal dump ID. """
|
||||
return str(uuid.uuid4().get_hex().upper()[0:16])
|
||||
|
||||
def do_GET(self):
|
||||
""" Default empty implementation for handling GET requests. """
|
||||
self._send_default_response_headers()
|
||||
self.wfile.write("<html><body><h1>GET!</h1></body></html>")
|
||||
|
||||
def do_HEAD(self):
|
||||
""" Default empty implementation for handling HEAD requests. """
|
||||
self._send_default_response_headers()
|
||||
|
||||
def do_POST(self):
|
||||
""" Handle a multi-part POST request submitted by Breakpad/Crashpad. """
|
||||
self._send_default_response_headers()
|
||||
|
||||
# Create a unique ID for the dump.
|
||||
dump_id = self._create_new_dump_id()
|
||||
|
||||
# Return the unique ID to the caller.
|
||||
self.wfile.write(dump_id)
|
||||
|
||||
dmp_stream = None
|
||||
metadata = {}
|
||||
|
||||
# Breakpad on Linux sends gzipped request contents.
|
||||
if 'Content-Encoding' in self.headers and self.headers['Content-Encoding'] == 'gzip':
|
||||
print_msg('Decompressing gzipped request')
|
||||
self.rfile = cStringIO.StringIO(zlib.decompress(self.rfile.read(), 16+zlib.MAX_WBITS))
|
||||
|
||||
# Parse the multi-part request.
|
||||
form_data = self._parse_post_data()
|
||||
for key in form_data.keys():
|
||||
if key == minidump_key and form_data[minidump_key].file:
|
||||
dmp_stream = form_data[minidump_key].file
|
||||
else:
|
||||
metadata[key] = form_data[key].value
|
||||
|
||||
if dmp_stream is None:
|
||||
# Exit early if the request is invalid.
|
||||
print_msg('Invalid dump %s' % dump_id)
|
||||
return
|
||||
|
||||
print_msg('Dump %s' % dump_id)
|
||||
|
||||
# Write the minidump to file.
|
||||
dump_file = os.path.join(self._dump_directory, dump_id + '.dmp')
|
||||
with open(dump_file, 'wb') as fp:
|
||||
shutil.copyfileobj(dmp_stream, fp)
|
||||
|
||||
# Write the metadata to file.
|
||||
meta_file = os.path.join(self._dump_directory, dump_id + '.json')
|
||||
with open(meta_file, 'w') as fp:
|
||||
json.dump(metadata, fp)
|
||||
|
||||
def HandleRequestsUsing(dump_store):
|
||||
return lambda *args: CrashHTTPRequestHandler(dump_directory, *args)
|
||||
|
||||
def RunCrashServer(port, dump_directory):
|
||||
""" Run the crash handler HTTP server. """
|
||||
httpd = HTTPServer(('', port), HandleRequestsUsing(dump_directory))
|
||||
print_msg('Starting httpd on port %d' % port)
|
||||
httpd.serve_forever()
|
||||
|
||||
# Program entry point.
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print 'Usage: %s <port> <dump_directory>' % os.path.basename(sys.argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
# Create the dump directory if necessary.
|
||||
dump_directory = sys.argv[2]
|
||||
if not os.path.exists(dump_directory):
|
||||
os.makedirs(dump_directory)
|
||||
if not os.path.isdir(dump_directory):
|
||||
raise Exception('Directory does not exist: %s' % dump_directory)
|
||||
|
||||
RunCrashServer(int(sys.argv[1]), dump_directory)
|
||||
|
Reference in New Issue
Block a user