├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── sentry_breakpad.rb └── sentry_breakpad │ ├── breakpad_parser.rb │ ├── hash_helper.rb │ └── version.rb ├── sentry_breakpad.gemspec └── spec ├── breakpad_parser_spec.rb ├── fixtures └── breakpad_report ├── hash_helper_spec.rb ├── sentry_breakpad_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | /*.gem 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisplayCopNames: true 3 | 4 | Metrics/LineLength: 5 | Max: 100 6 | 7 | Metrics/MethodLength: 8 | Max: 15 9 | 10 | Documentation: 11 | Enabled: false 12 | 13 | Style/AccessModifierIndentation: 14 | EnforcedStyle: outdent 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | rvm: 4 | - 1.9 5 | - 1.9.3 6 | - 2.0 7 | - 2.1 8 | - 2.2 9 | - 2.3.3 10 | - 2.4.0 11 | before_install: gem update --remote bundler 12 | script: 13 | - bundle exec rspec 14 | - bundle exec rubocop -c .rubocop.yml 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in sentry_breakpad.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | sentry_breakpad (0.1.10) 5 | sentry-raven (~> 2.2.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | ast (2.3.0) 11 | diff-lcs (1.2.5) 12 | faraday (0.10.0) 13 | multipart-post (>= 1.2, < 3) 14 | multipart-post (2.0.0) 15 | parser (2.3.3.1) 16 | ast (~> 2.2) 17 | powerpack (0.1.1) 18 | rainbow (2.1.0) 19 | rspec (3.4.0) 20 | rspec-core (~> 3.4.0) 21 | rspec-expectations (~> 3.4.0) 22 | rspec-mocks (~> 3.4.0) 23 | rspec-core (3.4.2) 24 | rspec-support (~> 3.4.0) 25 | rspec-expectations (3.4.0) 26 | diff-lcs (>= 1.2.0, < 2.0) 27 | rspec-support (~> 3.4.0) 28 | rspec-mocks (3.4.1) 29 | diff-lcs (>= 1.2.0, < 2.0) 30 | rspec-support (~> 3.4.0) 31 | rspec-support (3.4.1) 32 | rubocop (0.38.0) 33 | parser (>= 2.3.0.6, < 3.0) 34 | powerpack (~> 0.1) 35 | rainbow (>= 1.99.1, < 3.0) 36 | ruby-progressbar (~> 1.7) 37 | unicode-display_width (~> 1.0, >= 1.0.1) 38 | ruby-progressbar (1.8.1) 39 | sentry-raven (2.2.0) 40 | faraday (>= 0.7.6, < 1.0) 41 | unicode-display_width (1.1.1) 42 | 43 | PLATFORMS 44 | ruby 45 | 46 | DEPENDENCIES 47 | rspec 48 | rubocop 49 | sentry_breakpad! 50 | 51 | BUNDLED WITH 52 | 1.13.6 53 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jean Rouge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/wk8/sentry_breakpad.svg?branch=master)](https://travis-ci.org/wk8/sentry_breakpad) 2 | 3 | # SentryBreakpad 4 | 5 | So you've integrated [Google 6 | Breakpad](https://chromium.googlesource.com/breakpad/breakpad/) into your C/C++ 7 | application, and that gives you nicely formatted reports, such as 8 | 9 | ``` 10 | Operating system: Windows NT 11 | 6.1.7601 Service Pack 1 12 | CPU: x86 13 | GenuineIntel family 6 model 70 stepping 1 14 | 4 CPUs 15 | 16 | Crash reason: EXCEPTION_ACCESS_VIOLATION_READ 17 | Crash address: 0xfffffffffee1dead 18 | 19 | Thread 0 (crashed) 20 | 0 ETClient.exe!QString::~QString() [qstring.h : 992 + 0xa] 21 | eip = 0x00d74e3a esp = 0x0018d1b4 ebp = 0x0018d1b8 ebx = 0x01ad947c 22 | esi = 0x05850000 edi = 0x058277f8 eax = 0xfee1dead ecx = 0xfee1dead 23 | edx = 0x00000004 efl = 0x00210282 24 | Found by: given as instruction pointer in context 25 | 1 ETClient.exe!QString::`scalar deleting destructor'(unsigned int) + 0xf 26 | eip = 0x00d9161f esp = 0x0018d1c0 ebp = 0x0018d1c4 27 | Found by: call frame info 28 | 2 ETClient.exe!buggyFunc() [machinelistitem.cpp : 181 + 0x1d] 29 | eip = 0x00daaea3 esp = 0x0018d1cc ebp = 0x0018d1dc 30 | Found by: call frame info 31 | 3 ETClient.exe!MachineListItem::on_EditButtonClicked() [machinelistitem.cpp : 197 + 0x5] 32 | eip = 0x00da5dd4 esp = 0x0018d1e4 ebp = 0x0018d250 33 | Found by: call frame info 34 | 4 ETClient.exe!MachineListItem::qt_static_metacall(QObject *,QMetaObject::Call,int,void * *) [moc_machinelistitem.cpp : 115 + 0x8] 35 | eip = 0x00de14a1 esp = 0x0018d258 ebp = 0x0018d27c 36 | Found by: call frame info 37 | 5 ETClient.exe!QMetaObject::activate(QObject *,int,int,void * *) + 0x4cf 38 | eip = 0x0135329f esp = 0x0018d284 ebp = 0x00000000 39 | Found by: call frame info 40 | 41 | Thread 1 42 | 0 ntdll.dll + 0x2019d 43 | eip = 0x7744019d esp = 0x03bffb64 ebp = 0x03bffcf8 ebx = 0x7745c4f8 44 | esi = 0x003d9148 edi = 0x00000000 eax = 0x00000000 ecx = 0x00000000 45 | edx = 0x00000000 efl = 0x00000246 46 | Found by: given as instruction pointer in context 47 | 1 kernel32.dll + 0x1338a 48 | eip = 0x75d3338a esp = 0x03bffd00 ebp = 0x03bffd04 49 | Found by: previous frame's frame pointer 50 | 2 ntdll.dll + 0x39882 51 | eip = 0x77459882 esp = 0x03bffd0c ebp = 0x03bffd44 52 | Found by: previous frame's frame pointer 53 | 3 ntdll.dll + 0x39855 54 | eip = 0x77459855 esp = 0x03bffd4c ebp = 0x03bffd5c 55 | Found by: previous frame's frame pointer 56 | 57 | Thread 2 58 | 0 ntdll.dll + 0x21f86 59 | eip = 0x77441f86 esp = 0x00d4f770 ebp = 0x00d4f8d0 ebx = 0x00010003 60 | esi = 0x00000002 edi = 0x003dc490 eax = 0x00000001 ecx = 0x00000000 61 | edx = 0x00000000 efl = 0x00000246 62 | Found by: given as instruction pointer in context 63 | 1 kernel32.dll + 0x1338a 64 | eip = 0x75d3338a esp = 0x00d4f8d8 ebp = 0x00d4f8dc 65 | Found by: previous frame's frame pointer 66 | 2 ntdll.dll + 0x39882 67 | eip = 0x77459882 esp = 0x00d4f8e4 ebp = 0x00d4f91c 68 | Found by: previous frame's frame pointer 69 | 3 ntdll.dll + 0x39855 70 | eip = 0x77459855 esp = 0x00d4f924 ebp = 0x00d4f934 71 | Found by: previous frame's frame pointer 72 | 73 | Loaded modules: 74 | 0x00d70000 - 0x01b39fff ETClient.exe ??? (main) 75 | 0x67070000 - 0x671a8fff libeay32.dll 1.0.2.4 76 | 0x71020000 - 0x71090fff msvcp120.dll 12.0.21005.1 77 | 0x71370000 - 0x713bbfff ssleay32.dll 1.0.2.4 78 | 0x72470000 - 0x724effff uxtheme.dll 6.1.7600.16385 79 | 0x726e0000 - 0x726f2fff dwmapi.dll 6.1.7601.18917 80 | 0x73160000 - 0x7316dfff RpcRtRemote.dll 6.1.7601.17514 81 | 0x731e0000 - 0x7321afff rsaenh.dll 6.1.7600.16385 82 | 0x73250000 - 0x7329efff webio.dll 6.1.7601.17725 83 | 0x732a0000 - 0x732f7fff winhttp.dll 6.1.7601.17514 84 | 0x73380000 - 0x73396fff cryptsp.dll 6.1.7601.18741 85 | ``` 86 | 87 | All well and good, but it's hard to reliably keep track of these reports... what bugs 88 | are new, which should be resolved, which are happening the most...? 89 | 90 | Then you think you could use [Sentry](https://getsentry.com) to help you track your 91 | Breakpad reports. 92 | 93 | This gem makes it easy to parse Breakpad reports like the above and format them into 94 | Sentry events. 95 | 96 | ## Installation 97 | 98 | Add this line to your application's Gemfile: 99 | 100 | ```ruby 101 | gem 'sentry_breakpad' 102 | ``` 103 | 104 | And then execute: 105 | 106 | $ bundle 107 | 108 | Or install it yourself as: 109 | 110 | $ gem install sentry_breakpad 111 | 112 | ## Usage 113 | 114 | Simply feed the content of a breakpad report to `SentryBreakpad.send_from_string` 115 | 116 | ```ruby 117 | my_breakpad_report = "Operating system: Windows NT\n 6.1.7601 Service Pack 1..." 118 | SentryBreakpad.send_from_string(my_breakpad_report) 119 | ``` 120 | 121 | If you want, you can also use `SentryBreakpad.send_from_file` instead, to read a 122 | breakpad report from the disk, and send it to Sentry: 123 | 124 | ```ruby 125 | SentryBreakpad.send_from_file('/path/to/report') 126 | ``` 127 | 128 | Both functions take an extra argument to allow you to add extra information to the 129 | generated Sentry event, e.g.: 130 | 131 | ```ruby 132 | SentryBreakpad.send_from_file('/path/to/report', { 133 | 'release' => '12.4', 134 | 'extra' => { 135 | 'time_running' => 25632 136 | }, 137 | 'tags' => { 138 | 'build_type' => 'release' 139 | } 140 | }) 141 | ``` 142 | 143 | The exhaustive list of all the extra information that can be passed can be found 144 | 145 | Please note that all of the above assumes that `Raven` has been properly configured 146 | (we use `Raven.client` to send the generated events) 147 | 148 | ## Development 149 | 150 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 151 | 152 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 153 | 154 | ## Contributing 155 | 156 | Bug reports and pull requests are welcome on GitHub at https://github.com/wk8/sentry_breakpad. 157 | 158 | ## License 159 | 160 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 161 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'sentry_breakpad' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require 'pry' 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /lib/sentry_breakpad.rb: -------------------------------------------------------------------------------- 1 | require 'sentry_breakpad/version' 2 | require 'sentry_breakpad/breakpad_parser' 3 | require 'sentry_breakpad/hash_helper' 4 | 5 | require 'raven' 6 | 7 | module SentryBreakpad 8 | class << self 9 | def send_from_file(file_path, extra_info = {}) 10 | send_event(BreakpadParser.from_file(file_path), extra_info) 11 | end 12 | 13 | def send_from_string(string, extra_info = {}) 14 | send_event(BreakpadParser.new(string), extra_info) 15 | end 16 | 17 | private 18 | 19 | def send_event(parser, extra_info) 20 | Raven.client.send_event(parser.raven_event(extra_info)) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/sentry_breakpad/breakpad_parser.rb: -------------------------------------------------------------------------------- 1 | require 'raven' 2 | 3 | module SentryBreakpad 4 | # parses a breakpad report and turns it into a Raven event 5 | class BreakpadParser # rubocop:disable Metrics/ClassLength 6 | def self.from_file(file_path) 7 | new(File.read(file_path)) 8 | end 9 | 10 | def initialize(breakpad_report_content) 11 | @breakpad_report_content = breakpad_report_content 12 | 13 | @message = nil 14 | @tags = {} 15 | @crashed_thread_stacktrace = [] 16 | # TODO: this is not leveraged yet, because the ruby-raven gem doesn't 17 | # support this yet... 18 | # come back to integrating other threads' stacktraces when 19 | # https://github.com/getsentry/raven-ruby/issues/551 20 | # is resolved 21 | @other_threads_stacktraces = {} 22 | @culprit = nil 23 | @modules = {} 24 | @extra = {} 25 | 26 | parse! 27 | end 28 | 29 | def raven_event(extra_info = {}) 30 | hash = HashHelper.deep_merge!(base_event_hash, HashHelper.deep_symbolize_keys!(extra_info)) 31 | Raven::Event.new(hash).tap do |event| 32 | unless @crashed_thread_stacktrace.empty? 33 | event[:stacktrace] = { frames: @crashed_thread_stacktrace } 34 | end 35 | end 36 | end 37 | 38 | private 39 | 40 | def base_event_hash 41 | { 42 | message: @message, 43 | tags: @tags, 44 | culprit: @culprit, 45 | modules: @modules, 46 | extra: @extra, 47 | level: 'fatal', 48 | logger: 'breakpad' 49 | } 50 | end 51 | 52 | def parse! 53 | parse_lines(@breakpad_report_content.split("\n").reverse!) 54 | end 55 | 56 | def parse_lines(lines) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/LineLength 57 | until lines.empty? 58 | case lines.last 59 | when /^Operating system:/ 60 | parse_operating_system(lines) 61 | when /^CPU: / 62 | parse_cpu(lines) 63 | when /^Crash reason:/ 64 | parse_crash_reason(lines.pop) 65 | when /^Crash address:/ 66 | parse_crash_address(lines.pop) 67 | when /^Thread ([0-9]+)(\s+\(crashed\))?/ 68 | parse_thread_stacktrace(lines, Regexp.last_match[1].to_i, !Regexp.last_match[2].nil?) 69 | when /^Loaded modules:/ 70 | parse_loaded_modules(lines) 71 | else 72 | lines.pop 73 | end 74 | end 75 | end 76 | 77 | # Parses something of the form 78 | # Operating system: Windows NT 79 | # 6.1.7601 Service Pack 1 80 | def parse_operating_system(lines) 81 | parse_indented_section(lines, 'Operating system:', :os) 82 | 83 | if @tags[:os] 84 | @extra[:server] ||= {} 85 | @extra[:server][:os] = { 86 | name: @tags[:os], 87 | version: @tags[:os_full] 88 | } 89 | end 90 | end 91 | 92 | # Parses something of the form 93 | # CPU: x86 94 | # GenuineIntel family 6 model 70 stepping 1 95 | # 4 CPUs 96 | def parse_cpu(lines) 97 | parse_indented_section(lines, 'CPU:', :cpu) 98 | end 99 | 100 | def parse_indented_section(lines, prefix, tag_name) 101 | short = remove_prefix(lines.pop, prefix) 102 | long = short 103 | 104 | while lines.last && lines.last.start_with?(' ') 105 | long += ' - ' + lines.pop.strip 106 | end 107 | 108 | @tags[tag_name] = short 109 | @tags["#{tag_name}_full".to_sym] = long 110 | end 111 | 112 | # Parses a single line like 113 | # Crash reason: EXCEPTION_ACCESS_VIOLATION_READ 114 | def parse_crash_reason(line) 115 | reason = remove_prefix(line, 'Crash reason:') 116 | 117 | @message = if @message.nil? 118 | reason 119 | else 120 | # we parse the crashed thread first 121 | "#{reason} at #{@message}" 122 | end 123 | end 124 | 125 | # Parses a single line like 126 | # Crash address: 0xfffffffffee1dead 127 | def parse_crash_address(line) 128 | address = remove_prefix(line, 'Crash address:') 129 | 130 | @extra[:crash_address] = address 131 | end 132 | 133 | def remove_prefix(line, prefix) 134 | line.slice!(prefix) 135 | line.strip 136 | end 137 | 138 | BACKTRACE_LINE_REGEX = /^\s*([0-9]+)\s+(.*)\s+(?:\[(([^ ]+)\s+:\s+([0-9]+))\s+\+\s+0x[0-9a-f]+\]|\+\s+0x[0-9a-f]+)\s*$/ # rubocop:disable Metrics/LineLength 139 | 140 | # rubocop:disable Metrics/LineLength 141 | # Parses something of the form 142 | # Thread 0 (crashed) 143 | # 0 ETClient.exe!QString::~QString() [qstring.h : 992 + 0xa] 144 | # eip = 0x00d74e3a esp = 0x0018d1b4 ebp = 0x0018d1b8 ebx = 0x01ad947c 145 | # esi = 0x05850000 edi = 0x058277f8 eax = 0xfee1dead ecx = 0xfee1dead 146 | # edx = 0x00000004 efl = 0x00210282 147 | # Found by: given as instruction pointer in context 148 | # 1 ETClient.exe!QString::`scalar deleting destructor'(unsigned int) + 0xf 149 | # eip = 0x00d9161f esp = 0x0018d1c0 ebp = 0x0018d1c4 150 | # Found by: call frame info 151 | # 2 ETClient.exe!buggyFunc() [machinelistitem.cpp : 181 + 0x1d] 152 | # eip = 0x00daaea3 esp = 0x0018d1cc ebp = 0x0018d1dc 153 | # Found by: call frame info 154 | # 3 ETClient.exe!MachineListItem::on_EditButtonClicked() [machinelistitem.cpp : 197 + 0x5] 155 | # eip = 0x00da5dd4 esp = 0x0018d1e4 ebp = 0x0018d250 156 | # Found by: call frame info 157 | # 4 ETClient.exe!MachineListItem::qt_static_metacall(QObject *,QMetaObject::Call,int,void * *) [moc_machinelistitem.cpp : 115 + 0x8] 158 | # eip = 0x00de14a1 esp = 0x0018d258 ebp = 0x0018d27c 159 | # Found by: call frame info 160 | # 5 ETClient.exe!QMetaObject::activate(QObject *,int,int,void * *) + 0x4cf 161 | # eip = 0x0135329f esp = 0x0018d284 ebp = 0x00000000 162 | # Found by: call frame info 163 | # 6 ETClient.exe!QMetaObject::activate(QObject *,QMetaObject const *,int,void * *) + 0x1e 164 | # eip = 0x0135345e esp = 0x0018d304 ebp = 0x0018d32c 165 | # Found by: call frame info 166 | # 7 ETClient.exe!ToggleButtonWid::sig_buttonClicked() [moc_togglebuttonwid.cpp : 123 + 0x12] 167 | # eip = 0x00de2759 esp = 0x0018d318 ebp = 0x0018d32c 168 | # Found by: call frame info 169 | # 8 ETClient.exe!ToggleButtonWid::mousePressEvent(QMouseEvent *) [togglebuttonwid.cpp : 122 + 0x8] 170 | # eip = 0x00dcac14 esp = 0x0018d334 ebp = 0x0018d338 171 | # Found by: call frame info 172 | # 9 ETClient.exe!QWidget::event(QEvent *) + 0x89 173 | # eip = 0x00df4fa9 esp = 0x0018d340 ebp = 0x05821ae0 174 | # Found by: call frame info 175 | # 10 ETClient.exe!QFrame::event(QEvent *) + 0x20 176 | # eip = 0x00e17280 esp = 0x0018d40c ebp = 0x0018d7f8 177 | # Found by: call frame info 178 | # 11 ETClient.exe!QLabel::event(QEvent *) + 0xdf 179 | # eip = 0x00e0714f esp = 0x0018d420 ebp = 0x0018d7f8 180 | # Found by: call frame info 181 | # 12 ETClient.exe!QApplicationPrivate::notify_helper(QObject *,QEvent *) + 0x98 182 | # eip = 0x00e1de08 esp = 0x0018d434 ebp = 0x0033c6f8 183 | # Found by: call frame info 184 | # 13 ETClient.exe!QApplication::notify(QObject *,QEvent *) + 0x659 185 | # eip = 0x00e1c5e9 esp = 0x0018d448 ebp = 0x0018d7f8 186 | # Found by: call frame info 187 | # 14 ETClient.exe!WinMain + 0x21358 188 | # eip = 0x015bb068 esp = 0x0018d690 ebp = 0x0018d7f8 189 | # Found by: call frame info 190 | # rubocop:enable Metrics/LineLength 191 | def parse_thread_stacktrace(lines, thread_id, is_crashed_thread) 192 | # remove the 1st line 193 | lines.pop 194 | 195 | stacktrace = [] 196 | process_matching_lines(lines, BACKTRACE_LINE_REGEX) do |match| 197 | stacktrace << parse_crashed_thread_stacktrace_line(match, is_crashed_thread) 198 | end 199 | 200 | # sentry wants their stacktrace with the oldest frame first 201 | stacktrace = stacktrace.reverse 202 | 203 | if is_crashed_thread 204 | @crashed_thread_stacktrace = stacktrace 205 | @extra[:crashed_thread_id] = thread_id 206 | else 207 | @other_threads_stacktraces[thread_id] = stacktrace 208 | end 209 | end 210 | 211 | def parse_crashed_thread_stacktrace_line(match, is_crashed_thread) 212 | frame_nb = match[1] 213 | function = match[2] 214 | filename = match[4] || 'unknown' 215 | lineno = match[5] 216 | 217 | parse_culprit_and_message(function, match[3]) if is_crashed_thread && frame_nb.to_i == 0 218 | 219 | { 220 | filename: filename, 221 | function: function 222 | }.tap { |frame| frame[:lineno] = lineno.to_i if lineno } 223 | end 224 | 225 | def parse_culprit_and_message(function, file_name_and_lineno) 226 | @culprit = function 227 | 228 | message_tail = function 229 | message_tail += " (#{file_name_and_lineno})" if file_name_and_lineno 230 | 231 | if @message.nil? 232 | @message = message_tail 233 | else 234 | # already parsed the crash reason 235 | @message += " at #{message_tail}" 236 | end 237 | end 238 | 239 | MODULE_LINE_REGEX = /0x[0-9a-f]+\s+-\s+0x[0-9a-f]+\s+([^ ]+)\s+([^ ]+)/ 240 | 241 | # rubocop:disable Metrics/LineLength 242 | # Parses something of the form 243 | # Loaded modules: 244 | # 0x00d70000 - 0x01b39fff ETClient.exe ??? (main) 245 | # 0x67070000 - 0x671a8fff libeay32.dll 1.0.2.4 246 | # 0x71020000 - 0x71090fff msvcp120.dll 12.0.21005.1 247 | # 0x71370000 - 0x713bbfff ssleay32.dll 1.0.2.4 248 | # 0x72470000 - 0x724effff uxtheme.dll 6.1.7600.16385 249 | # 0x726e0000 - 0x726f2fff dwmapi.dll 6.1.7601.18917 250 | # 0x73160000 - 0x7316dfff RpcRtRemote.dll 6.1.7601.17514 251 | # 0x731e0000 - 0x7321afff rsaenh.dll 6.1.7600.16385 252 | # 0x73250000 - 0x7329efff webio.dll 6.1.7601.17725 253 | # 0x732a0000 - 0x732f7fff winhttp.dll 6.1.7601.17514 254 | # 0x73380000 - 0x73396fff cryptsp.dll 6.1.7601.18741 255 | # 0x733b0000 - 0x7349afff dbghelp.dll 6.1.7601.17514 256 | # 0x736e0000 - 0x736ecfff dhcpcsvc6.DLL 6.1.7601.17970 257 | # 0x736f0000 - 0x73701fff dhcpcsvc.dll 6.1.7600.16385 258 | # 0x73710000 - 0x73716fff winnsi.dll 6.1.7600.16385 259 | # 0x73720000 - 0x7373bfff IPHLPAPI.DLL 6.1.7601.17514 260 | # 0x73740000 - 0x73744fff WSHTCPIP.DLL 6.1.7600.16385 261 | # 0x73750000 - 0x7378bfff mswsock.dll 6.1.7601.18254 262 | # 0x73790000 - 0x737c1fff winmm.dll 6.1.7601.17514 263 | # 0x737d0000 - 0x737f4fff powrprof.dll 6.1.7600.16385 264 | # 0x73800000 - 0x73805fff wlanutil.dll 6.1.7600.16385 265 | # 0x73810000 - 0x73824fff d3d9.dll 4.3.22.0 266 | # 0x73850000 - 0x7393dfff msvcr120.dll 12.0.21005.1 267 | # 0x73cc0000 - 0x73cd5fff wlanapi.dll 6.1.7600.16385 268 | # 0x74d40000 - 0x74d47fff credssp.dll 6.1.7601.19110 269 | # 0x74d70000 - 0x74d7bfff CRYPTBASE.dll 6.1.7601.19110 270 | # 0x74d80000 - 0x74ddffff sspicli.dll 6.1.7601.19110 271 | # 0x74e80000 - 0x75acafff shell32.dll 6.1.7601.18952 272 | # 0x75b30000 - 0x75b39fff lpk.dll 6.1.7601.18985 273 | # 0x75b40000 - 0x75bebfff msvcrt.dll 7.0.7601.17744 274 | # 0x75bf0000 - 0x75c01fff devobj.dll 6.1.7601.17621 275 | # 0x75c10000 - 0x75c1bfff msasn1.dll 6.1.7601.17514 276 | # 0x75c80000 - 0x75cd6fff shlwapi.dll 6.1.7601.17514 277 | # 0x75d20000 - 0x75e2ffff kernel32.dll 6.1.7601.19110 (WARNING: No symbols, wkernel32.pdb, 5B44EEB548A94000AB2677B80472D6442) 278 | # 0x75e30000 - 0x75ed0fff advapi32.dll 6.1.7601.19091 279 | # 0x75ee0000 - 0x75f6efff oleaut32.dll 6.1.7601.18679 280 | # 0x75f70000 - 0x7605ffff rpcrt4.dll 6.1.7601.19110 281 | # 0x76090000 - 0x761b0fff crypt32.dll 6.1.7601.18839 282 | # 0x761e0000 - 0x7627cfff usp10.dll 1.626.7601.19054 283 | # 0x76280000 - 0x7630ffff gdi32.dll 6.1.7601.19091 284 | # 0x76310000 - 0x763dbfff msctf.dll 6.1.7601.18731 285 | # 0x763e0000 - 0x763e5fff nsi.dll 6.1.7600.16385 286 | # 0x76420000 - 0x7657bfff ole32.dll 6.1.7601.18915 (WARNING: No symbols, ole32.pdb, DE14A7E1B047414B826E606585170A892) 287 | # 0x76580000 - 0x7671cfff setupapi.dll 6.1.7601.17514 288 | # 0x76a80000 - 0x76ab4fff ws2_32.dll 6.1.7601.17514 289 | # 0x76ac0000 - 0x76acafff profapi.dll 6.1.7600.16385 290 | # 0x76ad0000 - 0x76ae8fff sechost.dll 6.1.7601.18869 291 | # 0x76af0000 - 0x76beffff user32.dll 6.1.7601.19061 (WARNING: No symbols, wuser32.pdb, 9B8CF996B7584AC1ADA9DB5CADE512922) 292 | # 0x76c80000 - 0x76cc6fff KERNELBASE.dll 6.1.7601.19110 (WARNING: No symbols, wkernelbase.pdb, A0567C574144432389F2D6539B69AA161) 293 | # 0x76f90000 - 0x76feffff imm32.dll 6.1.7601.17514 294 | # 0x76ff0000 - 0x77016fff cfgmgr32.dll 6.1.7601.17621 295 | # 0x77420000 - 0x7759ffff ntdll.dll 6.1.7601.19110 (WARNING: No symbols, wntdll.pdb, 992AA396746C4D548F7F497DD63B4EEC2) 296 | # rubocop:enable Metrics/LineLength 297 | def parse_loaded_modules(lines) 298 | # remove the 1st line 299 | lines.pop 300 | 301 | process_matching_lines(lines, MODULE_LINE_REGEX) do |match| 302 | name = match[1] 303 | version = match[2] 304 | 305 | @modules[name] = version 306 | end 307 | end 308 | 309 | def process_matching_lines(lines, regex, &_blk) 310 | loop do 311 | line = lines.pop 312 | line.strip! if line 313 | 314 | break if !line || line.empty? 315 | 316 | match = regex.match(line) 317 | yield(match) if match 318 | end 319 | end 320 | end 321 | end 322 | -------------------------------------------------------------------------------- /lib/sentry_breakpad/hash_helper.rb: -------------------------------------------------------------------------------- 1 | # Seemed somewhat silly to pull ActiveSupport as a dependency for just a couple 2 | # of methods... 3 | 4 | module SentryBreakpad 5 | class HashHelper 6 | class << self 7 | # merges hash2 into hash1, recursively 8 | def deep_merge!(hash1, hash2) 9 | hash2.each do |key, value| 10 | hash1[key] = if hash1.key?(key) && hash1[key].is_a?(Hash) && value.is_a?(Hash) 11 | deep_merge!(hash1[key], value) 12 | else 13 | value 14 | end 15 | end 16 | 17 | hash1 18 | end 19 | 20 | # recursively symbolizes a hash's keys 21 | def deep_symbolize_keys!(hash) 22 | hash.each_with_object({}) do |(key, value), new_hash| 23 | new_key = key.respond_to?(:to_sym) ? key.to_sym : key 24 | new_value = value.is_a?(Hash) ? deep_symbolize_keys!(value) : value 25 | new_hash[new_key] = new_value 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/sentry_breakpad/version.rb: -------------------------------------------------------------------------------- 1 | module SentryBreakpad 2 | VERSION = '0.1.10'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /sentry_breakpad.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'sentry_breakpad/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'sentry_breakpad' 8 | spec.version = SentryBreakpad::VERSION 9 | spec.authors = ['Jean Rouge'] 10 | spec.email = ['jer329@cornell.edu'] 11 | 12 | spec.summary = "Converts Google breakpad's reports into Sentry/Raven events" 13 | spec.homepage = 'https://github.com/wk8/sentry_breakpad' 14 | spec.license = 'MIT' 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.bindir = 'exe' 18 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_dependency 'sentry-raven', '~> 2.2.0' 22 | 23 | spec.add_development_dependency 'rspec' 24 | spec.add_development_dependency 'rubocop' 25 | end 26 | -------------------------------------------------------------------------------- /spec/breakpad_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryBreakpad::BreakpadParser do 4 | # rubocop:disable Metrics/LineLength 5 | let(:expected_hash) do 6 | { 7 | culprit: 'ETClient.exe!QString::~QString()', 8 | extra: { 9 | server: { 10 | os: { 11 | name: 'Windows NT', 12 | version: 'Windows NT - 6.1.7601 Service Pack 1' 13 | } 14 | }, 15 | crash_address: '0xfffffffffee1dead', 16 | crashed_thread_id: 0 17 | }, 18 | level: Raven::Event::LOG_LEVELS['fatal'], 19 | logger: 'breakpad', 20 | message: 'EXCEPTION_ACCESS_VIOLATION_READ at ETClient.exe!QString::~QString() (qstring.h : 992)', 21 | modules: { 22 | 'ETClient.exe' => '???', 23 | 'libeay32.dll' => '1.0.2.4', 24 | 'msvcp120.dll' => '12.0.21005.1', 25 | 'ssleay32.dll' => '1.0.2.4', 26 | 'uxtheme.dll' => '6.1.7600.16385', 27 | 'dwmapi.dll' => '6.1.7601.18917', 28 | 'RpcRtRemote.dll' => '6.1.7601.17514', 29 | 'rsaenh.dll' => '6.1.7600.16385', 30 | 'webio.dll' => '6.1.7601.17725', 31 | 'winhttp.dll' => '6.1.7601.17514', 32 | 'cryptsp.dll' => '6.1.7601.18741', 33 | 'dbghelp.dll' => '6.1.7601.17514', 34 | 'dhcpcsvc6.DLL' => '6.1.7601.17970', 35 | 'dhcpcsvc.dll' => '6.1.7600.16385', 36 | 'winnsi.dll' => '6.1.7600.16385', 37 | 'IPHLPAPI.DLL' => '6.1.7601.17514', 38 | 'WSHTCPIP.DLL' => '6.1.7600.16385', 39 | 'mswsock.dll' => '6.1.7601.18254', 40 | 'winmm.dll' => '6.1.7601.17514', 41 | 'powrprof.dll' => '6.1.7600.16385', 42 | 'wlanutil.dll' => '6.1.7600.16385', 43 | 'd3d9.dll' => '4.3.22.0', 44 | 'msvcr120.dll' => '12.0.21005.1', 45 | 'wlanapi.dll' => '6.1.7600.16385', 46 | 'credssp.dll' => '6.1.7601.19110', 47 | 'CRYPTBASE.dll' => '6.1.7601.19110', 48 | 'sspicli.dll' => '6.1.7601.19110', 49 | 'shell32.dll' => '6.1.7601.18952', 50 | 'lpk.dll' => '6.1.7601.18985', 51 | 'msvcrt.dll' => '7.0.7601.17744', 52 | 'devobj.dll' => '6.1.7601.17621', 53 | 'msasn1.dll' => '6.1.7601.17514', 54 | 'shlwapi.dll' => '6.1.7601.17514', 55 | 'kernel32.dll' => '6.1.7601.19110', 56 | 'advapi32.dll' => '6.1.7601.19091', 57 | 'oleaut32.dll' => '6.1.7601.18679', 58 | 'rpcrt4.dll' => '6.1.7601.19110', 59 | 'crypt32.dll' => '6.1.7601.18839', 60 | 'usp10.dll' => '1.626.7601.19054', 61 | 'gdi32.dll' => '6.1.7601.19091', 62 | 'msctf.dll' => '6.1.7601.18731', 63 | 'nsi.dll' => '6.1.7600.16385', 64 | 'ole32.dll' => '6.1.7601.18915', 65 | 'setupapi.dll' => '6.1.7601.17514', 66 | 'ws2_32.dll' => '6.1.7601.17514', 67 | 'profapi.dll' => '6.1.7600.16385', 68 | 'sechost.dll' => '6.1.7601.18869', 69 | 'user32.dll' => '6.1.7601.19061', 70 | 'KERNELBASE.dll' => '6.1.7601.19110', 71 | 'imm32.dll' => '6.1.7601.17514', 72 | 'cfgmgr32.dll' => '6.1.7601.17621', 73 | 'ntdll.dll' => '6.1.7601.19110' 74 | }, 75 | stacktrace: { 76 | frames: [ 77 | { filename: 'unknown', function: 'ETClient.exe!WinMain' }, 78 | { filename: 'unknown', function: 'ETClient.exe!QApplication::notify(QObject *,QEvent *)' }, 79 | { filename: 'unknown', function: 'ETClient.exe!QApplicationPrivate::notify_helper(QObject *,QEvent *)' }, 80 | { filename: 'unknown', function: 'ETClient.exe!QLabel::event(QEvent *)' }, 81 | { filename: 'unknown', function: 'ETClient.exe!QFrame::event(QEvent *)' }, 82 | { filename: 'unknown', function: 'ETClient.exe!QWidget::event(QEvent *)' }, 83 | { filename: 'togglebuttonwid.cpp', function: 'ETClient.exe!ToggleButtonWid::mousePressEvent(QMouseEvent *)', lineno: 122 }, 84 | { filename: 'moc_togglebuttonwid.cpp', function: 'ETClient.exe!ToggleButtonWid::sig_buttonClicked()', lineno: 123 }, 85 | { filename: 'unknown', function: 'ETClient.exe!QMetaObject::activate(QObject *,QMetaObject const *,int,void * *)' }, 86 | { filename: 'unknown', function: 'ETClient.exe!QMetaObject::activate(QObject *,int,int,void * *)' }, 87 | { filename: 'moc_machinelistitem.cpp', function: 'ETClient.exe!MachineListItem::qt_static_metacall(QObject *,QMetaObject::Call,int,void * *)', lineno: 115 }, 88 | { filename: 'machinelistitem.cpp', function: 'ETClient.exe!MachineListItem::on_EditButtonClicked()', lineno: 197 }, 89 | { filename: 'machinelistitem.cpp', function: 'ETClient.exe!buggyFunc()', lineno: 181 }, 90 | { filename: 'unknown', function: "ETClient.exe!QString::`scalar deleting destructor'(unsigned int)" }, 91 | { filename: 'qstring.h', function: 'ETClient.exe!QString::~QString()', lineno: 992 } 92 | ] 93 | }, 94 | tags: { 95 | os: 'Windows NT', 96 | os_full: 'Windows NT - 6.1.7601 Service Pack 1', 97 | cpu: 'x86', 98 | cpu_full: 'x86 - GenuineIntel family 6 model 70 stepping 1 - 4 CPUs' 99 | } 100 | } 101 | end 102 | # rubocop:enable Metrics/LineLength 103 | 104 | describe '#raven_event' do 105 | let(:breakpad_report_content) { read_fixture('breakpad_report') } 106 | 107 | let(:parser) { described_class.new(breakpad_report_content) } 108 | 109 | it 'returns a Raven event ready to be submitted' do 110 | expect(parser.raven_event.to_hash).to include(expected_hash) 111 | end 112 | 113 | context 'when passed some extra info' do 114 | let(:extra_info) do 115 | { 116 | 'release' => '1.2.3', 117 | 'extra' => { 118 | 'started_at_utc' => '2016-12-27T01:21:22+00:00', 119 | 'client_logs' => "log line 1\nlog line 2\nlogline 3", 120 | 'daemon_logs' => "log line 4\nlog line 5\nlogline 6" 121 | }, 122 | 'tags' => { 123 | 'app_name' => 'client', 124 | 'build_type' => 'release', 125 | 'client_id' => 12, 126 | 'user_id' => 28 127 | } 128 | } 129 | end 130 | 131 | it 'includes it in the event' do 132 | sym_keys_extra_info = SentryBreakpad::HashHelper.deep_symbolize_keys!(extra_info) 133 | expected_hash_with_extra_info = SentryBreakpad::HashHelper.deep_merge!(expected_hash, 134 | sym_keys_extra_info) 135 | expect(parser.raven_event(extra_info).to_hash).to include(expected_hash_with_extra_info) 136 | end 137 | end 138 | end 139 | 140 | describe '.from_file' do 141 | it 'builds a Raven event from a file path' do 142 | parser = described_class.from_file(fixture_path('breakpad_report')) 143 | 144 | expect(parser.raven_event.to_hash).to include(expected_hash) 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /spec/fixtures/breakpad_report: -------------------------------------------------------------------------------- 1 | Operating system: Windows NT 2 | 6.1.7601 Service Pack 1 3 | CPU: x86 4 | GenuineIntel family 6 model 70 stepping 1 5 | 4 CPUs 6 | 7 | Crash reason: EXCEPTION_ACCESS_VIOLATION_READ 8 | Crash address: 0xfffffffffee1dead 9 | 10 | Thread 0 (crashed) 11 | 0 ETClient.exe!QString::~QString() [qstring.h : 992 + 0xa] 12 | eip = 0x00d74e3a esp = 0x0018d1b4 ebp = 0x0018d1b8 ebx = 0x01ad947c 13 | esi = 0x05850000 edi = 0x058277f8 eax = 0xfee1dead ecx = 0xfee1dead 14 | edx = 0x00000004 efl = 0x00210282 15 | Found by: given as instruction pointer in context 16 | 1 ETClient.exe!QString::`scalar deleting destructor'(unsigned int) + 0xf 17 | eip = 0x00d9161f esp = 0x0018d1c0 ebp = 0x0018d1c4 18 | Found by: call frame info 19 | 2 ETClient.exe!buggyFunc() [machinelistitem.cpp : 181 + 0x1d] 20 | eip = 0x00daaea3 esp = 0x0018d1cc ebp = 0x0018d1dc 21 | Found by: call frame info 22 | 3 ETClient.exe!MachineListItem::on_EditButtonClicked() [machinelistitem.cpp : 197 + 0x5] 23 | eip = 0x00da5dd4 esp = 0x0018d1e4 ebp = 0x0018d250 24 | Found by: call frame info 25 | 4 ETClient.exe!MachineListItem::qt_static_metacall(QObject *,QMetaObject::Call,int,void * *) [moc_machinelistitem.cpp : 115 + 0x8] 26 | eip = 0x00de14a1 esp = 0x0018d258 ebp = 0x0018d27c 27 | Found by: call frame info 28 | 5 ETClient.exe!QMetaObject::activate(QObject *,int,int,void * *) + 0x4cf 29 | eip = 0x0135329f esp = 0x0018d284 ebp = 0x00000000 30 | Found by: call frame info 31 | 6 ETClient.exe!QMetaObject::activate(QObject *,QMetaObject const *,int,void * *) + 0x1e 32 | eip = 0x0135345e esp = 0x0018d304 ebp = 0x0018d32c 33 | Found by: call frame info 34 | 7 ETClient.exe!ToggleButtonWid::sig_buttonClicked() [moc_togglebuttonwid.cpp : 123 + 0x12] 35 | eip = 0x00de2759 esp = 0x0018d318 ebp = 0x0018d32c 36 | Found by: call frame info 37 | 8 ETClient.exe!ToggleButtonWid::mousePressEvent(QMouseEvent *) [togglebuttonwid.cpp : 122 + 0x8] 38 | eip = 0x00dcac14 esp = 0x0018d334 ebp = 0x0018d338 39 | Found by: call frame info 40 | 9 ETClient.exe!QWidget::event(QEvent *) + 0x89 41 | eip = 0x00df4fa9 esp = 0x0018d340 ebp = 0x05821ae0 42 | Found by: call frame info 43 | 10 ETClient.exe!QFrame::event(QEvent *) + 0x20 44 | eip = 0x00e17280 esp = 0x0018d40c ebp = 0x0018d7f8 45 | Found by: call frame info 46 | 11 ETClient.exe!QLabel::event(QEvent *) + 0xdf 47 | eip = 0x00e0714f esp = 0x0018d420 ebp = 0x0018d7f8 48 | Found by: call frame info 49 | 12 ETClient.exe!QApplicationPrivate::notify_helper(QObject *,QEvent *) + 0x98 50 | eip = 0x00e1de08 esp = 0x0018d434 ebp = 0x0033c6f8 51 | Found by: call frame info 52 | 13 ETClient.exe!QApplication::notify(QObject *,QEvent *) + 0x659 53 | eip = 0x00e1c5e9 esp = 0x0018d448 ebp = 0x0018d7f8 54 | Found by: call frame info 55 | 14 ETClient.exe!WinMain + 0x21358 56 | eip = 0x015bb068 esp = 0x0018d690 ebp = 0x0018d7f8 57 | Found by: call frame info 58 | 59 | Thread 1 60 | 0 ntdll.dll + 0x2019d 61 | eip = 0x7744019d esp = 0x03bffb64 ebp = 0x03bffcf8 ebx = 0x7745c4f8 62 | esi = 0x003d9148 edi = 0x00000000 eax = 0x00000000 ecx = 0x00000000 63 | edx = 0x00000000 efl = 0x00000246 64 | Found by: given as instruction pointer in context 65 | 1 kernel32.dll + 0x1338a 66 | eip = 0x75d3338a esp = 0x03bffd00 ebp = 0x03bffd04 67 | Found by: previous frame's frame pointer 68 | 2 ntdll.dll + 0x39882 69 | eip = 0x77459882 esp = 0x03bffd0c ebp = 0x03bffd44 70 | Found by: previous frame's frame pointer 71 | 3 ntdll.dll + 0x39855 72 | eip = 0x77459855 esp = 0x03bffd4c ebp = 0x03bffd5c 73 | Found by: previous frame's frame pointer 74 | 75 | Thread 2 76 | 0 ntdll.dll + 0x21f86 77 | eip = 0x77441f86 esp = 0x00d4f770 ebp = 0x00d4f8d0 ebx = 0x00010003 78 | esi = 0x00000002 edi = 0x003dc490 eax = 0x00000001 ecx = 0x00000000 79 | edx = 0x00000000 efl = 0x00000246 80 | Found by: given as instruction pointer in context 81 | 1 kernel32.dll + 0x1338a 82 | eip = 0x75d3338a esp = 0x00d4f8d8 ebp = 0x00d4f8dc 83 | Found by: previous frame's frame pointer 84 | 2 ntdll.dll + 0x39882 85 | eip = 0x77459882 esp = 0x00d4f8e4 ebp = 0x00d4f91c 86 | Found by: previous frame's frame pointer 87 | 3 ntdll.dll + 0x39855 88 | eip = 0x77459855 esp = 0x00d4f924 ebp = 0x00d4f934 89 | Found by: previous frame's frame pointer 90 | 91 | Thread 3 92 | 0 ntdll.dll + 0x21f86 93 | eip = 0x77441f86 esp = 0x03e2f65c ebp = 0x03e2f7bc ebx = 0x00000004 94 | esi = 0x00000002 edi = 0x003dc490 eax = 0x00000002 ecx = 0x00000000 95 | edx = 0x00000000 efl = 0x00000246 96 | Found by: given as instruction pointer in context 97 | 1 kernel32.dll + 0x1338a 98 | eip = 0x75d3338a esp = 0x03e2f7c4 ebp = 0x03e2f7c8 99 | Found by: previous frame's frame pointer 100 | 2 ntdll.dll + 0x39882 101 | eip = 0x77459882 esp = 0x03e2f7d0 ebp = 0x03e2f808 102 | Found by: previous frame's frame pointer 103 | 3 ntdll.dll + 0x39855 104 | eip = 0x77459855 esp = 0x03e2f810 ebp = 0x03e2f820 105 | Found by: previous frame's frame pointer 106 | 107 | Thread 4 108 | 0 ntdll.dll + 0x1fdd1 109 | eip = 0x7743fdd1 esp = 0x0351fca4 ebp = 0x0351fd0c ebx = 0x00007530 110 | esi = 0x0351fce8 edi = 0x00000000 eax = 0x00000000 ecx = 0x00000000 111 | edx = 0x00000000 efl = 0x00000246 112 | Found by: given as instruction pointer in context 113 | 1 KERNELBASE.dll + 0x144ab 114 | eip = 0x76c944ab esp = 0x0351fd14 ebp = 0x0351fd1c 115 | Found by: previous frame's frame pointer 116 | 2 ole32.dll + 0x2d97d 117 | eip = 0x7644d97d esp = 0x0351fd24 ebp = 0x0351fd44 118 | Found by: previous frame's frame pointer 119 | 3 ole32.dll + 0x2d86a 120 | eip = 0x7644d86a esp = 0x0351fd4c ebp = 0x0351fd54 121 | Found by: previous frame's frame pointer 122 | 4 kernel32.dll + 0x1338a 123 | eip = 0x75d3338a esp = 0x0351fd5c ebp = 0x0351fd60 124 | Found by: previous frame's frame pointer 125 | 5 ntdll.dll + 0x39882 126 | eip = 0x77459882 esp = 0x0351fd68 ebp = 0x0351fda0 127 | Found by: previous frame's frame pointer 128 | 6 ntdll.dll + 0x39855 129 | eip = 0x77459855 esp = 0x0351fda8 ebp = 0x0351fdb8 130 | Found by: previous frame's frame pointer 131 | 132 | Thread 5 133 | 0 ntdll.dll + 0x2019d 134 | eip = 0x7744019d esp = 0x04c6f894 ebp = 0x04c6f93c ebx = 0x00000000 135 | esi = 0x00000000 edi = 0x00000000 eax = 0x00000000 ecx = 0x00000000 136 | edx = 0x00000000 efl = 0x00000246 137 | Found by: given as instruction pointer in context 138 | 1 kernel32.dll + 0x1338a 139 | eip = 0x75d3338a esp = 0x04c6f944 ebp = 0x04c6f948 140 | Found by: previous frame's frame pointer 141 | 2 ntdll.dll + 0x39882 142 | eip = 0x77459882 esp = 0x04c6f950 ebp = 0x04c6f988 143 | Found by: previous frame's frame pointer 144 | 3 ntdll.dll + 0x39855 145 | eip = 0x77459855 esp = 0x04c6f990 ebp = 0x04c6f9a0 146 | Found by: previous frame's frame pointer 147 | 148 | Thread 6 149 | 0 ntdll.dll + 0x2019d 150 | eip = 0x7744019d esp = 0x04e7dac8 ebp = 0x04e7db64 ebx = 0x04e7db18 151 | esi = 0x00000001 edi = 0x00000000 eax = 0x000001ac ecx = 0x00000000 152 | edx = 0x00000000 efl = 0x00000246 153 | Found by: given as instruction pointer in context 154 | 1 kernel32.dll + 0x11a0c 155 | eip = 0x75d31a0c esp = 0x04e7db6c ebp = 0x04e7dbac 156 | Found by: previous frame's frame pointer 157 | 2 user32.dll + 0x2087a 158 | eip = 0x76b1087a esp = 0x04e7dbb4 ebp = 0x04e7dc00 159 | Found by: previous frame's frame pointer 160 | 3 ETClient.exe!QEventDispatcherWin32::processEvents(QFlags) + 0x623 161 | eip = 0x013f4f93 esp = 0x04e7dc08 ebp = 0x003d54d0 162 | Found by: previous frame's frame pointer 163 | 4 ETClient.exe!WinMain + 0x2862c 164 | eip = 0x015c233c esp = 0x04e7f9bc ebp = 0x04e7f8b4 165 | Found by: call frame info 166 | 167 | Thread 7 168 | 0 ntdll.dll + 0x21f86 169 | eip = 0x77441f86 esp = 0x04fcf838 ebp = 0x04fcf998 ebx = 0x049ba5b0 170 | esi = 0x00000002 edi = 0x049ba5b0 eax = 0x00000000 ecx = 0x00000000 171 | edx = 0x00000000 efl = 0x00000246 172 | Found by: given as instruction pointer in context 173 | 1 kernel32.dll + 0x1338a 174 | eip = 0x75d3338a esp = 0x04fcf9a0 ebp = 0x04fcf9a4 175 | Found by: previous frame's frame pointer 176 | 2 ntdll.dll + 0x39882 177 | eip = 0x77459882 esp = 0x04fcf9ac ebp = 0x04fcf9e4 178 | Found by: previous frame's frame pointer 179 | 3 ntdll.dll + 0x39855 180 | eip = 0x77459855 esp = 0x04fcf9ec ebp = 0x04fcf9fc 181 | Found by: previous frame's frame pointer 182 | 183 | Thread 8 184 | 0 ntdll.dll + 0x21f86 185 | eip = 0x77441f86 esp = 0x053cf6d4 ebp = 0x053cf834 ebx = 0x00010003 186 | esi = 0x00000002 edi = 0x003dd120 eax = 0x00000003 ecx = 0x00000000 187 | edx = 0x00000000 efl = 0x00000246 188 | Found by: given as instruction pointer in context 189 | 1 kernel32.dll + 0x1338a 190 | eip = 0x75d3338a esp = 0x053cf83c ebp = 0x053cf840 191 | Found by: previous frame's frame pointer 192 | 2 ntdll.dll + 0x39882 193 | eip = 0x77459882 esp = 0x053cf848 ebp = 0x053cf880 194 | Found by: previous frame's frame pointer 195 | 3 ntdll.dll + 0x39855 196 | eip = 0x77459855 esp = 0x053cf888 ebp = 0x053cf898 197 | Found by: previous frame's frame pointer 198 | 199 | Thread 9 200 | 0 ntdll.dll + 0x21f86 201 | eip = 0x77441f86 esp = 0x0555fadc ebp = 0x0555fc3c ebx = 0x00000004 202 | esi = 0x00000002 edi = 0x003dd120 eax = 0x00000001 ecx = 0x00000000 203 | edx = 0x00000000 efl = 0x00000246 204 | Found by: given as instruction pointer in context 205 | 1 kernel32.dll + 0x1338a 206 | eip = 0x75d3338a esp = 0x0555fc44 ebp = 0x0555fc48 207 | Found by: previous frame's frame pointer 208 | 2 ntdll.dll + 0x39882 209 | eip = 0x77459882 esp = 0x0555fc50 ebp = 0x0555fc88 210 | Found by: previous frame's frame pointer 211 | 3 ntdll.dll + 0x39855 212 | eip = 0x77459855 esp = 0x0555fc90 ebp = 0x0555fca0 213 | Found by: previous frame's frame pointer 214 | 215 | Thread 10 216 | 0 ntdll.dll + 0x21f86 217 | eip = 0x77441f86 esp = 0x056bf8b0 ebp = 0x056bfa10 ebx = 0x00000003 218 | esi = 0x00000002 edi = 0x003dc490 eax = 0x00000001 ecx = 0x00000000 219 | edx = 0x00000000 efl = 0x00000246 220 | Found by: given as instruction pointer in context 221 | 1 kernel32.dll + 0x1338a 222 | eip = 0x75d3338a esp = 0x056bfa18 ebp = 0x056bfa1c 223 | Found by: previous frame's frame pointer 224 | 2 ntdll.dll + 0x39882 225 | eip = 0x77459882 esp = 0x056bfa24 ebp = 0x056bfa5c 226 | Found by: previous frame's frame pointer 227 | 3 ntdll.dll + 0x39855 228 | eip = 0x77459855 esp = 0x056bfa64 ebp = 0x056bfa74 229 | Found by: previous frame's frame pointer 230 | 231 | Thread 11 232 | 0 ntdll.dll + 0x1f999 233 | eip = 0x7743f999 esp = 0x052cf9b8 ebp = 0x052cf9e4 ebx = 0x00000000 234 | esi = 0x76b13bba edi = 0x00000400 eax = 0x00000001 ecx = 0x00000000 235 | edx = 0x00000000 efl = 0x00000246 236 | Found by: given as instruction pointer in context 237 | 1 kernel32.dll + 0x1338a 238 | eip = 0x75d3338a esp = 0x052cf9ec ebp = 0x052cf9f0 239 | Found by: previous frame's frame pointer 240 | 2 ntdll.dll + 0x39882 241 | eip = 0x77459882 esp = 0x052cf9f8 ebp = 0x052cfa30 242 | Found by: previous frame's frame pointer 243 | 3 ntdll.dll + 0x39855 244 | eip = 0x77459855 esp = 0x052cfa38 ebp = 0x052cfa48 245 | Found by: previous frame's frame pointer 246 | 247 | Thread 12 248 | 0 ntdll.dll + 0x2019d 249 | eip = 0x7744019d esp = 0x062ed968 ebp = 0x062eda04 ebx = 0x062ed9b8 250 | esi = 0x00000001 edi = 0x00000000 eax = 0x00000230 ecx = 0x00000000 251 | edx = 0x00000000 efl = 0x00000246 252 | Found by: given as instruction pointer in context 253 | 1 kernel32.dll + 0x11a0c 254 | eip = 0x75d31a0c esp = 0x062eda0c ebp = 0x062eda4c 255 | Found by: previous frame's frame pointer 256 | 2 user32.dll + 0x2087a 257 | eip = 0x76b1087a esp = 0x062eda54 ebp = 0x062edaa0 258 | Found by: previous frame's frame pointer 259 | 3 ETClient.exe!QEventDispatcherWin32::processEvents(QFlags) + 0x623 260 | eip = 0x013f4f93 esp = 0x062edaa8 ebp = 0x003d5710 261 | Found by: previous frame's frame pointer 262 | 4 ETClient.exe!WinMain + 0x2862c 263 | eip = 0x015c233c esp = 0x062ef85c ebp = 0x062ef754 264 | Found by: call frame info 265 | 266 | Loaded modules: 267 | 0x00d70000 - 0x01b39fff ETClient.exe ??? (main) 268 | 0x67070000 - 0x671a8fff libeay32.dll 1.0.2.4 269 | 0x71020000 - 0x71090fff msvcp120.dll 12.0.21005.1 270 | 0x71370000 - 0x713bbfff ssleay32.dll 1.0.2.4 271 | 0x72470000 - 0x724effff uxtheme.dll 6.1.7600.16385 272 | 0x726e0000 - 0x726f2fff dwmapi.dll 6.1.7601.18917 273 | 0x73160000 - 0x7316dfff RpcRtRemote.dll 6.1.7601.17514 274 | 0x731e0000 - 0x7321afff rsaenh.dll 6.1.7600.16385 275 | 0x73250000 - 0x7329efff webio.dll 6.1.7601.17725 276 | 0x732a0000 - 0x732f7fff winhttp.dll 6.1.7601.17514 277 | 0x73380000 - 0x73396fff cryptsp.dll 6.1.7601.18741 278 | 0x733b0000 - 0x7349afff dbghelp.dll 6.1.7601.17514 279 | 0x736e0000 - 0x736ecfff dhcpcsvc6.DLL 6.1.7601.17970 280 | 0x736f0000 - 0x73701fff dhcpcsvc.dll 6.1.7600.16385 281 | 0x73710000 - 0x73716fff winnsi.dll 6.1.7600.16385 282 | 0x73720000 - 0x7373bfff IPHLPAPI.DLL 6.1.7601.17514 283 | 0x73740000 - 0x73744fff WSHTCPIP.DLL 6.1.7600.16385 284 | 0x73750000 - 0x7378bfff mswsock.dll 6.1.7601.18254 285 | 0x73790000 - 0x737c1fff winmm.dll 6.1.7601.17514 286 | 0x737d0000 - 0x737f4fff powrprof.dll 6.1.7600.16385 287 | 0x73800000 - 0x73805fff wlanutil.dll 6.1.7600.16385 288 | 0x73810000 - 0x73824fff d3d9.dll 4.3.22.0 289 | 0x73850000 - 0x7393dfff msvcr120.dll 12.0.21005.1 290 | 0x73cc0000 - 0x73cd5fff wlanapi.dll 6.1.7600.16385 291 | 0x74d40000 - 0x74d47fff credssp.dll 6.1.7601.19110 292 | 0x74d70000 - 0x74d7bfff CRYPTBASE.dll 6.1.7601.19110 293 | 0x74d80000 - 0x74ddffff sspicli.dll 6.1.7601.19110 294 | 0x74e80000 - 0x75acafff shell32.dll 6.1.7601.18952 295 | 0x75b30000 - 0x75b39fff lpk.dll 6.1.7601.18985 296 | 0x75b40000 - 0x75bebfff msvcrt.dll 7.0.7601.17744 297 | 0x75bf0000 - 0x75c01fff devobj.dll 6.1.7601.17621 298 | 0x75c10000 - 0x75c1bfff msasn1.dll 6.1.7601.17514 299 | 0x75c80000 - 0x75cd6fff shlwapi.dll 6.1.7601.17514 300 | 0x75d20000 - 0x75e2ffff kernel32.dll 6.1.7601.19110 (WARNING: No symbols, wkernel32.pdb, 5B44EEB548A94000AB2677B80472D6442) 301 | 0x75e30000 - 0x75ed0fff advapi32.dll 6.1.7601.19091 302 | 0x75ee0000 - 0x75f6efff oleaut32.dll 6.1.7601.18679 303 | 0x75f70000 - 0x7605ffff rpcrt4.dll 6.1.7601.19110 304 | 0x76090000 - 0x761b0fff crypt32.dll 6.1.7601.18839 305 | 0x761e0000 - 0x7627cfff usp10.dll 1.626.7601.19054 306 | 0x76280000 - 0x7630ffff gdi32.dll 6.1.7601.19091 307 | 0x76310000 - 0x763dbfff msctf.dll 6.1.7601.18731 308 | 0x763e0000 - 0x763e5fff nsi.dll 6.1.7600.16385 309 | 0x76420000 - 0x7657bfff ole32.dll 6.1.7601.18915 (WARNING: No symbols, ole32.pdb, DE14A7E1B047414B826E606585170A892) 310 | 0x76580000 - 0x7671cfff setupapi.dll 6.1.7601.17514 311 | 0x76a80000 - 0x76ab4fff ws2_32.dll 6.1.7601.17514 312 | 0x76ac0000 - 0x76acafff profapi.dll 6.1.7600.16385 313 | 0x76ad0000 - 0x76ae8fff sechost.dll 6.1.7601.18869 314 | 0x76af0000 - 0x76beffff user32.dll 6.1.7601.19061 (WARNING: No symbols, wuser32.pdb, 9B8CF996B7584AC1ADA9DB5CADE512922) 315 | 0x76c80000 - 0x76cc6fff KERNELBASE.dll 6.1.7601.19110 (WARNING: No symbols, wkernelbase.pdb, A0567C574144432389F2D6539B69AA161) 316 | 0x76f90000 - 0x76feffff imm32.dll 6.1.7601.17514 317 | 0x76ff0000 - 0x77016fff cfgmgr32.dll 6.1.7601.17621 318 | 0x77420000 - 0x7759ffff ntdll.dll 6.1.7601.19110 (WARNING: No symbols, wntdll.pdb, 992AA396746C4D548F7F497DD63B4EEC2) 319 | -------------------------------------------------------------------------------- /spec/hash_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryBreakpad::HashHelper do 4 | describe '.deep_merge!' do 5 | it 'performs a deep merge' do 6 | hash1 = { a1: { b1: { c1: 2 }, b2: 12, b3: { c2: 42 } }, a2: { b4: 76 } } 7 | hash2 = { a1: { b1: { c1: 3 }, b3: { c3: 56 } }, a2: { b4: 7 } } 8 | 9 | expected_merged_hash = { a1: { b1: { c1: 3 }, b2: 12, b3: { c2: 42, c3: 56 } }, 10 | a2: { b4: 7 } } 11 | expect(described_class.deep_merge!(hash1, hash2)).to eq(expected_merged_hash) 12 | end 13 | end 14 | 15 | describe '.deep_symbolize_keys!' do 16 | it "recursively symbolizes a hash's keys" do 17 | hash = { 'coucou' => 'toi', "t'as" => { de: { beaux: 'yeux' } }, 'tu' => :sais } 18 | 19 | expected_result = { coucou: 'toi', :"t'as" => { de: { beaux: 'yeux' } }, tu: :sais } 20 | expect(described_class.deep_symbolize_keys!(hash)).to eq(expected_result) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/sentry_breakpad_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe SentryBreakpad do 4 | it 'has a version number' do 5 | expect(SentryBreakpad::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'sentry_breakpad' 3 | 4 | def fixtures_dir 5 | File.expand_path('../fixtures', __FILE__) 6 | end 7 | 8 | def fixture_path(fixture_name) 9 | File.join(fixtures_dir, fixture_name) 10 | end 11 | 12 | def read_fixture(fixture_name) 13 | File.read(fixture_path(fixture_name)) 14 | end 15 | --------------------------------------------------------------------------------