2004-03-12 05:15:32 +00:00
|
|
|
/* RageThreads helpers for threads in Linux, which are based on PIDs and TIDs. */
|
|
|
|
|
|
|
|
|
|
#include "global.h"
|
|
|
|
|
#include "LinuxThreadHelpers.h"
|
|
|
|
|
#include "RageUtil.h"
|
|
|
|
|
|
|
|
|
|
#include "Backtrace.h"
|
2004-09-08 04:45:16 +00:00
|
|
|
#include "archutils/Unix/RunningUnderValgrind.h"
|
2004-03-12 05:15:32 +00:00
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <sys/ptrace.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <linux/unistd.h>
|
|
|
|
|
#define _LINUX_PTRACE_H // hack to prevent broken linux/ptrace.h from conflicting with sys/ptrace.h
|
|
|
|
|
#include <linux/user.h>
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
|
|
static _syscall0(pid_t,gettid)
|
|
|
|
|
|
2004-05-25 06:54:37 +00:00
|
|
|
#ifndef _CS_GNU_LIBPTHREAD_VERSION
|
|
|
|
|
#define _CS_GNU_LIBPTHREAD_VERSION 3
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CString ThreadsVersion()
|
2004-03-12 05:15:32 +00:00
|
|
|
{
|
2004-05-25 06:54:37 +00:00
|
|
|
char buf[1024] = "(error)";
|
|
|
|
|
int ret = confstr( _CS_GNU_LIBPTHREAD_VERSION, buf, sizeof(buf) );
|
|
|
|
|
if( ret == -1 )
|
|
|
|
|
return "(unknown)";
|
|
|
|
|
|
|
|
|
|
return buf;
|
2004-03-12 05:15:32 +00:00
|
|
|
}
|
|
|
|
|
|
2004-05-25 06:54:37 +00:00
|
|
|
/* Crash-conditions-safe: */
|
2004-05-27 04:56:09 +00:00
|
|
|
bool UsingNPTL()
|
2004-05-25 06:54:37 +00:00
|
|
|
{
|
|
|
|
|
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: */
|
2004-03-12 05:15:32 +00:00
|
|
|
void InitializePidThreadHelpers()
|
|
|
|
|
{
|
2004-05-25 06:54:37 +00:00
|
|
|
static bool bInitialized = false;
|
2004-03-12 05:15:32 +00:00
|
|
|
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). */
|
2004-09-08 04:45:16 +00:00
|
|
|
uint64_t GetCurrentThreadId()
|
2004-03-12 05:15:32 +00:00
|
|
|
{
|
2004-09-08 04:45:16 +00:00
|
|
|
/* 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();
|
|
|
|
|
|
2004-03-12 05:15:32 +00:00
|
|
|
InitializePidThreadHelpers(); // for g_bUsingNPTL
|
|
|
|
|
|
2004-08-22 01:16:00 +00:00
|
|
|
/* 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();
|
2004-03-12 05:15:32 +00:00
|
|
|
|
2004-08-22 01:16:00 +00:00
|
|
|
/* 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;
|
2004-03-12 05:15:32 +00:00
|
|
|
|
2004-08-22 01:16:00 +00:00
|
|
|
ASSERT( !g_bUsingNPTL );
|
|
|
|
|
GetTidUnsupported = true;
|
|
|
|
|
}
|
2004-03-12 05:15:32 +00:00
|
|
|
|
|
|
|
|
return getpid();
|
|
|
|
|
}
|
|
|
|
|
|
2004-09-07 12:59:08 +00:00
|
|
|
int SuspendThread( uint64_t ThreadID )
|
2004-03-12 05:15:32 +00:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2004-09-07 12:59:08 +00:00
|
|
|
return PtraceAttach( int(ThreadID) );
|
2004-03-12 05:15:32 +00:00
|
|
|
// kill( ThreadID, SIGSTOP );
|
|
|
|
|
}
|
|
|
|
|
|
2004-09-07 12:59:08 +00:00
|
|
|
int ResumeThread( uint64_t ThreadID )
|
2004-03-12 05:15:32 +00:00
|
|
|
{
|
2004-09-07 12:59:08 +00:00
|
|
|
return PtraceDetach( int(ThreadID) );
|
2004-03-12 05:15:32 +00:00
|
|
|
// kill( ThreadID, SIGSTOP );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2004-03-19 09:25:45 +00:00
|
|
|
/* Get a BacktraceContext for a thread. ThreadID must not be the current thread.
|
2004-03-12 05:15:32 +00:00
|
|
|
*
|
|
|
|
|
* 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.
|
2004-03-19 20:39:15 +00:00
|
|
|
* 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. */
|
2004-06-16 03:01:54 +00:00
|
|
|
#if defined(CRASH_HANDLER)
|
2004-09-07 12:59:08 +00:00
|
|
|
bool GetThreadBacktraceContext( uint64_t ThreadID, BacktraceContext *ctx )
|
2004-03-12 05:15:32 +00:00
|
|
|
{
|
2004-03-19 09:25:45 +00:00
|
|
|
/* Can't GetThreadBacktraceContext the current thread. */
|
2004-03-12 05:15:32 +00:00
|
|
|
ASSERT( ThreadID != GetCurrentThreadId() );
|
|
|
|
|
|
2004-03-19 20:39:15 +00:00
|
|
|
/* 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
|
2004-03-12 05:15:32 +00:00
|
|
|
* already have attached to it via SuspendThread.
|
|
|
|
|
*
|
|
|
|
|
* If it's in a debugger, we won't be able to ptrace(PTRACE_GETREGS). If
|
2004-03-19 20:39:15 +00:00
|
|
|
* it's us that attached, we will. */
|
2004-09-07 12:59:08 +00:00
|
|
|
if( PtraceAttach( int(ThreadID) ) == -1 )
|
2004-03-12 05:15:32 +00:00
|
|
|
{
|
2004-03-21 04:27:21 +00:00
|
|
|
if( errno != EPERM )
|
|
|
|
|
{
|
2004-09-07 12:59:08 +00:00
|
|
|
CHECKPOINT_M( ssprintf( "%s (pid %i tid %i locking tid %i)",
|
2004-09-08 04:45:16 +00:00
|
|
|
strerror(errno), getpid(), (int)GetCurrentThreadId(), int(ThreadID) ) );
|
2004-03-21 04:27:21 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2004-03-12 05:15:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user_regs_struct regs;
|
2004-09-07 12:59:08 +00:00
|
|
|
if( ptrace( PTRACE_GETREGS, pid_t(ThreadID), NULL, ®s ) == -1 )
|
2004-03-19 20:39:15 +00:00
|
|
|
return false;
|
2004-03-12 05:15:32 +00:00
|
|
|
|
2004-09-07 12:59:08 +00:00
|
|
|
ctx->pid = pid_t(ThreadID);
|
2004-06-15 23:38:11 +00:00
|
|
|
#if defined(CPU_X86_64)
|
|
|
|
|
ctx->eip = (void *) regs.rip;
|
|
|
|
|
ctx->ebp = (void *) regs.rbp;
|
|
|
|
|
ctx->esp = (void *) regs.rsp;
|
2004-06-16 02:58:58 +00:00
|
|
|
#elif defined(CPU_X86)
|
2004-03-19 21:37:31 +00:00
|
|
|
ctx->eip = (void *) regs.eip;
|
|
|
|
|
ctx->ebp = (void *) regs.ebp;
|
2004-03-21 03:36:54 +00:00
|
|
|
ctx->esp = (void *) regs.esp;
|
2004-06-16 02:58:58 +00:00
|
|
|
#else
|
|
|
|
|
#error GetThreadBacktraceContext: which arch?
|
2004-06-15 23:38:11 +00:00
|
|
|
#endif
|
2004-03-12 05:15:32 +00:00
|
|
|
|
2004-03-19 20:39:15 +00:00
|
|
|
return true;
|
2004-03-12 05:15:32 +00:00
|
|
|
}
|
2004-06-16 03:01:54 +00:00
|
|
|
#endif
|
2004-05-15 08:23:53 +00:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* (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.
|
|
|
|
|
*/
|