24
24
#include < unistd.h>
25
25
#include < algorithm>
26
26
#include < array>
27
- #include < chrono>
28
- #include < ios>
29
- #include < map>
27
+ #include < mutex>
30
28
#include < set>
31
- #include < sstream>
32
29
#include < string>
33
30
#include < system_error>
34
31
#include < vector>
35
32
36
33
#include < time.h>
37
34
38
- #include < forkjail/ForkJail.h>
35
+ #include < profilo/profiler/SignalHandler.h>
36
+ #include < profilo/util/common.h>
39
37
40
38
namespace fbjni = facebook::jni;
41
39
42
40
namespace facebook {
43
41
namespace profilo {
42
+
43
+ using profiler::SignalHandler;
44
+
44
45
namespace artcompat {
45
46
46
47
struct JavaFrame {
@@ -177,6 +178,32 @@ uint64_t now() {
177
178
return ((uint64_t )ts.tv_sec ) * 1'000'000'000 + ts.tv_nsec ;
178
179
}
179
180
181
+ namespace {
182
+
183
+ struct SignalState {
184
+ sigjmp_buf sigJmpBuf;
185
+ std::atomic_bool inSection;
186
+ uint64_t tid;
187
+ };
188
+ // Signal handler to safely bail out. This is inspired by how the
189
+ // SamplingProfiler signal handling works, but is way simpler.
190
+
191
+ void JumpToSafetySignalHandler (
192
+ SignalHandler::HandlerScope scope,
193
+ int signum,
194
+ siginfo_t * siginfo,
195
+ void * ucontext) {
196
+ SignalState& state = *(SignalState*)scope.GetData ();
197
+
198
+ if (state.tid == threadID () && state.inSection .load ()) {
199
+ scope.siglongjmp (state.sigJmpBuf , 1 );
200
+ }
201
+
202
+ scope.CallPreviousHandler (signum, siginfo, ucontext);
203
+ }
204
+
205
+ } // namespace
206
+
180
207
//
181
208
// We collect two stack traces, from the same JNI function (and therefore VM
182
209
// frame) - one from Java, using normal VM APIs and one using our
@@ -198,20 +225,20 @@ uint64_t now() {
198
225
bool runJavaCompatibilityCheckInternal (
199
226
versions::AndroidVersion version,
200
227
profiler::JavaBaseTracer* tracer) {
228
+ // Because we only have one instance of the signal handling state,
229
+ // we wrap everything in a lock to serialize all callers and simplify the
230
+ // logic.
231
+ static std::mutex exclusiveRunLock;
232
+ std::lock_guard<std::mutex> lg{exclusiveRunLock};
233
+
201
234
auto begin = now ();
202
- constexpr int kExitCodeSuccess = 100 ;
203
- constexpr int kExitCodeFailure = 150 ;
204
- constexpr int kTimeoutSec = 1 ;
205
235
206
236
auto jlThread_class = getThreadClass ();
207
237
auto jlThread_currentThread = jlThread_class->getStaticMethod <jobject ()>(
208
238
" currentThread" , " ()Ljava/lang/Thread;" );
209
239
auto jlThread = jlThread_currentThread (jlThread_class);
210
240
211
- //
212
- // We must collect the Java stack trace before we fork because of internal
213
- // allocation locks within art.
214
- //
241
+ // Collect the Java stack trace
215
242
auto beginJava = now ();
216
243
std::vector<JavaFrame> javaStack;
217
244
try {
@@ -221,52 +248,49 @@ bool runJavaCompatibilityCheckInternal(
221
248
}
222
249
auto endJava = now ();
223
250
224
- // Performs initialization in the parent, before we fork.
225
- tracer->prepare ();
226
-
227
- auto beginCpp = now ();
228
- forkjail::ForkJail jail (
229
- [&javaStack, tracer] {
230
- try {
231
- tracer->startTracing ();
232
-
233
- std::array<CppUnwinderJavaFrame, kStackSize > cppStack;
234
- auto cppStackSize = getCppStackTrace (tracer, cppStack);
235
-
236
- if (compareStackTraces (cppStack, cppStackSize, javaStack)) {
237
- FBLOGV (" compareStackTraces returned true" );
238
- forkjail::ForkJail::real_exit (kExitCodeSuccess );
239
- }
240
-
241
- FBLOGV (" compareStackTraces returned false" );
242
- } catch (...) {
243
- FBLOGV (" Ignored exception" );
244
- // intentionally ignored
245
- }
246
-
247
- forkjail::ForkJail::real_exit (kExitCodeFailure );
248
- },
249
- kTimeoutSec );
250
-
251
+ // Collect our tracer's stack trace
251
252
try {
252
- auto child = jail.forkAndRun ();
253
- // Child process would never reach here. Only the parent continues.
254
- // Wait for the child to exit.
255
- int status = 0 ;
256
-
257
- do {
258
- if (waitpid (child, &status, 0 ) != child) {
259
- throw std::system_error (errno, std::system_category (), " waitpid" );
260
- }
261
- } while (!WIFEXITED (status) && !WIFSIGNALED (status));
253
+ tracer->prepare ();
254
+ auto beginCpp = now ();
255
+
256
+ tracer->startTracing ();
257
+
258
+ bool cppSuccess = false ;
259
+ size_t cppStackSize = 0 ;
260
+ std::array<CppUnwinderJavaFrame, kStackSize > cppStack;
261
+
262
+ // Sets up signal handlers for SIGSEGV and SIGBUS. Uses SignalHandler, so
263
+ // cannot be run concurrently with SamplingProfiler's usage (but that's
264
+ // okay, compatibility checks gate the SamplingProfiler usage).
265
+ //
266
+ // Ultimately, we do need to use the exact same safety mechanism as the
267
+ // profiler to work around the exact same bugs in Android's signal handling.
268
+ static SignalState state{};
269
+ auto & handlerSegv =
270
+ SignalHandler::Initialize (SIGSEGV, JumpToSafetySignalHandler);
271
+ handlerSegv.SetData (&state);
272
+ handlerSegv.Enable ();
273
+
274
+ auto & handlerBus =
275
+ SignalHandler::Initialize (SIGBUS, JumpToSafetySignalHandler);
276
+ handlerBus.SetData (&state);
277
+ handlerBus.Enable ();
278
+
279
+ if (sigsetjmp (state.sigJmpBuf , 1 ) == 0 ) {
280
+ state.tid = threadID ();
281
+ state.inSection .store (true );
282
+ cppStackSize = getCppStackTrace (tracer, cppStack);
283
+ state.inSection .store (false );
284
+
285
+ cppSuccess = true ;
286
+ } else {
287
+ // Long jump from signal handler
288
+ state.inSection .store (false );
289
+ cppSuccess = false ;
290
+ }
262
291
263
- FBLOGD (
264
- " Cpp stack child exited: %i status: %i (%s) signalled: %i signal: %i" ,
265
- WIFEXITED (status),
266
- WEXITSTATUS (status),
267
- WEXITSTATUS (status) == kExitCodeSuccess ? " success" : " failure" ,
268
- WIFSIGNALED (status),
269
- WTERMSIG (status));
292
+ handlerSegv.Disable ();
293
+ handlerBus.Disable ();
270
294
271
295
auto end = now ();
272
296
FBLOGD (
@@ -276,10 +300,16 @@ bool runJavaCompatibilityCheckInternal(
276
300
(endJava - beginJava) / 1'000'000 ,
277
301
(end - beginCpp) / 1'000'000 );
278
302
279
- if (!WIFEXITED (status) || WEXITSTATUS (status) != kExitCodeSuccess ) {
303
+ if (!cppSuccess) {
304
+ FBLOGE (" getCppStackTrace signalled" );
280
305
return false ;
281
306
}
282
307
308
+ if (!compareStackTraces (cppStack, cppStackSize, javaStack)) {
309
+ FBLOGE (" compareStackTraces returned false" );
310
+ return false ;
311
+ }
312
+ FBLOGI (" Compatibility check succeeded" );
283
313
return true ;
284
314
} catch (std::system_error& ex) {
285
315
FBLOGE (" Caught system error: %s" , ex.what ());
0 commit comments