From: Philippe Waroquiers Date: Mon, 16 Jun 2014 20:00:14 +0000 (+0000) Subject: Add helgrind intercepts to have helgrind understanding Ada tasks terination rules X-Git-Tag: svn/VALGRIND_3_10_0~391 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f3ee06ddcea9200fc92cff8acd6741ae0dcd4ab4;p=thirdparty%2Fvalgrind.git Add helgrind intercepts to have helgrind understanding Ada tasks terination rules A recent gnatpro version is needed for this to work. Thanks to these intercepts, some false positive errors are avoided, and helgrind properly recuperates some internal memory associated to the terminated task. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@14046 --- diff --git a/NEWS b/NEWS index 60c61093ad..1c746fc728 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,10 @@ Release 3.10.0 (?? ?????? 201?) * Helgrind: - Helgrind GDB server monitor command 'info locks' giving the list of locks, their location, and their status. + - Helgrind now understands the Ada task termination rules + and creates a 'H-B relationship' between a terminated task and + its master. This only works with gnatpro >= 7.3.0w-20140611 + or gcc >= ????? (TBD: check when changes pushed to FSF gcc). * Callgrind: - callgrind_control now supports the --vgdb-prefix argument, diff --git a/helgrind/helgrind.h b/helgrind/helgrind.h index f777c7b8a1..406dc67f8d 100644 --- a/helgrind/helgrind.h +++ b/helgrind/helgrind.h @@ -116,8 +116,10 @@ typedef _VG_USERREQ__HG_ARANGE_MAKE_TRACKED, /* Addr a, ulong len */ _VG_USERREQ__HG_PTHREAD_BARRIER_RESIZE_PRE, /* pth_bar_t*, ulong */ _VG_USERREQ__HG_CLEAN_MEMORY_HEAPBLOCK, /* Addr start_of_block */ - _VG_USERREQ__HG_PTHREAD_COND_INIT_POST /* pth_cond_t*, pth_cond_attr_t*/ - + _VG_USERREQ__HG_PTHREAD_COND_INIT_POST, /* pth_cond_t*, pth_cond_attr_t*/ + _VG_USERREQ__HG_GNAT_MASTER_HOOK, /* void*d,void*m,Word ml */ + _VG_USERREQ__HG_GNAT_MASTER_COMPLETED_HOOK /* void*s,Word ml */ + } Vg_TCheckClientRequest; diff --git a/helgrind/hg_intercepts.c b/helgrind/hg_intercepts.c index 5f02e5b280..71bd40288b 100644 --- a/helgrind/hg_intercepts.c +++ b/helgrind/hg_intercepts.c @@ -60,6 +60,7 @@ #define TRACE_PTH_FNS 0 #define TRACE_QT4_FNS 0 +#define TRACE_GNAT_FNS 0 /*----------------------------------------------------------------*/ @@ -403,6 +404,80 @@ That's the key. The kernel resets the TID field after the thread is done. No way the joiner can return before the thread is gone. */ +//----------------------------------------------------------- +// Ada gcc gnat runtime: +// The gnat gcc Ada runtime does not use pthread_join. Instead, it uses +// a combination of other pthread primitives to ensure a child thread +// is gone. This combination is somewhat functionally equivalent to a +// pthread_join. +// We wrap two hook procedures called by the gnat gcc Ada runtime +// that allows helgrind to understand the semantic of Ada task dependencies +// and termination. + +// System.Tasking.Debug.Master_Hook is called by a task Dependent to +// indicate that its master is identified by master+master_level. +void I_WRAP_SONAME_FNNAME_ZU + (Za, + system__tasking__debug__master_hook) + (void *dependent, void *master, int master_level); +void I_WRAP_SONAME_FNNAME_ZU + (Za, + system__tasking__debug__master_hook) + (void *dependent, void *master, int master_level) +{ + OrigFn fn; + VALGRIND_GET_ORIG_FN(fn); + if (TRACE_GNAT_FNS) { + fprintf(stderr, "<< GNAT master_hook wrapper " + "dependent %p master %p master_level %d\n", + dependent, master, master_level); fflush(stderr); + } + + // We call the wrapped function, even if it is a null body. + CALL_FN_v_WWW(fn, dependent, master, master_level); + + DO_CREQ_v_WWW(_VG_USERREQ__HG_GNAT_MASTER_HOOK, + void*,dependent, void*,master, + Word, (Word)master_level); + + if (TRACE_GNAT_FNS) { + fprintf(stderr, " :: GNAT master_hook >>\n"); + } +} + +// System.Tasking.Debug.Master_Completed_Hook is called by a task to +// indicate that it has completed a master. +// This indicates that all its Dependent tasks (that identified themselves +// with the Master_Hook call) are terminated. Helgrind can consider +// at this point that the equivalent of a 'pthread_join' has been done +// between self_id and all dependent tasks at master_level. +void I_WRAP_SONAME_FNNAME_ZU + (Za, + system__tasking__debug__master_completed_hook) + (void *self_id, int master_level); +void I_WRAP_SONAME_FNNAME_ZU + (Za, + system__tasking__debug__master_completed_hook) + (void *self_id, int master_level) +{ + OrigFn fn; + VALGRIND_GET_ORIG_FN(fn); + if (TRACE_GNAT_FNS) { + fprintf(stderr, "<< GNAT master_completed_hook wrapper " + "self_id %p master_level %d\n", + self_id, master_level); fflush(stderr); + } + + // We call the wrapped function, even if it is a null body. + CALL_FN_v_WW(fn, self_id, master_level); + + DO_CREQ_v_WW(_VG_USERREQ__HG_GNAT_MASTER_COMPLETED_HOOK, + void*,self_id, Word,(Word)master_level); + + if (TRACE_GNAT_FNS) { + fprintf(stderr, " :: GNAT master_completed_hook >>\n"); + } +} /*----------------------------------------------------------------*/ /*--- pthread_mutex_t functions ---*/ diff --git a/helgrind/hg_main.c b/helgrind/hg_main.c index 5e12520bb1..f5d02b227c 100644 --- a/helgrind/hg_main.c +++ b/helgrind/hg_main.c @@ -1670,6 +1670,36 @@ void evh__atfork_child ( ThreadId tid ) } } +/* generate a dependence from the hbthr_q quitter to the hbthr_s stayer. */ +static +void generate_quitter_stayer_dependence (Thr* hbthr_q, Thr* hbthr_s) +{ + SO* so; + /* Allocate a temporary synchronisation object and use it to send + an imaginary message from the quitter to the stayer, the purpose + being to generate a dependence from the quitter to the + stayer. */ + so = libhb_so_alloc(); + tl_assert(so); + /* Send last arg of _so_send as False, since the sending thread + doesn't actually exist any more, so we don't want _so_send to + try taking stack snapshots of it. */ + libhb_so_send(hbthr_q, so, True/*strong_send*//*?!? wrt comment above*/); + libhb_so_recv(hbthr_s, so, True/*strong_recv*/); + libhb_so_dealloc(so); + + /* Tell libhb that the quitter has been reaped. Note that we might + have to be cleverer about this, to exclude 2nd and subsequent + notifications for the same hbthr_q, in the case where the app is + buggy (calls pthread_join twice or more on the same thread) AND + where libpthread is also buggy and doesn't return ESRCH on + subsequent calls. (If libpthread isn't thusly buggy, then the + wrapper for pthread_join in hg_intercepts.c will stop us getting + notified here multiple times for the same joinee.) See also + comments in helgrind/tests/jointwice.c. */ + libhb_joinedwith_done(hbthr_q); +} + static void evh__HG_PTHREAD_JOIN_POST ( ThreadId stay_tid, Thread* quit_thr ) @@ -1678,7 +1708,6 @@ void evh__HG_PTHREAD_JOIN_POST ( ThreadId stay_tid, Thread* quit_thr ) Thread* thr_q; Thr* hbthr_s; Thr* hbthr_q; - SO* so; if (SHOW_EVENTS >= 1) VG_(printf)("evh__post_thread_join(stayer=%d, quitter=%p)\n", @@ -1698,29 +1727,7 @@ void evh__HG_PTHREAD_JOIN_POST ( ThreadId stay_tid, Thread* quit_thr ) tl_assert( libhb_get_Thr_hgthread(hbthr_s) == thr_s ); tl_assert( libhb_get_Thr_hgthread(hbthr_q) == thr_q ); - /* Allocate a temporary synchronisation object and use it to send - an imaginary message from the quitter to the stayer, the purpose - being to generate a dependence from the quitter to the - stayer. */ - so = libhb_so_alloc(); - tl_assert(so); - /* Send last arg of _so_send as False, since the sending thread - doesn't actually exist any more, so we don't want _so_send to - try taking stack snapshots of it. */ - libhb_so_send(hbthr_q, so, True/*strong_send*//*?!? wrt comment above*/); - libhb_so_recv(hbthr_s, so, True/*strong_recv*/); - libhb_so_dealloc(so); - - /* Tell libhb that the quitter has been reaped. Note that we might - have to be cleverer about this, to exclude 2nd and subsequent - notifications for the same hbthr_q, in the case where the app is - buggy (calls pthread_join twice or more on the same thread) AND - where libpthread is also buggy and doesn't return ESRCH on - subsequent calls. (If libpthread isn't thusly buggy, then the - wrapper for pthread_join in hg_intercepts.c will stop us getting - notified here multiple times for the same joinee.) See also - comments in helgrind/tests/jointwice.c. */ - libhb_joinedwith_done(hbthr_q); + generate_quitter_stayer_dependence (hbthr_q, hbthr_s); /* evh__pre_thread_ll_exit issues an error message if the exiting thread holds any locks. No need to check here. */ @@ -4740,6 +4747,26 @@ static void map_pthread_t_to_Thread_INIT ( void ) { } } +/* A list of Ada dependent tasks and their masters. Used for implementing + the Ada task termination semantic as implemented by the + gcc gnat Ada runtime. */ +typedef + struct { + void* dependent; // Ada Task Control Block of the Dependent + void* master; // ATCB of the master + Word master_level; // level of dependency between master and dependent + Thread* hg_dependent; // helgrind Thread* for dependent task. + } + GNAT_dmml; +static XArray* gnat_dmmls; /* of GNAT_dmml */ +static void gnat_dmmls_INIT (void) +{ + if (UNLIKELY(gnat_dmmls == NULL)) { + gnat_dmmls = VG_(newXA) (HG_(zalloc), "hg.gnat_md.1", + HG_(free), + sizeof(GNAT_dmml) ); + } +} static void print_monitor_help ( void ) { VG_(gdb_printf) @@ -4936,6 +4963,60 @@ Bool hg_handle_client_request ( ThreadId tid, UWord* args, UWord* ret) break; } + /* This thread (tid) is informing us of its master. */ + case _VG_USERREQ__HG_GNAT_MASTER_HOOK: { + GNAT_dmml dmml; + dmml.dependent = (void*)args[1]; + dmml.master = (void*)args[2]; + dmml.master_level = (Word)args[3]; + dmml.hg_dependent = map_threads_maybe_lookup( tid ); + tl_assert(dmml.hg_dependent); + + if (0) + VG_(printf)("HG_GNAT_MASTER_HOOK (tid %d): " + "dependent = %p master = %p master_level = %ld" + " dependent Thread* = %p\n", + (Int)tid, dmml.dependent, dmml.master, dmml.master_level, + dmml.hg_dependent); + gnat_dmmls_INIT(); + VG_(addToXA) (gnat_dmmls, &dmml); + break; + } + + /* This thread (tid) is informing us that it has completed a + master. */ + case _VG_USERREQ__HG_GNAT_MASTER_COMPLETED_HOOK: { + Word n; + const Thread *stayer = map_threads_maybe_lookup( tid ); + const void *master = (void*)args[1]; + const Word master_level = (Word) args[2]; + tl_assert(stayer); + + if (0) + VG_(printf)("HG_GNAT_MASTER_COMPLETED_HOOK (tid %d): " + "self_id = %p master_level = %ld Thread* = %p\n", + (Int)tid, master, master_level, stayer); + + gnat_dmmls_INIT(); + /* Reverse loop on the array, simulating a pthread_join for + the Dependent tasks of the completed master, and removing + them from the array. */ + for (n = VG_(sizeXA) (gnat_dmmls) - 1; n >= 0; n--) { + GNAT_dmml *dmml = (GNAT_dmml*) VG_(indexXA)(gnat_dmmls, n); + if (dmml->master == master + && dmml->master_level == master_level) { + if (0) + VG_(printf)("quitter %p dependency to stayer %p\n", + dmml->hg_dependent->hbthr, stayer->hbthr); + tl_assert(dmml->hg_dependent->hbthr != stayer->hbthr); + generate_quitter_stayer_dependence (dmml->hg_dependent->hbthr, + stayer->hbthr); + VG_(removeIndexXA) (gnat_dmmls, n); + } + } + break; + } + /* EXPOSITION only: by intercepting lock init events we can show the user where the lock was initialised, rather than only being able to show where it was first locked. Intercepting