Skeletal support for TSan-compatible annotations.


git-svn-id: svn://svn.valgrind.org/valgrind/trunk@10810 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/helgrind/helgrind.h b/helgrind/helgrind.h
index f42d5c4..317695b 100644
--- a/helgrind/helgrind.h
+++ b/helgrind/helgrind.h
@@ -60,6 +60,10 @@
 
 #include "valgrind.h"
 
+/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !!
+   This enum comprises an ABI exported by Valgrind to programs
+   which use client requests.  DO NOT CHANGE THE ORDER OF THESE
+   ENTRIES, NOR DELETE ANY -- add new ones at the end. */
 typedef
    enum {
       VG_USERREQ__HG_CLEAN_MEMORY = VG_USERREQ_TOOL_BASE('H','G'),
@@ -100,20 +104,389 @@
       _VG_USERREQ__HG_PTHREAD_SPIN_INIT_OR_UNLOCK_POST, /* pth_slk_t* */
       _VG_USERREQ__HG_PTHREAD_SPIN_LOCK_PRE,      /* pth_slk_t* */
       _VG_USERREQ__HG_PTHREAD_SPIN_LOCK_POST,     /* pth_slk_t* */
-      _VG_USERREQ__HG_PTHREAD_SPIN_DESTROY_PRE    /* pth_slk_t* */
+      _VG_USERREQ__HG_PTHREAD_SPIN_DESTROY_PRE,   /* pth_slk_t* */
+      _VG_USERREQ__HG_CLIENTREQ_UNIMP,            /* char* */
+      _VG_USERREQ__HG_USERSO_SEND_PRE,        /* arbitrary UWord SO-tag */
+      _VG_USERREQ__HG_USERSO_RECV_POST,       /* arbitrary UWord SO-tag */
+      _VG_USERREQ__HG_RESERVED1,              /* Do not use */
+      _VG_USERREQ__HG_RESERVED2               /* Do not use */
 
    } Vg_TCheckClientRequest;
 
-/* Clean memory state.  This makes Helgrind forget everything it knew
-   about the specified memory range, and resets it to New.  This is
-   particularly useful for memory allocators that wish to recycle
-   memory. */
-#define VALGRIND_HG_CLEAN_MEMORY(_qzz_start, _qzz_len)                    \
-   do {                                                                   \
-     unsigned long _qzz_res;                                              \
-     VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, VG_USERREQ__HG_CLEAN_MEMORY, \
-                                _qzz_start, _qzz_len, 0, 0, 0);	          \
-     (void)0;                                                             \
+
+/*----------------------------------------------------------------*/
+/*--- An implementation-only request -- not for end user use   ---*/
+/*----------------------------------------------------------------*/
+
+#define _HG_CLIENTREQ_UNIMP(_qzz_str)                            \
+   do {                                                          \
+     unsigned long _qzz_res;                                     \
+     VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0,                     \
+                                _VG_USERREQ__HG_CLIENTREQ_UNIMP, \
+                                _qzz_str, 0, 0, 0, 0);           \
+     (void)0;                                                    \
    } while(0)
 
+
+/*----------------------------------------------------------------*/
+/*--- Misc requests                                            ---*/
+/*----------------------------------------------------------------*/
+
+/* Clean memory state.  This makes Helgrind forget everything it knew
+   about the specified memory range.  Effectively this announces that
+   the specified memory range now "belongs" to the calling thread, so
+   that: (1) the calling thread can access it safely without
+   synchronisation, and (2) all other threads must sync with this one
+   to access it safely.  This is particularly useful for memory
+   allocators that wish to recycle memory. */
+#define VALGRIND_HG_CLEAN_MEMORY(_qzz_start, _qzz_len) \
+   do {                                                \
+     unsigned long _qzz_res;                           \
+     VALGRIND_DO_CLIENT_REQUEST(                       \
+        (_qzz_res), 0, VG_USERREQ__HG_CLEAN_MEMORY,    \
+        (_qzz_start), (_qzz_len), 0, 0, 0              \
+     );                                                \
+     (void)0;                                          \
+   } while(0)
+
+
+/*----------------------------------------------------------------*/
+/*--- ThreadSanitizer-compatible requests                      ---*/
+/*----------------------------------------------------------------*/
+
+/* A quite-broad set of annotations, as used in the ThreadSanitizer
+   project.  This implementation aims to be a (source-level)
+   compatible implementation of the macros defined in:
+
+   http://code.google.com/p/google-perftools/source  \
+                         /browse/trunk/src/base/dynamic_annotations.h
+
+   (some of the comments below are taken from the above file)
+
+   The implementation here is very incomplete, and intended as a
+   starting point.  Many of the macros are unimplemented.  Rather than
+   allowing unimplemented macros to silently do nothing, they cause an
+   assertion.  Intention is to implement them on demand.
+
+   The major use of these macros is to make visible to race detectors,
+   the behaviour (effects) of user-implemented synchronisation
+   primitives, that the detectors could not otherwise deduce from the
+   normal observation of pthread etc calls.
+
+   Some of the macros are no-ops in Helgrind.  That's because Helgrind
+   is a pure happens-before detector, whereas ThreadSanitizer uses a
+   hybrid lockset and happens-before scheme, which requires more
+   accurate annotations for correct operation.
+
+   The macros are listed in the same order as in dynamic_annotations.h
+   (URL just above).
+
+   I should point out that I am less than clear about the intended
+   semantics of quite a number of them.  Comments and clarifications
+   welcomed!
+*/
+
+/* ----------------------------------------------------------------
+   These four allow description of user-level condition variables,
+   apparently in the style of POSIX's pthread_cond_t.  Currently
+   unimplemented and will assert.
+   ----------------------------------------------------------------
+*/
+/* Report that wait on the condition variable at address CV has
+   succeeded and the lock at address LOCK is now held.  CV and LOCK
+   are completely arbitrary memory addresses which presumably mean
+   something to the application, but are meaningless to Helgrind. */
+#define ANNOTATE_CONDVAR_LOCK_WAIT(cv, lock) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_CONDVAR_LOCK_WAIT")
+
+/* Report that wait on the condition variable at CV has succeeded.
+   Variant w/o lock. */
+#define ANNOTATE_CONDVAR_WAIT(cv) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_CONDVAR_WAIT")
+
+/* Report that we are about to signal on the condition variable at
+   address CV. */
+#define ANNOTATE_CONDVAR_SIGNAL(cv) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_CONDVAR_SIGNAL")
+  
+/* Report that we are about to signal_all on the condition variable at
+   CV. */
+#define ANNOTATE_CONDVAR_SIGNAL_ALL(cv) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_CONDVAR_SIGNAL_ALL")
+
+
+/* ----------------------------------------------------------------
+   Create completely arbitrary happens-before edges between threads.
+   If thread T1 does ANNOTATE_HAPPENS_BEFORE(obj) and later (w.r.t.
+   some notional global clock for the computation) thread T2 does
+   ANNOTATE_HAPPENS_AFTER(obj), then Helgrind will regard all memory
+   accesses done by T1 before the ..BEFORE.. call as happening-before
+   all memory accesses done by T2 after the ..AFTER.. call.  Hence
+   Helgrind won't complain about races if T2's accesses afterwards are
+   to the same locations as T1's accesses before.
+
+   OBJ is a machine word (unsigned long, or void*), is completely
+   arbitrary, and denotes the identity of some synchronisation object
+   you're modelling.
+
+   You must do the _BEFORE call just before the real sync event on the
+   signaller's side, and _AFTER just after the real sync event on the
+   waiter's side.
+
+   If none of the rest of these macros make sense to you, at least
+   take the time to understand these two.  They form the very essence
+   of describing arbitrary inter-thread synchronisation events to
+   Helgrind.  You can get a long way just with them alone.
+   ----------------------------------------------------------------
+*/
+#define ANNOTATE_HAPPENS_BEFORE(obj) \
+   do {                                                          \
+     unsigned long _qzz_res;                                     \
+     VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0,                     \
+                                _VG_USERREQ__HG_USERSO_SEND_PRE, \
+                                obj, 0, 0, 0, 0);                \
+     (void)0;                                                    \
+   } while (0)
+
+#define ANNOTATE_HAPPENS_AFTER(obj) \
+   do {                                                           \
+     unsigned long _qzz_res;                                      \
+     VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0,                      \
+                                _VG_USERREQ__HG_USERSO_RECV_POST, \
+                                obj, 0, 0, 0, 0);                 \
+     (void)0;                                                     \
+   } while (0)
+
+
+/* ----------------------------------------------------------------
+   Memory publishing.  The TSan sources say:
+
+     Report that the bytes in the range [pointer, pointer+size) are about
+     to be published safely. The race checker will create a happens-before
+     arc from the call ANNOTATE_PUBLISH_MEMORY_RANGE(pointer, size) to
+     subsequent accesses to this memory.
+
+   I'm not sure I understand what this means exactly, nor whether it
+   is relevant for a pure h-b detector.  Leaving unimplemented for
+   now.
+   ----------------------------------------------------------------
+*/
+#define ANNOTATE_PUBLISH_MEMORY_RANGE(pointer, size) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_PUBLISH_MEMORY_RANGE")
+
+
+/* ----------------------------------------------------------------
+   TSan sources say:
+   
+     Instruct the tool to create a happens-before arc between
+     MU->Unlock() and MU->Lock().  This annotation may slow down the
+     race detector; normally it is used only when it would be
+     difficult to annotate each of the mutex's critical sections
+     individually using the annotations above.
+
+   If MU is a posix pthread_mutex_t then Helgrind will do this anyway.
+   In any case, leave as unimp for now.  I'm unsure about the intended
+   behaviour.
+   ---------------------------------------------------------------- 
+*/
+#define ANNOTATE_MUTEX_IS_USED_AS_CONDVAR(mu) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_MUTEX_IS_USED_AS_CONDVAR")
+
+
+/* ----------------------------------------------------------------
+   TSan sources say:
+   
+     Annotations useful when defining memory allocators, or when
+     memory that was protected in one way starts to be protected in
+     another.
+
+     Report that a new memory at "address" of size "size" has been
+     allocated.  This might be used when the memory has been retrieved
+     from a free list and is about to be reused, or when a the locking
+     discipline for a variable changes.
+
+   AFAICS this is the same as VALGRIND_HG_CLEAN_MEMORY.
+   ---------------------------------------------------------------- 
+*/
+#define ANNOTATE_NEW_MEMORY(address, size) \
+   VALGRIND_HG_CLEAN_MEMORY((address), (size))
+
+
+/* ----------------------------------------------------------------
+   TSan sources say:
+
+     Annotations useful when defining FIFO queues that transfer data
+     between threads.
+
+   All unimplemented.  Am not claiming to understand this (yet).
+   ---------------------------------------------------------------- 
+*/
+
+/* Report that the producer-consumer queue object at address PCQ has
+   been created.  The ANNOTATE_PCQ_* annotations should be used only
+   for FIFO queues.  For non-FIFO queues use ANNOTATE_HAPPENS_BEFORE
+   (for put) and ANNOTATE_HAPPENS_AFTER (for get). */
+#define ANNOTATE_PCQ_CREATE(pcq) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_PCQ_CREATE")
+
+/* Report that the queue at address PCQ is about to be destroyed. */
+#define ANNOTATE_PCQ_DESTROY(pcq) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_PCQ_DESTROY")
+
+/* Report that we are about to put an element into a FIFO queue at
+   address PCQ. */
+#define ANNOTATE_PCQ_PUT(pcq) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_PCQ_PUT")
+
+/* Report that we've just got an element from a FIFO queue at address
+   PCQ. */
+#define ANNOTATE_PCQ_GET(pcq) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_PCQ_GET")
+
+
+/* ----------------------------------------------------------------
+   Annotations that suppress errors.  It is usually better to express
+   the program's synchronization using the other annotations, but
+   these can be used when all else fails.
+
+   Currently these are all unimplemented.  I can't think of a simple
+   way to implement them without at least some performance overhead.
+   ----------------------------------------------------------------
+*/
+
+/* Report that we may have a benign race on ADDRESS.  Insert at the
+   point where ADDRESS has been allocated, preferably close to the
+   point where the race happens.  See also ANNOTATE_BENIGN_RACE_STATIC.
+
+   XXX: what's this actually supposed to do?  And what's the type of
+   DESCRIPTION?  When does the annotation stop having an effect?
+*/
+#define ANNOTATE_BENIGN_RACE(address, description) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_BENIGN_RACE")
+   
+
+/* Request the analysis tool to ignore all reads in the current thread
+   until ANNOTATE_IGNORE_READS_END is called.  Useful to ignore
+   intentional racey reads, while still checking other reads and all
+   writes. */
+#define ANNOTATE_IGNORE_READS_BEGIN() \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_IGNORE_READS_BEGIN")
+
+/* Stop ignoring reads. */
+#define ANNOTATE_IGNORE_READS_END() \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_IGNORE_READS_END")
+
+/* Similar to ANNOTATE_IGNORE_READS_BEGIN, but ignore writes. */
+#define ANNOTATE_IGNORE_WRITES_BEGIN() \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_IGNORE_WRITES_BEGIN")
+
+/* Stop ignoring writes. */
+#define ANNOTATE_IGNORE_WRITES_END() \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_IGNORE_WRITES_END")
+
+/* Start ignoring all memory accesses (reads and writes). */
+#define ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN() \
+   do { \
+      ANNOTATE_IGNORE_READS_BEGIN(); \
+      ANNOTATE_IGNORE_WRITES_BEGIN(); \
+   } while (0)
+
+/* Stop ignoring all memory accesses. */
+#define ANNOTATE_IGNORE_READS_AND_WRITES_END() \
+   do { \
+      ANNOTATE_IGNORE_WRITES_END(); \
+      ANNOTATE_IGNORE_READS_END(); \
+   } while (0)
+
+
+/* ----------------------------------------------------------------
+   Annotations useful for debugging.
+
+   Again, so for unimplemented, partly for performance reasons.
+   ----------------------------------------------------------------
+*/
+
+/* Request to trace every access to ADDRESS. */
+#define ANNOTATE_TRACE_MEMORY(address) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_TRACE_MEMORY")
+
+/* Report the current thread name to a race detector. */
+#define ANNOTATE_THREAD_NAME(name) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_THREAD_NAME")
+
+
+/* ----------------------------------------------------------------
+   Annotations for describing behaviour of user-implemented lock
+   primitives.  In all cases, the LOCK argument is a completely
+   arbitrary machine word (unsigned long, or void*) and can be any
+   value which gives a unique identity to the lock objects being
+   modelled.
+
+   We just pretend they're ordinary posix rwlocks.  That'll probably
+   give some rather confusing wording in error messages, claiming that
+   the arbitrary LOCK values are pthread_rwlock_t*'s, when in fact
+   they are not.  Ah well.
+   ----------------------------------------------------------------
+*/
+/* Report that a lock has just been created at address LOCK. */
+#define ANNOTATE_RWLOCK_CREATE(lock) \
+   do {                                                          \
+     unsigned long _qzz_res;                                     \
+     VALGRIND_DO_CLIENT_REQUEST(                                 \
+        _qzz_res, 0, _VG_USERREQ__HG_PTHREAD_RWLOCK_INIT_POST,   \
+        lock, 0, 0, 0, 0                                         \
+     );                                                          \
+     (void)0;                                                    \
+   } while(0)
+    
+/* Report that the lock at address LOCK is about to be destroyed. */
+#define ANNOTATE_RWLOCK_DESTROY(lock) \
+   do {                                                          \
+     unsigned long _qzz_res;                                     \
+     VALGRIND_DO_CLIENT_REQUEST(                                 \
+        _qzz_res, 0, _VG_USERREQ__HG_PTHREAD_RWLOCK_DESTROY_PRE, \
+        lock, 0, 0, 0, 0                                         \
+     );                                                          \
+     (void)0;                                                    \
+   } while(0)
+
+/* Report that the lock at address LOCK has just been acquired.
+   is_w=1 for writer lock, is_w=0 for reader lock. */
+#define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) \
+   do {                                                          \
+     unsigned long _qzz_res;                                     \
+     VALGRIND_DO_CLIENT_REQUEST(                                 \
+        _qzz_res, 0, _VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST,   \
+        lock, is_w ? 1 : 0, 0, 0, 0                              \
+     );                                                          \
+     (void)0;                                                    \
+   } while(0)
+
+/* Report that the lock at address LOCK is about to be released. */
+  #define ANNOTATE_RWLOCK_RELEASED(lock, is_w) \
+   do {                                                          \
+     unsigned long _qzz_res;                                     \
+     VALGRIND_DO_CLIENT_REQUEST(                                 \
+        _qzz_res, 0, _VG_USERREQ__HG_PTHREAD_RWLOCK_UNLOCK_PRE,  \
+        lock, 0, 0, 0, 0                                         \
+     );                                                          \
+     (void)0;                                                    \
+   } while(0)
+
+
+/* ----------------------------------------------------------------
+   Annotations useful for testing race detectors.
+   ----------------------------------------------------------------
+*/
+
+/* Report that we expect a race on the variable at ADDRESS.  Use only
+   in unit tests for a race detector. */
+#define ANNOTATE_EXPECT_RACE(address, description) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_EXPECT_RACE")
+
+/* A no-op. Insert where you like to test the interceptors. */
+#define ANNOTATE_NO_OP(arg) \
+   _HG_CLIENTREQ_UNIMP("ANNOTATE_NO_OP")
+
+
 #endif /* __HELGRIND_H */
diff --git a/helgrind/hg_main.c b/helgrind/hg_main.c
index e5ba438..ee72e30 100644
--- a/helgrind/hg_main.c
+++ b/helgrind/hg_main.c
@@ -2966,6 +2966,107 @@
 }
 
 
+/* ----------------------------------------------------- */
+/* ----- events to do with user-specified HB edges ----- */
+/* ----------------------------------------------------- */
+
+/* A mapping from arbitrary UWord tag to the SO associated with it.
+   The UWord tags are meaningless to us, interpreted only by the
+   user. */
+
+
+
+/* UWord -> SO* */
+static WordFM* map_usertag_to_SO = NULL;
+
+static void map_usertag_to_SO_INIT ( void ) {
+   if (UNLIKELY(map_usertag_to_SO == NULL)) {
+      map_usertag_to_SO = VG_(newFM)( HG_(zalloc),
+                                      "hg.mutS.1", HG_(free), NULL );
+      tl_assert(map_usertag_to_SO != NULL);
+   }
+}
+
+static SO* map_usertag_to_SO_lookup_or_alloc ( UWord usertag ) {
+   UWord key, val;
+   map_usertag_to_SO_INIT();
+   if (VG_(lookupFM)( map_usertag_to_SO, &key, &val, usertag )) {
+      tl_assert(key == (UWord)usertag);
+      return (SO*)val;
+   } else {
+      SO* so = libhb_so_alloc();
+      VG_(addToFM)( map_usertag_to_SO, usertag, (UWord)so );
+      return so;
+   }
+}
+
+// If it's ever needed (XXX check before use)
+//static void map_usertag_to_SO_delete ( UWord usertag ) {
+//   UWord keyW, valW;
+//   map_usertag_to_SO_INIT();
+//   if (VG_(delFromFM)( map_usertag_to_SO, &keyW, &valW, usertag )) {
+//      SO* so = (SO*)valW;
+//      tl_assert(keyW == usertag);
+//      tl_assert(so);
+//      libhb_so_dealloc(so);
+//   }
+//}
+
+
+static
+void evh__HG_USERSO_SEND_PRE ( ThreadId tid, UWord usertag )
+{
+   /* TID is just about to notionally sent a message on a notional
+      abstract synchronisation object whose identity is given by
+      USERTAG.  Bind USERTAG to a real SO if it is not already so
+      bound, and do a 'strong send' on the SO.  This is later used by
+      other thread(s) which successfully 'receive' from the SO,
+      thereby acquiring a dependency on this signalling event. */
+   Thread* thr;
+   SO*     so;
+
+   if (SHOW_EVENTS >= 1)
+      VG_(printf)("evh__HG_USERSO_SEND_PRE(ctid=%d, usertag=%#lx)\n", 
+                  (Int)tid, usertag );
+
+   thr = map_threads_maybe_lookup( tid );
+   tl_assert(thr); /* cannot fail - Thread* must already exist */
+
+   so = map_usertag_to_SO_lookup_or_alloc( usertag );
+   tl_assert(so);
+
+   libhb_so_send( thr->hbthr, so, True/*strong_send*/ );
+}
+
+static
+void evh__HG_USERSO_RECV_POST ( ThreadId tid, UWord usertag )
+{
+   /* TID has just notionally received a message from a notional
+      abstract synchronisation object whose identity is given by
+      USERTAG.  Bind USERTAG to a real SO if it is not already so
+      bound.  If the SO has at some point in the past been 'sent' on,
+      to a 'strong receive' on it, thereby acquiring a dependency on
+      the sender. */
+   Thread* thr;
+   SO*     so;
+
+   if (SHOW_EVENTS >= 1)
+      VG_(printf)("evh__HG_USERSO_RECV_POST(ctid=%d, usertag=%#lx)\n", 
+                  (Int)tid, usertag );
+
+   thr = map_threads_maybe_lookup( tid );
+   tl_assert(thr); /* cannot fail - Thread* must already exist */
+
+   so = map_usertag_to_SO_lookup_or_alloc( usertag );
+   tl_assert(so);
+
+   /* Acquire a dependency on it.  If the SO has never so far been
+      sent on, then libhb_so_recv will do nothing.  So we're safe
+      regardless of SO's history. */
+   libhb_so_recv( thr->hbthr, so, True/*strong_recv*/ );
+}
+
+
 /*--------------------------------------------------------------*/
 /*--- Lock acquisition order monitoring                      ---*/
 /*--------------------------------------------------------------*/
@@ -4242,6 +4343,30 @@
          evh__HG_PTHREAD_SPIN_DESTROY_PRE( tid, (void*)args[1] );
          break;
 
+      case _VG_USERREQ__HG_CLIENTREQ_UNIMP: {
+         /* char* who */
+         HChar*  who = (HChar*)args[1];
+         HChar   buf[50 + 50];
+         Thread* thr = map_threads_maybe_lookup( tid );
+         tl_assert( thr ); /* I must be mapped */
+         tl_assert( who );
+         tl_assert( VG_(strlen)(who) <= 50 );
+         VG_(sprintf)(buf, "Unimplemented client request macro \"%s\"", who );
+         /* record_error_Misc strdup's buf, so this is safe: */
+         HG_(record_error_Misc)( thr, buf );
+         break;
+      }
+
+      case _VG_USERREQ__HG_USERSO_SEND_PRE:
+         /* UWord arbitrary-SO-tag */
+         evh__HG_USERSO_SEND_PRE( tid, args[1] );
+         break;
+
+      case _VG_USERREQ__HG_USERSO_RECV_POST:
+         /* UWord arbitrary-SO-tag */
+         evh__HG_USERSO_RECV_POST( tid, args[1] );
+         break;
+
       default:
          /* Unhandled Helgrind client request! */
          tl_assert2(0, "unhandled Helgrind client request 0x%lx",