/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include "machdep.h" #include "reloc.h" #include "msg.h" #include "_librtld.h" #include "alist.h" static const char *unknown = 0; /* Stash MSG_INTL(MSG_STR_UNKNOWN) */ /* * Process all relocation records. A new `Reloc' structure is allocated to * cache the processing decisions deduced, and these will be applied during * update_reloc(). * A count of the number of null relocations (i.e., relocations that will be * fixed and whoes records will be nulled out), data and function relocations * is maintained. This allows the relocation records themselves to be * rearranged (localized) later if necessary. Note that the number of function * relocations, although coounted, shouldn't differ from the original file, * the index of a .plt must be maintained to the index of its relocation record * within the associated relocation section. * * The intention behind this file is to maintain as much relocation logic as * possible in a generic form. */ int count_reloc(Cache *cache, Cache *_cache, Rt_map *lmp, int flags, Addr addr, Xword *null, Xword *data, Xword *func, Alist *nodirect) { Rel *rel; Reloc *reloc; Shdr *shdr; Xword ent, cnt, _cnt; Sym *syms; const char *strs; Cache *__cache; Xword pltndx = 0; /* * Determine the number of relocation entries we'll be dealing with. */ shdr = _cache->c_shdr; rel = (Rel *)_cache->c_data->d_buf; ent = shdr->sh_entsize; cnt = shdr->sh_size / ent; /* * Allocate a relocation structure for this relocation section. */ if ((reloc = calloc(cnt, sizeof (Reloc))) == 0) return (1); _cache->c_info = (void *)reloc; /* * Determine the relocations associated symbol and string table. */ __cache = &cache[shdr->sh_link]; syms = (Sym *)__cache->c_data->d_buf; shdr = __cache->c_shdr; __cache = &cache[shdr->sh_link]; strs = (const char *)__cache->c_data->d_buf; /* * Loop through the relocation table. */ for (_cnt = 0; _cnt < cnt; _cnt++, reloc++, rel = (Rel *)((uintptr_t)rel + ent)) { const char *name; /* LINTED */ uchar_t type = (uchar_t)ELF_R_TYPE(rel->r_info); uchar_t bind; ulong_t offset = rel->r_offset + addr; Rt_map *_lmp; int _bound, _weak; ulong_t rsymndx = ELF_R_SYM(rel->r_info); Slookup sl; uint_t binfo; Sym *_sym, *sym = (syms + rsymndx); if (type == M_R_JMP_SLOT) reloc->r_pltndx = ++pltndx; /* * Analyze the case where no relocations are to be applied. */ if ((flags & RTLD_REL_ALL) == 0) { /* * Don't apply any relocations to the new image but * insure their offsets are incremented to reflect any * new fixed address. */ reloc->r_flags = FLG_R_INC; /* * Undo any relocations that might have already been * applied to the memory image. */ if (flags & RTLD_MEMORY) { reloc->r_flags |= FLG_R_UNDO; /* * If a copy relocation is involved we'll need * to know the size of the copy. */ if (type == M_R_COPY) reloc->r_size = sym->st_size; else reloc->r_size = 0; } /* * Save the objects new address. */ reloc->r_value = addr; if (type == M_R_JMP_SLOT) (*func)++; else (*data)++; continue; } /* * Determine the symbol binding of the relocation. Don't assume * that relative relocations are simply M_R_RELATIVE. Although * a pic generated shared object can normally be viewed as * having relative and non-relative relocations, a non-pic * shared object will contain a number of relocations against * local symbols (normally sections). If a relocation is * against a local symbol it qualifies as a relative relocation. */ if ((type == M_R_RELATIVE) || (type == M_R_NONE) || (ELF_ST_BIND(sym->st_info) == STB_LOCAL)) bind = STB_LOCAL; else bind = STB_GLOBAL; /* * Analyze the case where only relative relocations are to be * applied. */ if ((flags & RTLD_REL_ALL) == RTLD_REL_RELATIVE) { if (flags & RTLD_MEMORY) { if (bind == STB_LOCAL) { /* * Save the relative relocations from * the memory image. The data itself * might already have been relocated, * thus clear the relocation record so * that it will not be performed again. */ reloc->r_flags = FLG_R_CLR; (*null)++; } else { /* * Any non-relative relocation must be * undone, and the relocation records * offset updated to any new fixed * address. */ reloc->r_flags = (FLG_R_UNDO | FLG_R_INC); reloc->r_value = addr; if (type == M_R_JMP_SLOT) (*func)++; else (*data)++; } } else { if (bind == STB_LOCAL) { /* * Apply relative relocation to the * file image. Clear the relocation * record so that it will not be * performed again. */ reloc->r_flags = (FLG_R_APPLY | FLG_R_CLR); reloc->r_value = addr; if (IS_PC_RELATIVE(type)) reloc->r_value -= offset; if (unknown == 0) unknown = MSG_INTL(MSG_STR_UNKNOWN); reloc->r_name = unknown; (*null)++; } else { /* * Any non-relative relocation should be * left alone, but its offset should be * updated to any new fixed address. */ reloc->r_flags = FLG_R_INC; reloc->r_value = addr; if (type == M_R_JMP_SLOT) (*func)++; else (*data)++; } } continue; } /* * Analyze the case where more than just relative relocations * are to be applied. */ if (bind == STB_LOCAL) { if (flags & RTLD_MEMORY) { /* * Save the relative relocations from the memory * image. The data itself has already been * relocated, thus clear the relocation record * so that it will not be performed again. */ reloc->r_flags = FLG_R_CLR; } else { /* * Apply relative relocation to the file image. * Clear the relocation record so that it will * not be performed again. */ reloc->r_flags = (FLG_R_APPLY | FLG_R_CLR); reloc->r_value = addr; if (IS_PC_RELATIVE(type)) reloc->r_value -= offset; if (unknown == 0) unknown = MSG_INTL(MSG_STR_UNKNOWN); reloc->r_name = unknown; } (*null)++; continue; } /* * At this point we're dealing with a non-relative relocation * that requires the symbol definition. */ name = strs + sym->st_name; /* * Find the symbol. As the object being investigated is already * a part of this process, the symbol lookup will likely * succeed. However, because of lazy binding, there is still * the possibility of a dangling .plt relocation. dldump() * users might be encouraged to set LD_FLAGS=loadavail (crle(1) * does this for them). */ sl.sl_name = name; sl.sl_cmap = lmp; sl.sl_imap = LIST(lmp)->lm_head; sl.sl_hash = 0; sl.sl_rsymndx = rsymndx; sl.sl_rsym = sym; if (type == M_R_COPY) sl.sl_flags = LKUP_COPY; else sl.sl_flags = LKUP_DEFT; _bound = _weak = 0; _sym = sym; if ((sym = lookup_sym(&sl, &_lmp, &binfo)) != 0) { /* * Determine from the various relocation requirements * whether this binding is appropriate. If we're called * from crle(1), RTLD_CONFSET is set, then only inspect * objects selected from the configuration file * (FL1_RT_CONFSET was set during load()). */ if (!(flags & RTLD_CONFSET) || (FLAGS1(_lmp) & FL1_RT_CONFSET)) { if (((flags & RTLD_REL_ALL) == RTLD_REL_ALL) || ((flags & RTLD_REL_EXEC) && (FLAGS(_lmp) & FLG_RT_ISMAIN)) || ((flags & RTLD_REL_DEPENDS) && (!(FLAGS(_lmp) & FLG_RT_ISMAIN))) || ((flags & RTLD_REL_PRELOAD) && (FLAGS(_lmp) & FLG_RT_PRELOAD)) || ((flags & RTLD_REL_SELF) && (lmp == _lmp))) { Aliste off; Word *ndx; _bound = 1; /* * If this symbol is explicitly defined * as nodirect, don't allow any local * binding. */ for (ALIST_TRAVERSE(nodirect, off, ndx)) { if (*ndx == rsymndx) { _bound = 0; break; } } } } } else { /* * If this is a weak reference and we've been asked to * bind unresolved weak references consider ourself * bound. This category is typically set by clre(1) for * an application cache. */ if ((ELF_ST_BIND(_sym->st_info) == STB_WEAK) && (_sym->st_shndx == SHN_UNDEF) && (flags & RTLD_REL_WEAK)) _bound = _weak = 1; } if (flags & RTLD_MEMORY) { if (_bound) { /* * We know that all data relocations will have * been performed at process startup thus clear * the relocation record so that it will not be * performed again. However, we don't know what * function relocations have been performed * because of lazy binding - regardless, we can * leave all the function relocation records in * place, because if the function has already * been bound the record won't be referenced * anyway. In the case of using LD_BIND_NOW, * a function may be bound twice - so what. */ if (type == M_R_JMP_SLOT) { reloc->r_flags = FLG_R_INC; (*func)++; } else { if (type != M_R_COPY) reloc->r_flags = FLG_R_CLR; (*null)++; } } else { /* * Clear any unrequired relocation. */ reloc->r_flags = FLG_R_UNDO | FLG_R_INC; reloc->r_value = addr; if (type == M_R_JMP_SLOT) (*func)++; else (*data)++; } } else { if (_bound) { /* * Apply the global relocation to the file * image. Clear the relocation record so that * it will not be performed again. */ if (_weak) { reloc->r_value = 0; reloc->r_size = 0; } else { reloc->r_value = sym->st_value; if (IS_PC_RELATIVE(type)) reloc->r_value -= offset; if ((!(FLAGS(_lmp) & FLG_RT_FIXED)) && (sym->st_shndx != SHN_ABS)) reloc->r_value += ADDR(_lmp); reloc->r_size = sym->st_size; } reloc->r_flags = FLG_R_APPLY | FLG_R_CLR; reloc->r_name = name; if (type == M_R_JMP_SLOT) (*func)++; else (*null)++; } else { /* * Do not apply any unrequired relocations. */ reloc->r_flags = FLG_R_INC; reloc->r_value = addr; if (type == M_R_JMP_SLOT) (*func)++; else (*data)++; } } } return (0); } /* * Perform any relocation updates to the new image using the information from * the `Reloc' structure constructed during count_reloc(). */ void update_reloc(Cache *ocache, Cache *icache, Cache *_icache, const char *name, Rt_map *lmp, Rel **null, Rel **data, Rel **func) { Shdr *shdr; Rel *rel; Reloc *reloc; Xword ent, cnt, _cnt; Cache *orcache, *ircache = 0; Half ndx; /* * Set up to read the output relocation table. */ shdr = _icache->c_shdr; rel = (Rel *)_icache->c_data->d_buf; reloc = (Reloc *)_icache->c_info; ent = shdr->sh_entsize; cnt = shdr->sh_size / ent; /* * Loop through the relocation table. */ for (_cnt = 0; _cnt < cnt; _cnt++, reloc++, rel = (Rel *)((uintptr_t)rel + ent)) { uchar_t *iaddr, *oaddr; /* LINTED */ uchar_t type = (uchar_t)ELF_R_TYPE(rel->r_info); Addr off, bgn, end; /* * Ignore null relocations (these may have been created from a * previous dldump() of this image). */ if (type == M_R_NONE) { (*null)++; continue; } /* * Determine the section being relocated if we haven't already * done so (we may have had to skip over some null relocation to * get to the first valid offset). The System V ABI states that * a relocation sections sh_info field indicates the section * that must be relocated. However, on Intel it seems that the * .rel.plt sh_info records the section index of the .plt, when * in fact it's the .got that gets relocated. In addition we * now create combined relocation sections with -zcomreloc. To * generically be able to cope with these anomalies, search for * the appropriate section to be relocated by comparing the * offset of the first relocation record against each sections * offset and size. */ /* BEGIN CSTYLED */ #if !defined(__lint) if ((ircache == (Cache *)0) || (rel->r_offset < bgn) || (rel->r_offset > end)) { #else /* * lint sees `bgn' and `end' as potentially referenced * before being set. */ if (ircache == (Cache *)0) { #endif _icache = icache; _icache++; for (ndx = 1; _icache->c_flags != FLG_C_END; ndx++, _icache++) { shdr = _icache->c_shdr; bgn = shdr->sh_addr; end = bgn + shdr->sh_size; if ((rel->r_offset >= bgn) && (rel->r_offset <= end)) break; } ircache = &icache[ndx]; orcache = &ocache[ndx]; } /* END CSTYLED */ /* * Determine the relocation location of both the input and * output data. Take into account that an input section may be * NOBITS (ppc .plt for example). */ off = rel->r_offset - ircache->c_shdr->sh_addr; if (ircache->c_data->d_buf) iaddr = (uchar_t *)ircache->c_data->d_buf + off; else iaddr = 0; oaddr = (uchar_t *)orcache->c_data->d_buf + off; /* * Apply the relocation to the new output image. Any base * address, or symbol value, will have been saved in the reloc * structure during count_reloc(). */ if (reloc->r_flags & FLG_R_APPLY) apply_reloc(rel, reloc, name, oaddr, lmp); /* * Undo any relocation that might already been applied to the * memory image by the runtime linker. Using the original * file, determine the relocation offset original value and * restore the new image to that value. */ if ((reloc->r_flags & FLG_R_UNDO) && (FLAGS(lmp) & FLG_RT_RELOCED)) undo_reloc(rel, oaddr, iaddr, reloc); /* * If a relocation has been applied then the relocation record * should be cleared so that the relocation isn't applied again * when the new image is used. */ if (reloc->r_flags & FLG_R_CLR) { if (type == M_R_JMP_SLOT) { clear_reloc(*func); *func = (Rel *)((uintptr_t)*func + ent); } else { clear_reloc(*null); *null = (Rel *)((uintptr_t)*null + ent); } } /* * If a relocation isn't applied, update the relocation record * to take into account the new address of the image. */ if (reloc->r_flags & FLG_R_INC) { if (type == M_R_JMP_SLOT) { inc_reloc(*func, rel, reloc, oaddr, iaddr); *func = (Rel *)((uintptr_t)*func + ent); } else { inc_reloc(*data, rel, reloc, oaddr, iaddr); *data = (Rel *)((uintptr_t)*data + ent); } } } }