The --export-dynamic linker option is not the only way that main's
global symbols may end up in the dynamic symbol table and thus be
used to satisfy symbol reference in a shared object. A symbol
may also be placed into the dynamic symbol table if ld(1)
notices a dependency in another object during the static link.
Verified by experiment; see previous commit.
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
The existing text wrongly implied that symbol look up first
occurred in the object and then in main, and did not mention
whether dependencies of main where used for symbol resolution.
Verified by experiment:
$ cat prog.c
#define _GNU_SOURCE
#include <link.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void /* A function defined in both main and lib_x1 */
prog_x1(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
/* The following function is forced into prog's dynamic symbol table
because of the static link-time reference in lib_m1.so */
void /* A function defined in both main and lib_y1 */
prog_y1_exp(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
/* The following function is not forced into prog's dynamic symbol table */
void /* A function defined in both main and lib_y1 */
prog_y1_noexp(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
static int
callback(struct dl_phdr_info *info, size_t size, void *data)
{
printf("\tName = %s\n", info->dlpi_name);
return 0;
}
int
main(int argc, char *argv[])
{
void *xHandle, *yHandle;
void (*funcp)(void);
char *err;
xHandle = dlopen("./lib_x1.so", RTLD_NOW | RTLD_GLOBAL);
if (xHandle == NULL) {
fprintf(stderr, "dlopen: %s\n", dlerror());
exit(EXIT_FAILURE);
}
yHandle = dlopen("./lib_y1.so", RTLD_NOW | RTLD_GLOBAL);
if (yHandle == NULL) {
fprintf(stderr, "dlopen: %s\n", dlerror());
exit(EXIT_FAILURE);
}
/* Optionally display the link map() */
if (argc > 1) {
printf("Link map as shown from dl_iterate_phdr() callbacks:\n");
dl_iterate_phdr(callback, NULL);
printf("\n");
}
(void) dlerror(); /* Clear dlerror() */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
funcp = (void (*)(void)) dlsym(yHandle, "y1_enter");
#pragma GCC diagnostic pop
err = dlerror();
if (err != NULL) {
fprintf(stderr, "dlsym: %s", err);
exit(EXIT_FAILURE);
}
(*funcp)();
exit(EXIT_SUCCESS);
}
$ cat lib_m1.c
#include <stdio.h>
void /* A function defined in both lib_m1 and lib_y1 */
m1_y1(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
#if 1
void
dummy(void)
{
extern void prog_y1_exp(void);
prog_y1_exp(); /* Forces prog_y1_exp into prog's dynamic symbol table,
so that it will be visible also to lib_y1.so */
}
#endif
$ cat lib_x1.c
#include <stdio.h>
void /* A function defined in both main and lib_x1 */
prog_x1(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
void /* A function defined in both lib_x1 and lib_y1 */
x1_y1(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
$ cat lib_y1.c
#include <stdio.h>
void /* A function defined in both lib_x1 and lib_y1 */
x1_y1(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
void /* A function defined in both main and lib_y1 */
prog_y1_exp(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
void /* A function defined in both lib_m1 and lib_y1 */
m1_y1(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
void /* A function defined in both main and lib_y1 */
prog_y1_noexp(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
void
y1_enter(void)
{
extern void y2(void);
printf("Called %s\n\n", __func__);
prog_x1();
prog_y1_exp();
prog_y1_noexp();
x1_y1();
m1_y1();
y2();
}
$ cat lib_y2.c
#include <stdio.h>
void
y2(void)
{
printf("Called %s::%s\n", __FILE__, __func__);
}
$ cat Build.sh
#!/bin/sh
CFLAGS="-Wno-implicit-function-declaration -Wl,--no-as-needed"
cc $CFLAGS -g -fPIC -shared -o lib_x1.so lib_x1.c
cc $CFLAGS -g -fPIC -shared -o lib_y2.so lib_y2.c
cc $CFLAGS -g -fPIC -shared -o lib_y1.so lib_y1.c ./lib_y2.so
cc $CFLAGS -g -fPIC -shared -o lib_m1.so lib_m1.c
#ED="-Wl,--export-dynamic"
cc $CFLAGS $ED -Wl,--rpath,$PWD -o prog prog.c -ldl lib_m1.so
$ sh Build.sh
$ ./prog x
Link map as shown from dl_iterate_phdr() callbacks:
Name =
Name = linux-vdso.so.1
Name = /lib64/libdl.so.2
Name = /home/mtk/tlpi/code/shlibs/dlopen_sym_res_expt/lib_m1.so
Name = /lib64/libc.so.6
Name = /lib64/ld-linux-x86-64.so.2
Name = ./lib_x1.so
Name = ./lib_y1.so
Name = ./lib_y2.so
Called y1_enter
Called lib_x1.c::prog_x1
Called prog.c::prog_y1_exp
Called lib_y1.c::prog_y1_noexp
Called lib_x1.c::x1_y1
Called lib_m1.c::m1_y1
Called lib_y2.c::y2
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
My earlier commit was in error:
commit 4a1af09bd1
Author: Michael Kerrisk <mtk.manpages@gmail.com>
Date: Sat Mar 14 21:40:35 2015 +0100
dlopen.3: Amend error in description of dlclose() behavior
-If the reference count drops to zero and no other loaded libraries use
-symbols in it, then the dynamic library is unloaded.
+If the reference count drops to zero,
+then the dynamic library is unloaded.
I doubted the removed text, because it provide little clue about
the scenario. The POSIX dlclose(3) specification actually details
the scenario sufficiently:
Although a dlclose() operation is not required to remove
any functions or data objects from the address space,
neither is an implementation prohibited from doing so.
The only restriction on such a removal is that no func‐
tion nor data object shall be removed to which references
have been relocated, until or unless all such references
are removed. For instance, an executable object file that
had been loaded with a dlopen() operation specifying the
RTLD_GLOBAL flag might provide a target for dynamic relo‐
cations performed in the processing of other relocatable
objects—in such environments, an application may assume
that no relocation, once made, shall be undone or remade
unless the executable object file containing the relo‐
cated object has itself been removed.
Verified by experiment:
$ cat openlibs.c # Test program
int
main(int argc, char *argv[])
{
void *libHandle[MAX_LIBS];
int lcnt;
if (argc < 2) {
fprintf(stderr, "Usage: %s lib-path...\n", argv[0]);
exit(EXIT_FAILURE);
}
lcnt = 0;
for (int j = 1; j < argc; j++) {
if (argv[j][0] != '-') {
if (lcnt >= MAX_LIBS) {
fprintf(stderr, "Too many libraries (limit: %d)\n", MAX_LIBS);
exit(EXIT_FAILURE);
}
printf("[%d] Opening %s\n", lcnt, argv[j]);
libHandle[lcnt] = dlopen(argv[j], RTLD_NOW | RTLD_GLOBAL);
if (libHandle[lcnt] == NULL) {
fprintf(stderr, "dlopen: %s\n", dlerror());
exit(EXIT_FAILURE);
}
lcnt++;
} else { /* "-N" closes the Nth handle */
int i = atoi(&argv[j][1]);
printf("Closing handle %d\n", i);
dlclose(libHandle[i]);
}
sleep(1);
printf("\n");
}
printf("Program about to exit\n");
exit(EXIT_SUCCESS);
}
$ cat lib_x1.c
void x1_func(void) { printf("Hello world\n"); }
__attribute__((constructor)) void x1_cstor(void)
{ printf("Called %s\n", __FUNCTION__); }
__attribute__((destructor)) void x1_dstor(void)
{ printf("Called %s\n", __FUNCTION__); }
$ cat lib_y1.c
void y1_func(void) { printf("Hello world\n"); }
__attribute__((constructor)) void y1_cstor(void)
{ printf("Called %s\n", __FUNCTION__); }
__attribute__((destructor)) void y1_dstor(void)
{ printf("Called %s\n", __FUNCTION__); }
static void testref(void) {
/* The following reference, to a symbol in lib_x1.so shows that
RTLD_GLOBAL may pin a library when it might otherwise have been
released with dlclose() */
extern void x1_func(void);
x1_func();
}
$ cc -shared -fPIC -o lib_x1.so lib_x1.c
$ cc -shared -fPIC -o lib_y1.so lib_y1.c
$ cc -o openlibs openlibs.c -ldl
$ LD_LIBRARY_PATH=. ./openlibs lib_x1.so lib_y1.so -0 -1
[0] Opening lib_x1.so
Called x1_cstor
[1] Opening lib_y1.so
Called y1_cstor
Closing handle 0
Closing handle 1
Called y1_dstor
Called x1_dstor
Program about to exit
<end program output>
Note that x1_dstor was called only when handle 1 (lib_y1.so) was closed.
But, if we edit lib_y1 to remove the reference to x1_func(), things are
different:
$ cat lib_y1.c # After editing
void y1_func(void) { printf("Hello world\n"); }
__attribute__((constructor)) void y1_cstor(void)
{ printf("Called %s\n", __FUNCTION__); }
__attribute__((destructor)) void y1_dstor(void)
{ printf("Called %s\n", __FUNCTION__); }
static void testref(void) {
// extern void x1_func(void);
// x1_func();
}
$ cc -shared -fPIC -o lib_y1.so lib_y1.c
$ LD_LIBRARY_PATH=. ./openlibs lib_x1.so lib_y1.so -0 -1
[0] Opening lib_x1.so
Called x1_cstor
[1] Opening lib_y1.so
Called y1_cstor
Closing handle 0
Called x1_dstor
Closing handle 1
Called y1_dstor
Program about to exit
<end program output>
This time, x1_dstor was called when handle 0 (lib_x1.so) was closed.
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
See fs/xattr.c::xattr_permission()"
/*
* In the user.* namespace, only regular files and directories can have
* extended attributes. For sticky directories, only the owner and
* privileged users can write attributes.
*/
if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) {
if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode))
return (mask & MAY_WRITE) ? -EPERM : -ENODATA;
if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) &&
(mask & MAY_WRITE) && !inode_owner_or_capable(inode))
return -EPERM;
}
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
If the file descriptors received in SCM_RIGHTS would cause
the process to its exceed RLIMIT_NOFILE limit, the excess
FDs are discarded.
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
Long ago, the sysvipc.7 page was called ipc.5, which was both a
misnaming (too general a name) and an inconsistent section. The
page was renamed (to svipc.7) many years ago, and the link with
the old name has probably ceased to be needed. So, remove it.
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
Here's a program for doing experiments:
/* on_expt_scope_expt.c
(C) Michael Kerrisk, 2019, Licensed GNU GPLv2+
*/
char *tos;
static void
exitFunc(int status, void *p)
{
int efloc;
int *xp = (int *) p;
printf("====== Entered exit handler\n");
printf("&efloc = %p (0x%llx)\n",
(void *) &efloc, (long long) (tos - (char *) &efloc));
printf("xp = %p (value: %d)\n", (void *) xp, *xp);
if (*xp != INIT_VALUE)
printf("It looks like the variable passed to the exit handler "
"has gone out of scope\n");
/* Produce a core dump, which we can examine with GDB to look at the
frames on the stack, if desired */
printf("===\n");
printf("About to abort\n");
abort();
}
static void
recur(int lev, int *xp)
{
int rloc;
int big[65536-12]; /* 12*4 == 48 other bytes allocated on
this stack frame */
tos = (char *) &rloc;
big[0] = lev;
big[0]++;
printf("&rloc = %p (%d) (%d)\n", (void *) &rloc, lev, *xp);
if (lev > 1)
recur(lev - 1, xp);
else {
printf("exit() from recur()\n");
exit(EXIT_SUCCESS);
}
}
int
main(int argc, char *argv[])
{
int lev;
int *xp;
int xx;
if (argc < 2) {
fprintf(stderr, "Usage: %s {s|h} [how]\n", argv[0]);
fprintf(stderr, "\ts => exitFunc() arg is in main() stack\n");
fprintf(stderr, "\th => exitFunc() arg is allocated on heapn");
fprintf(stderr, "\tIf 'how' is not present, then return from main()\n");
fprintf(stderr, "\tIf 'how' is 0, then exit() from main()\n");
fprintf(stderr, "\tIf 'how' is > 0, then make 'how' recursive "
"function calls, and then exit()\n");
exit(EXIT_FAILURE);
}
tos = (char *) &xp;
if (argv[1][0] == 'h') {
xp = malloc(sizeof(int));
if (xp == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
printf("Argument for exitFunc() is allocated on heap\n");
} else {
xp = &xx;
printf("Argument for exitFunc() is allocated on stack in main()\n");
}
*xp = INIT_VALUE;
printf("xp = %p (value: %d)\n", (void *) xp, *xp);
printf("===\n");
on_exit(exitFunc, xp);
if (argc == 2) {
printf("return from main\n");
return 0;
}
lev = atoi(argv[2]);
if (lev < 1) {
printf("Calling exit() from main\n");
exit(EXIT_SUCCESS);
} else {
recur(lev, xp);
}
}
Reported-by: Sami Kerola <kerolasa@iki.fi>
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
To get the pkey_alloc, pkey_free and pkey_mprotect functions
_GNU_SOURCE needs to be defined before including sys/mman.h.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
Signed-off-by: Mark Wielaard <mark@klomp.org>
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
The mprotect.2 NOTES say:
On systems that do not support protection keys in
hardware, pkey_mprotect() may still be used, but pkey must
be set to 0. When called this way, the operation of
pkey_mprotect() is equivalent to mprotect().
But this is not what the glibc manual says:
It is also possible to call pkey_mprotect with a key value
of -1, in which case it will behave in the same way as
mprotect.
Which is correct. Both the glibc implementation and the
kernel check whether pkey is -1. 0 is not a valid pkey when
memory protection keys are not supported in hardware.
Signed-off-by: Mark Wielaard <mark@klomp.org>
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
I've found the exec man page quite difficult to read when trying
to find the behavior for a specific function. Since the names of
the functions are inline and the order of the descriptions isn't
clear, it's hard to find which paragraphs apply to each function.
I thought it would be much easier to read if the grouping based on
letters is stated.
The Blackfin port was removed in Linux 4.17. Mention this in the
section concerning Blackfin vDSO functions.
Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
Improved the readability of a sentence that describes the use of
FAN_REPORT_FID and how this particular flag influences what data
structures a listening application could expect to receive when
describing an event.
Signed-off-by: Matthew Bobrowski <mbobrowski@mbobrowski.org>
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>
Document the symbols exported by the RISCV vDSO which is present
from kernel 4.15 onwards.
See kernel source files in arch/riscv/kernel/vdso.
Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Reviewed-by: Palmer Dabbelt <palmer@sifive.com>
Signed-off-by: Michael Kerrisk <mtk.manpages@gmail.com>