mirror of
https://github.com/gperftools/gperftools
synced 2025-01-03 05:02:04 +00:00
[tcmalloc_unittest] don't disable threaded test for fork torturing
We now detect "SIGTRAP is blocked" case and handle it properly. This enables us to run pthread_create. So there is no more need to disable ManyThreads test. Note, we're not propagating single-stepping mode into child threads, so we're still only fork-testing main thread, but that should be plenty good for now. I.e. merely not crashing on pthread_create anymore when fork torturing is enabled.
This commit is contained in:
parent
2e6061d07a
commit
6f7f81b699
@ -82,11 +82,11 @@
|
||||
|
||||
#if __linux__ && __x86_64__
|
||||
// for fork testing
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <sched.h>
|
||||
#include <semaphore.h>
|
||||
#include <signal.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
@ -601,8 +601,6 @@ class TesterThread {
|
||||
};
|
||||
|
||||
TEST(TCMallocTest, ManyThreads) {
|
||||
if (running_fork_testing) return;
|
||||
|
||||
printf("Testing threaded allocation/deallocation (%d threads)\n",
|
||||
FLAGS_numthreads);
|
||||
|
||||
@ -1894,6 +1892,8 @@ std::function<void()> SetupExec(int argc, char** argv) {
|
||||
}
|
||||
|
||||
#ifdef HAVE_FORK_TESTING_SUPPORT
|
||||
namespace fork_torture {
|
||||
|
||||
// Fork torture testing.
|
||||
//
|
||||
// Basic idea is to enable x86 single-stepping mode. And have signal
|
||||
@ -1937,12 +1937,16 @@ void xsem_wait(sem_t* sem) {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uintptr_t kTF = 0x100; // Trace flag in x86 FLAGS register.
|
||||
|
||||
bool try_handle_sigtrap_blocking(uint8_t* at_rip, ucontext_t* uc);
|
||||
|
||||
void step_handler(int signo, siginfo_t* si, void* _uc) {
|
||||
constexpr uintptr_t TF_FLAG = 0x100;
|
||||
ucontext_t* uc = static_cast<ucontext_t*>(_uc);
|
||||
auto at_rip = reinterpret_cast<uint8_t*>(uc->uc_mcontext.gregs[REG_RIP]);
|
||||
|
||||
if (stepping_stop_requested) {
|
||||
uc->uc_mcontext.gregs[REG_EFL] &= ~TF_FLAG;
|
||||
uc->uc_mcontext.gregs[REG_EFL] &= ~kTF;
|
||||
while (in_fork) {
|
||||
(void)*const_cast<volatile bool*>(&in_fork);
|
||||
}
|
||||
@ -1950,6 +1954,10 @@ void step_handler(int signo, siginfo_t* si, void* _uc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try_handle_sigtrap_blocking(at_rip, uc)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_fork) {
|
||||
return;
|
||||
}
|
||||
@ -1957,12 +1965,11 @@ void step_handler(int signo, siginfo_t* si, void* _uc) {
|
||||
// Add TF to flags and request SIGTRAP on every instruction in this
|
||||
// thread. We could do it only once, but it is harmless to do it
|
||||
// always.
|
||||
uc->uc_mcontext.gregs[REG_EFL] |= TF_FLAG;
|
||||
uc->uc_mcontext.gregs[REG_EFL] |= kTF;
|
||||
|
||||
static bool last_was_lock;
|
||||
|
||||
if (!last_was_lock) {
|
||||
auto at_rip = reinterpret_cast<uint8_t*>(uc->uc_mcontext.gregs[REG_RIP]);
|
||||
if (*at_rip == 0xf0) { // lock prefix.
|
||||
last_was_lock = true;
|
||||
}
|
||||
@ -1979,6 +1986,45 @@ void step_handler(int signo, siginfo_t* si, void* _uc) {
|
||||
errno = errno_save;
|
||||
}
|
||||
|
||||
bool try_handle_sigtrap_blocking(uint8_t* at_rip, ucontext_t* uc) {
|
||||
if (at_rip[0] != 0x0f || at_rip[1] != 0x05) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// syscall instruction. Lets check if someone is about to block
|
||||
// SIGTRAP. If so we must turn off single-stepping, because
|
||||
// otherwise blocked SIGTRAP and pending single-stepping will kill
|
||||
// the process.
|
||||
|
||||
auto& regs = uc->uc_mcontext.gregs;
|
||||
if (regs[REG_RAX] != SYS_rt_sigprocmask) {
|
||||
return false;
|
||||
}
|
||||
if (regs[REG_RDI] != SIG_SETMASK && regs[REG_RDI] != SIG_BLOCK) {
|
||||
return false;
|
||||
}
|
||||
sigset_t* newmask = reinterpret_cast<sigset_t*>(regs[REG_RSI]);
|
||||
if (!newmask || !sigismember(newmask, SIGTRAP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// okay, once we detected this case, we drop single-stepping
|
||||
// flag, block SIGTRAP and raise it. So that when SIGTRAP is
|
||||
// eventually unblocked, we'll get back to signal hander and
|
||||
// re-set single-stepping back.
|
||||
regs[REG_EFL] &= ~kTF;
|
||||
raise(SIGTRAP);
|
||||
sigset_t* oldmask = reinterpret_cast<sigset_t*>(regs[REG_RDX]);
|
||||
if (oldmask) {
|
||||
*oldmask = uc->uc_sigmask;
|
||||
regs[REG_RDX] = 0; // handle "get old mask" part, so we can block
|
||||
// our signal
|
||||
}
|
||||
sigaddset(&uc->uc_sigmask, SIGTRAP);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
tcmalloc::Cleanup<std::function<void()>> setup_fork_testing(int* argc, char *** argv) {
|
||||
if (*argc < 2 || (*argv)[1] != std::string("--with-fork-torture")) {
|
||||
printf("Not enabling fork torture\n");
|
||||
@ -2088,6 +2134,9 @@ tcmalloc::Cleanup<std::function<void()>> setup_fork_testing(int* argc, char ***
|
||||
printf("Done with fork torturing!\n");
|
||||
}));
|
||||
}
|
||||
} // namespace fork_torture
|
||||
|
||||
using fork_torture::setup_fork_testing;
|
||||
|
||||
#else // HAVE_FORK_TESTING_SUPPORT
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user