|
From: <sv...@va...> - 2014-06-16 20:00:27
|
Author: philippe
Date: Mon Jun 16 20:00:14 2014
New Revision: 14046
Log:
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.
Modified:
trunk/NEWS
trunk/helgrind/helgrind.h
trunk/helgrind/hg_intercepts.c
trunk/helgrind/hg_main.c
Modified: trunk/NEWS
==============================================================================
--- trunk/NEWS (original)
+++ trunk/NEWS Mon Jun 16 20:00:14 2014
@@ -17,6 +17,10 @@
* 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,
Modified: trunk/helgrind/helgrind.h
==============================================================================
--- trunk/helgrind/helgrind.h (original)
+++ trunk/helgrind/helgrind.h Mon Jun 16 20:00:14 2014
@@ -116,8 +116,10 @@
_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;
Modified: trunk/helgrind/hg_intercepts.c
==============================================================================
--- trunk/helgrind/hg_intercepts.c (original)
+++ trunk/helgrind/hg_intercepts.c Mon Jun 16 20:00:14 2014
@@ -60,6 +60,7 @@
#define TRACE_PTH_FNS 0
#define TRACE_QT4_FNS 0
+#define TRACE_GNAT_FNS 0
/*----------------------------------------------------------------*/
@@ -403,6 +404,80 @@
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 ---*/
Modified: trunk/helgrind/hg_main.c
==============================================================================
--- trunk/helgrind/hg_main.c (original)
+++ trunk/helgrind/hg_main.c Mon Jun 16 20:00:14 2014
@@ -1670,6 +1670,36 @@
}
}
+/* 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 @@
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 @@
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 @@
}
}
+/* 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 @@
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
|