aboutsummaryrefslogtreecommitdiff
path: root/elf
diff options
context:
space:
mode:
Diffstat (limited to 'elf')
-rw-r--r--elf/dl-deps.c241
-rw-r--r--elf/dl-load.c47
-rw-r--r--elf/dl-lookup.c11
-rw-r--r--elf/dl-runtime.c26
-rw-r--r--elf/dl-version.c15
-rw-r--r--elf/elf.h7
-rw-r--r--elf/rtld.c139
7 files changed, 273 insertions, 213 deletions
diff --git a/elf/dl-deps.c b/elf/dl-deps.c
index 3fbb3db..38db767 100644
--- a/elf/dl-deps.c
+++ b/elf/dl-deps.c
@@ -28,6 +28,10 @@
is signaled by the AUXTAG entry in l_info. */
#define AUXTAG (DT_NUM + DT_PROCNUM + DT_VERSIONTAGNUM \
+ DT_EXTRATAGIDX (DT_AUXILIARY))
+/* Whether an shared object references one or more auxiliary objects
+ is signaled by the AUXTAG entry in l_info. */
+#define FILTERTAG (DT_NUM + DT_PROCNUM + DT_VERSIONTAGNUM \
+ + DT_EXTRATAGIDX (DT_FILTER))
/* When loading auxiliary objects we must ignore errors. It's ok if
@@ -138,7 +142,7 @@ _dl_map_object_deps (struct link_map *map,
{
struct link_map *l = runp->map;
- if (l->l_info[AUXTAG] || l->l_info[DT_NEEDED])
+ if (l->l_info[AUXTAG] || l->l_info[FILTERTAG] || l->l_info[DT_NEEDED])
{
const char *strtab = ((void *) l->l_addr
+ l->l_info[DT_STRTAB]->d_un.d_ptr);
@@ -190,119 +194,87 @@ _dl_map_object_deps (struct link_map *map,
dep->l_reserved = 1;
}
}
- else if (d->d_tag == DT_AUXILIARY)
+ else if (d->d_tag == DT_AUXILIARY || d->d_tag == DT_FILTER)
{
char *errstring;
const char *objname;
+ struct list *newp;
- /* Store the tag in the argument structure. */
- args.d = d;
-
- if (_dl_catch_error (&errstring, &objname, openaux, &args))
+ if (d->d_tag == DT_AUXILIARY)
{
- /* We are not interested in the error message. */
- assert (errstring != NULL);
- free (errstring);
+ /* Store the tag in the argument structure. */
+ args.d = d;
+
+ /* We must be prepared that the addressed shared
+ object is not available. */
+ if (_dl_catch_error (&errstring, &objname, openaux, &args))
+ {
+ /* We are not interested in the error message. */
+ assert (errstring != NULL);
+ free (errstring);
+
+ /* Simply ignore this error and continue the work. */
+ continue;
+ }
}
else
+ /* For filter objects the dependency must be available. */
+ args.aux = _dl_map_object (l, strtab + d->d_un.d_val,
+ (l->l_type == lt_executable
+ ? lt_library : l->l_type),
+ trace_mode);
+
+ /* The auxiliary object is actually available.
+ Incorporate the map in all the lists. */
+
+ /* Allocate new entry. This always has to be done. */
+ newp = alloca (sizeof (struct list));
+
+ /* Copy the content of the current entry over. */
+ memcpy (newp, orig, sizeof (*newp));
+
+ /* Initialize new entry. */
+ orig->done = 0;
+ orig->map = args.aux;
+ orig->dup = newp;
+
+ /* We must handle two situations here: the map is new,
+ so we must add it in all three lists. If the map
+ is already known, we have two further possibilities:
+ - if the object is before the current map in the
+ search list, we do nothing. It is already found
+ early
+ - if the object is after the current one, we must
+ move it just before the current map to make sure
+ the symbols are found early enough
+ */
+ if (args.aux->l_reserved)
{
- /* The auxiliary object is actually available.
- Incorporate the map in all the lists. */
-
- /* Allocate new entry. This always has to be done. */
- struct list *newp = alloca (sizeof (struct list));
-
- /* Copy the content of the current entry over. */
- memcpy (newp, orig, sizeof (*newp));
-
- /* Initialize new entry. */
- orig->done = 0;
- orig->map = args.aux;
- orig->dup = newp;
-
- /* We must handle two situations here: the map is new,
- so we must add it in all three lists. If the map
- is already known, we have two further possibilities:
- - if the object is before the current map in the
- search list, we do nothing. It is already found
- early
- - if the object is after the current one, we must
- move it just before the current map to make sure
- the symbols are found early enough
- */
- if (args.aux->l_reserved)
- {
- /* The object is already somewhere in the
- list. Locate it first. */
- struct list *late;
-
- /* This object is already in the search list
- we are building. Don't add a duplicate
- pointer. Release the reference just added
- by _dl_map_object. */
- --args.aux->l_opencount;
-
- for (late = orig; late->unique; late = late->unique)
- if (late->unique->map == args.aux)
- break;
-
- if (late->unique)
- {
- /* The object is somewhere behind the current
- position in the search path. We have to
- move it to this earlier position. */
- orig->unique = newp;
-
- /* Now remove the later entry from the unique
- list. */
- late->unique = late->unique->unique;
-
- /* We must move the earlier in the chain. */
- if (args.aux->l_prev)
- args.aux->l_prev->l_next = args.aux->l_next;
- if (args.aux->l_next)
- args.aux->l_next->l_prev = args.aux->l_prev;
-
- args.aux->l_prev = newp->map->l_prev;
- newp->map->l_prev = args.aux;
- if (args.aux->l_prev != NULL)
- args.aux->l_prev->l_next = args.aux;
- args.aux->l_next = newp->map;
- }
- else
- {
- /* The object must be somewhere earlier in
- the list. That's good, we only have to
- insert an entry for the duplicate list. */
- orig->unique = NULL; /* Never used. */
-
- /* Now we have a problem. The element pointing
- to ORIG in the unique list must point to
- NEWP now. This is the only place where we
- need this backreference and this situation
- is really not that frequent. So we don't
- use a double-linked list but instead search
- for the preceding element. */
- late = head;
- while (late->unique != orig)
- late = late->unique;
- late->unique = newp;
- }
- }
- else
+ /* The object is already somewhere in the list.
+ Locate it first. */
+ struct list *late;
+
+ /* This object is already in the search list we
+ are building. Don't add a duplicate pointer.
+ Release the reference just added by
+ _dl_map_object. */
+ --args.aux->l_opencount;
+
+ for (late = orig; late->unique; late = late->unique)
+ if (late->unique->map == args.aux)
+ break;
+
+ if (late->unique)
{
- /* This is easy. We just add the symbol right
- here. */
+ /* The object is somewhere behind the current
+ position in the search path. We have to
+ move it to this earlier position. */
orig->unique = newp;
- ++nlist;
- /* Set the mark bit that says it's already in
- the list. */
- args.aux->l_reserved = 1;
-
- /* The only problem is that in the double linked
- list of all objects we don't have this new
- object at the correct place. Correct this
- here. */
+
+ /* Now remove the later entry from the unique list. */
+ late->unique = late->unique->unique;
+
+ /* We must move the earlier in the chain. */
if (args.aux->l_prev)
args.aux->l_prev->l_next = args.aux->l_next;
if (args.aux->l_next)
@@ -314,19 +286,60 @@ _dl_map_object_deps (struct link_map *map,
args.aux->l_prev->l_next = args.aux;
args.aux->l_next = newp->map;
}
+ else
+ {
+ /* The object must be somewhere earlier in the
+ list. That's good, we only have to insert
+ an entry for the duplicate list. */
+ orig->unique = NULL; /* Never used. */
+
+ /* Now we have a problem. The element
+ pointing to ORIG in the unique list must
+ point to NEWP now. This is the only place
+ where we need this backreference and this
+ situation is really not that frequent. So
+ we don't use a double-linked list but
+ instead search for the preceding element. */
+ late = head;
+ while (late->unique != orig)
+ late = late->unique;
+ late->unique = newp;
+ }
+ }
+ else
+ {
+ /* This is easy. We just add the symbol right here. */
+ orig->unique = newp;
+ ++nlist;
+ /* Set the mark bit that says it's already in the list. */
+ args.aux->l_reserved = 1;
+
+ /* The only problem is that in the double linked
+ list of all objects we don't have this new
+ object at the correct place. Correct this here. */
+ if (args.aux->l_prev)
+ args.aux->l_prev->l_next = args.aux->l_next;
+ if (args.aux->l_next)
+ args.aux->l_next->l_prev = args.aux->l_prev;
+
+ args.aux->l_prev = newp->map->l_prev;
+ newp->map->l_prev = args.aux;
+ if (args.aux->l_prev != NULL)
+ args.aux->l_prev->l_next = args.aux;
+ args.aux->l_next = newp->map;
+ }
- /* Move the tail pointers if necessary. */
- if (orig == utail)
- utail = newp;
- if (orig == dtail)
- dtail = newp;
+ /* Move the tail pointers if necessary. */
+ if (orig == utail)
+ utail = newp;
+ if (orig == dtail)
+ dtail = newp;
- /* Move on the insert point. */
- orig = newp;
+ /* Move on the insert point. */
+ orig = newp;
- /* We always add an entry to the duplicate list. */
- ++nduplist;
- }
+ /* We always add an entry to the duplicate list. */
+ ++nduplist;
}
}
else
diff --git a/elf/dl-load.c b/elf/dl-load.c
index ce3bd9f..9a6aae6 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -406,6 +406,9 @@ _dl_init_paths (void)
{
fake_path_list = (struct r_search_path_elem **)
malloc ((nllp + 1) * sizeof (struct r_search_path_elem *));
+ if (fake_path_list == NULL)
+ _dl_signal_error (ENOMEM, NULL,
+ "cannot create cache for search path");
(void) fillin_rpath (local_strdup (llp), fake_path_list, ":;",
__libc_enable_secure ? trusted_dirs : NULL);
@@ -901,23 +904,33 @@ _dl_map_object (struct link_map *loader, const char *name, int type,
/* Look for this name among those already loaded. */
for (l = _dl_loaded; l; l = l->l_next)
- if (l->l_opencount > 0 && _dl_name_match_p (name, l) ||
- /* If the requested name matches the soname of a loaded object,
- use that object. */
- (l->l_info[DT_SONAME] &&
- ! strcmp (name, (const char *) (l->l_addr +
- l->l_info[DT_STRTAB]->d_un.d_ptr +
- l->l_info[DT_SONAME]->d_un.d_val))))
- {
- /* The object is already loaded.
- Just bump its reference count and return it. */
- const char *soname = (const char *) (l->l_addr +
- l->l_info[DT_STRTAB]->d_un.d_ptr +
- l->l_info[DT_SONAME]->d_un.d_val);
- add_name_to_object (l, local_strdup (soname));
- ++l->l_opencount;
- return l;
- }
+ {
+ /* If the requested name matches the soname of a loaded object,
+ use that object. Elide this check for names that have not
+ yet been opened. */
+ if (l->l_opencount <= 0)
+ continue;
+ if (!_dl_name_match_p (name, l))
+ {
+ const char *soname;
+
+ if (l->l_info[DT_SONAME] == NULL)
+ continue;
+
+ soname = (const char *) (l->l_addr
+ + l->l_info[DT_STRTAB]->d_un.d_ptr
+ + l->l_info[DT_SONAME]->d_un.d_val);
+ if (strcmp (name, soname) != 0)
+ continue;
+
+ /* We have a match on a new name -- cache it. */
+ add_name_to_object (l, local_strdup (soname));
+ }
+
+ /* We have a match -- bump the reference count and return it. */
+ ++l->l_opencount;
+ return l;
+ }
if (strchr (name, '/') == NULL)
{
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index 4cc1f2d..c3a67ff 100644
--- a/elf/dl-lookup.c
+++ b/elf/dl-lookup.c
@@ -93,6 +93,10 @@ do_lookup (const char *undef_name, unsigned long int hash,
map->l_type == lt_executable)
continue;
+ /* Skip objects without symbol tables. */
+ if (map->l_info[DT_SYMTAB] == NULL)
+ continue;
+
symtab = ((void *) map->l_addr + map->l_info[DT_SYMTAB]->d_un.d_ptr);
strtab = ((void *) map->l_addr + map->l_info[DT_STRTAB]->d_un.d_ptr);
if (map->l_nversions > 0 && map->l_info[VERSTAG (DT_VERSYM)] != NULL)
@@ -364,8 +368,13 @@ _dl_lookup_versioned_symbol_skip (const char *undef_name,
void
_dl_setup_hash (struct link_map *map)
{
- ElfW(Symndx) *hash = (void *)(map->l_addr + map->l_info[DT_HASH]->d_un.d_ptr);
+ ElfW(Symndx) *hash;
ElfW(Symndx) nchain;
+
+ if (!map->l_info[DT_HASH])
+ return;
+ hash = (void *)(map->l_addr + map->l_info[DT_HASH]->d_un.d_ptr);
+
map->l_nbuckets = *hash++;
nchain = *hash++;
map->l_buckets = hash;
diff --git a/elf/dl-runtime.c b/elf/dl-runtime.c
index 8ee1838..46c0e1b 100644
--- a/elf/dl-runtime.c
+++ b/elf/dl-runtime.c
@@ -87,6 +87,10 @@ _dl_object_relocation_scope (struct link_map *l)
# define VERSYMIDX(sym) (DT_NUM + DT_PROCNUM + DT_VERSIONTAGIDX (sym))
#endif
+#ifndef ELF_FIXUP_RETURN_VALUE
+#define ELF_FIXUP_RETURN_VALUE(map, result) (result)
+#endif
+
/* We need to define the function as a local symbol so that the reference
in the trampoline code will be a local PC-relative call. Tell the
compiler not to worry that the function appears not to be called. */
@@ -120,6 +124,7 @@ fixup (
const PLTREL *const reloc
= (const void *) (l->l_addr + l->l_info[DT_JMPREL]->d_un.d_ptr +
reloc_offset);
+ ElfW(Addr) *const rel_addr = (ElfW(Addr) *)(l->l_addr + reloc->r_offset);
/* Set up the scope to find symbols referenced by this object. */
struct link_map **scope = _dl_object_relocation_scope (l);
@@ -144,21 +149,17 @@ fixup (
elf_machine_relplt (l, reloc, &symtab[ELFW(R_SYM) (reloc->r_info)],
&l->l_versions[ndx],
- (void *) (l->l_addr + reloc->r_offset));
+ (void *) rel_addr);
}
else
elf_machine_relplt (l, reloc, &symtab[ELFW(R_SYM) (reloc->r_info)],
- NULL, (void *) (l->l_addr + reloc->r_offset));
+ NULL, (void *) rel_addr);
}
*_dl_global_scope_end = NULL;
/* Return the address that was written by the relocation. */
-#ifdef ELF_FIXUP_RETURNS_ADDRESS
- return (ElfW(Addr))(l->l_addr + reloc->r_offset);
-#else
- return *(ElfW(Addr) *) (l->l_addr + reloc->r_offset);
-#endif
+ return ELF_FIXUP_RETURN_VALUE(l, *rel_addr);
}
@@ -219,17 +220,10 @@ profile_fixup (
}
*_dl_global_scope_end = NULL;
-
- /* Return the address that was written by the relocation. */
-#ifdef ELF_FIXUP_RETURNS_ADDRESS
(*mcount_fct) (retaddr, result);
- return &result; /* XXX This cannot work. What to do??? --drepper */
-#else
- (*mcount_fct) (retaddr, result);
-
- return result;
-#endif
+ /* Return the address that was written by the relocation. */
+ return ELF_FIXUP_RETURN_VALUE(l, result);
}
#endif
diff --git a/elf/dl-version.c b/elf/dl-version.c
index 0675b1c..b5515c8 100644
--- a/elf/dl-version.c
+++ b/elf/dl-version.c
@@ -155,16 +155,23 @@ int
_dl_check_map_versions (struct link_map *map, int verbose)
{
int result = 0;
- const char *strtab = (const char *) (map->l_addr
- + map->l_info[DT_STRTAB]->d_un.d_ptr);
+ const char *strtab;
/* Pointer to section with needed versions. */
- ElfW(Dyn) *dyn = map->l_info[VERSTAG (DT_VERNEED)];
+ ElfW(Dyn) *dyn;
/* Pointer to dynamic section with definitions. */
- ElfW(Dyn) *def = map->l_info[VERSTAG (DT_VERDEF)];
+ ElfW(Dyn) *def;
/* We need to find out which is the highest version index used
in a dependecy. */
unsigned int ndx_high = 0;
+ /* If we don't have a string table, we must be ok. */
+ if (map->l_info[DT_STRTAB] == NULL)
+ return 0;
+ strtab = (const char *) (map->l_addr + map->l_info[DT_STRTAB]->d_un.d_ptr);
+
+ dyn = map->l_info[VERSTAG (DT_VERNEED)];
+ def = map->l_info[VERSTAG (DT_VERDEF)];
+
if (dyn != NULL)
{
/* This file requires special versions from its dependencies. */
diff --git a/elf/elf.h b/elf/elf.h
index e78bcb5..cf8403b 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -783,6 +783,13 @@ typedef struct
#define DT_SPARC_PLTFMT 0x70000001 /* .plt format version/type */
#define DT_SPARC_NUM 2
+/* Bits present in AT_HWCAP, primarily for Sparc32. */
+
+#define HWCAP_SPARC_FLUSH 1 /* The cpu supports flush insn. */
+#define HWCAP_SPARC_STBAR 2
+#define HWCAP_SPARC_SWAP 4
+#define HWCAP_SPARC_MULDIV 8
+
/* MIPS R3000 specific definitions. */
/* Legal values for e_flags field of Elf32_Ehdr. */
diff --git a/elf/rtld.c b/elf/rtld.c
index c3d81e1..60a102a 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -53,13 +53,13 @@ static void print_unresolved (int errcode, const char *objname,
static void print_missing_version (int errcode, const char *objname,
const char *errsting);
-
int _dl_argc;
char **_dl_argv;
const char *_dl_rpath;
int _dl_verbose;
const char *_dl_platform;
size_t _dl_platformlen;
+unsigned long _dl_hwcap;
struct r_search_path *_dl_search_paths;
const char *_dl_profile;
const char *_dl_profile_output;
@@ -80,6 +80,7 @@ static void dl_main (const ElfW(Phdr) *phdr,
struct link_map _dl_rtld_map;
struct libname_list _dl_rtld_libname;
+struct libname_list _dl_rtld_libname2;
#ifdef RTLD_START
RTLD_START
@@ -119,19 +120,22 @@ _dl_start (void *arg)
the operating system's program loader where to find the program
header table in core. */
-
/* Transfer data about ourselves to the permanent link_map structure. */
_dl_rtld_map.l_addr = bootstrap_map.l_addr;
_dl_rtld_map.l_ld = bootstrap_map.l_ld;
+ _dl_rtld_map.l_opencount = 1;
memcpy (_dl_rtld_map.l_info, bootstrap_map.l_info,
sizeof _dl_rtld_map.l_info);
_dl_setup_hash (&_dl_rtld_map);
/* Cache the DT_RPATH stored in ld.so itself; this will be
the default search path. */
- _dl_rpath = (void *) (_dl_rtld_map.l_addr +
- _dl_rtld_map.l_info[DT_STRTAB]->d_un.d_ptr +
- _dl_rtld_map.l_info[DT_RPATH]->d_un.d_val);
+ if (_dl_rtld_map.l_info[DT_STRTAB] && _dl_rtld_map.l_info[DT_RPATH])
+ {
+ _dl_rpath = (void *) (_dl_rtld_map.l_addr +
+ _dl_rtld_map.l_info[DT_STRTAB]->d_un.d_ptr +
+ _dl_rtld_map.l_info[DT_RPATH]->d_un.d_val);
+ }
/* Call the OS-dependent function to set up life so we can do things like
file access. It will call `dl_main' (below) to do all the real work
@@ -416,6 +420,23 @@ of this helper program; chances are you did not intend to run this program.\n",
_dl_rtld_libname.name = (const char *) main_map->l_addr + ph->p_vaddr;
_dl_rtld_libname.next = NULL;
_dl_rtld_map.l_libname = &_dl_rtld_libname;
+
+ /* Ordinarilly, we would get additional names for the loader from
+ our DT_SONAME. This can't happen if we were actually linked as
+ a static executable (detect this case when we have no DYNAMIC).
+ If so, assume the filename component of the interpreter path to
+ be our SONAME, and add it to our name list. */
+ if (_dl_rtld_map.l_ld == NULL)
+ {
+ char *p = strrchr (_dl_rtld_libname.name, '/');
+ if (p)
+ {
+ _dl_rtld_libname2.name = p+1;
+ _dl_rtld_libname2.next = NULL;
+ _dl_rtld_libname.next = &_dl_rtld_libname2;
+ }
+ }
+
has_interp = 1;
break;
}
@@ -696,74 +717,70 @@ of this helper program; chances are you did not intend to run this program.\n",
for (map = _dl_loaded; map != NULL; map = map->l_next)
{
- const char *strtab =
- (const char *) (map->l_addr
- + map->l_info[DT_STRTAB]->d_un.d_ptr);
+ const char *strtab;
ElfW(Dyn) *dyn = map->l_info[VERNEEDTAG];
+ ElfW(Verneed) *ent;
+
+ if (dyn == NULL)
+ continue;
+
+ strtab = (const char *)
+ (map->l_addr + map->l_info[DT_STRTAB]->d_un.d_ptr);
+ ent = (ElfW(Verneed) *) (map->l_addr + dyn->d_un.d_ptr);
- if (dyn != NULL)
+ if (first)
{
- ElfW(Verneed) *ent =
- (ElfW(Verneed) *) (map->l_addr + dyn->d_un.d_ptr);
+ _dl_sysdep_message ("\n\tVersion information:\n", NULL);
+ first = 0;
+ }
- if (first)
- {
- _dl_sysdep_message ("\n\tVersion information:\n",
- NULL);
- first = 0;
- }
+ _dl_sysdep_message ("\t", (map->l_name[0]
+ ? map->l_name : _dl_argv[0]),
+ ":\n", NULL);
+
+ while (1)
+ {
+ ElfW(Vernaux) *aux;
+ struct link_map *needed;
- _dl_sysdep_message ("\t", (map->l_name[0]
- ? map->l_name
- : _dl_argv[0]), ":\n",
- NULL);
+ needed = find_needed (strtab + ent->vn_file);
+ aux = (ElfW(Vernaux) *) ((char *) ent + ent->vn_aux);
while (1)
{
- ElfW(Vernaux) *aux;
- struct link_map *needed;
-
- needed = find_needed (strtab + ent->vn_file);
- aux = (ElfW(Vernaux) *) ((char *) ent + ent->vn_aux);
-
- while (1)
- {
- const char *fname = NULL;
-
- _dl_sysdep_message ("\t\t",
- strtab + ent->vn_file,
- " (", strtab + aux->vna_name,
- ") ",
- (aux->vna_flags
- & VER_FLG_WEAK
- ? "[WEAK] " : ""),
- "=> ", NULL);
-
- if (needed != NULL
- && match_version (strtab + aux->vna_name,
- needed))
- fname = needed->l_name;
-
- _dl_sysdep_message (fname ?: "not found", "\n",
- NULL);
-
- if (aux->vna_next == 0)
- /* No more symbols. */
- break;
-
- /* Next symbol. */
- aux = (ElfW(Vernaux) *) ((char *) aux
- + aux->vna_next);
- }
+ const char *fname = NULL;
+
+ _dl_sysdep_message ("\t\t",
+ strtab + ent->vn_file,
+ " (", strtab + aux->vna_name,
+ ") ",
+ (aux->vna_flags
+ & VER_FLG_WEAK
+ ? "[WEAK] " : ""),
+ "=> ", NULL);
+
+ if (needed != NULL
+ && match_version (strtab+aux->vna_name, needed))
+ fname = needed->l_name;
+
+ _dl_sysdep_message (fname ?: "not found", "\n",
+ NULL);
- if (ent->vn_next == 0)
- /* No more dependencies. */
+ if (aux->vna_next == 0)
+ /* No more symbols. */
break;
- /* Next dependency. */
- ent = (ElfW(Verneed) *) ((char *) ent
- + ent->vn_next);
+ /* Next symbol. */
+ aux = (ElfW(Vernaux) *) ((char *) aux
+ + aux->vna_next);
}
+
+ if (ent->vn_next == 0)
+ /* No more dependencies. */
+ break;
+
+ /* Next dependency. */
+ ent = (ElfW(Verneed) *) ((char *) ent + ent->vn_next);
}
}
}