blob: 330bbde3f3bd405adc4d021e21097628af8ed611 [file] [log] [blame]
/*--------------------------------------------------------------------*/
/*--- A user-space pthreads implementation. vg_scheduler.c ---*/
/*--------------------------------------------------------------------*/
/*
This file is part of Valgrind, an x86 protected-mode emulator
designed for debugging and profiling binaries on x86-Unixes.
Copyright (C) 2000-2002 Julian Seward
jseward@acm.org
Julian_Seward@muraroa.demon.co.uk
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307, USA.
The GNU General Public License is contained in the file LICENSE.
*/
#include "vg_include.h"
#include "vg_constants.h"
#include "valgrind.h" /* for VG_USERREQ__MAKE_NOACCESS and
VG_USERREQ__DO_LEAK_CHECK */
/* BORKAGE/ISSUES as of 14 Apr 02
Note! This pthreads implementation is so poor as to not be
suitable for use by anyone at all!
- Currently, when a signal is run, just the ThreadStatus.status fields
are saved in the signal frame, along with the CPU state. Question:
should I also save and restore:
ThreadStatus.joiner
ThreadStatus.waited_on_mid
ThreadStatus.awaken_at
ThreadStatus.retval
Currently unsure, and so am not doing so.
- Signals interrupting read/write and nanosleep: SA_RESTART settings.
Read/write correctly return with EINTR when SA_RESTART isn't
specified and they are interrupted by a signal. nanosleep just
pretends signals don't exist -- should be fixed.
- Read/write syscall starts: don't crap out when the initial
nonblocking read/write returns an error.
- Get rid of restrictions re use of sigaltstack; they are no longer
needed.
*/
/* ---------------------------------------------------------------------
Types and globals for the scheduler.
------------------------------------------------------------------ */
/* type ThreadId is defined in vg_include.h. */
/* struct ThreadState is defined in vg_include.h. */
/* Private globals. A statically allocated array of threads. */
static ThreadState vg_threads[VG_N_THREADS];
/* vg_oursignalhandler() might longjmp(). Here's the jmp_buf. */
jmp_buf VG_(scheduler_jmpbuf);
/* ... and if so, here's the signal which caused it to do so. */
Int VG_(longjmpd_on_signal);
/* Machinery to keep track of which threads are waiting on which
fds. */
typedef
struct {
/* The thread which made the request. */
ThreadId tid;
/* The next two fields describe the request. */
/* File descriptor waited for. -1 means this slot is not in use */
Int fd;
/* The syscall number the fd is used in. */
Int syscall_no;
/* False => still waiting for select to tell us the fd is ready
to go. True => the fd is ready, but the results have not yet
been delivered back to the calling thread. Once the latter
happens, this entire record is marked as no longer in use, by
making the fd field be -1. */
Bool ready;
}
VgWaitedOnFd;
static VgWaitedOnFd vg_waiting_fds[VG_N_WAITING_FDS];
typedef
struct {
/* Is this slot in use, or free? */
Bool in_use;
/* If in_use, is this mutex held by some thread, or not? */
Bool held;
/* if held==True, owner indicates who by. */
ThreadId owner;
}
VgMutex;
static VgMutex vg_mutexes[VG_N_MUTEXES];
/* Forwards */
static void do_nontrivial_clientreq ( ThreadId tid );
/* ---------------------------------------------------------------------
Helper functions for the scheduler.
------------------------------------------------------------------ */
static
void pp_sched_status ( void )
{
Int i;
VG_(printf)("\nsched status:\n");
for (i = 0; i < VG_N_THREADS; i++) {
if (vg_threads[i].status == VgTs_Empty) continue;
VG_(printf)("tid %d: ", i);
switch (vg_threads[i].status) {
case VgTs_Runnable: VG_(printf)("Runnable\n"); break;
case VgTs_WaitFD: VG_(printf)("WaitFD\n"); break;
case VgTs_WaitJoiner: VG_(printf)("WaitJoiner(%d)\n",
vg_threads[i].joiner); break;
case VgTs_WaitJoinee: VG_(printf)("WaitJoinee\n"); break;
case VgTs_Sleeping: VG_(printf)("Sleeping\n"); break;
default: VG_(printf)("???"); break;
}
}
VG_(printf)("\n");
}
static
void add_waiting_fd ( ThreadId tid, Int fd, Int syscall_no )
{
Int i;
vg_assert(fd != -1); /* avoid total chaos */
for (i = 0; i < VG_N_WAITING_FDS; i++)
if (vg_waiting_fds[i].fd == -1)
break;
if (i == VG_N_WAITING_FDS)
VG_(panic)("add_waiting_fd: VG_N_WAITING_FDS is too low");
/*
VG_(printf)("add_waiting_fd: add (tid %d, fd %d) at slot %d\n",
tid, fd, i);
*/
vg_waiting_fds[i].fd = fd;
vg_waiting_fds[i].tid = tid;
vg_waiting_fds[i].ready = False;
vg_waiting_fds[i].syscall_no = syscall_no;
}
static
void print_sched_event ( ThreadId tid, Char* what )
{
VG_(message)(Vg_DebugMsg, "SCHED[%d]: %s", tid, what );
}
static
void print_pthread_event ( ThreadId tid, Char* what )
{
VG_(message)(Vg_DebugMsg, "PTHREAD[%d]: %s", tid, what );
}
static
Char* name_of_sched_event ( UInt event )
{
switch (event) {
case VG_TRC_EBP_JMP_SYSCALL: return "SYSCALL";
case VG_TRC_EBP_JMP_CLIENTREQ: return "CLIENTREQ";
case VG_TRC_INNER_COUNTERZERO: return "COUNTERZERO";
case VG_TRC_INNER_FASTMISS: return "FASTMISS";
case VG_TRC_UNRESUMABLE_SIGNAL: return "FATALSIGNAL";
default: return "??UNKNOWN??";
}
}
/* Create a translation of the client basic block beginning at
orig_addr, and add it to the translation cache & translation table.
This probably doesn't really belong here, but, hey ...
*/
void VG_(create_translation_for) ( Addr orig_addr )
{
Addr trans_addr;
TTEntry tte;
Int orig_size, trans_size;
/* Ensure there is space to hold a translation. */
VG_(maybe_do_lru_pass)();
VG_(translate)( orig_addr, &orig_size, &trans_addr, &trans_size );
/* Copy data at trans_addr into the translation cache.
Returned pointer is to the code, not to the 4-byte
header. */
/* Since the .orig_size and .trans_size fields are
UShort, be paranoid. */
vg_assert(orig_size > 0 && orig_size < 65536);
vg_assert(trans_size > 0 && trans_size < 65536);
tte.orig_size = orig_size;
tte.orig_addr = orig_addr;
tte.trans_size = trans_size;
tte.trans_addr = VG_(copy_to_transcache)
( trans_addr, trans_size );
tte.mru_epoch = VG_(current_epoch);
/* Free the intermediary -- was allocated by VG_(emit_code). */
VG_(jitfree)( (void*)trans_addr );
/* Add to trans tab and set back pointer. */
VG_(add_to_trans_tab) ( &tte );
/* Update stats. */
VG_(this_epoch_in_count) ++;
VG_(this_epoch_in_osize) += orig_size;
VG_(this_epoch_in_tsize) += trans_size;
VG_(overall_in_count) ++;
VG_(overall_in_osize) += orig_size;
VG_(overall_in_tsize) += trans_size;
/* Record translated area for SMC detection. */
VG_(smc_mark_original) ( orig_addr, orig_size );
}
/* Allocate a completely empty ThreadState record. */
static
ThreadId vg_alloc_ThreadState ( void )
{
Int i;
for (i = 0; i < VG_N_THREADS; i++) {
if (vg_threads[i].status == VgTs_Empty)
return i;
}
VG_(printf)("vg_alloc_ThreadState: no free slots available\n");
VG_(printf)("Increase VG_N_THREADS, rebuild and try again.\n");
VG_(panic)("VG_N_THREADS is too low");
/*NOTREACHED*/
}
ThreadState* VG_(get_thread_state) ( ThreadId tid )
{
vg_assert(tid >= 0 && tid < VG_N_THREADS);
vg_assert(vg_threads[tid].status != VgTs_Empty);
return & vg_threads[tid];
}
/* Find an unused VgMutex record. */
static
MutexId vg_alloc_VgMutex ( void )
{
Int i;
for (i = 0; i < VG_N_MUTEXES; i++) {
if (!vg_mutexes[i].in_use)
return i;
}
VG_(printf)("vg_alloc_VgMutex: no free slots available\n");
VG_(printf)("Increase VG_N_MUTEXES, rebuild and try again.\n");
VG_(panic)("VG_N_MUTEXES is too low");
/*NOTREACHED*/
}
/* Copy the saved state of a thread into VG_(baseBlock), ready for it
to be run. */
__inline__
void VG_(load_thread_state) ( ThreadId tid )
{
Int i;
VG_(baseBlock)[VGOFF_(m_eax)] = vg_threads[tid].m_eax;
VG_(baseBlock)[VGOFF_(m_ebx)] = vg_threads[tid].m_ebx;
VG_(baseBlock)[VGOFF_(m_ecx)] = vg_threads[tid].m_ecx;
VG_(baseBlock)[VGOFF_(m_edx)] = vg_threads[tid].m_edx;
VG_(baseBlock)[VGOFF_(m_esi)] = vg_threads[tid].m_esi;
VG_(baseBlock)[VGOFF_(m_edi)] = vg_threads[tid].m_edi;
VG_(baseBlock)[VGOFF_(m_ebp)] = vg_threads[tid].m_ebp;
VG_(baseBlock)[VGOFF_(m_esp)] = vg_threads[tid].m_esp;
VG_(baseBlock)[VGOFF_(m_eflags)] = vg_threads[tid].m_eflags;
VG_(baseBlock)[VGOFF_(m_eip)] = vg_threads[tid].m_eip;
for (i = 0; i < VG_SIZE_OF_FPUSTATE_W; i++)
VG_(baseBlock)[VGOFF_(m_fpustate) + i] = vg_threads[tid].m_fpu[i];
VG_(baseBlock)[VGOFF_(sh_eax)] = vg_threads[tid].sh_eax;
VG_(baseBlock)[VGOFF_(sh_ebx)] = vg_threads[tid].sh_ebx;
VG_(baseBlock)[VGOFF_(sh_ecx)] = vg_threads[tid].sh_ecx;
VG_(baseBlock)[VGOFF_(sh_edx)] = vg_threads[tid].sh_edx;
VG_(baseBlock)[VGOFF_(sh_esi)] = vg_threads[tid].sh_esi;
VG_(baseBlock)[VGOFF_(sh_edi)] = vg_threads[tid].sh_edi;
VG_(baseBlock)[VGOFF_(sh_ebp)] = vg_threads[tid].sh_ebp;
VG_(baseBlock)[VGOFF_(sh_esp)] = vg_threads[tid].sh_esp;
VG_(baseBlock)[VGOFF_(sh_eflags)] = vg_threads[tid].sh_eflags;
}
/* Copy the state of a thread from VG_(baseBlock), presumably after it
has been descheduled. For sanity-check purposes, fill the vacated
VG_(baseBlock) with garbage so as to make the system more likely to
fail quickly if we erroneously continue to poke around inside
VG_(baseBlock) without first doing a load_thread_state().
*/
__inline__
void VG_(save_thread_state) ( ThreadId tid )
{
Int i;
const UInt junk = 0xDEADBEEF;
vg_threads[tid].m_eax = VG_(baseBlock)[VGOFF_(m_eax)];
vg_threads[tid].m_ebx = VG_(baseBlock)[VGOFF_(m_ebx)];
vg_threads[tid].m_ecx = VG_(baseBlock)[VGOFF_(m_ecx)];
vg_threads[tid].m_edx = VG_(baseBlock)[VGOFF_(m_edx)];
vg_threads[tid].m_esi = VG_(baseBlock)[VGOFF_(m_esi)];
vg_threads[tid].m_edi = VG_(baseBlock)[VGOFF_(m_edi)];
vg_threads[tid].m_ebp = VG_(baseBlock)[VGOFF_(m_ebp)];
vg_threads[tid].m_esp = VG_(baseBlock)[VGOFF_(m_esp)];
vg_threads[tid].m_eflags = VG_(baseBlock)[VGOFF_(m_eflags)];
vg_threads[tid].m_eip = VG_(baseBlock)[VGOFF_(m_eip)];
for (i = 0; i < VG_SIZE_OF_FPUSTATE_W; i++)
vg_threads[tid].m_fpu[i] = VG_(baseBlock)[VGOFF_(m_fpustate) + i];
vg_threads[tid].sh_eax = VG_(baseBlock)[VGOFF_(sh_eax)];
vg_threads[tid].sh_ebx = VG_(baseBlock)[VGOFF_(sh_ebx)];
vg_threads[tid].sh_ecx = VG_(baseBlock)[VGOFF_(sh_ecx)];
vg_threads[tid].sh_edx = VG_(baseBlock)[VGOFF_(sh_edx)];
vg_threads[tid].sh_esi = VG_(baseBlock)[VGOFF_(sh_esi)];
vg_threads[tid].sh_edi = VG_(baseBlock)[VGOFF_(sh_edi)];
vg_threads[tid].sh_ebp = VG_(baseBlock)[VGOFF_(sh_ebp)];
vg_threads[tid].sh_esp = VG_(baseBlock)[VGOFF_(sh_esp)];
vg_threads[tid].sh_eflags = VG_(baseBlock)[VGOFF_(sh_eflags)];
/* Fill it up with junk. */
VG_(baseBlock)[VGOFF_(m_eax)] = junk;
VG_(baseBlock)[VGOFF_(m_ebx)] = junk;
VG_(baseBlock)[VGOFF_(m_ecx)] = junk;
VG_(baseBlock)[VGOFF_(m_edx)] = junk;
VG_(baseBlock)[VGOFF_(m_esi)] = junk;
VG_(baseBlock)[VGOFF_(m_edi)] = junk;
VG_(baseBlock)[VGOFF_(m_ebp)] = junk;
VG_(baseBlock)[VGOFF_(m_esp)] = junk;
VG_(baseBlock)[VGOFF_(m_eflags)] = junk;
VG_(baseBlock)[VGOFF_(m_eip)] = junk;
for (i = 0; i < VG_SIZE_OF_FPUSTATE_W; i++)
VG_(baseBlock)[VGOFF_(m_fpustate) + i] = junk;
}
/* Run the thread tid for a while, and return a VG_TRC_* value to the
scheduler indicating what happened. */
static
UInt run_thread_for_a_while ( ThreadId tid )
{
UInt trc = 0;
vg_assert(tid >= 0 && tid < VG_N_THREADS);
vg_assert(vg_threads[tid].status != VgTs_Empty);
vg_assert(VG_(bbs_to_go) > 0);
VG_(load_thread_state) ( tid );
if (__builtin_setjmp(VG_(scheduler_jmpbuf)) == 0) {
/* try this ... */
trc = VG_(run_innerloop)();
/* We get here if the client didn't take a fault. */
} else {
/* We get here if the client took a fault, which caused our
signal handler to longjmp. */
vg_assert(trc == 0);
trc = VG_TRC_UNRESUMABLE_SIGNAL;
}
VG_(save_thread_state) ( tid );
return trc;
}
/* Increment the LRU epoch counter. */
static
void increment_epoch ( void )
{
VG_(current_epoch)++;
if (VG_(clo_verbosity) > 2) {
UInt tt_used, tc_used;
VG_(get_tt_tc_used) ( &tt_used, &tc_used );
VG_(message)(Vg_UserMsg,
"%lu bbs, in: %d (%d -> %d), out %d (%d -> %d), TT %d, TC %d",
VG_(bbs_done),
VG_(this_epoch_in_count),
VG_(this_epoch_in_osize),
VG_(this_epoch_in_tsize),
VG_(this_epoch_out_count),
VG_(this_epoch_out_osize),
VG_(this_epoch_out_tsize),
tt_used, tc_used
);
}
VG_(this_epoch_in_count) = 0;
VG_(this_epoch_in_osize) = 0;
VG_(this_epoch_in_tsize) = 0;
VG_(this_epoch_out_count) = 0;
VG_(this_epoch_out_osize) = 0;
VG_(this_epoch_out_tsize) = 0;
}
/* Initialise the scheduler. Create a single "main" thread ready to
run, with special ThreadId of zero. This is called at startup; the
caller takes care to park the client's state is parked in
VG_(baseBlock).
*/
void VG_(scheduler_init) ( void )
{
Int i;
Addr startup_esp;
ThreadId tid_main;
startup_esp = VG_(baseBlock)[VGOFF_(m_esp)];
if ((startup_esp & VG_STARTUP_STACK_MASK) != VG_STARTUP_STACK_MASK) {
VG_(printf)("%%esp at startup = %p is not near %p; aborting\n",
(void*)startup_esp, (void*)VG_STARTUP_STACK_MASK);
VG_(panic)("unexpected %esp at startup");
}
for (i = 0; i < VG_N_THREADS; i++) {
vg_threads[i].stack_size = 0;
vg_threads[i].stack_base = (Addr)NULL;
}
for (i = 0; i < VG_N_WAITING_FDS; i++)
vg_waiting_fds[i].fd = -1; /* not in use */
for (i = 0; i < VG_N_MUTEXES; i++)
vg_mutexes[i].in_use = False;
/* Assert this is thread zero, which has certain magic
properties. */
tid_main = vg_alloc_ThreadState();
vg_assert(tid_main == 0);
vg_threads[tid_main].status = VgTs_Runnable;
vg_threads[tid_main].joiner = VG_INVALID_THREADID;
vg_threads[tid_main].retval = NULL; /* not important */
/* Copy VG_(baseBlock) state to tid_main's slot. */
VG_(save_thread_state) ( tid_main );
}
/* What if fd isn't a valid fd? */
static
void set_fd_nonblocking ( Int fd )
{
Int res = VG_(fcntl)( fd, VKI_F_GETFL, 0 );
vg_assert(!VG_(is_kerror)(res));
res |= VKI_O_NONBLOCK;
res = VG_(fcntl)( fd, VKI_F_SETFL, res );
vg_assert(!VG_(is_kerror)(res));
}
static
void set_fd_blocking ( Int fd )
{
Int res = VG_(fcntl)( fd, VKI_F_GETFL, 0 );
vg_assert(!VG_(is_kerror)(res));
res &= ~VKI_O_NONBLOCK;
res = VG_(fcntl)( fd, VKI_F_SETFL, res );
vg_assert(!VG_(is_kerror)(res));
}
static
Bool fd_is_blockful ( Int fd )
{
Int res = VG_(fcntl)( fd, VKI_F_GETFL, 0 );
vg_assert(!VG_(is_kerror)(res));
return (res & VKI_O_NONBLOCK) ? False : True;
}
/* Do a purely thread-local request for tid, and put the result in its
%EDX, without changing its scheduling state in any way, nor that of
any other threads. Return True if so.
If the request is non-trivial, return False; a more capable but
slower mechanism will deal with it.
*/
static
Bool maybe_do_trivial_clientreq ( ThreadId tid )
{
# define SIMPLE_RETURN(vvv) \
{ tst->m_edx = (vvv); \
return True; \
}
ThreadState* tst = &vg_threads[tid];
UInt* arg = (UInt*)(tst->m_eax);
UInt req_no = arg[0];
switch (req_no) {
case VG_USERREQ__MALLOC:
SIMPLE_RETURN(
(UInt)VG_(client_malloc) ( tst, arg[1], Vg_AllocMalloc )
);
case VG_USERREQ__BUILTIN_NEW:
SIMPLE_RETURN(
(UInt)VG_(client_malloc) ( tst, arg[1], Vg_AllocNew )
);
case VG_USERREQ__BUILTIN_VEC_NEW:
SIMPLE_RETURN(
(UInt)VG_(client_malloc) ( tst, arg[1], Vg_AllocNewVec )
);
case VG_USERREQ__FREE:
VG_(client_free) ( tst, (void*)arg[1], Vg_AllocMalloc );
SIMPLE_RETURN(0); /* irrelevant */
case VG_USERREQ__BUILTIN_DELETE:
VG_(client_free) ( tst, (void*)arg[1], Vg_AllocNew );
SIMPLE_RETURN(0); /* irrelevant */
case VG_USERREQ__BUILTIN_VEC_DELETE:
VG_(client_free) ( tst, (void*)arg[1], Vg_AllocNewVec );
SIMPLE_RETURN(0); /* irrelevant */
case VG_USERREQ__CALLOC:
SIMPLE_RETURN(
(UInt)VG_(client_calloc) ( tst, arg[1], arg[2] )
);
case VG_USERREQ__REALLOC:
SIMPLE_RETURN(
(UInt)VG_(client_realloc) ( tst, (void*)arg[1], arg[2] )
);
case VG_USERREQ__MEMALIGN:
SIMPLE_RETURN(
(UInt)VG_(client_memalign) ( tst, arg[1], arg[2] )
);
default:
/* Too hard; wimp out. */
return False;
}
# undef SIMPLE_RETURN
}
static
void sched_do_syscall ( ThreadId tid )
{
UInt saved_eax;
UInt res, syscall_no;
UInt fd;
Bool might_block, assumed_nonblocking;
Bool orig_fd_blockness;
Char msg_buf[100];
vg_assert(tid >= 0 && tid < VG_N_THREADS);
vg_assert(vg_threads[tid].status == VgTs_Runnable);
syscall_no = vg_threads[tid].m_eax; /* syscall number */
if (syscall_no == __NR_nanosleep) {
ULong t_now, t_awaken;
struct vki_timespec* req;
req = (struct vki_timespec*)vg_threads[tid].m_ebx; /* arg1 */
t_now = VG_(read_microsecond_timer)();
t_awaken
= t_now
+ (ULong)1000000ULL * (ULong)(req->tv_sec)
+ (ULong)( (UInt)(req->tv_nsec) / 1000 );
vg_threads[tid].status = VgTs_Sleeping;
vg_threads[tid].awaken_at = t_awaken;
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf, "at %lu: nanosleep for %lu",
t_now, t_awaken-t_now);
print_sched_event(tid, msg_buf);
}
/* Force the scheduler to run something else for a while. */
return;
}
switch (syscall_no) {
case __NR_read:
case __NR_write:
assumed_nonblocking
= False;
might_block
= fd_is_blockful(vg_threads[tid].m_ebx /* arg1 */);
break;
default:
might_block = False;
assumed_nonblocking = True;
}
if (assumed_nonblocking) {
/* We think it's non-blocking. Just do it in the normal way. */
VG_(perform_assumed_nonblocking_syscall)(tid);
/* The thread is still runnable. */
return;
}
/* It might block. Take evasive action. */
switch (syscall_no) {
case __NR_read:
case __NR_write:
fd = vg_threads[tid].m_ebx; break;
default:
vg_assert(3+3 == 7);
}
/* Set the fd to nonblocking, and do the syscall, which will return
immediately, in order to lodge a request with the Linux kernel.
We later poll for I/O completion using select(). */
orig_fd_blockness = fd_is_blockful(fd);
set_fd_nonblocking(fd);
vg_assert(!fd_is_blockful(fd));
VG_(check_known_blocking_syscall)(tid, syscall_no, NULL /* PRE */);
/* This trashes the thread's %eax; we have to preserve it. */
saved_eax = vg_threads[tid].m_eax;
KERNEL_DO_SYSCALL(tid,res);
/* Restore original blockfulness of the fd. */
if (orig_fd_blockness)
set_fd_blocking(fd);
else
set_fd_nonblocking(fd);
if (res != -VKI_EWOULDBLOCK) {
/* It didn't block; it went through immediately. So finish off
in the normal way. Don't restore %EAX, since that now
(correctly) holds the result of the call. */
VG_(check_known_blocking_syscall)(tid, syscall_no, &res /* POST */);
/* We're still runnable. */
vg_assert(vg_threads[tid].status == VgTs_Runnable);
} else {
/* It would have blocked. First, restore %EAX to what it was
before our speculative call. */
vg_threads[tid].m_eax = saved_eax;
/* Put this fd in a table of fds on which we are waiting for
completion. The arguments for select() later are constructed
from this table. */
add_waiting_fd(tid, fd, saved_eax /* which holds the syscall # */);
/* Deschedule thread until an I/O completion happens. */
vg_threads[tid].status = VgTs_WaitFD;
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf,"block until I/O ready on fd %d", fd);
print_sched_event(tid, msg_buf);
}
}
}
/* Find out which of the fds in vg_waiting_fds are now ready to go, by
making enquiries with select(), and mark them as ready. We have to
wait for the requesting threads to fall into the the WaitFD state
before we can actually finally deliver the results, so this
procedure doesn't do that; complete_blocked_syscalls() does it.
It might seem odd that a thread which has done a blocking syscall
is not in WaitFD state; the way this can happen is if it initially
becomes WaitFD, but then a signal is delivered to it, so it becomes
Runnable for a while. In this case we have to wait for the
sighandler to return, whereupon the WaitFD state is resumed, and
only at that point can the I/O result be delivered to it. However,
this point may be long after the fd is actually ready.
So, poll_for_ready_fds() merely detects fds which are ready.
complete_blocked_syscalls() does the second half of the trick,
possibly much later: it delivers the results from ready fds to
threads in WaitFD state.
*/
static
void poll_for_ready_fds ( void )
{
vki_ksigset_t saved_procmask;
vki_fd_set readfds;
vki_fd_set writefds;
vki_fd_set exceptfds;
struct vki_timeval timeout;
Int fd, fd_max, i, n_ready, syscall_no, n_ok;
ThreadId tid;
Bool rd_ok, wr_ok, ex_ok;
Char msg_buf[100];
struct vki_timespec* rem;
ULong t_now;
/* Awaken any sleeping threads whose sleep has expired. */
t_now = VG_(read_microsecond_timer)();
for (tid = 0; tid < VG_N_THREADS; tid++) {
if (vg_threads[tid].status != VgTs_Sleeping)
continue;
if (t_now >= vg_threads[tid].awaken_at) {
/* Resume this thread. Set to zero the remaining-time (second)
arg of nanosleep, since it's used up all its time. */
vg_assert(vg_threads[tid].m_eax == __NR_nanosleep);
rem = (struct vki_timespec *)vg_threads[tid].m_ecx; /* arg2 */
if (rem != NULL) {
rem->tv_sec = 0;
rem->tv_nsec = 0;
}
/* Make the syscall return 0 (success). */
vg_threads[tid].m_eax = 0;
/* Reschedule this thread. */
vg_threads[tid].status = VgTs_Runnable;
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf, "at %lu: nanosleep done",
t_now);
print_sched_event(tid, msg_buf);
}
}
}
/* And look for threads waiting on file descriptors which are now
ready for I/O.*/
timeout.tv_sec = 0;
timeout.tv_usec = 0;
VKI_FD_ZERO(&readfds);
VKI_FD_ZERO(&writefds);
VKI_FD_ZERO(&exceptfds);
fd_max = -1;
for (i = 0; i < VG_N_WAITING_FDS; i++) {
if (vg_waiting_fds[i].fd == -1 /* not in use */)
continue;
if (vg_waiting_fds[i].ready /* already ready? */)
continue;
fd = vg_waiting_fds[i].fd;
/* VG_(printf)("adding QUERY for fd %d\n", fd); */
vg_assert(fd >= 0);
if (fd > fd_max)
fd_max = fd;
tid = vg_waiting_fds[i].tid;
vg_assert(tid >= 0 && tid < VG_N_THREADS);
syscall_no = vg_waiting_fds[i].syscall_no;
switch (syscall_no) {
case __NR_read:
VKI_FD_SET(fd, &readfds); break;
case __NR_write:
VKI_FD_SET(fd, &writefds); break;
default:
VG_(panic)("poll_for_ready_fds: unexpected syscall");
/*NOTREACHED*/
break;
}
}
/* Short cut: if no fds are waiting, give up now. */
if (fd_max == -1)
return;
/* BLOCK ALL SIGNALS. We don't want the complication of select()
getting interrupted. */
VG_(block_all_host_signals)( &saved_procmask );
n_ready = VG_(select)
( fd_max+1, &readfds, &writefds, &exceptfds, &timeout);
if (VG_(is_kerror)(n_ready)) {
VG_(printf)("poll_for_ready_fds: select returned %d\n", n_ready);
VG_(panic)("poll_for_ready_fds: select failed?!");
/*NOTREACHED*/
}
/* UNBLOCK ALL SIGNALS */
VG_(restore_host_signals)( &saved_procmask );
/* VG_(printf)("poll_for_io_completions: %d fs ready\n", n_ready); */
if (n_ready == 0)
return;
/* Inspect all the fds we know about, and handle any completions that
have happened. */
/*
VG_(printf)("\n\n");
for (fd = 0; fd < 100; fd++)
if (VKI_FD_ISSET(fd, &writefds) || VKI_FD_ISSET(fd, &readfds)) {
VG_(printf)("X"); } else { VG_(printf)("."); };
VG_(printf)("\n\nfd_max = %d\n", fd_max);
*/
for (fd = 0; fd <= fd_max; fd++) {
rd_ok = VKI_FD_ISSET(fd, &readfds);
wr_ok = VKI_FD_ISSET(fd, &writefds);
ex_ok = VKI_FD_ISSET(fd, &exceptfds);
n_ok = (rd_ok ? 1 : 0) + (wr_ok ? 1 : 0) + (ex_ok ? 1 : 0);
if (n_ok == 0)
continue;
if (n_ok > 1) {
VG_(printf)("offending fd = %d\n", fd);
VG_(panic)("poll_for_ready_fds: multiple events on fd");
}
/* An I/O event completed for fd. Find the thread which
requested this. */
for (i = 0; i < VG_N_WAITING_FDS; i++) {
if (vg_waiting_fds[i].fd == -1 /* not in use */)
continue;
if (vg_waiting_fds[i].fd == fd)
break;
}
/* And a bit more paranoia ... */
vg_assert(i >= 0 && i < VG_N_WAITING_FDS);
/* Mark the fd as ready. */
vg_assert(! vg_waiting_fds[i].ready);
vg_waiting_fds[i].ready = True;
}
}
/* See comment attached to poll_for_ready_fds() for explaination. */
static
void complete_blocked_syscalls ( void )
{
Int fd, i, res, syscall_no;
ThreadId tid;
Char msg_buf[100];
/* Inspect all the outstanding fds we know about. */
for (i = 0; i < VG_N_WAITING_FDS; i++) {
if (vg_waiting_fds[i].fd == -1 /* not in use */)
continue;
if (! vg_waiting_fds[i].ready)
continue;
fd = vg_waiting_fds[i].fd;
tid = vg_waiting_fds[i].tid;
vg_assert(tid >= 0 && tid < VG_N_THREADS);
/* The thread actually has to be waiting for the I/O event it
requested before we can deliver the result! */
if (vg_threads[tid].status != VgTs_WaitFD)
continue;
/* Ok, actually do it! We can safely use %EAX as the syscall
number, because the speculative call made by
sched_do_syscall() doesn't change %EAX in the case where the
call would have blocked. */
syscall_no = vg_waiting_fds[i].syscall_no;
vg_assert(syscall_no == vg_threads[tid].m_eax);
KERNEL_DO_SYSCALL(tid,res);
VG_(check_known_blocking_syscall)(tid, syscall_no, &res /* POST */);
/* Reschedule. */
vg_threads[tid].status = VgTs_Runnable;
/* Mark slot as no longer in use. */
vg_waiting_fds[i].fd = -1;
/* pp_sched_status(); */
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf,"resume due to I/O completion on fd %d", fd);
print_sched_event(tid, msg_buf);
}
}
}
static
void nanosleep_for_a_while ( void )
{
Int res;
struct vki_timespec req;
struct vki_timespec rem;
req.tv_sec = 0;
req.tv_nsec = 20 * 1000 * 1000;
res = VG_(nanosleep)( &req, &rem );
/* VG_(printf)("after ns, unused = %d\n", rem.tv_nsec ); */
vg_assert(res == 0);
}
/* ---------------------------------------------------------------------
The scheduler proper.
------------------------------------------------------------------ */
/* Run user-space threads until either
* Deadlock occurs
* One thread asks to shutdown Valgrind
* The specified number of basic blocks has gone by.
*/
VgSchedReturnCode VG_(scheduler) ( void )
{
ThreadId tid, tid_next;
UInt trc;
UInt dispatch_ctr_SAVED;
Int request_code, done_this_time, n_in_fdwait_or_sleep;
Char msg_buf[100];
Addr trans_addr;
/* For the LRU structures, records when the epoch began. */
ULong lru_epoch_started_at = 0;
/* Start with the root thread. tid in general indicates the
currently runnable/just-finished-running thread. */
tid = 0;
/* This is the top level scheduler loop. It falls into three
phases. */
while (True) {
/* ======================= Phase 1 of 3 =======================
Handle I/O completions and signals. This may change the
status of various threads. Then select a new thread to run,
or declare deadlock, or sleep if there are no runnable
threads but some are blocked on I/O. */
/* Age the LRU structures if an epoch has been completed. */
if (VG_(bbs_done) - lru_epoch_started_at >= VG_BBS_PER_EPOCH) {
lru_epoch_started_at = VG_(bbs_done);
increment_epoch();
}
/* Was a debug-stop requested? */
if (VG_(bbs_to_go) == 0)
goto debug_stop;
/* Do the following loop until a runnable thread is found, or
deadlock is detected. */
while (True) {
/* For stats purposes only. */
VG_(num_scheduling_events_MAJOR) ++;
/* See if any I/O operations which we were waiting for have
completed, and, if so, make runnable the relevant waiting
threads. */
poll_for_ready_fds();
complete_blocked_syscalls();
/* See if there are any signals which need to be delivered. If
so, choose thread(s) to deliver them to, and build signal
delivery frames on those thread(s) stacks. */
VG_(deliver_signals)( 0 /*HACK*/ );
VG_(do_sanity_checks)(0 /*HACK*/, False);
/* Try and find a thread (tid) to run. */
tid_next = tid;
n_in_fdwait_or_sleep = 0;
while (True) {
tid_next++;
if (tid_next >= VG_N_THREADS) tid_next = 0;
if (vg_threads[tid_next].status == VgTs_WaitFD
|| vg_threads[tid_next].status == VgTs_Sleeping)
n_in_fdwait_or_sleep ++;
if (vg_threads[tid_next].status == VgTs_Runnable)
break; /* We can run this one. */
if (tid_next == tid)
break; /* been all the way round */
}
tid = tid_next;
if (vg_threads[tid].status == VgTs_Runnable) {
/* Found a suitable candidate. Fall out of this loop, so
we can advance to stage 2 of the scheduler: actually
running the thread. */
break;
}
/* We didn't find a runnable thread. Now what? */
if (n_in_fdwait_or_sleep == 0) {
/* No runnable threads and no prospect of any appearing
even if we wait for an arbitrary length of time. In
short, we have a deadlock. */
pp_sched_status();
return VgSrc_Deadlock;
}
/* At least one thread is in a fd-wait state. Delay for a
while, and go round again, in the hope that eventually a
thread becomes runnable. */
nanosleep_for_a_while();
// pp_sched_status();
// VG_(printf)(".\n");
}
/* ======================= Phase 2 of 3 =======================
Wahey! We've finally decided that thread tid is runnable, so
we now do that. Run it for as much of a quanta as possible.
Trivial requests are handled and the thread continues. The
aim is not to do too many of Phase 1 since it is expensive. */
if (0)
VG_(printf)("SCHED: tid %d, used %d\n", tid, VG_N_THREADS);
/* Figure out how many bbs to ask vg_run_innerloop to do. Note
that it decrements the counter before testing it for zero, so
that if VG_(dispatch_ctr) is set to N you get at most N-1
iterations. Also this means that VG_(dispatch_ctr) must
exceed zero before entering the innerloop. Also also, the
decrement is done before the bb is actually run, so you
always get at least one decrement even if nothing happens.
*/
if (VG_(bbs_to_go) >= VG_SCHEDULING_QUANTUM)
VG_(dispatch_ctr) = VG_SCHEDULING_QUANTUM + 1;
else
VG_(dispatch_ctr) = (UInt)VG_(bbs_to_go) + 1;
/* ... and remember what we asked for. */
dispatch_ctr_SAVED = VG_(dispatch_ctr);
/* Actually run thread tid. */
while (True) {
/* For stats purposes only. */
VG_(num_scheduling_events_MINOR) ++;
if (0)
VG_(message)(Vg_DebugMsg, "thread %d: running for %d bbs",
tid, VG_(dispatch_ctr) - 1 );
trc = run_thread_for_a_while ( tid );
/* Deal quickly with trivial scheduling events, and resume the
thread. */
if (trc == VG_TRC_INNER_FASTMISS) {
vg_assert(VG_(dispatch_ctr) > 0);
/* Trivial event. Miss in the fast-cache. Do a full
lookup for it. */
trans_addr
= VG_(search_transtab) ( vg_threads[tid].m_eip );
if (trans_addr == (Addr)0) {
/* Not found; we need to request a translation. */
VG_(create_translation_for)( vg_threads[tid].m_eip );
trans_addr = VG_(search_transtab) ( vg_threads[tid].m_eip );
if (trans_addr == (Addr)0)
VG_(panic)("VG_TRC_INNER_FASTMISS: missing tt_fast entry");
}
continue; /* with this thread */
}
if (trc == VG_TRC_EBP_JMP_CLIENTREQ) {
Bool is_triv = maybe_do_trivial_clientreq(tid);
if (is_triv) {
/* NOTE: a trivial request is something like a call to
malloc() or free(). It DOES NOT change the
Runnability of this thread nor the status of any
other thread; it is purely thread-local. */
continue; /* with this thread */
}
}
/* It's a non-trivial event. Give up running this thread and
handle things the expensive way. */
break;
}
/* ======================= Phase 3 of 3 =======================
Handle non-trivial thread requests, mostly pthread stuff. */
/* Ok, we've fallen out of the dispatcher for a
non-completely-trivial reason. First, update basic-block
counters. */
done_this_time = (Int)dispatch_ctr_SAVED - (Int)VG_(dispatch_ctr) - 1;
vg_assert(done_this_time >= 0);
VG_(bbs_to_go) -= (ULong)done_this_time;
VG_(bbs_done) += (ULong)done_this_time;
if (0 && trc != VG_TRC_INNER_FASTMISS)
VG_(message)(Vg_DebugMsg, "thread %d: completed %d bbs, trc %d",
tid, done_this_time, (Int)trc );
if (0 && trc != VG_TRC_INNER_FASTMISS)
VG_(message)(Vg_DebugMsg, "thread %d: %ld bbs, event %s",
tid, VG_(bbs_done),
name_of_sched_event(trc) );
/* Examine the thread's return code to figure out why it
stopped, and handle requests. */
switch (trc) {
case VG_TRC_INNER_FASTMISS:
VG_(panic)("VG_(scheduler): VG_TRC_INNER_FASTMISS");
/*NOTREACHED*/
break;
case VG_TRC_INNER_COUNTERZERO:
/* Timeslice is out. Let a new thread be scheduled,
simply by doing nothing, causing us to arrive back at
Phase 1. */
if (VG_(bbs_to_go) == 0) {
goto debug_stop;
}
vg_assert(VG_(dispatch_ctr) == 0);
break;
case VG_TRC_UNRESUMABLE_SIGNAL:
/* It got a SIGSEGV/SIGBUS, which we need to deliver right
away. Again, do nothing, so we wind up back at Phase
1, whereupon the signal will be "delivered". */
break;
case VG_TRC_EBP_JMP_SYSCALL:
/* Do a syscall for the vthread tid. This could cause it
to become non-runnable. */
sched_do_syscall(tid);
break;
case VG_TRC_EBP_JMP_CLIENTREQ:
/* Do a client request for the vthread tid. Note that
some requests will have been handled by
maybe_do_trivial_clientreq(), so we don't expect to see
those here.
*/
/* The thread's %EAX points at an arg block, the first
word of which is the request code. */
request_code = ((UInt*)(vg_threads[tid].m_eax))[0];
if (0) {
VG_(sprintf)(msg_buf, "request 0x%x", request_code );
print_sched_event(tid, msg_buf);
}
/* Do a non-trivial client request for thread tid. tid's
%EAX points to a short vector of argument words, the
first of which is the request code. The result of the
request is put in tid's %EDX. Alternatively, perhaps
the request causes tid to become non-runnable and/or
other blocked threads become runnable. In general we
can and often do mess with the state of arbitrary
threads at this point. */
if (request_code == VG_USERREQ__SHUTDOWN_VALGRIND) {
return VgSrc_Shutdown;
} else {
do_nontrivial_clientreq(tid);
}
break;
default:
VG_(printf)("\ntrc = %d\n", trc);
VG_(panic)("VG_(scheduler), phase 3: "
"unexpected thread return code");
/* NOTREACHED */
break;
} /* switch (trc) */
/* That completes Phase 3 of 3. Return now to the top of the
main scheduler loop, to Phase 1 of 3. */
} /* top-level scheduler loop */
/* NOTREACHED */
VG_(panic)("scheduler: post-main-loop ?!");
/* NOTREACHED */
debug_stop:
/* If we exited because of a debug stop, print the translation
of the last block executed -- by translating it again, and
throwing away the result. */
VG_(printf)(
"======vvvvvvvv====== LAST TRANSLATION ======vvvvvvvv======\n");
VG_(translate)( vg_threads[tid].m_eip, NULL, NULL, NULL );
VG_(printf)("\n");
VG_(printf)(
"======^^^^^^^^====== LAST TRANSLATION ======^^^^^^^^======\n");
return VgSrc_BbsDone;
}
/* ---------------------------------------------------------------------
The pthread implementation.
------------------------------------------------------------------ */
#include <pthread.h>
#include <errno.h>
#if !defined(PTHREAD_STACK_MIN)
# define PTHREAD_STACK_MIN (16384 - VG_AR_CLIENT_STACKBASE_REDZONE_SZB)
#endif
/* /usr/include/bits/pthreadtypes.h:
typedef unsigned long int pthread_t;
*/
static
void do_pthread_cancel ( ThreadId tid_canceller,
pthread_t tid_cancellee )
{
Char msg_buf[100];
/* We want make is appear that this thread has returned to
do_pthread_create_bogusRA with PTHREAD_CANCELED as the
return value. So: simple: put PTHREAD_CANCELED into %EAX
and &do_pthread_create_bogusRA into %EIP and keep going! */
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf, "cancelled by %d", tid_canceller);
print_sched_event(tid_cancellee, msg_buf);
}
vg_threads[tid_cancellee].m_eax = (UInt)PTHREAD_CANCELED;
vg_threads[tid_cancellee].m_eip = (UInt)&VG_(pthreadreturn_bogusRA);
vg_threads[tid_cancellee].status = VgTs_Runnable;
}
/* Thread tid is exiting, by returning from the function it was
created with. Or possibly due to pthread_exit or cancellation.
The main complication here is to resume any thread waiting to join
with this one. */
static
void handle_pthread_return ( ThreadId tid, void* retval )
{
ThreadId jnr; /* joiner, the thread calling pthread_join. */
UInt* jnr_args;
void** jnr_thread_return;
Char msg_buf[100];
/* Mark it as not in use. Leave the stack in place so the next
user of this slot doesn't reallocate it. */
vg_assert(tid >= 0 && tid < VG_N_THREADS);
vg_assert(vg_threads[tid].status != VgTs_Empty);
vg_threads[tid].retval = retval;
if (vg_threads[tid].joiner == VG_INVALID_THREADID) {
/* No one has yet done a join on me */
vg_threads[tid].status = VgTs_WaitJoiner;
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf,
"root fn returns, waiting for a call pthread_join(%d)",
tid);
print_sched_event(tid, msg_buf);
}
} else {
/* Some is waiting; make their join call return with success,
putting my exit code in the place specified by the caller's
thread_return param. This is all very horrible, since we
need to consult the joiner's arg block -- pointed to by its
%EAX -- in order to extract the 2nd param of its pthread_join
call. TODO: free properly the slot (also below).
*/
jnr = vg_threads[tid].joiner;
vg_assert(jnr >= 0 && jnr < VG_N_THREADS);
vg_assert(vg_threads[jnr].status == VgTs_WaitJoinee);
jnr_args = (UInt*)vg_threads[jnr].m_eax;
jnr_thread_return = (void**)(jnr_args[2]);
if (jnr_thread_return != NULL)
*jnr_thread_return = vg_threads[tid].retval;
vg_threads[jnr].m_edx = 0; /* success */
vg_threads[jnr].status = VgTs_Runnable;
vg_threads[tid].status = VgTs_Empty; /* bye! */
if (VG_(clo_instrument) && tid != 0)
VGM_(make_noaccess)( vg_threads[tid].stack_base,
vg_threads[tid].stack_size );
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf,
"root fn returns, to find a waiting pthread_join(%d)", tid);
print_sched_event(tid, msg_buf);
VG_(sprintf)(msg_buf,
"my pthread_join(%d) returned; resuming", tid);
print_sched_event(jnr, msg_buf);
}
}
/* Return value is irrelevant; this thread will not get
rescheduled. */
}
static
void do_pthread_join ( ThreadId tid, ThreadId jee, void** thread_return )
{
Char msg_buf[100];
/* jee, the joinee, is the thread specified as an arg in thread
tid's call to pthread_join. So tid is the join-er. */
vg_assert(tid >= 0 && tid < VG_N_THREADS);
vg_assert(vg_threads[tid].status == VgTs_Runnable);
if (jee == tid) {
vg_threads[tid].m_edx = EDEADLK; /* libc constant, not a kernel one */
vg_threads[tid].status = VgTs_Runnable;
return;
}
if (jee < 0
|| jee >= VG_N_THREADS
|| vg_threads[jee].status == VgTs_Empty) {
/* Invalid thread to join to. */
vg_threads[tid].m_edx = EINVAL;
vg_threads[tid].status = VgTs_Runnable;
return;
}
if (vg_threads[jee].joiner != VG_INVALID_THREADID) {
/* Someone already did join on this thread */
vg_threads[tid].m_edx = EINVAL;
vg_threads[tid].status = VgTs_Runnable;
return;
}
/* if (vg_threads[jee].detached) ... */
/* Perhaps the joinee has already finished? If so return
immediately with its return code, and free up the slot. TODO:
free it properly (also above). */
if (vg_threads[jee].status == VgTs_WaitJoiner) {
vg_assert(vg_threads[jee].joiner == VG_INVALID_THREADID);
vg_threads[tid].m_edx = 0; /* success */
if (thread_return != NULL)
*thread_return = vg_threads[jee].retval;
vg_threads[tid].status = VgTs_Runnable;
vg_threads[jee].status = VgTs_Empty; /* bye! */
if (VG_(clo_instrument) && jee != 0)
VGM_(make_noaccess)( vg_threads[jee].stack_base,
vg_threads[jee].stack_size );
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf,
"someone called pthread_join() on me; bye!");
print_sched_event(jee, msg_buf);
VG_(sprintf)(msg_buf,
"my pthread_join(%d) returned immediately",
jee );
print_sched_event(tid, msg_buf);
}
return;
}
/* Ok, so we'll have to wait on jee. */
vg_threads[jee].joiner = tid;
vg_threads[tid].status = VgTs_WaitJoinee;
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf,
"blocking on call of pthread_join(%d)", jee );
print_sched_event(tid, msg_buf);
}
/* So tid's join call does not return just now. */
}
static
void do_pthread_create ( ThreadId parent_tid,
pthread_t* thread,
pthread_attr_t* attr,
void* (*start_routine)(void *),
void* arg )
{
Addr new_stack;
UInt new_stk_szb;
ThreadId tid;
Char msg_buf[100];
/* Paranoia ... */
vg_assert(sizeof(pthread_t) == sizeof(UInt));
vg_assert(vg_threads[parent_tid].status != VgTs_Empty);
tid = vg_alloc_ThreadState();
/* If we've created the main thread's tid, we're in deep trouble :) */
vg_assert(tid != 0);
/* Copy the parent's CPU state into the child's, in a roundabout
way (via baseBlock). */
VG_(load_thread_state)(parent_tid);
VG_(save_thread_state)(tid);
/* Consider allocating the child a stack, if the one it already has
is inadequate. */
new_stk_szb = PTHREAD_STACK_MIN;
if (new_stk_szb > vg_threads[tid].stack_size) {
/* Again, for good measure :) We definitely don't want to be
allocating a stack for the main thread. */
vg_assert(tid != 0);
/* for now, we don't handle the case of anything other than
assigning it for the first time. */
vg_assert(vg_threads[tid].stack_size == 0);
vg_assert(vg_threads[tid].stack_base == (Addr)NULL);
new_stack = (Addr)VG_(get_memory_from_mmap)( new_stk_szb );
vg_threads[tid].stack_base = new_stack;
vg_threads[tid].stack_size = new_stk_szb;
vg_threads[tid].m_esp
= new_stack + new_stk_szb
- VG_AR_CLIENT_STACKBASE_REDZONE_SZB;
}
if (VG_(clo_instrument))
VGM_(make_noaccess)( vg_threads[tid].m_esp,
VG_AR_CLIENT_STACKBASE_REDZONE_SZB );
/* push arg */
vg_threads[tid].m_esp -= 4;
* (UInt*)(vg_threads[tid].m_esp) = (UInt)arg;
/* push (magical) return address */
vg_threads[tid].m_esp -= 4;
* (UInt*)(vg_threads[tid].m_esp) = (UInt)VG_(pthreadreturn_bogusRA);
if (VG_(clo_instrument))
VGM_(make_readable)( vg_threads[tid].m_esp, 2 * 4 );
/* this is where we start */
vg_threads[tid].m_eip = (UInt)start_routine;
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf,
"new thread, created by %d", parent_tid );
print_sched_event(tid, msg_buf);
}
/* store the thread id in *thread. */
// if (VG_(clo_instrument))
// ***** CHECK *thread is writable
*thread = (pthread_t)tid;
/* return zero */
vg_threads[tid].joiner = VG_INVALID_THREADID;
vg_threads[tid].status = VgTs_Runnable;
vg_threads[tid].m_edx = 0; /* success */
}
/* Horrible hacks to do with pthread_mutex_t: the real pthread_mutex_t
is a struct with at least 5 words:
typedef struct
{
int __m_reserved; -- Reserved for future use
int __m_count; -- Depth of recursive locking
_pthread_descr __m_owner; -- Owner thread (if recursive or errcheck)
int __m_kind; -- Mutex kind: fast, recursive or errcheck
struct _pthread_fastlock __m_lock; -- Underlying fast lock
} pthread_mutex_t;
Ours is just a single word, an index into vg_mutexes[].
For now I'll park it in the __m_reserved field.
Uninitialised mutexes (PTHREAD_MUTEX_INITIALIZER) all have
a zero __m_count field (see /usr/include/pthread.h). So I'll
use zero to mean non-inited, and 1 to mean inited.
How convenient.
*/
static
void initialise_mutex ( ThreadId tid, pthread_mutex_t *mutex )
{
MutexId mid;
Char msg_buf[100];
/* vg_alloc_MutexId aborts if we can't allocate a mutex, for
whatever reason. */
mid = vg_alloc_VgMutex();
vg_mutexes[mid].in_use = True;
vg_mutexes[mid].held = False;
vg_mutexes[mid].owner = VG_INVALID_THREADID; /* irrelevant */
mutex->__m_reserved = mid;
mutex->__m_count = 1; /* initialised */
if (VG_(clo_trace_pthread)) {
VG_(sprintf)(msg_buf, "(initialise mutex) (%p) -> %d",
mutex, mid );
print_pthread_event(tid, msg_buf);
}
}
/* Allocate a new MutexId and write it into *mutex. Ideally take
notice of the attributes in *mutexattr. */
static
void do_pthread_mutex_init ( ThreadId tid,
pthread_mutex_t *mutex,
const pthread_mutexattr_t *mutexattr)
{
Char msg_buf[100];
/* Paranoia ... */
vg_assert(sizeof(pthread_mutex_t) >= sizeof(UInt));
initialise_mutex(tid, mutex);
if (VG_(clo_trace_pthread)) {
VG_(sprintf)(msg_buf, "pthread_mutex_init (%p) -> %d",
mutex, mutex->__m_reserved );
print_pthread_event(tid, msg_buf);
}
/*
RETURN VALUE
pthread_mutex_init always returns 0. The other mutex functions
return 0 on success and a non-zero error code on error.
*/
/* THIS THREAD returns with 0. */
vg_threads[tid].m_edx = 0;
}
static
void do_pthread_mutex_lock( ThreadId tid, pthread_mutex_t *mutex )
{
MutexId mid;
Char msg_buf[100];
/* *mutex contains the MutexId, or one of the magic values
PTHREAD_*MUTEX_INITIALIZER*, indicating we need to initialise it
now. See comment(s) above re use of __m_count to indicated
initialisation status.
*/
/* POSIX doesn't mandate this, but for sanity ... */
if (mutex == NULL) {
vg_threads[tid].m_edx = EINVAL;
return;
}
if (mutex->__m_count == 0) {
initialise_mutex(tid, mutex);
}
mid = mutex->__m_reserved;
if (mid < 0 || mid >= VG_N_MUTEXES || !vg_mutexes[mid].in_use) {
vg_threads[tid].m_edx = EINVAL;
return;
}
if (VG_(clo_trace_pthread)) {
VG_(sprintf)(msg_buf, "pthread_mutex_lock %d (%p)",
mid, mutex );
print_pthread_event(tid, msg_buf);
}
/* Assert initialised. */
vg_assert(mutex->__m_count == 1);
/* Assume tid valid. */
vg_assert(vg_threads[tid].status == VgTs_Runnable);
if (vg_mutexes[mid].held) {
if (vg_mutexes[mid].owner == tid) {
vg_threads[tid].m_edx = EDEADLK;
return;
}
/* Someone else has it; we have to wait. */
vg_threads[tid].status = VgTs_WaitMX;
vg_threads[tid].waited_on_mid = mid;
/* No assignment to %EDX, since we're blocking. */
if (VG_(clo_trace_pthread)) {
VG_(sprintf)(msg_buf, "pthread_mutex_lock %d (%p): BLOCK",
mid, mutex );
print_pthread_event(tid, msg_buf);
}
} else {
/* We get it! */
vg_mutexes[mid].held = True;
vg_mutexes[mid].owner = tid;
/* return 0 (success). */
vg_threads[tid].m_edx = 0;
}
}
static
void do_pthread_mutex_unlock ( ThreadId tid,
pthread_mutex_t *mutex )
{
MutexId mid;
Int i;
Char msg_buf[100];
if (mutex == NULL
|| mutex->__m_count != 1) {
vg_threads[tid].m_edx = EINVAL;
return;
}
mid = mutex->__m_reserved;
if (mid < 0 || mid >= VG_N_MUTEXES || !vg_mutexes[mid].in_use) {
vg_threads[tid].m_edx = EINVAL;
return;
}
if (VG_(clo_trace_pthread)) {
VG_(sprintf)(msg_buf, "pthread_mutex_unlock %d (%p)",
mid, mutex );
print_pthread_event(tid, msg_buf);
}
/* Assume tid valid */
vg_assert(vg_threads[tid].status == VgTs_Runnable);
/* Barf if we don't currently hold the mutex. */
if (!vg_mutexes[mid].held || vg_mutexes[mid].owner != tid) {
vg_threads[tid].m_edx = EPERM;
return;
}
/* Find some arbitrary thread waiting on this mutex, and make it
runnable. If none are waiting, mark the mutex as not held. */
for (i = 0; i < VG_N_THREADS; i++) {
if (vg_threads[i].status == VgTs_Empty)
continue;
if (vg_threads[i].status == VgTs_WaitMX
&& vg_threads[i].waited_on_mid == mid)
break;
}
vg_assert(i <= VG_N_THREADS);
if (i == VG_N_THREADS) {
/* Nobody else is waiting on it. */
vg_mutexes[mid].held = False;
} else {
/* Notionally transfer the hold to thread i, whose
pthread_mutex_lock() call now returns with 0 (success). */
vg_mutexes[mid].owner = i;
vg_threads[i].status = VgTs_Runnable;
vg_threads[i].m_edx = 0; /* pth_lock() success */
if (VG_(clo_trace_pthread)) {
VG_(sprintf)(msg_buf, "pthread_mutex_lock %d: RESUME",
mid );
print_pthread_event(tid, msg_buf);
}
}
/* In either case, our (tid's) pth_unlock() returns with 0
(success). */
vg_threads[tid].m_edx = 0; /* Success. */
}
static void do_pthread_mutex_destroy ( ThreadId tid,
pthread_mutex_t *mutex )
{
MutexId mid;
Char msg_buf[100];
if (mutex == NULL
|| mutex->__m_count != 1) {
vg_threads[tid].m_edx = EINVAL;
return;
}
mid = mutex->__m_reserved;
if (mid < 0 || mid >= VG_N_MUTEXES || !vg_mutexes[mid].in_use) {
vg_threads[tid].m_edx = EINVAL;
return;
}
if (VG_(clo_trace_pthread)) {
VG_(sprintf)(msg_buf, "pthread_mutex_destroy %d (%p)",
mid, mutex );
print_pthread_event(tid, msg_buf);
}
/* Assume tid valid */
vg_assert(vg_threads[tid].status == VgTs_Runnable);
/* Barf if the mutex is currently held. */
if (vg_mutexes[mid].held) {
vg_threads[tid].m_edx = EBUSY;
return;
}
mutex->__m_count = 0; /* uninitialised */
vg_mutexes[mid].in_use = False;
vg_threads[tid].m_edx = 0;
}
/* vthread tid is returning from a signal handler; modify its
stack/regs accordingly. */
static
void handle_signal_return ( ThreadId tid )
{
Char msg_buf[100];
Bool restart_blocked_syscalls = VG_(signal_returns)(tid);
if (restart_blocked_syscalls)
/* Easy; we don't have to do anything. */
return;
if (vg_threads[tid].status == VgTs_WaitFD) {
vg_assert(vg_threads[tid].m_eax == __NR_read
|| vg_threads[tid].m_eax == __NR_write);
/* read() or write() interrupted. Force a return with EINTR. */
vg_threads[tid].m_eax = -VKI_EINTR;
vg_threads[tid].status = VgTs_Runnable;
if (VG_(clo_trace_sched)) {
VG_(sprintf)(msg_buf,
"read() / write() interrupted by signal; return EINTR" );
print_sched_event(tid, msg_buf);
}
return;
}
if (vg_threads[tid].status == VgTs_WaitFD) {
vg_assert(vg_threads[tid].m_eax == __NR_nanosleep);
/* We interrupted a nanosleep(). The right thing to do is to
write the unused time to nanosleep's second param and return
EINTR, but I'm too lazy for that. */
return;
}
/* All other cases? Just return. */
}
/* ---------------------------------------------------------------------
Handle non-trivial client requests.
------------------------------------------------------------------ */
static
void do_nontrivial_clientreq ( ThreadId tid )
{
UInt* arg = (UInt*)(vg_threads[tid].m_eax);
UInt req_no = arg[0];
switch (req_no) {
case VG_USERREQ__PTHREAD_CREATE:
do_pthread_create( tid,
(pthread_t*)arg[1],
(pthread_attr_t*)arg[2],
(void*(*)(void*))arg[3],
(void*)arg[4] );
break;
case VG_USERREQ__PTHREAD_RETURNS:
handle_pthread_return( tid, (void*)arg[1] );
break;
case VG_USERREQ__PTHREAD_JOIN:
do_pthread_join( tid, arg[1], (void**)(arg[2]) );
break;
/* Sigh ... this probably will cause huge numbers of major
(expensive) scheduling events, for no real reason.
Perhaps should be classified as a trivial-request. */
case VG_USERREQ__PTHREAD_GET_THREADID:
vg_threads[tid].m_edx = tid;
break;
case VG_USERREQ__PTHREAD_MUTEX_INIT:
do_pthread_mutex_init( tid,
(pthread_mutex_t *)(arg[1]),
(pthread_mutexattr_t *)(arg[2]) );
break;
case VG_USERREQ__PTHREAD_MUTEX_LOCK:
do_pthread_mutex_lock( tid, (pthread_mutex_t *)(arg[1]) );
break;
case VG_USERREQ__PTHREAD_MUTEX_UNLOCK:
do_pthread_mutex_unlock( tid, (pthread_mutex_t *)(arg[1]) );
break;
case VG_USERREQ__PTHREAD_MUTEX_DESTROY:
do_pthread_mutex_destroy( tid, (pthread_mutex_t *)(arg[1]) );
break;
case VG_USERREQ__PTHREAD_CANCEL:
do_pthread_cancel( tid, (pthread_t)(arg[1]) );
break;
case VG_USERREQ__MAKE_NOACCESS:
case VG_USERREQ__MAKE_WRITABLE:
case VG_USERREQ__MAKE_READABLE:
case VG_USERREQ__DISCARD:
case VG_USERREQ__CHECK_WRITABLE:
case VG_USERREQ__CHECK_READABLE:
case VG_USERREQ__MAKE_NOACCESS_STACK:
case VG_USERREQ__RUNNING_ON_VALGRIND:
case VG_USERREQ__DO_LEAK_CHECK:
vg_threads[tid].m_edx
= VG_(handle_client_request) ( &vg_threads[tid], arg );
break;
case VG_USERREQ__SIGNAL_RETURNS:
handle_signal_return(tid);
break;
default:
VG_(printf)("panic'd on private request = 0x%x\n", arg[0] );
VG_(panic)("handle_private_client_pthread_request: "
"unknown request");
/*NOTREACHED*/
break;
}
}
/*--------------------------------------------------------------------*/
/*--- end vg_scheduler.c ---*/
/*--------------------------------------------------------------------*/