├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── CODE_OF_CONDUCT.md
├── Impact.podspec
├── Impact.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── Impact.xcscheme
│ └── ImpactTestMac.xcscheme
├── Impact
├── DWARF
│ ├── ImpactDWARF.c
│ ├── ImpactDWARF.h
│ ├── ImpactDWARFCFIInstructions.c
│ ├── ImpactDWARFCFIInstructions.h
│ ├── ImpactDWARFDefines.h
│ ├── ImpactDWARFParser.c
│ ├── ImpactDWARFParser.h
│ ├── ImpactDataCursor.c
│ ├── ImpactDataCursor.h
│ └── ImpactLEB.h
├── Impact.h
├── Impact.xcconfig
├── ImpactBinaryImage.c
├── ImpactBinaryImage.h
├── ImpactMonitor.h
├── ImpactMonitor.m
├── ImpactState.c
├── ImpactState.h
├── ImpactThread.c
├── ImpactThread.h
├── Info.plist
├── Monitoring
│ ├── ImpactCrashHandler.c
│ ├── ImpactCrashHandler.h
│ ├── ImpactMachException.c
│ ├── ImpactMachException.h
│ ├── ImpactMonitoredApplication.h
│ ├── ImpactMonitoredApplication.m
│ ├── ImpactRuntimeException.h
│ ├── ImpactRuntimeException.mm
│ ├── ImpactSignal.c
│ └── ImpactSignal.h
├── Unwind
│ ├── ImpactCompactUnwind.c
│ ├── ImpactCompactUnwind.h
│ ├── ImpactUnwind.c
│ ├── ImpactUnwind.h
│ ├── ImpactUnwind_arm.c
│ ├── ImpactUnwind_arm64.c
│ ├── ImpactUnwind_i386.c
│ └── ImpactUnwind_x86_64.c
├── Utility
│ ├── ImpactCPU.c
│ ├── ImpactCPU.h
│ ├── ImpactDebug.h
│ ├── ImpactLog.h
│ ├── ImpactLog.m
│ ├── ImpactPointer.h
│ ├── ImpactResult.h
│ ├── ImpactUtility.c
│ └── ImpactUtility.h
└── include
│ ├── ImpactMonitor.h
│ └── ImpactMonitoredApplication.h
├── ImpactTestMac
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── MainMenu.xib
├── Crashes
│ ├── CPPException.h
│ ├── CPPException.mm
│ ├── CrashInvocation.h
│ ├── CrashInvocation.m
│ ├── InvokeAbort.h
│ ├── InvokeAbort.m
│ ├── NonMainThreadUncaughtNSException.h
│ ├── NonMainThreadUncaughtNSException.m
│ ├── NullDereference.h
│ ├── NullDereference.m
│ ├── SubprocessCrash.h
│ ├── SubprocessCrash.m
│ ├── UncaughtNSException.h
│ └── UncaughtNSException.m
├── ImpactTestMac-Bridging-Header.h
├── ImpactTestMac.entitlements
├── ImpactTestMac.xcconfig
├── Info.plist
└── ViewController.swift
├── ImpactTestiOS
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── LaunchScreen.storyboard
├── ContentView.swift
├── Info.plist
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
└── SceneDelegate.swift
├── ImpactTests
├── ImpactCompactUnwindTests.m
├── ImpactCrashHelper.h
├── ImpactCrashHelper.m
├── ImpactCrashTests.swift
├── ImpactDWARFCFITests.m
├── ImpactLogTests.m
├── ImpactTests-Bridging-Header.h
├── Info.plist
└── Resources
│ └── unwind
│ ├── libobjc.A.dylib_14_4_4_18E226.gz
│ ├── libobjc_10_14_4_18E226.eh_frame.i386.bin
│ ├── libobjc_10_14_4_18E226.eh_frame.x86_64.bin
│ ├── libobjc_10_14_4_18E226.unwind_info.i386.bin
│ └── libobjc_10_14_4_18E226.unwind_info.x86_64.bin
├── LICENSE
├── Package.swift
├── README.md
└── crasher
└── main.swift
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | test:
7 | name: Test on destination ${{ matrix.destination }}
8 | runs-on: macOS-latest
9 | strategy:
10 | matrix:
11 | destination: ["platform=macOS"]
12 |
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: Use platform ${{ matrix.destination }}
16 | run: |
17 | git submodule update --init
18 | xcodebuild test -scheme Impact -destination "${{ matrix.destination }}"
19 | - name: SPM
20 | run: swift build
21 | - name: Carthage
22 | run: carthage build --no-skip-current --use-xcframeworks
23 | - name: CocoaPods
24 | run: pod lib lint
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /Carthage
5 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "xcconfigs"]
2 | path = xcconfigs
3 | url = https://github.com/mattmassicotte/xcconfigs
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at support@chimehq.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
--------------------------------------------------------------------------------
/Impact.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'Impact'
3 | s.version = '0.4.0'
4 | s.summary = 'crash capturing library for Apple platforms'
5 |
6 | s.homepage = 'https://github.com/stacksift/Impact'
7 | s.license = { :type => 'BSD-3-Clause', :file => 'LICENSE' }
8 | s.author = { 'Stacksift' => 'support@stacksift.io' }
9 | s.social_media_url = 'https://twitter.com/stacksift'
10 |
11 | s.source = { :git => 'https://github.com/stacksift/Impact.git', :tag => s.version }
12 |
13 | s.module_name = "Impact"
14 | s.source_files = 'Impact/**/*.{c,h,mm,m}'
15 | s.public_header_files = 'Impact/Impact.h', 'Impact/ImpactMonitor.h', 'Impact/Monitoring/ImpactMonitoredApplication.h'
16 |
17 | s.compiler_flags = '-DCURRENT_PROJECT_VERSION=13', '-I Impact', '-I Impact/Utility', '-I Impact/DWARF', '-I Impact/Unwind', '-I Impact/Monitoring'
18 |
19 | s.libraries = 'c++abi'
20 |
21 | s.osx.deployment_target = '10.13'
22 | s.ios.deployment_target = '12.0'
23 | s.tvos.deployment_target = '12.0'
24 |
25 | s.cocoapods_version = '>= 1.4.0'
26 | s.swift_version = '5.0'
27 | end
28 |
--------------------------------------------------------------------------------
/Impact.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Impact.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Impact.xcodeproj/xcshareddata/xcschemes/Impact.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
39 |
40 |
41 |
42 |
45 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
79 |
80 |
81 |
82 |
84 |
85 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/Impact.xcodeproj/xcshareddata/xcschemes/ImpactTestMac.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactDWARF.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDWARF.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-05-06.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactDWARF.h"
10 | #include "ImpactDWARFParser.h"
11 | #include "ImpactDWARFCFIInstructions.h"
12 | #include "ImpactDataCursor.h"
13 | #include "ImpactDWARFDefines.h"
14 | #include "ImpactUtility.h"
15 |
16 | #if IMPACT_DWARF_CFI_SUPPORTED
17 |
18 | ImpactResult ImpactDWARFRunCFIInstructions(const ImpactDWARFCFIInstructions* instructions, const ImpactDWARFCIE* cie, uintptr_t pcOffset, ImpactDWARFCFIState* state) {
19 | ImpactDataCursor cursor = {0};
20 |
21 | ImpactResult result = ImpactDataCursorInitialize(&cursor, (uintptr_t)instructions->data, instructions->length, 0);
22 | if (result != ImpactResultSuccess) {
23 | ImpactDebugLog("[Log:WARN] %s failed to initialize data cursor %d\n", __func__, result);
24 | return result;
25 | }
26 |
27 | uint32_t location = 0;
28 |
29 | // ignore the location for CIE instructions, because those are run for all
30 | // functions, regardless of our relative position within the function
31 | const bool considerLocation = &cie->instructions != instructions;
32 |
33 | ImpactDebugLog("[Log:INFO] running CFI instructions with offset 0x%lx\n", pcOffset);
34 |
35 | while (!ImpactDataCursorAtEnd(&cursor)) {
36 | if (considerLocation && (location >= pcOffset)) {
37 | // we have advanced past the last location described by this entry
38 | break;
39 | }
40 |
41 | uint8_t opcode = 0;
42 |
43 | result = ImpactDataCursorReadUint8(&cursor, &opcode);
44 | if (result != ImpactResultSuccess) {
45 | ImpactDebugLog("[Log:WARN] failed to read CFI opcode %d\n", result);
46 | return result;
47 | }
48 |
49 | result = ImpactResultFailure;
50 | const uint8_t operand = opcode & 0x3F;
51 |
52 | switch (opcode) {
53 | case DW_CFA_nop:
54 | ImpactDebugLog("[Log:INFO] DW_CFA_nop\n");
55 | result = ImpactResultSuccess;
56 | break;
57 | case DW_CFA_def_cfa:
58 | result = ImpactDWARFRun_DW_CFA_def_cfa(&cursor, state);
59 | break;
60 | case DW_CFA_def_cfa_offset:
61 | result = ImpactDWARFRun_DW_CFA_def_cfa_offset(&cursor, state);
62 | break;
63 | default:
64 | switch (opcode & 0xC0) {
65 | case DW_CFA_offset:
66 | result = ImpactDWARFRun_DW_CFA_offset(&cursor, operand, cie, state);
67 | break;
68 | case DW_CFA_advance_loc:
69 | result = ImpactDWARFRun_DW_CFA_advance_loc(&cursor, operand, cie, state, &location);
70 | break;
71 | default:
72 | ImpactDebugLog("[Log:WARN] %s unhandled opcode %x\n", __func__, opcode);
73 | return ImpactResultFailure;
74 | }
75 | break;
76 | }
77 |
78 | // Looking through all of /usr/lib on a number of macOS versions, I've only found these other opcodes in use
79 | // DW_CFA_def_cfa_register
80 | // DW_CFA_advance_loc1
81 | // DW_CFA_advance_loc4
82 | // DW_CFA_remember_state
83 | // DW_CFA_restore
84 | // DW_CFA_restore_state
85 | //
86 | // libc++abi.dylib contains DWARF instructions for ARM64. Interestingly, the only time DW_CFA_register
87 | // has come up so for is in the hand-written CFI code in CrashProbe.
88 |
89 | if (result != ImpactResultSuccess) {
90 | return result;
91 | }
92 | }
93 |
94 | return ImpactResultSuccess;
95 | }
96 |
97 | ImpactResult ImpactDWARFRunInstructions(const ImpactDWARFCFIData* data, ImpactDWARFTarget target, ImpactDWARFCFIState* state) {
98 | // We need to run all CIE instructions, regardless of PC. So, pass in zero here.
99 | ImpactResult result = ImpactDWARFRunCFIInstructions(&data->cie.instructions, &data->cie, 0, state);
100 | if (result != ImpactResultSuccess) {
101 | return result;
102 | }
103 |
104 | // Now, this FDE can cover a range that extends past the current PC. So, compute
105 | // limit.
106 | uint64_t targetAddress = 0;
107 |
108 | result = ImpactDWARFResolveEncodedPointer(data->cie.augmentationData.pointerEncoding, data->fde.target_address, &targetAddress);
109 | if (result != ImpactResultSuccess) {
110 | return result;
111 | }
112 |
113 | ImpactDebugLog("[Log:INFO] pc=%lx target=%llx\n", target.pc, targetAddress);
114 |
115 | const uintptr_t pcOffset = target.pc - targetAddress;
116 |
117 | result = ImpactDWARFRunCFIInstructions(&data->fde.instructions, &data->cie, pcOffset, state);
118 | if (result != ImpactResultSuccess) {
119 | return result;
120 | }
121 |
122 | return ImpactResultSuccess;
123 | }
124 |
125 | ImpactResult ImpactDWARFReadData(ImpactMachODataRegion cfiRegion, ImpactDWARFEnvironment env, uint32_t offset, ImpactDWARFCFIData* data) {
126 | ImpactDataCursor cursor = {0};
127 |
128 | ImpactResult result = ImpactDataCursorInitialize(&cursor, cfiRegion.address, cfiRegion.length, offset);
129 | if (result != ImpactResultSuccess) {
130 | ImpactDebugLog("[Log:WARN] %s failed to initialize data cursor %d\n", __func__, result);
131 | return result;
132 | }
133 |
134 | result = ImpactDWARFReadCFI(&cursor, env, data);
135 | if (result != ImpactResultSuccess) {
136 | ImpactDebugLog("[Log:WARN] %s parsing CFI failed %d\n", __func__, result);
137 | return result;
138 | }
139 |
140 | return ImpactResultSuccess;
141 | }
142 |
143 | ImpactResult ImpactDWARFResolveEncodedPointer(uint8_t encoding, uint64_t value, uint64_t *resolvedValue) {
144 | if (ImpactInvalidPtr(resolvedValue)) {
145 | return ImpactResultPointerInvalid;
146 | }
147 |
148 | if (encoding & DW_EH_PE_indirect) {
149 | // read the pointer here
150 | return ImpactResultUnimplemented;
151 | }
152 |
153 | *resolvedValue = value;
154 |
155 | return ImpactResultSuccess;
156 | }
157 |
158 | ImpactResult ImpactDWARFGetCFAValue(const ImpactDWARFCFIState* state, const ImpactCPURegisters* registers, uintptr_t *value) {
159 | if (ImpactInvalidPtr(state) || ImpactInvalidPtr(registers) || ImpactInvalidPtr(value)) {
160 | return ImpactResultPointerInvalid;
161 | }
162 |
163 | switch (state->cfaDefinition.rule) {
164 | case ImpactDWARFCFADefinitionRuleRegisterOffset: {
165 | const ImpactCPURegister regNum = state->cfaDefinition.registerNum;
166 |
167 | ImpactResult result = ImpactCPUGetRegister(registers, regNum, value);
168 | if (result != ImpactResultSuccess) {
169 | return result;
170 | }
171 |
172 | *value += state->cfaDefinition.value;
173 |
174 | return ImpactResultSuccess;
175 | }
176 | default:
177 | break;
178 |
179 | }
180 |
181 | ImpactDebugLog("[Log:WARN] unsupported CFA definition %d \n", state->cfaDefinition.rule);
182 |
183 | return ImpactResultUnimplemented;
184 | }
185 |
186 | ImpactResult ImpactDWARFGetRegisterValue(const ImpactDWARFCFIState* state, uintptr_t cfa, ImpactDWARFRegister dwarfRegister, uintptr_t* value) {
187 | if (ImpactInvalidPtr(state) || ImpactInvalidPtr(value)) {
188 | ImpactDebugLog("[Log:WARN] %s pointer argument invalid\n", __func__);
189 | return ImpactResultPointerInvalid;
190 | }
191 |
192 | switch (dwarfRegister.rule) {
193 | case ImpactDWARFCFIRegisterRuleOffsetFromCFA: {
194 | const uintptr_t addr = cfa + dwarfRegister.value;
195 |
196 | return ImpactReadMemory(addr, sizeof(void*), value);
197 | }
198 | default:
199 | ImpactDebugLog("[Log:WARN] unsupported DWARF register rule %d\n", dwarfRegister.rule);
200 | break;
201 | }
202 |
203 | return ImpactResultUnimplemented;
204 | }
205 |
206 | ImpactResult ImpactDWARFStepRegisters(const ImpactDWARFCFIData* cfiData, ImpactDWARFTarget target, ImpactCPURegisters* registers) {
207 | if (ImpactInvalidPtr(cfiData) || ImpactInvalidPtr(registers)) {
208 | return ImpactResultPointerInvalid;
209 | }
210 |
211 | ImpactDWARFCFIState state = {0};
212 |
213 | ImpactResult result = ImpactDWARFRunInstructions(cfiData, target, &state);
214 | if (result != ImpactResultSuccess) {
215 | return result;
216 | }
217 |
218 | uintptr_t cfaValue = 0;
219 |
220 | result = ImpactDWARFGetCFAValue(&state, registers, &cfaValue);
221 | if (result != ImpactResultSuccess) {
222 | ImpactDebugLog("[Log:WARN] %s failed to get CFA %d\n", __func__, result);
223 | return result;
224 | }
225 |
226 | ImpactDebugLog("[Log:INFO] CFA=%lx, RA=%lld\n", cfaValue, cfiData->cie.return_address_register);
227 |
228 | if (ImpactInvalidPtr((void*)cfaValue)) {
229 | ImpactDebugLog("[Log:WARN] CFA invalid\n");
230 | return ImpactResultFailure;
231 | }
232 |
233 | ImpactCPURegisters updatedRegisters = *registers;
234 |
235 | if (cfiData->cie.augmentationData.signedWithBKey) {
236 | ImpactDebugLog("[Log:WARN] addresses are signed\n");
237 | }
238 |
239 | for (uint32_t i = 0; i < ImpactCPUDWARFRegisterCount; ++i) {
240 | const ImpactDWARFRegister reg = state.registerRules[i];
241 |
242 | if (reg.rule == ImpactDWARFCFIRegisterRuleUnused) {
243 | continue;
244 | }
245 |
246 | uintptr_t regValue = 0;
247 | result = ImpactDWARFGetRegisterValue(&state, cfaValue, reg, ®Value);
248 | if (result != ImpactResultSuccess) {
249 | return result;
250 | }
251 |
252 | ImpactDebugLog("[Log:INFO] restoring register %d with 0x%lx\n", i, regValue);
253 |
254 | if (i == cfiData->cie.return_address_register) {
255 | result = ImpactCPUSetRegister(&updatedRegisters, ImpactCPURegisterInstructionPointer, regValue);
256 |
257 | if (result != ImpactResultSuccess) {
258 | return result;
259 | }
260 | }
261 |
262 | result = ImpactCPUSetRegister(&updatedRegisters, i, regValue);
263 | if (result != ImpactResultSuccess) {
264 | return result;
265 | }
266 | }
267 |
268 | result = ImpactCPUSetRegister(&updatedRegisters, ImpactCPURegisterStackPointer, cfaValue);
269 | if (result != ImpactResultSuccess) {
270 | return result;
271 | }
272 |
273 | *registers = updatedRegisters;
274 |
275 | return ImpactResultSuccess;
276 | }
277 |
278 | #endif
279 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactDWARF.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDWARF.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-05-06.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactDWARF_h
10 | #define ImpactDWARF_h
11 |
12 | #include
13 |
14 | #include "ImpactBinaryImage.h"
15 | #include "ImpactLEB.h"
16 | #include "ImpactCPU.h"
17 | #include "ImpactResult.h"
18 |
19 | #if defined(__x86_64__) || defined(__i386__) || defined(__arm64__)
20 | #define IMPACT_DWARF_CFI_SUPPORTED 1
21 | #else
22 | #define IMPACT_DWARF_CFI_SUPPORTED 0
23 | #endif
24 |
25 | #if IMPACT_DWARF_CFI_SUPPORTED
26 |
27 | typedef struct {
28 | uint32_t length32;
29 | uint64_t length64;
30 | } ImpactDWARFCFILength;
31 |
32 | typedef struct {
33 | ImpactDWARFCFILength length;
34 | uint64_t CIE_id;
35 | } ImpactDWARFCFIHeader;
36 |
37 | static bool ImpactDWARFCFILengthHas64BitMarker(ImpactDWARFCFILength length) {
38 | return length.length32 == 0xffffffff;
39 | }
40 |
41 | #pragma clang diagnostic push
42 | #pragma clang diagnostic ignored "-Wunused-function"
43 | // these are useful for test targets
44 | static uint64_t ImpactDWARFCFILengthGetValue(ImpactDWARFCFILength length) {
45 | if (ImpactDWARFCFILengthHas64BitMarker(length)) {
46 | return length.length64;
47 | }
48 |
49 | return length.length32;
50 | }
51 |
52 | static uint64_t ImpactDWARFCFIHeaderGetLength(ImpactDWARFCFIHeader header) {
53 | return ImpactDWARFCFILengthGetValue(header.length);
54 | }
55 |
56 | static uint64_t ImpactDWARFCFIHeaderLengthFieldSize(ImpactDWARFCFIHeader header) {
57 | if (ImpactDWARFCFILengthHas64BitMarker(header.length)) {
58 | return 4 + 8;
59 | } else {
60 | return 4;
61 | }
62 | }
63 |
64 | static uint64_t ImpactDWARFCFIGetTotalLength(ImpactDWARFCFIHeader header) {
65 | // the length field of a CFI entry does not include itself
66 | return ImpactDWARFCFIHeaderGetLength(header) + ImpactDWARFCFIHeaderLengthFieldSize(header);
67 | }
68 |
69 | #pragma clang diagnostic pop
70 |
71 | typedef struct {
72 | const void* data;
73 | uint8_t length;
74 | } ImpactDWARFCFIInstructions;
75 |
76 | typedef struct {
77 | ImpactDWARFCFIHeader header;
78 | uintptr_t initial_location;
79 | uintptr_t segment_selector;
80 | uint64_t target_address;
81 | uint64_t address_range;
82 | ImpactDWARFCFIInstructions instructions;
83 | } ImpactDWARFFDE;
84 |
85 | typedef struct {
86 | uleb128 length;
87 | bool fdesHaveAugmentationData;
88 | uint8_t personalityEncoding;
89 | uint64_t personality;
90 | uint8_t lsdaEncoding;
91 | uint8_t pointerEncoding;
92 | bool isSignalFrame;
93 | bool signedWithBKey;
94 | } ImpactDWARFCIEAppleAugmentationData;
95 |
96 | typedef struct {
97 | ImpactDWARFCFIHeader header;
98 | uint8_t version;
99 | const char* augmentation;
100 | uint8_t address_size;
101 | uint8_t segment_size;
102 | uleb128 code_alignment_factor;
103 | sleb128 data_alignment_factor;
104 | uleb128 return_address_register;
105 | ImpactDWARFCIEAppleAugmentationData augmentationData;
106 | ImpactDWARFCFIInstructions instructions;
107 | } ImpactDWARFCIE;
108 |
109 | typedef struct {
110 | ImpactDWARFCIE cie;
111 | ImpactDWARFFDE fde;
112 | } ImpactDWARFCFIData;
113 |
114 | typedef enum {
115 | ImpactDWARFCFADefinitionRuleUndefined = 0,
116 | ImpactDWARFCFADefinitionRuleRegisterOffset = 1,
117 | ImpactDWARFCFADefinitionRuleExpressiom = 2
118 | } ImpactDWARFCFARegisterRule;
119 |
120 | typedef enum {
121 | ImpactDWARFCFIRegisterRuleUnused = 0,
122 | ImpactDWARFCFIRegisterRuleOffsetFromCFA = 1
123 | } ImpactDWARFCFIRegisterRule;
124 |
125 | typedef struct {
126 | ImpactDWARFCFIRegisterRule rule;
127 | int64_t value;
128 | } ImpactDWARFRegister;
129 |
130 | typedef struct {
131 | ImpactDWARFCFARegisterRule rule;
132 | uint32_t registerNum;
133 | int64_t value;
134 | } ImpactDWARFCFADefinition;
135 |
136 | typedef struct {
137 | ImpactDWARFCFADefinition cfaDefinition;
138 | ImpactDWARFRegister registerRules[ImpactCPUDWARFRegisterCount];
139 | } ImpactDWARFCFIState;
140 |
141 | typedef struct {
142 | uint8_t pointerWidth;
143 | } ImpactDWARFEnvironment;
144 |
145 | typedef struct {
146 | uintptr_t pc;
147 | ImpactMachODataRegion ehFrameRegion;
148 | ImpactDWARFEnvironment environment;
149 | } ImpactDWARFTarget;
150 |
151 | ImpactResult ImpactDWARFRunInstructions(const ImpactDWARFCFIData* data, ImpactDWARFTarget target, ImpactDWARFCFIState* state);
152 | ImpactResult ImpactDWARFReadData(ImpactMachODataRegion cfiRegion, ImpactDWARFEnvironment env, uint32_t offset, ImpactDWARFCFIData* data);
153 | ImpactResult ImpactDWARFResolveEncodedPointer(uint8_t encoding, uint64_t value, uint64_t *resolvedValue);
154 |
155 | ImpactResult ImpactDWARFGetCFAValue(const ImpactDWARFCFIState* state, const ImpactCPURegisters* registers, uintptr_t *value);
156 | ImpactResult ImpactDWARFGetRegisterValue(const ImpactDWARFCFIState* state, uintptr_t cfa, ImpactDWARFRegister dwarfRegister, uintptr_t* value);
157 |
158 | ImpactResult ImpactDWARFStepRegisters(const ImpactDWARFCFIData* cfiData, ImpactDWARFTarget target, ImpactCPURegisters* registers);
159 |
160 | #endif
161 |
162 | #endif /* ImpactDWARF_h */
163 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactDWARFCFIInstructions.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDWARFCFIInstructions.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-13.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactDWARFCFIInstructions.h"
10 | #include "ImpactUtility.h"
11 |
12 | #if IMPACT_DWARF_CFI_SUPPORTED
13 |
14 | ImpactResult ImpactDWARFRun_DW_CFA_def_cfa(ImpactDataCursor* cursor, ImpactDWARFCFIState* state) {
15 | if (ImpactInvalidPtr(state)) {
16 | return ImpactResultPointerInvalid;
17 | }
18 |
19 | uleb128 value = 0;
20 |
21 | ImpactResult result = ImpactDataCursorReadULEB128(cursor, &value);
22 | if (result != ImpactResultSuccess) {
23 | return result;
24 | }
25 |
26 | state->cfaDefinition.rule = ImpactDWARFCFADefinitionRuleRegisterOffset;
27 |
28 | state->cfaDefinition.registerNum = (int32_t)value;
29 |
30 | value = 0;
31 | result = ImpactDataCursorReadULEB128(cursor, &value);
32 | if (result != ImpactResultSuccess) {
33 | return result;
34 | }
35 |
36 | state->cfaDefinition.value = value;
37 |
38 | ImpactDebugLog("[Log:INFO] DW_CFA_def_cfa reg %d offset %lld\n", state->cfaDefinition.registerNum, state->cfaDefinition.value);
39 |
40 | return ImpactResultSuccess;
41 | }
42 |
43 | ImpactResult ImpactDWARFRun_DW_CFA_offset(ImpactDataCursor* cursor, uint8_t operand, const ImpactDWARFCIE* cie, ImpactDWARFCFIState* state) {
44 | if (ImpactInvalidPtr(cie) || ImpactInvalidPtr(state)) {
45 | return ImpactResultPointerInvalid;
46 | }
47 |
48 | if (operand >= ImpactCPUDWARFRegisterCount) {
49 | ImpactDebugLog("[Log:WARN] DW_CFA_offset register out of range %d\n", operand);
50 | return ImpactResultInconsistentData;
51 | }
52 |
53 | uint64_t value = 0;
54 |
55 | ImpactResult result = ImpactDataCursorReadULEB128(cursor, &value);
56 | if (result != ImpactResultSuccess) {
57 | ImpactDebugLog("[Log:WARN] DW_CFA_offset failed to read uleb128 %d\n", result);
58 | return result;
59 | }
60 |
61 | const int64_t offset = value * cie->data_alignment_factor;
62 |
63 | state->registerRules[operand].rule = ImpactDWARFCFIRegisterRuleOffsetFromCFA;
64 | state->registerRules[operand].value = offset;
65 |
66 | ImpactDebugLog("[Log:INFO] DW_CFA_offset reg=%d offset=%lld\n", operand, offset);
67 |
68 | return ImpactResultSuccess;
69 | }
70 |
71 | ImpactResult ImpactDWARFRun_DW_CFA_advance_loc(ImpactDataCursor* cursor, uint8_t operand, const ImpactDWARFCIE* cie, ImpactDWARFCFIState* state, uint32_t* location) {
72 | if (ImpactInvalidPtr(cie) || ImpactInvalidPtr(state) || ImpactInvalidPtr(location)) {
73 | return ImpactResultPointerInvalid;
74 | }
75 |
76 | // should be a little more careful about checking bounds here
77 | const int32_t delta = operand * (int32_t)cie->code_alignment_factor;
78 |
79 | ImpactDebugLog("[Log:INFO] DW_CFA_advance_loc delta=%d\n", delta);
80 |
81 | *location += delta;
82 |
83 | return ImpactResultSuccess;
84 | }
85 |
86 | ImpactResult ImpactDWARFRun_DW_CFA_def_cfa_offset(ImpactDataCursor* cursor, ImpactDWARFCFIState* state) {
87 | if (ImpactInvalidPtr(state)) {
88 | return ImpactResultPointerInvalid;
89 | }
90 |
91 | uleb128 value = 0;
92 | ImpactResult result = ImpactDataCursorReadULEB128(cursor, &value);
93 | if (result != ImpactResultSuccess) {
94 | return result;
95 | }
96 |
97 | // per spec, only defined for register/offset rules
98 | if (state->cfaDefinition.rule != ImpactDWARFCFIRegisterRuleOffsetFromCFA) {
99 | ImpactDebugLog("[Log:WARN] invalid for current CFA rule %d\n", state->cfaDefinition.rule);
100 | return ImpactResultInconsistentData;
101 | }
102 |
103 | ImpactDebugLog("[Log:INFO] DW_CFA_def_cfa_offset offset=%llu\n", value);
104 |
105 | state->cfaDefinition.value = value;
106 |
107 | return ImpactResultSuccess;
108 | }
109 |
110 | #endif
111 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactDWARFCFIInstructions.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDWARFCFIInstructions.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-13.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactDWARFCFIInstructions_h
10 | #define ImpactDWARFCFIInstructions_h
11 |
12 | #include "ImpactDataCursor.h"
13 | #include "ImpactDWARF.h"
14 |
15 | #if IMPACT_DWARF_CFI_SUPPORTED
16 |
17 | ImpactResult ImpactDWARFRun_DW_CFA_def_cfa(ImpactDataCursor* cursor, ImpactDWARFCFIState* state);
18 | ImpactResult ImpactDWARFRun_DW_CFA_offset(ImpactDataCursor* cursor, uint8_t operand, const ImpactDWARFCIE* cie, ImpactDWARFCFIState* state);
19 | ImpactResult ImpactDWARFRun_DW_CFA_advance_loc(ImpactDataCursor* cursor, uint8_t operand, const ImpactDWARFCIE* cie, ImpactDWARFCFIState* state, uint32_t* location);
20 | ImpactResult ImpactDWARFRun_DW_CFA_def_cfa_offset(ImpactDataCursor* cursor, ImpactDWARFCFIState* state);
21 |
22 | #endif
23 |
24 | #endif /* ImpactDWARFCFIInstructions_h */
25 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactDWARFDefines.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDWARFDefines.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-07-27.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactDWARFDefines_h
10 | #define ImpactDWARFDefines_h
11 |
12 | // Pointer Encoding
13 | #define DW_EH_PE_type_mask (0x0F)
14 | #define DW_EH_PE_offset_mask (0x70)
15 |
16 | enum {
17 | DW_EH_PE_ptr = 0x00,
18 | DW_EH_PE_uleb128 = 0x01,
19 | DW_EH_PE_udata2 = 0x02,
20 | DW_EH_PE_udata4 = 0x03,
21 | DW_EH_PE_udata8 = 0x04,
22 | DW_EH_PE_signed = 0x08,
23 | DW_EH_PE_sleb128 = 0x09,
24 | DW_EH_PE_sdata2 = 0x0A,
25 | DW_EH_PE_sdata4 = 0x0B,
26 | DW_EH_PE_sdata8 = 0x0C,
27 | DW_EH_PE_absptr = 0x00,
28 | DW_EH_PE_pcrel = 0x10,
29 | DW_EH_PE_textrel = 0x20,
30 | DW_EH_PE_datarel = 0x30,
31 | DW_EH_PE_funcrel = 0x40,
32 | DW_EH_PE_aligned = 0x50,
33 | DW_EH_PE_indirect = 0x80,
34 | DW_EH_PE_omit = 0xFF
35 | };
36 |
37 | enum {
38 | DW_CFA_nop = 0x0,
39 | DW_CFA_set_loc = 0x1,
40 | DW_CFA_advance_loc1 = 0x2,
41 | DW_CFA_advance_loc2 = 0x3,
42 | DW_CFA_advance_loc4 = 0x4,
43 | DW_CFA_offset_extended = 0x5,
44 | DW_CFA_restore_extended = 0x6,
45 | DW_CFA_undefined = 0x7,
46 | DW_CFA_same_value = 0x8,
47 | DW_CFA_register = 0x9,
48 | DW_CFA_remember_state = 0xA,
49 | DW_CFA_restore_state = 0xB,
50 | DW_CFA_def_cfa = 0xC,
51 | DW_CFA_def_cfa_register = 0xD,
52 | DW_CFA_def_cfa_offset = 0xE,
53 | DW_CFA_def_cfa_expression = 0xF,
54 | DW_CFA_expression = 0x10,
55 | DW_CFA_offset_extended_sf = 0x11,
56 | DW_CFA_def_cfa_sf = 0x12,
57 | DW_CFA_def_cfa_offset_sf = 0x13,
58 | DW_CFA_val_offset = 0x14,
59 | DW_CFA_val_offset_sf = 0x15,
60 | DW_CFA_val_expression = 0x16,
61 | DW_CFA_advance_loc = 0x40, // high 2 bits are 0x1, lower 6 bits are delta
62 | DW_CFA_offset = 0x80, // high 2 bits are 0x2, lower 6 bits are register
63 | DW_CFA_restore = 0xC0, // high 2 bits are 0x3, lower 6 bits are register
64 |
65 | // GNU extensions
66 | DW_CFA_GNU_window_save = 0x2D,
67 | DW_CFA_GNU_args_size = 0x2E,
68 | DW_CFA_GNU_negative_offset_extended = 0x2F
69 | };
70 |
71 | #endif /* ImpactDWARFDefines_h */
72 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactDWARFParser.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDWARFParser.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-05-07.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactDWARFParser_h
10 | #define ImpactDWARFParser_h
11 |
12 | #include "ImpactDWARF.h"
13 | #include "ImpactDataCursor.h"
14 |
15 | #if IMPACT_DWARF_CFI_SUPPORTED
16 |
17 | ImpactResult ImpactDWARFReadCIE(ImpactDataCursor* cursor, ImpactDWARFEnvironment env, ImpactDWARFCIE* cie);
18 | ImpactResult ImpactDWARFReadCFI(ImpactDataCursor* cursor, ImpactDWARFEnvironment env, ImpactDWARFCFIData* cfiData);
19 |
20 | #endif
21 |
22 | #endif /* ImpactDWARFParser_h */
23 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactDataCursor.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDataCursor.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-08-01.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactDataCursor.h"
10 | #include "ImpactUtility.h"
11 |
12 | ImpactResult ImpactDataCursorInitialize(ImpactDataCursor* cursor, uintptr_t address, uintptr_t limit, uintptr_t offset) {
13 | if (ImpactInvalidPtr(cursor) || ImpactInvalidPtr((void*)address)) {
14 | return ImpactResultPointerInvalid;
15 | }
16 |
17 | cursor->address = address;
18 | cursor->limit = limit;
19 | cursor->offset = offset;
20 |
21 | return ImpactResultSuccess;
22 | }
23 |
24 | bool ImpactDataCursorIsValid(const ImpactDataCursor* cursor) {
25 | if (ImpactInvalidPtr(cursor)) {
26 | return false;
27 | }
28 |
29 | if (ImpactInvalidPtr((void*)cursor->address)) {
30 | return false;
31 | }
32 |
33 | return !ImpactDataCursorAtEnd(cursor);
34 | }
35 |
36 | bool ImpactDataCursorAtEnd(const ImpactDataCursor* cursor) {
37 | if (ImpactInvalidPtr(cursor)) {
38 | return true;
39 | }
40 |
41 | return cursor->offset >= cursor->limit;
42 | }
43 |
44 | const void* ImpactDataCursorCurrentPointer(const ImpactDataCursor* cursor) {
45 | if (!ImpactDataCursorIsValid(cursor)) {
46 | return NULL;
47 | }
48 |
49 | return (void*)(cursor->address + cursor->offset);
50 | }
51 |
52 | ImpactResult ImpactDataCursorReadValue(ImpactDataCursor* cursor, size_t size, void* value) {
53 | if (ImpactInvalidPtr(cursor) || ImpactInvalidPtr(value)) {
54 | return ImpactResultPointerInvalid;
55 | }
56 |
57 | const void* ptr = ImpactDataCursorCurrentPointer(cursor);
58 | if (ImpactInvalidPtr(ptr)) {
59 | return ImpactResultStateInvalid;
60 | }
61 |
62 | switch (size) {
63 | case 1:
64 | *(uint8_t*)value = *(const uint8_t*)ptr;
65 | break;
66 | case 2:
67 | *(uint16_t*)value = *(const uint16_t*)ptr;
68 | break;
69 | case 4:
70 | *(uint32_t*)value = *(const uint32_t*)ptr;
71 | break;
72 | case 8:
73 | *(uint64_t*)value = *(const uint64_t*)ptr;
74 | break;
75 | default:
76 | return ImpactResultArgumentInvalid;
77 | }
78 |
79 | cursor->offset += size;
80 |
81 | return ImpactResultSuccess;
82 | }
83 |
84 | ImpactResult ImpactDataCursorReadUint8(ImpactDataCursor* cursor, uint8_t* value) {
85 | return ImpactDataCursorReadValue(cursor, 1, value);
86 | }
87 |
88 | ImpactResult ImpactDataCursorReadUint32(ImpactDataCursor* cursor, uint32_t* value) {
89 | return ImpactDataCursorReadValue(cursor, 4, value);
90 | }
91 |
92 | ImpactResult ImpactDataCursorReadUint64(ImpactDataCursor* cursor, uint64_t* value) {
93 | return ImpactDataCursorReadValue(cursor, 8, value);
94 | }
95 |
96 | ImpactResult ImpactDataCursorReadULEB128(ImpactDataCursor* cursor, uleb128* value) {
97 | int shift = 0;
98 | size_t count = 0;
99 |
100 | for (; count <= 8; ++count) {
101 | uint8_t byte = 0;
102 |
103 | ImpactResult result = ImpactDataCursorReadUint8(cursor, &byte);
104 | if (result != ImpactResultSuccess) {
105 | return result;
106 | }
107 |
108 | *value |= (byte & 0x7f) << shift;
109 | shift += 7;
110 |
111 | if (!(byte & 0x80)) {
112 | break;
113 | }
114 | }
115 |
116 | return count < 8 ? ImpactResultSuccess : ImpactResultFailure;
117 | }
118 |
119 | ImpactResult ImpactDataCursorReadSLEB128(ImpactDataCursor* cursor, sleb128* value) {
120 | int shift = 0;
121 | size_t count = 0;
122 | uint8_t byte = 0;
123 |
124 | for (; count <= 8; ++count) {
125 | ImpactResult result = ImpactDataCursorReadUint8(cursor, &byte);
126 | if (result != ImpactResultSuccess) {
127 | return result;
128 | }
129 |
130 | *value |= (byte & 0x7f) << shift;
131 | shift += 7;
132 |
133 | if (!(byte & 0x80)) {
134 | break;
135 | }
136 | }
137 |
138 | if (count >= 8) {
139 | return ImpactResultFailure;
140 | }
141 |
142 | if (shift < sizeof(uint64_t) && (byte & 0x40)) {
143 | *value |= (-1ULL) << shift;
144 | }
145 |
146 | return ImpactResultSuccess;
147 | }
148 |
149 | ImpactResult ImpactDataCursorReadString(ImpactDataCursor* cursor, const char **string, uint32_t *length) {
150 | if (ImpactInvalidPtr(cursor) || ImpactInvalidPtr(string) || ImpactInvalidPtr(length)) {
151 | return ImpactResultPointerInvalid;
152 | }
153 |
154 | *string = ImpactDataCursorCurrentPointer(cursor);
155 |
156 | for (uint32_t i = 0; i < ImpactDataCursorMaxStringLength; ++i) {
157 | uint8_t c = 0;
158 |
159 | ImpactResult result = ImpactDataCursorReadUint8(cursor, &c);
160 | if (result != ImpactResultSuccess) {
161 | return result;
162 | }
163 |
164 | if (c == 0) {
165 | break;
166 | }
167 |
168 | *length += 1;
169 | }
170 |
171 | return ImpactResultSuccess;
172 | }
173 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactDataCursor.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDataCursor.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-08-01.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactDataCursor_h
10 | #define ImpactDataCursor_h
11 |
12 | #include "ImpactResult.h"
13 | #include "ImpactLEB.h"
14 |
15 | #include
16 | #include
17 | #include
18 |
19 | typedef struct {
20 | uintptr_t address;
21 | uintptr_t limit;
22 | uintptr_t offset;
23 | } ImpactDataCursor;
24 |
25 | static const uint32_t ImpactDataCursorMaxStringLength = 4096;
26 |
27 | ImpactResult ImpactDataCursorInitialize(ImpactDataCursor* cursor, uintptr_t address, uintptr_t limit, uintptr_t offset);
28 |
29 | const void* ImpactDataCursorCurrentPointer(const ImpactDataCursor* cursor);
30 | bool ImpactDataCursorIsValid(const ImpactDataCursor* cursor);
31 | bool ImpactDataCursorAtEnd(const ImpactDataCursor* cursor);
32 |
33 | ImpactResult ImpactDataCursorReadValue(ImpactDataCursor* cursor, size_t size, void* value);
34 | ImpactResult ImpactDataCursorReadUint8(ImpactDataCursor* cursor, uint8_t* value);
35 | ImpactResult ImpactDataCursorReadUint32(ImpactDataCursor* cursor, uint32_t* value);
36 | ImpactResult ImpactDataCursorReadUint64(ImpactDataCursor* cursor, uint64_t* value);
37 | ImpactResult ImpactDataCursorReadULEB128(ImpactDataCursor* cursor, uleb128* value);
38 | ImpactResult ImpactDataCursorReadSLEB128(ImpactDataCursor* cursor, sleb128* value);
39 | ImpactResult ImpactDataCursorReadString(ImpactDataCursor* cursor, const char **string, uint32_t *length);
40 |
41 | #endif /* ImpactDataCursor_h */
42 |
--------------------------------------------------------------------------------
/Impact/DWARF/ImpactLEB.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactLEB.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-08-01.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactLEB_h
10 | #define ImpactLEB_h
11 |
12 | #include
13 |
14 | typedef uint64_t uleb128;
15 | typedef int64_t sleb128;
16 |
17 | #endif /* ImpactLEB_h */
18 |
--------------------------------------------------------------------------------
/Impact/Impact.h:
--------------------------------------------------------------------------------
1 | //
2 | // Impact.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-17.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Impact.
12 | FOUNDATION_EXPORT double ImpactVersionNumber;
13 |
14 | //! Project version string for Impact.
15 | FOUNDATION_EXPORT const unsigned char ImpactVersionString[];
16 |
17 | #import
18 | #import
19 |
--------------------------------------------------------------------------------
/Impact/Impact.xcconfig:
--------------------------------------------------------------------------------
1 | // Configuration settings file format documentation can be found at:
2 | // https://help.apple.com/xcode/#/dev745c5c974
3 |
4 | #include "../xcconfigs/UniversalFramework_Framework.xcconfig"
5 |
6 | APPLICATION_EXTENSION_API_ONLY = YES
7 | MACH_O_TYPE = staticlib
8 | SWIFT_VERSION = 5.0
9 |
10 | PRODUCT_NAME = Impact
11 | PRODUCT_BUNDLE_IDENTIFIER = io.chimehq.Impact
12 | PRODUCT_MODULE_NAME = Impact
13 | CURRENT_PROJECT_VERSION = 14
14 | DYLIB_CURRENT_VERSION = $(CURRENT_PROJECT_VERSION)
15 | MARKETING_VERSION = 0.4.1
16 |
17 | INFOPLIST_FILE = Impact/Info.plist
18 |
19 | MACOSX_DEPLOYMENT_TARGET = 10.13
20 | IPHONEOS_DEPLOYMENT_TARGET = 12.0
21 | TVOS_DEPLOYMENT_TARGET = 12.0
22 | WATCHOS_DEPLOYMENT_TARGET = 4.0
23 |
24 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) CURRENT_PROJECT_VERSION=$(CURRENT_PROJECT_VERSION)
25 |
--------------------------------------------------------------------------------
/Impact/ImpactBinaryImage.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactBinaryImage.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-02.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactBinaryImage.h"
10 | #include "ImpactCPU.h"
11 | #include "ImpactUtility.h"
12 | #include "ImpactLog.h"
13 |
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 |
21 | #include
22 |
23 | //static void ImpactBinaryImageAdded(const struct mach_header* mh, intptr_t vmaddr_slide);
24 | //static void ImpactBinaryImageRemoved(const struct mach_header* mh, intptr_t vmaddr_slide);
25 |
26 | static uint32_t ImpactBinaryImageNotFoundFlag = ~0;
27 |
28 | static ImpactResult ImpactBinaryImageFindDyldInfo(struct task_dyld_info* info);
29 |
30 | ImpactResult ImpactBinaryImageInitialize(ImpactState* state) {
31 | // _dyld_register_func_for_add_image(ImpactBinaryImageAdded);
32 | // _dyld_register_func_for_remove_image(ImpactBinaryImageRemoved);
33 |
34 | state->mutableState.images.lastFoundIndex = ImpactBinaryImageNotFoundFlag;
35 | state->mutableState.images.writtenIndex = ImpactBinaryImageNotFoundFlag;
36 |
37 | return ImpactBinaryImageFindDyldInfo(&state->mutableState.images.dyldInfo);
38 | }
39 |
40 | ImpactResult ImpactBinaryImageGetSectionData(const ImpactSegmentCommand* segCommand, ImpactMachOData* data, intptr_t slide) {
41 | if (ImpactInvalidPtr(segCommand) || ImpactInvalidPtr(data)) {
42 | return ImpactResultPointerInvalid;
43 | }
44 |
45 | const uint8_t *ptr = (uint8_t *)segCommand + sizeof(ImpactSegmentCommand);
46 | uint32_t foundSections = 0;
47 |
48 | for (uint32_t i = 0; i < segCommand->nsects && foundSections < 2; ++i) {
49 | const ImpactSection* const section = (ImpactSection*)ptr;
50 |
51 | if (strncmp(section->sectname, "__unwind_info", 13) == 0) {
52 | data->unwindInfoRegion.address = section->addr + slide;
53 | data->unwindInfoRegion.loadAddress = section->addr;
54 | data->unwindInfoRegion.length = section->size;
55 |
56 | foundSections++;
57 | } else if (strncmp(section->sectname, "__eh_frame", 10) == 0) {
58 | data->ehFrameRegion.address = section->addr + slide;
59 | data->ehFrameRegion.loadAddress = section->addr;
60 | data->ehFrameRegion.length = section->size;
61 |
62 | foundSections++;
63 | }
64 |
65 | ptr += sizeof(ImpactSection);
66 | }
67 |
68 | return ImpactResultSuccess;
69 | }
70 |
71 | ImpactResult ImpactBinaryImageGetData(const ImpactMachOHeader* header, const char* path, ImpactMachOData* data) {
72 | if (ImpactInvalidPtr(header) || ImpactInvalidPtr(data)) {
73 | return ImpactResultPointerInvalid;
74 | }
75 |
76 | const uint8_t *ptr = (uint8_t *)header + sizeof(ImpactMachOHeader);
77 |
78 | data->loadAddress = (uintptr_t)header;
79 | data->path = path;
80 |
81 | for (uint32_t i = 0; i < header->ncmds; ++i) {
82 | const struct load_command* const lcmd = (struct load_command*)ptr;
83 | const uint32_t cmdCode = lcmd->cmd & ~LC_REQ_DYLD;
84 |
85 | switch (cmdCode) {
86 | case LC_UUID:
87 | data->uuid = ((struct uuid_command*)ptr)->uuid;
88 |
89 | break;
90 | case Impact_LC_SEGMENT: {
91 | const ImpactSegmentCommand* segCommand = (const ImpactSegmentCommand*)lcmd;
92 |
93 | if (strncmp(segCommand->segname, "__TEXT", 6) != 0) {
94 | break;
95 | }
96 |
97 | data->slide = (uintptr_t)header - segCommand->vmaddr;
98 | data->textSize = segCommand->vmsize;
99 |
100 | ImpactBinaryImageGetSectionData(segCommand, data, data->slide);
101 | } break;
102 | default:
103 | break;
104 | }
105 |
106 | ptr += lcmd->cmdsize;
107 | }
108 |
109 | return ImpactResultSuccess;
110 | }
111 |
112 | static ImpactResult ImpactBinaryImageLogPath(ImpactLogger* log, const char* path, bool last) {
113 | #if TARGET_OS_OSX
114 | // This strips out the username, should it be present in the path
115 | if (strncmp(path, "/Users/", 7) == 0) {
116 | const char *sanitizedPath = path + 7; // advance the string past the initial "/Users/" path component
117 |
118 | sanitizedPath = strchr(sanitizedPath, '/');
119 | if (sanitizedPath != NULL) {
120 | ImpactLogWriteString(log, "path: /Users/USER");
121 | ImpactLogWriteString(log, sanitizedPath);
122 |
123 | return ImpactLogWriteString(log, last ? "\n" : ", ");
124 | }
125 | }
126 | #endif
127 |
128 | return ImpactLogWriteKeyString(log, "path", path, last);
129 | }
130 |
131 | static ImpactResult ImpactBinaryImageLog(ImpactLogger* log, const ImpactMachOData* imageData) {
132 | if (ImpactInvalidPtr(log) || ImpactInvalidPtr(imageData)) {
133 | return ImpactResultPointerInvalid;
134 | }
135 |
136 | ImpactLogWriteString(log, "[Binary:Found] ");
137 |
138 | ImpactBinaryImageLogPath(log, imageData->path, false);
139 | ImpactLogWriteKeyInteger(log, "address", imageData->loadAddress, false);
140 | ImpactLogWriteKeyInteger(log, "size", imageData->textSize, false);
141 | ImpactLogWriteKeyHexData(log, "uuid", imageData->uuid, 16, true);
142 |
143 | return ImpactResultSuccess;
144 | }
145 |
146 | static ImpactResult ImpactBinaryImageFindDyldInfo(struct task_dyld_info* info) {
147 | mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
148 |
149 | const kern_return_t result = task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)info, &count);
150 | if (result != KERN_SUCCESS) {
151 | ImpactDebugLog("[Log:ERROR:%s] unable to lookup task dyld info %d\n", __func__, result);
152 | return ImpactResultCallFailed;
153 | }
154 |
155 | return ImpactResultSuccess;
156 | }
157 |
158 | ImpactResult ImpactBinaryImageGetDyldImageData(const struct dyld_all_image_infos* imagesInfo, const int index, ImpactMachOData* data) {
159 | if (index > imagesInfo->infoArrayCount) {
160 | return ImpactResultArgumentInvalid;
161 | }
162 |
163 | const struct dyld_image_info* imageInfo = imagesInfo->infoArray + index;
164 | const ImpactMachOHeader* const header = (ImpactMachOHeader*)imageInfo->imageLoadAddress;
165 | const char* path = imageInfo->imageFilePath;
166 |
167 | return ImpactBinaryImageGetData(header, path, data);
168 | }
169 |
170 | static bool ImpactMachODataContainsAddress(const ImpactMachOData* data, uintptr_t address) {
171 | if (ImpactInvalidPtr(data)) {
172 | return false;
173 | }
174 |
175 | const uintptr_t upperAddress = data->loadAddress + data->textSize;
176 |
177 | return address >= data->loadAddress && address < upperAddress;
178 | }
179 |
180 | ImpactResult ImpactBinaryImageFind(ImpactState* state, uintptr_t address, ImpactMachOData* data) {
181 | if (ImpactInvalidPtr(state) || ImpactInvalidPtr(data)) {
182 | return ImpactResultPointerInvalid;
183 | }
184 |
185 | if (ImpactInvalidPtr((const void*)address)) {
186 | return ImpactResultArgumentInvalid;
187 | }
188 |
189 | ImpactLogger* logger = ImpactStateGetLog(state);
190 |
191 | ImpactBinaryImages* images = &state->mutableState.images;
192 | const struct dyld_all_image_infos* imagesInfo = (void *)images->dyldInfo.all_image_info_addr;
193 | const size_t imageCount = imagesInfo->infoArrayCount;
194 |
195 | // first, check the last match, as it's likely these repeat
196 | ImpactMachOData imageData = {0};
197 |
198 | if (images->lastFoundIndex != ImpactBinaryImageNotFoundFlag) {
199 | const ImpactResult result = ImpactBinaryImageGetDyldImageData(imagesInfo, images->lastFoundIndex, &imageData);
200 | if (result != ImpactResultSuccess) {
201 | return result;
202 | }
203 |
204 | if (ImpactMachODataContainsAddress(&imageData, address)) {
205 | *data = imageData;
206 | return ImpactResultSuccess;
207 | }
208 | }
209 |
210 | for (int i = 0; i < imageCount; ++i) {
211 | const ImpactResult result = ImpactBinaryImageGetDyldImageData(imagesInfo, i, &imageData);
212 | if (result != ImpactResultSuccess) {
213 | return result;
214 | }
215 |
216 | if (i > images->writtenIndex || images->writtenIndex == ImpactBinaryImageNotFoundFlag) {
217 | // There isn't an obvious action to take on error here, so we can just ignore
218 | // for now.
219 | ImpactBinaryImageLog(logger, &imageData);
220 |
221 | images->writtenIndex = i;
222 | }
223 |
224 | if (ImpactMachODataContainsAddress(&imageData, address)) {
225 | *data = imageData;
226 | images->lastFoundIndex = i;
227 | return ImpactResultSuccess;
228 | }
229 | }
230 |
231 | return ImpactResultFailure;
232 | }
233 |
234 | ImpactResult ImpactBinaryImageLogRemainingImages(ImpactState* state) {
235 | if (ImpactInvalidPtr(state)) {
236 | return ImpactResultPointerInvalid;
237 | }
238 |
239 | ImpactLogger* logger = ImpactStateGetLog(state);
240 |
241 | ImpactBinaryImages* images = &state->mutableState.images;
242 | const struct dyld_all_image_infos* imagesInfo = (void *)images->dyldInfo.all_image_info_addr;
243 | const size_t imageCount = imagesInfo->infoArrayCount;
244 |
245 | ImpactMachOData imageData = {0};
246 |
247 | for (int i = images->writtenIndex + 1; i < imageCount; ++i) {
248 | ImpactResult result = ImpactBinaryImageGetDyldImageData(imagesInfo, i, &imageData);
249 | if (result != ImpactResultSuccess) {
250 | return result;
251 | }
252 |
253 | result = ImpactBinaryImageLog(logger, &imageData);
254 | if (result != ImpactResultSuccess) {
255 | return result;
256 | }
257 | }
258 |
259 | return ImpactResultSuccess;
260 | }
261 |
--------------------------------------------------------------------------------
/Impact/ImpactBinaryImage.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactBinaryImage.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-02.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactBinaryImage_h
10 | #define ImpactBinaryImage_h
11 |
12 | #include "ImpactState.h"
13 | #include "ImpactResult.h"
14 |
15 | __BEGIN_DECLS
16 |
17 | typedef struct {
18 | uintptr_t address;
19 | intptr_t loadAddress;
20 | uintptr_t length;
21 | } ImpactMachODataRegion;
22 |
23 | typedef struct {
24 | const uint8_t* uuid;
25 | intptr_t slide;
26 | ImpactMachODataRegion ehFrameRegion;
27 | ImpactMachODataRegion unwindInfoRegion;
28 | uintptr_t loadAddress;
29 | uintptr_t textSize;
30 | const char* path;
31 | } ImpactMachOData;
32 |
33 | #if __LP64__
34 | typedef struct mach_header_64 ImpactMachOHeader;
35 | typedef struct section_64 ImpactMachOSection;
36 | typedef struct segment_command_64 ImpactSegmentCommand;
37 | typedef struct section_64 ImpactSection;
38 |
39 | #define Impact_LC_SEGMENT LC_SEGMENT_64
40 | #else
41 | typedef struct mach_header ImpactMachOHeader;
42 | typedef struct section ImpactMachOSection;
43 | typedef struct segment_command ImpactSegmentCommand;
44 | typedef struct section ImpactSection;
45 |
46 | #define Impact_LC_SEGMENT LC_SEGMENT
47 | #endif
48 |
49 | ImpactResult ImpactBinaryImageInitialize(ImpactState* state);
50 |
51 | ImpactResult ImpactBinaryImageGetData(const ImpactMachOHeader* header, const char* path, ImpactMachOData* data);
52 |
53 | ImpactResult ImpactBinaryImageFind(ImpactState* state, uintptr_t address, ImpactMachOData* data);
54 |
55 | ImpactResult ImpactBinaryImageLogRemainingImages(ImpactState* state);
56 |
57 | __END_DECLS
58 |
59 | #endif /* ImpactBinaryImage_h */
60 |
--------------------------------------------------------------------------------
/Impact/ImpactMonitor.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactMonitor.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | FOUNDATION_EXTERN const char* ImpactPlatformName;
14 |
15 | @interface ImpactMonitor : NSObject
16 |
17 | @property (class, nonatomic, assign, readonly) ImpactMonitor *shared;
18 |
19 | @property (class, nonatomic, assign, readonly) NSInteger buildNumber;
20 | @property (class, nonatomic, assign, readonly) NSString *platform;
21 |
22 | - (void)startWithURL:(NSURL *)url identifier:(NSUUID *)uuid;
23 |
24 | @property (nonatomic) BOOL suppressReportCrash;
25 |
26 | @property (nonatomic, nullable) NSString *applicationIdentifier;
27 | @property (nonatomic, nullable) NSString *organizationIdentifier;
28 | @property (nonatomic, nullable) NSString *installIdentifier;
29 |
30 | @end
31 |
32 | NS_ASSUME_NONNULL_END
33 |
--------------------------------------------------------------------------------
/Impact/ImpactMonitor.m:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactMonitor.m
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "ImpactMonitor.h"
10 | #include "ImpactState.h"
11 | #include "ImpactLog.h"
12 | #include "ImpactSignal.h"
13 | #include "ImpactMachException.h"
14 | #include "ImpactBinaryImage.h"
15 | #include "ImpactUtility.h"
16 | #include "ImpactCPU.h"
17 | #include "ImpactRuntimeException.h"
18 |
19 | #include
20 | #import
21 |
22 | ImpactState* GlobalImpactState = NULL;
23 |
24 | #if TARGET_OS_OSX
25 | const char* ImpactPlatformName = "macOS";
26 | #elif TARGET_OS_IOS
27 | const char* ImpactPlatformName = "iOS";
28 | #elif TARGET_OS_TV
29 | const char* ImpactPlatformName = "tvOS";
30 | #elif TARGET_OS_WATCH
31 | const char* ImpactPlatformName = "watchOS";
32 | #else
33 | #error("Unsupported platform")
34 | #endif
35 |
36 | @implementation ImpactMonitor
37 |
38 | + (ImpactMonitor *)shared {
39 | static ImpactMonitor *sharedMyManager = nil;
40 | static dispatch_once_t onceToken;
41 | dispatch_once(&onceToken, ^{
42 | sharedMyManager = [[self alloc] init];
43 | });
44 | return sharedMyManager;
45 | }
46 |
47 | + (NSInteger)buildNumber {
48 | return CURRENT_PROJECT_VERSION;
49 | }
50 |
51 | +(NSString *)platform {
52 | return [NSString stringWithUTF8String:ImpactPlatformName];
53 | }
54 |
55 | - (instancetype)init {
56 | self = [super init];
57 | if (self) {
58 | _suppressReportCrash = NO;
59 | }
60 |
61 | return self;
62 | }
63 |
64 | - (void)startWithURL:(NSURL *)url identifier:(NSUUID *)uuid {
65 | if (ImpactDebuggerAttached()) {
66 | NSLog(@"[Impact] Debugger attached, monitoring disabled");
67 | return;
68 | }
69 |
70 | GlobalImpactState = malloc(sizeof(ImpactState));
71 |
72 | GlobalImpactState->constantState.suppressReportCrash = self.suppressReportCrash == YES;
73 |
74 | atomic_store(&GlobalImpactState->mutableState.crashState, ImpactCrashStateUninitialized);
75 |
76 | ImpactResult result;
77 |
78 | NSLog(@"[Impact] trying to start with: %s", url.fileSystemRepresentation);
79 |
80 | result = ImpactLogInitialize(GlobalImpactState, url.fileSystemRepresentation);
81 | if (result != ImpactResultSuccess) {
82 | NSLog(@"[Impact] Unable to initialize log %d", result);
83 | return;
84 | }
85 |
86 | [self logExecutableData:GlobalImpactState];
87 | [self logEnvironmentDataWithId:uuid state:GlobalImpactState];
88 |
89 | result = ImpactSignalInitialize(GlobalImpactState);
90 | if (result != ImpactResultSuccess) {
91 | NSLog(@"[Impact] Unable to initialize signal %d", result);
92 | }
93 |
94 | result = ImpactBinaryImageInitialize(GlobalImpactState);
95 | if (result != ImpactResultSuccess) {
96 | NSLog(@"[Impact] Unable to initialize binary images %d", result);
97 | return;
98 | }
99 |
100 | result = ImpactRuntimeExceptionInitialize(GlobalImpactState);
101 | if (result != ImpactResultSuccess) {
102 | NSLog(@"[Impact] Unable to initialize run time exceptions %d", result);
103 | }
104 |
105 | #if IMPACT_MACH_EXCEPTION_SUPPORTED
106 | result = ImpactMachExceptionInitialize(GlobalImpactState);
107 | if (result != ImpactResultSuccess) {
108 | NSLog(@"[Impact] Unable to initialize mach exceptions %d", result);
109 | }
110 | #endif
111 |
112 | atomic_store(&GlobalImpactState->mutableState.crashState, ImpactCrashStateInitialized);
113 |
114 | ImpactDebugLog("[Log:INFO] finished initialization\n");
115 | }
116 |
117 | - (NSString *)OSVersionString {
118 | NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
119 |
120 | return [NSString stringWithFormat:@"%d.%d.%d", (int)version.majorVersion, (int)version.minorVersion, (int)version.patchVersion];
121 | }
122 |
123 | - (NSString *)hardwareModel {
124 | #if TARGET_OS_OSX
125 | char str[128] = {0};
126 | size_t size = sizeof(str);
127 |
128 | const int result = sysctlbyname("hw.model", str, &size, NULL, 0);
129 | if (result != 0) {
130 | return nil;
131 | }
132 |
133 | return [NSString stringWithUTF8String:str];
134 | #else
135 | struct utsname systemInfo = {0};
136 |
137 | const int result = uname(&systemInfo);
138 | if (result != 0) {
139 | return nil;
140 | }
141 |
142 | return [NSString stringWithUTF8String:systemInfo.machine];
143 | #endif
144 | }
145 |
146 | - (NSString *)simulatorModel {
147 | return NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"];
148 | }
149 |
150 | - (const char *)targetTranslated {
151 | int ret = 0;
152 | size_t size = sizeof(ret);
153 |
154 | if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
155 | if (errno == ENOENT) {
156 | return "false";
157 | }
158 |
159 | ImpactDebugLog("[Log:WARN] failed to determine if process is being translated\n");
160 |
161 | return "";
162 | }
163 |
164 | return ret == 1 ? "true" : "false";
165 | }
166 |
167 | - (void)logExecutableData:(ImpactState *)state {
168 | ImpactLogger* log = ImpactStateGetLog(state);
169 |
170 | NSBundle *mainBundle = [NSBundle mainBundle];
171 |
172 | ImpactLogWriteString(log, "[Application] ");
173 |
174 | if (self.applicationIdentifier.length != 0) {
175 | ImpactLogWriteKeyStringObject(log, "id", self.applicationIdentifier, false);
176 | } else {
177 | ImpactLogWriteKeyStringObject(log, "id", [mainBundle bundleIdentifier], false);
178 | }
179 |
180 | ImpactLogWriteKeyStringObject(log, "org_id", self.organizationIdentifier, false);
181 |
182 | ImpactLogWriteKeyStringObject(log, "version", [mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"], false);
183 | ImpactLogWriteKeyStringObject(log, "short_version", [mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"], true);
184 | }
185 |
186 | - (void)logEnvironmentDataWithId:(NSUUID *)identifier state:(ImpactState *)state {
187 | ImpactLogger* log = ImpactStateGetLog(state);
188 |
189 | ImpactLogWriteString(log, "[Environment] ");
190 |
191 | ImpactLogWriteKeyString(log, "platform", ImpactPlatformName, false);
192 |
193 | ImpactLogWriteKeyString(log, "arch", ImpactCPUArchitectureName, false);
194 |
195 | NSString* reportId = [[[identifier UUIDString] stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString];
196 |
197 | ImpactLogWriteKeyString(log, "report_id", reportId.UTF8String, false);
198 | ImpactLogWriteKeyStringObject(log, "install_id", self.installIdentifier, false);
199 |
200 | char str[256] = {0};
201 | size_t size = sizeof(str);
202 |
203 | int result = 0;
204 |
205 | result = sysctlbyname("kern.osversion", str, &size, NULL, 0);
206 | if (result == 0) {
207 | ImpactLogWriteKeyString(log, "os_build", str, false);
208 | } else {
209 | ImpactLogWriteKeyString(log, "os_build", "", false);
210 | }
211 |
212 | ImpactLogWriteKeyStringObject(log, "model", self.hardwareModel , false);
213 |
214 | ImpactLogWriteKeyStringObject(log, "simulated_model", self.simulatorModel, false);
215 |
216 | ImpactLogWriteKeyString(log, "os_version", [self OSVersionString].UTF8String, false);
217 |
218 | ImpactLogWriteKeyString(log, "translated", self.targetTranslated, false);
219 |
220 | ImpactLogWriteKeyString(log, "region", [NSLocale currentLocale].countryCode.UTF8String, false);
221 |
222 | ImpactLogWriteKeyInteger(log, "pid", getpid(), false);
223 | ImpactLogWriteKeyInteger(log, "ppid", getppid(), false);
224 |
225 | ImpactLogWriteTime(log, "time", false);
226 |
227 | ImpactLogWriteKeyInteger(log, "impact_version", [self.class buildNumber], true);
228 | }
229 |
230 | @end
231 |
--------------------------------------------------------------------------------
/Impact/ImpactState.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactState.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-26.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactState.h"
10 | #include "ImpactDebug.h"
11 |
12 | #include
13 |
14 | void ImpactStateTransitionCtx(ImpactState* state, const char* logContext, ImpactCrashState expectedState, ImpactCrashState newState) {
15 | if (atomic_compare_exchange_strong(&state->mutableState.crashState, &expectedState, newState)) {
16 | ImpactDebugLog("[Log:INFO:%s] transition %d -> %d\n", logContext, expectedState, newState);
17 | return;
18 | }
19 |
20 | ImpactDebugLog("[Log:ERROR:%s] state transition failed %d -> %d\n", logContext, expectedState, newState);
21 |
22 | _exit(1);
23 | }
24 |
25 | _Noreturn void ImpactStateInvalidCtx(const char* logContext, ImpactCrashState invalidState) {
26 | ImpactDebugLog("[Log:ERROR:%s] state invalid %d\n", logContext, invalidState);
27 |
28 | _exit(1);
29 | }
30 |
--------------------------------------------------------------------------------
/Impact/ImpactState.h:
--------------------------------------------------------------------------------
1 | #ifndef ImpactState_h
2 | #define ImpactState_h
3 |
4 | #include "ImpactDebug.h"
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | enum { ImpactLogBufferSize = 256 };
13 |
14 | typedef struct {
15 | int fd;
16 | uint32_t bufferCount;
17 | char buffer[ImpactLogBufferSize];
18 | } ImpactLogger;
19 |
20 | enum { ImpactSignalCount = 5 };
21 |
22 | typedef struct {
23 | exception_mask_t masks[EXC_TYPES_COUNT];
24 | exception_handler_t handlers[EXC_TYPES_COUNT];
25 | exception_behavior_t behaviors[EXC_TYPES_COUNT];
26 | thread_state_flavor_t flavors[EXC_TYPES_COUNT];
27 |
28 | mach_msg_type_number_t count;
29 | } ImpactMachExceptionHandlers;
30 |
31 | typedef enum {
32 | ImpactCrashStateUninitialized = 0,
33 | ImpactCrashStateInitialized,
34 | ImpactCrashStateMachException,
35 | ImpactCrashStateMachExceptionReplied,
36 | ImpactCrashStateSignal,
37 | ImpactCrashStateSignalHandled,
38 | ImpactCrashStateSignalAfterMachException,
39 | ImpactCrashStateSignalHandledAfterMachException,
40 | ImpactCrashStateSignalAfterMachExceptionReplied,
41 | ImpactCrashStateMachExceptionAfterSignal,
42 | ImpactCrashStateMachExceptionAfterSignalHandled,
43 | ImpactCrashStateSecondMachException,
44 | ImpactCrashStateSecondMachExceptionReplied,
45 | ImpactCrashStateSecondSignal,
46 | ImpactCrashStateSecondSignalHandled
47 | } ImpactCrashState;
48 |
49 | typedef struct {
50 | struct task_dyld_info dyldInfo;
51 | uint32_t writtenIndex;
52 | uint32_t lastFoundIndex;
53 | } ImpactBinaryImages;
54 |
55 | typedef struct {
56 | // signals
57 | struct sigaction preexistingActions[ImpactSignalCount];
58 |
59 | // mach exception
60 | ImpactMachExceptionHandlers preexistingMachExceptionHandlers;
61 | mach_port_t machExceptionPort;
62 |
63 | // general configuration
64 | bool suppressReportCrash;
65 |
66 | void* preexistingNSExceptionHandler;
67 | } ImpactConstantState;
68 |
69 | typedef struct {
70 | ImpactLogger log;
71 | ImpactBinaryImages images;
72 |
73 | _Atomic ImpactCrashState crashState;
74 | _Atomic uint32_t exceptionCount;
75 | } ImpactMutableState;
76 |
77 | typedef struct {
78 | ImpactConstantState constantState;
79 | ImpactMutableState mutableState;
80 | } ImpactState;
81 |
82 | extern ImpactState* GlobalImpactState;
83 |
84 | static inline ImpactLogger* ImpactStateGetLog(ImpactState* state) {
85 | return &state->mutableState.log;
86 | }
87 |
88 | #include
89 |
90 | void ImpactStateTransitionCtx(ImpactState* state, const char* logContext, ImpactCrashState expectedState, ImpactCrashState newState);
91 | _Noreturn void ImpactStateInvalidCtx(const char* logContext, ImpactCrashState invalidState);
92 |
93 | #define ImpactStateTransition(state, expectedState, newState) ImpactStateTransitionCtx(state, __func__, expectedState, newState)
94 | #define ImpactStateInvalid(invalidState) ImpactStateInvalidCtx(__func__, invalidState)
95 |
96 | #endif /* ImpactState_h */
97 |
--------------------------------------------------------------------------------
/Impact/ImpactThread.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactThread.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-27.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactThread.h"
10 | #include "ImpactUtility.h"
11 | #include "ImpactLog.h"
12 | #include "ImpactCPU.h"
13 | #include "ImpactUnwind.h"
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | ImpactResult ImpactThreadListInitialize(ImpactThreadList* list, thread_act_t crashedThread, const ImpactCPURegisters* crashedThreadRegisters) {
21 | if (ImpactInvalidPtr(list)) {
22 | return ImpactResultArgumentInvalid;
23 | }
24 |
25 | const kern_return_t kr = task_threads(mach_task_self(), &list->threads, &list->count);
26 | if (kr != KERN_SUCCESS) {
27 | ImpactDebugLog("[Log:ERROR] unable to get threads %d\n", kr);
28 | return ImpactResultFailure;
29 | }
30 |
31 | list->threadSelf = mach_thread_self();
32 |
33 | if (crashedThread == ImpactThreadAssumeSelfCrashed) {
34 | list->crashedThread = list->threadSelf;
35 | } else {
36 | list->crashedThread = crashedThread;
37 | }
38 |
39 | if (!MACH_PORT_VALID(list->crashedThread)) {
40 | ImpactDebugLog("[Log:WARN] crashed thread is invalid\n");
41 | }
42 |
43 | list->crashedThreadRegisters = crashedThreadRegisters;
44 |
45 | return ImpactResultSuccess;
46 | }
47 |
48 | ImpactResult ImpactThreadListDeinitialize(ImpactThreadList* list) {
49 | const task_t task = mach_task_self();
50 |
51 | for (mach_msg_type_number_t i = 0; i < list->count; ++i) {
52 | const kern_return_t kr = mach_port_deallocate(task, list->threads[i]);
53 | if (kr != KERN_SUCCESS) {
54 | ImpactDebugLog("[Log:%s] unable to dealloc thread port %d\n", __func__, kr);
55 | }
56 |
57 | list->threads[i] = MACH_PORT_NULL;
58 | }
59 |
60 | const vm_size_t size = sizeof(thread_t) * list->count;
61 |
62 | kern_return_t kr = vm_deallocate(task, (vm_address_t)list->threads, size);
63 | if (kr != KERN_SUCCESS) {
64 | ImpactDebugLog("[Log:%s] unable to dealloc thread storage %d\n", __func__, kr);
65 | }
66 |
67 | kr = mach_port_deallocate(task, list->threadSelf);
68 | if (kr != KERN_SUCCESS) {
69 | ImpactDebugLog("[Log:%s] unable to dealloc self thread port %d\n", __func__, kr);
70 | }
71 |
72 | return ImpactResultSuccess;
73 | }
74 |
75 | ImpactResult ImpactThreadGetState(const ImpactThreadList* list, thread_act_t thread, ImpactCPURegisters* registers) {
76 | if (ImpactInvalidPtr(list) || ImpactInvalidPtr(registers)) {
77 | return ImpactResultArgumentInvalid;
78 | }
79 |
80 | if (MACH_PORT_VALID(list->crashedThread) && thread == list->crashedThread && !ImpactInvalidPtr(list->crashedThreadRegisters)) {
81 | *registers = *list->crashedThreadRegisters;
82 | return ImpactResultSuccess;
83 | }
84 |
85 | #if IMPACT_THREADS_SUPPORTED
86 | mach_msg_type_number_t count = ImpactCPUThreadStateCount;
87 | thread_state_t state = (thread_state_t)®isters->__ss;
88 |
89 | const kern_return_t kr = thread_get_state(thread, ImpactCPUThreadStateFlavor, state, &count);
90 | if (kr != KERN_SUCCESS) {
91 | ImpactDebugLog("[Log:%s] unable to read thread state %d\n", __func__, kr);
92 | return ImpactResultFailure;
93 | }
94 |
95 | return ImpactResultSuccess;
96 | #else
97 | return ImpactResultFailure;
98 | #endif
99 | }
100 |
101 | #if IMPACT_THREADS_SUPPORTED
102 | static ImpactResult ImpactThreadListSuspendAllExceptForCurrent(const ImpactThreadList* list) {
103 | if (ImpactInvalidPtr(list)) {
104 | return ImpactResultArgumentInvalid;
105 | }
106 |
107 | for (mach_msg_type_number_t i = 0; i < list->count; ++i) {
108 | const thread_act_t thread = list->threads[i];
109 |
110 | if (thread == list->threadSelf) {
111 | continue;
112 | }
113 |
114 | const kern_return_t kr = thread_suspend(thread);
115 | if (kr != KERN_SUCCESS) {
116 | ImpactDebugLog("[Log:%s] failed to suspend thread %d\n", __func__, kr);
117 | }
118 | }
119 |
120 | return ImpactResultSuccess;
121 | }
122 |
123 | static ImpactResult ImpactThreadListResumeAllExceptForCurrent(const ImpactThreadList* list) {
124 | if (ImpactInvalidPtr(list)) {
125 | return ImpactResultArgumentInvalid;
126 | }
127 |
128 | for (mach_msg_type_number_t i = 0; i < list->count; ++i) {
129 | const thread_act_t thread = list->threads[i];
130 |
131 | if (thread == list->threadSelf) {
132 | continue;
133 | }
134 |
135 | const kern_return_t kr = thread_resume(thread);
136 | if (kr != KERN_SUCCESS) {
137 | ImpactDebugLog("[Log:%s] failed to suspend thread %d\n", __func__, kr);
138 | }
139 | }
140 |
141 | return ImpactResultSuccess;
142 | }
143 | #endif
144 |
145 | static ImpactResult ImpactThreadLogFrame(ImpactState* state, const ImpactCPURegisters* registers) {
146 | if (ImpactInvalidPtr(state) || ImpactInvalidPtr(registers)) {
147 | return ImpactResultArgumentInvalid;
148 | }
149 |
150 | ImpactLogger* log = ImpactStateGetLog(state);
151 |
152 | ImpactLogWriteString(log, "[Thread:Frame] ");
153 |
154 | uintptr_t value = 0;
155 | ImpactResult result;
156 |
157 | result = ImpactCPUGetRegister(registers, ImpactCPURegisterInstructionPointer, &value);
158 | if (result != ImpactResultSuccess) {
159 | return result;
160 | }
161 |
162 | ImpactLogWriteKeyInteger(log, "ip", value, false);
163 |
164 | result = ImpactCPUGetRegister(registers, ImpactCPURegisterStackPointer, &value);
165 | if (result != ImpactResultSuccess) {
166 | return result;
167 | }
168 |
169 | ImpactLogWriteKeyInteger(log, "sp", value, false);
170 |
171 | result = ImpactCPUGetRegister(registers, ImpactCPURegisterFramePointer, &value);
172 | if (result != ImpactResultSuccess) {
173 | return result;
174 | }
175 |
176 | ImpactLogWriteKeyInteger(log, "fp", value, true);
177 |
178 | return ImpactResultSuccess;
179 | }
180 |
181 | static ImpactResult ImpactThreadLogStacktrace(ImpactState* state, const ImpactCPURegisters* registers) {
182 | if (ImpactInvalidPtr(state) || ImpactInvalidPtr(registers)) {
183 | return ImpactResultArgumentInvalid;
184 | }
185 |
186 | ImpactCPURegisters unwindRegisters = *registers;
187 |
188 | // for now, impose a limit on how many frames we write out
189 | for (uint32_t i = 0; i < 512; ++i) {
190 | ImpactResult result = ImpactThreadLogFrame(state, &unwindRegisters);
191 | if (result != ImpactResultSuccess) {
192 | ImpactDebugLog("[Log:%s] failed to write frame %x\n", __func__, result);
193 | }
194 |
195 | result = ImpactUnwindStepRegisters(state, &unwindRegisters);
196 | switch (result) {
197 | case ImpactResultEndOfStack:
198 | return ImpactResultSuccess;
199 | case ImpactResultSuccess:
200 | break;
201 | default:
202 | ImpactDebugLog("[Log:WARN] failed to step registers %x\n", result);
203 | return result;
204 | }
205 | }
206 |
207 | ImpactDebugLog("[Log:%s] exceeded maximum number of frames\n", __func__);
208 |
209 | return ImpactResultFailure;
210 | }
211 |
212 | ImpactResult ImpactThreadLog(ImpactState* state, const ImpactThreadList* list, thread_act_t thread) {
213 | if (ImpactInvalidPtr(state) || ImpactInvalidPtr(list)) {
214 | return ImpactResultArgumentInvalid;
215 | }
216 |
217 | ImpactCPURegisters registers = {0};
218 |
219 | ImpactResult result = ImpactThreadGetState(list, thread, ®isters);
220 | if (result != ImpactResultSuccess) {
221 | ImpactDebugLog("[Log:%s] failed to get thread state %d\n", __func__, result);
222 | return result;
223 | }
224 |
225 | result = ImpactCPURegistersLog(state, ®isters);
226 | if (result != ImpactResultSuccess) {
227 | ImpactDebugLog("[Log:%s] failed to log thread state %d\n", __func__, result);
228 | }
229 |
230 | if (thread == list->crashedThread && MACH_PORT_VALID(thread)) {
231 | ImpactLogger* log = ImpactStateGetLog(state);
232 |
233 | ImpactLogWriteString(log, "[Thread:Crashed]\n");
234 | ImpactLogFlush(log);
235 | }
236 |
237 | result = ImpactThreadLogStacktrace(state, ®isters);
238 | if (result != ImpactResultSuccess) {
239 | ImpactDebugLog("[Log:%s] failed to log thread stack trace %d\n", __func__, result);
240 | }
241 |
242 | return ImpactResultSuccess;
243 | }
244 |
245 | ImpactResult ImpactThreadListLog(ImpactState* state, const ImpactThreadList* list) {
246 | ImpactResult result = ImpactResultFailure;
247 |
248 | #if IMPACT_THREADS_SUPPORTED
249 | result = ImpactThreadListSuspendAllExceptForCurrent(list);
250 | #endif
251 |
252 | for (mach_msg_type_number_t i = 0; i < list->count; ++i) {
253 | const thread_act_t thread = list->threads[i];
254 |
255 | result = ImpactThreadLog(state, list, thread);
256 | if (result != ImpactResultSuccess) {
257 | ImpactDebugLog("[Log:%s] failed to log thread %d %d\n", __func__, i, result);
258 | }
259 | }
260 |
261 | #if IMPACT_THREADS_SUPPORTED
262 | result = ImpactThreadListResumeAllExceptForCurrent(list);
263 | #endif
264 |
265 | return ImpactResultSuccess;
266 | }
267 |
--------------------------------------------------------------------------------
/Impact/ImpactThread.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactThread.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-27.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactThread_h
10 | #define ImpactThread_h
11 |
12 | #include "ImpactResult.h"
13 | #include "ImpactState.h"
14 | #include "ImpactCPU.h"
15 |
16 | #include
17 |
18 | #define IMPACT_THREADS_SUPPORTED (TARGET_OS_OSX || TARGET_OS_IOS)
19 |
20 | #include
21 |
22 | typedef struct {
23 | thread_act_array_t threads;
24 | mach_msg_type_number_t count;
25 |
26 | thread_act_t threadSelf;
27 | thread_act_t crashedThread;
28 | const ImpactCPURegisters* crashedThreadRegisters;
29 | } ImpactThreadList;
30 |
31 | static const thread_act_t ImpactThreadAssumeSelfCrashed = MACH_PORT_NULL;
32 |
33 | ImpactResult ImpactThreadListInitialize(ImpactThreadList* list, thread_act_t crashedThread, const ImpactCPURegisters* crashedThreadRegisters);
34 | ImpactResult ImpactThreadListDeinitialize(ImpactThreadList* list);
35 | ImpactResult ImpactThreadListLog(ImpactState* state, const ImpactThreadList* list);
36 |
37 | #endif /* ImpactThread_h */
38 |
--------------------------------------------------------------------------------
/Impact/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2019 Chime Systems Inc. All rights reserved.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactCrashHandler.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCrashHandler.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-19.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactCrashHandler.h"
10 | #include "ImpactUtility.h"
11 | #include "ImpactThread.h"
12 | #include "ImpactBinaryImage.h"
13 |
14 | #include
15 |
16 | ImpactResult ImpactCrashHandler(ImpactState* state, thread_act_t crashedThread, const ImpactCPURegisters* registers) {
17 | if (ImpactInvalidPtr(state)) {
18 | return ImpactResultArgumentInvalid;
19 | }
20 |
21 | ImpactDebugLog("[Log:INFO] entering the crash handler\n");
22 |
23 | ImpactThreadList list = {0};
24 |
25 | ImpactResult result = ImpactThreadListInitialize(&list, crashedThread, registers);
26 | if (result != ImpactResultSuccess) {
27 | ImpactDebugLog("[Log:ERROR:%s] unable to initialize thread list %d\n", __func__, result);
28 | return result;
29 | }
30 |
31 | result = ImpactThreadListLog(state, &list);
32 | if (result != ImpactResultSuccess) {
33 | ImpactDebugLog("[Log:ERROR:%s] unable to log thread list %d\n", __func__, result);
34 | return result;
35 | }
36 |
37 | result = ImpactBinaryImageLogRemainingImages(state);
38 | if (result != ImpactResultSuccess) {
39 | ImpactDebugLog("[Log:ERROR] unable to log remaining images %d\n", result);
40 | return result;
41 | }
42 |
43 | ImpactDebugLog("[Log:INFO] exiting the crash handler\n");
44 |
45 | if (state->constantState.suppressReportCrash) {
46 | // There are lots of exit functions. Only _exit is listed
47 | // as safe to call from a signal handler.
48 |
49 | ImpactDebugLog("[Log:WARN:%s] suppressing ReportCrash\n", __func__);
50 |
51 | _exit(0);
52 | return ImpactResultSuccess;
53 | }
54 |
55 | return ImpactResultSuccess;
56 | }
57 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactCrashHandler.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCrashHandler.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-19.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef CrashHandler_h
10 | #define CrashHandler_h
11 |
12 | #include "ImpactResult.h"
13 | #include "ImpactCPU.h"
14 | #include "ImpactState.h"
15 |
16 | ImpactResult ImpactCrashHandler(ImpactState* state, thread_act_t crashedThread, const ImpactCPURegisters* registers);
17 |
18 | #endif /* CrashHandler_h */
19 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactMachException.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactMachException.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-19.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactMachException_h
10 | #define ImpactMachException_h
11 |
12 | #include "ImpactResult.h"
13 | #include "ImpactState.h"
14 |
15 | #include
16 |
17 | #define IMPACT_MACH_EXCEPTION_SUPPORTED (TARGET_OS_OSX || TARGET_OS_IOS)
18 |
19 | #if IMPACT_MACH_EXCEPTION_SUPPORTED
20 |
21 | ImpactResult ImpactMachExceptionInitialize(ImpactState* state);
22 |
23 | #endif
24 |
25 | #endif /* ImpactMachException_h */
26 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactMonitoredApplication.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactMonitoredApplication.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-30.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #if TARGET_OS_OSX
12 | #import
13 |
14 | NS_ASSUME_NONNULL_BEGIN
15 |
16 | @interface ImpactMonitoredApplication : NSApplication
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactMonitoredApplication.m:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactMonitoredApplication.m
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-30.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "ImpactMonitoredApplication.h"
10 | #import "ImpactRuntimeException.h"
11 |
12 | #if TARGET_OS_OSX
13 |
14 | @implementation ImpactMonitoredApplication
15 |
16 | // AppKit is a real pain when it comes to exceptions. It wraps many calls in @try/@catch
17 | // and silently ignores exceptions by default. This is not good.
18 |
19 | - (void)sendEvent:(NSEvent *)event {
20 | @try {
21 | [super sendEvent:event];
22 | } @catch(NSException *e) {
23 | [self reportException:e];
24 | @throw e;
25 | }
26 | }
27 |
28 | - (void)reportException:(NSException *)exception {
29 | ImpactDebugLog("[Log:INFO] ImpactMonitoredApplication reporting exception\n");
30 | ImpactRuntimeExceptionLogNSException(exception);
31 |
32 | [super reportException:exception];
33 | }
34 |
35 | @end
36 |
37 | #endif
38 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactRuntimeException.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactRuntimeException.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-30.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactRuntimeException_h
10 | #define ImpactRuntimeException_h
11 |
12 | #include "ImpactState.h"
13 | #include "ImpactResult.h"
14 |
15 | @class NSException;
16 |
17 | __BEGIN_DECLS
18 |
19 | ImpactResult ImpactRuntimeExceptionInitialize(ImpactState * state);
20 |
21 | void ImpactRuntimeExceptionLogNSException(NSException* exception);
22 |
23 | __END_DECLS
24 |
25 |
26 | #endif /* ImpactRuntimeException_h */
27 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactRuntimeException.mm:
--------------------------------------------------------------------------------
1 | #include "ImpactRuntimeException.h"
2 | #include "ImpactLog.h"
3 | #include "ImpactUtility.h"
4 | #include "ImpactSignal.h"
5 | #include "ImpactBinaryImage.h"
6 |
7 | #import
8 |
9 | static void ImpactNSUncaughtExceptionExceptionHandler(NSException *exception) {
10 | ImpactDebugLog("[Log:INFO] uncaught exception\n");
11 | ImpactRuntimeExceptionLogNSException(exception);
12 |
13 | ImpactState* state = GlobalImpactState;
14 |
15 | if (ImpactInvalidPtr(state)) {
16 | return;
17 | }
18 |
19 | NSUncaughtExceptionHandler* existing = (NSUncaughtExceptionHandler*)state->constantState.preexistingNSExceptionHandler;
20 |
21 | if (ImpactInvalidPtr((void*)existing)) {
22 | ImpactDebugLog("[Log:WARN] invalid existing NSUncaughtExceptionHandler\n");
23 | return;
24 | }
25 |
26 | existing(exception);
27 | }
28 |
29 | ImpactResult ImpactRuntimeExceptionInitialize(ImpactState* state) {
30 | atomic_store(&state->mutableState.exceptionCount, 1);
31 |
32 | state->constantState.preexistingNSExceptionHandler = (void*)NSGetUncaughtExceptionHandler();
33 |
34 | NSSetUncaughtExceptionHandler(ImpactNSUncaughtExceptionExceptionHandler);
35 |
36 | return ImpactResultSuccess;
37 | }
38 |
39 | void ImpactRuntimeExceptionLogNSException(NSException* exception) {
40 | ImpactState* state = GlobalImpactState;
41 |
42 | if (ImpactInvalidPtr(state)) {
43 | return;
44 | }
45 |
46 | if (atomic_fetch_add(&state->mutableState.exceptionCount, 1) > 1) {
47 | ImpactDebugLog("[Log:WARN] subsequent invocation of ImpactRuntimeExceptionLogNSException ignored\n");
48 | return;
49 | }
50 |
51 | ImpactLogger* log = ImpactStateGetLog(state);
52 |
53 | if (!ImpactLogIsValid(log)) {
54 | return;
55 | }
56 |
57 | ImpactLogWriteString(log, "[Exception] ");
58 | ImpactLogWriteKeyString(log, "type", "objc", false);
59 | ImpactLogWriteKeyStringObject(log, "name", exception.name, false);
60 | ImpactLogWriteKeyStringObject(log, "message", exception.reason, false);
61 | ImpactLogWriteTime(log, "time", true);
62 |
63 | ImpactMachOData imageData = {0};
64 |
65 | for (NSNumber *address in exception.callStackReturnAddresses) {
66 | const uintptr_t addr = address.unsignedIntegerValue;
67 |
68 | ImpactLogWriteString(log, "[Exception:Frame] ");
69 | ImpactLogWriteKeyInteger(log, "ip", addr, true);
70 |
71 | ImpactBinaryImageFind(state, addr - 1, &imageData);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactSignal.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactSignal.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactSignal.h"
10 | #include "ImpactLog.h"
11 | #include "ImpactCrashHandler.h"
12 | #include "ImpactUtility.h"
13 | #include "ImpactThread.h"
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | static const int ImpactHandledSignals[ImpactSignalCount] = {
21 | SIGBUS,
22 | SIGABRT,
23 | SIGILL,
24 | SIGSEGV,
25 | SIGSYS
26 | };
27 |
28 | static void ImpactSignalHandler(int signal, siginfo_t* info, ucontext_t* uap);
29 |
30 | ImpactResult ImpactSignalInitialize(ImpactState* state) {
31 | sigset_t set = {0};
32 |
33 | sigemptyset(&set);
34 |
35 | // We have to carefully consider what happens when a subsequent
36 | // signal is devliered while our handler is running. Including a
37 | // signal caused by the handler itself.
38 | //
39 | // SA_RESETHAND ensures that a second identical signal will
40 | // not invoke our handler.
41 | //
42 | // SA_NODEFER ensures that the signal we are handling can
43 | // be raised during our handler.
44 | const struct sigaction action = {
45 | (void*)ImpactSignalHandler,
46 | set,
47 | SA_SIGINFO | SA_RESETHAND | SA_NODEFER
48 | };
49 |
50 | bool someFailed = false;
51 |
52 | for (uint32_t i = 0; i < ImpactSignalCount; ++i) {
53 | const int signal = ImpactHandledSignals[i];
54 | struct sigaction* preexistingAction = &state->constantState.preexistingActions[i];
55 |
56 | const int result = sigaction(signal, &action, preexistingAction);
57 | if (result != 0) {
58 | someFailed = true;
59 | }
60 | }
61 |
62 | return someFailed ? ImpactResultFailure : ImpactResultSuccess;
63 | }
64 |
65 | static ImpactResult ImpactSignalInstallDefaultHandlers(void) {
66 | bool someFailed = false;
67 |
68 | struct sigaction action = {0};
69 |
70 | action.sa_flags = 0;
71 | sigemptyset(&action.sa_mask);
72 | action.sa_handler = SIG_DFL;
73 |
74 | for (uint32_t i = 0; i < ImpactSignalCount; ++i) {
75 | const int signal = ImpactHandledSignals[i];
76 |
77 | int result = sigaction(signal, &action, NULL);
78 | if (result != 0) {
79 | ImpactDebugLog("[Log:%s] unable to install default signal handler %d %d\n", __func__, signal, result);
80 | someFailed = true;
81 | }
82 | }
83 |
84 | return someFailed ? ImpactResultFailure : ImpactResultSuccess;
85 | }
86 |
87 | static ImpactResult ImpactSignalRestorePreexisting(const ImpactState* state) {
88 | if (ImpactInvalidPtr(state)) {
89 | return ImpactResultArgumentInvalid;
90 | }
91 |
92 | bool someFailed = false;
93 |
94 | for (uint32_t i = 0; i < ImpactSignalCount; ++i) {
95 | const int signal = ImpactHandledSignals[i];
96 | const struct sigaction action = state->constantState.preexistingActions[i];
97 |
98 | int result = sigaction(signal, &action, NULL);
99 | if (result != 0) {
100 | ImpactDebugLog("[Log:%s] unable to restore signal handler %d %d\n", __func__, signal, result);
101 | someFailed = true;
102 | }
103 | }
104 |
105 | return someFailed ? ImpactResultFailure : ImpactResultSuccess;
106 | }
107 |
108 | ImpactResult ImpactSignalUninstallHandlers(const ImpactState* state) {
109 | ImpactResult result = ImpactSignalRestorePreexisting(state);
110 | if (result == ImpactResultSuccess) {
111 | return result;
112 | }
113 |
114 | ImpactDebugLog("[Log:%s] failed to restore preexisting handlers %d\n", __func__, result);
115 |
116 | // ok, fall back to removing all handlers
117 | return ImpactSignalInstallDefaultHandlers();
118 | }
119 |
120 | static ImpactResult ImpactSignalLog(ImpactState* state, int signal, siginfo_t* info) {
121 | ImpactLogger* log = ImpactStateGetLog(state);
122 |
123 | ImpactLogWriteString(log, "[Signal] ");
124 |
125 | if (ImpactInvalidPtr(info)) {
126 | ImpactLogWriteKeyInteger(log, "signal", signal, true);
127 | } else {
128 | ImpactLogWriteKeyInteger(log, "signal", signal, false);
129 | ImpactLogWriteKeyInteger(log, "code", info->si_code, false);
130 | ImpactLogWriteKeyPointer(log, "address", info->si_addr, false);
131 | ImpactLogWriteTime(log, "time", false);
132 | ImpactLogWriteKeyInteger(log, "errno", info->si_errno, true);
133 | }
134 |
135 | return ImpactResultSuccess;
136 | }
137 |
138 | static void ImpactSignalHandlerEntranceAdjustState(ImpactState* state, ImpactCrashState *currentState) {
139 | if (ImpactInvalidPtr(state) || ImpactInvalidPtr(currentState)) {
140 | return;
141 | }
142 |
143 | *currentState = atomic_load(&state->mutableState.crashState);
144 | switch (*currentState) {
145 | case ImpactCrashStateInitialized:
146 | ImpactStateTransition(state, *currentState, ImpactCrashStateSignal);
147 | break;
148 | case ImpactCrashStateMachException:
149 | ImpactStateTransition(state, *currentState, ImpactCrashStateSignalAfterMachException);
150 | break;
151 | case ImpactCrashStateMachExceptionReplied:
152 | ImpactStateTransition(state, *currentState, ImpactCrashStateSignalAfterMachExceptionReplied);
153 | break;
154 | default:
155 | ImpactStateInvalid(*currentState);
156 | break;
157 | }
158 | }
159 |
160 | static void ImpactSignalHandlerExitAdjustState(ImpactState* state) {
161 | if (ImpactInvalidPtr(state)) {
162 | return;
163 | }
164 |
165 | ImpactCrashState currentState = atomic_load(&state->mutableState.crashState);
166 | switch (currentState) {
167 | case ImpactCrashStateSignal:
168 | ImpactStateTransition(state, currentState, ImpactCrashStateSignalHandled);
169 | break;
170 | case ImpactCrashStateSignalAfterMachException:
171 | case ImpactCrashStateSignalAfterMachExceptionReplied:
172 | ImpactStateTransition(state, currentState, ImpactCrashStateSignalHandledAfterMachException);
173 | break;
174 | default:
175 | ImpactStateInvalid(currentState);
176 | break;
177 | }
178 | }
179 |
180 | static void ImpactSignalHandler(int signal, siginfo_t* info, ucontext_t* uap) {
181 | // save the current thread's errno before we make any calls that might
182 | // have the side-effect of changing it
183 | int savedErrno = errno;
184 | ImpactState* state = GlobalImpactState;
185 |
186 | // If our state has got bad, what can we reasonably do?
187 | if (ImpactInvalidPtr(state)) {
188 | return;
189 | }
190 |
191 | // Attempt to put back whatever handlers were in place when we started. It is
192 | // important to give previous handlers a chance to run, particularly if we fail
193 | // at some point. This is why we do this early.
194 |
195 | ImpactResult result = ImpactSignalUninstallHandlers(state);
196 | if (result != ImpactResultSuccess) {
197 | ImpactDebugLog("[Log:%s] failed to uninstall handlers %d\n", __func__, result);
198 | }
199 |
200 | // At this point, any signal, including the current, could be
201 | // reraised and our handler would not be invoked. This protects us
202 | // from a crash loop within the handler.
203 |
204 | // log signal first, before adjusting state. Helpful to have this in the log.
205 | ImpactSignalLog(state, signal, info);
206 |
207 | ImpactCrashState currentState = ImpactCrashStateUninitialized;
208 | ImpactSignalHandlerEntranceAdjustState(state, ¤tState);
209 |
210 | // we only trigger the crash handler on our first invocation
211 | if (currentState == ImpactCrashStateInitialized) {
212 | if (ImpactInvalidPtr(uap)) {
213 | ImpactDebugLog("[Log:Error] %suap pointer invalid\n", __func__);
214 | } else {
215 | ImpactCrashHandler(state, ImpactThreadAssumeSelfCrashed, uap->uc_mcontext);
216 | }
217 |
218 | }
219 | ImpactSignalHandlerExitAdjustState(state);
220 |
221 | // restore errno before allowing signal to be re-raised
222 | errno = savedErrno;
223 | }
224 |
--------------------------------------------------------------------------------
/Impact/Monitoring/ImpactSignal.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactSignal.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactSignal_h
10 | #define ImpactSignal_h
11 |
12 | #include "ImpactResult.h"
13 | #include "ImpactState.h"
14 |
15 | ImpactResult ImpactSignalInitialize(ImpactState* state);
16 | ImpactResult ImpactSignalUninstallHandlers(const ImpactState* state);
17 |
18 | #endif /* ImpactSignal_h */
19 |
--------------------------------------------------------------------------------
/Impact/Unwind/ImpactCompactUnwind.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCompactUnwind.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-03.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactCompactUnwind.h"
10 | #include "ImpactUtility.h"
11 | #include "ImpactUnwind.h"
12 | #include "ImpactState.h"
13 | #include "ImpactLog.h"
14 | #include "ImpactDWARF.h"
15 |
16 | #include
17 |
18 | typedef struct unwind_info_section_header CompactUnwindHeader;
19 | typedef struct unwind_info_section_header_index_entry CompactUnwindIndexEntry;
20 | typedef struct unwind_info_compressed_second_level_page_header CompactUnwindCompressedHeader;
21 |
22 | static uint32_t ImpactCompactUnwindSearch(const void* ptr, const void* ctx, size_t elementSize, uint32_t count, bool (*comparsionFn) (const void *, const void *));
23 | static bool ImpactCompactUnwindCompareIndexEntry(const void *a, const void *b);
24 | static bool ImpactCompactUnwindCompareCompressedEntry(const void *a, const void *b);
25 |
26 | ImpactResult ImpactCompactUnwindLookupFirstLevel(ImpactCompactUnwindTarget target, const struct unwind_info_section_header_index_entry** index) {
27 | if (ImpactInvalidPtr(index)) {
28 | return ImpactResultPointerInvalid;
29 | }
30 |
31 | const uintptr_t imageRelativeAddress = target.address - target.imageLoadAddress;
32 |
33 | *index = ImpactPointerOffset(target.header, target.header->indexSectionOffset);
34 |
35 | const uint32_t count = target.header->indexCount;
36 | const size_t size = sizeof(struct unwind_info_section_header_index_entry);
37 |
38 | uint32_t idx = ImpactCompactUnwindSearch(*index, &imageRelativeAddress, size, count, ImpactCompactUnwindCompareIndexEntry);
39 | if (idx == count) {
40 | // the last index is actually a limit, so we do not need to check
41 | // if that's actually our match
42 | *index = NULL;
43 |
44 | return ImpactResultFailure;
45 | }
46 |
47 | if (idx <= 0) {
48 | return ImpactResultFailure;
49 | }
50 |
51 | *index = ImpactPointerOffset(*index, (idx - 1) * size);
52 |
53 | return ImpactResultSuccess;
54 | }
55 |
56 | ImpactResult ImpactCompactUnwindLookupSecondLevelRegular(ImpactCompactUnwindTarget target, const CompactUnwindIndexEntry* index, const compact_unwind_encoding_t** encoding) {
57 | return ImpactResultUnimplemented;
58 | }
59 |
60 | ImpactResult ImpactCompactUnwindLookupSecondLevelCompressedEntry(ImpactCompactUnwindTarget target, const CompactUnwindIndexEntry* index, const compact_unwind_encoding_t** encoding) {
61 | if (ImpactInvalidPtr(index) || ImpactInvalidPtr(encoding)) {
62 | return ImpactResultPointerInvalid;
63 | }
64 |
65 | const CompactUnwindCompressedHeader* compressedHeader = ImpactPointerOffset(target.header, index->secondLevelPagesSectionOffset);
66 | if (compressedHeader->kind != UNWIND_SECOND_LEVEL_COMPRESSED) {
67 | ImpactDebugLog("[Log:WARN:%s] compressed header kind invalid %d\n", __func__, compressedHeader->kind);
68 | return ImpactResultInconsistentData;
69 | }
70 |
71 | const uintptr_t imageRelativeAddress = target.address - target.imageLoadAddress;
72 |
73 | if (imageRelativeAddress < index->functionOffset) {
74 | ImpactDebugLog("[Log:WARN:%s] address not in range\n", __func__);
75 | return ImpactResultInconsistentData;
76 | }
77 |
78 | const uintptr_t targetFunctionOffset = imageRelativeAddress - index->functionOffset;
79 |
80 | *encoding = ImpactPointerOffset(compressedHeader, compressedHeader->entryPageOffset);
81 |
82 | const uint32_t count = compressedHeader->entryCount;
83 | const size_t size = sizeof(compact_unwind_encoding_t);
84 |
85 | const uint32_t idx = ImpactCompactUnwindSearch(*encoding, &targetFunctionOffset, size, count, ImpactCompactUnwindCompareCompressedEntry);
86 | if (idx == count) {
87 | // we have to special-case the last entry, in case that's out match
88 | const CompactUnwindIndexEntry* nextIndex = index + 1;
89 |
90 | if (imageRelativeAddress >= nextIndex->functionOffset) {
91 | *encoding = NULL;
92 | ImpactDebugLog("[Log:WARN:%s] address not in within found function\n", __func__);
93 | return ImpactResultFailure;
94 | }
95 | }
96 |
97 | if (idx <= 0) {
98 | return ImpactResultFailure;
99 | }
100 |
101 | // remember, the returned result will be the first *past* our match
102 | *encoding = ImpactPointerOffset(*encoding, (idx - 1) * size);
103 |
104 | return ImpactResultSuccess;
105 | }
106 |
107 | ImpactResult ImpactCompactUnwindLookupSecondLevelEncoding(const CompactUnwindHeader* header, const CompactUnwindCompressedHeader* secondLevelHeader, uint16_t encodingIndex, const compact_unwind_encoding_t** encoding) {
108 | if (ImpactInvalidPtr(header) || ImpactInvalidPtr(encoding)) {
109 | return ImpactResultPointerInvalid;
110 | }
111 |
112 | if (encodingIndex < header->commonEncodingsArrayCount) {
113 | const uintptr_t offset = encodingIndex * sizeof(compact_unwind_encoding_t);
114 |
115 | *encoding = ImpactPointerOffset(header, header->commonEncodingsArraySectionOffset + offset);
116 |
117 | return ImpactResultSuccess;
118 | }
119 |
120 | if (ImpactInvalidPtr(secondLevelHeader)) {
121 | return ImpactResultPointerInvalid;
122 | }
123 |
124 | const uint16_t index = encodingIndex - header->commonEncodingsArrayCount;
125 | const uintptr_t offset = index * sizeof(compact_unwind_encoding_t);
126 |
127 | *encoding = ImpactPointerOffset(secondLevelHeader, secondLevelHeader->encodingsPageOffset + offset);
128 |
129 | return ImpactResultSuccess;
130 | }
131 |
132 | ImpactResult ImpactCompactUnwindLookupSecondLevelCompressed(ImpactCompactUnwindTarget target, const CompactUnwindIndexEntry* index, const compact_unwind_encoding_t** encoding) {
133 | ImpactResult result = ImpactCompactUnwindLookupSecondLevelCompressedEntry(target, index, encoding);
134 | if (result != ImpactResultSuccess) {
135 | return result;
136 | }
137 |
138 | if (ImpactInvalidPtr(encoding) || ImpactInvalidPtr(target.header) || ImpactInvalidPtr(index)) {
139 | return ImpactResultPointerInvalid;
140 | }
141 |
142 | uint16_t encodingIndex = UNWIND_INFO_COMPRESSED_ENTRY_ENCODING_INDEX(**encoding);
143 | const CompactUnwindCompressedHeader* compressedHeader = ImpactPointerOffset(target.header, index->secondLevelPagesSectionOffset);
144 |
145 | return ImpactCompactUnwindLookupSecondLevelEncoding(target.header, compressedHeader, encodingIndex, encoding);
146 | }
147 |
148 | ImpactResult ImpactCompactUnwindLookupSecondLevel(ImpactCompactUnwindTarget target, const CompactUnwindIndexEntry* index, const compact_unwind_encoding_t** encoding) {
149 | const struct unwind_info_regular_second_level_page_header* secondLevelHeader = ImpactPointerOffset(target.header, index->secondLevelPagesSectionOffset);
150 |
151 | switch (secondLevelHeader->kind) {
152 | case UNWIND_SECOND_LEVEL_REGULAR:
153 | return ImpactCompactUnwindLookupSecondLevelRegular(target, index, encoding);
154 | case UNWIND_SECOND_LEVEL_COMPRESSED:
155 | return ImpactCompactUnwindLookupSecondLevelCompressed(target, index, encoding);
156 | default:
157 | break;
158 | }
159 |
160 | return ImpactResultInconsistentData;
161 | }
162 |
163 | ImpactResult ImpactCompactUnwindLookupEncoding(ImpactCompactUnwindTarget target, compact_unwind_encoding_t* encoding) {
164 | if (ImpactInvalidPtr(target.header) || ImpactInvalidPtr(encoding)) {
165 | return ImpactResultPointerInvalid;
166 | }
167 |
168 | if (target.header->version != UNWIND_SECTION_VERSION) {
169 | ImpactDebugLog("[Log:INFO:%s] compact unwind version invalid %d\n", __func__, target.header->version);
170 |
171 | return ImpactResultInconsistentData;
172 | }
173 |
174 | const CompactUnwindIndexEntry *firstLevelEntry = NULL;
175 | ImpactResult result;
176 |
177 | result = ImpactCompactUnwindLookupFirstLevel(target, &firstLevelEntry);
178 | if (result != ImpactResultSuccess) {
179 | ImpactDebugLog("[Log:INFO:%s] failed to lookup first level encoding %d\n", __func__, result);
180 | return result;
181 | }
182 |
183 | const compact_unwind_encoding_t* encodingPtr = NULL;
184 | result = ImpactCompactUnwindLookupSecondLevel(target, firstLevelEntry, &encodingPtr);
185 | if (result != ImpactResultSuccess) {
186 | ImpactDebugLog("[Log:INFO:%s] failed to lookup second level encoding %d\n", __func__, result);
187 | return result;
188 | }
189 |
190 | *encoding = *encodingPtr;
191 |
192 | return ImpactResultSuccess;
193 | }
194 |
195 | ImpactResult ImpactCompactUnwindStepRegisters(ImpactCompactUnwindTarget target, ImpactCPURegisters* registers, uint32_t* dwarfFDEOffset) {
196 | compact_unwind_encoding_t encoding = 0;
197 |
198 | ImpactResult result = ImpactCompactUnwindLookupEncoding(target, &encoding);
199 | if (result != ImpactResultSuccess) {
200 | ImpactDebugLog("[Log:WARN] failed to look up compact unwind encoding %d\n", result);
201 | return result;
202 | }
203 |
204 | if (encoding == 0) {
205 | ImpactDebugLog("[Log:INFO] no unwind info available\n");
206 |
207 | return ImpactResultMissingUnwindInfo;
208 | }
209 |
210 | ImpactDebugLog("[Log:INFO] found compact unwind encoding 0x%x\n", encoding);
211 |
212 | return ImpactCompactUnwindStepArchRegisters(target, registers, encoding, dwarfFDEOffset);
213 | }
214 |
215 | static uint32_t ImpactCompactUnwindSearch(const void* ptr, const void* ctx, size_t elementSize, uint32_t count, bool (*comparsionFn) (const void *, const void *)) {
216 | uint32_t low = 0;
217 | uint32_t high = count;
218 |
219 | while (low < high) {
220 | const uint32_t mid = (low + high) / 2;
221 |
222 | const void *entry = ImpactPointerOffset(ptr, mid * elementSize);
223 |
224 | if (comparsionFn(ctx, entry)) {
225 | high = mid;
226 | } else {
227 | low = mid + 1;
228 | }
229 | }
230 |
231 | if (low > high) {
232 | return count;
233 | }
234 |
235 | return low;
236 | }
237 |
238 | static bool ImpactCompactUnwindCompareIndexEntry(const void *a, const void *b) {
239 | const uintptr_t targetOffset = *(uintptr_t *)a;
240 | const struct unwind_info_section_header_index_entry* index = b;
241 |
242 | const uint32_t functionOffset = index->functionOffset;
243 |
244 | return targetOffset < functionOffset;
245 | }
246 |
247 | static bool ImpactCompactUnwindCompareCompressedEntry(const void *a, const void *b) {
248 | const uintptr_t targetOffset = *(uintptr_t *)a;
249 | const compact_unwind_encoding_t* encoding = b;
250 |
251 | const uint32_t functionOffset = UNWIND_INFO_COMPRESSED_ENTRY_FUNC_OFFSET(*encoding);
252 |
253 | return targetOffset < functionOffset;
254 | }
255 |
--------------------------------------------------------------------------------
/Impact/Unwind/ImpactCompactUnwind.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCompactUnwind.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-03.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactCompactUnwind_h
10 | #define ImpactCompactUnwind_h
11 |
12 | #include "ImpactResult.h"
13 | #include "ImpactCPU.h"
14 |
15 | #include
16 | #include
17 |
18 | #if defined(__x86_64__) || defined(__i386__) || defined(__arm64__)
19 | #define IMPACT_COMPACT_UNWIND_SUPPORTED 1
20 | #else
21 | #define IMPACT_COMPACT_UNWIND_SUPPORTED 0
22 | #endif
23 |
24 | typedef struct {
25 | uintptr_t address;
26 | uintptr_t imageLoadAddress;
27 | const struct unwind_info_section_header* header;
28 | } ImpactCompactUnwindTarget;
29 |
30 | ImpactResult ImpactCompactUnwindLookupEncoding(ImpactCompactUnwindTarget target, compact_unwind_encoding_t* encoding);
31 |
32 | ImpactResult ImpactCompactUnwindStepRegisters(ImpactCompactUnwindTarget target, ImpactCPURegisters* registers, uint32_t* dwarfFDEOffset);
33 | ImpactResult ImpactCompactUnwindStepArchRegisters(ImpactCompactUnwindTarget target, ImpactCPURegisters* registers, compact_unwind_encoding_t encoding, uint32_t* dwarfFDEOffset);
34 |
35 |
36 | #endif /* ImpactCompactUnwind_h */
37 |
--------------------------------------------------------------------------------
/Impact/Unwind/ImpactUnwind.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactUnwind.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-28.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactUnwind.h"
10 | #include "ImpactUtility.h"
11 | #include "ImpactBinaryImage.h"
12 | #include "ImpactCompactUnwind.h"
13 | #include "ImpactDWARF.h"
14 |
15 | #include
16 |
17 | // this structure is only intended for use with x86_64. However, it should work in practice for any ABI that follows similar conventions
18 | #pragma pack(push, 8)
19 | typedef struct {
20 | struct ImpactStackFrame* previous;
21 | uintptr_t returnAddress;
22 | } ImpactStackFrameEntry;
23 | #pragma pack(pop)
24 |
25 | _Static_assert(sizeof(ImpactStackFrameEntry) == (sizeof(void*) * 2), "Frame entries must be exactly two pointers in size");
26 |
27 | ImpactResult ImpactUnwindStepRegistersWithFramePointer(ImpactCPURegisters* registers) {
28 | if (ImpactInvalidPtr(registers)) {
29 | return ImpactResultPointerInvalid;
30 | }
31 |
32 | const ImpactStackFrameEntry* frame = NULL;
33 |
34 | ImpactResult result = ImpactCPUGetRegister(registers, ImpactCPURegisterFramePointer, (uintptr_t *)&frame);
35 | if (result != ImpactResultSuccess) {
36 | return result;
37 | }
38 |
39 | if (ImpactInvalidPtr(frame)) {
40 | ImpactDebugLog("[Log:ERROR] frame pointer invalid %p\n", frame);
41 | return ImpactResultFailure;
42 | }
43 |
44 | if (frame->previous == NULL) {
45 | return ImpactResultEndOfStack;
46 | }
47 |
48 | result = ImpactCPUSetRegister(registers, ImpactCPURegisterFramePointer, (uintptr_t)frame->previous);
49 | if (result != ImpactResultSuccess) {
50 | return result;
51 | }
52 |
53 | result = ImpactCPUSetRegister(registers, ImpactCPURegisterInstructionPointer, (uintptr_t)frame->returnAddress);
54 | if (result != ImpactResultSuccess) {
55 | return result;
56 | }
57 |
58 | // assumes that stack grows towards lower memory (so adding moves towards the calling function)
59 | const uintptr_t newSP = (uintptr_t)frame + sizeof(ImpactStackFrameEntry);
60 |
61 | return ImpactCPUSetRegister(registers, ImpactCPURegisterStackPointer, newSP);
62 | }
63 |
64 | #if IMPACT_DWARF_CFI_SUPPORTED
65 | static ImpactResult ImpactUnwindDWARFCFIStepRegisters(ImpactMachODataRegion ehFrameRegion, uintptr_t pc, ImpactCPURegisters* registers, uint32_t fdeOffset) {
66 | if (ImpactInvalidPtr(registers)) {
67 | return ImpactResultPointerInvalid;
68 | }
69 |
70 | ImpactDWARFCFIData cfiData = {0};
71 | const ImpactDWARFEnvironment env = {
72 | .pointerWidth = sizeof(void*)
73 | };
74 |
75 | ImpactResult result = ImpactDWARFReadData(ehFrameRegion, env, fdeOffset, &cfiData);
76 | if (result != ImpactResultSuccess) {
77 | ImpactDebugLog("[Log:WARN] %s failed to parse CFI data %d\n", __func__, result);
78 | return result;
79 | }
80 |
81 | const ImpactDWARFTarget dwarfTarget = {
82 | .pc = pc,
83 | .ehFrameRegion = ehFrameRegion,
84 | .environment = env
85 | };
86 |
87 | return ImpactDWARFStepRegisters(&cfiData, dwarfTarget, registers);
88 | }
89 | #endif
90 |
91 | static ImpactResult ImpactUnwindCompactUnwindStepRegisters(ImpactMachOData* imageData, uintptr_t pc, ImpactCPURegisters* registers, uint32_t* dwarfFDEOFfset) {
92 | const ImpactCompactUnwindTarget target = {
93 | .address = pc,
94 | .imageLoadAddress = imageData->loadAddress,
95 | .header = (const struct unwind_info_section_header*)imageData->unwindInfoRegion.address
96 | };
97 |
98 | return ImpactCompactUnwindStepRegisters(target, registers, dwarfFDEOFfset);
99 | }
100 |
101 | ImpactResult ImpactUnwindStepRegisters(ImpactState* state, ImpactCPURegisters* registers) {
102 | uintptr_t pc = 0;
103 |
104 | ImpactResult result = ImpactCPUGetRegister(registers, ImpactCPURegisterInstructionPointer, &pc);
105 | if (result != ImpactResultSuccess) {
106 | return result;
107 | }
108 |
109 | ImpactMachOData imageData = {0};
110 |
111 | result = ImpactBinaryImageFind(state, pc, &imageData);
112 | if (result != ImpactResultSuccess) {
113 | ImpactDebugLog("[Log:WARN] unable to find binary image %d\n", result);
114 |
115 | return result;
116 | }
117 |
118 | ImpactDebugLog("[Log:INFO] found image at %p\n", (void*)imageData.loadAddress);
119 |
120 | if (pc <= imageData.loadAddress) {
121 | ImpactDebugLog("[Log:WARN] pc not within image range\n");
122 |
123 | return ImpactUnwindStepRegistersWithFramePointer(registers);
124 | }
125 |
126 | uint32_t dwarfFDEOFfset = 0;
127 | result = ImpactUnwindCompactUnwindStepRegisters(&imageData, pc, registers, &dwarfFDEOFfset);
128 | if (result == ImpactResultEndOfStack) {
129 | return ImpactResultEndOfStack;
130 | }
131 |
132 | if (result == ImpactResultMissingUnwindInfo) {
133 | // this is a weirdly common situation, because some apple libs are missing unwind_info section entries
134 | return ImpactUnwindStepRegistersWithFramePointer(registers);
135 | }
136 |
137 |
138 | if (result != ImpactResultSuccess) {
139 | ImpactDebugLog("[Log:WARN] compact unwind failed %d\n", result);
140 |
141 | // fall back to the frame pointer
142 | return ImpactUnwindStepRegistersWithFramePointer(registers);
143 | }
144 |
145 | if (dwarfFDEOFfset == 0) {
146 | // compact unwind has done the step
147 | return ImpactResultSuccess;
148 | }
149 |
150 | #if IMPACT_DWARF_CFI_SUPPORTED
151 | ImpactDebugLog("[Log:INFO] using DWARF CFI with FDE offset 0x%x\n", dwarfFDEOFfset);
152 |
153 | result = ImpactUnwindDWARFCFIStepRegisters(imageData.ehFrameRegion, pc, registers, dwarfFDEOFfset);
154 | if (result == ImpactResultSuccess || result == ImpactResultEndOfStack) {
155 | return result;
156 | }
157 |
158 | ImpactDebugLog("[Log:WARN] DWARF CFI unwind failed %d\n", result);
159 | #endif
160 |
161 | return ImpactUnwindStepRegistersWithFramePointer(registers);
162 | }
163 |
--------------------------------------------------------------------------------
/Impact/Unwind/ImpactUnwind.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactUnwind.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-28.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactUnwind_h
10 | #define ImpactUnwind_h
11 |
12 | #include "ImpactResult.h"
13 | #include "ImpactCPU.h"
14 | #include "ImpactState.h"
15 |
16 | ImpactResult ImpactUnwindStepRegistersWithFramePointer(ImpactCPURegisters* registers);
17 | ImpactResult ImpactUnwindStepRegisters(ImpactState* state, ImpactCPURegisters* registers);
18 |
19 | #endif /* ImpactUnwind_h */
20 |
--------------------------------------------------------------------------------
/Impact/Unwind/ImpactUnwind_arm.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactUnwind_arm.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-08.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactCompactUnwind.h"
10 | #include "ImpactUtility.h"
11 | #include "ImpactUnwind.h"
12 |
13 | #if defined(__arm__)
14 |
15 | ImpactResult ImpactCompactUnwindStepArchRegisters(ImpactCompactUnwindTarget target, ImpactCPURegisters* registers, compact_unwind_encoding_t encoding, uint32_t* dwarfFDEOffset) {
16 | return ImpactResultFailure;
17 | }
18 |
19 | #endif
20 |
--------------------------------------------------------------------------------
/Impact/Unwind/ImpactUnwind_arm64.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactUnwind_arm64.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-09.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactCompactUnwind.h"
10 | #include "ImpactUtility.h"
11 | #include "ImpactUnwind.h"
12 | #include "ImpactDWARF.h"
13 |
14 | #include
15 |
16 | #if defined(__arm64__)
17 |
18 | static const uint32_t ImpactCompactUnwindSavedRegisterPairMask = UNWIND_ARM64_FRAME_X19_X20_PAIR &
19 | UNWIND_ARM64_FRAME_X21_X22_PAIR &
20 | UNWIND_ARM64_FRAME_X23_X24_PAIR &
21 | UNWIND_ARM64_FRAME_X25_X26_PAIR &
22 | UNWIND_ARM64_FRAME_X27_X28_PAIR &
23 | UNWIND_ARM64_FRAME_D8_D9_PAIR &
24 | UNWIND_ARM64_FRAME_D10_D11_PAIR &
25 | UNWIND_ARM64_FRAME_D12_D13_PAIR &
26 | UNWIND_ARM64_FRAME_D14_D15_PAIR;
27 |
28 | #pragma pack(push, 8)
29 | // the ordering here is flipped to account for the stack direction
30 | typedef struct {
31 | uintptr_t regB;
32 | uintptr_t regA;
33 | } ImpactCompactUnwindSavedRegisterPair;
34 | #pragma pack(pop)
35 |
36 | static ImpactResult ImpactCompactUnwindRestoreFrameRegisterPair(ImpactCPURegisters* registers, ImpactCompactUnwindSavedRegisterPair pair, ImpactCPURegister firstReg, ImpactCPURegister secondReg) {
37 | ImpactResult result = ImpactCPUSetRegister(registers, firstReg, pair.regA);
38 | if (result != ImpactResultSuccess) {
39 | return result;
40 | }
41 |
42 | return ImpactCPUSetRegister(registers, secondReg, pair.regB);
43 | }
44 |
45 | static ImpactResult ImpactCompactUnwindRestoreSavedRegisters(ImpactCPURegisters* registers, uintptr_t address, compact_unwind_encoding_t encoding) {
46 | ImpactResult result = ImpactResultFailure;
47 |
48 | // Read the saved registers a pair at a time. In theory, we could read the entire stack area containing saved registers
49 | // in one shot. This would be a performance optimization for the common case. But, stack corruption or other failures would then
50 | // prevent us from doing any partial restores, which might end up saving the unwind. Overall, probably best to do it
51 | // simply and on the slower side.
52 |
53 | const ImpactCompactUnwindSavedRegisterPair* pair = (ImpactCompactUnwindSavedRegisterPair*)address;
54 |
55 | if (encoding & UNWIND_ARM64_FRAME_X19_X20_PAIR) {
56 | result = ImpactCompactUnwindRestoreFrameRegisterPair(registers, *pair, ImpactCPURegister_ARM64_X19, ImpactCPURegister_ARM64_X20);
57 | if (result != ImpactResultSuccess) {
58 | return result;
59 | }
60 |
61 | pair -= 1;
62 | }
63 |
64 | if (encoding & UNWIND_ARM64_FRAME_X21_X22_PAIR) {
65 | result = ImpactCompactUnwindRestoreFrameRegisterPair(registers, *pair, ImpactCPURegister_ARM64_X21, ImpactCPURegister_ARM64_X22);
66 | if (result != ImpactResultSuccess) {
67 | return result;
68 | }
69 |
70 | pair -= 1;
71 | }
72 |
73 | if (encoding & UNWIND_ARM64_FRAME_X23_X24_PAIR) {
74 | result = ImpactCompactUnwindRestoreFrameRegisterPair(registers, *pair, ImpactCPURegister_ARM64_X23, ImpactCPURegister_ARM64_X24);
75 | if (result != ImpactResultSuccess) {
76 | return result;
77 | }
78 |
79 | pair -= 1;
80 | }
81 |
82 | if (encoding & UNWIND_ARM64_FRAME_X25_X26_PAIR) {
83 | result = ImpactCompactUnwindRestoreFrameRegisterPair(registers, *pair, ImpactCPURegister_ARM64_X25, ImpactCPURegister_ARM64_X26);
84 | if (result != ImpactResultSuccess) {
85 | return result;
86 | }
87 |
88 | pair -= 1;
89 | }
90 |
91 | if (encoding & UNWIND_ARM64_FRAME_X27_X28_PAIR) {
92 | result = ImpactCompactUnwindRestoreFrameRegisterPair(registers, *pair, ImpactCPURegister_ARM64_X27, ImpactCPURegister_ARM64_X28);
93 | if (result != ImpactResultSuccess) {
94 | return result;
95 | }
96 |
97 | pair -= 1;
98 | }
99 |
100 | // floating point registers would happen here, but we don't support that
101 |
102 | return ImpactResultSuccess;
103 | }
104 |
105 | static ImpactResult ImpactCompactUnwindStepFrame(ImpactCPURegisters* registers, compact_unwind_encoding_t encoding) {
106 | uintptr_t savedRegisterLocation = 0;
107 | ImpactResult result = ImpactCPUGetRegister(registers, ImpactCPURegisterFramePointer, &savedRegisterLocation);
108 | if (result != ImpactResultSuccess) {
109 | return result;
110 | }
111 |
112 | result = ImpactCompactUnwindRestoreSavedRegisters(registers, savedRegisterLocation, encoding);
113 | if (result != ImpactResultSuccess) {
114 | return result;
115 | }
116 |
117 | return ImpactUnwindStepRegistersWithFramePointer(registers);
118 | }
119 |
120 | static ImpactResult ImpactCompactUnwindStepFrameless(ImpactCPURegisters* registers, compact_unwind_encoding_t encoding) {
121 | uintptr_t stackPointer = 0;
122 | ImpactResult result = ImpactCPUGetRegister(registers, ImpactCPURegisterStackPointer, &stackPointer);
123 | if (result != ImpactResultSuccess) {
124 | return result;
125 | }
126 |
127 | // compute the stack size, and use that to offset into the stack to find where the registers
128 | // are saved
129 | const uint32_t stackSize = ((encoding & UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK) >> 12) * 16;
130 |
131 | const uintptr_t savedRegisterLocation = stackPointer + stackSize;
132 |
133 | result = ImpactCompactUnwindRestoreSavedRegisters(registers, savedRegisterLocation, encoding);
134 | if (result != ImpactResultSuccess) {
135 | return result;
136 | }
137 |
138 | // adjust the stack pointer by counting the number of registers saved, and subtracting that value
139 |
140 | const uint32_t savedRegisterCount = __builtin_popcount(encoding & ImpactCompactUnwindSavedRegisterPairMask) * 2;
141 | const uintptr_t previousStackPointer = stackPointer - savedRegisterCount * sizeof(uintptr_t);
142 |
143 | result = ImpactCPUSetRegister(registers, ImpactCPURegisterStackPointer, previousStackPointer);
144 | if (result != ImpactResultSuccess) {
145 | return result;
146 | }
147 |
148 | uintptr_t registerValue = 0;
149 |
150 | // move LR -> PC
151 | result = ImpactCPUGetRegister(registers, ImpactCPURegisterLinkRegister, ®isterValue);
152 | if (result != ImpactResultSuccess) {
153 | return result;
154 | }
155 |
156 | result = ImpactCPUSetRegister(registers, ImpactCPURegisterInstructionPointer, registerValue);
157 | if (result != ImpactResultSuccess) {
158 | return result;
159 | }
160 |
161 | return ImpactUnwindStepRegistersWithFramePointer(registers);
162 | }
163 |
164 | ImpactResult ImpactCompactUnwindStepArchRegisters(ImpactCompactUnwindTarget target, ImpactCPURegisters* registers, compact_unwind_encoding_t encoding, uint32_t* dwarfFDEOffset) {
165 | const uint32_t mode = encoding & UNWIND_ARM64_MODE_MASK;
166 | switch (mode) {
167 | case UNWIND_ARM64_MODE_FRAME:
168 | return ImpactCompactUnwindStepFrame(registers, encoding);
169 | case UNWIND_ARM64_MODE_FRAMELESS:
170 | return ImpactCompactUnwindStepFrameless(registers, encoding);
171 | case UNWIND_ARM64_MODE_DWARF: {
172 | *dwarfFDEOffset = encoding & UNWIND_ARM64_DWARF_SECTION_OFFSET;
173 | return ImpactResultSuccess;
174 | }
175 | }
176 |
177 | return ImpactResultArgumentInvalid;
178 | }
179 |
180 | #endif
181 |
182 |
--------------------------------------------------------------------------------
/Impact/Unwind/ImpactUnwind_i386.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactUnwind_i386.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-09.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactCompactUnwind.h"
10 | #include "ImpactUtility.h"
11 | #include "ImpactUnwind.h"
12 | #include "ImpactDWARF.h"
13 |
14 | #include
15 |
16 | #if defined(__i386__)
17 |
18 | ImpactResult ImpactCompactUnwindStepArchRegisters(ImpactCompactUnwindTarget target, ImpactCPURegisters* registers, compact_unwind_encoding_t encoding, uint32_t* dwarfFDEOffset) {
19 | return ImpactResultFailure;
20 | }
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/Impact/Unwind/ImpactUnwind_x86_64.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactUnwind_x86_64.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-03.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactCompactUnwind.h"
10 | #include "ImpactUtility.h"
11 | #include "ImpactUnwind.h"
12 | #include "ImpactDWARF.h"
13 | #include "ImpactCPU.h"
14 |
15 | #include
16 |
17 | #define UNWIND_X86_64_RBP_FRAME_REG_MASK 0x7
18 |
19 | #if defined(__x86_64__)
20 |
21 | static const uint32_t ImpactCompactUnwindRBPRegisterCount = 5;
22 |
23 | static ImpactResult ImpactCompactUnwindUpdateRBPFrameRegister(ImpactCPURegisters* registers, uint32_t identifier, uintptr_t address) {
24 | if (identifier == UNWIND_X86_64_REG_NONE) {
25 | return ImpactResultSuccess;
26 | }
27 |
28 | uintptr_t value = 0;
29 |
30 | ImpactResult result = ImpactReadMemory(address, sizeof(uintptr_t), &value);
31 | if (result != ImpactResultSuccess) {
32 | return result;
33 | }
34 |
35 | switch (identifier) {
36 | case UNWIND_X86_64_REG_RBX:
37 | registers->__ss.__rbx = value;
38 | break;
39 | case UNWIND_X86_64_REG_R12:
40 | registers->__ss.__r12 = value;
41 | break;
42 | case UNWIND_X86_64_REG_R13:
43 | registers->__ss.__r13 = value;
44 | break;
45 | case UNWIND_X86_64_REG_R14:
46 | registers->__ss.__r14 = value;
47 | break;
48 | case UNWIND_X86_64_REG_R15:
49 | registers->__ss.__r15 = value;
50 | break;
51 | case UNWIND_X86_64_REG_RBP:
52 | registers->__ss.__rbp = value;
53 | break;
54 | default:
55 | return ImpactResultArgumentInvalid;
56 | }
57 |
58 | return ImpactResultSuccess;
59 | }
60 |
61 | static ImpactResult ImpactCompactUnwindRestoreRBPFrameRegisters(ImpactCPURegisters* registers, compact_unwind_encoding_t encoding) {
62 | const uint32_t registersOffset = (encoding & UNWIND_X86_64_RBP_FRAME_OFFSET) >> 16;
63 | uint32_t registerIdenfifiers = encoding & UNWIND_X86_64_RBP_FRAME_REGISTERS;
64 |
65 | uintptr_t registerEntry = registers->__ss.__rbp - sizeof(uintptr_t) * registersOffset;
66 |
67 | for (uint32_t i = 0; i < ImpactCompactUnwindRBPRegisterCount; ++i) {
68 | const uint32_t regIdentifier = registerIdenfifiers & UNWIND_X86_64_RBP_FRAME_REG_MASK;
69 |
70 | ImpactResult updateResult = ImpactCompactUnwindUpdateRBPFrameRegister(registers, regIdentifier, registerEntry);
71 | if (updateResult != ImpactResultSuccess) {
72 | return updateResult;
73 | }
74 |
75 | // look at the next identifier entry and corresponding stack address
76 | registerIdenfifiers = registerIdenfifiers >> 3;
77 | registerEntry += sizeof(uintptr_t);
78 | }
79 |
80 | return ImpactResultSuccess;
81 | }
82 |
83 | static ImpactResult ImpactCompactUnwindStepRBPFrame(ImpactCPURegisters* registers, compact_unwind_encoding_t encoding) {
84 | ImpactResult result = ImpactCompactUnwindRestoreRBPFrameRegisters(registers, encoding);
85 | if (result != ImpactResultSuccess) {
86 | return result;
87 | }
88 |
89 | return ImpactUnwindStepRegistersWithFramePointer(registers);
90 | }
91 |
92 | ImpactResult ImpactCompactUnwindStepArchRegisters(ImpactCompactUnwindTarget target, ImpactCPURegisters* registers, compact_unwind_encoding_t encoding, uint32_t* dwarfFDEOffset) {
93 | const uint32_t mode = encoding & UNWIND_X86_64_MODE_MASK;
94 | switch (mode) {
95 | case UNWIND_X86_64_MODE_RBP_FRAME:
96 | return ImpactCompactUnwindStepRBPFrame(registers, encoding);
97 | case UNWIND_X86_64_MODE_STACK_IMMD:
98 | return ImpactResultUnimplemented;
99 | case UNWIND_X86_64_MODE_STACK_IND:
100 | return ImpactResultUnimplemented;
101 | case UNWIND_X86_64_MODE_DWARF: {
102 | *dwarfFDEOffset = encoding & UNWIND_X86_64_DWARF_SECTION_OFFSET;
103 | return ImpactResultSuccess;
104 | }
105 | }
106 |
107 | return ImpactResultArgumentInvalid;
108 | }
109 |
110 | #endif
111 |
--------------------------------------------------------------------------------
/Impact/Utility/ImpactCPU.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCPU.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-19.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactCPU_h
10 | #define ImpactCPU_h
11 |
12 | #include
13 |
14 | #include "ImpactResult.h"
15 | #include "ImpactState.h"
16 |
17 | typedef _STRUCT_MCONTEXT ImpactCPURegisters;
18 |
19 | #if defined(__x86_64__)
20 | // These register number values are significant. Their definitions come from
21 | //
22 | // https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf
23 | // System V Application Binary Interface AMD64 Architecture Processor Supplement Draft Version 0.3
24 | //
25 | // DWARF uses abstract numbers to name registers. That document has a table labelled
26 | // "DWARF Register Number Mapping" which defines them. There are 130.
27 | //
28 | // Apple's libunwind uses a negative number to represent rip. DWARF defines the CFA in terms
29 | // of a uleb, (ie unsigned), so I believe this is a safe sentinel.
30 | typedef enum {
31 | ImpactCPURegister_X86_64_RIP = -1,
32 |
33 | ImpactCPURegister_X86_64_RAX = 0,
34 | ImpactCPURegister_X86_64_RDX = 1,
35 | ImpactCPURegister_X86_64_RCX = 2,
36 | ImpactCPURegister_X86_64_RBX = 3,
37 | ImpactCPURegister_X86_64_RSI = 4,
38 | ImpactCPURegister_X86_64_RDI = 5,
39 | ImpactCPURegister_X86_64_RBP = 6,
40 | ImpactCPURegister_X86_64_RSP = 7,
41 | ImpactCPURegister_X86_64_R8 = 8,
42 | ImpactCPURegister_X86_64_R9 = 9,
43 | ImpactCPURegister_X86_64_R10 = 10,
44 | ImpactCPURegister_X86_64_R11 = 11,
45 | ImpactCPURegister_X86_64_R12 = 12,
46 | ImpactCPURegister_X86_64_R13 = 13,
47 | ImpactCPURegister_X86_64_R14 = 14,
48 | ImpactCPURegister_X86_64_R15 = 15,
49 |
50 | ImpactCPURegister_X86_64_RA = 16
51 |
52 | // there are 130 of these defined, I haven't found a need to define them all yet
53 | } ImpactCPURegister;
54 |
55 | enum {
56 | ImpactCPUDWARFRegisterCount = 17
57 | };
58 |
59 | static const ImpactCPURegister ImpactCPURegisterStackPointer = ImpactCPURegister_X86_64_RSP;
60 | static const ImpactCPURegister ImpactCPURegisterInstructionPointer = ImpactCPURegister_X86_64_RIP;
61 | static const ImpactCPURegister ImpactCPURegisterFramePointer = ImpactCPURegister_X86_64_RBP;
62 |
63 | static const mach_msg_type_number_t ImpactCPUThreadStateCount = x86_THREAD_STATE64_COUNT;
64 | static const thread_state_flavor_t ImpactCPUThreadStateFlavor = x86_THREAD_STATE64;
65 |
66 | static const char* ImpactCPUArchitectureName = "x86_64";
67 | #elif defined(__i386__)
68 | typedef enum {
69 | ImpactCPURegister_i386_RIP = -1
70 | } ImpactCPURegister;
71 |
72 | enum {
73 | ImpactCPUDWARFRegisterCount = 0
74 | };
75 |
76 | static const ImpactCPURegister ImpactCPURegisterStackPointer = ImpactCPURegister_i386_RIP;
77 | static const ImpactCPURegister ImpactCPURegisterInstructionPointer = ImpactCPURegister_i386_RIP;
78 | static const ImpactCPURegister ImpactCPURegisterFramePointer = ImpactCPURegister_i386_RIP;
79 |
80 | static const mach_msg_type_number_t ImpactCPUThreadStateCount = x86_THREAD_STATE_COUNT;
81 | static const thread_state_flavor_t ImpactCPUThreadStateFlavor = x86_THREAD_STATE;
82 |
83 | static const char* ImpactCPUArchitectureName = "i386";
84 | #elif defined(__arm64__)
85 | // Aarch64 is documented here:
86 | // https://developer.arm.com/docs/ihi0057/c/dwarf-for-the-arm-64-bit-architecture-aarch64-abi-2018q4
87 |
88 | typedef enum {
89 | ImpactCPURegister_ARM64_RIP = -1,
90 |
91 | ImpactCPURegister_ARM64_X0 = 0,
92 | ImpactCPURegister_ARM64_X1 = 1,
93 | ImpactCPURegister_ARM64_X2 = 2,
94 | ImpactCPURegister_ARM64_X3 = 3,
95 | ImpactCPURegister_ARM64_X4 = 4,
96 | ImpactCPURegister_ARM64_X5 = 5,
97 | ImpactCPURegister_ARM64_X6 = 6,
98 | ImpactCPURegister_ARM64_X7 = 7,
99 | ImpactCPURegister_ARM64_X8 = 8,
100 | ImpactCPURegister_ARM64_X9 = 9,
101 | ImpactCPURegister_ARM64_X10 = 10,
102 | ImpactCPURegister_ARM64_X11 = 11,
103 | ImpactCPURegister_ARM64_X12 = 12,
104 | ImpactCPURegister_ARM64_X13 = 13,
105 | ImpactCPURegister_ARM64_X14 = 14,
106 | ImpactCPURegister_ARM64_X15 = 15,
107 | ImpactCPURegister_ARM64_X16 = 16,
108 | ImpactCPURegister_ARM64_X17 = 17,
109 | ImpactCPURegister_ARM64_X18 = 18,
110 | ImpactCPURegister_ARM64_X19 = 19,
111 | ImpactCPURegister_ARM64_X20 = 20,
112 | ImpactCPURegister_ARM64_X21 = 21,
113 | ImpactCPURegister_ARM64_X22 = 22,
114 | ImpactCPURegister_ARM64_X23 = 23,
115 | ImpactCPURegister_ARM64_X24 = 24,
116 | ImpactCPURegister_ARM64_X25 = 25,
117 | ImpactCPURegister_ARM64_X26 = 26,
118 | ImpactCPURegister_ARM64_X27 = 27,
119 | ImpactCPURegister_ARM64_X28 = 28,
120 | ImpactCPURegister_ARM64_X29 = 29,
121 | ImpactCPURegister_ARM64_X30 = 30,
122 | ImpactCPURegister_ARM64_X31 = 31,
123 |
124 | ImpactCPURegister_ARM64_ELR_mode = 33,
125 | ImpactCPURegister_ARM64_RA_SIGN_STATE = 34,
126 |
127 | // lots more vector stuff here
128 | } ImpactCPURegister;
129 |
130 | enum {
131 | ImpactCPUDWARFRegisterCount = 35
132 | };
133 |
134 | static const ImpactCPURegister ImpactCPURegisterStackPointer = ImpactCPURegister_ARM64_X31;
135 | static const ImpactCPURegister ImpactCPURegisterInstructionPointer = ImpactCPURegister_ARM64_RIP;
136 | static const ImpactCPURegister ImpactCPURegisterFramePointer = ImpactCPURegister_ARM64_X29;
137 | static const ImpactCPURegister ImpactCPURegisterLinkRegister = ImpactCPURegister_ARM64_X30;
138 |
139 | static const mach_msg_type_number_t ImpactCPUThreadStateCount = ARM_THREAD_STATE64_COUNT;
140 | static const thread_state_flavor_t ImpactCPUThreadStateFlavor = ARM_THREAD_STATE64;
141 |
142 | #if defined(__arm64e__)
143 | static const char* ImpactCPUArchitectureName = "arm64e";
144 | #else
145 | static const char* ImpactCPUArchitectureName = "arm64";
146 | #endif
147 |
148 | #elif defined(__arm__) && !defined(__arm64__)
149 | typedef enum {
150 | ImpactCPURegister_ARMv7_RIP = -1
151 | } ImpactCPURegister;
152 |
153 | static const ImpactCPURegister ImpactCPURegisterStackPointer = ImpactCPURegister_ARMv7_RIP;
154 | static const ImpactCPURegister ImpactCPURegisterInstructionPointer = ImpactCPURegister_ARMv7_RIP;
155 | static const ImpactCPURegister ImpactCPURegisterFramePointer = ImpactCPURegister_ARMv7_RIP;
156 |
157 | static const mach_msg_type_number_t ImpactCPUThreadStateCount = ARM_THREAD_STATE_COUNT;
158 | static const thread_state_flavor_t ImpactCPUThreadStateFlavor = ARM_THREAD_STATE;
159 |
160 | static const char* ImpactCPUArchitectureName = "armv7";
161 | #endif
162 |
163 |
164 | ImpactResult ImpactCPURegistersLog(ImpactState* state, const ImpactCPURegisters* registers);
165 | ImpactResult ImpactCPUGetRegister(const ImpactCPURegisters* registers, ImpactCPURegister num, uintptr_t* value);
166 | ImpactResult ImpactCPUSetRegister(ImpactCPURegisters* registers, ImpactCPURegister num, uintptr_t value);
167 |
168 | #endif /* ImpactCPU_h */
169 |
--------------------------------------------------------------------------------
/Impact/Utility/ImpactDebug.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDebug.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-20.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactDebug_h
10 | #define ImpactDebug_h
11 |
12 | #include "ImpactState.h"
13 | #include "ImpactPointer.h"
14 |
15 | #include
16 |
17 | #define VA_ARGS(...) , ##__VA_ARGS__
18 |
19 | #if 1
20 |
21 | #define ImpactDebugLog(format, ...) do { \
22 | if (ImpactInvalidPtr(GlobalImpactState)) { \
23 | break; \
24 | } \
25 | dprintf(GlobalImpactState->mutableState.log.fd, format VA_ARGS(__VA_ARGS__)); \
26 | } while(0)
27 |
28 | #else
29 |
30 | #define ImpactDebugLog(format, ...) ImpactLog(format VA_ARGS(__VA_ARGS__))
31 |
32 | #endif
33 |
34 | #endif /* ImpactDebug_h */
35 |
--------------------------------------------------------------------------------
/Impact/Utility/ImpactLog.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactLog.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactLog_h
10 | #define ImpactLog_h
11 |
12 | #include "ImpactResult.h"
13 | #include "ImpactState.h"
14 |
15 | #include
16 | #include
17 |
18 | #if __OBJC__
19 | #import
20 | #endif
21 |
22 | _Pragma("clang assume_nonnull begin")
23 | __BEGIN_DECLS
24 |
25 | ImpactResult ImpactLogInitialize(ImpactState* state, const char* path);
26 | ImpactResult ImpactLogDeinitialize(ImpactLogger* log);
27 | bool ImpactLogIsValid(const ImpactLogger* log);
28 |
29 | ImpactResult ImpactLog(const char * __restrict format, ...) __printflike(1, 2);
30 |
31 | ImpactResult ImpactLogFlush(ImpactLogger* log);
32 | ImpactResult ImpactLogWriteData(ImpactLogger* log, const char* data, size_t length);
33 | ImpactResult ImpactLogWriteString(ImpactLogger* log, const char* string);
34 | ImpactResult ImpactLogWriteInteger(ImpactLogger* log, uintptr_t number);
35 |
36 | ImpactResult ImpactLogWriteKeyInteger(ImpactLogger* log, const char* key, uintptr_t number, bool last);
37 | ImpactResult ImpactLogWriteKeyPointer(ImpactLogger* log, const char* key, const void* _Nullable ptr, bool last);
38 | ImpactResult ImpactLogWriteKeyString(ImpactLogger* log, const char* key, const char* string, bool last);
39 |
40 | #if __OBJC__
41 | ImpactResult ImpactLogWriteKeyStringObject(ImpactLogger* log, const char* key, NSString* string, bool last);
42 | #endif
43 |
44 | ImpactResult ImpactLogWriteKeyHexData(ImpactLogger* log, const char* key, const uint8_t* _Nullable data, size_t length, bool last);
45 | ImpactResult ImpactLogWriteTime(ImpactLogger* log, const char* key, bool last);
46 |
47 | __END_DECLS
48 | _Pragma("clang assume_nonnull end")
49 |
50 | #endif /* ImpactLog_h */
51 |
--------------------------------------------------------------------------------
/Impact/Utility/ImpactLog.m:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactLog.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactLog.h"
10 | #include "ImpactPointer.h"
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 |
17 | ImpactResult ImpactLogInitialize(ImpactState* state, const char* _Nonnull path) {
18 | if (ImpactInvalidPtr(state) || ImpactInvalidPtr(path)) {
19 | return ImpactResultPointerInvalid;
20 | }
21 |
22 | int fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC, 0666);
23 | if (fd == -1) {
24 | return ImpactResultFailure;
25 | }
26 |
27 | state->mutableState.log.fd = fd;
28 |
29 | state->mutableState.log.bufferCount = 0;
30 | memset(state->mutableState.log.buffer, 0, ImpactLogBufferSize);
31 |
32 | return ImpactResultSuccess;
33 | }
34 |
35 | ImpactResult ImpactLogDeinitialize(ImpactLogger* _Nonnull log) {
36 | if (ImpactInvalidPtr(log)) {
37 | return ImpactResultPointerInvalid;
38 | }
39 |
40 | return ImpactResultFailure;
41 | }
42 |
43 | bool ImpactLogIsValid(const ImpactLogger* log) {
44 | if (ImpactInvalidPtr(log)) {
45 | return false;
46 | }
47 |
48 | if (log->fd <= 0) {
49 | return false;
50 | }
51 |
52 | return log->bufferCount <= ImpactLogBufferSize;
53 | }
54 |
55 | ImpactResult ImpactLogWriteData(ImpactLogger* log, const char* data, size_t length) {
56 | if (length == 0) {
57 | return ImpactResultSuccess;
58 | }
59 |
60 | for(uint32_t i = 0; i < 100; ++i) {
61 | const ssize_t spaceRemaining = ImpactLogBufferSize - log->bufferCount;
62 | ssize_t chunkSize = MIN(length, spaceRemaining);
63 | if (chunkSize <= 0) {
64 | const ImpactResult result = ImpactLogFlush(log);
65 | if (result != ImpactResultSuccess) {
66 | return result;
67 | }
68 |
69 | chunkSize = MIN(length, ImpactLogBufferSize);
70 | }
71 |
72 | void* buffer = log->buffer + log->bufferCount;
73 |
74 | memcpy(buffer, data, chunkSize);
75 |
76 | log->bufferCount += chunkSize;
77 | length -= chunkSize;
78 | data += chunkSize;
79 |
80 | if (length == 0) {
81 | return ImpactResultSuccess;
82 | }
83 | }
84 |
85 | return ImpactResultTooManyIterations;
86 | }
87 |
88 | ImpactResult ImpactLogFlush(ImpactLogger* log) {
89 | size_t writeCount = log->bufferCount;
90 | const void* data = log->buffer;
91 |
92 | while (writeCount > 0) {
93 | const ssize_t count = write(log->fd, data, writeCount);
94 |
95 | if (count == -1) {
96 | return ImpactResultCallFailed;
97 | }
98 |
99 | // this accounts for a partial write
100 | writeCount -= count;
101 | data = log->buffer + count;
102 | }
103 |
104 | log->bufferCount = 0;
105 |
106 | return ImpactResultSuccess;
107 | }
108 |
109 | static char ImpactLogValueToHexChar(uint8_t value) {
110 | uint8_t maskedValue = value & 0x0F;
111 |
112 | if (maskedValue >= 10) {
113 | return maskedValue - 10 + 'a';
114 | } else {
115 | return maskedValue + '0';
116 | }
117 | }
118 |
119 | ImpactResult ImpactLogWriteString(ImpactLogger* log, const char* string) {
120 | if (!ImpactLogIsValid(log)) {
121 | return ImpactResultArgumentInvalid;
122 | }
123 |
124 | if (ImpactInvalidPtr(string)) {
125 | return ImpactResultArgumentInvalid;
126 | }
127 |
128 | return ImpactLogWriteData(log, string, strlen(string));
129 | }
130 |
131 | ImpactResult ImpactLogWriteInteger(ImpactLogger* log, uintptr_t number) {
132 | if (!ImpactLogIsValid(log)) {
133 | return ImpactResultArgumentInvalid;
134 | }
135 |
136 | char buffer[16];
137 | char *ptr = buffer + sizeof(buffer) - 1;
138 |
139 | memset(buffer, '0', sizeof(buffer));
140 |
141 | while (number > 0) {
142 | int modResult = number % 16;
143 |
144 | *ptr = ImpactLogValueToHexChar(modResult);
145 |
146 | number = number / 16;
147 |
148 | if (number > 0) {
149 | ptr -= 1;
150 | }
151 | }
152 |
153 | ImpactLogWriteString(log, "0x");
154 |
155 | const size_t length = sizeof(buffer) - (ptr - buffer);
156 |
157 | return ImpactLogWriteData(log, ptr, length);
158 | }
159 |
160 | ImpactResult ImpactLogWriteHexData(ImpactLogger* log, const uint8_t* data, size_t length) {
161 | char buffer[2] = {0};
162 |
163 | for (size_t i = 0; i < length; ++i) {
164 | buffer[1] = ImpactLogValueToHexChar(data[i]);
165 | buffer[0] = ImpactLogValueToHexChar(data[i] >> 4);
166 |
167 | ImpactLogWriteData(log, buffer, 2);
168 | }
169 |
170 | return ImpactResultSuccess;
171 | }
172 |
173 | ImpactResult ImpactLogWriteSeparator(ImpactLogger* log, bool ending) {
174 | const ImpactResult result = ImpactLogWriteString(log, ending ? "\n" : ", ");
175 | if (result != ImpactResultSuccess) {
176 | return result;
177 | }
178 |
179 | if (ending) {
180 | return ImpactLogFlush(log);
181 | }
182 |
183 | return ImpactResultSuccess;
184 | }
185 |
186 | ImpactResult ImpactLogWriteKeyInteger(ImpactLogger* log, const char* key, uintptr_t number, bool last) {
187 | ImpactLogWriteString(log, key);
188 | ImpactLogWriteString(log, ": ");
189 | ImpactLogWriteInteger(log, number);
190 |
191 | return ImpactLogWriteSeparator(log, last);
192 | }
193 |
194 | ImpactResult ImpactLogWriteKeyPointer(ImpactLogger* log, const char* key, const void* ptr, bool last) {
195 | return ImpactLogWriteKeyInteger(log, key, (uintptr_t)ptr, last);
196 | }
197 |
198 | ImpactResult ImpactLogWriteKeyString(ImpactLogger* log, const char* key, const char* string, bool last) {
199 | ImpactLogWriteString(log, key);
200 | ImpactLogWriteString(log, ": ");
201 | ImpactLogWriteString(log, string);
202 |
203 | return ImpactLogWriteSeparator(log, last);
204 | }
205 |
206 | ImpactResult ImpactLogWriteKeyStringObject(ImpactLogger* log, const char* key, NSString* string, bool last) {
207 | if (string.length == 0) {
208 | return ImpactLogWriteKeyString(log, key, "", last);
209 | }
210 |
211 | NSString* encodedString = [[string dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];
212 |
213 | return ImpactLogWriteKeyString(log, key, encodedString.UTF8String, last);
214 | }
215 |
216 | ImpactResult ImpactLogWriteKeyHexData(ImpactLogger* log, const char* key, const uint8_t* _Nullable data, size_t length, bool last) {
217 | ImpactLogWriteString(log, key);
218 | ImpactLogWriteString(log, ": ");
219 | ImpactLogWriteHexData(log, data, length);
220 |
221 | return ImpactLogWriteSeparator(log, last);
222 | }
223 |
224 | ImpactResult ImpactLogWriteTime(ImpactLogger* log, const char* key, bool last) {
225 | const uint64_t epochTimeMS = time(NULL) * 1000;
226 |
227 | return ImpactLogWriteKeyInteger(log, key, epochTimeMS, last);
228 | }
229 |
--------------------------------------------------------------------------------
/Impact/Utility/ImpactPointer.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactPointer.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactPointer_h
10 | #define ImpactPointer_h
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 | // the upper half of the address space is all kernel stuff, so
19 | // we cannot have a pointer to that region
20 | const static uintptr_t ImpactPointerMax = UINTPTR_MAX / 2;
21 |
22 | #define ImpactPointerMin vm_kernel_page_size
23 |
24 | static inline bool ImpactInvalidPtr(const void * const ptr) {
25 | return ((uintptr_t)ptr < ImpactPointerMin) || ((uintptr_t)ptr > ImpactPointerMax);
26 | }
27 |
28 | static inline void* ImpactPointerOffset(const void* ptr, uintptr_t offset) {
29 | void* result = (void*)((uintptr_t)ptr + offset);
30 |
31 | if (ImpactInvalidPtr(result)) {
32 | return NULL;
33 | }
34 |
35 | return result;
36 | }
37 |
38 | #endif /* ImpactPointer_h */
39 |
--------------------------------------------------------------------------------
/Impact/Utility/ImpactResult.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactResult.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactResult_h
10 | #define ImpactResult_h
11 |
12 | typedef enum {
13 | ImpactResultSuccess = 0,
14 | ImpactResultFailure,
15 | ImpactResultPointerInvalid,
16 | ImpactResultArgumentInvalid,
17 | ImpactResultCallFailed,
18 | ImpactResultInconsistentData,
19 | ImpactResultMemoryReadFailed,
20 | ImpactResultUnimplemented,
21 | ImpactResultEndOfData,
22 | ImpactResultStateInvalid,
23 | ImpactResultUnexpectedData,
24 | ImpactResultEndOfStack,
25 | ImpactResultMissingUnwindInfo,
26 | ImpactResultTooManyIterations,
27 | } ImpactResult;
28 |
29 | #endif /* ImpactResult_h */
30 |
--------------------------------------------------------------------------------
/Impact/Utility/ImpactUtility.c:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactUtility.c
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-10-06.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #include "ImpactUtility.h"
10 |
11 | #include
12 | #include
13 |
14 | // code from:
15 | // https://developer.apple.com/library/archive/qa/qa1361/_index.html
16 | bool ImpactDebuggerAttached(void) {
17 | int mib[4] = {0};
18 | struct kinfo_proc info = {0};
19 |
20 | info.kp_proc.p_flag = 0;
21 |
22 | mib[0] = CTL_KERN;
23 | mib[1] = KERN_PROC;
24 | mib[2] = KERN_PROC_PID;
25 | mib[3] = getpid();
26 |
27 | size_t size = sizeof(info);
28 | const int ret = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
29 | if (ret != 0) {
30 | return true;
31 | }
32 |
33 | return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
34 | }
35 |
36 | ImpactResult ImpactReadMemory(uintptr_t address, size_t size, void* buffer) {
37 | if (ImpactInvalidPtr((void*)address)) {
38 | return ImpactResultPointerInvalid;
39 | }
40 |
41 | if (ImpactInvalidPtr(buffer)) {
42 | return ImpactResultPointerInvalid;
43 | }
44 |
45 | *(uintptr_t *)buffer = *(uintptr_t*)address;
46 |
47 | return ImpactResultSuccess;
48 | }
49 |
--------------------------------------------------------------------------------
/Impact/Utility/ImpactUtility.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactUtility.h
3 | // Impact
4 | //
5 | // Created by Matt Massicotte on 2019-09-20.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #ifndef ImpactUtility_h
10 | #define ImpactUtility_h
11 |
12 | #include "ImpactPointer.h"
13 | #include "ImpactDebug.h"
14 | #include "ImpactResult.h"
15 |
16 | #include
17 |
18 | bool ImpactDebuggerAttached(void);
19 | ImpactResult ImpactReadMemory(uintptr_t address, size_t size, void* buffer);
20 |
21 | #endif /* ImpactUtility_h */
22 |
--------------------------------------------------------------------------------
/Impact/include/ImpactMonitor.h:
--------------------------------------------------------------------------------
1 | ../ImpactMonitor.h
--------------------------------------------------------------------------------
/Impact/include/ImpactMonitoredApplication.h:
--------------------------------------------------------------------------------
1 | ../Monitoring/ImpactMonitoredApplication.h
--------------------------------------------------------------------------------
/ImpactTestMac/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-17.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Impact
11 |
12 | @NSApplicationMain
13 | class AppDelegate: NSObject, NSApplicationDelegate {
14 | var window: NSWindow!
15 | var viewController: ViewController!
16 |
17 | func applicationDidFinishLaunching(_ aNotification: Notification) {
18 | UserDefaults.standard.register(defaults: [
19 | "NSApplicationCrashOnExceptions": true,
20 | ])
21 |
22 | let path = UserDefaults.standard.string(forKey: "output_path") ?? "/tmp/impact";
23 | let url = URL(fileURLWithPath: path, isDirectory: false)
24 |
25 | ImpactMonitor.shared.suppressReportCrash = UserDefaults.standard.bool(forKey: "suppressReportCrash")
26 | ImpactMonitor.shared.start(with: url, identifier: UUID())
27 |
28 | self.viewController = ViewController()
29 | self.window = NSWindow(contentViewController: viewController)
30 |
31 | window.setContentSize(NSSize(width: 400, height: 300))
32 | window.makeKeyAndOrderFront(self)
33 |
34 | guard let crash = crashFromCommandLineArgument() else {
35 | return
36 | }
37 |
38 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
39 | crash.execute()
40 | }
41 | }
42 |
43 | func applicationWillTerminate(_ aNotification: Notification) {
44 | }
45 | }
46 |
47 | extension AppDelegate {
48 | func crashFromCommandLineArgument() -> CrashInvocation? {
49 | let crashName = UserDefaults.standard.string(forKey: "run")
50 |
51 | switch crashName {
52 | case "invokeAbort"?:
53 | return InvokeAbort()
54 | case "nullDereference"?:
55 | return NullDereference()
56 | case "uncaughtNSException"?:
57 | return UncaughtNSException()
58 | case "nonMainThreadUncaughtNSException"?:
59 | return NonMainThreadUncaughtNSException()
60 | case "subprocessCrash"?:
61 | return SubprocessCrash()
62 | default:
63 | return nil
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ImpactTestMac/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/ImpactTestMac/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/CPPException.h:
--------------------------------------------------------------------------------
1 | //
2 | // CPPException.h
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-10-10.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "CrashInvocation.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface CPPException : CrashInvocation
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/CPPException.mm:
--------------------------------------------------------------------------------
1 | //
2 | // CPPException.m
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-10-10.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "CPPException.h"
10 |
11 | #include
12 |
13 | @implementation CPPException
14 |
15 | - (void)execute {
16 | throw std::runtime_error("boom");
17 | }
18 |
19 | - (NSString *)name {
20 | return @"Uncaught C++ Exception";
21 | }
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/CrashInvocation.h:
--------------------------------------------------------------------------------
1 | //
2 | // CrashInvocation.h
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface CrashInvocation : NSObject
14 |
15 | - (void)execute;
16 |
17 | @property (nonatomic, readonly) NSString* name;
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/CrashInvocation.m:
--------------------------------------------------------------------------------
1 | //
2 | // CrashInvocation.m
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "CrashInvocation.h"
10 |
11 | @implementation CrashInvocation
12 |
13 | - (void)execute {
14 | }
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/InvokeAbort.h:
--------------------------------------------------------------------------------
1 | //
2 | // InvokeAbort.h
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "CrashInvocation.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface InvokeAbort : CrashInvocation
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/InvokeAbort.m:
--------------------------------------------------------------------------------
1 | //
2 | // InvokeAbort.m
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-18.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "InvokeAbort.h"
10 |
11 | @implementation InvokeAbort
12 |
13 | - (void)execute {
14 | abort();
15 | }
16 |
17 | - (NSString *)name {
18 | return @"Invoke Abort";
19 | }
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/NonMainThreadUncaughtNSException.h:
--------------------------------------------------------------------------------
1 | //
2 | // NonMainThreadUncaughtNSException.h
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-10-01.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "CrashInvocation.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface NonMainThreadUncaughtNSException : CrashInvocation
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/NonMainThreadUncaughtNSException.m:
--------------------------------------------------------------------------------
1 | //
2 | // NonMainThreadUncaughtNSException.m
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-10-01.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "NonMainThreadUncaughtNSException.h"
10 |
11 | @implementation NonMainThreadUncaughtNSException
12 |
13 | - (void)execute {
14 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
15 | [NSException raise:@"AnException" format:@"something bad happened"];
16 | });
17 | }
18 |
19 | - (NSString *)name {
20 | return @"Non-Main Thread Uncaught NSException";
21 | }
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/NullDereference.h:
--------------------------------------------------------------------------------
1 | //
2 | // NullDereference.h
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-19.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "CrashInvocation.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface NullDereference : CrashInvocation
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/NullDereference.m:
--------------------------------------------------------------------------------
1 | //
2 | // NullDereference.m
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-19.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "NullDereference.h"
10 |
11 | @implementation NullDereference
12 |
13 | - (void)execute {
14 | int *x = NULL;
15 |
16 | *x = 42;
17 | }
18 |
19 | - (NSString *)name {
20 | return @"Null Dereference";
21 | }
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/SubprocessCrash.h:
--------------------------------------------------------------------------------
1 | //
2 | // SubprocessCrash.h
3 | // ImpactTestMac
4 | //
5 | // Created by Matthew Massicotte on 2021-07-20.
6 | // Copyright © 2021 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "CrashInvocation.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SubprocessCrash : CrashInvocation
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/SubprocessCrash.m:
--------------------------------------------------------------------------------
1 | //
2 | // SubprocessCrash.m
3 | // ImpactTestMac
4 | //
5 | // Created by Matthew Massicotte on 2021-07-20.
6 | // Copyright © 2021 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "SubprocessCrash.h"
10 |
11 | @implementation SubprocessCrash
12 |
13 | - (void)execute {
14 | NSURL* crasherURL = [[NSBundle mainBundle] URLForAuxiliaryExecutable:@"crasher"];
15 |
16 | NSLog(@"url: %@", crasherURL);
17 |
18 | NSError* error = nil;
19 | NSTask* task = [NSTask launchedTaskWithExecutableURL:crasherURL arguments:@[] error:&error terminationHandler:^(NSTask *tsk) {
20 |
21 | }];
22 |
23 | [task waitUntilExit];
24 | }
25 |
26 | - (NSString *)name {
27 | return @"Subprocess Crash";
28 | }
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/UncaughtNSException.h:
--------------------------------------------------------------------------------
1 | //
2 | // UncaughtNSException.h
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-30.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "CrashInvocation.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface UncaughtNSException : CrashInvocation
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/ImpactTestMac/Crashes/UncaughtNSException.m:
--------------------------------------------------------------------------------
1 | //
2 | // UncaughtNSException.m
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-30.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "UncaughtNSException.h"
10 |
11 | @implementation UncaughtNSException
12 |
13 | - (void)execute {
14 | [NSException raise:@"AnException" format:@"something bad happened"];
15 | }
16 |
17 | - (NSString *)name {
18 | return @"Uncaught NSException";
19 | }
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/ImpactTestMac/ImpactTestMac-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "InvokeAbort.h"
6 | #import "NullDereference.h"
7 | #import "UncaughtNSException.h"
8 | #import "NonMainThreadUncaughtNSException.h"
9 | #import "CPPException.h"
10 | #import "SubprocessCrash.h"
11 |
--------------------------------------------------------------------------------
/ImpactTestMac/ImpactTestMac.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ImpactTestMac/ImpactTestMac.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactTestMac.xcconfig
3 | // Impact
4 | //
5 | // Created by Matthew Massicotte on 2021-06-17.
6 | // Copyright © 2021 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | // Configuration settings file format documentation can be found at:
10 | // https://help.apple.com/xcode/#/dev745c5c974
11 |
12 | OTHER_LDFLAGS = -ObjC
13 |
--------------------------------------------------------------------------------
/ImpactTestMac/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2019 Chime Systems Inc. All rights reserved.
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | ImpactMonitoredApplication
31 | NSSupportsAutomaticTermination
32 |
33 | NSSupportsSuddenTermination
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ImpactTestMac/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // ImpactTestMac
4 | //
5 | // Created by Matt Massicotte on 2019-09-17.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ViewController: NSViewController {
12 | private let tableView: NSTableView
13 | private let crashInvocations: [CrashInvocation]
14 |
15 | init() {
16 | self.tableView = NSTableView()
17 | self.crashInvocations = [
18 | InvokeAbort(),
19 | NullDereference(),
20 | UncaughtNSException(),
21 | NonMainThreadUncaughtNSException(),
22 | CPPException(),
23 | SubprocessCrash(),
24 | ]
25 |
26 | super.init(nibName: nil, bundle: nil)
27 | }
28 |
29 | required init?(coder: NSCoder) {
30 | fatalError("init(coder:) has not been implemented")
31 | }
32 |
33 | override func loadView() {
34 | let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("column"))
35 | column.width = 100.0
36 |
37 | tableView.addTableColumn(column)
38 | tableView.delegate = self
39 | tableView.dataSource = self
40 | tableView.headerView = nil
41 | tableView.allowsTypeSelect = false
42 | tableView.allowsMultipleSelection = false
43 | tableView.allowsColumnSelection = false
44 | tableView.target = self
45 | tableView.doubleAction = #selector(performCrash(_:))
46 |
47 | let scrollView = NSScrollView()
48 |
49 | scrollView.documentView = tableView
50 | scrollView.hasVerticalScroller = true
51 | scrollView.hasHorizontalScroller = true
52 |
53 | self.view = scrollView
54 | }
55 |
56 | @objc func performCrash(_ sender: Any?) {
57 | let row = tableView.selectedRow
58 |
59 | let invocation = crashInvocations[row]
60 |
61 | invocation.execute()
62 | }
63 | }
64 |
65 | extension ViewController: NSTableViewDelegate {
66 | }
67 |
68 | extension ViewController: NSTableViewDataSource {
69 | func numberOfRows(in tableView: NSTableView) -> Int {
70 | return crashInvocations.count
71 | }
72 |
73 | private func makeNewRowView() -> NSTextField {
74 | let textField = NSTextField()
75 |
76 | textField.drawsBackground = false
77 | // textField.backgroundColor = NSColor.white
78 | textField.isBezeled = false
79 | textField.allowsDefaultTighteningForTruncation = true
80 | textField.isEditable = false
81 | textField.maximumNumberOfLines = 1
82 | textField.cell?.truncatesLastVisibleLine = true
83 |
84 | return textField
85 | }
86 |
87 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
88 | let reusedView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("column"), owner: self)
89 | let cellView = (reusedView as? NSTextField) ?? makeNewRowView()
90 |
91 | let invocation = crashInvocations[row]
92 |
93 | cellView.stringValue = invocation.name
94 |
95 | return cellView
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/ImpactTestiOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ImpactTestiOS
4 | //
5 | // Created by Matt Massicotte on 2020-06-24.
6 | // Copyright © 2020 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Impact
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
15 | let backupPath = NSTemporaryDirectory() + "/impact"
16 | let path = UserDefaults.standard.string(forKey: "output_path") ?? backupPath;
17 | let url = URL(fileURLWithPath: path, isDirectory: false)
18 |
19 | ImpactMonitor.shared.suppressReportCrash = UserDefaults.standard.bool(forKey: "suppressReportCrash")
20 | ImpactMonitor.shared.start(with: url, identifier: UUID())
21 |
22 | return true
23 | }
24 |
25 | // MARK: UISceneSession Lifecycle
26 |
27 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
28 | // Called when a new scene session is being created.
29 | // Use this method to select a configuration to create the new scene with.
30 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
31 | }
32 |
33 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
34 | // Called when the user discards a scene session.
35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
37 | }
38 |
39 |
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/ImpactTestiOS/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ImpactTestiOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/ImpactTestiOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ImpactTestiOS/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/ImpactTestiOS/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // ImpactTestiOS
4 | //
5 | // Created by Matt Massicotte on 2020-06-24.
6 | // Copyright © 2020 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ContentView: View {
12 | var body: some View {
13 | List {
14 | Text("Precondition Failure").onTapGesture {
15 | precondition(false)
16 | }
17 | Text("Uncaught Exception").onTapGesture {
18 | _ = NSArray()[0]
19 | }
20 | }
21 | }
22 | }
23 |
24 | struct ContentView_Previews: PreviewProvider {
25 | static var previews: some View {
26 | ContentView()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ImpactTestiOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 |
37 |
38 |
39 |
40 | UIApplicationSupportsIndirectInputEvents
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UISupportedInterfaceOrientations~ipad
55 |
56 | UIInterfaceOrientationPortrait
57 | UIInterfaceOrientationPortraitUpsideDown
58 | UIInterfaceOrientationLandscapeLeft
59 | UIInterfaceOrientationLandscapeRight
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/ImpactTestiOS/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ImpactTestiOS/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // ImpactTestiOS
4 | //
5 | // Created by Matt Massicotte on 2020-06-24.
6 | // Copyright © 2020 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
21 |
22 | // Create the SwiftUI view that provides the window contents.
23 | let contentView = ContentView()
24 |
25 | // Use a UIHostingController as window root view controller.
26 | if let windowScene = scene as? UIWindowScene {
27 | let window = UIWindow(windowScene: windowScene)
28 | window.rootViewController = UIHostingController(rootView: contentView)
29 | self.window = window
30 | window.makeKeyAndVisible()
31 | }
32 | }
33 |
34 | func sceneDidDisconnect(_ scene: UIScene) {
35 | // Called as the scene is being released by the system.
36 | // This occurs shortly after the scene enters the background, or when its session is discarded.
37 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
38 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
39 | }
40 |
41 | func sceneDidBecomeActive(_ scene: UIScene) {
42 | // Called when the scene has moved from an inactive state to an active state.
43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
44 | }
45 |
46 | func sceneWillResignActive(_ scene: UIScene) {
47 | // Called when the scene will move from an active state to an inactive state.
48 | // This may occur due to temporary interruptions (ex. an incoming phone call).
49 | }
50 |
51 | func sceneWillEnterForeground(_ scene: UIScene) {
52 | // Called as the scene transitions from the background to the foreground.
53 | // Use this method to undo the changes made on entering the background.
54 | }
55 |
56 | func sceneDidEnterBackground(_ scene: UIScene) {
57 | // Called as the scene transitions from the foreground to the background.
58 | // Use this method to save data, release shared resources, and store enough scene-specific state information
59 | // to restore the scene back to its current state.
60 | }
61 |
62 |
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/ImpactTests/ImpactCompactUnwindTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCompactUnwindTests.m
3 | // ImpactTests
4 | //
5 | // Created by Matt Massicotte on 2019-10-03.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "ImpactCompactUnwind.h"
12 | #import "ImpactCrashHelper.h"
13 |
14 | @interface ImpactCompactUnwindTests : XCTestCase
15 |
16 | @end
17 |
18 | @implementation ImpactCompactUnwindTests
19 |
20 | - (void)testFunctionLookupInMiddleOfCompressedPage {
21 | ImpactMachODataRegion region = [ImpactCrashHelper regionWithName:@"unwind/libobjc_10_14_4_18E226.unwind_info.x86_64.bin"
22 | loadAddress:0x784688];
23 |
24 | const uintptr_t imageAddress = region.address - region.loadAddress;
25 |
26 | const ImpactCompactUnwindTarget target = {
27 | .address = (0x0000A3ED + 2) + imageAddress,
28 | .imageLoadAddress = imageAddress,
29 | .header = (void*)region.address
30 | };
31 | compact_unwind_encoding_t encoding = 0;
32 |
33 | ImpactResult result = ImpactCompactUnwindLookupEncoding(target, &encoding);
34 |
35 | XCTAssertEqual(result, ImpactResultSuccess);
36 | XCTAssertEqual(encoding, 0x010558D1);
37 | }
38 |
39 | - (void)testFunctionLookupAtFirstEntryOfCompressedPage {
40 | ImpactMachODataRegion region = [ImpactCrashHelper regionWithName:@"unwind/libobjc_10_14_4_18E226.unwind_info.x86_64.bin"
41 | loadAddress:0x784688];
42 |
43 | const uintptr_t imageAddress = region.address - region.loadAddress;
44 |
45 | const ImpactCompactUnwindTarget target = {
46 | .address = 0x00000F01 + imageAddress,
47 | .imageLoadAddress = imageAddress,
48 | .header = (void*)region.address
49 | };
50 | compact_unwind_encoding_t encoding = 0;
51 |
52 | ImpactResult result = ImpactCompactUnwindLookupEncoding(target, &encoding);
53 |
54 | XCTAssertEqual(result, ImpactResultSuccess);
55 | XCTAssertEqual(encoding, 0x01030161);
56 | }
57 |
58 | - (void)testFunctionLookupLastEntryOfLastCompressedPage {
59 | ImpactMachODataRegion region = [ImpactCrashHelper regionWithName:@"unwind/libobjc_10_14_4_18E226.unwind_info.x86_64.bin"
60 | loadAddress:0x784688];
61 |
62 | const uintptr_t imageAddress = region.address - region.loadAddress;
63 |
64 | const ImpactCompactUnwindTarget target = {
65 | .address = 0x00021A28 + 1 + imageAddress,
66 | .imageLoadAddress = imageAddress,
67 | .header = (void*)region.address
68 | };
69 | compact_unwind_encoding_t encoding = 0;
70 |
71 | ImpactResult result = ImpactCompactUnwindLookupEncoding(target, &encoding);
72 |
73 | XCTAssertEqual(result, ImpactResultSuccess);
74 | XCTAssertEqual(encoding, 0x01010001);
75 | }
76 |
77 | - (void)testFunctionLookupEntryWithoutCommonEncoding {
78 | ImpactMachODataRegion region = [ImpactCrashHelper regionWithName:@"unwind/libobjc_10_14_4_18E226.unwind_info.x86_64.bin"
79 | loadAddress:0x784688];
80 |
81 | const uintptr_t imageAddress = region.address - region.loadAddress;
82 |
83 | const ImpactCompactUnwindTarget target = {
84 | .address = 0x0001995D + 2 + imageAddress,
85 | .imageLoadAddress = imageAddress,
86 | .header = (void*)region.address
87 | };
88 | compact_unwind_encoding_t encoding = 0;
89 |
90 | ImpactResult result = ImpactCompactUnwindLookupEncoding(target, &encoding);
91 |
92 | XCTAssertEqual(result, ImpactResultSuccess);
93 | XCTAssertEqual(encoding, 0x040013E0);
94 | }
95 |
96 | @end
97 |
--------------------------------------------------------------------------------
/ImpactTests/ImpactCrashHelper.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCrashHelper.h
3 | // ImpactTests
4 | //
5 | // Created by Matt Massicotte on 2019-10-14.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "ImpactBinaryImage.h"
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | @interface ImpactCrashHelper : NSObject
15 |
16 | + (ImpactMachODataRegion)regionWithName:(NSString *)name loadAddress:(uint64_t)addr;
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/ImpactTests/ImpactCrashHelper.m:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCrashHelper.m
3 | // ImpactTests
4 | //
5 | // Created by Matt Massicotte on 2019-10-14.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import "ImpactCrashHelper.h"
10 |
11 | @implementation ImpactCrashHelper
12 |
13 | + (ImpactMachODataRegion)regionWithName:(NSString *)name loadAddress:(uint64_t)addr {
14 | NSBundle* bundle = [NSBundle bundleForClass:[self class]];
15 | NSString* path = [bundle pathForResource:name ofType:nil];
16 | NSData* data = [NSData dataWithContentsOfFile:path];
17 |
18 | const void* bytes = [data bytes];
19 |
20 | ImpactMachODataRegion region = {
21 | .address = (uintptr_t)bytes,
22 | .loadAddress = addr,
23 | .length = (uint32_t)data.length
24 | };
25 |
26 | return region;
27 | }
28 |
29 | @end
30 |
--------------------------------------------------------------------------------
/ImpactTests/ImpactCrashTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactCrashTests.swift
3 | // ImpactTests
4 | //
5 | // Created by Matt Massicotte on 2019-09-17.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class ImpactCrashTests: XCTestCase {
12 | lazy var testAppURL: URL = {
13 | let bundleURL = Bundle(for: type(of: self)).bundleURL
14 | let buildDirectoryURL = bundleURL.deletingLastPathComponent()
15 |
16 | return buildDirectoryURL.appendingPathComponent("ImpactTestMac.app")
17 | }()
18 |
19 | lazy var outputDirectory: URL = {
20 | return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
21 | }()
22 |
23 | func newOutputURL() -> URL {
24 | return outputDirectory.appendingPathComponent(UUID().uuidString, isDirectory: false)
25 | }
26 |
27 | func launchAppAndExecute(crash name: String) throws -> URL {
28 | let tempURL = newOutputURL()
29 |
30 | Swift.print("setting: \(tempURL.path)")
31 | let args = ["-output_path", tempURL.path, "-run", name, "-suppressReportCrash", "YES"]
32 |
33 | let app = try NSWorkspace.shared.launchApplication(at: testAppURL,
34 | options: [.withoutActivation, .withoutAddingToRecents],
35 | configuration: [.arguments: args])
36 |
37 | // There appear to be a races around launching, waiting for app.isFinishedLaunching,
38 | // and then waiting for app.isTerminated
39 |
40 | let terminationExpectation = keyValueObservingExpectation(for: app, keyPath: "isTerminated", expectedValue: true)
41 |
42 | wait(for: [terminationExpectation], timeout: 2.0)
43 |
44 | return tempURL
45 | }
46 |
47 | func readCrashData(at url: URL) -> [String] {
48 | let contents = try! Data(contentsOf: url)
49 | let stringContents = String(data: contents, encoding: .utf8)!
50 |
51 | return stringContents.components(separatedBy: "\n")
52 | }
53 |
54 | func testLaunchWithoutCrash() throws {
55 | let url = newOutputURL()
56 |
57 | let args = ["-output_path", url.path]
58 | let app = try NSWorkspace.shared.launchApplication(at: testAppURL,
59 | options: [.withoutActivation, .withoutAddingToRecents],
60 | configuration: [.arguments: args])
61 |
62 | app.terminate()
63 |
64 | let terminationExpectation = keyValueObservingExpectation(for: app, keyPath: "isTerminated", expectedValue: true)
65 |
66 | wait(for: [terminationExpectation], timeout: 2.0)
67 |
68 | let lines = readCrashData(at: url)
69 |
70 | XCTAssertFalse(lines.contains(where: { $0.hasPrefix("[MachException]") }))
71 | XCTAssertFalse(lines.contains("[Thread:Crashed]"))
72 | XCTAssertTrue(lines.contains(where: { $0.hasPrefix("[Application] id: Y29tLnN0YWNrc2lmdC5JbXBhY3RUZXN0TWFj") }))
73 | XCTAssertTrue(lines.contains(where: { $0.hasPrefix("[Environment] platform: macOS") }))
74 | }
75 |
76 | func testCallAbort() throws {
77 | let url = try launchAppAndExecute(crash: "invokeAbort")
78 | let lines = readCrashData(at: url)
79 |
80 | XCTAssertTrue(lines.contains(where: { $0.hasPrefix("[Signal] signal: 0x6") }))
81 | XCTAssertTrue(lines.contains("[Thread:Crashed]"))
82 |
83 | try? FileManager.default.removeItem(at: url)
84 | }
85 |
86 | func testNullDereference() throws {
87 | let url = try launchAppAndExecute(crash: "nullDereference")
88 | let lines = readCrashData(at: url)
89 |
90 | XCTAssertTrue(lines.contains(where: { $0.hasPrefix("[MachException]") }))
91 | XCTAssertTrue(lines.contains("[Thread:Crashed]"))
92 |
93 | try? FileManager.default.removeItem(at: url)
94 | }
95 |
96 | func testUncaughtNSException() throws {
97 | let url = try launchAppAndExecute(crash: "uncaughtNSException")
98 | let lines = readCrashData(at: url)
99 |
100 | XCTAssertTrue(lines.contains(where: { $0.hasPrefix("[MachException]") }))
101 | XCTAssertTrue(lines.contains(where: { $0.hasPrefix("[Exception] type: objc, name: QW5FeGNlcHRpb24=, message: c29tZXRoaW5nIGJhZCBoYXBwZW5lZA==") }))
102 |
103 | try? FileManager.default.removeItem(at: url)
104 | }
105 |
106 | func testNonMainThreadUncaughtNSException() throws {
107 | let url = try launchAppAndExecute(crash: "nonMainThreadUncaughtNSException")
108 | let lines = readCrashData(at: url)
109 |
110 | XCTAssertTrue(lines.contains(where: { $0.hasPrefix("[MachException]") }))
111 | XCTAssertTrue(lines.contains(where: { $0.hasPrefix("[Exception] type: objc, name: QW5FeGNlcHRpb24=, message: c29tZXRoaW5nIGJhZCBoYXBwZW5lZA==") }))
112 |
113 | try? FileManager.default.removeItem(at: url)
114 | }
115 |
116 | func testSubprocessCrash() throws {
117 | let tempURL = newOutputURL()
118 |
119 | let args = ["-output_path", tempURL.path, "-run", "subprocessCrash", "-suppressReportCrash", "YES"]
120 |
121 | let app = try NSWorkspace.shared.launchApplication(at: testAppURL,
122 | options: [.withoutActivation, .withoutAddingToRecents],
123 | configuration: [.arguments: args])
124 |
125 | let launchExpectation = keyValueObservingExpectation(for: app, keyPath: "isFinishedLaunching", expectedValue: true)
126 |
127 | wait(for: [launchExpectation], timeout: 2.0)
128 |
129 | // there is a race here, and I'm not sure how to avoid it
130 | sleep(2)
131 |
132 | let lines = readCrashData(at: tempURL)
133 |
134 | XCTAssertFalse(lines.contains(where: { $0.hasPrefix("[MachException]") }))
135 |
136 | try? FileManager.default.removeItem(at: tempURL)
137 |
138 | app.terminate()
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/ImpactTests/ImpactDWARFCFITests.m:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactDWARFCFITests.m
3 | // ImpactTests
4 | //
5 | // Created by Matt Massicotte on 2019-10-05.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "ImpactDWARF.h"
12 | #import "ImpactDWARFParser.h"
13 | #import "ImpactState.h"
14 | #import "ImpactCrashHelper.h"
15 |
16 | #import
17 |
18 | @interface ImpactDWARFCFITests : XCTestCase
19 |
20 | @end
21 |
22 | @implementation ImpactDWARFCFITests
23 |
24 | - (void)setUp {
25 | GlobalImpactState = malloc(sizeof(ImpactState));
26 |
27 | GlobalImpactState->mutableState.log.fd = STDERR_FILENO;
28 | }
29 |
30 | - (void)tearDown {
31 | free(GlobalImpactState);
32 | }
33 |
34 | - (void)testFunctionReadFDEData {
35 | const ImpactMachODataRegion region = [ImpactCrashHelper regionWithName:@"unwind/libobjc_10_14_4_18E226.eh_frame.x86_64.bin"
36 | loadAddress:0x785ee0];
37 | const ImpactDWARFEnvironment env = {
38 | .pointerWidth = 8
39 | };
40 |
41 | ImpactDWARFCFIData cfiData = {0};
42 |
43 | // first compact unwind entry with a dwarf offset "_tls_init"
44 | ImpactResult result = ImpactDWARFReadData(region, env, 0x00001C78, &cfiData);
45 |
46 | XCTAssertEqual(result, ImpactResultSuccess);
47 |
48 | XCTAssertEqual(cfiData.fde.header.length.length32, 28);
49 | XCTAssertEqual(cfiData.fde.header.length.length64, 0);
50 | XCTAssertEqual(ImpactDWARFCFIHeaderGetLength(cfiData.fde.header), 28);
51 | XCTAssertEqual(cfiData.fde.header.CIE_id, 0x7c);
52 |
53 | // this is a complex one, so it's tough. The spec defines this value as relative to the eh_frame data itself. This results
54 | // in an absolute pointer value that makes sense within a running binary. But, we have a pointer to the eh_frame section
55 | // only, so we have to do a little math.
56 | //
57 | // target_address = functionOffset + imageLoadAddress
58 | // imageLoadAddress = region.address - region.loadAddress
59 | XCTAssertEqual(cfiData.fde.target_address, 0x1298 + (region.address - region.loadAddress));
60 | XCTAssertEqual(cfiData.fde.address_range, 0x18);
61 |
62 | XCTAssertEqual(cfiData.fde.instructions.length, 7);
63 |
64 | XCTAssertEqual(ImpactDWARFCFIHeaderGetLength(cfiData.cie.header), 0x14);
65 | XCTAssertEqual(cfiData.cie.header.CIE_id, 0);
66 | XCTAssertEqual(cfiData.cie.version, 1);
67 | XCTAssertEqualObjects([NSString stringWithUTF8String:cfiData.cie.augmentation], @"zR");
68 | XCTAssertEqual(cfiData.cie.code_alignment_factor, 1);
69 | XCTAssertEqual(cfiData.cie.data_alignment_factor, -8);
70 | XCTAssertEqual(cfiData.cie.return_address_register, 16);
71 |
72 | XCTAssertTrue(cfiData.cie.augmentationData.fdesHaveAugmentationData);
73 | XCTAssertEqual(cfiData.cie.augmentationData.pointerEncoding, 0x10);
74 |
75 | XCTAssertEqual(cfiData.cie.instructions.length, 7);
76 | }
77 |
78 | - (void)testImpactDWARFReadCIEWithPersonality {
79 | const ImpactMachODataRegion region = [ImpactCrashHelper regionWithName:@"unwind/libobjc_10_14_4_18E226.eh_frame.x86_64.bin"
80 | loadAddress:0x785ee0];
81 | const ImpactDWARFEnvironment env = {
82 | .pointerWidth = 8
83 | };
84 |
85 | ImpactDataCursor cursor = {0};
86 | ImpactDWARFCIE cie = {0};
87 |
88 | // 000008e0 => first CIE in this executable with "zPLR" augmentation
89 | ImpactResult result = ImpactDataCursorInitialize(&cursor, region.address, region.length, 0x000008e0);
90 | XCTAssertEqual(result, ImpactResultSuccess);
91 |
92 | result = ImpactDWARFReadCIE(&cursor, env, &cie);
93 | XCTAssertEqual(result, ImpactResultSuccess);
94 |
95 | XCTAssertEqualObjects([NSString stringWithUTF8String:cie.augmentation], @"zPLR");
96 | XCTAssertEqual(cie.augmentationData.pointerEncoding, 0x10);
97 | XCTAssertEqual(cie.augmentationData.personalityEncoding, 0x9b);
98 |
99 | // This is a "pre-resolved" pointer, because the encoding contains DW_EH_PE_indirect. I'm not
100 | // yet certain how decode this successfully. However, since we don't need pesonality functions,
101 | // it isn't critical.
102 | XCTAssertEqual(cie.augmentationData.personality, 0x00789040 + (region.address - region.loadAddress));
103 | }
104 |
105 | - (void)testDWARFCFIRunInstructions {
106 | #if defined(__x86_64__)
107 | const ImpactMachODataRegion region = [ImpactCrashHelper regionWithName:@"unwind/libobjc_10_14_4_18E226.eh_frame.x86_64.bin"
108 | loadAddress:0x785ee0];
109 | const ImpactDWARFEnvironment env = {
110 | .pointerWidth = 8
111 | };
112 |
113 | ImpactDWARFCFIData cfiData = {0};
114 |
115 | // first compact unwind entry with a dwarf offset "_tls_init"
116 | ImpactResult result = ImpactDWARFReadData(region, env, 0x00000C88, &cfiData);
117 | XCTAssertEqual(result, ImpactResultSuccess);
118 |
119 | // 00000bc8 0000001c ffffffff CIE
120 | // Version: 1
121 | // Augmentation: "zPLR"
122 | // Code alignment factor: 1
123 | // Data alignment factor: -8
124 | // Return address column: 16
125 | // Personality Address: 0000258d
126 | // Augmentation data: 9B 8D 25 00 00 10 10
127 | //
128 | // DW_CFA_def_cfa: reg7 +8
129 | // DW_CFA_offset: reg16 -8
130 | // DW_CFA_nop:
131 | // DW_CFA_nop:
132 | //
133 | // 00000c88 0000001c 0000017c FDE cie=0000017c pc=ff88fcb0...ff88fcbb
134 | // DW_CFA_advance_loc: 1
135 | // DW_CFA_def_cfa_offset: +16
136 | // DW_CFA_nop:
137 | // DW_CFA_nop:
138 | // DW_CFA_nop:
139 | // DW_CFA_nop:
140 |
141 | const uint64_t stack[] = {
142 | 0x0,
143 | 0x11223344, // RIP (-8)
144 | 0x0, // CFA (RSP + 16)
145 | 0x0
146 | };
147 | const uint64_t rip = 0x0;
148 | const uint64_t rbp = 0xccaabb;
149 | const uint64_t rsp = (uint64_t)stack;
150 |
151 | const ImpactDWARFTarget target = {
152 | .pc = rip,
153 | .ehFrameRegion = region,
154 | .environment = env
155 | };
156 |
157 | ImpactCPURegisters registers = {0};
158 |
159 | result = ImpactCPUSetRegister(®isters, ImpactCPURegister_X86_64_RIP, rip);
160 | XCTAssertEqual(result, ImpactResultSuccess);
161 |
162 | result = ImpactCPUSetRegister(®isters, ImpactCPURegister_X86_64_RBP, rbp);
163 | XCTAssertEqual(result, ImpactResultSuccess);
164 |
165 | result = ImpactCPUSetRegister(®isters, ImpactCPURegister_X86_64_RSP, rsp);
166 | XCTAssertEqual(result, ImpactResultSuccess);
167 |
168 | result = ImpactDWARFStepRegisters(&cfiData, target, ®isters);
169 | XCTAssertEqual(result, ImpactResultSuccess);
170 |
171 | // now, verify that all the registers are mutated as expected
172 | uintptr_t value = 0;
173 |
174 | result = ImpactCPUGetRegister(®isters, ImpactCPURegister_X86_64_RIP, &value);
175 | XCTAssertEqual(result, ImpactResultSuccess);
176 | XCTAssertEqual(value, 0x11223344);
177 |
178 | result = ImpactCPUGetRegister(®isters, ImpactCPURegister_X86_64_RBP, &value);
179 | XCTAssertEqual(result, ImpactResultSuccess);
180 | XCTAssertEqual(value, 0xccaabb);
181 |
182 | result = ImpactCPUGetRegister(®isters, ImpactCPURegister_X86_64_RSP, &value);
183 | XCTAssertEqual(result, ImpactResultSuccess);
184 | XCTAssertEqual(value, (uint64_t)stack + 16);
185 | #endif
186 | }
187 |
188 | @end
189 |
--------------------------------------------------------------------------------
/ImpactTests/ImpactLogTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // ImpactLogTests.m
3 | // ImpactTests
4 | //
5 | // Created by Matt Massicotte on 2019-09-27.
6 | // Copyright © 2019 Chime Systems Inc. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "ImpactLog.h"
11 |
12 | @interface ImpactLogTests : XCTestCase
13 |
14 | @end
15 |
16 | @implementation ImpactLogTests
17 |
18 | - (NSString *)logContentsWithBlock:(void (^)(ImpactLogger*))block {
19 | NSString* path = [NSString stringWithUTF8String:"/tmp/test.log"];
20 | ImpactState state = {0};
21 |
22 | ImpactResult result = ImpactLogInitialize(&state, path.UTF8String);
23 |
24 | if (result != ImpactResultSuccess) {
25 | return nil;
26 | }
27 |
28 | ImpactLogger* log = ImpactStateGetLog(&state);
29 | block(log);
30 |
31 | ImpactLogFlush(log);
32 |
33 | return [NSString stringWithContentsOfFile:path
34 | encoding:NSUTF8StringEncoding
35 | error:nil];
36 | }
37 |
38 | - (void)testLogZeroInteger {
39 | NSString* contents = [self logContentsWithBlock:^(ImpactLogger* log) {
40 | ImpactLogWriteInteger(log, 0);
41 | }];
42 |
43 | XCTAssertEqualObjects(contents, @"0x0");
44 | }
45 |
46 | - (void)testLogOneDigitInteger {
47 | NSString* contents = [self logContentsWithBlock:^(ImpactLogger* log) {
48 | ImpactLogWriteInteger(log, 9);
49 | }];
50 |
51 | XCTAssertEqualObjects(contents, @"0x9");
52 | }
53 |
54 | - (void)testLogFirstHexInteger {
55 | NSString* contents = [self logContentsWithBlock:^(ImpactLogger* log) {
56 | ImpactLogWriteInteger(log, 10);
57 | }];
58 |
59 | XCTAssertEqualObjects(contents, @"0xa");
60 | }
61 |
62 | - (void)testLogMaxInteger {
63 | NSString* contents = [self logContentsWithBlock:^(ImpactLogger* log) {
64 | ImpactLogWriteInteger(log, UINTPTR_MAX);
65 | }];
66 |
67 | XCTAssertEqualObjects(contents, @"0xffffffffffffffff");
68 | }
69 |
70 | - (void)testLogPastBufferSize {
71 | const size_t length = ImpactLogBufferSize + 1;
72 |
73 | NSString* contents = [self logContentsWithBlock:^(ImpactLogger* log) {
74 | for (int i = 0; i < length; ++i) {
75 | ImpactLogWriteString(log, "a");
76 | }
77 | }];
78 |
79 | NSString *matchingString = [@"" stringByPaddingToLength:length withString: @"a" startingAtIndex:0];
80 |
81 | XCTAssertEqualObjects(contents, matchingString);
82 | }
83 |
84 | @end
85 |
--------------------------------------------------------------------------------
/ImpactTests/ImpactTests-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 |
--------------------------------------------------------------------------------
/ImpactTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ImpactTests/Resources/unwind/libobjc.A.dylib_14_4_4_18E226.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChimeHQ/Impact/0fc5d538e5a93dba52cbe5fedc4b0ce28c19e2c6/ImpactTests/Resources/unwind/libobjc.A.dylib_14_4_4_18E226.gz
--------------------------------------------------------------------------------
/ImpactTests/Resources/unwind/libobjc_10_14_4_18E226.eh_frame.i386.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChimeHQ/Impact/0fc5d538e5a93dba52cbe5fedc4b0ce28c19e2c6/ImpactTests/Resources/unwind/libobjc_10_14_4_18E226.eh_frame.i386.bin
--------------------------------------------------------------------------------
/ImpactTests/Resources/unwind/libobjc_10_14_4_18E226.eh_frame.x86_64.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChimeHQ/Impact/0fc5d538e5a93dba52cbe5fedc4b0ce28c19e2c6/ImpactTests/Resources/unwind/libobjc_10_14_4_18E226.eh_frame.x86_64.bin
--------------------------------------------------------------------------------
/ImpactTests/Resources/unwind/libobjc_10_14_4_18E226.unwind_info.i386.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChimeHQ/Impact/0fc5d538e5a93dba52cbe5fedc4b0ce28c19e2c6/ImpactTests/Resources/unwind/libobjc_10_14_4_18E226.unwind_info.i386.bin
--------------------------------------------------------------------------------
/ImpactTests/Resources/unwind/libobjc_10_14_4_18E226.unwind_info.x86_64.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChimeHQ/Impact/0fc5d538e5a93dba52cbe5fedc4b0ce28c19e2c6/ImpactTests/Resources/unwind/libobjc_10_14_4_18E226.unwind_info.x86_64.bin
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019, Chime Systems Inc.
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "Impact",
7 | platforms: [.macOS(.v10_13), .iOS(.v12), .tvOS(.v12)],
8 | products: [
9 | .library(name: "Impact", targets: ["Impact"]),
10 | ],
11 | dependencies: [],
12 | targets: [
13 | .target(name: "Impact",
14 | dependencies: [],
15 | path: "Impact/",
16 | cSettings: [
17 | .headerSearchPath(""),
18 | .headerSearchPath("Utility"),
19 | .headerSearchPath("DWARF"),
20 | .headerSearchPath("Unwind"),
21 | .headerSearchPath("Monitoring"),
22 | .define("CURRENT_PROJECT_VERSION", to: "13")
23 | ]
24 | ),
25 | .testTarget(name: "ImpactTests",
26 | dependencies: ["Impact"],
27 | path: "ImpactTests/",
28 | exclude: ["ImpactCrashTests.swift", "ImpactCrashHelper.m"],
29 | cSettings: [
30 | .headerSearchPath("Impact"),
31 | .headerSearchPath("Utility"),
32 | .headerSearchPath("DWARF"),
33 | .headerSearchPath("Unwind"),
34 | .headerSearchPath("Monitoring"),
35 | .define("CURRENT_PROJECT_VERSION", to: "13")
36 | ]
37 | ),
38 | ]
39 | )
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/ChimeHQ/Impact/actions)
2 |
3 | # Impact
4 |
5 | Impact is a crash detection and recording library for Apple platforms. It is **not** a full crash reporting system. But, it could be the core of one. Its design goals are:
6 |
7 | * Improve understanding of crash reporting systems
8 | * Support for all Apple platforms
9 | * Reliablity
10 | * Accuracy
11 | * Simplicity
12 | * Fun
13 |
14 | Current feature set:
15 |
16 | * Mach Exceptions
17 | * UNIX signals
18 | * NSExceptions (including from within AppKit)
19 | * Frame pointer-based stack unwinding
20 | * Partial compact unwind support for x86_64 and arm64
21 | * Partial DWARF CFI support for x86_64 and arm64
22 |
23 | Impact uses a text-based log format. While the format itself is stable, the contents are still **unstable**. It is designed to be simple, while still making both debugging and parsing possible in the face of crash-time failure.
24 |
25 | ## Isn't Crash Reporting a Solved Problem?
26 |
27 | In-process crash reporting is just terrible. The mechanisms available for crash event detection, UNIX signals and Mach exceptions, are complex, buggy, and are not capable of capturing all kinds of failures. On top of that, the enviroment in which a crash reporter needs to run is extraordinarily hostile. It's just messy business.
28 |
29 | Apple has had all of the device-side pieces in place to produce a world-class crash reporting system for a long time. While they have the reporting side down, the developer experience (analysis, presentation, investigative tools) leaves a lot to be desired. This has kept 3rd-party reporting services essential for the vast majority of app developers. Apple's system also doesn't work for macOS apps outside the App Store, which is disappointing.
30 |
31 | My sincere hope is that Apple addresses these limitations so we can all stop this foolishness once and for all.
32 |
33 | But, for now, in-process reporting is a necessary component for most developers. There are many trade-offs and design decisions that dramatically affect the qualities of a reporting system. Understanding those trade-offs, and being explicit about choices that affect them is one of the goals of this project.
34 |
35 | Also, crash reporting is just a fun and fascinating problem. It tends to be very commonly used and very poorly understood. I think the Apple development community could benefit a lot from a more in-depth understanding of the area.
36 |
37 | ## Can I Use Impact in my App?
38 |
39 | You must keep in mind that Impact **only** captures information about crash events. It does not have any facilities for transmitting those events back to you or translating them into human-readable versions. A full reporting system requires a little more work. Here are a few options:
40 |
41 | - Impact on its own, with full control over how you produce and consume reports
42 | - Integrate with [Wells](https://github.com/ChimeHQ/Wells) for report transmission and management
43 | - MetricKit crash reporting with graceful fallback via [Meter](https://github.com/ChimeHQ/Meter) and [ImpactMeterAdapter](https://github.com/ChimeHQ/ImpactMeterAdapter)
44 |
45 | ## Relationship to Crashlytics
46 |
47 | I worked at [Crashlytics](https://firebase.google.com/products/crashlytics) for many years. During my time there, I briefly worked with [PLCrashReporter](https://www.plcrashreporter.org) before starting from scratch and building a completely custom system. I spent a considerable amount of time there analyzing and understanding the failure modes of in-process crash reporting. That work shaped most of the design of the Crashlytics SDK, though things might have changed since I left.
48 |
49 | Impact does share many of those design philosophies. It's hard to unsee solutions, sometimes. But, it's primarily because I believe those core design concepts are the best approach.
50 |
51 | ## Contributing
52 |
53 | It would be wonderful to see contributions. If you'd like to work on something, the safest bet is to open an issue or PR first. That way, we can discuss the changes before you spend too much time working.
54 |
55 | There is absolutely no experience/knowledge requirement. Interest is all you need, I am happy to help.
56 |
57 | ## Suggestions or Feedback
58 |
59 | We'd love to hear from you! Get in touch via an issue or pull request.
60 |
61 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
62 |
--------------------------------------------------------------------------------
/crasher/main.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | precondition(false)
4 |
--------------------------------------------------------------------------------