Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// Copyright 2019 Google
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
 
15
#include "Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h"
16
#include "Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h"
17
#include "Crashlytics/Crashlytics/Unwind/Compact/FIRCLSCompactUnwind.h"
18
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
19
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
20
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
21
 
22
#include <mach/mach.h>
23
#include <signal.h>
24
#include <stdio.h>
25
 
26
// Without a limit on the number of frames we unwind, there's a real possibility
27
// we'll get stuck in an infinite loop. But, we still need pretty big limits,
28
// because stacks can get quite big. Also, the stacks are different on the platforms.
29
// These values were empirically determined (~525000 on OS X, ~65000 on iOS).
30
#if TARGET_OS_EMBEDDED
31
const uint32_t FIRCLSUnwindMaxFrames = 100000;
32
#else
33
const uint32_t FIRCLSUnwindMaxFrames = 600000;
34
#endif
35
 
36
const uint32_t FIRCLSUnwindInfiniteRecursionCountThreshold = 10;
37
 
38
#pragma mark Prototypes
39
static bool FIRCLSUnwindNextFrameUsingAllStrategies(FIRCLSUnwindContext* context);
40
#if CLS_COMPACT_UNWINDING_SUPPORTED
41
static bool FIRCLSUnwindWithCompactUnwindInfo(FIRCLSUnwindContext* context);
42
#endif
43
bool FIRCLSUnwindContextHasValidPCAndSP(FIRCLSUnwindContext* context);
44
 
45
#pragma mark - API
46
bool FIRCLSUnwindInit(FIRCLSUnwindContext* context, FIRCLSThreadContext threadContext) {
47
  if (!context) {
48
    return false;
49
  }
50
 
51
  memset(context, 0, sizeof(FIRCLSUnwindContext));
52
 
53
  context->registers = threadContext;
54
 
55
  return true;
56
}
57
 
58
bool FIRCLSUnwindNextFrame(FIRCLSUnwindContext* context) {
59
  if (!FIRCLSIsValidPointer(context)) {
60
    FIRCLSSDKLog("Error: invalid inputs\n");
61
    return false;
62
  }
63
 
64
  if (!FIRCLSUnwindContextHasValidPCAndSP(context)) {
65
    // This is a special-case. It is possible to try to unwind a thread that has no stack (ie, is
66
    // executing zero functions. I believe this happens when a thread has exited, but before the
67
    // kernel has actually cleaned it up. This situation can only apply to the first frame. So, in
68
    // that case, we don't count it as an error. But, if it happens mid-unwind, it's a problem.
69
 
70
    if (context->frameCount == 0) {
71
      FIRCLSSDKLog("Cancelling unwind for thread with invalid PC/SP\n");
72
    } else {
73
      FIRCLSSDKLog("Error: thread PC/SP invalid before unwind\n");
74
    }
75
 
76
    return false;
77
  }
78
 
79
  if (!FIRCLSUnwindNextFrameUsingAllStrategies(context)) {
80
    FIRCLSSDKLogError("Failed to advance to the next frame\n");
81
    return false;
82
  }
83
 
84
  uintptr_t pc = FIRCLSUnwindGetPC(context);
85
  uintptr_t sp = FIRCLSUnwindGetStackPointer(context);
86
 
87
  // Unwinding will complete when this is no longer a valid value
88
  if (!FIRCLSIsValidPointer(pc)) {
89
    return false;
90
  }
91
 
92
  // after unwinding, validate that we have a sane register value
93
  if (!FIRCLSIsValidPointer(sp)) {
94
    FIRCLSSDKLog("Error: SP (%p) isn't a valid pointer\n", (void*)sp);
95
    return false;
96
  }
97
 
98
  // track repeating frames
99
  if (context->lastFramePC == pc) {
100
    context->repeatCount += 1;
101
  } else {
102
    context->repeatCount = 0;
103
  }
104
 
105
  context->frameCount += 1;
106
  context->lastFramePC = pc;
107
 
108
  return true;
109
}
110
 
111
#pragma mark - Register Accessors
112
uintptr_t FIRCLSUnwindGetPC(FIRCLSUnwindContext* context) {
113
  if (!FIRCLSIsValidPointer(context)) {
114
    return 0;
115
  }
116
 
117
  return FIRCLSThreadContextGetPC(&context->registers);
118
}
119
 
120
uintptr_t FIRCLSUnwindGetStackPointer(FIRCLSUnwindContext* context) {
121
  if (!FIRCLSIsValidPointer(context)) {
122
    return 0;
123
  }
124
 
125
  return FIRCLSThreadContextGetStackPointer(&context->registers);
126
}
127
 
128
static uintptr_t FIRCLSUnwindGetFramePointer(FIRCLSUnwindContext* context) {
129
  if (!FIRCLSIsValidPointer(context)) {
130
    return 0;
131
  }
132
 
133
  return FIRCLSThreadContextGetFramePointer(&context->registers);
134
}
135
 
136
uint32_t FIRCLSUnwindGetFrameRepeatCount(FIRCLSUnwindContext* context) {
137
  if (!FIRCLSIsValidPointer(context)) {
138
    return 0;
139
  }
140
 
141
  return context->repeatCount;
142
}
143
 
144
#pragma mark - Unwind Strategies
145
static bool FIRCLSUnwindNextFrameUsingAllStrategies(FIRCLSUnwindContext* context) {
146
  if (!FIRCLSIsValidPointer(context)) {
147
    FIRCLSSDKLogError("Arguments invalid\n");
148
    return false;
149
  }
150
 
151
  if (context->frameCount >= FIRCLSUnwindMaxFrames) {
152
    FIRCLSSDKLogWarn("Exceeded maximum number of frames\n");
153
    return false;
154
  }
155
 
156
  uintptr_t pc = FIRCLSUnwindGetPC(context);
157
 
158
  // Ok, what's going on here? libunwind's UnwindCursor<A,R>::setInfoBasedOnIPRegister has a
159
  // parameter that, if true, does this subtraction. Despite the comments in the code
160
  // (of 35.1), I found that the parameter was almost always set to true.
161
  //
162
  // I then ran into a problem when unwinding from _pthread_start -> thread_start. This
163
  // is a common transition, which happens in pretty much every report. An extra frame
164
  // was being generated, because the PC we get for _pthread_start was mapping to exactly
165
  // one greater than the function's last byte, according to the compact unwind info. This
166
  // resulted in using the wrong compact encoding, and picking the next function, which
167
  // turned out to be dwarf instead of a frame pointer.
168
 
169
  // So, the moral is - do the subtraction for all frames except the first. I haven't found
170
  // a case where it produces an incorrect result. Also note that at first, I thought this would
171
  // subtract one from the final addresses too. But, the end of this function will *compute* PC,
172
  // so this value is used only to look up unwinding data.
173
 
174
  if (context->frameCount > 0) {
175
    --pc;
176
    if (!FIRCLSThreadContextSetPC(&context->registers, pc)) {
177
      FIRCLSSDKLogError("Unable to set PC\n");
178
      return false;
179
    }
180
  }
181
 
182
  if (!FIRCLSIsValidPointer(pc)) {
183
    FIRCLSSDKLogError("PC is invalid\n");
184
    return false;
185
  }
186
 
187
  // the first frame is special - as the registers we need
188
  // are already loaded by definition
189
  if (context->frameCount == 0) {
190
    return true;
191
  }
192
 
193
#if CLS_COMPACT_UNWINDING_SUPPORTED
194
  // attempt to advance to the next frame using compact unwinding, and
195
  // only fall back to the frame pointer if that fails
196
  if (FIRCLSUnwindWithCompactUnwindInfo(context)) {
197
    return true;
198
  }
199
#endif
200
 
201
  // If the frame pointer is zero, we cannot use an FP-based unwind and we can reasonably
202
  // assume that we've just gotten to the end of the stack.
203
  if (FIRCLSUnwindGetFramePointer(context) == 0) {
204
    FIRCLSSDKLogWarn("FP is zero, aborting unwind\n");
205
    // make sure to set the PC to zero, to indicate the unwind is complete
206
    return FIRCLSThreadContextSetPC(&context->registers, 0);
207
  }
208
 
209
  // Only allow stack scanning (as a last resort) if we're on the first frame. All others
210
  // are too likely to screw up.
211
  if (FIRCLSUnwindWithFramePointer(&context->registers, context->frameCount == 1)) {
212
    return true;
213
  }
214
 
215
  FIRCLSSDKLogError("Unable to use frame pointer\n");
216
 
217
  return false;
218
}
219
 
220
#if CLS_COMPACT_UNWINDING_SUPPORTED
221
static bool FIRCLSUnwindWithCompactUnwindInfo(FIRCLSUnwindContext* context) {
222
  if (!context) {
223
    return false;
224
  }
225
 
226
  // step one - find the image the current pc is within
227
  FIRCLSBinaryImageRuntimeNode image;
228
 
229
  uintptr_t pc = FIRCLSUnwindGetPC(context);
230
 
231
  if (!FIRCLSBinaryImageSafeFindImageForAddress(pc, &image)) {
232
    FIRCLSSDKLogWarn("Unable to find binary for %p\n", (void*)pc);
233
    return false;
234
  }
235
 
236
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
237
  FIRCLSSDKLogDebug("Binary image for %p at %p => %s\n", (void*)pc, image.baseAddress, image.name);
238
#else
239
  FIRCLSSDKLogDebug("Binary image for %p at %p\n", (void*)pc, image.baseAddress);
240
#endif
241
 
242
  if (!FIRCLSBinaryImageSafeHasUnwindInfo(&image)) {
243
    FIRCLSSDKLogInfo("Binary image at %p has no unwind info\n", image.baseAddress);
244
    return false;
245
  }
246
 
247
  if (!FIRCLSCompactUnwindInit(&context->compactUnwindState, image.unwindInfo, image.ehFrame,
248
                               (uintptr_t)image.baseAddress)) {
249
    FIRCLSSDKLogError("Unable to read unwind info\n");
250
    return false;
251
  }
252
 
253
  // this function will actually attempt to find compact unwind info for the current PC,
254
  // and use it to mutate the context register state
255
  return FIRCLSCompactUnwindLookupAndCompute(&context->compactUnwindState, &context->registers);
256
}
257
#endif
258
 
259
#pragma mark - Utility Functions
260
bool FIRCLSUnwindContextHasValidPCAndSP(FIRCLSUnwindContext* context) {
261
  return FIRCLSIsValidPointer(FIRCLSUnwindGetPC(context)) &&
262
         FIRCLSIsValidPointer(FIRCLSUnwindGetStackPointer(context));
263
}
264
 
265
#if CLS_CPU_64BIT
266
#define BASIC_INFO_TYPE vm_region_basic_info_64_t
267
#define BASIC_INFO VM_REGION_BASIC_INFO_64
268
#define BASIC_INFO_COUNT VM_REGION_BASIC_INFO_COUNT_64
269
#define vm_region_query_fn vm_region_64
270
#else
271
#define BASIC_INFO_TYPE vm_region_basic_info_t
272
#define BASIC_INFO VM_REGION_BASIC_INFO
273
#define BASIC_INFO_COUNT VM_REGION_BASIC_INFO_COUNT
274
#define vm_region_query_fn vm_region
275
#endif
276
bool FIRCLSUnwindIsAddressExecutable(vm_address_t address) {
277
#if CLS_COMPACT_UNWINDING_SUPPORTED
278
  FIRCLSBinaryImageRuntimeNode unusedNode;
279
 
280
  return FIRCLSBinaryImageSafeFindImageForAddress(address, &unusedNode);
281
#else
282
  return true;
283
#endif
284
}
285
 
286
bool FIRCLSUnwindFirstExecutableAddress(vm_address_t start,
287
                                        vm_address_t end,
288
                                        vm_address_t* foundAddress) {
289
  // This function walks up the data on the stack, looking for the first value that is an address on
290
  // an exectuable page.  This is a heurestic, and can hit false positives.
291
 
292
  *foundAddress = 0;  // write in a 0
293
 
294
  do {
295
    vm_address_t address;
296
 
297
    FIRCLSSDKLogDebug("Checking address %p => %p\n", (void*)start, (void*)*(uintptr_t*)start);
298
 
299
    // if start isn't a valid pointer, don't even bother trying
300
    if (FIRCLSIsValidPointer(start)) {
301
      if (!FIRCLSReadMemory(start, &address, sizeof(void*))) {
302
        // if we fail to read from the stack, we're done
303
        return false;
304
      }
305
 
306
      FIRCLSSDKLogDebug("Checking for executable %p\n", (void*)address);
307
      // when we find an exectuable address, we're finished
308
      if (FIRCLSUnwindIsAddressExecutable(address)) {
309
        *foundAddress = address;
310
        return true;
311
      }
312
    }
313
 
314
    start += sizeof(void*);  // move back up the stack
315
 
316
  } while (start < end);
317
 
318
  return false;
319
}