-
-
Notifications
You must be signed in to change notification settings - Fork 316
/
SentryThreadInspector.m
218 lines (179 loc) · 8.32 KB
/
SentryThreadInspector.m
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#import "SentryThreadInspector.h"
#import "SentryBinaryImageCache.h"
#import "SentryCrashDefaultMachineContextWrapper.h"
#import "SentryCrashStackCursor.h"
#include "SentryCrashStackCursor_MachineContext.h"
#import "SentryCrashStackEntryMapper.h"
#include "SentryCrashSymbolicator.h"
#import "SentryFrame.h"
#import "SentryInAppLogic.h"
#import "SentryOptions.h"
#import "SentryStacktrace.h"
#import "SentryStacktraceBuilder.h"
#import "SentryThread.h"
#include <pthread.h>
@interface
SentryThreadInspector ()
@property (nonatomic, strong) SentryStacktraceBuilder *stacktraceBuilder;
@property (nonatomic, strong) id<SentryCrashMachineContextWrapper> machineContextWrapper;
@end
typedef struct {
SentryCrashThread thread;
SentryCrashStackEntry stackEntries[MAX_STACKTRACE_LENGTH];
int stackLength;
} SentryThreadInfo;
// We need a C function to retrieve information from the stack trace in order to avoid
// calling into not async-signal-safe code while there are suspended threads.
unsigned int
getStackEntriesFromThread(SentryCrashThread thread, struct SentryCrashMachineContext *context,
SentryCrashStackEntry *buffer, unsigned int maxEntries)
{
sentrycrashmc_getContextForThread(thread, context, NO);
SentryCrashStackCursor stackCursor;
sentrycrashsc_initWithMachineContext(&stackCursor, MAX_STACKTRACE_LENGTH, context);
unsigned int entries = 0;
while (stackCursor.advanceCursor(&stackCursor)) {
if (entries == maxEntries)
break;
if (stackCursor.symbolicate(&stackCursor)) {
buffer[entries] = stackCursor.stackEntry;
entries++;
}
}
return entries;
}
@implementation SentryThreadInspector
- (id)initWithStacktraceBuilder:(SentryStacktraceBuilder *)stacktraceBuilder
andMachineContextWrapper:(id<SentryCrashMachineContextWrapper>)machineContextWrapper
{
if (self = [super init]) {
self.stacktraceBuilder = stacktraceBuilder;
self.machineContextWrapper = machineContextWrapper;
}
return self;
}
- (instancetype)initWithOptions:(SentryOptions *)options
{
SentryInAppLogic *inAppLogic =
[[SentryInAppLogic alloc] initWithInAppIncludes:options.inAppIncludes
inAppExcludes:options.inAppExcludes];
SentryCrashStackEntryMapper *crashStackEntryMapper =
[[SentryCrashStackEntryMapper alloc] initWithInAppLogic:inAppLogic];
SentryStacktraceBuilder *stacktraceBuilder =
[[SentryStacktraceBuilder alloc] initWithCrashStackEntryMapper:crashStackEntryMapper];
stacktraceBuilder.symbolicate = options.debug;
id<SentryCrashMachineContextWrapper> machineContextWrapper =
[[SentryCrashDefaultMachineContextWrapper alloc] init];
return [self initWithStacktraceBuilder:stacktraceBuilder
andMachineContextWrapper:machineContextWrapper];
}
- (SentryStacktrace *)stacktraceForCurrentThreadAsyncUnsafe
{
return [self.stacktraceBuilder buildStacktraceForCurrentThreadAsyncUnsafe];
}
- (NSArray<SentryThread *> *)getCurrentThreads
{
NSMutableArray<SentryThread *> *threads = [NSMutableArray new];
SentryCrashMC_NEW_CONTEXT(context);
SentryCrashThread currentThread = sentrycrashthread_self();
[self.machineContextWrapper fillContextForCurrentThread:context];
int threadCount = [self.machineContextWrapper getThreadCount:context];
for (int i = 0; i < threadCount; i++) {
SentryCrashThread thread = [self.machineContextWrapper getThread:context withIndex:i];
SentryThread *sentryThread = [[SentryThread alloc] initWithThreadId:@(i)];
sentryThread.isMain =
[NSNumber numberWithBool:[self.machineContextWrapper isMainThread:thread]];
sentryThread.name = [self getThreadName:thread];
sentryThread.crashed = @NO;
bool isCurrent = thread == currentThread;
sentryThread.current = @(isCurrent);
if (isCurrent) {
sentryThread.stacktrace = [self.stacktraceBuilder buildStacktraceForCurrentThread];
}
// We need to make sure the main thread is always the first thread in the result
if ([self.machineContextWrapper isMainThread:thread])
[threads insertObject:sentryThread atIndex:0];
else
[threads addObject:sentryThread];
}
return threads;
}
/**
* We are not sharing code with 'getCurrentThreads' because both methods use different approaches.
* This method retrieves thread information from the suspend method
* while the other retrieves information from the machine context.
* Having both approaches in the same method can lead to inconsistency between the number of
* threads, and while there is suspended threads we can't call into obj-c, so the previous approach
* wont work for retrieving stacktrace information for every thread.
*/
- (NSArray<SentryThread *> *)getCurrentThreadsWithStackTrace
{
NSMutableArray<SentryThread *> *threads = [NSMutableArray new];
@synchronized(self) {
SentryCrashMC_NEW_CONTEXT(context);
SentryCrashThread currentThread = sentrycrashthread_self();
thread_act_array_t suspendedThreads = NULL;
mach_msg_type_number_t numSuspendedThreads = 0;
// SentryThreadInspector is crashing when there is too many threads.
// We add a limit of 70 threads because in test with up to 100 threads it seems fine.
// We are giving it an extra safety margin.
sentrycrashmc_suspendEnvironment_upToMaxSupportedThreads(
&suspendedThreads, &numSuspendedThreads, 70);
// DANGER: Do not try to allocate memory in the heap or call Objective-C code in this
// section Doing so when the threads are suspended may lead to deadlocks or crashes.
// If no threads was suspended we don't need to do anything.
// This may happen if there is more than max amount of threads (70).
if (numSuspendedThreads == 0) {
return threads;
}
SentryThreadInfo threadsInfos[numSuspendedThreads];
for (int i = 0; i < numSuspendedThreads; i++) {
if (suspendedThreads[i] != currentThread) {
int numberOfEntries = getStackEntriesFromThread(suspendedThreads[i], context,
threadsInfos[i].stackEntries, MAX_STACKTRACE_LENGTH);
threadsInfos[i].stackLength = numberOfEntries;
} else {
// We can't use 'getStackEntriesFromThread' to retrieve stack frames from the
// current thread. We are using the stackTraceBuilder to retrieve this information
// later.
threadsInfos[i].stackLength = 0;
}
threadsInfos[i].thread = suspendedThreads[i];
}
sentrycrashmc_resumeEnvironment(suspendedThreads, numSuspendedThreads);
// DANGER END: You may call Objective-C code again or allocate memory.
for (int i = 0; i < numSuspendedThreads; i++) {
SentryThread *sentryThread = [[SentryThread alloc] initWithThreadId:@(i)];
sentryThread.isMain = [NSNumber numberWithBool:i == 0];
sentryThread.name = [self getThreadName:threadsInfos[i].thread];
sentryThread.crashed = @NO;
bool isCurrent = threadsInfos[i].thread == currentThread;
sentryThread.current = @(isCurrent);
if (isCurrent) {
sentryThread.stacktrace = [self.stacktraceBuilder buildStacktraceForCurrentThread];
} else {
sentryThread.stacktrace = [self.stacktraceBuilder
buildStackTraceFromStackEntries:threadsInfos[i].stackEntries
amount:threadsInfos[i].stackLength];
}
// We need to make sure the main thread is always the first thread in the result
if ([self.machineContextWrapper isMainThread:threadsInfos[i].thread])
[threads insertObject:sentryThread atIndex:0];
else
[threads addObject:sentryThread];
}
}
return threads;
}
- (NSString *)getThreadName:(SentryCrashThread)thread
{
char buffer[128];
char *const pBuffer = buffer;
[self.machineContextWrapper getThreadName:thread andBuffer:pBuffer andBufLength:128];
NSString *threadName = [NSString stringWithCString:pBuffer encoding:NSUTF8StringEncoding];
if (nil == threadName) {
threadName = @"";
}
return threadName;
}
@end