diff options
author | Marco Bubke <marco.bubke@qt.io> | 2016-11-14 12:28:51 +0100 |
---|---|---|
committer | Orgad Shaneh <orgads@gmail.com> | 2017-07-25 14:24:11 +0000 |
commit | 4beaae0f79ebe820fa9fdfdfb8195e5aeac89369 (patch) | |
tree | 3148e2c5032cfd79bed47787d0df97e0d9206f86 | |
parent | 131a796f9f0afe10e744f27071f426af26296458 (diff) |
Introduce Breakpad crash handler
Google Breakpad (https://chromium.googlesource.com/breakpad/breakpad) is a
widely used crash handler framework, e.g. by Mozilla and Chromium. It is
providing a platform neutral solution to generate mini dumps, collect
debug information and generate stack traces from those.
Done-with: Orgad Shaneh <orgad.shaneh@audiocodes.com>
Change-Id: I09382e7db0dc9e29b228e7b554fda7b6f5684349
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
28 files changed, 2169 insertions, 22 deletions
diff --git a/.gitignore b/.gitignore index 3e0ccac715..335868cd0f 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ wrapper.sh /src/app/Info.plist /src/plugins/**/*.json /src/plugins/coreplugin/ide_version.h +/src/libs/qt-breakpad/bin app_version.h phony.c diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri index 5a10394f18..39599385a8 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri @@ -13,11 +13,6 @@ include (../interfaces/interfaces.pri) include (../types/types.pri) include (../qmlprivategate/qmlprivategate.pri) -QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH) -!isEmpty(QT_BREAKPAD_ROOT_PATH) { - include($$QT_BREAKPAD_ROOT_PATH/qtbreakpad.pri) -} - SOURCES += $$PWD/qml2puppetmain.cpp RESOURCES += $$PWD/../qmlpuppet.qrc diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp index 0d29b554e6..22460eaf6b 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp @@ -111,7 +111,8 @@ int internalMain(QGuiApplication *application) #ifdef ENABLE_QT_BREAKPAD - QtSystemExceptionHandler systemExceptionHandler; + const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + RELATIVE_LIBEXEC_PATH; + QtSystemExceptionHandler systemExceptionHandler(libexecPath); #endif new QmlDesigner::Qt5NodeInstanceClientProxy(application); diff --git a/src/app/app.pro b/src/app/app.pro index 1414e7d7fc..4d6030bfe7 100644 --- a/src/app/app.pro +++ b/src/app/app.pro @@ -12,13 +12,10 @@ HEADERS += ../tools/qtcreatorcrashhandler/crashhandlersetup.h SOURCES += main.cpp ../tools/qtcreatorcrashhandler/crashhandlersetup.cpp include(../rpath.pri) +include(../libs/qt-breakpad/qtbreakpad.pri) LIBS *= -l$$qtLibraryName(ExtensionSystem) -l$$qtLibraryName(Aggregation) -l$$qtLibraryName(Utils) -QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH) -!isEmpty(QT_BREAKPAD_ROOT_PATH) { - include($$QT_BREAKPAD_ROOT_PATH/qtbreakpad.pri) -} win32 { # We need the version in two separate formats for the .rc file # RC_VERSION=4,3,82,0 (quadruple) diff --git a/src/app/main.cpp b/src/app/main.cpp index c1ae3245fb..4888b8db58 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -329,13 +329,13 @@ int main(int argc, char **argv) const int threadCount = QThreadPool::globalInstance()->maxThreadCount(); QThreadPool::globalInstance()->setMaxThreadCount(qMax(4, 2 * threadCount)); - // Display a backtrace once a serious signal is delivered (Linux only). const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + RELATIVE_LIBEXEC_PATH; - CrashHandlerSetup setupCrashHandler(appNameC, CrashHandlerSetup::EnableRestart, libexecPath); - #ifdef ENABLE_QT_BREAKPAD - QtSystemExceptionHandler systemExceptionHandler; + QtSystemExceptionHandler systemExceptionHandler(libexecPath); +#else + // Display a backtrace once a serious signal is delivered (Linux only). + CrashHandlerSetup setupCrashHandler(appNameC, CrashHandlerSetup::EnableRestart, libexecPath); #endif app.setAttribute(Qt::AA_UseHighDpiPixmaps); diff --git a/src/libs/qt-breakpad/poster/__init__.py b/src/libs/qt-breakpad/poster/__init__.py new file mode 100644 index 0000000000..e963d70b96 --- /dev/null +++ b/src/libs/qt-breakpad/poster/__init__.py @@ -0,0 +1,32 @@ +# Copyright (c) 2010 Chris AtLee +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""poster module + +Support for streaming HTTP uploads, and multipart/form-data encoding + +```poster.version``` is a 3-tuple of integers representing the version number. +New releases of poster will always have a version number that compares greater +than an older version of poster. +New in version 0.6.""" + +import poster.streaminghttp +import poster.encode + +version = (0, 8, 0) # Thanks JP! diff --git a/src/libs/qt-breakpad/poster/encode.py b/src/libs/qt-breakpad/poster/encode.py new file mode 100644 index 0000000000..fbc4e76452 --- /dev/null +++ b/src/libs/qt-breakpad/poster/encode.py @@ -0,0 +1,433 @@ +# Copyright (c) 2010 Chris AtLee +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""multipart/form-data encoding module + +This module provides functions that faciliate encoding name/value pairs +as multipart/form-data suitable for a HTTP POST or PUT request. + +multipart/form-data is the standard way to upload files over HTTP""" + +__all__ = ['gen_boundary', 'encode_and_quote', 'MultipartParam', + 'encode_string', 'encode_file_header', 'get_body_size', 'get_headers', + 'multipart_encode'] + +try: + import uuid + def gen_boundary(): + """Returns a random string to use as the boundary for a message""" + return uuid.uuid4().hex +except ImportError: + import random, sha + def gen_boundary(): + """Returns a random string to use as the boundary for a message""" + bits = random.getrandbits(160) + return sha.new(str(bits)).hexdigest() + +import urllib, re, os, mimetypes +try: + from email.header import Header +except ImportError: + # Python 2.4 + from email.Header import Header + +def encode_and_quote(data): + """If ``data`` is unicode, return urllib.quote_plus(data.encode("utf-8")) + otherwise return urllib.quote_plus(data)""" + if data is None: + return None + + if isinstance(data, unicode): + data = data.encode("utf-8") + return urllib.quote_plus(data) + +def _strify(s): + """If s is a unicode string, encode it to UTF-8 and return the results, + otherwise return str(s), or None if s is None""" + if s is None: + return None + if isinstance(s, unicode): + return s.encode("utf-8") + return str(s) + +class MultipartParam(object): + """Represents a single parameter in a multipart/form-data request + + ``name`` is the name of this parameter. + + If ``value`` is set, it must be a string or unicode object to use as the + data for this parameter. + + If ``filename`` is set, it is what to say that this parameter's filename + is. Note that this does not have to be the actual filename any local file. + + If ``filetype`` is set, it is used as the Content-Type for this parameter. + If unset it defaults to "text/plain; charset=utf8" + + If ``filesize`` is set, it specifies the length of the file ``fileobj`` + + If ``fileobj`` is set, it must be a file-like object that supports + .read(). + + Both ``value`` and ``fileobj`` must not be set, doing so will + raise a ValueError assertion. + + If ``fileobj`` is set, and ``filesize`` is not specified, then + the file's size will be determined first by stat'ing ``fileobj``'s + file descriptor, and if that fails, by seeking to the end of the file, + recording the current position as the size, and then by seeking back to the + beginning of the file. + + ``cb`` is a callable which will be called from iter_encode with (self, + current, total), representing the current parameter, current amount + transferred, and the total size. + """ + def __init__(self, name, value=None, filename=None, filetype=None, + filesize=None, fileobj=None, cb=None): + self.name = Header(name).encode() + self.value = _strify(value) + if filename is None: + self.filename = None + else: + if isinstance(filename, unicode): + # Encode with XML entities + self.filename = filename.encode("ascii", "xmlcharrefreplace") + else: + self.filename = str(filename) + self.filename = self.filename.encode("string_escape").\ + replace('"', '\\"') + self.filetype = _strify(filetype) + + self.filesize = filesize + self.fileobj = fileobj + self.cb = cb + + if self.value is not None and self.fileobj is not None: + raise ValueError("Only one of value or fileobj may be specified") + + if fileobj is not None and filesize is None: + # Try and determine the file size + try: + self.filesize = os.fstat(fileobj.fileno()).st_size + except (OSError, AttributeError): + try: + fileobj.seek(0, 2) + self.filesize = fileobj.tell() + fileobj.seek(0) + except: + raise ValueError("Could not determine filesize") + + def __cmp__(self, other): + attrs = ['name', 'value', 'filename', 'filetype', 'filesize', 'fileobj'] + myattrs = [getattr(self, a) for a in attrs] + oattrs = [getattr(other, a) for a in attrs] + return cmp(myattrs, oattrs) + + def reset(self): + if self.fileobj is not None: + self.fileobj.seek(0) + elif self.value is None: + raise ValueError("Don't know how to reset this parameter") + + @classmethod + def from_file(cls, paramname, filename): + """Returns a new MultipartParam object constructed from the local + file at ``filename``. + + ``filesize`` is determined by os.path.getsize(``filename``) + + ``filetype`` is determined by mimetypes.guess_type(``filename``)[0] + + ``filename`` is set to os.path.basename(``filename``) + """ + + return cls(paramname, filename=os.path.basename(filename), + filetype=mimetypes.guess_type(filename)[0], + filesize=os.path.getsize(filename), + fileobj=open(filename, "rb")) + + @classmethod + def from_params(cls, params): + """Returns a list of MultipartParam objects from a sequence of + name, value pairs, MultipartParam instances, + or from a mapping of names to values + + The values may be strings or file objects, or MultipartParam objects. + MultipartParam object names must match the given names in the + name,value pairs or mapping, if applicable.""" + if hasattr(params, 'items'): + params = params.items() + + retval = [] + for item in params: + if isinstance(item, cls): + retval.append(item) + continue + name, value = item + if isinstance(value, cls): + assert value.name == name + retval.append(value) + continue + if hasattr(value, 'read'): + # Looks like a file object + filename = getattr(value, 'name', None) + if filename is not None: + filetype = mimetypes.guess_type(filename)[0] + else: + filetype = None + + retval.append(cls(name=name, filename=filename, + filetype=filetype, fileobj=value)) + else: + retval.append(cls(name, value)) + return retval + + def encode_hdr(self, boundary): + """Returns the header of the encoding of this parameter""" + boundary = encode_and_quote(boundary) + + headers = ["--%s" % boundary] + + if self.filename: + disposition = 'form-data; name="%s"; filename="%s"' % (self.name, + self.filename) + else: + disposition = 'form-data; name="%s"' % self.name + + headers.append("Content-Disposition: %s" % disposition) + + if self.filetype: + filetype = self.filetype + else: + filetype = "text/plain; charset=utf-8" + + headers.append("Content-Type: %s" % filetype) + + headers.append("") + headers.append("") + + return "\r\n".join(headers) + + def encode(self, boundary): + """Returns the string encoding of this parameter""" + if self.value is None: + value = self.fileobj.read() + else: + value = self.value + + if re.search("^--%s$" % re.escape(boundary), value, re.M): + raise ValueError("boundary found in encoded string") + + return "%s%s\r\n" % (self.encode_hdr(boundary), value) + + def iter_encode(self, boundary, blocksize=4096): + """Yields the encoding of this parameter + If self.fileobj is set, then blocks of ``blocksize`` bytes are read and + yielded.""" + total = self.get_size(boundary) + current = 0 + if self.value is not None: + block = self.encode(boundary) + current += len(block) + yield block + if self.cb: + self.cb(self, current, total) + else: + block = self.encode_hdr(boundary) + current += len(block) + yield block + if self.cb: + self.cb(self, current, total) + last_block = "" + encoded_boundary = "--%s" % encode_and_quote(boundary) + boundary_exp = re.compile("^%s$" % re.escape(encoded_boundary), + re.M) + while True: + block = self.fileobj.read(blocksize) + if not block: + current += 2 + yield "\r\n" + if self.cb: + self.cb(self, current, total) + break + last_block += block + if boundary_exp.search(last_block): + raise ValueError("boundary found in file data") + last_block = last_block[-len(encoded_boundary)-2:] + current += len(block) + yield block + if self.cb: + self.cb(self, current, total) + + def get_size(self, boundary): + """Returns the size in bytes that this param will be when encoded + with the given boundary.""" + if self.filesize is not None: + valuesize = self.filesize + else: + valuesize = len(self.value) + + return len(self.encode_hdr(boundary)) + 2 + valuesize + +def encode_string(boundary, name, value): + """Returns ``name`` and ``value`` encoded as a multipart/form-data + variable. ``boundary`` is the boundary string used throughout + a single request to separate variables.""" + + return MultipartParam(name, value).encode(boundary) + +def encode_file_header(boundary, paramname, filesize, filename=None, + filetype=None): + """Returns the leading data for a multipart/form-data field that contains + file data. + + ``boundary`` is the boundary string used throughout a single request to + separate variables. + + ``paramname`` is the name of the variable in this request. + + ``filesize`` is the size of the file data. + + ``filename`` if specified is the filename to give to this field. This + field is only useful to the server for determining the original filename. + + ``filetype`` if specified is the MIME type of this file. + + The actual file data should be sent after this header has been sent. + """ + + return MultipartParam(paramname, filesize=filesize, filename=filename, + filetype=filetype).encode_hdr(boundary) + +def get_body_size(params, boundary): + """Returns the number of bytes that the multipart/form-data encoding + of ``params`` will be.""" + size = sum(p.get_size(boundary) for p in MultipartParam.from_params(params)) + return size + len(boundary) + 6 + +def get_headers(params, boundary): + """Returns a dictionary with Content-Type and Content-Length headers + for the multipart/form-data encoding of ``params``.""" + headers = {} + boundary = urllib.quote_plus(boundary) + headers['Content-Type'] = "multipart/form-data; boundary=%s" % boundary + headers['Content-Length'] = str(get_body_size(params, boundary)) + return headers + +class multipart_yielder: + def __init__(self, params, boundary, cb): + self.params = params + self.boundary = boundary + self.cb = cb + + self.i = 0 + self.p = None + self.param_iter = None + self.current = 0 + self.total = get_body_size(params, boundary) + + def __iter__(self): + return self + + def next(self): + """generator function to yield multipart/form-data representation + of parameters""" + if self.param_iter is not None: + try: + block = self.param_iter.next() + self.current += len(block) + if self.cb: + self.cb(self.p, self.current, self.total) + return block + except StopIteration: + self.p = None + self.param_iter = None + + if self.i is None: + raise StopIteration + elif self.i >= len(self.params): + self.param_iter = None + self.p = None + self.i = None + block = "--%s--\r\n" % self.boundary + self.current += len(block) + if self.cb: + self.cb(self.p, self.current, self.total) + return block + + self.p = self.params[self.i] + self.param_iter = self.p.iter_encode(self.boundary) + self.i += 1 + return self.next() + + def reset(self): + self.i = 0 + self.current = 0 + for param in self.params: + param.reset() + +def multipart_encode(params, boundary=None, cb=None): + """Encode ``params`` as multipart/form-data. + + ``params`` should be a sequence of (name, value) pairs or MultipartParam + objects, or a mapping of names to values. + Values are either strings parameter values, or file-like objects to use as + the parameter value. The file-like objects must support .read() and either + .fileno() or both .seek() and .tell(). + + If ``boundary`` is set, then it as used as the MIME boundary. Otherwise + a randomly generated boundary will be used. In either case, if the + boundary string appears in the parameter values a ValueError will be + raised. + + If ``cb`` is set, it should be a callback which will get called as blocks + of data are encoded. It will be called with (param, current, total), + indicating the current parameter being encoded, the current amount encoded, + and the total amount to encode. + + Returns a tuple of `datagen`, `headers`, where `datagen` is a + generator that will yield blocks of data that make up the encoded + parameters, and `headers` is a dictionary with the assoicated + Content-Type and Content-Length headers. + + Examples: + + >>> datagen, headers = multipart_encode( [("key", "value1"), ("key", "value2")] ) + >>> s = "".join(datagen) + >>> assert "value2" in s and "value1" in s + + >>> p = MultipartParam("key", "value2") + >>> datagen, headers = multipart_encode( [("key", "value1"), p] ) + >>> s = "".join(datagen) + >>> assert "value2" in s and "value1" in s + + >>> datagen, headers = multipart_encode( {"key": "value1"} ) + >>> s = "".join(datagen) + >>> assert "value2" not in s and "value1" in s + + """ + if boundary is None: + boundary = gen_boundary() + else: + boundary = urllib.quote_plus(boundary) + + headers = get_headers(params, boundary) + params = MultipartParam.from_params(params) + + return multipart_yielder(params, boundary, cb), headers diff --git a/src/libs/qt-breakpad/poster/streaminghttp.py b/src/libs/qt-breakpad/poster/streaminghttp.py new file mode 100644 index 0000000000..eb3b4661d3 --- /dev/null +++ b/src/libs/qt-breakpad/poster/streaminghttp.py @@ -0,0 +1,216 @@ +# Copyright (c) 2010 Chris AtLee +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""Streaming HTTP uploads module. + +This module extends the standard httplib and urllib2 objects so that +iterable objects can be used in the body of HTTP requests. + +In most cases all one should have to do is call :func:`register_openers()` +to register the new streaming http handlers which will take priority over +the default handlers, and then you can use iterable objects in the body +of HTTP requests. + +**N.B.** You must specify a Content-Length header if using an iterable object +since there is no way to determine in advance the total size that will be +yielded, and there is no way to reset an interator. + +Example usage: + +>>> from StringIO import StringIO +>>> import urllib2, poster.streaminghttp + +>>> opener = poster.streaminghttp.register_openers() + +>>> s = "Test file data" +>>> f = StringIO(s) + +>>> req = urllib2.Request("http://localhost:5000", f, +... {'Content-Length': str(len(s))}) +""" + +import httplib, urllib2, socket +from httplib import NotConnected + +__all__ = ['StreamingHTTPConnection', 'StreamingHTTPRedirectHandler', + 'StreamingHTTPHandler', 'register_openers'] + +if hasattr(httplib, 'HTTPS'): + __all__.extend(['StreamingHTTPSHandler', 'StreamingHTTPSConnection']) + +class _StreamingHTTPMixin: + """Mixin class for HTTP and HTTPS connections that implements a streaming + send method.""" + def send(self, value): + """Send ``value`` to the server. + + ``value`` can be a string object, a file-like object that supports + a .read() method, or an iterable object that supports a .next() + method. + """ + # Based on python 2.6's httplib.HTTPConnection.send() + if self.sock is None: + if self.auto_open: + self.connect() + else: + raise NotConnected() + + # send the data to the server. if we get a broken pipe, then close + # the socket. we want to reconnect when somebody tries to send again. + # + # NOTE: we DO propagate the error, though, because we cannot simply + # ignore the error... the caller will know if they can retry. + if self.debuglevel > 0: + print "send:", repr(value) + try: + blocksize = 8192 + if hasattr(value, 'read') : + if hasattr(value, 'seek'): + value.seek(0) + if self.debuglevel > 0: + print "sendIng a read()able" + data = value.read(blocksize) + while data: + self.sock.sendall(data) + data = value.read(blocksize) + elif hasattr(value, 'next'): + if hasattr(value, 'reset'): + value.reset() + if self.debuglevel > 0: + print "sendIng an iterable" + for data in value: + self.sock.sendall(data) + else: + self.sock.sendall(value) + except socket.error, v: + if v[0] == 32: # Broken pipe + self.close() + raise + +class StreamingHTTPConnection(_StreamingHTTPMixin, httplib.HTTPConnection): + """Subclass of `httplib.HTTPConnection` that overrides the `send()` method + to support iterable body objects""" + +class StreamingHTTPRedirectHandler(urllib2.HTTPRedirectHandler): + """Subclass of `urllib2.HTTPRedirectHandler` that overrides the + `redirect_request` method to properly handle redirected POST requests + + This class is required because python 2.5's HTTPRedirectHandler does + not remove the Content-Type or Content-Length headers when requesting + the new resource, but the body of the original request is not preserved. + """ + + handler_order = urllib2.HTTPRedirectHandler.handler_order - 1 + + # From python2.6 urllib2's HTTPRedirectHandler + def redirect_request(self, req, fp, code, msg, headers, newurl): + """Return a Request or None in response to a redirect. + + This is called by the http_error_30x methods when a + redirection response is received. If a redirection should + take place, return a new Request to allow http_error_30x to + perform the redirect. Otherwise, raise HTTPError if no-one + else should try to handle this url. Return None if you can't + but another Handler might. + """ + m = req.get_method() + if (code in (301, 302, 303, 307) and m in ("GET", "HEAD") + or code in (301, 302, 303) and m == "POST"): + # Strictly (according to RFC 2616), 301 or 302 in response + # to a POST MUST NOT cause a redirection without confirmation + # from the user (of urllib2, in this case). In practice, + # essentially all clients do redirect in this case, so we + # do the same. + # be conciliant with URIs containing a space + newurl = newurl.replace(' ', '%20') + newheaders = dict((k, v) for k, v in req.headers.items() + if k.lower() not in ( + "content-length", "content-type") + ) + return urllib2.Request(newurl, + headers=newheaders, + origin_req_host=req.get_origin_req_host(), + unverifiable=True) + else: + raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp) + +class StreamingHTTPHandler(urllib2.HTTPHandler): + """Subclass of `urllib2.HTTPHandler` that uses + StreamingHTTPConnection as its http connection class.""" + + handler_order = urllib2.HTTPHandler.handler_order - 1 + + def http_open(self, req): + """Open a StreamingHTTPConnection for the given request""" + return self.do_open(StreamingHTTPConnection, req) + + def http_request(self, req): + """Handle a HTTP request. Make sure that Content-Length is specified + if we're using an interable value""" + # Make sure that if we're using an iterable object as the request + # body, that we've also specified Content-Length + if req.has_data(): + data = req.get_data() + if hasattr(data, 'read') or hasattr(data, 'next'): + if not req.has_header('Content-length'): + raise ValueError( + "No Content-Length specified for iterable body") + return urllib2.HTTPHandler.do_request_(self, req) + +if hasattr(httplib, 'HTTPS'): + class StreamingHTTPSConnection(_StreamingHTTPMixin, + httplib.HTTPSConnection): + """Subclass of `httplib.HTTSConnection` that overrides the `send()` + method to support iterable body objects""" + + class StreamingHTTPSHandler(urllib2.HTTPSHandler): + """Subclass of `urllib2.HTTPSHandler` that uses + StreamingHTTPSConnection as its http connection class.""" + + handler_order = urllib2.HTTPSHandler.handler_order - 1 + + def https_open(self, req): + return self.do_open(StreamingHTTPSConnection, req) + + def https_request(self, req): + # Make sure that if we're using an iterable object as the request + # body, that we've also specified Content-Length + if req.has_data(): + data = req.get_data() + if hasattr(data, 'read') or hasattr(data, 'next'): + if not req.has_header('Content-length'): + raise ValueError( + "No Content-Length specified for iterable body") + return urllib2.HTTPSHandler.do_request_(self, req) + + +def register_openers(): + """Register the streaming http handlers in the global urllib2 default + opener object. + + Returns the created OpenerDirector object.""" + handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler] + if hasattr(httplib, "HTTPS"): + handlers.append(StreamingHTTPSHandler) + + opener = urllib2.build_opener(*handlers) + + urllib2.install_opener(opener) + + return opener diff --git a/src/libs/qt-breakpad/qtbreakpad.pri b/src/libs/qt-breakpad/qtbreakpad.pri new file mode 100644 index 0000000000..4eada575c1 --- /dev/null +++ b/src/libs/qt-breakpad/qtbreakpad.pri @@ -0,0 +1,64 @@ +isEmpty(BREAKPAD_SOURCE_DIR): return() + +HEADERS += $$PWD/qtbreakpad/qtsystemexceptionhandler.h +SOURCES += $$PWD/qtbreakpad/qtsystemexceptionhandler.cpp + +DEFINES += ENABLE_QT_BREAKPAD + +win32:BREAKPAD_SOURCE_DIR ~= s,\\,/, + +INCLUDEPATH += \ + $$BREAKPAD_SOURCE_DIR/src \ + $$PWD/qtbreakpad + +SOURCES += \ + $$BREAKPAD_SOURCE_DIR/src/common/string_conversion.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/convert_UTF.c \ + $$BREAKPAD_SOURCE_DIR/src/common/md5.cc + +linux:SOURCES += \ + $$BREAKPAD_SOURCE_DIR/src/client/minidump_file_writer.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/log/log.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/handler/exception_handler.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/handler/minidump_descriptor.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/linux/guid_creator.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/dump_writer_common/thread_info.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/dump_writer_common/ucontext_reader.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/minidump_writer/linux_dumper.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/minidump_writer/minidump_writer.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/minidump_writer/linux_ptrace_dumper.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/microdump_writer/microdump_writer.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/linux/file_id.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/linux/elfutils.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/linux/linux_libc_support.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/linux/memory_mapped_file.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/linux/safe_readlink.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/linux/crash_generation/crash_generation_client.cc + +win32:SOURCES += \ + $$BREAKPAD_SOURCE_DIR/src/common/windows/guid_string.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/windows/handler/exception_handler.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/windows/crash_generation/minidump_generator.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/windows/crash_generation/client_info.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/windows/crash_generation/crash_generation_client.cc + +macos { + SOURCES += \ + $$BREAKPAD_SOURCE_DIR/src/client/minidump_file_writer.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/mac/crash_generation/crash_generation_client.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/mac/handler/exception_handler.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/mac/handler/minidump_generator.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/mac/handler/breakpad_nlist_64.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/mac/handler/dynamic_images.cc \ + $$BREAKPAD_SOURCE_DIR/src/client/mac/handler/protected_memory_allocator.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/mac/bootstrap_compat.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/mac/file_id.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/mac/macho_id.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/mac/macho_reader.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/mac/macho_utilities.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/mac/macho_walker.cc \ + $$BREAKPAD_SOURCE_DIR/src/common/mac/string_utilities.cc + OBJECTIVE_SOURCES += \ + $$BREAKPAD_SOURCE_DIR/src/common/mac/MachIPC.mm + LIBS += -framework Foundation +} diff --git a/src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp b/src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp new file mode 100644 index 0000000000..d439ba112d --- /dev/null +++ b/src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "qtsystemexceptionhandler.h" + +#include <utils/fileutils.h> +#include <utils/hostosinfo.h> + +#include <QCoreApplication> +#include <QDebug> +#include <QDir> +#include <QProcess> + +#if defined(Q_OS_LINUX) +# include "client/linux/handler/exception_handler.h" +#elif defined(Q_OS_WIN) +# include "client/windows/handler/exception_handler.h" +#elif defined(Q_OS_MACOS) +# include "client/mac/handler/exception_handler.h" +#endif + +#if defined(Q_OS_LINUX) +static bool exceptionHandlerCallback(const google_breakpad::MinidumpDescriptor& descriptor, + void* /*context*/, + bool succeeded) +{ + if (!succeeded) + return succeeded; + + const QStringList argumentList = { + QString::fromLocal8Bit(descriptor.path()), + QString::number(QtSystemExceptionHandler::startTime().toTime_t()), + QCoreApplication::applicationName(), + QCoreApplication::applicationVersion(), + QtSystemExceptionHandler::plugins(), + QtSystemExceptionHandler::buildVersion(), + QCoreApplication::applicationFilePath() + }; + + return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList); +} +#elif defined(Q_OS_MACOS) +static bool exceptionHandlerCallback(const char *dump_dir, + const char *minidump_id, + void *context, + bool succeeded) +{ + Q_UNUSED(context); + + if (!succeeded) + return succeeded; + + const QString path = QString::fromLocal8Bit(dump_dir) + '/' + + QString::fromLocal8Bit(minidump_id) + ".dmp"; + const QStringList argumentList = { + path, + QString::number(QtSystemExceptionHandler::startTime().toTime_t()), + QCoreApplication::applicationName(), + QCoreApplication::applicationVersion(), + QtSystemExceptionHandler::plugins(), + QtSystemExceptionHandler::buildVersion(), + QCoreApplication::applicationFilePath() + }; + + return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList); +} +#elif defined(Q_OS_WIN) +static bool exceptionHandlerCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded) +{ + Q_UNUSED(assertion); + Q_UNUSED(exinfo); + Q_UNUSED(context); + + if (!succeeded) + return succeeded; + + const QString path = QString::fromWCharArray(dump_path, int(wcslen(dump_path))) + '/' + + QString::fromWCharArray(minidump_id, int(wcslen(minidump_id))) + ".dmp"; + const QStringList argumentList = { + path, + QString::number(QtSystemExceptionHandler::startTime().toTime_t()), + QCoreApplication::applicationName(), + QCoreApplication::applicationVersion(), + QtSystemExceptionHandler::plugins(), + QtSystemExceptionHandler::buildVersion(), + QCoreApplication::applicationFilePath() + }; + + return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList); +} +#endif + +static QDateTime s_startTime; +static QString s_plugins; +static QString s_buildVersion; +static QString s_crashHandlerPath; + +#if defined(Q_OS_LINUX) +QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath) + : exceptionHandler(new google_breakpad::ExceptionHandler( + google_breakpad::MinidumpDescriptor(QDir::tempPath().toStdString()), + NULL, + exceptionHandlerCallback, + NULL, + true, + -1)) +{ + init(libexecPath); +} +#elif defined(Q_OS_MACOS) +QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath) + : exceptionHandler(new google_breakpad::ExceptionHandler( + QDir::tempPath().toStdString(), + NULL, + exceptionHandlerCallback, + NULL, + true, + NULL)) +{ + init(libexecPath); +} +#elif defined(Q_OS_WIN) +QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath) + : exceptionHandler(new google_breakpad::ExceptionHandler( + QDir::tempPath().toStdWString(), + NULL, + exceptionHandlerCallback, + NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL)) +{ + init(libexecPath); +} +#else +QtSystemExceptionHandler::QtSystemExceptionHandler(const QString & /*libexecPath*/) + : exceptionHandler(0) +{ + +} +#endif + +void QtSystemExceptionHandler::init(const QString &libexecPath) +{ + s_startTime = QDateTime::currentDateTime(); + s_crashHandlerPath = libexecPath + Utils::HostOsInfo::withExecutableSuffix("/qtcrashhandler"); +} + +QtSystemExceptionHandler::~QtSystemExceptionHandler() +{ +#ifdef ENABLE_QT_BREAKPAD + delete exceptionHandler; +#endif +} + +void QtSystemExceptionHandler::setPlugins(const QStringList &pluginNameList) +{ + s_plugins = QString("{%1}").arg(pluginNameList.join(",")); +} + +void QtSystemExceptionHandler::setBuildVersion(const QString &version) +{ + s_buildVersion = version; +} + +QString QtSystemExceptionHandler::buildVersion() +{ + return s_buildVersion; +} + +QString QtSystemExceptionHandler::plugins() +{ + return s_plugins; +} + +void QtSystemExceptionHandler::setCrashHandlerPath(const QString &crashHandlerPath) +{ + s_crashHandlerPath = crashHandlerPath; +} + +QString QtSystemExceptionHandler::crashHandlerPath() +{ + return s_crashHandlerPath; +} + +void QtSystemExceptionHandler::crash() +{ + int *a = (int*)0x42; + + fprintf(stdout, "Going to crash...\n"); + fprintf(stdout, "A = %d", *a); +} + +QDateTime QtSystemExceptionHandler::startTime() +{ + return s_startTime; +} diff --git a/src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h b/src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h new file mode 100644 index 0000000000..2cc3067d77 --- /dev/null +++ b/src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QDateTime> + +namespace google_breakpad { + class ExceptionHandler; +} + +class QtSystemExceptionHandler +{ +public: + QtSystemExceptionHandler(const QString &libexecPath); + ~QtSystemExceptionHandler(); + + static void crash(); + static void setPlugins(const QStringList &pluginNameList); + static void setBuildVersion(const QString &version); + static void setCrashHandlerPath(const QString &crashHandlerPath); + + static QString plugins(); + static QString buildVersion(); + static QString crashHandlerPath(); + + static QDateTime startTime(); + +protected: + void init(const QString &libexecPath); + +private: + google_breakpad::ExceptionHandler *exceptionHandler = nullptr; +}; diff --git a/src/libs/qt-breakpad/qtbreakpadsymbols b/src/libs/qt-breakpad/qtbreakpadsymbols new file mode 100755 index 0000000000..3b7c5af8ff --- /dev/null +++ b/src/libs/qt-breakpad/qtbreakpadsymbols @@ -0,0 +1,271 @@ +#!/usr/bin/python + +import sys +import os +import os.path +import subprocess +import shlex +import shutil +import base64 + +from poster.encode import multipart_encode +from poster.encode import MultipartParam +from poster.streaminghttp import register_openers +import urllib2 + +from optparse import OptionParser + +register_openers() + +breakpadSourceDir = os.environ['BREAKPAD_SOURCE_DIR'] +breakpadUploadUrl = os.environ['BREAKPAD_UPLOAD_URL'] +breakpadUserName = os.environ['BREAKPAD_USER_NAME'] +breakpadUserPassword = os.environ['BREAKPAD_USER_PASSWORD'] + +if sys.platform == 'win32': + nullfilename = 'nul:' +else: + nullfilename = '/dev/null' +nullfile = open(nullfilename, 'r+b') + + +def stdoutFromProcess(process): + (stdout, stderr) = process.communicate() + if stderr: + print stderr + raise SystemError() + sys.exit(1) + return stdout + +def toolPath(): + if sys.platform == 'linux2': + dumpsymsPath = os.path.join(breakpadSourceDir, 'src/tools/linux/dump_syms/dump_syms') + elif sys.platform == 'darwin': + dumpsymsPath = os.path.join(breakpadSourceDir, 'src/tools/mac/dump_syms/build/Release/dump_syms') + elif sys.platform == 'win32': + dumpsymsPath = os.path.join(breakpadSourceDir,'src\\tools\\windows\\binaries\\dump_syms.exe') + else: + sys.exit(1) + return dumpsymsPath + +gitRootDirectoryCache = {} + +def gitRootDirectory(path): + directory = os.path.dirname(path) + if directory in gitRootDirectoryCache: + return gitRootDirectoryCache[directory] + directoryList = directory.split(os.sep) + while len(directoryList) > 0: + gitDirectory = os.sep.join(directoryList + ['.git']) + if os.path.exists(gitDirectory): + gitDirectory = os.sep.join(directoryList) + gitRootDirectoryCache[directory] = gitDirectory + return gitDirectory + directoryList.pop() + + return None + + +gitCommitShaCache = {} + + +def gitCommitSha(gitRootPath): + if gitRootPath in gitCommitShaCache: + return gitCommitShaCache[gitRootPath] + gitProcess = subprocess.Popen(shlex.split('git rev-parse HEAD'), + stdout=subprocess.PIPE, + stderr=nullfile, cwd=gitRootPath) + commitSha = stdoutFromProcess(gitProcess).strip('\n') + gitCommitShaCache[gitRootPath] = commitSha + return commitSha + + +gitRemoteRepositoryCache = {} + + +def gitRemoteRepository(gitRootPath): + if gitRootPath in gitRemoteRepositoryCache: + return gitRemoteRepositoryCache[gitRootPath] + gitProcess = \ + subprocess.Popen(shlex.split('git config remote.origin.url'), + stdout=subprocess.PIPE, stderr=nullfile, + cwd=gitRootPath) + repository = stdoutFromProcess(gitProcess).strip('\n') + gitRemoteRepositoryCache[gitRootPath] = repository + return repository + + +gitFilePathCache = {} + + +def populateFilePathCache(gitRootPath): + if gitRootPath not in gitFilePathCache: + gitProcess = \ + subprocess.Popen(shlex.split('git ls-files --full-name'), + stdout=subprocess.PIPE, stderr=nullfile, + cwd=gitRootPath) + fileNameList = stdoutFromProcess(gitProcess).split('\n') + filePathCache = {} + for fileName in fileNameList: + baseFileName = os.path.basename(fileName) + filePathCache[baseFileName] = fileName + gitFilePathCache[gitRootPath] = filePathCache + + +def gitFilePath(path, gitRootPath): + if not gitRootPath: + return path + + populateFilePathCache(gitRootPath) + + baseFileName = os.path.basename(path) + filePathCache = gitFilePathCache[gitRootPath] + if baseFileName in filePathCache: + return filePathCache[baseFileName] + else: + return os.path.relpath(path, gitRootPath) + + +def isInRepository(path): + gitRootPath = gitRootDirectory(path) + if not gitRootPath: + return False + + populateFilePathCache(gitRootPath) + baseFileName = os.path.basename(path) + if baseFileName in gitFilePathCache[gitRootPath]: + return True + else: + return False + + +def sendSymbolsToServer( + breakpadUploadUrl, + symbolText, + codeFile, + debugFile, + debugIdentifier, + operatingSystem, + cpu, + ): + (data, headers) = multipart_encode({ + 'symbol_file': MultipartParam('symbol_file', value=symbolText, + filename='symbol_file'), + 'code_file': codeFile, + 'debug_file': debugFile, + 'debug_identifier': debugIdentifier, + 'os': operatingSystem, + 'cpu': cpu, + }) + request = urllib2.Request(breakpadUploadUrl, data, headers) + auth = base64.encodestring('%s:%s' % (breakpadUserName, + breakpadUserPassword))[:-1] # This is just standard un/pw encoding + request.add_header('Authorization', 'Basic %s' % auth) # Add Auth header to request + result = urllib2.urlopen(request).read() + + +def generateSymbolFilesAndSend(binaryPath, projectPath): + dumpsymsPath = toolPath() + + originalBinaryPath = binaryPath + + if sys.platform == 'darwin': + dsymutilProcess = \ + subprocess.Popen(shlex.split('/usr/bin/dsymutil "' + + binaryPath + '"'), stdout=nullfile, + stderr=nullfile) + dsymutilProcess.wait() + binaryPath += os.path.join('.dSYM/Contents/Resources/DWARF/', + os.path.basename(binaryPath)) + + binaryPath = os.path.normpath(binaryPath) + + dumpsymsProcess = subprocess.Popen(shlex.split('"' + dumpsymsPath + + '" "' + binaryPath + '"'), stdout=subprocess.PIPE, + stderr=nullfile, cwd=projectPath) + symbolList = stdoutFromProcess(dumpsymsProcess).split('\n') + + outputTextList = [] + codeFile = os.path.basename(binaryPath) + debugFile = '' + debugIdentifier = '' + operatingSystem = '' + cpu = '' + moduleNotParsed = True + for line in symbolList: + line = line.strip('\n').strip('\r') + if line[:4] == 'FILE': + (marker, idnumber, filepath) = line.split(' ', 2) + filepath = os.path.normpath(os.path.join(projectPath, + filepath)) + + if isInRepository(filepath): + gitRootPath = gitRootDirectory(filepath) + commitSha = gitCommitSha(gitRootPath) + repository = \ + gitRemoteRepository(gitRootPath).replace(':', '/') + relativeFilePath = gitFilePath(filepath, + gitRootPath).replace('\\', '/') + outputTextList.append('FILE ' + idnumber + ' git:' + + repository + ':' + relativeFilePath + ':' + + commitSha) + else: + outputTextList.append(line) + elif moduleNotParsed and line[:6] == 'MODULE': + (operatingSystem, cpu, debugIdentifier, debugFile) = \ + line[7:].split(' ', 3) + moduleNotParsed = False + elif line: + outputTextList.append(line) + + if moduleNotParsed: + print 'Module not parsed' + sys.exit(1) + + sendSymbolsToServer( + breakpadUploadUrl, + '\n'.join(outputTextList), + codeFile, + debugFile, + debugIdentifier, + operatingSystem, + cpu, + ) + + if sys.platform == 'darwin': + shutil.rmtree(originalBinaryPath + '.dSYM') + + +def testForBreakpad(): + try: + dumpsymsPath = toolPath() + if not dumpsymsPath: + sys.exit(1) + subprocess.Popen([dumpsymsPath], stdout=nullfile, + stderr=nullfile) + except (OSError, KeyError): + print 'No dumpsyms can be executed. Maybe BREAKPAD_SOURCE_DIR is wrong.' + sys.exit(1) + sys.exit(0) + + +def main(): + usage = 'usage: %prog [options] binary projectpath' + parser = OptionParser(usage=usage) + parser.add_option('-v', '--verbose', action='store_true', + dest='verbose') + parser.add_option('-e', '--breakpad-exists', action='store_true', + dest='testForBreakpad') + + (options, args) = parser.parse_args() + + if options.testForBreakpad == True: + testForBreakpad() + if len(args) > 1: + generateSymbolFilesAndSend(args[0], args[1]) + else: + parser.print_help() + + +if __name__ == '__main__': + main() diff --git a/src/libs/qt-breakpad/qtbreakpadsymbols.bat b/src/libs/qt-breakpad/qtbreakpadsymbols.bat new file mode 100644 index 0000000000..aad26594d6 --- /dev/null +++ b/src/libs/qt-breakpad/qtbreakpadsymbols.bat @@ -0,0 +1 @@ +@python qtbreakpadsymbols %* diff --git a/src/libs/qt-breakpad/qtcrashhandler.pri b/src/libs/qt-breakpad/qtcrashhandler.pri new file mode 100644 index 0000000000..c452c09bd8 --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler.pri @@ -0,0 +1,21 @@ +isEmpty(BREAKPAD_SOURCE_DIR): return() + +QT += network + +INCLUDEPATH += \ + $$BREAKPAD_SOURCE_DIR/src \ + $$PWD/qtcrashhandler + +SOURCES += \ + $$PWD/qtcrashhandler/main.cpp \ + $$PWD/qtcrashhandler/mainwidget.cpp \ + $$PWD/qtcrashhandler/detaildialog.cpp \ + $$PWD/qtcrashhandler/dumpsender.cpp + +HEADERS += \ + $$PWD/qtcrashhandler/mainwidget.h \ + $$PWD/qtcrashhandler/detaildialog.h \ + $$PWD/qtcrashhandler/dumpsender.h + +FORMS += \ + $$PWD/qtcrashhandler/mainwidget.ui diff --git a/src/libs/qt-breakpad/qtcrashhandler/detaildialog.cpp b/src/libs/qt-breakpad/qtcrashhandler/detaildialog.cpp new file mode 100644 index 0000000000..21c7e87c89 --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/detaildialog.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "detaildialog.h" + +#include <QDialogButtonBox> +#include <QTextBrowser> +#include <QVBoxLayout> + +DetailDialog::DetailDialog(QWidget *parent) : + QDialog(parent) +{ + resize(640, 480); + QVBoxLayout *verticalLayout = new QVBoxLayout(this); + textBrowser = new QTextBrowser(this); + verticalLayout->addWidget(textBrowser); + buttonBox = new QDialogButtonBox(this); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Close); + + verticalLayout->addWidget(buttonBox); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +DetailDialog::~DetailDialog() +{ +} + +void DetailDialog::setText(const QString &text) +{ + textBrowser->setPlainText(text); +} diff --git a/src/libs/qt-breakpad/qtcrashhandler/detaildialog.h b/src/libs/qt-breakpad/qtcrashhandler/detaildialog.h new file mode 100644 index 0000000000..9db319a4e8 --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/detaildialog.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QDialog> + +QT_BEGIN_NAMESPACE +class QTextBrowser; +class QDialogButtonBox; +QT_END_NAMESPACE + +class DetailDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DetailDialog(QWidget *parent = nullptr); + ~DetailDialog(); + + void setText(const QString &text); + +private: + QTextBrowser *textBrowser = nullptr; + QDialogButtonBox *buttonBox = nullptr; +}; diff --git a/src/libs/qt-breakpad/qtcrashhandler/dumpsender.cpp b/src/libs/qt-breakpad/qtcrashhandler/dumpsender.cpp new file mode 100644 index 0000000000..5b0d1ac3d5 --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/dumpsender.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dumpsender.h" + +#include <QCoreApplication> +#include <QDateTime> +#include <QDebug> +#include <QFile> +#include <QFileInfo> +#include <QHttpMultiPart> +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QPair> +#include <QProcess> +#include <QStringList> +#include <QTemporaryFile> +#include <QUrl> + +static const QByteArray boundary = "12345cfdfzfsdfsdfassssaaadsd24324jxccxzzzzz98xzcz"; + +DumpSender::DumpSender(QObject *parent) : + QObject(parent), + m_httpMultiPart(QHttpMultiPart::FormDataType) +{ + const QString dumpPath = QCoreApplication::arguments().at(1); + const QByteArray startupTime = QCoreApplication::arguments().at(2).toLocal8Bit(); + const QByteArray applicationName = QCoreApplication::arguments().at(3).toLocal8Bit(); + QByteArray applicationVersion = QCoreApplication::arguments().at(4).toLocal8Bit(); + const QByteArray plugins = QCoreApplication::arguments().at(5).toLocal8Bit(); + // QByteArray ideRevision = QCoreApplication::arguments().at(6).toLocal8Bit(); + m_applicationFilePath = QCoreApplication::arguments().at(7); + + if (applicationVersion.isEmpty()) + applicationVersion = "1.0.0"; + + QFile dumpFile(dumpPath, this); + const bool isOpen = dumpFile.open(QIODevice::ReadOnly); + Q_ASSERT(isOpen); + Q_UNUSED(isOpen); + + const QList<QPair<QByteArray, QByteArray> > pairList = { + { "StartupTime", startupTime }, + { "Vendor", "Qt Project" }, + { "InstallTime", "0" }, + { "Add-ons", plugins }, + { "BuildID", "" }, + { "SecondsSinceLastCrash", "0" }, + { "ProductName", applicationName }, + { "URL", "" }, + { "Theme", "" }, + { "Version", applicationVersion }, + { "CrashTime", QByteArray::number(QDateTime::currentDateTime().toTime_t()) }, + { "Throttleable", "0" } + }; + + m_formData.append("--" + boundary + "\r\n"); + for (const auto &pair : pairList) { + m_formData.append("Content-Disposition: form-data; name=\"" + pair.first + "\"\r\n\r\n"); + m_formData.append(pair.second + "\r\n"); + m_formData.append("--" + boundary + "\r\n"); + } + + + QByteArray dumpArray = dumpFile.readAll(); + m_formData.append("Content-Type: application/octet-stream\r\n"); + m_formData.append("Content-Disposition: form-data; name=\"upload_file_minidump\"; filename=\"" + + QFileInfo(dumpPath).baseName().toUtf8() + "\r\n"); + m_formData.append("Content-Transfer-Encoding: binary\r\n\r\n"); + m_formData.append(dumpArray); + + m_formData.append("--" + boundary + "--\r\n"); + + for (const auto &pair : pairList) { + QHttpPart httpPart; + httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"" + pair.first + "\""); + httpPart.setBody(pair.second); + m_httpMultiPart.append(httpPart); + } + + QHttpPart dumpPart; + dumpPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream"); + dumpPart.setHeader(QNetworkRequest::ContentDispositionHeader, + "form-data; name=\"upload_file_minidump\"; filename=\"" + + QFileInfo(dumpPath).baseName().toUtf8() + "\""); + dumpPart.setRawHeader("Content-Transfer-Encoding:", "binary"); + dumpPart.setBody(dumpArray); + m_httpMultiPart.append(dumpPart); +} + +void DumpSender::sendDumpAndQuit() +{ + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + + QNetworkRequest request(QUrl("http://crashes.qt.io/submit")); + + request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary); + + QList<QPair<QByteArray, QByteArray>> pairList; + + if (!m_emailAddress.isEmpty()) + pairList.append({ "Email", m_emailAddress.toLocal8Bit() }); + + if (!m_commentText.isEmpty()) + pairList.append({ "Comments", m_commentText.toLocal8Bit() }); + + for (const auto &pair : pairList) { + m_formData.append("Content-Disposition: form-data; name=\"" + pair.first + "\"\r\n\r\n"); + m_formData.append(pair.second + "\r\n"); + m_formData.append("--" + boundary + "\r\n"); + } + + for (const auto &pair : pairList) { + QHttpPart httpPart; + httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"" + pair.first + "\""); + httpPart.setBody(pair.second); + m_httpMultiPart.append(httpPart); + } + + QNetworkReply *reply = manager->post(request, &m_httpMultiPart); + + m_httpMultiPart.setParent(reply); + + connect(reply, &QNetworkReply::uploadProgress, this, &DumpSender::uploadProgress); + connect(reply, &QNetworkReply::finished, QCoreApplication::instance(), &QCoreApplication::quit); + connect(reply, + static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), + QCoreApplication::instance(), &QCoreApplication::quit); +} + +void DumpSender::restartCrashedApplicationAndSendDump() +{ + QProcess::startDetached(m_applicationFilePath); + sendDumpAndQuit(); +} + +void DumpSender::restartCrashedApplication() +{ + QProcess::startDetached(m_applicationFilePath); + QCoreApplication::quit(); +} + +void DumpSender::setEmailAddress(const QString &email) +{ + m_emailAddress = email; +} + +void DumpSender::setCommentText(const QString &comment) +{ + m_commentText = comment; +} + +int DumpSender::dumperSize() const +{ + return m_formData.size(); +} diff --git a/src/libs/qt-breakpad/qtcrashhandler/dumpsender.h b/src/libs/qt-breakpad/qtcrashhandler/dumpsender.h new file mode 100644 index 0000000000..4a57a5e99d --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/dumpsender.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QHttpMultiPart> +#include <QObject> + +class DumpSender : public QObject +{ + Q_OBJECT + +public: + explicit DumpSender(QObject *parent = nullptr); + + int dumperSize() const; + + void sendDumpAndQuit(); + void restartCrashedApplication(); + void restartCrashedApplicationAndSendDump(); + void setEmailAddress(const QString &email); + void setCommentText(const QString &comment); + +signals: + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + +private: + QHttpMultiPart m_httpMultiPart; + QByteArray m_formData; + QString m_applicationFilePath; + QString m_emailAddress; + QString m_commentText; +}; diff --git a/src/libs/qt-breakpad/qtcrashhandler/main.cpp b/src/libs/qt-breakpad/qtcrashhandler/main.cpp new file mode 100644 index 0000000000..7a307381cc --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/main.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "mainwidget.h" +#include "dumpsender.h" + +#include <QApplication> +#include <QFileInfo> +#include <QHostInfo> +#include <QNetworkProxyFactory> + +int main(int argc, char *argv[]) +{ + QApplication application(argc, argv); + + if (application.arguments().count() <= 1) + return 0; + + const QString dumpPath = QApplication::arguments().at(1); + if (!QFileInfo(dumpPath).exists()) + return 0; + + QNetworkProxyFactory::setUseSystemConfiguration(true); + + QHostInfo hostInfo = QHostInfo::fromName("crashes.qt.io"); + +// if (hostInfo.error() != QHostInfo::NoError) +// return 0; + + DumpSender dumpSender; + + MainWidget mainWindow; + + mainWindow.setProgressbarMaximum(dumpSender.dumperSize()); + + QObject::connect(&mainWindow, &MainWidget::restartCrashedApplication, + &dumpSender, &DumpSender::restartCrashedApplication); + QObject::connect(&mainWindow, &MainWidget::restartCrashedApplicationAndSendDump, + &dumpSender, &DumpSender::restartCrashedApplicationAndSendDump); + QObject::connect(&mainWindow, &MainWidget::sendDump, + &dumpSender, &DumpSender::sendDumpAndQuit); + QObject::connect(&mainWindow, &MainWidget::commentChanged, + &dumpSender, &DumpSender::setCommentText); + QObject::connect(&mainWindow, &MainWidget::emailAdressChanged, + &dumpSender, &DumpSender::setEmailAddress); + QObject::connect(&dumpSender, &DumpSender::uploadProgress, + &mainWindow, &MainWidget::updateProgressBar); + + mainWindow.show(); + return application.exec(); +} diff --git a/src/libs/qt-breakpad/qtcrashhandler/mainwidget.cpp b/src/libs/qt-breakpad/qtcrashhandler/mainwidget.cpp new file mode 100644 index 0000000000..0fc4f2e1d4 --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/mainwidget.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "mainwidget.h" +#include "ui_mainwidget.h" + +#include <QApplication> +#include <QDateTime> +#include <QFile> +#include <QFileInfo> +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QPair> +#include <QStringList> +#include <QTemporaryFile> +#include <QUrl> + +MainWidget::MainWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::MainWidget) +{ + ui->setupUi(this); + + connect(ui->restartButton, &QAbstractButton::clicked, this, &MainWidget::restartApplication); + connect(ui->quitButton, &QAbstractButton::clicked, this, &MainWidget::quitApplication); + connect(ui->detailButton, &QAbstractButton::clicked, this, &MainWidget::showDetails); + connect(ui->commentTextEdit, &QTextEdit::textChanged, this, &MainWidget::commentIsProvided); + connect(ui->emailLineEdit, &QLineEdit::textEdited, this, &MainWidget::emailAdressChanged); +} + +MainWidget::~MainWidget() +{ + delete ui; +} + +void MainWidget::setProgressbarMaximum(int maximum) +{ + ui->progressBar->setMaximum(maximum); +} + +void MainWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + if (e->type() == QEvent::LanguageChange) + ui->retranslateUi(this); +} + +void MainWidget::updateProgressBar(qint64 progressCount, qint64 fullCount) +{ + ui->progressBar->setValue(static_cast<int>(progressCount)); + ui->progressBar->setMaximum(static_cast<int>(fullCount)); +} + +void MainWidget::showError(QNetworkReply::NetworkError error) +{ + QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); + if (error != QNetworkReply::NoError && reply) { + ui->commentTextEdit->setReadOnly(true); + ui->commentTextEdit->setPlainText(reply->errorString()); + } +} + +void MainWidget::restartApplication() +{ + if (ui->sendDumpCheckBox->isChecked()) + emit restartCrashedApplicationAndSendDump(); + else + emit restartCrashedApplication(); +} + +void MainWidget::quitApplication() +{ + ui->quitButton->setEnabled(false); + if (ui->sendDumpCheckBox->isChecked()) + emit sendDump(); + else + QCoreApplication::quit(); +} + +void MainWidget::commentIsProvided() +{ + m_commentIsProvided = true; + emit commentChanged(ui->commentTextEdit->toPlainText()); +} + +void MainWidget::showDetails() +{ + if (m_detailDialog.isNull()) { + m_detailDialog = new DetailDialog(this); + + QString detailText; + + detailText.append(tr("We specifically send the following information:\n\n")); + + QString dumpPath = QApplication::arguments().at(1); + QString startupTime = QApplication::arguments().at(2); + QString applicationName = QApplication::arguments().at(3); + QString applicationVersion = QApplication::arguments().at(4); + QString plugins = QApplication::arguments().at(5); + QString ideRevision = QApplication::arguments().at(6); + + detailText.append(QString("StartupTime: %1\n").arg(startupTime)); + detailText.append(QString("Vendor: %1\n").arg("Qt Project")); + detailText.append(QString("InstallTime: %1\n").arg("0")); + detailText.append(QString("Add-ons: %1\n").arg(plugins)); + detailText.append(QString("BuildID: %1\n").arg("0")); + detailText.append(QString("SecondsSinceLastCrash: %1\n").arg("0")); + detailText.append(QString("ProductName: %1\n").arg(applicationName)); + detailText.append(QString("URL: %1\n").arg("")); + detailText.append(QString("Theme: %1\n").arg("")); + detailText.append(QString("Version: %1\n").arg(applicationVersion)); + detailText.append(QString("CrashTime: %1\n").arg(QString::number(QDateTime::currentDateTime().toTime_t()))); + + if (!ui->emailLineEdit->text().isEmpty()) + detailText.append(tr("Email: %1\n").arg(ui->emailLineEdit->text())); + + if (m_commentIsProvided) + detailText.append(tr("Comments: %1\n").arg(ui->commentTextEdit->toPlainText())); + + detailText.append( + tr("In addition, we send a Microsoft Minidump file, which contains information " + "about this computer, such as the operating system and CPU, and most " + "importantly, it contains the stacktrace, which is an internal structure that " + "shows where the program crashed. This information will help us to identify " + "the cause of the crash and to fix it.")); + + m_detailDialog.data()->setText(detailText); + } + if (m_detailDialog->isVisible()) + m_detailDialog->showNormal(); + else + m_detailDialog->show(); +} diff --git a/src/libs/qt-breakpad/qtcrashhandler/mainwidget.h b/src/libs/qt-breakpad/qtcrashhandler/mainwidget.h new file mode 100644 index 0000000000..4e3b9880c1 --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/mainwidget.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "detaildialog.h" + +#include <QByteArray> +#include <QNetworkReply> +#include <QPointer> +#include <QWidget> + +namespace Ui { class MainWidget; } + +class MainWidget : public QWidget +{ + Q_OBJECT + +public: + explicit MainWidget(QWidget *parent = nullptr); + ~MainWidget(); + + void setProgressbarMaximum(int maximum); + void updateProgressBar(qint64 progressCount, qint64 fullCount); + +signals: + void restartCrashedApplication(); + void sendDump(); + void restartCrashedApplicationAndSendDump(); + void emailAdressChanged(const QString &email); + void commentChanged(const QString &comment); + +protected: + void changeEvent(QEvent *e); + +private: + void restartApplication(); + void quitApplication(); + void showError(QNetworkReply::NetworkError error); + void showDetails(); + void commentIsProvided(); + +private: + Ui::MainWidget *ui; + + QPointer<DetailDialog> m_detailDialog; + bool m_commentIsProvided = false; +}; diff --git a/src/libs/qt-breakpad/qtcrashhandler/mainwidget.ui b/src/libs/qt-breakpad/qtcrashhandler/mainwidget.ui new file mode 100644 index 0000000000..f509a70569 --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/mainwidget.ui @@ -0,0 +1,143 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWidget</class> + <widget class="QWidget" name="MainWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>422</width> + <height>510</height> + </rect> + </property> + <property name="windowTitle"> + <string>Crash Handler</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="font"> + <font> + <pointsize>20</pointsize> + </font> + </property> + <property name="text"> + <string>Qt Creator has crashed</string> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>You can send us a crash report in order to help us diagnose and fix the problem.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Email:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="emailLineEdit"> + <property name="placeholderText"> + <string>Enter here your email (optional)</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="sendDumpCheckBox"> + <property name="text"> + <string>Tell The Qt Company about this crash so they can fix it</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="detailButton"> + <property name="text"> + <string>Details</string> + </property> + </widget> + </item> + <item> + <widget class="QTextEdit" name="commentTextEdit"> + <property name="overwriteMode"> + <bool>true</bool> + </property> + <property name="placeholderText"> + <string>Please describe what you did before it crashed (comments are publicly visible)</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Your crash report will be submitted before you quit or restart.</string> + </property> + </widget> + </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="value"> + <number>0</number> + </property> + <property name="format"> + <string>%v/%m Bytes</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="restartButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Restart</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="quitButton"> + <property name="text"> + <string>Quit</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> +</ui> diff --git a/src/libs/qt-breakpad/qtcrashhandler/qtcrashhandler.pro b/src/libs/qt-breakpad/qtcrashhandler/qtcrashhandler.pro new file mode 100644 index 0000000000..f4bca4e322 --- /dev/null +++ b/src/libs/qt-breakpad/qtcrashhandler/qtcrashhandler.pro @@ -0,0 +1,6 @@ +TARGET = qtcrashhandler +QT += network +TEMPLATE = app + +include(../qtcrashhandler.pri) +DESTDIR = ../bin diff --git a/src/libs/qt-breakpad/testapp/main.cpp b/src/libs/qt-breakpad/testapp/main.cpp new file mode 100644 index 0000000000..1f21f86e4b --- /dev/null +++ b/src/libs/qt-breakpad/testapp/main.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include <qtsystemexceptionhandler.h> + +#include <QCoreApplication> +#include <QTimer> + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + a.setApplicationName("testapp"); + a.setApplicationVersion("1.0.0"); + + QtSystemExceptionHandler exceptionHandler; + + QTimer::singleShot(100, [] { QtSystemExceptionHandler::crash(); }); + + return a.exec(); +} diff --git a/src/libs/qt-breakpad/testapp/testapp.pro b/src/libs/qt-breakpad/testapp/testapp.pro new file mode 100644 index 0000000000..407ad1e2ea --- /dev/null +++ b/src/libs/qt-breakpad/testapp/testapp.pro @@ -0,0 +1,14 @@ +QT -= gui + +include(../qtbreakpad.pri) + +unix:TARGET = testapp.bin +win32:TARGET = testapp + +## install_name_tool -change libQtBreakpad.1.dylib @executable_path/../lib/qtbreakpad/libQtBreakpad.dylib + +DESTDIR = ../bin + +CONFIG += console release c++11 + +SOURCES += main.cpp diff --git a/src/tools/qml2puppet/qml2puppet/qml2puppet.pro b/src/tools/qml2puppet/qml2puppet/qml2puppet.pro index 9644cf4bca..af8591e8ec 100644 --- a/src/tools/qml2puppet/qml2puppet/qml2puppet.pro +++ b/src/tools/qml2puppet/qml2puppet/qml2puppet.pro @@ -1,7 +1,7 @@ TARGET = qml2puppet TEMPLATE = app - +QTC_LIB_DEPENDS += utils include(../../../../qtcreator.pri) osx: DESTDIR = $$IDE_LIBEXEC_PATH/qmldesigner @@ -9,6 +9,7 @@ else: DESTDIR = $$IDE_LIBEXEC_PATH include(../../../rpath.pri) +include(../../../libs/qt-breakpad/qtbreakpad.pri) include(../../../../share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri) isEmpty(PRECOMPILED_HEADER):PRECOMPILED_HEADER = $$PWD/../../../shared/qtcreator_pch.h diff --git a/src/tools/qtcrashhandler/qtcrashhandler.pro b/src/tools/qtcrashhandler/qtcrashhandler.pro index f6ba7590ae..f2a75957d5 100644 --- a/src/tools/qtcrashhandler/qtcrashhandler.pro +++ b/src/tools/qtcrashhandler/qtcrashhandler.pro @@ -1,9 +1,6 @@ TARGET = qtcrashhandler TEMPLATE = app -QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH) -include($${QT_BREAKPAD_ROOT_PATH}/qtcrashhandler.pri) -include(../../../qtcreator.pri) -DESTDIR = $$IDE_BIN_PATH -include(../../rpath.pri) +include(../../qtcreatortool.pri) +include(../../libs/qt-breakpad/qtcrashhandler.pri) diff --git a/src/tools/tools.pro b/src/tools/tools.pro index cfe34abaa0..23268020b7 100644 --- a/src/tools/tools.pro +++ b/src/tools/tools.pro @@ -41,8 +41,7 @@ isEmpty(BUILD_CPLUSPLUS_TOOLS):BUILD_CPLUSPLUS_TOOLS=$$(BUILD_CPLUSPLUS_TOOLS) cplusplus-update-frontend } -QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH) -!isEmpty(QT_BREAKPAD_ROOT_PATH) { +!isEmpty(BREAKPAD_SOURCE_DIR) { SUBDIRS += qtcrashhandler } else { linux-* { |