Skip to content

Commit 208231d

Browse files
authored
[compiler-rt][TSan] Add support for Android (#147580)
1. Fixed Android setjmp issue. The root cause is that TSan initializes before longjmp_xor_key is set up. During __libc_init_vdso, a call to strcmp triggers TSan initialization, which occurs before __libc_init_setjmp_cookie. The solution is to call InitializeLongjmpXorKey on the first use of longjmp_xor_key. Additionally, correct LONG_JMP_SP_ENV_SLOT by following the bionic source code. 2. Skip thr object range check on Android. On Android, thr is allocated on the heap, causing the check to fail. 3. Disable intercepting clone on Android. pthread_create internally calls clone. Disabling the interception of clone resolves the issue in most scenarios. 4. Use a workaround to recover the thr pointer stored in TLS_SLOT_SANITIZER slot, whose value was modified by Skia. This PR solved the issue from NDK android/ndk#1041. Test project: https://github.com/bytedance/android_tsan_sample/
1 parent d7feeda commit 208231d

File tree

3 files changed

+49
-14
lines changed

3 files changed

+49
-14
lines changed

compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2412,7 +2412,11 @@ TSAN_INTERCEPTOR(int, vfork, int fake) {
24122412
}
24132413
#endif
24142414

2415-
#if SANITIZER_LINUX
2415+
#if SANITIZER_LINUX && !SANITIZER_ANDROID
2416+
// Bionic's pthread_create internally calls clone. When the CLONE_THREAD flag is
2417+
// set, clone does not create a new process but a new thread. This is a
2418+
// workaround for Android. Disabling the interception of clone solves the
2419+
// problem in most scenarios.
24162420
TSAN_INTERCEPTOR(int, clone, int (*fn)(void *), void *stack, int flags,
24172421
void *arg, int *parent_tid, void *tls, pid_t *child_tid) {
24182422
SCOPED_INTERCEPTOR_RAW(clone, fn, stack, flags, arg, parent_tid, tls,
@@ -3135,7 +3139,7 @@ void InitializeInterceptors() {
31353139

31363140
TSAN_INTERCEPT(fork);
31373141
TSAN_INTERCEPT(vfork);
3138-
#if SANITIZER_LINUX
3142+
#if SANITIZER_LINUX && !SANITIZER_ANDROID
31393143
TSAN_INTERCEPT(clone);
31403144
#endif
31413145
#if !SANITIZER_ANDROID

compiler-rt/lib/tsan/rtl/tsan_platform_linux.cpp

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -486,8 +486,20 @@ int ExtractRecvmsgFDs(void *msgp, int *fds, int nfd) {
486486

487487
// Reverse operation of libc stack pointer mangling
488488
static uptr UnmangleLongJmpSp(uptr mangled_sp) {
489-
#if defined(__x86_64__)
490-
# if SANITIZER_LINUX
489+
# if SANITIZER_ANDROID
490+
if (longjmp_xor_key == 0) {
491+
// bionic libc initialization process: __libc_init_globals ->
492+
// __libc_init_vdso (calls strcmp) -> __libc_init_setjmp_cookie. strcmp is
493+
// intercepted by TSan, so during TSan initialization the setjmp_cookie
494+
// remains uninitialized. On Android, longjmp_xor_key must be set on first
495+
// use.
496+
InitializeLongjmpXorKey();
497+
CHECK_NE(longjmp_xor_key, 0);
498+
}
499+
# endif
500+
501+
# if defined(__x86_64__)
502+
# if SANITIZER_LINUX
491503
// Reverse of:
492504
// xor %fs:0x30, %rsi
493505
// rol $0x11, %rsi
@@ -542,21 +554,31 @@ static uptr UnmangleLongJmpSp(uptr mangled_sp) {
542554
# else
543555
# define LONG_JMP_SP_ENV_SLOT 2
544556
# endif
545-
#elif SANITIZER_LINUX
546-
# ifdef __aarch64__
547-
# define LONG_JMP_SP_ENV_SLOT 13
548-
# elif defined(__loongarch__)
549-
# define LONG_JMP_SP_ENV_SLOT 1
550-
# elif defined(__mips64)
551-
# define LONG_JMP_SP_ENV_SLOT 1
557+
# elif SANITIZER_ANDROID
558+
# ifdef __aarch64__
559+
# define LONG_JMP_SP_ENV_SLOT 3
560+
# elif SANITIZER_RISCV64
561+
# define LONG_JMP_SP_ENV_SLOT 3
562+
# elif defined(__x86_64__)
563+
# define LONG_JMP_SP_ENV_SLOT 6
564+
# else
565+
# error unsupported
566+
# endif
567+
# elif SANITIZER_LINUX
568+
# ifdef __aarch64__
569+
# define LONG_JMP_SP_ENV_SLOT 13
570+
# elif defined(__loongarch__)
571+
# define LONG_JMP_SP_ENV_SLOT 1
572+
# elif defined(__mips64)
573+
# define LONG_JMP_SP_ENV_SLOT 1
552574
# elif SANITIZER_RISCV64
553575
# define LONG_JMP_SP_ENV_SLOT 13
554576
# elif defined(__s390x__)
555577
# define LONG_JMP_SP_ENV_SLOT 9
556578
# else
557579
# define LONG_JMP_SP_ENV_SLOT 6
558580
# endif
559-
#endif
581+
# endif
560582

561583
uptr ExtractLongJmpSp(uptr *env) {
562584
uptr mangled_sp = env[LONG_JMP_SP_ENV_SLOT];
@@ -653,7 +675,12 @@ ThreadState *cur_thread() {
653675
}
654676
CHECK_EQ(0, internal_sigprocmask(SIG_SETMASK, &oldset, nullptr));
655677
}
656-
return thr;
678+
679+
// Skia calls mallopt(M_THREAD_DISABLE_MEM_INIT, 1), which sets the least
680+
// significant bit of TLS_SLOT_SANITIZER to 1. Scudo allocator uses this bit
681+
// as a flag to disable memory initialization. This is a workaround to get the
682+
// correct ThreadState pointer.
683+
reinterpret_cast<ThreadState*>(addr & ~1ULL);
657684
}
658685

659686
void set_cur_thread(ThreadState *thr) {

compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,14 @@ void ThreadStart(ThreadState *thr, Tid tid, ThreadID os_id,
206206
}
207207
#endif
208208

209-
#if !SANITIZER_GO
209+
#if !SANITIZER_GO && !SANITIZER_ANDROID
210210
// Don't imitate stack/TLS writes for the main thread,
211211
// because its initialization is synchronized with all
212212
// subsequent threads anyway.
213+
// Because thr is created by MmapOrDie, the thr object
214+
// is not in tls, the pointer to the thr object is in
215+
// TLS_SLOT_SANITIZER slot. So skip this check on
216+
// Android platform.
213217
if (tid != kMainTid) {
214218
if (stk_addr && stk_size) {
215219
const uptr pc = StackTrace::GetNextInstructionPc(

0 commit comments

Comments
 (0)