From 0518ccb9ca9b776915cf9a3b683b493716e4e423 Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 3 Sep 2026 01:00:13 +0200 Subject: [PATCH] libs/libc/elf, binfmt: Describe the GOT by base and size, not by index. gotindex named the .got section header, and every user then reached through shdr[] for what it actually wanted. Only one of the five wanted the index. gotbase and gotsize say it directly. gotsize is the extent of .got and is also what says the object has one, and gotbase is where the GOT ended up: the placed address of .got for an ordinary object, or DT_PLTGOT for an FDPIC one, which libelf_bind() already reads. Both are set in libelf_loadfile(), after the sections are placed, so gotbase is the address the object will be read at rather than the one it was linked for. The GOT walk in libelf_loadfile() now runs only when there is a base, which also keeps it off an FDPIC object. An FDPIC object's sections are never placed, so .got carried a link time sh_addr there, and the walk read and wrote through it. Its GOT is relocated through its own relocations. The check that gates libelf_xipacquire() runs before the load, when neither field is set, so it looks the section up by name. It hands the index it found to libelf_loadfile(), which is the only reason that function takes one: the object is searched once, not twice. One behaviour changes: a .got that exists but is empty now reads as no GOT. There is nothing for any of the five users to do with an empty one. Built for pimoroni-pico-2-plus with CONFIG_PIC, CONFIG_ELF and CONFIG_LIBC_ELF, and for mps3-an547:bl, which is the board that read the index. Run on QEMU with mps3-an547:picostest, which loads PIC ELF modules from a romfs: hello prints, and ostest reaches the timed mutex test, the same as before the change. Assisted-by: Claude Opus 5 (1M context) Signed-off-by: Marco Casaroli --- binfmt/elf.c | 6 +- boards/arm/mps/mps3-an547/src/mps3_bringup.c | 2 +- include/nuttx/lib/elf.h | 8 ++- libs/libc/elf/elf_bind.c | 5 +- libs/libc/elf/elf_load.c | 62 +++++++++++++++----- libs/libc/elf/elf_symbols.c | 2 +- 6 files changed, 60 insertions(+), 25 deletions(-) diff --git a/binfmt/elf.c b/binfmt/elf.c index fc896300538..a4ad873a65a 100644 --- a/binfmt/elf.c +++ b/binfmt/elf.c @@ -139,7 +139,7 @@ static int elf_loadbinary(FAR struct binary_s *binp, /* Bind the program to the exported symbol table */ - if (loadinfo.ehdr.e_type == ET_REL || loadinfo.gotindex >= 0) + if (loadinfo.ehdr.e_type == ET_REL || loadinfo.gotsize != 0) { ret = libelf_bind(&binp->mod, &loadinfo, exports, nexports); if (ret != 0) @@ -270,7 +270,7 @@ static int elf_loadbinary(FAR struct binary_s *binp, libelf_dumpentrypt(&loadinfo); #ifdef CONFIG_PIC - if (loadinfo.gotindex >= 0) + if (loadinfo.gotsize != 0) { FAR struct dspace_s *dspaces = kmm_zalloc(sizeof(struct dspace_s)); @@ -280,7 +280,7 @@ static int elf_loadbinary(FAR struct binary_s *binp, goto errout_with_load; } - dspaces->region = (FAR void *)loadinfo.shdr[loadinfo.gotindex].sh_addr; + dspaces->region = (FAR void *)loadinfo.gotbase; dspaces->crefs = 1; binp->picbase = (FAR void *)dspaces; } diff --git a/boards/arm/mps/mps3-an547/src/mps3_bringup.c b/boards/arm/mps/mps3-an547/src/mps3_bringup.c index 5c7edfb523c..e3c044aacb2 100644 --- a/boards/arm/mps/mps3-an547/src/mps3_bringup.c +++ b/boards/arm/mps/mps3-an547/src/mps3_bringup.c @@ -147,7 +147,7 @@ int board_boot_image(const char *path, uint32_t hdr_size) } bss = libelf_findsection(&loadinfo, ".bss"); - got = loadinfo.shdr[loadinfo.gotindex].sh_addr; + got = loadinfo.gotbase; msp = loadinfo.shdr[bss].sh_addr + loadinfo.shdr[bss].sh_size + CONFIG_IDLETHREAD_STACKSIZE; diff --git a/include/nuttx/lib/elf.h b/include/nuttx/lib/elf.h index 7eee93fe61e..e30399eede9 100644 --- a/include/nuttx/lib/elf.h +++ b/include/nuttx/lib/elf.h @@ -262,7 +262,6 @@ struct mod_loadinfo_s uint16_t buflen; /* size of iobuffer[] */ int filfd; /* Descriptor for the file being loaded */ int nexports; /* ET_DYN - Number of symbols exported */ - int gotindex; /* Index to the GOT section */ uintptr_t xipbase; /* if elf is position independent, and use * romfs/tmps, we can try get xipbase, * skip the copy. @@ -278,11 +277,14 @@ struct mod_loadinfo_s FAR struct file *pinfile; #endif - /* The object's data base, from DT_PLTGOT. An FDPIC module runs with this - * in the PIC base register. + /* The object's GOT. gotbase is where it ended up: the placed address of + * .got, or DT_PLTGOT for an FDPIC object, which runs with it in the PIC + * base register. gotsize is the extent of .got, and is also what says + * the object has one at all. */ uintptr_t gotbase; + size_t gotsize; /* Pool of function descriptors behind the writable segment. Reserved * when the segment is sized, and bounded by the relocation count. diff --git a/libs/libc/elf/elf_bind.c b/libs/libc/elf/elf_bind.c index 5409b79bfbb..4e0727222dd 100644 --- a/libs/libc/elf/elf_bind.c +++ b/libs/libc/elf/elf_bind.c @@ -337,7 +337,7 @@ static int libelf_relocate(FAR struct module_s *modp, /* Calculate the relocation address. */ - if (loadinfo->gotindex >= 0) + if (loadinfo->gotsize != 0) { if (sym->st_shndx == SHN_UNDEF) { @@ -345,8 +345,7 @@ static int libelf_relocate(FAR struct module_s *modp, * to the value of the symbol. */ - FAR Elf_Shdr *gotsec = &loadinfo->shdr[loadinfo->gotindex]; - FAR uintptr_t *gotaddr = (FAR uintptr_t *)(gotsec->sh_addr + + FAR uintptr_t *gotaddr = (FAR uintptr_t *)(loadinfo->gotbase + *((FAR uintptr_t *)(dstsec->sh_addr + rel->r_offset))); *gotaddr = sym->st_value; diff --git a/libs/libc/elf/elf_load.c b/libs/libc/elf/elf_load.c index 6abdff25424..8536e8e5c68 100644 --- a/libs/libc/elf/elf_load.c +++ b/libs/libc/elf/elf_load.c @@ -366,13 +366,19 @@ static void libelf_set_emptysect_vma(FAR struct mod_loadinfo_s *loadinfo, * Read the section data into memory. Section addresses in the shdr[] are * updated to point to the corresponding position in the memory. * + * Input Parameters: + * loadinfo - The load state. + * gotidx - Section index of .got, which the caller has already looked + * up, or a negative value if the object has none. + * * Returned Value: * 0 (OK) is returned on success and a negated errno is returned on * failure. * ****************************************************************************/ -static inline int libelf_loadfile(FAR struct mod_loadinfo_s *loadinfo) +static inline int libelf_loadfile(FAR struct mod_loadinfo_s *loadinfo, + int gotidx) { FAR uint8_t *text = (FAR uint8_t *)loadinfo->textalloc; FAR uint8_t *data = (FAR uint8_t *)loadinfo->datastart; @@ -546,13 +552,29 @@ skipload: } } - /* Update GOT table */ + /* Note the GOT. The sections are placed by now, thus .got carries the + * address it will be read at. An FDPIC object's sections are never + * placed, and libelf_bind() takes its base from DT_PLTGOT instead. + */ - if (loadinfo->gotindex >= 0) + if (gotidx >= 0) { - FAR Elf_Shdr *gotshdr = &loadinfo->shdr[loadinfo->gotindex]; - FAR uintptr_t *got = (FAR uintptr_t *)gotshdr->sh_addr; - FAR uintptr_t *end = got + gotshdr->sh_size / sizeof(uintptr_t); + loadinfo->gotsize = loadinfo->shdr[gotidx].sh_size; + + if (!loadinfo->fdpic) + { + loadinfo->gotbase = loadinfo->shdr[gotidx].sh_addr; + } + } + + /* Update GOT table. An FDPIC object's entries are relocated through its + * own relocations, so there is nothing to do for one here. + */ + + if (loadinfo->gotbase != 0) + { + FAR uintptr_t *got = (FAR uintptr_t *)loadinfo->gotbase; + FAR uintptr_t *end = got + loadinfo->gotsize / sizeof(uintptr_t); for (; got < end; got++) { @@ -699,6 +721,7 @@ void libelf_pinrelease(FAR struct file **pinfile) int libelf_load(FAR struct mod_loadinfo_s *loadinfo) { + int gotidx; int ret; int i; @@ -714,10 +737,15 @@ int libelf_load(FAR struct mod_loadinfo_s *loadinfo) goto errout_with_buffers; } - loadinfo->gotindex = libelf_findsection(loadinfo, ".got"); - if (loadinfo->gotindex >= 0) + /* An object with a GOT is position independent, thus its read-only part + * may be able to stay where the filesystem holds it. Keep the index: + * libelf_loadfile() notes the section once it has placed it. + */ + + gotidx = libelf_findsection(loadinfo, ".got"); + if (gotidx >= 0) { - binfo("GOT section found! index %d\n", loadinfo->gotindex); + binfo("GOT section found! index %d\n", gotidx); libelf_xipacquire(loadinfo); } @@ -889,7 +917,7 @@ int libelf_load(FAR struct mod_loadinfo_s *loadinfo) /* Load ELF section data into memory */ - ret = libelf_loadfile(loadinfo); + ret = libelf_loadfile(loadinfo, gotidx); if (ret < 0) { berr("ERROR: libelf_loadfile failed: %d\n", ret); @@ -935,6 +963,7 @@ errout_with_buffers: #ifdef CONFIG_ARCH_ADDRENV int libelf_load_with_addrenv(FAR struct mod_loadinfo_s *loadinfo) { + int gotidx; int ret; binfo("loadinfo: %p\n", loadinfo); @@ -949,10 +978,15 @@ int libelf_load_with_addrenv(FAR struct mod_loadinfo_s *loadinfo) goto errout_with_buffers; } - loadinfo->gotindex = libelf_findsection(loadinfo, ".got"); - if (loadinfo->gotindex >= 0) + /* An object with a GOT is position independent, thus its read-only part + * may be able to stay where the filesystem holds it. Keep the index: + * libelf_loadfile() notes the section once it has placed it. + */ + + gotidx = libelf_findsection(loadinfo, ".got"); + if (gotidx >= 0) { - binfo("GOT section found! index %d\n", loadinfo->gotindex); + binfo("GOT section found! index %d\n", gotidx); libelf_xipacquire(loadinfo); } @@ -980,7 +1014,7 @@ int libelf_load_with_addrenv(FAR struct mod_loadinfo_s *loadinfo) goto errout_with_buffers; } - ret = libelf_loadfile(loadinfo); + ret = libelf_loadfile(loadinfo, gotidx); if (ret < 0) { berr("ERROR: libelf_loadfile failed: %d\n", ret); diff --git a/libs/libc/elf/elf_symbols.c b/libs/libc/elf/elf_symbols.c index b1feda244c5..3ba6fb5eeda 100644 --- a/libs/libc/elf/elf_symbols.c +++ b/libs/libc/elf/elf_symbols.c @@ -451,7 +451,7 @@ int libelf_symvalue(FAR struct module_s *modp, (uintptr_t)(sym->st_value + secbase)); sym->st_value += secbase; - if (loadinfo->gotindex >= 0) + if (loadinfo->gotsize != 0) { sym->st_value -= loadinfo->shdr[sym->st_shndx].sh_offset; }