Below is a patch for "src/libexec/rtld-elf" which should fix the
assert failures in wine. I'd appreciate hearing from anybody who
tests this with multithreaded packages such as wine, JDK, Mozilla,
and linuxthreads.
Just a reminder -- be extra careful when messing with the dynamic
linker. It's easy to paint yourself into a corner if it's broken
badly. Make a backup copy of your current working dynamic linker
(/usr/libexec/ld-elf.so.1) before installing the experimental version.
Then if things fall apart you can recover with something like this:
cd /usr/libexec
chflags 0 ld-elf.so.1*
mv ld-elf.so.1.good ld-elf.so.1
Thanks in advance for any testing you folks can make time to do.
John
Index: Makefile
===================================================================
RCS file: /home/ncvs/src/libexec/rtld-elf/Makefile,v
retrieving revision 1.10
diff -u -r1.10 Makefile
--- Makefile 2000/01/29 03:16:54 1.10
+++ Makefile 2000/03/01 02:39:13
@@ -3,7 +3,7 @@
#
MAINTAINER= jdp
PROG= ld-elf.so.1
-SRCS= rtld_start.S rtld.c lockdflt.c map_object.c malloc.c \
+SRCS= rtld_start.S rtld.c map_object.c malloc.c \
xmalloc.c debug.c reloc.c
MAN1= rtld.1
CFLAGS+= -Wall -DFREEBSD_ELF -I${.CURDIR}/${MACHINE_ARCH} -I${.CURDIR}
Index: lockdflt.c
===================================================================
RCS file: lockdflt.c
diff -N lockdflt.c
--- /tmp/cvscXuMc22613 Sun Mar 5 17:48:37 2000
+++ /dev/null Sun Mar 5 02:02:18 2000
@@ -1,89 +0,0 @@
-/*-
- * Copyright 1999, 2000 John D. Polstra.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * $FreeBSD: src/libexec/rtld-elf/lockdflt.c,v 1.4 2000/01/25 01:32:56 jdp Exp $
- */
-
-/*
- * Default thread locking implementation for the dynamic linker. It
- * is used until the client registers a different implementation with
- * dllockinit(). The default implementation does mutual exclusion by
- * blocking almost all signals. This is based on the observation that
- * most userland thread packages use signals to support preemption.
- */
-
-#include <dlfcn.h>
-#include <signal.h>
-#include <stdlib.h>
-
-#include "debug.h"
-#include "rtld.h"
-
-typedef struct Struct_LockDflt {
- sigset_t lock_mask;
- sigset_t old_mask;
- int depth;
-} LockDflt;
-
-void
-lockdflt_acquire(void *lock)
-{
- LockDflt *l = (LockDflt *)lock;
- sigprocmask(SIG_BLOCK, &l->lock_mask, &l->old_mask);
- assert(l->depth == 0);
- l->depth++;
-}
-
-void *
-lockdflt_create(void *context)
-{
- LockDflt *l;
-
- l = NEW(LockDflt);
- l->depth = 0;
- sigfillset(&l->lock_mask);
- sigdelset(&l->lock_mask, SIGTRAP);
- sigdelset(&l->lock_mask, SIGABRT);
- sigdelset(&l->lock_mask, SIGBUS);
- sigdelset(&l->lock_mask, SIGSEGV);
- sigdelset(&l->lock_mask, SIGKILL);
- sigdelset(&l->lock_mask, SIGSTOP);
- return l;
-}
-
-void
-lockdflt_destroy(void *lock)
-{
- LockDflt *l = (LockDflt *)lock;
- free(l);
-}
-
-void
-lockdflt_release(void *lock)
-{
- LockDflt *l = (LockDflt *)lock;
- assert(l->depth == 1);
- l->depth--;
- sigprocmask(SIG_SETMASK, &l->old_mask, NULL);
-}
Index: rtld.c
===================================================================
RCS file: /home/ncvs/src/libexec/rtld-elf/rtld.c,v
retrieving revision 1.43
diff -u -r1.43 rtld.c
--- rtld.c 2000/01/29 01:26:59 1.43
+++ rtld.c 2000/03/06 01:44:11
@@ -61,6 +61,9 @@
typedef struct Struct_LockInfo {
void *context; /* Client context for creating locks */
void *thelock; /* The one big lock */
+ /* Debugging aids. */
+ volatile int rcount; /* Number of readers holding lock */
+ volatile int wcount; /* Number of writers holding lock */
/* Methods */
void (*rlock_acquire)(void *lock);
void (*wlock_acquire)(void *lock);
@@ -70,6 +73,16 @@
} LockInfo;
/*
+ * This structure provides a reentrant way to keep a list of objects and
+ * check which ones have already been processed in some way.
+ */
+typedef struct Struct_DoneList {
+ Obj_Entry **objs; /* Array of object pointers */
+ unsigned int num_alloc; /* Allocated size of the array */
+ unsigned int num_used; /* Number of array slots used */
+} DoneList;
+
+/*
* Function declarations.
*/
static const char *basename(const char *);
@@ -77,6 +90,7 @@
static void digest_dynamic(Obj_Entry *);
static Obj_Entry *digest_phdr(const Elf_Phdr *, int, caddr_t, const char *);
static Obj_Entry *dlcheck(void *);
+static bool donelist_check(DoneList *, Obj_Entry *);
static char *find_library(const char *, const Obj_Entry *);
static void funclist_call(Funclist *);
static void funclist_clear(Funclist *);
@@ -85,7 +99,7 @@
static void funclist_push_tail(Funclist *, InitFunc);
static const char *gethints(void);
static void init_dag(Obj_Entry *);
-static void init_dag1(Obj_Entry *root, Obj_Entry *obj);
+static void init_dag1(Obj_Entry *root, Obj_Entry *obj, DoneList *);
static void init_rtld(caddr_t);
static bool is_exported(const Elf_Sym *);
static void linkmap_add(Obj_Entry *);
@@ -93,6 +107,7 @@
static int load_needed_objects(Obj_Entry *);
static int load_preload_objects(void);
static Obj_Entry *load_object(char *);
+static void lock_check(void);
static void lock_nop(void *);
static Obj_Entry *obj_from_addr(const void *);
static void objlist_add(Objlist *, Obj_Entry *);
@@ -104,7 +119,7 @@
static char *search_library_path(const char *, const char *);
static void set_program_var(const char *, const void *);
static const Elf_Sym *symlook_list(const char *, unsigned long,
- Objlist *, const Obj_Entry **, bool in_plt);
+ Objlist *, const Obj_Entry **, bool in_plt, DoneList *);
static void trace_loaded_objects(Obj_Entry *obj);
static void unload_object(Obj_Entry *);
static void unref_dag(Obj_Entry *);
@@ -128,7 +143,7 @@
static Obj_Entry **obj_tail; /* Link field of last object in list */
static Obj_Entry *obj_main; /* The main program shared object */
static Obj_Entry obj_rtld; /* The dynamic linker shared object */
-static unsigned long curmark; /* Current mark value */
+static unsigned int obj_count; /* Number of objects in obj_list */
static Objlist list_global = /* Objects dlopened with RTLD_GLOBAL */
STAILQ_HEAD_INITIALIZER(list_global);
@@ -167,21 +182,44 @@
char *__progname;
char **environ;
+/*
+ * Fill in a DoneList with an allocation large enough to hold all of
+ * the currently-loaded objects. Keep this as a macro since it calls
+ * alloca and we want that to occur within the scope of the caller.
+ */
+#define donelist_init(dlp) \
+ ((dlp)->objs = alloca(obj_count * sizeof (dlp)->objs[0]), \
+ assert((dlp)->objs != NULL), \
+ (dlp)->num_alloc = obj_count, \
+ (dlp)->num_used = 0)
+
static __inline void
rlock_acquire(void)
{
lockinfo.rlock_acquire(lockinfo.thelock);
+ atomic_incr_int(&lockinfo.rcount);
+ lock_check();
}
static __inline void
wlock_acquire(void)
{
lockinfo.wlock_acquire(lockinfo.thelock);
+ atomic_incr_int(&lockinfo.wcount);
+ lock_check();
+}
+
+static __inline void
+rlock_release(void)
+{
+ atomic_decr_int(&lockinfo.rcount);
+ lockinfo.lock_release(lockinfo.thelock);
}
static __inline void
-lock_release(void)
+wlock_release(void)
{
+ atomic_decr_int(&lockinfo.wcount);
lockinfo.lock_release(lockinfo.thelock);
}
@@ -316,6 +354,7 @@
/* Link the main program into the list of objects. */
*obj_tail = obj_main;
obj_tail = &obj_main->next;
+ obj_count++;
obj_main->refcount++;
/* Initialize a fake symbol for resolving undefined weak references. */
@@ -366,7 +405,7 @@
funclist_call(&initlist);
wlock_acquire();
funclist_clear(&initlist);
- lock_release();
+ wlock_release();
dbg("transferring control to program entry point = %p", obj_main->entry);
@@ -385,7 +424,7 @@
Elf_Addr *where;
Elf_Addr target;
- wlock_acquire();
+ rlock_acquire();
if (obj->pltrel)
rel = (const Elf_Rel *) ((caddr_t) obj->pltrel + reloff);
else
@@ -403,7 +442,7 @@
(void *)target, basename(defobj->path));
reloc_jmpslot(where, target);
- lock_release();
+ rlock_release();
return target;
}
@@ -671,6 +710,28 @@
}
/*
+ * If the given object is already in the donelist, return true. Otherwise
+ * add the object to the list and return false.
+ */
+static bool
+donelist_check(DoneList *dlp, Obj_Entry *obj)
+{
+ unsigned int i;
+
+ for (i = 0; i < dlp->num_used; i++)
+ if (dlp->objs[i] == obj)
+ return true;
+ /*
+ * Our donelist allocation should always be sufficient. But if a
+ * threads package hasn't set up its locking properly, more shared
+ * objects could have been loaded since we allocated the list.
+ */
+ if (dlp->num_used < dlp->num_alloc)
+ dlp->objs[dlp->num_used++] = obj;
+ return false;
+}
+
+/*
* Hash function for symbol table lookup. Don't even think about changing
* this. It is specified by the System V ABI.
*/
@@ -741,6 +802,7 @@
find_symdef(unsigned long symnum, Obj_Entry *refobj,
const Obj_Entry **defobj_out, bool in_plt)
{
+ DoneList donelist;
const Elf_Sym *ref;
const Elf_Sym *def;
const Elf_Sym *symp;
@@ -755,11 +817,11 @@
hash = elf_hash(name);
def = NULL;
defobj = NULL;
- curmark++;
+ donelist_init(&donelist);
- if (refobj->symbolic) { /* Look first in the referencing object */
+ /* Look first in the referencing object if linked symbolically. */
+ if (refobj->symbolic && !donelist_check(&donelist, refobj)) {
symp = symlook_obj(name, hash, refobj, in_plt);
- refobj->mark = curmark;
if (symp != NULL) {
def = symp;
defobj = refobj;
@@ -768,7 +830,7 @@
/* Search all objects loaded at program start up. */
if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) {
- symp = symlook_list(name, hash, &list_main, &obj, in_plt);
+ symp = symlook_list(name, hash, &list_main, &obj, in_plt, &donelist);
if (symp != NULL &&
(def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) {
def = symp;
@@ -780,7 +842,8 @@
STAILQ_FOREACH(elm, &refobj->dldags, link) {
if (def != NULL && ELF_ST_BIND(def->st_info) != STB_WEAK)
break;
- symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt);
+ symp = symlook_list(name, hash, &elm->obj->dagmembers, &obj, in_plt,
+ &donelist);
if (symp != NULL &&
(def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) {
def = symp;
@@ -790,7 +853,7 @@
/* Search all RTLD_GLOBAL objects. */
if (def == NULL || ELF_ST_BIND(def->st_info) == STB_WEAK) {
- symp = symlook_list(name, hash, &list_global, &obj, in_plt);
+ symp = symlook_list(name, hash, &list_global, &obj, in_plt, &donelist);
if (symp != NULL &&
(def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK)) {
def = symp;
@@ -919,23 +982,24 @@
static void
init_dag(Obj_Entry *root)
{
- curmark++;
- init_dag1(root, root);
+ DoneList donelist;
+
+ donelist_init(&donelist);
+ init_dag1(root, root, &donelist);
}
static void
-init_dag1(Obj_Entry *root, Obj_Entry *obj)
+init_dag1(Obj_Entry *root, Obj_Entry *obj, DoneList *dlp)
{
const Needed_Entry *needed;
- if (obj->mark == curmark)
+ if (donelist_check(dlp, obj))
return;
- obj->mark = curmark;
objlist_add(&obj->dldags, root);
objlist_add(&root->dagmembers, obj);
for (needed = obj->needed; needed != NULL; needed = needed->next)
if (needed->obj != NULL)
- init_dag1(root, needed->obj);
+ init_dag1(root, needed->obj, dlp);
}
/*
@@ -971,6 +1035,7 @@
*/
obj_list = &obj_rtld;
obj_tail = &obj_rtld.next;
+ obj_count = 1;
relocate_objects(&obj_rtld, true);
}
@@ -978,6 +1043,7 @@
/* Make the object list empty again. */
obj_list = NULL;
obj_tail = &obj_list;
+ obj_count = 0;
/* Replace the path with a dynamically allocated copy. */
obj_rtld.path = xstrdup(obj_rtld.path);
@@ -1118,6 +1184,7 @@
*obj_tail = obj;
obj_tail = &obj->next;
+ obj_count++;
linkmap_add(obj); /* for GDB */
dbg(" %p .. %p: %s", obj->mapbase,
@@ -1131,6 +1198,26 @@
return obj;
}
+/*
+ * Check for locking violations and die if one is found.
+ */
+static void
+lock_check(void)
+{
+ int rcount, wcount;
+
+ rcount = lockinfo.rcount;
+ wcount = lockinfo.wcount;
+ assert(rcount >= 0);
+ assert(wcount >= 0);
+ if (wcount > 1 || (wcount != 0 && rcount != 0)) {
+ _rtld_error("Application locking error: %d readers and %d writers"
+ " in dynamic linker. See DLLOCKINIT(3) in manual pages.",
+ rcount, wcount);
+ die();
+ }
+}
+
static void
lock_nop(void *lock)
{
@@ -1317,7 +1404,7 @@
wlock_acquire();
root = dlcheck(handle);
if (root == NULL) {
- lock_release();
+ wlock_release();
return -1;
}
@@ -1336,7 +1423,7 @@
if (obj->refcount == 0 && obj->fini != NULL)
funclist_push_tail(&finilist, obj->fini);
- lock_release();
+ wlock_release();
funclist_call(&finilist);
wlock_acquire();
funclist_clear(&finilist);
@@ -1346,7 +1433,7 @@
unload_object(root);
GDB_STATE(RT_CONSISTENT);
}
- lock_release();
+ wlock_release();
return 0;
}
@@ -1373,10 +1460,8 @@
if (lock_create == NULL) {
is_dflt = true;
context = NULL;
- lock_create = lockdflt_create;
- rlock_acquire = wlock_acquire = lockdflt_acquire;
- lock_release = lockdflt_release;
- lock_destroy = lockdflt_destroy;
+ rlock_acquire = wlock_acquire = lock_release = lock_nop;
+ lock_destroy = NULL;
context_destroy = NULL;
}
@@ -1394,17 +1479,15 @@
/*
* Make sure the shared objects containing the locking methods are
* fully bound, to avoid infinite recursion when they are called
- * from the lazy binding code.
+ * from the lazy binding code. Then allocate the lock we will use.
*/
if (!is_dflt) {
prebind((void *)rlock_acquire);
prebind((void *)wlock_acquire);
prebind((void *)lock_release);
+ lockinfo.thelock = lock_create(lockinfo.context);
}
- /* Allocate our lock. */
- lockinfo.thelock = lock_create(lockinfo.context);
-
/* Record the new method information. */
lockinfo.context = context;
lockinfo.rlock_acquire = rlock_acquire;
@@ -1482,11 +1565,11 @@
GDB_STATE(RT_CONSISTENT);
/* Call the init functions with no locks held. */
- lock_release();
+ wlock_release();
funclist_call(&initlist);
wlock_acquire();
funclist_clear(&initlist);
- lock_release();
+ wlock_release();
return obj;
}
@@ -1502,14 +1585,14 @@
def = NULL;
defobj = NULL;
- wlock_acquire();
+ rlock_acquire();
if (handle == NULL || handle == RTLD_NEXT) {
void *retaddr;
retaddr = __builtin_return_address(0); /* __GNUC__ only */
if ((obj = obj_from_addr(retaddr)) == NULL) {
_rtld_error("Cannot determine caller's shared object");
- lock_release();
+ rlock_release();
return NULL;
}
if (handle == NULL) { /* Just the caller's shared object. */
@@ -1525,14 +1608,17 @@
}
} else {
if ((obj = dlcheck(handle)) == NULL) {
- lock_release();
+ rlock_release();
return NULL;
}
if (obj->mainprog) {
+ DoneList donelist;
+
/* Search main program and all libraries loaded by it. */
- curmark++;
- def = symlook_list(name, hash, &list_main, &defobj, true);
+ donelist_init(&donelist);
+ def = symlook_list(name, hash, &list_main, &defobj, true,
+ &donelist);
} else {
/*
* XXX - This isn't correct. The search should include the whole
@@ -1544,12 +1630,12 @@
}
if (def != NULL) {
- lock_release();
+ rlock_release();
return defobj->relocbase + def->st_value;
}
_rtld_error("Undefined symbol \"%s\"", name);
- lock_release();
+ rlock_release();
return NULL;
}
@@ -1561,11 +1647,11 @@
void *symbol_addr;
unsigned long symoffset;
- wlock_acquire();
+ rlock_acquire();
obj = obj_from_addr(addr);
if (obj == NULL) {
_rtld_error("No shared object contains address");
- lock_release();
+ rlock_release();
return 0;
}
info->dli_fname = obj->path;
@@ -1604,7 +1690,7 @@
if (info->dli_saddr == addr)
break;
}
- lock_release();
+ rlock_release();
return 1;
}
@@ -1695,7 +1781,7 @@
static const Elf_Sym *
symlook_list(const char *name, unsigned long hash, Objlist *objlist,
- const Obj_Entry **defobj_out, bool in_plt)
+ const Obj_Entry **defobj_out, bool in_plt, DoneList *dlp)
{
const Elf_Sym *symp;
const Elf_Sym *def;
@@ -1705,9 +1791,8 @@
def = NULL;
defobj = NULL;
STAILQ_FOREACH(elm, objlist, link) {
- if (elm->obj->mark == curmark)
+ if (donelist_check(dlp, elm->obj))
continue;
- elm->obj->mark = curmark;
if ((symp = symlook_obj(name, hash, elm->obj, in_plt)) != NULL) {
if (def == NULL || ELF_ST_BIND(symp->st_info) != STB_WEAK) {
def = symp;
@@ -1877,6 +1962,7 @@
munmap(obj->mapbase, obj->mapsize);
linkmap_delete(obj);
*linkp = obj->next;
+ obj_count--;
obj_free(obj);
} else
linkp = &obj->next;
Index: rtld.h
===================================================================
RCS file: /home/ncvs/src/libexec/rtld-elf/rtld.h,v
retrieving revision 1.15
diff -u -r1.15 rtld.h
--- rtld.h 2000/01/29 01:26:59 1.15
+++ rtld.h 2000/03/01 02:39:27
@@ -149,7 +149,6 @@
Objlist dagmembers; /* DAG has these members (%) */
dev_t dev; /* Object's filesystem's device */
ino_t ino; /* Object's inode number */
- unsigned long mark; /* Set to "curmark" to avoid repeat visits */
} Obj_Entry;
#define RTLD_MAGIC 0xd550b87a
@@ -170,10 +169,6 @@
const Elf_Sym *find_symdef(unsigned long, Obj_Entry *, const Obj_Entry **,
bool);
void init_pltgot(Obj_Entry *);
-void lockdflt_acquire(void *);
-void *lockdflt_create(void *);
-void lockdflt_destroy(void *);
-void lockdflt_release(void *);
void obj_free(Obj_Entry *);
Obj_Entry *obj_new(void);
int reloc_non_plt(Obj_Entry *, Obj_Entry *);
Index: alpha/rtld_machdep.h
===================================================================
RCS file: /home/ncvs/src/libexec/rtld-elf/alpha/rtld_machdep.h,v
retrieving revision 1.3
diff -u -r1.3 rtld_machdep.h
--- alpha/rtld_machdep.h 1999/08/28 00:10:13 1.3
+++ alpha/rtld_machdep.h 2000/03/06 01:37:22
@@ -34,4 +34,8 @@
void reloc_jmpslot(Elf_Addr *, Elf_Addr);
+/* Atomically increment / decrement an int. */
+void atomic_incr_int(volatile int *);
+void atomic_decr_int(volatile int *);
+
#endif
Index: alpha/rtld_start.S
===================================================================
RCS file: /home/ncvs/src/libexec/rtld-elf/alpha/rtld_start.S,v
retrieving revision 1.3
diff -u -r1.3 rtld_start.S
--- alpha/rtld_start.S 1999/08/28 00:10:13 1.3
+++ alpha/rtld_start.S 2000/03/06 01:37:58
@@ -166,6 +166,24 @@
jmp $31, ($27)
.end _rtld_bind_start
+/* Atomically increment an int. */
+LEAF(atomic_incr_int, 1)
+0: ldl_l t0, 0(a0)
+ addq t0, 1, t0
+ stl_c t0, 0(a0)
+ beq t0, 1f
+ mb
+ RET
+1: br 0b
+ END(atomic_incr_int)
-
-
+/* Atomically decrement an int. */
+LEAF(atomic_decr_int, 1)
+0: ldl_l t0, 0(a0)
+ subq t0, 1, t0
+ stl_c t0, 0(a0)
+ beq t0, 1f
+ mb
+ RET
+1: br 0b
+ END(atomic_decr_int)
Index: i386/rtld_machdep.h
===================================================================
RCS file: /home/ncvs/src/libexec/rtld-elf/i386/rtld_machdep.h,v
retrieving revision 1.3
diff -u -r1.3 rtld_machdep.h
--- i386/rtld_machdep.h 1999/08/28 00:10:14 1.3
+++ i386/rtld_machdep.h 2000/03/06 01:40:47
@@ -41,4 +41,18 @@
(*(Elf_Addr *)(where) = (Elf_Addr)(target)); \
} while (0)
+/* Atomically increment / decrement an int. */
+
+static __inline void
+atomic_incr_int(volatile int *p)
+{
+ __asm __volatile("lock; incl %0" : "=m" (*p) : "0" (*p));
+}
+
+static __inline void
+atomic_decr_int(volatile int *p)
+{
+ __asm __volatile("lock; decl %0" : "=m" (*p) : "0" (*p));
+}
+
#endif
To Unsubscribe: send mail to [EMAIL PROTECTED]
with "unsubscribe freebsd-current" in the body of the message