/**************************************************************************** * libs/libc/modlib/modlib_bind.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include "libc.h" #include "modlib/modlib.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define I_REL 0 /* Index into relxxx[] arrays for relocations */ #define I_PLT 1 /* ... for PLTs */ #define N_RELS 2 /* Number of relxxx[] indexes */ /**************************************************************************** * Private Types ****************************************************************************/ /* REVISIT: This naming breaks the NuttX coding standard, but is consistent * with legacy naming of other ELF types. */ typedef struct { dq_entry_t entry; Elf_Sym sym; int idx; } Elf_SymCache; struct { int stroff; /* offset to string table */ int symoff; /* offset to symbol table */ int lsymtab; /* size of symbol table */ int relentsz[2]; /* size of relocation entry */ int reloff[2]; /* offset to the relocation section */ int relsz[2]; /* size of relocation table */ int relrela[2]; /* type of relocation type - 0: DT_REL / 1: DT_RELA */ } reldata; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: modlib_readrels * * Description: * Read the (ELF_Rel structure * buffer count) into memory. * ****************************************************************************/ static inline int modlib_readrels(FAR struct mod_loadinfo_s *loadinfo, FAR const Elf_Shdr *relsec, int index, FAR Elf_Rel *rels, int count) { off_t offset; int size; /* Verify that the symbol table index lies within symbol table */ if (index < 0 || index > (relsec->sh_size / sizeof(Elf_Rel))) { berr("ERROR: Bad relocation symbol index: %d\n", index); return -EINVAL; } /* Get the file offset to the symbol table entry */ offset = sizeof(Elf_Rel) * index; size = sizeof(Elf_Rel) * count; if (offset + size > relsec->sh_size) { size = relsec->sh_size - offset; } /* And, finally, read the symbol table entry into memory */ return modlib_read(loadinfo, (FAR uint8_t *)rels, size, relsec->sh_offset + offset); } /**************************************************************************** * Name: modlib_readrelas * * Description: * Read the (ELF_Rela structure * buffer count) into memory. * ****************************************************************************/ static inline int modlib_readrelas(FAR struct mod_loadinfo_s *loadinfo, FAR const Elf_Shdr *relsec, int index, FAR Elf_Rela *relas, int count) { off_t offset; int size; /* Verify that the symbol table index lies within symbol table */ if (index < 0 || index > (relsec->sh_size / sizeof(Elf_Rela))) { berr("ERROR: Bad relocation symbol index: %d\n", index); return -EINVAL; } /* Get the file offset to the symbol table entry */ offset = sizeof(Elf_Rela) * index; size = sizeof(Elf_Rela) * count; if (offset + size > relsec->sh_size) { size = relsec->sh_size - offset; } /* And, finally, read the symbol table entry into memory */ return modlib_read(loadinfo, (FAR uint8_t *)relas, size, relsec->sh_offset + offset); } /**************************************************************************** * Name: modlib_relocate and modlib_relocateadd * * Description: * Perform all relocations associated with a section. * * Returned Value: * 0 (OK) is returned on success and a negated errno is returned on * failure. * ****************************************************************************/ static int modlib_relocate(FAR struct module_s *modp, FAR struct mod_loadinfo_s *loadinfo, int relidx) { FAR Elf_Shdr *relsec = &loadinfo->shdr[relidx]; FAR Elf_Shdr *dstsec = &loadinfo->shdr[relsec->sh_info]; FAR Elf_Rel *rels; FAR Elf_Rel *rel; FAR Elf_SymCache *cache; FAR Elf_Sym *sym; FAR dq_entry_t *e; dq_queue_t q; uintptr_t addr; int symidx; int ret = OK; int i; int j; rels = lib_malloc(CONFIG_MODLIB_RELOCATION_BUFFERCOUNT * sizeof(Elf_Rel)); if (!rels) { berr("Failed to allocate memory for elf relocation rels\n"); return -ENOMEM; } dq_init(&q); /* Examine each relocation in the section. 'relsec' is the section * containing the relations. 'dstsec' is the section containing the data * to be relocated. */ for (i = j = 0; i < relsec->sh_size / sizeof(Elf_Rel); i++) { /* Read the relocation entry into memory */ rel = &rels[i % CONFIG_MODLIB_RELOCATION_BUFFERCOUNT]; if (!(i % CONFIG_MODLIB_RELOCATION_BUFFERCOUNT)) { ret = modlib_readrels(loadinfo, relsec, i, rels, CONFIG_MODLIB_RELOCATION_BUFFERCOUNT); if (ret < 0) { berr("ERROR: Section %d reloc %d: " "Failed to read relocation entry: %d\n", relidx, i, ret); break; } } /* Get the symbol table index for the relocation. This is contained * in a bit-field within the r_info element. */ symidx = ELF_R_SYM(rel->r_info); /* First try the cache */ sym = NULL; for (e = dq_peek(&q); e; e = dq_next(e)) { cache = (FAR Elf_SymCache *)e; if (cache->idx == symidx) { dq_rem(&cache->entry, &q); dq_addfirst(&cache->entry, &q); sym = &cache->sym; break; } } /* If the symbol was not found in the cache, we will need to read the * symbol from the file. */ if (sym == NULL) { if (j < CONFIG_MODLIB_SYMBOL_CACHECOUNT) { cache = lib_malloc(sizeof(Elf_SymCache)); if (!cache) { berr("Failed to allocate memory for elf symbols\n"); ret = -ENOMEM; break; } j++; } else { cache = (FAR Elf_SymCache *)dq_remlast(&q); } sym = &cache->sym; /* Read the symbol table entry into memory */ ret = modlib_readsym(loadinfo, symidx, sym, &loadinfo->shdr[loadinfo->symtabidx]); if (ret < 0) { berr("ERROR: Section %d reloc %d: " "Failed to read symbol[%d]: %d\n", relidx, i, symidx, ret); lib_free(cache); break; } /* Get the value of the symbol (in sym.st_value) */ ret = modlib_symvalue(modp, loadinfo, sym, loadinfo->shdr[loadinfo->strtabidx].sh_offset); if (ret < 0) { /* The special error -ESRCH is returned only in one condition: * The symbol has no name. * * There are a few relocations for a few architectures that do * no depend upon a named symbol. We don't know if that is the * case here, but we will use a NULL symbol pointer to indicate * that case to up_relocate(). That function can then do what * is best. */ if (ret == -ESRCH) { berr("ERROR: Section %d reloc %d: " "Undefined symbol[%d] has no name: %d\n", relidx, i, symidx, ret); } else { berr("ERROR: Section %d reloc %d: " "Failed to get value of symbol[%d]: %d\n", relidx, i, symidx, ret); lib_free(cache); break; } } cache->idx = symidx; dq_addfirst(&cache->entry, &q); } if (sym->st_shndx == SHN_UNDEF && sym->st_name == 0) { sym = NULL; } /* Calculate the relocation address. */ if (rel->r_offset + sizeof(uint32_t) > dstsec->sh_size) { berr("ERROR: Section %d reloc %d: " "Relocation address out of range, " "offset %" PRIuPTR " size %ju\n", relidx, i, (uintptr_t)rel->r_offset, (uintmax_t)dstsec->sh_size); ret = -EINVAL; break; } addr = dstsec->sh_addr + rel->r_offset; /* Now perform the architecture-specific relocation */ ret = up_relocate(rel, sym, addr); if (ret < 0) { berr("ERROR: Section %d reloc %d: Relocation failed: %d\n", relidx, i, ret); break; } } lib_free(rels); while ((e = dq_peek(&q)) != NULL) { dq_rem(e, &q); lib_free(e); } return ret; } static int modlib_relocateadd(FAR struct module_s *modp, FAR struct mod_loadinfo_s *loadinfo, int relidx) { FAR Elf_Shdr *relsec = &loadinfo->shdr[relidx]; FAR Elf_Shdr *dstsec = &loadinfo->shdr[relsec->sh_info]; FAR Elf_Rela *relas; FAR Elf_Rela *rela; FAR Elf_SymCache *cache; FAR Elf_Sym *sym; FAR dq_entry_t *e; dq_queue_t q; uintptr_t addr; int symidx; int ret = OK; int i; int j; relas = lib_malloc(CONFIG_MODLIB_RELOCATION_BUFFERCOUNT * sizeof(Elf_Rela)); if (!relas) { berr("Failed to allocate memory for elf relocation relas\n"); return -ENOMEM; } dq_init(&q); /* Examine each relocation in the section. 'relsec' is the section * containing the relations. 'dstsec' is the section containing the data * to be relocated. */ for (i = j = 0; i < relsec->sh_size / sizeof(Elf_Rela); i++) { /* Read the relocation entry into memory */ rela = &relas[i % CONFIG_MODLIB_RELOCATION_BUFFERCOUNT]; if (!(i % CONFIG_MODLIB_RELOCATION_BUFFERCOUNT)) { ret = modlib_readrelas(loadinfo, relsec, i, relas, CONFIG_MODLIB_RELOCATION_BUFFERCOUNT); if (ret < 0) { berr("ERROR: Section %d reloc %d: " "Failed to read relocation entry: %d\n", relidx, i, ret); break; } } /* Get the symbol table index for the relocation. This is contained * in a bit-field within the r_info element. */ symidx = ELF_R_SYM(rela->r_info); /* First try the cache */ sym = NULL; for (e = dq_peek(&q); e; e = dq_next(e)) { cache = (FAR Elf_SymCache *)e; if (cache->idx == symidx) { dq_rem(&cache->entry, &q); dq_addfirst(&cache->entry, &q); sym = &cache->sym; break; } } /* If the symbol was not found in the cache, we will need to read the * symbol from the file. */ if (sym == NULL) { if (j < CONFIG_MODLIB_SYMBOL_CACHECOUNT) { cache = lib_malloc(sizeof(Elf_SymCache)); if (!cache) { berr("Failed to allocate memory for elf symbols\n"); ret = -ENOMEM; break; } j++; } else { cache = (FAR Elf_SymCache *)dq_remlast(&q); } sym = &cache->sym; /* Read the symbol table entry into memory */ ret = modlib_readsym(loadinfo, symidx, sym, &loadinfo->shdr[loadinfo->symtabidx]); if (ret < 0) { berr("ERROR: Section %d reloc %d: " "Failed to read symbol[%d]: %d\n", relidx, i, symidx, ret); lib_free(cache); break; } /* Get the value of the symbol (in sym.st_value) */ ret = modlib_symvalue(modp, loadinfo, sym, loadinfo->shdr[loadinfo->strtabidx].sh_offset); if (ret < 0) { /* The special error -ESRCH is returned only in one condition: * The symbol has no name. * * There are a few relocations for a few architectures that do * no depend upon a named symbol. We don't know if that is the * case here, but we will use a NULL symbol pointer to indicate * that case to up_relocate(). That function can then do what * is best. */ if (ret == -ESRCH) { berr("ERROR: Section %d reloc %d: " "Undefined symbol[%d] has no name: %d\n", relidx, i, symidx, ret); } else { berr("ERROR: Section %d reloc %d: " "Failed to get value of symbol[%d]: %d\n", relidx, i, symidx, ret); lib_free(cache); break; } } cache->idx = symidx; dq_addfirst(&cache->entry, &q); } if (sym->st_shndx == SHN_UNDEF && sym->st_name == 0) { sym = NULL; } /* Calculate the relocation address. */ if (rela->r_offset + sizeof(uint32_t) > dstsec->sh_size) { berr("ERROR: Section %d reloc %d: " "Relocation address out of range, " "offset %" PRIuPTR " size %ju\n", relidx, i, (uintptr_t)rela->r_offset, (uintmax_t)dstsec->sh_size); ret = -EINVAL; break; } addr = dstsec->sh_addr + rela->r_offset; /* Now perform the architecture-specific relocation */ ret = up_relocateadd(rela, sym, addr); if (ret < 0) { berr("ERROR: Section %d reloc %d: Relocation failed: %d\n", relidx, i, ret); break; } } lib_free(relas); while ((e = dq_peek(&q)) != NULL) { dq_rem(e, &q); lib_free(e); } return ret; } /**************************************************************************** * Name: modlib_relocatedyn * * Description: * Perform all relocations associated with a dynamic section. * * Returned Value: * 0 (OK) is returned on success and a negated errno is returned on * failure. * ****************************************************************************/ static int modlib_relocatedyn(FAR struct module_s *modp, FAR struct mod_loadinfo_s *loadinfo, int relidx) { FAR Elf_Shdr *shdr = &loadinfo->shdr[relidx]; FAR Elf_Shdr *symhdr; FAR Elf_Dyn *dyn = NULL; FAR Elf_Rel *rels = NULL; FAR Elf_Rel *rel; FAR Elf_Rela *relas = NULL; FAR Elf_Rela *rela; FAR Elf_Sym *sym = NULL; uintptr_t addr; int ret; int i; int idx_rel; int idx_sym; dyn = lib_malloc(shdr->sh_size); ret = modlib_read(loadinfo, (FAR uint8_t *)dyn, shdr->sh_size, shdr->sh_offset); if (ret < 0) { berr("Failed to read dynamic section header"); return ret; } /* Assume DT_RELA to get maximum size required */ rels = lib_malloc(CONFIG_MODLIB_RELOCATION_BUFFERCOUNT * sizeof(Elf_Rela)); if (!rels) { berr("Failed to allocate memory for elf relocation rels\n"); lib_free(dyn); return -ENOMEM; } memset((void *)&reldata, 0, sizeof(reldata)); relas = (FAR Elf_Rela *)rels; for (i = 0; dyn[i].d_tag != DT_NULL; i++) { switch (dyn[i].d_tag) { case DT_REL: reldata.reloff[I_REL] = dyn[i].d_un.d_val; break; case DT_RELSZ: reldata.relsz[I_REL] = dyn[i].d_un.d_val; break; case DT_RELENT: reldata.relentsz[I_REL] = dyn[i].d_un.d_val; break; case DT_SYMTAB: reldata.symoff = dyn[i].d_un.d_val; break; case DT_STRTAB: reldata.stroff = dyn[i].d_un.d_val; break; case DT_JMPREL: reldata.reloff[I_PLT] = dyn[i].d_un.d_val; break; case DT_PLTRELSZ: reldata.relsz[I_PLT] = dyn[i].d_un.d_val; break; case DT_PLTREL: if (dyn[i].d_un.d_val == DT_REL) { reldata.relentsz[I_PLT] = sizeof(Elf_Rel); reldata.relrela[I_PLT] = 0; } else { reldata.relentsz[I_PLT] = sizeof(Elf_Rela); reldata.relrela[I_PLT] = 1; } break; } } symhdr = &loadinfo->shdr[loadinfo->dsymtabidx]; sym = lib_malloc(symhdr->sh_size); if (!sym) { berr("Error obtaining storage for dynamic symbol table"); lib_free(rels); lib_free(dyn); return -ENOMEM; } ret = modlib_read(loadinfo, (FAR uint8_t *)sym, symhdr->sh_size, symhdr->sh_offset); if (ret < 0) { berr("Error reading dynamic symbol table - %d", ret); lib_free(sym); lib_free(rels); lib_free(dyn); return ret; } reldata.lsymtab = reldata.stroff - reldata.symoff; for (idx_rel = 0; idx_rel < N_RELS; idx_rel++) { int lrelent; if ((reldata.relsz[idx_rel] == 0) || (reldata.reloff[idx_rel] == 0)) { continue; } /* Examine each relocation in the .rel.* section. */ ret = OK; lrelent = reldata.relsz[idx_rel] / reldata.relentsz[idx_rel]; for (i = 0; i < lrelent; i++) { /* Process each relocation entry * - we cheat by using the fact the 1st two fields of Elf_Rel * and Elf_Rela are identical so can do things based on the * former until it's important */ if (reldata.relrela[idx_rel] == 0) { rel = &rels[i % CONFIG_MODLIB_RELOCATION_BUFFERCOUNT]; rela = (Elf_Rela *)rel; /* Just to keep the compiler happy */ } else { rela = &relas[i % CONFIG_MODLIB_RELOCATION_BUFFERCOUNT]; rel = (Elf_Rel *)rela; } if (!(i % CONFIG_MODLIB_RELOCATION_BUFFERCOUNT)) { size_t relsize = (sizeof(Elf_Rela) * CONFIG_MODLIB_RELOCATION_BUFFERCOUNT); if (reldata.relsz[idx_rel] < relsize) { relsize = reldata.relsz[idx_rel]; } ret = modlib_read(loadinfo, (FAR uint8_t *)rels, relsize, reldata.reloff[idx_rel] + i * sizeof(Elf_Rel)); if (ret < 0) { berr("ERROR: Section %d reloc %d:" "Failed to read relocation entry: %d\n", relidx, i, ret); break; } } /* Calculate the relocation address. */ if (rel->r_offset < 0) { berr("ERROR: Section %d reloc %d:" "Relocation address out of range, offset %u\n", relidx, i, (int)rel->r_offset); ret = -EINVAL; lib_free(sym); lib_free(rels); lib_free(dyn); return ret; } /* Now perform the architecture-specific relocation */ if ((idx_sym = ELF_R_SYM(rel->r_info)) != 0) { if (sym[idx_sym].st_shndx == SHN_UNDEF) /* We have an external reference */ { FAR void *ep; ep = modlib_findglobal(modp, loadinfo, symhdr, &sym[idx_sym]); if ((ep == NULL) && (ELF_ST_BIND(sym[idx_sym].st_info) != STB_WEAK)) { berr("ERROR: Unable to resolve addr of ext ref %s\n", loadinfo->iobuffer); ret = -EINVAL; lib_free(sym); lib_free(rels); lib_free(dyn); return ret; } addr = rel->r_offset + loadinfo->textalloc; if (reldata.relrela[idx_rel] == 1) { addr += rela->r_addend; } *(FAR uintptr_t *)addr = (uintptr_t)ep; } } else { Elf_Sym dynsym; addr = rel->r_offset - loadinfo->datasec + loadinfo->datastart; if (reldata.relrela[idx_rel] == 1) { addr += rela->r_addend; } if ((*(FAR uint32_t *)addr) < loadinfo->datasec) { dynsym.st_value = *(FAR uint32_t *)addr + loadinfo->textalloc; } else { dynsym.st_value = *(FAR uint32_t *)addr - loadinfo->datasec + loadinfo->datastart; } ret = up_relocate(rel, &dynsym, addr); } if (ret < 0) { berr("ERROR: Section %d reloc %d: Relocation failed: %d\n", relidx, i, ret); lib_free(sym); lib_free(rels); lib_free(dyn); return ret; } } } /* Iterate through the dynamic symbol table looking for global symbols * to put in our own symbol table for use with dlgetsym() */ /* Relocate the entries in the table */ for (i = 0; i < symhdr->sh_size / sizeof(Elf_Sym); i++) { FAR Elf_Shdr *s = &loadinfo->shdr[sym[i].st_shndx]; if (sym[i].st_shndx != SHN_UNDEF) { if (s->sh_addr < loadinfo->datasec) { sym[i].st_value = sym[i].st_value + loadinfo->textalloc; } else { sym[i].st_value = sym[i].st_value - loadinfo->datasec + loadinfo->datastart; } } } ret = modlib_insertsymtab(modp, loadinfo, symhdr, sym); lib_free(sym); lib_free(rels); lib_free(dyn); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: modlib_bind * * Description: * Bind the imported symbol names in the loaded module described by * 'loadinfo' using the exported symbol values provided by * modlib_setsymtab(). * * Input Parameters: * modp - Module state information * loadinfo - Load state information * * Returned Value: * 0 (OK) is returned on success and a negated errno is returned on * failure. * ****************************************************************************/ int modlib_bind(FAR struct module_s *modp, FAR struct mod_loadinfo_s *loadinfo) { int ret; int i; /* Find the symbol and string tables */ ret = modlib_findsymtab(loadinfo); if (ret < 0) { return ret; } /* Process relocations in every allocated section */ for (i = 1; i < loadinfo->ehdr.e_shnum; i++) { /* Get the index to the relocation section */ int infosec = loadinfo->shdr[i].sh_info; if (infosec >= loadinfo->ehdr.e_shnum) { continue; } if (loadinfo->ehdr.e_type == ET_DYN) { modp->dynamic = 1; switch (loadinfo->shdr[i].sh_type) { case SHT_DYNAMIC: ret = modlib_relocatedyn(modp, loadinfo, i); break; case SHT_DYNSYM: loadinfo->dsymtabidx = i; break; case SHT_INIT_ARRAY: loadinfo->initarr = loadinfo->shdr[i].sh_addr - loadinfo->datasec + loadinfo->datastart; loadinfo->ninit = loadinfo->shdr[i].sh_size / sizeof(uintptr_t); break; case SHT_FINI_ARRAY: loadinfo->finiarr = loadinfo->shdr[i].sh_addr - loadinfo->datasec + loadinfo->datastart; loadinfo->nfini = loadinfo->shdr[i].sh_size / sizeof(uintptr_t); break; case SHT_PREINIT_ARRAY: loadinfo->preiarr = loadinfo->shdr[i].sh_addr - loadinfo->datasec + loadinfo->datastart; loadinfo->nprei = loadinfo->shdr[i].sh_size / sizeof(uintptr_t); break; } } else { modp->dynamic = 0; /* Make sure that the section is allocated. We can't * relocate sections that were not loaded into memory. */ if ((loadinfo->shdr[infosec].sh_flags & SHF_ALLOC) == 0) { continue; } /* Process the relocations by type */ switch (loadinfo->shdr[i].sh_type) { case SHT_REL: ret = modlib_relocate(modp, loadinfo, i); break; case SHT_RELA: ret = modlib_relocateadd(modp, loadinfo, i); break; } } if (ret < 0) { break; } } /* Ensure that the I and D caches are coherent before starting the newly * loaded module by cleaning the D cache (i.e., flushing the D cache * contents to memory and invalidating the I cache). */ up_coherent_dcache(loadinfo->textalloc, loadinfo->textsize); up_coherent_dcache(loadinfo->datastart, loadinfo->datasize); return ret; }