/* RageThreads helpers for threads in Linux, which are based on PIDs and TIDs. */ #include "global.h" #include "LinuxThreadHelpers.h" #include "RageUtil.h" #include "RageThreads.h" #include "Backtrace.h" #include "archutils/Unix/RunningUnderValgrind.h" #if defined(LINUX) #include #include #include #include #include #include #include #include #include #define _LINUX_PTRACE_H // hack to prevent broken linux/ptrace.h from conflicting with sys/ptrace.h #include /* In Linux, we might be using PID-based or TID-based threads. With PID-based * threads, getpid() returns a unique value for each thread; each thread is a * separate process. Newer kernels using NPTL return the same PID for all * threads; these systems support a gettid() call to get a unique TID for each * thread. */ static bool g_bUsingNPTL = false; #define gettid() syscall(SYS_gettid) #ifndef _CS_GNU_LIBPTHREAD_VERSION #define _CS_GNU_LIBPTHREAD_VERSION 3 #endif RString ThreadsVersion() { char buf[1024] = "(error)"; int ret = confstr( _CS_GNU_LIBPTHREAD_VERSION, buf, sizeof(buf) ); if( ret == -1 ) return "(unknown)"; return buf; } /* Crash-conditions-safe: */ bool UsingNPTL() { char buf[1024] = ""; int ret = confstr( _CS_GNU_LIBPTHREAD_VERSION, buf, sizeof(buf) ); if( ret == -1 ) return false; return !strncmp( buf, "NPTL", 4 ); } /* Crash-conditions-safe: */ void InitializePidThreadHelpers() { static bool bInitialized = false; if( bInitialized ) return; bInitialized = true; g_bUsingNPTL = UsingNPTL(); } /* waitpid(); ThreadID can be a PID or (in NPTL) a TID; doesn't care if the ID * is a clone() or not. */ static int waittid( int ThreadID, int *status, int options ) { static bool bSupportsWall = true; if( bSupportsWall ) { int ret = waitpid( ThreadID, status, options | __WALL ); if( ret != -1 || errno != EINVAL ) return ret; bSupportsWall = false; } /* XXX: on 2.2, we need to use __WCLONE only if ThreadID isn't the main thread; * perhaps wait and retry without it if errno == ECHILD? */ int ret; ret = waitpid( ThreadID, status, options ); return ret; } /* Attempt to PTRACE_ATTACH to a thread, and wait for the SIGSTOP. */ static int PtraceAttach( int ThreadID ) { int ret; ret = ptrace( PTRACE_ATTACH, ThreadID, NULL, NULL ); if( ret == -1 ) { printf("ptrace failed: %s\n", strerror(errno) ); return -1; } /* Wait for the SIGSTOP from the ptrace call. */ int status; ret = waittid( ThreadID, &status, 0 ); if( ret == -1 ) return -1; // printf( "ret %i, exited %i, signalled %i, sig %i, stopped %i, stopsig %i\n", ret, WIFEXITED(status), // WIFSIGNALED(status), WTERMSIG(status), WIFSTOPPED(status), WSTOPSIG(status)); return 0; } static int PtraceDetach( int ThreadID ) { return ptrace( PTRACE_DETACH, ThreadID, NULL, NULL ); } /* Get this thread's ID (this may be a TID or a PID). */ static uint64_t GetCurrentThreadIdInternal() { /* If we're under Valgrind, neither the PID nor the TID is associated with the * thread. Return the pthread ID. This can't be used to kill threads, etc., * but that only happens under error conditions anyway. If we don't return a * usable, unique ID, then mutexes won't work. */ if( RunningUnderValgrind() ) return (int) pthread_self(); InitializePidThreadHelpers(); // for g_bUsingNPTL /* Don't keep calling gettid() if it's not supported; it'll make valgrind spam us. */ static bool GetTidUnsupported = 0; if( !GetTidUnsupported ) { pid_t ret = gettid(); /* If this fails with ENOSYS, we're on a kernel before gettid, or we're * under valgrind. If we don't have NPTL, then just use getpid(). If * we're on NPTL and don't have gettid(), something's wrong. */ if( ret != -1 ) return ret; ASSERT( !g_bUsingNPTL ); GetTidUnsupported = true; } return getpid(); } uint64_t GetCurrentThreadId() { #if defined(HAVE_TLS) /* This is called each time we lock a mutex, and gettid() is a little slow, so * cache the result if we support TLS. */ if( RageThread::GetSupportsTLS() ) { static thread_local uint64_t cached_tid = 0; static thread_local bool cached = false; if( !cached ) { cached_tid = GetCurrentThreadIdInternal(); cached = true; } return cached_tid; } #endif return GetCurrentThreadIdInternal(); } int SuspendThread( uint64_t ThreadID ) { /* * Linux: We can't simply kill(SIGSTOP) (or tkill), since that will stop all processes * (grr). We can ptrace(PTRACE_ATTACH) the process to stop it, and PTRACE_DETACH * to restart it. */ return PtraceAttach( int(ThreadID) ); // kill( ThreadID, SIGSTOP ); } int ResumeThread( uint64_t ThreadID ) { return PtraceDetach( int(ThreadID) ); // kill( ThreadID, SIGSTOP ); } /* Get a BacktraceContext for a thread. ThreadID must not be the current thread. * * tid() is a PID (from getpid) or a TID (from gettid). Note that this may have kernel compatibility * problems, because NPTL is new and its interactions with ptrace() aren't well-defined. * If we're on a non-NPTL system, tid is a regular PID. * * This call leaves the given thread suspended, so the returned context doesn't become invalid. * ResumeThread() can be used to resume a thread after this call. */ #if defined(CRASH_HANDLER) bool GetThreadBacktraceContext( uint64_t ThreadID, BacktraceContext *ctx ) { /* Can't GetThreadBacktraceContext the current thread. */ ASSERT( ThreadID != GetCurrentThreadId() ); /* Attach to the thread. This may fail with EPERM. This can happen for at least * two common reasons: the process might be in a debugger already, or *we* might * already have attached to it via SuspendThread. * * If it's in a debugger, we won't be able to ptrace(PTRACE_GETREGS). If * it's us that attached, we will. */ if( PtraceAttach( int(ThreadID) ) == -1 && errno != EPERM ) { CHECKPOINT_M( ssprintf( "%s (pid %i tid %i locking tid %i)", strerror(errno), getpid(), (int)GetCurrentThreadId(), int(ThreadID) ) ); return false; } #if defined(CPU_X86_64) || defined(CPU_X86) user_regs_struct regs; if( ptrace( PTRACE_GETREGS, pid_t(ThreadID), NULL, ®s ) == -1 ) return false; ctx->pid = pid_t(ThreadID); #if defined(CPU_X86_64) ctx->ip = (void *) regs.rip; ctx->bp = (void *) regs.rbp; ctx->sp = (void *) regs.rsp; #else ctx->ip = (void *) regs.eip; ctx->bp = (void *) regs.ebp; ctx->sp = (void *) regs.esp; #endif #elif defined(CPU_PPC) errno = 0; ctx->FramePtr = (const Frame *)ptrace( PTRACE_PEEKUSER, pid_t(ThreadID), (void *)(PT_R1<<2), 0 ); if( errno ) return false; ctx->PC = (void *)ptrace( PTRACE_PEEKUSER, pid_t(ThreadID), (void *)(PT_NIP<<2), 0 ); if( errno ) return false; #else #error GetThreadBacktraceContext: which arch? #endif return true; } #endif #elif defined(UNIX) #include #include RString ThreadsVersion() { return "(unknown)"; } uint64_t GetCurrentThreadId() { return uint64_t( pthread_self() ); } int SuspendThread( uint64_t id ) { return pthread_kill( pthread_t(id), SIGSTOP ); } int ResumeThread( uint64_t id ) { return pthread_kill( pthread_t(id), SIGCONT ); } #endif /* * (c) 2004 Glenn Maynard * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */