Skip to content

Commit

Permalink
Implement optional exit analysis optimization
Browse files Browse the repository at this point in the history
For control libraries that implement function-granularity task abstractions with custom interfaces,
it is impossible for a task to run from its definition in the shared group unless it is defined in
the same object file that interacts with the task abstraction's interface (because otherwise the
object file interacting with the interface would see the function's symbol resolve to a PLOT stub*).
Then, assuming said abstraction is implemented as an internal control library, we can tell whether
a given object file can ever exit the shared namespace once it is running in that copy based on
whether that object file depends on our object file (since otherwise it could never be running from
this copy while its [next] group was set to an ancillary namespace).

This patch adds a static configuration flag in the form of the weak libgotcha_exitanalysis boolean,
which a control library can define to request that we skip intercepting calls out of object files in
the shared namespace that do not meet this test.  Note that this is not currently useful to external
control libraries, but could be made so by turning it into something like a string that the control
library could use to identify itself.

One CANNOT apply this optimization to control libraries that change the semantics of an existing
abstraction with a well-established interface, such as signal handling or thread cancellation.
(This is because using such an abstraction involves calls to a third-party library such as libc, so
the sieve wouldn't work unless we extended the definition of dependency to include object files with
symbol references that resolved back to our module.)  An easy rule of thumb is that if you could
preload the control library to obtain an effect similar to that of linking the application against
it, then this is not the optimization you are looking for.

* This is assuming, of course, that the task abstraction did not use or expose a frontend to our
libgotcha_group_symbol*() interfaces.
  • Loading branch information
solb committed Feb 18, 2022
1 parent 540b04b commit 32f363d
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 3 deletions.
6 changes: 6 additions & 0 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ bool config_staticlink(void) {
return &libgotcha_staticlink && libgotcha_staticlink;
}

bool config_exitanalysis(void) {
#pragma weak libgotcha_exitanalysis
extern const bool libgotcha_exitanalysis;
return &libgotcha_exitanalysis && libgotcha_exitanalysis;
}

bool config_skip(const char *progname) {
static bool memo;
static const char *list;
Expand Down
1 change: 1 addition & 0 deletions config.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <stdio.h>

bool config_staticlink(void);
bool config_exitanalysis(void);
bool config_skip(const char *);
ssize_t config_numgroups(void);
bool config_sharedlibc(void);
Expand Down
13 changes: 10 additions & 3 deletions handle.c
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,13 @@ enum error handle_init(struct handle *h, const struct link_map *l, struct link_m
h->symtab = (ElfW(Sym) *) (h->baseaddr + (uintptr_t) h->symtab);
symhash = (struct sym_hash *) (h->baseaddr + (uintptr_t) symhash);
h->strtab = (char *) (h->baseaddr + (uintptr_t) h->strtab);
}
} else if(config_exitanalysis()) {
const char *self = strrchr(namespace_self()->l_name, '/') + 1;
for(const ElfW(Dyn) *d = l->l_ld; d->d_tag != DT_NULL; ++d)
if(d->d_tag == DT_NEEDED && !strcmp(h->strtab + d->d_un.d_val, self))
h->dependent = true;
} else
h->dependent = true;

// Use the symbol hash table to determine the size of the symbol table, if the former is
// present. Otherwise, employ the same heuristic used by GNU ld.so's dl-addr.c
Expand Down Expand Up @@ -644,6 +650,7 @@ static inline void handle_got_shadow_init(const struct handle *h, Lmid_t n, uint
assert(n <= config_numgroups());

bool self = myself(h);
bool noexits = !h->dependent && !n;
bool partial = whitelist_so_partial(h->path);

// First, update ancillary namespaces' shadow GOT entries for symbols defined in this same
Expand Down Expand Up @@ -707,7 +714,7 @@ static inline void handle_got_shadow_init(const struct handle *h, Lmid_t n, uint
tramp = h->globdats[r - h->miscrels];
}

if(tramp && (!redirected || n))
if(tramp && (!redirected || n) && !noexits)
// ...and install it over the GOT entry.
*got = tramp;
}
Expand Down Expand Up @@ -752,7 +759,7 @@ static inline void handle_got_shadow_init(const struct handle *h, Lmid_t n, uint

// Skip updating the GOT for *this* library, because we don't want to force
// any automatic namespace switches once our own code is already running.
if(!self) {
if(!self && !noexits) {
// Install our corresponding PLOT trampoline over the GOT entry. Or reject
// their reality and substitute the one from *this* library if it's an
// interposed symbol. But never do the latter for a partially-whitelisted
Expand Down
1 change: 1 addition & 0 deletions handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct handle {
uintptr_t baseaddr;
bool vdso;
bool owned;
bool dependent;
bool eager;
bool sonamed;
void (*ldaccess)(void);
Expand Down

0 comments on commit 32f363d

Please sign in to comment.