25a7c3fd57
This test was flaky because there was no synchronization between the end of the test function scope and the jumper threads, resulting in a racy use-after-scope when accessing the thread array. Fix it by joining the threads before leaving the test function scope. Replace the thread array access, which can race with the store to the thread array in the main thread, with an access to a pre-initialized variable, which acts as a kind of enforcement that the threads are done before leaving the test. Bug: 227390656 Change-Id: Icfbb0d7cae66c12e5ce31072c34529e3c5fdf563
339 lines
9.2 KiB
C++
339 lines
9.2 KiB
C++
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <setjmp.h>
|
|
#include <stdlib.h>
|
|
#include <sys/syscall.h>
|
|
#include <unistd.h>
|
|
|
|
#include <android-base/silent_death_test.h>
|
|
#include <android-base/test_utils.h>
|
|
|
|
#include "SignalUtils.h"
|
|
|
|
using setjmp_DeathTest = SilentDeathTest;
|
|
|
|
TEST(setjmp, setjmp_smoke) {
|
|
int value;
|
|
jmp_buf jb;
|
|
if ((value = setjmp(jb)) == 0) {
|
|
longjmp(jb, 123);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
ASSERT_EQ(123, value);
|
|
}
|
|
}
|
|
|
|
TEST(setjmp, _setjmp_smoke) {
|
|
int value;
|
|
jmp_buf jb;
|
|
if ((value = _setjmp(jb)) == 0) {
|
|
_longjmp(jb, 456);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
ASSERT_EQ(456, value);
|
|
}
|
|
}
|
|
|
|
TEST(setjmp, sigsetjmp_0_smoke) {
|
|
int value;
|
|
sigjmp_buf jb;
|
|
if ((value = sigsetjmp(jb, 0)) == 0) {
|
|
siglongjmp(jb, 789);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
ASSERT_EQ(789, value);
|
|
}
|
|
}
|
|
|
|
TEST(setjmp, sigsetjmp_1_smoke) {
|
|
int value;
|
|
sigjmp_buf jb;
|
|
if ((value = sigsetjmp(jb, 0)) == 0) {
|
|
siglongjmp(jb, 0xabc);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
ASSERT_EQ(0xabc, value);
|
|
}
|
|
}
|
|
|
|
// Two distinct signal sets.
|
|
struct SigSets {
|
|
SigSets() : one(MakeSigSet(0)), two(MakeSigSet(1)) {
|
|
}
|
|
|
|
static sigset64_t MakeSigSet(int offset) {
|
|
sigset64_t ss;
|
|
sigemptyset64(&ss);
|
|
sigaddset64(&ss, SIGUSR1 + offset);
|
|
#if defined(__BIONIC__)
|
|
// TIMER_SIGNAL.
|
|
sigaddset64(&ss, __SIGRTMIN);
|
|
#endif
|
|
sigaddset64(&ss, SIGRTMIN + offset);
|
|
return ss;
|
|
}
|
|
|
|
sigset64_t one;
|
|
sigset64_t two;
|
|
};
|
|
|
|
void AssertSigmaskEquals(const sigset64_t& expected) {
|
|
sigset64_t actual;
|
|
sigprocmask64(SIG_SETMASK, nullptr, &actual);
|
|
size_t end = sizeof(expected) * 8;
|
|
for (size_t i = 1; i <= end; ++i) {
|
|
EXPECT_EQ(sigismember64(&expected, i), sigismember64(&actual, i)) << i;
|
|
}
|
|
}
|
|
|
|
TEST(setjmp, _setjmp_signal_mask) {
|
|
SignalMaskRestorer smr;
|
|
|
|
// _setjmp/_longjmp do not save/restore the signal mask.
|
|
SigSets ss;
|
|
sigprocmask64(SIG_SETMASK, &ss.one, nullptr);
|
|
jmp_buf jb;
|
|
if (_setjmp(jb) == 0) {
|
|
sigprocmask64(SIG_SETMASK, &ss.two, nullptr);
|
|
_longjmp(jb, 1);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
AssertSigmaskEquals(ss.two);
|
|
}
|
|
}
|
|
|
|
TEST(setjmp, setjmp_signal_mask) {
|
|
SignalMaskRestorer smr;
|
|
|
|
// setjmp/longjmp do save/restore the signal mask on bionic, but not on glibc.
|
|
// This is a BSD versus System V historical accident. POSIX leaves the
|
|
// behavior unspecified, so any code that cares needs to use sigsetjmp.
|
|
SigSets ss;
|
|
sigprocmask64(SIG_SETMASK, &ss.one, nullptr);
|
|
jmp_buf jb;
|
|
if (setjmp(jb) == 0) {
|
|
sigprocmask64(SIG_SETMASK, &ss.two, nullptr);
|
|
longjmp(jb, 1);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
#if defined(__BIONIC__)
|
|
// bionic behaves like BSD and does save/restore the signal mask.
|
|
AssertSigmaskEquals(ss.one);
|
|
#else
|
|
// glibc behaves like System V and doesn't save/restore the signal mask.
|
|
AssertSigmaskEquals(ss.two);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
TEST(setjmp, sigsetjmp_0_signal_mask) {
|
|
SignalMaskRestorer smr;
|
|
|
|
// sigsetjmp(0)/siglongjmp do not save/restore the signal mask.
|
|
SigSets ss;
|
|
sigprocmask64(SIG_SETMASK, &ss.one, nullptr);
|
|
sigjmp_buf sjb;
|
|
if (sigsetjmp(sjb, 0) == 0) {
|
|
sigprocmask64(SIG_SETMASK, &ss.two, nullptr);
|
|
siglongjmp(sjb, 1);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
AssertSigmaskEquals(ss.two);
|
|
}
|
|
}
|
|
|
|
TEST(setjmp, sigsetjmp_1_signal_mask) {
|
|
SignalMaskRestorer smr;
|
|
|
|
// sigsetjmp(1)/siglongjmp does save/restore the signal mask.
|
|
SigSets ss;
|
|
sigprocmask64(SIG_SETMASK, &ss.one, nullptr);
|
|
sigjmp_buf sjb;
|
|
if (sigsetjmp(sjb, 1) == 0) {
|
|
sigprocmask64(SIG_SETMASK, &ss.two, nullptr);
|
|
siglongjmp(sjb, 1);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
AssertSigmaskEquals(ss.one);
|
|
}
|
|
}
|
|
|
|
#if defined(__aarch64__)
|
|
#define SET_FREG(n, v) asm volatile("fmov d"#n ", "#v : : : "d"#n)
|
|
#define CLEAR_FREG(n) asm volatile("fmov d"#n ", xzr" : : : "d"#n)
|
|
#define SET_FREGS \
|
|
SET_FREG(8, 8.0); SET_FREG(9, 9.0); SET_FREG(10, 10.0); SET_FREG(11, 11.0); \
|
|
SET_FREG(12, 12.0); SET_FREG(13, 13.0); SET_FREG(14, 14.0); SET_FREG(15, 15.0);
|
|
#define CLEAR_FREGS \
|
|
CLEAR_FREG(8); CLEAR_FREG(9); CLEAR_FREG(10); CLEAR_FREG(11); \
|
|
CLEAR_FREG(12); CLEAR_FREG(13); CLEAR_FREG(14); CLEAR_FREG(15);
|
|
#define GET_FREG(n) ({ double _r; asm volatile("fmov %0, d"#n : "=r"(_r) : :); _r; })
|
|
#define CHECK_FREGS \
|
|
EXPECT_EQ(8.0, GET_FREG(8)); EXPECT_EQ(9.0, GET_FREG(9)); \
|
|
EXPECT_EQ(10.0, GET_FREG(10)); EXPECT_EQ(11.0, GET_FREG(11)); \
|
|
EXPECT_EQ(12.0, GET_FREG(12)); EXPECT_EQ(13.0, GET_FREG(13)); \
|
|
EXPECT_EQ(14.0, GET_FREG(14)); EXPECT_EQ(15.0, GET_FREG(15));
|
|
#elif defined(__arm__)
|
|
#define SET_FREG(n, v) \
|
|
({ const double _v{v}; asm volatile("fcpyd d"#n ", %P0" : : "w"(_v) : "d"#n); })
|
|
#define SET_FREGS \
|
|
SET_FREG(8, 8); SET_FREG(9, 9); SET_FREG(10, 10); SET_FREG(11, 11); \
|
|
SET_FREG(12, 12); SET_FREG(13, 13); SET_FREG(14, 14); SET_FREG(15, 15);
|
|
#define CLEAR_FREGS \
|
|
SET_FREG(8, 0); SET_FREG(9, 0); SET_FREG(10, 0); SET_FREG(11, 0); \
|
|
SET_FREG(12, 0); SET_FREG(13, 0); SET_FREG(14, 0); SET_FREG(15, 0);
|
|
#define GET_FREG(n) ({ double _r; asm volatile("fcpyd %P0, d"#n : "=w"(_r) : :); _r;})
|
|
#define CHECK_FREGS \
|
|
EXPECT_EQ(8.0, GET_FREG(8)); EXPECT_EQ(9.0, GET_FREG(9)); \
|
|
EXPECT_EQ(10.0, GET_FREG(10)); EXPECT_EQ(11.0, GET_FREG(11)); \
|
|
EXPECT_EQ(12.0, GET_FREG(12)); EXPECT_EQ(13.0, GET_FREG(13)); \
|
|
EXPECT_EQ(14.0, GET_FREG(14)); EXPECT_EQ(15.0, GET_FREG(15));
|
|
#else
|
|
/* The other architectures don't save/restore fp registers. */
|
|
#define SET_FREGS
|
|
#define CLEAR_FREGS
|
|
#define CHECK_FREGS
|
|
#endif
|
|
|
|
TEST(setjmp, setjmp_fp_registers) {
|
|
int value;
|
|
jmp_buf jb;
|
|
SET_FREGS;
|
|
if ((value = setjmp(jb)) == 0) {
|
|
CLEAR_FREGS;
|
|
longjmp(jb, 123);
|
|
FAIL(); // Unreachable.
|
|
} else {
|
|
ASSERT_EQ(123, value);
|
|
CHECK_FREGS;
|
|
}
|
|
}
|
|
|
|
#if defined(__arm__)
|
|
#define JB_SIGFLAG_OFFSET 0
|
|
#elif defined(__aarch64__)
|
|
#define JB_SIGFLAG_OFFSET 0
|
|
#elif defined(__i386__)
|
|
#define JB_SIGFLAG_OFFSET 8
|
|
#elif defined(__riscv)
|
|
#define JB_SIGFLAG_OFFSET 0
|
|
#elif defined(__x86_64)
|
|
#define JB_SIGFLAG_OFFSET 8
|
|
#endif
|
|
|
|
TEST_F(setjmp_DeathTest, setjmp_cookie) {
|
|
jmp_buf jb;
|
|
int value = setjmp(jb);
|
|
ASSERT_EQ(0, value);
|
|
|
|
long* sigflag = reinterpret_cast<long*>(jb) + JB_SIGFLAG_OFFSET;
|
|
|
|
// Make sure there's actually a cookie.
|
|
EXPECT_NE(0, *sigflag & ~1);
|
|
|
|
// Wipe it out
|
|
*sigflag &= 1;
|
|
EXPECT_DEATH(longjmp(jb, 0), "");
|
|
}
|
|
|
|
TEST_F(setjmp_DeathTest, setjmp_cookie_checksum) {
|
|
jmp_buf jb;
|
|
int value = setjmp(jb);
|
|
|
|
if (value == 0) {
|
|
// Flip a bit.
|
|
reinterpret_cast<long*>(jb)[1] ^= 1;
|
|
|
|
EXPECT_DEATH(longjmp(jb, 1), "checksum mismatch");
|
|
} else {
|
|
fprintf(stderr, "setjmp_cookie_checksum: longjmp succeeded?");
|
|
}
|
|
}
|
|
|
|
__attribute__((noinline)) void call_longjmp(jmp_buf buf) {
|
|
longjmp(buf, 123);
|
|
}
|
|
|
|
TEST(setjmp, setjmp_stack) {
|
|
jmp_buf buf;
|
|
int value = setjmp(buf);
|
|
if (value == 0) call_longjmp(buf);
|
|
EXPECT_EQ(123, value);
|
|
}
|
|
|
|
TEST(setjmp, bug_152210274) {
|
|
// Ensure that we never have a mangled value in the stack pointer.
|
|
#if defined(__BIONIC__)
|
|
struct sigaction sa = {.sa_flags = SA_SIGINFO, .sa_sigaction = [](int, siginfo_t*, void*) {}};
|
|
ASSERT_EQ(0, sigaction(SIGPROF, &sa, 0));
|
|
|
|
constexpr size_t kNumThreads = 20;
|
|
|
|
// Start a bunch of threads calling setjmp/longjmp.
|
|
auto jumper = [](void* arg) -> void* {
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigaddset(&set, SIGPROF);
|
|
pthread_sigmask(SIG_UNBLOCK, &set, nullptr);
|
|
|
|
jmp_buf buf;
|
|
for (size_t count = 0; count < 100000; ++count) {
|
|
if (setjmp(buf) != 0) {
|
|
perror("setjmp");
|
|
abort();
|
|
}
|
|
// This will never be true, but the compiler doesn't know that, so the
|
|
// setjmp won't be removed by DCE. With HWASan/MTE this also acts as a
|
|
// kind of enforcement that the threads are done before leaving the test.
|
|
if (*static_cast<size_t*>(arg) != 123) longjmp(buf, 1);
|
|
}
|
|
return nullptr;
|
|
};
|
|
pthread_t threads[kNumThreads];
|
|
pid_t tids[kNumThreads] = {};
|
|
size_t var = 123;
|
|
for (size_t i = 0; i < kNumThreads; ++i) {
|
|
ASSERT_EQ(0, pthread_create(&threads[i], nullptr, jumper, &var));
|
|
tids[i] = pthread_gettid_np(threads[i]);
|
|
}
|
|
|
|
// Start the interrupter thread.
|
|
auto interrupter = [](void* arg) -> void* {
|
|
pid_t* tids = static_cast<pid_t*>(arg);
|
|
for (size_t count = 0; count < 1000; ++count) {
|
|
for (size_t i = 0; i < kNumThreads; i++) {
|
|
if (tgkill(getpid(), tids[i], SIGPROF) == -1 && errno != ESRCH) {
|
|
perror("tgkill failed");
|
|
abort();
|
|
}
|
|
}
|
|
usleep(100);
|
|
}
|
|
return nullptr;
|
|
};
|
|
pthread_t t;
|
|
ASSERT_EQ(0, pthread_create(&t, nullptr, interrupter, tids));
|
|
pthread_join(t, nullptr);
|
|
for (size_t i = 0; i < kNumThreads; i++) {
|
|
pthread_join(threads[i], nullptr);
|
|
}
|
|
#else
|
|
GTEST_SKIP() << "tests uses functions not in glibc";
|
|
#endif
|
|
}
|