diff options
Diffstat (limited to 'elf')
-rw-r--r-- | elf/Makefile | 7 | ||||
-rw-r--r-- | elf/dl-debug.c | 11 | ||||
-rw-r--r-- | elf/dl-debug_state.c | 30 | ||||
-rw-r--r-- | elf/dl-find_object.c | 74 | ||||
-rw-r--r-- | elf/dl-find_object.h | 57 | ||||
-rw-r--r-- | elf/rtld.c | 84 | ||||
-rw-r--r-- | elf/rtld_static_init.c | 1 | ||||
-rw-r--r-- | elf/tst-dl_find_object.c | 14 | ||||
-rw-r--r-- | elf/tst-glibc-hwcaps-2-cache.root/etc/ld.so.conf | 2 | ||||
-rw-r--r-- | elf/tst-glibc-hwcaps-cache.root/etc/ld.so.conf | 2 | ||||
-rw-r--r-- | elf/tst-ldconfig-bad-aux-cache.root/etc/ld.so.conf | 2 | ||||
-rw-r--r-- | elf/tst-link-map-contiguous-ldso.c | 98 | ||||
-rw-r--r-- | elf/tst-link-map-contiguous-libc.c | 57 | ||||
-rw-r--r-- | elf/tst-link-map-contiguous-main.c | 45 |
14 files changed, 396 insertions, 88 deletions
diff --git a/elf/Makefile b/elf/Makefile index 44b9f19..3a5596e 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -57,6 +57,7 @@ dl-routines = \ dl-close \ dl-debug \ dl-debug-symbols \ + dl-debug_state \ dl-deps \ dl-exception \ dl-execstack \ @@ -542,6 +543,8 @@ tests-internal += \ tst-dl_find_object-threads \ tst-dlmopen2 \ tst-hash-collision3 \ + tst-link-map-contiguous-ldso \ + tst-link-map-contiguous-libc \ tst-ptrguard1 \ tst-stackguard1 \ tst-tls-surplus \ @@ -553,6 +556,10 @@ tests-internal += \ unload2 \ # tests-internal +ifeq ($(build-hardcoded-path-in-tests),yes) +tests-internal += tst-link-map-contiguous-main +endif + tests-container += \ tst-dlopen-self-container \ tst-dlopen-tlsmodid-container \ diff --git a/elf/dl-debug.c b/elf/dl-debug.c index 38a5b9a..7052f4a 100644 --- a/elf/dl-debug.c +++ b/elf/dl-debug.c @@ -167,14 +167,3 @@ _dl_debug_initialize (ElfW(Addr) ldbase, Lmid_t ns) return &r->base; } - - -/* This function exists solely to have a breakpoint set on it by the - debugger. The debugger is supposed to find this function's address by - examining the r_brk member of struct r_debug, but GDB 4.15 in fact looks - for this particular symbol name in the PT_INTERP file. */ -void -_dl_debug_state (void) -{ -} -rtld_hidden_def (_dl_debug_state) diff --git a/elf/dl-debug_state.c b/elf/dl-debug_state.c new file mode 100644 index 0000000..40c134a --- /dev/null +++ b/elf/dl-debug_state.c @@ -0,0 +1,30 @@ +/* Debugger hook called after dynamic linker updates. + Copyright (C) 1996-2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <ldsodefs.h> + +/* This function exists solely to have a breakpoint set on it by the + debugger. The debugger is supposed to find this function's address by + examining the r_brk member of struct r_debug, but GDB 4.15 in fact looks + for this particular symbol name in the PT_INTERP file. Therefore, + this function must not be inlined. */ +void +_dl_debug_state (void) +{ +} +rtld_hidden_def (_dl_debug_state) diff --git a/elf/dl-find_object.c b/elf/dl-find_object.c index 1e76373..c9f4c1c 100644 --- a/elf/dl-find_object.c +++ b/elf/dl-find_object.c @@ -465,6 +465,37 @@ _dl_find_object (void *pc1, struct dl_find_object *result) } rtld_hidden_def (_dl_find_object) +/* Subroutine of _dlfo_process_initial to split out noncontigous link + maps. NODELETE is the number of used _dlfo_nodelete_mappings + elements. It is incremented as needed, and the new NODELETE value + is returned. */ +static size_t +_dlfo_process_initial_noncontiguous_map (struct link_map *map, + size_t nodelete) +{ + struct dl_find_object_internal dlfo; + _dl_find_object_from_map (map, &dlfo); + + /* PT_LOAD segments for a non-contiguous link map are added to the + non-closeable mappings. */ + const ElfW(Phdr) *ph = map->l_phdr; + const ElfW(Phdr) *ph_end = map->l_phdr + map->l_phnum; + for (; ph < ph_end; ++ph) + if (ph->p_type == PT_LOAD) + { + if (_dlfo_nodelete_mappings != NULL) + { + /* Second pass only. */ + _dlfo_nodelete_mappings[nodelete] = dlfo; + ElfW(Addr) start = ph->p_vaddr + map->l_addr; + _dlfo_nodelete_mappings[nodelete].map_start = start; + _dlfo_nodelete_mappings[nodelete].map_end = start + ph->p_memsz; + } + ++nodelete; + } + return nodelete; +} + /* _dlfo_process_initial is called twice. First to compute the array sizes from the initial loaded mappings. Second to fill in the bases and infos arrays with the (still unsorted) data. Returns the @@ -476,29 +507,8 @@ _dlfo_process_initial (void) size_t nodelete = 0; if (!main_map->l_contiguous) - { - struct dl_find_object_internal dlfo; - _dl_find_object_from_map (main_map, &dlfo); - - /* PT_LOAD segments for a non-contiguous are added to the - non-closeable mappings. */ - for (const ElfW(Phdr) *ph = main_map->l_phdr, - *ph_end = main_map->l_phdr + main_map->l_phnum; - ph < ph_end; ++ph) - if (ph->p_type == PT_LOAD) - { - if (_dlfo_nodelete_mappings != NULL) - { - /* Second pass only. */ - _dlfo_nodelete_mappings[nodelete] = dlfo; - _dlfo_nodelete_mappings[nodelete].map_start - = ph->p_vaddr + main_map->l_addr; - _dlfo_nodelete_mappings[nodelete].map_end - = _dlfo_nodelete_mappings[nodelete].map_start + ph->p_memsz; - } - ++nodelete; - } - } + /* Contiguous case already handled in _dl_find_object_init. */ + nodelete = _dlfo_process_initial_noncontiguous_map (main_map, nodelete); size_t loaded = 0; for (Lmid_t ns = 0; ns < GL(dl_nns); ++ns) @@ -510,11 +520,18 @@ _dlfo_process_initial (void) /* lt_library link maps are implicitly NODELETE. */ if (l->l_type == lt_library || l->l_nodelete_active) { - if (_dlfo_nodelete_mappings != NULL) - /* Second pass only. */ - _dl_find_object_from_map - (l, _dlfo_nodelete_mappings + nodelete); - ++nodelete; + /* The kernel may have loaded ld.so with gaps. */ + if (!l->l_contiguous && is_rtld_link_map (l)) + nodelete + = _dlfo_process_initial_noncontiguous_map (l, nodelete); + else + { + if (_dlfo_nodelete_mappings != NULL) + /* Second pass only. */ + _dl_find_object_from_map + (l, _dlfo_nodelete_mappings + nodelete); + ++nodelete; + } } else if (l->l_type == lt_loaded) { @@ -764,7 +781,6 @@ _dl_find_object_update_1 (struct link_map **loaded, size_t count) /* Prefer newly loaded link map. */ assert (loaded_index1 > 0); _dl_find_object_from_map (loaded[loaded_index1 - 1], dlfo); - loaded[loaded_index1 - 1]->l_find_object_processed = 1; --loaded_index1; } diff --git a/elf/dl-find_object.h b/elf/dl-find_object.h index e433ff8..d9d75c4 100644 --- a/elf/dl-find_object.h +++ b/elf/dl-find_object.h @@ -43,6 +43,7 @@ struct dl_find_object_internal #if DLFO_STRUCT_HAS_EH_COUNT int eh_count; #endif + void *sframe; }; /* Create a copy of *SOURCE in *COPY using relaxed MO loads and @@ -67,13 +68,14 @@ _dl_find_object_internal_copy (const struct dl_find_object_internal *source, atomic_store_relaxed (©->eh_count, atomic_load_relaxed (&source->eh_count)); #endif + atomic_store_relaxed (©->sframe, + atomic_load_relaxed (&source->sframe)); } static inline void _dl_find_object_to_external (struct dl_find_object_internal *internal, struct dl_find_object *external) { - external->dlfo_flags = 0; external->dlfo_map_start = (void *) internal->map_start; external->dlfo_map_end = (void *) internal->map_end; external->dlfo_link_map = internal->map; @@ -84,14 +86,22 @@ _dl_find_object_to_external (struct dl_find_object_internal *internal, # if DLFO_STRUCT_HAS_EH_COUNT external->dlfo_eh_count = internal->eh_count; # endif + external->dlfo_sframe = internal->sframe; + if (internal->sframe != NULL) + external->dlfo_flags = DLFO_FLAG_SFRAME; + else + external->dlfo_flags = 0; } /* Extract the object location data from a link map and writes it to - *RESULT using relaxed MO stores. */ + *RESULT using relaxed MO stores. Set L->l_find_object_processed. */ static void __attribute__ ((unused)) _dl_find_object_from_map (struct link_map *l, struct dl_find_object_internal *result) { + /* A mask to find out which segment has been read out. */ + unsigned int read_seg = 0; + atomic_store_relaxed (&result->map_start, (uintptr_t) l->l_map_start); atomic_store_relaxed (&result->map_end, (uintptr_t) l->l_map_end); atomic_store_relaxed (&result->map, l); @@ -100,23 +110,42 @@ _dl_find_object_from_map (struct link_map *l, atomic_store_relaxed (&result->eh_dbase, (void *) l->l_info[DT_PLTGOT]); #endif - for (const ElfW(Phdr) *ph = l->l_phdr, *ph_end = l->l_phdr + l->l_phnum; - ph < ph_end; ++ph) - if (ph->p_type == DLFO_EH_SEGMENT_TYPE) - { - atomic_store_relaxed (&result->eh_frame, - (void *) (ph->p_vaddr + l->l_addr)); + /* Initialize object's exception handling segment and SFrame segment + data. */ + atomic_store_relaxed (&result->sframe, NULL); + atomic_store_relaxed (&result->eh_frame, NULL); #if DLFO_STRUCT_HAS_EH_COUNT - atomic_store_relaxed (&result->eh_count, ph->p_memsz / 8); + atomic_store_relaxed (&result->eh_count, 0); #endif - return; - } - /* Object has no exception handling segment. */ - atomic_store_relaxed (&result->eh_frame, NULL); + for (const ElfW(Phdr) *ph = l->l_phdr, *ph_end = l->l_phdr + l->l_phnum; + ph < ph_end; ++ph) + { + switch (ph->p_type) + { + case DLFO_EH_SEGMENT_TYPE: + atomic_store_relaxed (&result->eh_frame, + (void *) (ph->p_vaddr + l->l_addr)); #if DLFO_STRUCT_HAS_EH_COUNT - atomic_store_relaxed (&result->eh_count, 0); + atomic_store_relaxed (&result->eh_count, ph->p_memsz / 8); #endif + read_seg |= 1; + break; + + case PT_GNU_SFRAME: + atomic_store_relaxed (&result->sframe, + (void *) (ph->p_vaddr + l->l_addr)); + read_seg |= 2; + /* Fall through. */ + default: + break; + } + if (read_seg == 3) + goto done; + } + + done: + l->l_find_object_processed = 1; } /* Called by the dynamic linker to set up the data structures for the @@ -371,7 +371,6 @@ struct rtld_global_ro _rtld_global_ro attribute_relro = ._dl_error_free = _dl_error_free, ._dl_tls_get_addr_soft = _dl_tls_get_addr_soft, ._dl_libc_freeres = __rtld_libc_freeres, - ._dl_readonly_area = _dl_readonly_area, }; /* If we would use strong_alias here the compiler would see a non-hidden definition. This would undo the effect of the previous @@ -458,6 +457,7 @@ _dl_start_final (void *arg, struct dl_start_final_info *info) /* Do not use an initializer for these members because it would interfere with __rtld_static_init. */ GLRO (dl_find_object) = &_dl_find_object; + GLRO (dl_readonly_area) = &_dl_readonly_area; /* If it hasn't happen yet record the startup time. */ rtld_timer_start (&start_time); @@ -1239,6 +1239,60 @@ rtld_setup_main_map (struct link_map *main_map) return has_interp; } +/* Set up the program header information for the dynamic linker + itself. It can be accessed via _r_debug and dl_iterate_phdr + callbacks, and it is used by _dl_find_object. */ +static void +rtld_setup_phdr (void) +{ + /* Starting from binutils-2.23, the linker will define the magic + symbol __ehdr_start to point to our own ELF header if it is + visible in a segment that also includes the phdrs. */ + + const ElfW(Ehdr) *rtld_ehdr = &__ehdr_start; + assert (rtld_ehdr->e_ehsize == sizeof *rtld_ehdr); + assert (rtld_ehdr->e_phentsize == sizeof (ElfW(Phdr))); + + const ElfW(Phdr) *rtld_phdr = (const void *) rtld_ehdr + rtld_ehdr->e_phoff; + + _dl_rtld_map.l_phdr = rtld_phdr; + _dl_rtld_map.l_phnum = rtld_ehdr->e_phnum; + + + _dl_rtld_map.l_contiguous = 1; + /* The linker may not have produced a contiguous object. The kernel + will load the object with actual gaps (unlike the glibc loader + for shared objects, which always produces a contiguous mapping). + See similar logic in rtld_setup_main_map above. */ + { + ElfW(Addr) expected_load_address = 0; + for (const ElfW(Phdr) *ph = rtld_phdr; ph < &rtld_phdr[rtld_ehdr->e_phnum]; + ++ph) + if (ph->p_type == PT_LOAD) + { + ElfW(Addr) mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1); + if (_dl_rtld_map.l_contiguous && expected_load_address != 0 + && expected_load_address != mapstart) + _dl_rtld_map.l_contiguous = 0; + ElfW(Addr) allocend = ph->p_vaddr + ph->p_memsz; + /* The next expected address is the page following this load + segment. */ + expected_load_address = ((allocend + GLRO(dl_pagesize) - 1) + & ~(GLRO(dl_pagesize) - 1)); + } + } + + /* PT_GNU_RELRO is usually the last phdr. */ + size_t cnt = rtld_ehdr->e_phnum; + while (cnt-- > 0) + if (rtld_phdr[cnt].p_type == PT_GNU_RELRO) + { + _dl_rtld_map.l_relro_addr = rtld_phdr[cnt].p_vaddr; + _dl_rtld_map.l_relro_size = rtld_phdr[cnt].p_memsz; + break; + } +} + /* Adjusts the contents of the stack and related globals for the user entry point. The ld.so processed skip_args arguments and bumped _dl_argv and _dl_argc accordingly. Those arguments are removed from @@ -1705,33 +1759,7 @@ dl_main (const ElfW(Phdr) *phdr, ++GL(dl_ns)[LM_ID_BASE]._ns_nloaded; ++GL(dl_load_adds); - /* Starting from binutils-2.23, the linker will define the magic symbol - __ehdr_start to point to our own ELF header if it is visible in a - segment that also includes the phdrs. If that's not available, we use - the old method that assumes the beginning of the file is part of the - lowest-addressed PT_LOAD segment. */ - - /* Set up the program header information for the dynamic linker - itself. It is needed in the dl_iterate_phdr callbacks. */ - const ElfW(Ehdr) *rtld_ehdr = &__ehdr_start; - assert (rtld_ehdr->e_ehsize == sizeof *rtld_ehdr); - assert (rtld_ehdr->e_phentsize == sizeof (ElfW(Phdr))); - - const ElfW(Phdr) *rtld_phdr = (const void *) rtld_ehdr + rtld_ehdr->e_phoff; - - _dl_rtld_map.l_phdr = rtld_phdr; - _dl_rtld_map.l_phnum = rtld_ehdr->e_phnum; - - - /* PT_GNU_RELRO is usually the last phdr. */ - size_t cnt = rtld_ehdr->e_phnum; - while (cnt-- > 0) - if (rtld_phdr[cnt].p_type == PT_GNU_RELRO) - { - _dl_rtld_map.l_relro_addr = rtld_phdr[cnt].p_vaddr; - _dl_rtld_map.l_relro_size = rtld_phdr[cnt].p_memsz; - break; - } + rtld_setup_phdr (); /* Add the dynamic linker to the TLS list if it also uses TLS. */ if (_dl_rtld_map.l_tls_blocksize != 0) diff --git a/elf/rtld_static_init.c b/elf/rtld_static_init.c index 6423790..9c56180 100644 --- a/elf/rtld_static_init.c +++ b/elf/rtld_static_init.c @@ -79,6 +79,7 @@ __rtld_static_init (struct link_map *map) attribute_hidden; dl->_dl_tls_static_size = _dl_tls_static_size; dl->_dl_find_object = _dl_find_object; + dl->_dl_readonly_area = _dl_readonly_area; __rtld_static_init_arch (map, dl); } diff --git a/elf/tst-dl_find_object.c b/elf/tst-dl_find_object.c index 96ec591..d85eb21 100644 --- a/elf/tst-dl_find_object.c +++ b/elf/tst-dl_find_object.c @@ -122,6 +122,14 @@ check (void *address, address, actual.dlfo_eh_count, expected->dlfo_eh_count); } #endif + if (actual.dlfo_flags & DLFO_FLAG_SFRAME + && actual.dlfo_sframe != expected->dlfo_sframe) + { + support_record_failure (); + printf ("%s:%d: error: %p: sframe is %p, expected %p\n", + __FILE__, line, + address, actual.dlfo_sframe, expected->dlfo_sframe); + } } /* Check that unwind data for the main executable and the dynamic @@ -180,6 +188,12 @@ do_test (void) dlfo.dlfo_eh_frame, ret); TEST_COMPARE (ret, 0); TEST_VERIFY (dlfo.dlfo_eh_frame != NULL); +#if ENABLE_SFRAME + TEST_VERIFY ((dlfo.dlfo_flags & DLFO_FLAG_SFRAME) == DLFO_FLAG_SFRAME); + TEST_VERIFY (dlfo.dlfo_sframe != NULL); +#else + TEST_VERIFY ((dlfo.dlfo_flags & DLFO_FLAG_SFRAME) != DLFO_FLAG_SFRAME); +#endif } check_initial (); diff --git a/elf/tst-glibc-hwcaps-2-cache.root/etc/ld.so.conf b/elf/tst-glibc-hwcaps-2-cache.root/etc/ld.so.conf deleted file mode 100644 index e1e74db..0000000 --- a/elf/tst-glibc-hwcaps-2-cache.root/etc/ld.so.conf +++ /dev/null @@ -1,2 +0,0 @@ -# This file was created to suppress a warning from ldconfig: -# /sbin/ldconfig: Warning: ignoring configuration file that cannot be opened: /etc/ld.so.conf: No such file or directory diff --git a/elf/tst-glibc-hwcaps-cache.root/etc/ld.so.conf b/elf/tst-glibc-hwcaps-cache.root/etc/ld.so.conf deleted file mode 100644 index e1e74db..0000000 --- a/elf/tst-glibc-hwcaps-cache.root/etc/ld.so.conf +++ /dev/null @@ -1,2 +0,0 @@ -# This file was created to suppress a warning from ldconfig: -# /sbin/ldconfig: Warning: ignoring configuration file that cannot be opened: /etc/ld.so.conf: No such file or directory diff --git a/elf/tst-ldconfig-bad-aux-cache.root/etc/ld.so.conf b/elf/tst-ldconfig-bad-aux-cache.root/etc/ld.so.conf deleted file mode 100644 index e1e74db..0000000 --- a/elf/tst-ldconfig-bad-aux-cache.root/etc/ld.so.conf +++ /dev/null @@ -1,2 +0,0 @@ -# This file was created to suppress a warning from ldconfig: -# /sbin/ldconfig: Warning: ignoring configuration file that cannot be opened: /etc/ld.so.conf: No such file or directory diff --git a/elf/tst-link-map-contiguous-ldso.c b/elf/tst-link-map-contiguous-ldso.c new file mode 100644 index 0000000..04de808 --- /dev/null +++ b/elf/tst-link-map-contiguous-ldso.c @@ -0,0 +1,98 @@ +/* Check that _dl_find_object behavior matches up with gaps. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <dlfcn.h> +#include <gnu/lib-names.h> +#include <link.h> +#include <stdbool.h> +#include <stdio.h> +#include <support/check.h> +#include <support/xdlfcn.h> +#include <support/xunistd.h> +#include <sys/mman.h> +#include <unistd.h> + +static int +do_test (void) +{ + struct link_map *l = xdlopen (LD_SO, RTLD_NOW); + if (!l->l_contiguous) + { + puts ("info: ld.so link map is not contiguous"); + + /* Try to find holes by probing with mmap. */ + int pagesize = getpagesize (); + bool gap_found = false; + ElfW(Addr) addr = l->l_map_start; + TEST_COMPARE (addr % pagesize, 0); + while (addr < l->l_map_end) + { + void *expected = (void *) addr; + void *ptr = xmmap (expected, 1, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1); + struct dl_find_object dlfo; + int dlfo_ret = _dl_find_object (expected, &dlfo); + if (ptr == expected) + { + if (dlfo_ret < 0) + { + TEST_COMPARE (dlfo_ret, -1); + printf ("info: hole without mapping data found at %p\n", ptr); + } + else + FAIL ("object \"%s\" found in gap at %p", + dlfo.dlfo_link_map->l_name, ptr); + gap_found = true; + } + else if (dlfo_ret == 0) + { + if ((void *) dlfo.dlfo_link_map != (void *) l) + { + printf ("info: object \"%s\" found at %p\n", + dlfo.dlfo_link_map->l_name, ptr); + gap_found = true; + } + } + else + TEST_COMPARE (dlfo_ret, -1); + xmunmap (ptr, 1); + addr += pagesize; + } + if (!gap_found) + FAIL ("no ld.so gap found"); + } + else + { + puts ("info: ld.so link map is contiguous"); + + /* Assert that ld.so is truly contiguous in memory. */ + volatile long int *p = (volatile long int *) l->l_map_start; + volatile long int *end = (volatile long int *) l->l_map_end; + while (p < end) + { + *p; + ++p; + } + } + + xdlclose (l); + + return 0; +} + +#include <support/test-driver.c> diff --git a/elf/tst-link-map-contiguous-libc.c b/elf/tst-link-map-contiguous-libc.c new file mode 100644 index 0000000..eb5728c --- /dev/null +++ b/elf/tst-link-map-contiguous-libc.c @@ -0,0 +1,57 @@ +/* Check that the entire libc.so program image is readable if contiguous. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <gnu/lib-names.h> +#include <link.h> +#include <support/check.h> +#include <support/xdlfcn.h> +#include <support/xunistd.h> +#include <sys/mman.h> +#include <unistd.h> + +static int +do_test (void) +{ + struct link_map *l = xdlopen (LIBC_SO, RTLD_NOW); + + /* The dynamic loader fills holes with PROT_NONE mappings. */ + if (!l->l_contiguous) + FAIL_EXIT1 ("libc.so link map is not contiguous"); + + /* Direct probing does not work because not everything is readable + due to PROT_NONE mappings. */ + int pagesize = getpagesize (); + ElfW(Addr) addr = l->l_map_start; + TEST_COMPARE (addr % pagesize, 0); + while (addr < l->l_map_end) + { + void *expected = (void *) addr; + void *ptr = xmmap (expected, 1, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1); + if (ptr == expected) + FAIL ("hole in libc.so memory image after %lu bytes", + (unsigned long int) (addr - l->l_map_start)); + xmunmap (ptr, 1); + addr += pagesize; + } + + xdlclose (l); + + return 0; +} +#include <support/test-driver.c> diff --git a/elf/tst-link-map-contiguous-main.c b/elf/tst-link-map-contiguous-main.c new file mode 100644 index 0000000..2d1a054 --- /dev/null +++ b/elf/tst-link-map-contiguous-main.c @@ -0,0 +1,45 @@ +/* Check that the entire main program image is readable if contiguous. + Copyright (C) 2025 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <link.h> +#include <support/check.h> +#include <support/xdlfcn.h> + +static int +do_test (void) +{ + struct link_map *l = xdlopen ("", RTLD_NOW); + if (!l->l_contiguous) + FAIL_UNSUPPORTED ("main link map is not contiguous"); + + /* This check only works if the kernel loaded the main program. The + dynamic loader replaces gaps with PROT_NONE mappings, resulting + in faults. */ + volatile long int *p = (volatile long int *) l->l_map_start; + volatile long int *end = (volatile long int *) l->l_map_end; + while (p < end) + { + *p; + ++p; + } + + xdlclose (l); + + return 0; +} +#include <support/test-driver.c> |