/* * 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 (c) 1988 AT&T * All Rights Reserved * * Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, Joyent, Inc. All rights reserved. * Copyright 2024 Oxide Computer Company */ /* * Map file parsing and input section to output segment mapping. */ #include #include #include #include "msg.h" #include "_libld.h" /* * Each time a section is placed, the function set_addralign() * is called. This function performs: * * - if the section is from an external file, check if this is empty or not. * If not, we know the segment this section will belong needs a program * header. (Of course, the program is needed only if this section falls * into a loadable segment.) * - compute the Least Common Multiplier for setting the segment alignment. */ static void set_addralign(Ofl_desc *ofl, Os_desc *osp, Is_desc *isp) { Shdr *shdr = isp->is_shdr; /* A discarded section has no influence on the output */ if (isp->is_flags & FLG_IS_DISCARD) return; /* * If this section has data or will be assigned data * later, mark this segment not-empty. */ if ((shdr->sh_size != 0) || ((isp->is_flags & FLG_IS_EXTERNAL) == 0)) osp->os_sgdesc->sg_flags |= FLG_SG_PHREQ; if ((ofl->ofl_dtflags_1 & DF_1_NOHDR) && (osp->os_sgdesc->sg_phdr).p_type != PT_LOAD) return; osp->os_sgdesc->sg_align = ld_lcm(osp->os_sgdesc->sg_align, shdr->sh_addralign); } /* * Return the first input descriptor for a given output descriptor, * or NULL if there are none. */ Is_desc * ld_os_first_isdesc(Os_desc *osp) { int i; for (i = 0; i < OS_ISD_NUM; i++) { APlist *ap_isdesc = osp->os_isdescs[i]; if (aplist_nitems(ap_isdesc) > 0) return ((Is_desc *)ap_isdesc->apl_data[0]); } return (NULL); } /* * Attach an input section to an output section * * entry: * ofl - File descriptor * osp - Output section descriptor * isp - Input section descriptor * mapfile_sort - True (1) if segment supports mapfile specified ordering * of otherwise unordered input sections, and False (0) otherwise. * * exit: * - The input section has been attached to the output section * - If the input section is a candidate for string table merging, * then it is appended to the output section's list of merge * candidates (os_mstridescs). * * On success, returns True (1). On failure, False (0). */ static int os_attach_isp(Ofl_desc *ofl, Os_desc *osp, Is_desc *isp, int mapfile_sort) { Aliste init_arritems; int os_isdescs_idx, do_append = 1; if ((isp->is_flags & FLG_IS_ORDERED) == 0) { init_arritems = AL_CNT_OS_ISDESCS; os_isdescs_idx = OS_ISD_DEFAULT; /* * If section ordering was specified for an unordered section * via the mapfile, then search in the OS_ISD_DEFAULT list * and insert it in the specified position. Ordered sections * are placed in ascending order before unordered sections * (sections with an is_ordndx value of zero). * * If no mapfile ordering was specified, we append it in * the usual way below. */ if (mapfile_sort && (isp->is_ordndx > 0)) { APlist *ap_isdesc = osp->os_isdescs[OS_ISD_DEFAULT]; Aliste idx2; Is_desc *isp2; for (APLIST_TRAVERSE(ap_isdesc, idx2, isp2)) { if (isp2->is_ordndx && (isp2->is_ordndx <= isp->is_ordndx)) continue; if (aplist_insert( &osp->os_isdescs[OS_ISD_DEFAULT], isp, init_arritems, idx2) == NULL) return (0); do_append = 0; break; } } } else { /* Ordered section (via shdr flags) */ Word shndx; /* SHF_ORDERED uses sh_info, SHF_LINK_ORDERED uses sh_link */ shndx = (isp->is_shdr->sh_flags & SHF_ORDERED) ? isp->is_shdr->sh_info : isp->is_shdr->sh_link; if (shndx == SHN_BEFORE) { init_arritems = AL_CNT_OS_ISDESCS_BA; os_isdescs_idx = OS_ISD_BEFORE; } else if (shndx == SHN_AFTER) { init_arritems = AL_CNT_OS_ISDESCS_BA; os_isdescs_idx = OS_ISD_AFTER; } else { init_arritems = AL_CNT_OS_ISDESCS; os_isdescs_idx = OS_ISD_ORDERED; } } /* * If we didn't insert a section into the default list using * mapfile specified ordering above, then append the input * section to the appropriate list. */ if (do_append && aplist_append(&(osp->os_isdescs[os_isdescs_idx]), isp, init_arritems) == NULL) return (0); isp->is_osdesc = osp; /* * A section can be merged if the following are true: * - The SHF_MERGE|SHF_STRINGS flags must be set * - String table compression must not be disabled (-znocompstrtab) * - Mapfile ordering must not have been used. * - The section must not be ordered via section header flags. * - It must not be the generated section being built to * replace the sections on this list. */ if (((isp->is_shdr->sh_flags & (SHF_MERGE | SHF_STRINGS)) != (SHF_MERGE | SHF_STRINGS)) || ((ofl->ofl_flags1 & FLG_OF1_NCSTTAB) != 0) || !do_append || ((isp->is_flags & (FLG_IS_ORDERED | FLG_IS_GNSTRMRG)) != 0)) return (1); /* * Skip sections with (sh_entsize > 1) or (sh_addralign > 1). * * sh_entsize: * We are currently only able to merge string tables containing * strings with 1-byte (char) characters. Support for wide * characters will require our string table compression code * to be extended to handle larger character sizes. * * sh_addralign: * Alignments greater than 1 would require our string table * compression code to insert null bytes to move each * string to the required alignment. */ if ((isp->is_shdr->sh_entsize > 1) || (isp->is_shdr->sh_addralign > 1)) { DBG_CALL(Dbg_sec_unsup_strmerge(ofl->ofl_lml, isp)); return (1); } if (aplist_append(&osp->os_mstrisdescs, isp, AL_CNT_OS_MSTRISDESCS) == NULL) return (0); /* * The SHF_MERGE|SHF_STRINGS flags tell us that the program that * created the section intended it to be mergeable. The * FLG_IS_INSTRMRG flag says that we have done validity testing * and decided that it is safe to act on that hint. */ isp->is_flags |= FLG_IS_INSTRMRG; return (1); } /* * Determine whether this input COMDAT section already exists for the associated * output section. If so, then discard this input section. Otherwise, this * must be the first COMDAT section, thus it is kept for future comparisons. */ static uintptr_t add_comdat(Ofl_desc *ofl, Os_desc *osp, Is_desc *isp) { Isd_node isd, *isdp; avl_tree_t *avlt; avl_index_t where; Group_desc *gr; /* * Sections to which COMDAT groups apply are FLG_IS_COMDAT but are * discarded separately by the group logic so should never be * discarded here. */ if ((isp->is_shdr->sh_flags & SHF_GROUP) && ((gr = ld_get_group(ofl, isp)) != NULL) && (gr->gd_data[0] & GRP_COMDAT)) return (1); /* * Create a COMDAT avl tree for this output section if required. */ if ((avlt = osp->os_comdats) == NULL) { if ((avlt = libld_calloc(1, sizeof (avl_tree_t))) == NULL) return (S_ERROR); avl_create(avlt, isdavl_compare, sizeof (Isd_node), SGSOFFSETOF(Isd_node, isd_avl)); osp->os_comdats = avlt; } /* * A standard COMDAT section uses the section name as search key. */ isd.isd_name = isp->is_name; isd.isd_hash = sgs_str_hash(isd.isd_name); if ((isdp = avl_find(avlt, &isd, &where)) != NULL) { isp->is_osdesc = osp; /* * If this section hasn't already been identified as discarded, * generate a suitable diagnostic. */ if ((isp->is_flags & FLG_IS_DISCARD) == 0) { isp->is_flags |= FLG_IS_DISCARD; isp->is_comdatkeep = isdp->isd_isp; DBG_CALL(Dbg_sec_discarded(ofl->ofl_lml, isp, isdp->isd_isp)); } /* * A discarded section does not require assignment to an output * section. However, if relaxed relocations have been enabled * (either from -z relaxreloc, or asserted with .gnu.linkonce * processing), then this section must still be assigned to an * output section so that the sloppy relocation logic will have * the information necessary to do its work. */ return (0); } /* * This is a new COMDAT section - so keep it. */ if ((isdp = libld_calloc(1, sizeof (Isd_node))) == NULL) return (S_ERROR); isdp->isd_name = isd.isd_name; isdp->isd_hash = isd.isd_hash; isdp->isd_isp = isp; avl_insert(avlt, isdp, where); return (1); } /* * Determine whether a GNU group COMDAT section name follows the convention * * section-name.symbol-name * * Each section within the input file is compared to see if the full section * name matches the beginning of the COMDAT section, with a following '.'. * A pointer to the symbol name, starting with the '.' is returned so that the * caller can strip off the required section name. */ static char * gnu_comdat_sym(Ifl_desc *ifl, Is_desc *gisp) { size_t ndx; for (ndx = 1; ndx < ifl->ifl_shnum; ndx++) { Is_desc *isp; size_t ssize; if (((isp = ifl->ifl_isdesc[ndx]) == NULL) || (isp == gisp) || (isp->is_name == NULL)) continue; /* * It's questionable whether this size should be cached in the * Is_desc. However, this seems an infrequent operation and * adding Is_desc members can escalate memory usage for large * link-edits. For now, size the section name dynamically. */ ssize = strlen(isp->is_name); if ((strncmp(isp->is_name, gisp->is_name, ssize) == 0) && (gisp->is_name[ssize] == '.')) return ((char *)&gisp->is_name[ssize]); } return (NULL); } /* * GNU .gnu.linkonce sections follow a naming convention that indicates the * required association with an output section. Determine whether this input * section follows the convention, and if so return the appropriate output * section name. * * .gnu.linkonce.b.* -> .bss * .gnu.linkonce.d.* -> .data * .gnu.linkonce.l.* -> .ldata * .gnu.linkonce.lb.* -> .lbss * .gnu.linkonce.lr.* -> .lrodata * .gnu.linkonce.r.* -> .rodata * .gnu.linkonce.s.* -> .sdata * .gnu.linkonce.s2.* -> .sdata2 * .gnu.linkonce.sb.* -> .sbss * .gnu.linkonce.sb2.* -> .sbss2 * .gnu.linkonce.t.* -> .text * .gnu.linkonce.tb.* -> .tbss * .gnu.linkonce.td.* -> .tdata * .gnu.linkonce.wi.* -> .debug_info */ #define NSTR_CH1(ch) (*(nstr + 1) == (ch)) #define NSTR_CH2(ch) (*(nstr + 2) == (ch)) #define NSTR_CH3(ch) (*(nstr + 3) == (ch)) static const char * gnu_linkonce_sec(const char *ostr) { const char *nstr = &ostr[MSG_SCN_GNU_LINKONCE_SIZE]; switch (*nstr) { case 'b': if (NSTR_CH1('.')) return (MSG_ORIG(MSG_SCN_BSS)); break; case 'd': if (NSTR_CH1('.')) return (MSG_ORIG(MSG_SCN_DATA)); break; case 'l': if (NSTR_CH1('.')) return (MSG_ORIG(MSG_SCN_LDATA)); else if (NSTR_CH1('b') && NSTR_CH2('.')) return (MSG_ORIG(MSG_SCN_LBSS)); else if (NSTR_CH1('r') && NSTR_CH2('.')) return (MSG_ORIG(MSG_SCN_LRODATA)); break; case 'r': if (NSTR_CH1('.')) return (MSG_ORIG(MSG_SCN_RODATA)); break; case 's': if (NSTR_CH1('.')) return (MSG_ORIG(MSG_SCN_SDATA)); else if (NSTR_CH1('2') && NSTR_CH2('.')) return (MSG_ORIG(MSG_SCN_SDATA2)); else if (NSTR_CH1('b') && NSTR_CH2('.')) return (MSG_ORIG(MSG_SCN_SBSS)); else if (NSTR_CH1('b') && NSTR_CH2('2') && NSTR_CH3('.')) return (MSG_ORIG(MSG_SCN_SBSS2)); break; case 't': if (NSTR_CH1('.')) return (MSG_ORIG(MSG_SCN_TEXT)); else if (NSTR_CH1('b') && NSTR_CH2('.')) return (MSG_ORIG(MSG_SCN_TBSS)); else if (NSTR_CH1('d') && NSTR_CH2('.')) return (MSG_ORIG(MSG_SCN_TDATA)); break; case 'w': if (NSTR_CH1('i') && NSTR_CH2('.')) return (MSG_ORIG(MSG_SCN_DEBUG_INFO)); break; default: break; } /* * No special name match found. */ return (ostr); } #undef NSTR_CH1 #undef NSTR_CH2 #undef NSTR_CH3 /* * The GNU link-editor maps sections generated by the GNU compiler separately * due to -ffunction-sections, -fdata-sections or for other reasons into the * "normal" section represented. * * Sections are named .
. where
is the usual section to * which it should be mapped, and is providing the unique name for * the original section. Both parts of the name may contain periods, in cases * where the unique part of the name contains a '.' and/or the section it * contributes to does (such as .data.rel.ro) * * .rodata.str* and .rodata.cst* are mapped to .rodata. * * As a further complication, the GNU link-editor may or may not merge * .ctors.* and .dtors.* into init_array and fini_array, rather than ctors and * dtors. We do not implement this at this time. * * The GNU link editor may also arrange for sections with .local in their name * to be mapped as above, but grouped together. We do not implement this (and * do not merge them at all, to make this clear) * * This table is processed in order. Longer mappings must come first. */ static struct split_sec_mapping { char *leader; char *section; boolean_t precise; } split_sec_mapping[] = { { ".bss.", ".bss", B_FALSE }, { ".ctors.", ".ctors", B_FALSE }, { ".data.rel.local.", ".data.rel.local", B_FALSE }, { ".data.rel.local", ".data.rel.local", B_TRUE }, { ".data.rel.ro.local.", ".data.rel.ro", B_FALSE }, { ".data.rel.ro.", ".data.rel.ro", B_FALSE }, { ".data.rel.ro", ".data.rel.ro", B_TRUE }, { ".data.rel.", ".data.rel", B_FALSE }, { ".data.rel", ".data.rel", B_TRUE }, { ".data.", ".data", B_FALSE }, { ".dtors.", ".dtors", B_FALSE }, { ".fini_array.", ".fini_array", B_FALSE }, { ".init_array.", ".init_array", B_FALSE }, { ".lbss.", ".lbss", B_FALSE }, { ".ldata.", ".ldata", B_FALSE }, { ".lrodata.", ".lrodata", B_FALSE }, /* This intentionally applies to .rodata.cstN and .rodata.strN, too */ { ".rodata.", ".rodata", B_FALSE }, { ".sbss2.", ".sbss2", B_FALSE }, { ".sbss.", ".sbss", B_FALSE }, { ".sdata2.", ".sdata2", B_FALSE }, { ".sdata.", ".sdata", B_FALSE }, { ".tbss.", ".tbss", B_FALSE }, { ".tdata.", ".tdata", B_FALSE }, { ".text.", ".text", B_FALSE }, { NULL, NULL, B_FALSE } }; static const char * gnu_split_sec(const char *ostr) { struct split_sec_mapping *mp; for (mp = split_sec_mapping; mp->leader != NULL; mp++) { if (mp->precise) { if (strcmp(ostr, mp->leader) == 0) return (mp->section); } else if (strncmp(ostr, mp->leader, strlen(mp->leader)) == 0) { return (mp->section); } } return (ostr); } /* * Initialize a path info buffer for use with ld_place_section(). * * entry: * ofl - Output descriptor * ifl - Descriptor for input file, or NULL if there is none. * info - Address of buffer to be initialized. * * exit: * If this is an input file, and if the entrance criteria list * contains at least one criteria that has a non-empty file string * match list (ec_files), then the block pointed at by info is * initialized, and info is returned. * * If there is no input file, and/or no entrance criteria containing * a non-empty ec_files list, then NULL is returned. This is not * an error --- the NULL is simply an optimization, understood by * ld_place_path(), that allows it to skip unnecessary work. */ Place_path_info * ld_place_path_info_init(Ofl_desc *ofl, Ifl_desc *ifl, Place_path_info *info) { /* * Return NULL if there is no input file (internally generated section) * or if the entrance criteria list does not contain any items that will * need to be compared to the path (all the ec_files lists are empty). */ if ((ifl == NULL) || !(ofl->ofl_flags & FLG_OF_EC_FILES)) return (NULL); info->ppi_path = ifl->ifl_name; info->ppi_path_len = strlen(info->ppi_path); info->ppi_isar = (ifl->ifl_flags & FLG_IF_EXTRACT) != 0; /* * The basename is the final segment of the path, equivalent to * the path itself if there are no '/' delimiters. */ info->ppi_bname = strrchr(info->ppi_path, '/'); if (info->ppi_bname == NULL) info->ppi_bname = info->ppi_path; else info->ppi_bname++; /* Skip leading '/' */ info->ppi_bname_len = info->ppi_path_len - (info->ppi_bname - info->ppi_path); /* * For an archive, the object name is the member name, which is * enclosed in () at the end of the name string. Otherwise, it is * the same as the basename. */ if (info->ppi_isar) { info->ppi_oname = strrchr(info->ppi_bname, '('); /* There must be an archive member suffix delimited by parens */ assert((info->ppi_bname[info->ppi_bname_len - 1] == ')') && (info->ppi_oname != NULL)); info->ppi_oname++; /* skip leading '(' */ info->ppi_oname_len = info->ppi_bname_len - (info->ppi_oname - info->ppi_bname + 1); } else { info->ppi_oname = info->ppi_bname; info->ppi_oname_len = info->ppi_bname_len; } return (info); } /* * Compare an input section path to the file comparison list the given * entrance criteria. * * entry: * path_info - A non-NULL Place_path_info block for the file * containing the input section, initialized by * ld_place_path_info_init() * enp - Entrance criteria with a non-empty ec_files list of file * comparisons to be carried out. * * exit: * Return TRUE if a match is seen, and FALSE otherwise. */ static Boolean eval_ec_files(Place_path_info *path_info, Ent_desc *enp) { Aliste idx; Ent_desc_file *edfp; size_t cmp_len; const char *cmp_str; for (ALIST_TRAVERSE(enp->ec_files, idx, edfp)) { Word type = edfp->edf_flags & TYP_ECF_MASK; /* * Determine the starting character, and # of characters, * from the file path to compare against this entrance criteria * file string. */ if (type == TYP_ECF_OBJNAME) { cmp_str = path_info->ppi_oname; cmp_len = path_info->ppi_oname_len; } else { int ar_stat_diff = path_info->ppi_isar != ((edfp->edf_flags & FLG_ECF_ARMEMBER) != 0); /* * If the entrance criteria specifies an archive member * and the file does not, then there can be no match. */ if (ar_stat_diff && !path_info->ppi_isar) continue; if (type == TYP_ECF_PATH) { cmp_str = path_info->ppi_path; cmp_len = path_info->ppi_path_len; } else { /* TYP_ECF_BASENAME */ cmp_str = path_info->ppi_bname; cmp_len = path_info->ppi_bname_len; } /* * If the entrance criteria does not specify an archive * member and the file does, then a match just requires * the paths (without the archive member) to match. * Reduce the length to not include the ar member or * the '(' that precedes it. */ if (ar_stat_diff && path_info->ppi_isar) cmp_len = path_info->ppi_oname - cmp_str - 1; } /* * Compare the resulting string to the one from the * entrance criteria. */ if ((cmp_len == edfp->edf_name_len) && (strncmp(edfp->edf_name, cmp_str, cmp_len) == 0)) return (TRUE); } return (FALSE); } /* * Replace the section header for the given input section with a new section * header of the specified type. All values in the replacement header other * than the type retain their previous values. * * entry: * isp - Input section to replace * sh_type - New section type to apply * * exit: * Returns the pointer to the new section header on success, and * NULL for failure. */ static Shdr * isp_convert_type(Is_desc *isp, Word sh_type) { Shdr *shdr; if ((shdr = libld_malloc(sizeof (Shdr))) == NULL) return (NULL); *shdr = *isp->is_shdr; isp->is_shdr = shdr; shdr->sh_type = sh_type; return (shdr); } /* * Issue a fatal warning for the given .eh_frame section, which * cannot be merged with the existing .eh_frame output section. */ static void eh_frame_muldef(Ofl_desc *ofl, Is_desc *isp) { Sg_desc *sgp; Is_desc *isp1; Os_desc *osp; Aliste idx1, idx2, idx3; /* * Locate the .eh_frame output section, and use the first section * assigned to it in the error message. The user can then compare * the two sections to determine what attribute prevented the merge. */ for (APLIST_TRAVERSE(ofl->ofl_segs, idx1, sgp)) { for (APLIST_TRAVERSE(sgp->sg_osdescs, idx2, osp)) { if ((osp->os_flags & FLG_OS_EHFRAME) == 0) continue; for (idx3 = 0; idx3 < OS_ISD_NUM; idx3++) { APlist *lst = osp->os_isdescs[idx3]; if (aplist_nitems(lst) == 0) continue; isp1 = lst->apl_data[0]; ld_eprintf(ofl, ERR_FATAL, MSG_INTL(MSG_UPD_MULEHFRAME), isp1->is_file->ifl_name, EC_WORD(isp1->is_scnndx), isp1->is_name, isp->is_file->ifl_name, EC_WORD(isp->is_scnndx), isp->is_name); return; } } } } /* * Hash the specified Os_desc on the specified segment descriptor, allocating * and resizing the hash table as necessary. (We only hash when the number of * output secctions exceeds a minimum, below which we deem it not worth it to * have the auxiliary structure.) */ static void os_desc_hash(Sg_desc *sgp, Os_desc *osp) { const size_t min_size = 31; Aliste nitems, idx, idx1; os_desc_hash_t *hash; size_t new_size; if ((nitems = aplist_nitems(sgp->sg_osdescs)) < min_size) { return; } if ((hash = sgp->sg_hashtab) != NULL && hash->osh_hashtab != NULL) { if (nitems < hash->osh_size) { /* * We have a hash table, and it's not undersized -- just * add our newest element. */ idx = osp->os_namehash % hash->osh_size; osp->os_hashnext = hash->osh_hashtab[idx]; hash->osh_hashtab[idx] = osp; return; } /* * We have a hash table, but it's full: we are going to want * to double our size and rehash all of the output section * descriptions. First, free our old hash table... */ libld_free(hash->osh_hashtab); new_size = (hash->osh_size << 1) - 1; } else { /* * We either don't have a hash structure or we don't have a * hash table (the partial construction of which may be due to * a previous allocation failure). Determine what we want * our new size to be, and allocate our hash structure as * needed. */ new_size = min_size; while (new_size <= nitems) new_size = (new_size << 1) - 1; if (hash == NULL) { hash = libld_calloc(1, sizeof (os_desc_hash_t)); if ((sgp->sg_hashtab = hash) == NULL) return; } } if ((hash->osh_hashtab = libld_calloc(new_size, sizeof (void *))) == NULL) { return; } /* * Set our new size, and scan everything and hash it in. */ hash->osh_size = new_size; for (APLIST_TRAVERSE(sgp->sg_osdescs, idx1, osp)) { idx = osp->os_namehash % hash->osh_size; osp->os_hashnext = hash->osh_hashtab[idx]; hash->osh_hashtab[idx] = osp; } } /* * Place a section into the appropriate segment and output section. * * entry: * ofl - File descriptor * isp - Input section descriptor of section to be placed. * path_info - NULL, or pointer to Place_path_info buffer initialized * by ld_place_path_info_init() for the file associated to isp, * for use in processing entrance criteria with non-empty * file matching string list (ec_files) * ident - Section identifier, used to order sections relative to * others within the output segment. * alt_os_name - If non-NULL, the name of the output section to place * isp into. If NULL, input sections go to an output section * with the same name as the input section. */ Os_desc * ld_place_section(Ofl_desc *ofl, Is_desc *isp, Place_path_info *path_info, int ident, const char *alt_os_name) { Ent_desc *enp; Sg_desc *sgp; Os_desc *osp; Aliste idx1, iidx; int os_ndx; Shdr *shdr = isp->is_shdr; Xword shflagmask, shflags = shdr->sh_flags; Ifl_desc *ifl = isp->is_file; char *oname, *sname; uint_t onamehash; Boolean is_ehframe = (isp->is_flags & FLG_IS_EHFRAME) != 0; Boolean linear_scan = TRUE; os_desc_hash_t *hash; /* * Define any sections that must be thought of as referenced. These * sections may not be referenced externally in a manner ld(1) can * discover, but they must be retained (ie. not removed by -zignore). */ static const Msg RefSecs[] = { MSG_SCN_INIT, /* MSG_ORIG(MSG_SCN_INIT) */ MSG_SCN_FINI, /* MSG_ORIG(MSG_SCN_FINI) */ MSG_SCN_EX_RANGES, /* MSG_ORIG(MSG_SCN_EX_RANGES) */ MSG_SCN_EX_SHARED, /* MSG_ORIG(MSG_SCN_EX_SHARED) */ MSG_SCN_CTORS, /* MSG_ORIG(MSG_SCN_CTORS) */ MSG_SCN_DTORS, /* MSG_ORIG(MSG_SCN_DTORS) */ MSG_SCN_EHFRAME, /* MSG_ORIG(MSG_SCN_EHFRAME) */ MSG_SCN_EHFRAME_HDR, /* MSG_ORIG(MSG_SCN_EHFRAME_HDR) */ MSG_SCN_JCR, /* MSG_ORIG(MSG_SCN_JCR) */ MSG_SCN_INITARRAY, /* MSG_ORIG(MSG_SCN_INITARRAY) */ MSG_SCN_FINIARRAY, /* MSG_ORIG(MSG_SCN_FINIARRAY) */ MSG_SCN_PREINITARRAY, /* MSG_ORIG(MSG_SCN_PREINITARRAY) */ 0 }; DBG_CALL(Dbg_sec_in(ofl->ofl_lml, isp)); /* * If this section identifies group members, or this section indicates * that it is a member of a group, determine whether the section is * still required. */ if ((shflags & SHF_GROUP) || (shdr->sh_type == SHT_GROUP)) { Group_desc *gdesc; if ((gdesc = ld_get_group(ofl, isp)) != NULL) { DBG_CALL(Dbg_sec_group(ofl->ofl_lml, isp, gdesc)); /* * If this group has been replaced by another group, * then this section needs to be discarded. */ if (gdesc->gd_oisc) { isp->is_flags |= FLG_IS_DISCARD; /* * Since we're discarding the section, we * can skip assigning it to an output section. * The exception is that if the user * specifies -z relaxreloc, then * we need to assign the output section so * that the sloppy relocation logic will have * the information necessary to do its work. */ if (!(ofl->ofl_flags1 & FLG_OF1_RLXREL)) return (NULL); } } /* * SHT_GROUP sections can only be included into relocatable * objects. */ if (shdr->sh_type == SHT_GROUP) { if ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0) { isp->is_flags |= FLG_IS_DISCARD; return (NULL); } } } /* * Always assign SHF_TLS sections to the DATA segment (and then the * PT_TLS embedded inside of there). */ if (shflags & SHF_TLS) shflags |= SHF_WRITE; /* * Traverse the entrance criteria list searching for a segment that * matches the input section we have. If an entrance criterion is set * then there must be an exact match. If we complete the loop without * finding a segment, then sgp will be NULL. */ sgp = NULL; for (APLIST_TRAVERSE(ofl->ofl_ents, idx1, enp)) { /* Disabled segments are not available for assignment */ if (enp->ec_segment->sg_flags & FLG_SG_DISABLED) continue; /* * If an entrance criteria doesn't have any of its fields * set, it will match any section it is tested against. * We set the FLG_EC_CATCHALL flag on these, primarily because * it helps readers of our debug output to understand what * the criteria means --- otherwise the user would just see * that every field is 0, but might not understand the * significance of that. * * Given that we set this flag, we can use it here as an * optimization to short circuit all of the tests in this * loop. Note however, that if we did not do this, the end * result would be the same --- the empty criteria will sail * past the following tests and reach the end of the loop. */ if (enp->ec_flags & FLG_EC_CATCHALL) { sgp = enp->ec_segment; break; } if (enp->ec_type && (enp->ec_type != shdr->sh_type)) continue; if (enp->ec_attrmask && /* LINTED */ (enp->ec_attrmask & enp->ec_attrbits) != (enp->ec_attrmask & shflags)) continue; if (enp->ec_is_name && (strcmp(enp->ec_is_name, isp->is_name) != 0)) continue; if ((alist_nitems(enp->ec_files) > 0) && ((path_info == NULL) || !eval_ec_files(path_info, enp))) continue; /* All entrance criteria tests passed */ sgp = enp->ec_segment; break; } /* * The final entrance criteria record is a FLG_EC_CATCHALL that points * at the final predefined segment "extra", and this final segment is * tagged FLG_SG_NODISABLE. Therefore, the above loop must always find * a segment. */ assert(sgp != NULL); /* * Transfer the input section sorting key from the entrance criteria * to the input section. A non-zero value means that the section * will be sorted on this key amoung the other sections that have a * non-zero key. These sorted sections are collectively placed at the * head of the output section. * * If the sort key is 0, the section is placed after the sorted * sections in the order they are encountered. */ isp->is_ordndx = enp->ec_ordndx; /* Remember that this entrance criteria has placed a section */ enp->ec_flags |= FLG_EC_USED; /* * If our caller has supplied an alternative name for the output * section, then we defer to their request. Otherwise, the default * is to use the same name as that of the input section being placed. * * The COMDAT, SHT_GROUP and GNU name translations that follow have * the potential to alter this initial name. */ oname = (char *)((alt_os_name == NULL) ? isp->is_name : alt_os_name); /* * Solaris section names may follow the convention: * * section-name%symbol-name * * This convention has been used to order the layout of sections within * segments for objects built with the compilers -xF option. However, * the final object should not contain individual section headers for * all such input sections, instead the symbol name is stripped from the * name to establish the final output section name. * * This convention has also been followed for COMDAT and sections * identified though SHT_GROUP data. * * Strip out the % from the section name for: * - Non-relocatable objects * - Relocatable objects if input section sorting is * in force for the segment in question. */ if (((ofl->ofl_flags & FLG_OF_RELOBJ) == 0) || (sgp->sg_flags & FLG_SG_IS_ORDER)) { if ((sname = strchr(isp->is_name, '%')) != NULL) { size_t size = sname - isp->is_name; if ((oname = libld_malloc(size + 1)) == NULL) return ((Os_desc *)S_ERROR); (void) strncpy(oname, isp->is_name, size); oname[size] = '\0'; DBG_CALL(Dbg_sec_redirected(ofl->ofl_lml, isp, oname)); } } /* * When building relocatable objects, we must not redirect COMDAT * section names into their outputs, such that our output object may * be successfully used as an input object also requiring COMDAT * processing */ /* * GNU section names may follow the convention: * * .gnu.linkonce.* * * The .gnu.linkonce is a section naming convention that indicates a * COMDAT requirement. Determine whether this section follows the GNU * pattern, and if so, determine whether this section should be * discarded or retained. The comparison of is_name[1] with 'g' * is an optimization to skip using strncmp() too much. This is safe, * because we know the name is not NULL, and therefore must have * at least one character plus a NULL termination. */ if ((isp->is_name == oname) && (isp->is_name[1] == 'g') && (strncmp(MSG_ORIG(MSG_SCN_GNU_LINKONCE), isp->is_name, MSG_SCN_GNU_LINKONCE_SIZE) == 0)) { if ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0) { if ((oname = (char *)gnu_linkonce_sec(isp->is_name)) != isp->is_name) { DBG_CALL(Dbg_sec_redirected(ofl->ofl_lml, isp, oname)); } } /* * Explicitly identify this section type as COMDAT. Also, * enable relaxed relocation processing, as this is typically * a requirement with .gnu.linkonce sections. */ isp->is_flags |= FLG_IS_COMDAT; if ((ofl->ofl_flags1 & FLG_OF1_NRLXREL) == 0) ofl->ofl_flags1 |= FLG_OF1_RLXREL; DBG_CALL(Dbg_sec_gnu_comdat(ofl->ofl_lml, isp, TRUE, (ofl->ofl_flags1 & FLG_OF1_RLXREL) != 0)); } /* * GNU section names may also follow the convention: * * section-name.symbol-name * * This convention is used when defining SHT_GROUP sections of type * COMDAT. Thus, any group processing will have discovered any group * sections, and this identification can be triggered by a pattern * match section names. */ if ((isp->is_name == oname) && (isp->is_flags & FLG_IS_COMDAT) && ((sname = gnu_comdat_sym(ifl, isp)) != NULL)) { size_t size = sname - isp->is_name; if ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0) { if ((oname = libld_malloc(size + 1)) == NULL) return ((Os_desc *)S_ERROR); (void) strncpy(oname, isp->is_name, size); oname[size] = '\0'; DBG_CALL(Dbg_sec_redirected(ofl->ofl_lml, isp, oname)); } /* * Enable relaxed relocation processing, as this is * typically a requirement with GNU COMDAT sections. */ if ((ofl->ofl_flags1 & FLG_OF1_NRLXREL) == 0) { ofl->ofl_flags1 |= FLG_OF1_RLXREL; DBG_CALL(Dbg_sec_gnu_comdat(ofl->ofl_lml, isp, FALSE, TRUE)); } } /* * GNU section names named section-name.symbol-name which are not * members of COMDAT groups are merged according to the behaviour of * the GNU link-editor. * * See the description of gnu_split_sec(). */ if (((ofl->ofl_flags & FLG_OF_RELOBJ) == 0) && (isp->is_name == oname) && ((oname = (char *)gnu_split_sec(oname)) != isp->is_name)) { DBG_CALL(Dbg_sec_redirected(ofl->ofl_lml, isp, oname)); } /* * Assign a hash value now that the output section name has been * finalized. */ onamehash = sgs_str_hash(oname); /* * Determine if output section ordering is turned on. If so, return * the appropriate ordering index for the section. This information * is derived from the Sg_desc->sg_os_order list that was built * up from the Mapfile. * * A value of 0 for os_ndx means that the section is not sorted * (i.e. is not found in the sg_os_order). The items in sg_os_order * are in the desired sort order, so adding 1 to their alist index * gives a suitable index for sorting. */ os_ndx = 0; if (alist_nitems(sgp->sg_os_order) > 0) { Sec_order *scop; for (ALIST_TRAVERSE(sgp->sg_os_order, idx1, scop)) { if (strcmp(scop->sco_secname, oname) == 0) { scop->sco_flags |= FLG_SGO_USED; os_ndx = idx1 + 1; break; } } } /* * Mask of section header flags to ignore when matching sections. We * are more strict with relocatable objects, ignoring only the order * flags, and keeping sections apart if they differ otherwise. This * follows the policy that sections in a relative object should only * be merged if their flags are the same, and avoids destroying * information prematurely. For final products however, we ignore all * flags that do not prevent a merge. */ shflagmask = (ofl->ofl_flags & FLG_OF_RELOBJ) ? ALL_SHF_ORDER : ALL_SHF_IGNORE; /* * Traverse the input section list for the output section we have been * assigned. If we find a matching section simply add this new section. */ iidx = 0; /* * To not become quadratic with respect to the number of output * sections, we want to avoid a scan of the existing output sections * on every section placement. To effect this, we use a hash table * for sections when the number of sections gets sufficiently large; * if this hash table is present, we check the absence of the other * (uncommon) conditions that necessitate a linear scan, using the * hash chain if we can. */ if (os_ndx == 0 && (hash = sgp->sg_hashtab) != NULL) { APlist *list = sgp->sg_osdescs; osp = list->apl_data[list->apl_nitems - 1]; if (ident >= osp->os_identndx) { linear_scan = FALSE; osp = hash->osh_hashtab[onamehash % hash->osh_size]; } } /* * We now want to iterate over output sections, looking for a match * or an insertion point. Note that this loop condition is a bit * convoluted because it's encoding two different ways of iterating * over output section descriptors: if it's a linear scan, we will * iterate over the list elements, and if it's not a linear scan we * will iterate over the hash chain that we discovered above. (The * only condition that will actually change over the loop is osp; the * mechanics of iterating to the next section depend on linear_scan * and are contained in the body of the loop.) */ while ((linear_scan && sgp->sg_osdescs != NULL) || (!linear_scan && osp != NULL)) { if (linear_scan) { if ((idx1 = iidx) >= sgp->sg_osdescs->apl_nitems) break; osp = sgp->sg_osdescs->apl_data[idx1]; } Shdr *os_shdr = osp->os_shdr; /* * An input section matches an output section if: * - The ident values match * - The names match * - Not a GROUP section * - Not a GROUP member, if producing a relocatable object * - Not a DTrace dof section * - Section types match * - Matching section flags, after screening out the * shflagmask flags. * * Section types are considered to match if any one of * the following are true: * - The type codes are the same * - Both are .eh_frame sections (regardless of type code) * - The input section is COMDAT, and the output section * is SHT_PROGBITS. */ if ((ident == osp->os_identndx) && (ident != ld_targ.t_id.id_rel) && (onamehash == osp->os_namehash) && (shdr->sh_type != SHT_GROUP) && (((shdr->sh_flags & SHF_GROUP) == 0) || ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0)) && (shdr->sh_type != SHT_SUNW_dof) && ((shdr->sh_type == os_shdr->sh_type) || (is_ehframe && (osp->os_flags & FLG_OS_EHFRAME)) || ((shdr->sh_type == SHT_SUNW_COMDAT) && (os_shdr->sh_type == SHT_PROGBITS))) && ((shflags & ~shflagmask) == (os_shdr->sh_flags & ~shflagmask)) && (strcmp(oname, osp->os_name) == 0)) { uintptr_t err; /* * Process any COMDAT section, keeping the first and * discarding all others. */ if ((isp->is_flags & FLG_IS_COMDAT) && ((err = add_comdat(ofl, osp, isp)) != 1)) return ((Os_desc *)err); /* * Set alignment */ set_addralign(ofl, osp, isp); /* * If this section is a non-empty TLS section indicate * that a PT_TLS program header is required. */ if ((shflags & SHF_TLS) && shdr->sh_size && ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0)) ofl->ofl_flags |= FLG_OF_TLSPHDR; /* * Insert the input section descriptor on the proper * output section descriptor list. * * If this segment requires input section ordering, * honor any mapfile specified ordering for otherwise * unordered sections by setting the mapfile_sort * argument of os_attach_isp() to True. */ if (os_attach_isp(ofl, osp, isp, (sgp->sg_flags & FLG_SG_IS_ORDER) != 0) == 0) return ((Os_desc *)S_ERROR); /* * If this input section and file is associated to an * artificially referenced output section, make sure * they are marked as referenced also. This ensures * that this input section and file isn't eliminated * when -zignore is in effect. * * See -zignore comments when creating a new output * section below. */ if (((ifl && (ifl->ifl_flags & FLG_IF_IGNORE)) || DBG_ENABLED) && (osp->os_flags & FLG_OS_SECTREF)) { isp->is_flags |= FLG_IS_SECTREF; if (ifl) ifl->ifl_flags |= FLG_IF_FILEREF; } DBG_CALL(Dbg_sec_added(ofl->ofl_lml, osp, sgp)); return (osp); } if (!linear_scan) { osp = osp->os_hashnext; continue; } /* * Do we need to worry about section ordering? */ if (os_ndx) { if (osp->os_ordndx) { if (os_ndx < osp->os_ordndx) /* insert section here. */ break; else { iidx = idx1 + 1; continue; } } else { /* insert section here. */ break; } } else if (osp->os_ordndx) { iidx = idx1 + 1; continue; } /* * If the new sections identifier is less than that of the * present input section we need to insert the new section * at this point. */ if (ident < osp->os_identndx) break; iidx = idx1 + 1; } if (!linear_scan) { iidx = sgp->sg_osdescs->apl_nitems; } /* * We are adding a new output section. Update the section header * count and associated string size. * * If the input section triggering this output section has been marked * for discard, and if no other non-discarded input section comes along * to join it, then we will over count. We cannot know if this will * happen or not until all input is seen. Set FLG_OF_AJDOSCNT to * trigger a final count readjustment. */ if (isp->is_flags & FLG_IS_DISCARD) ofl->ofl_flags |= FLG_OF_ADJOSCNT; ofl->ofl_shdrcnt++; if (st_insert(ofl->ofl_shdrsttab, oname) == -1) return ((Os_desc *)S_ERROR); /* * Create a new output section descriptor. */ if ((osp = libld_calloc(1, sizeof (Os_desc))) == NULL) return ((Os_desc *)S_ERROR); if ((osp->os_shdr = libld_calloc(1, sizeof (Shdr))) == NULL) return ((Os_desc *)S_ERROR); /* * Convert COMDAT section to PROGBITS as this the first section of the * output section. Save any COMDAT section for later processing, as * additional COMDAT sections that match this section need discarding. */ if ((shdr->sh_type == SHT_SUNW_COMDAT) && ((shdr = isp_convert_type(isp, SHT_PROGBITS)) == NULL)) return ((Os_desc *)S_ERROR); if ((isp->is_flags & FLG_IS_COMDAT) && (add_comdat(ofl, osp, isp) == S_ERROR)) return ((Os_desc *)S_ERROR); if (is_ehframe) { /* * Executable or sharable objects can have at most a single * .eh_frame section. Detect attempts to create more than * one. This occurs if the input sections have incompatible * attributes. */ if ((ofl->ofl_flags & FLG_OF_EHFRAME) && !(ofl->ofl_flags & FLG_OF_RELOBJ)) { eh_frame_muldef(ofl, isp); return ((Os_desc *)S_ERROR); } ofl->ofl_flags |= FLG_OF_EHFRAME; /* * For .eh_frame sections, we always set the type to be the * type specified by the ABI. This allows .eh_frame sections * of type SHT_PROGBITS to be correctly merged with .eh_frame * sections of the ABI-defined type (e.g. SHT_AMD64_UNWIND), * with the output being of the ABI-defined type. */ osp->os_shdr->sh_type = ld_targ.t_m.m_sht_unwind; } else { osp->os_shdr->sh_type = shdr->sh_type; } osp->os_shdr->sh_flags = shdr->sh_flags; osp->os_shdr->sh_entsize = shdr->sh_entsize; osp->os_name = oname; osp->os_namehash = onamehash; osp->os_ordndx = os_ndx; osp->os_sgdesc = sgp; if (is_ehframe) osp->os_flags |= FLG_OS_EHFRAME; if (ifl && (shdr->sh_type == SHT_PROGBITS)) { /* * Try to preserve the intended meaning of sh_link/sh_info. * See the translate_link() in update.c. */ osp->os_shdr->sh_link = shdr->sh_link; if (shdr->sh_flags & SHF_INFO_LINK) osp->os_shdr->sh_info = shdr->sh_info; } /* * When -zignore is in effect, user supplied sections and files that are * not referenced from other sections, are eliminated from the object * being produced. Some sections, although unreferenced, are special, * and must not be eliminated. Determine if this new output section is * one of those special sections, and if so mark it artificially as * referenced. Any input section and file associated to this output * section is also be marked as referenced, and thus won't be eliminated * from the final output. */ if (ifl && ((ofl->ofl_flags1 & FLG_OF1_IGNPRC) || DBG_ENABLED)) { const Msg *refsec; for (refsec = RefSecs; *refsec; refsec++) { if (strcmp(osp->os_name, MSG_ORIG(*refsec)) == 0) { osp->os_flags |= FLG_OS_SECTREF; if ((ifl->ifl_flags & FLG_IF_IGNORE) || DBG_ENABLED) { isp->is_flags |= FLG_IS_SECTREF; ifl->ifl_flags |= FLG_IF_FILEREF; } break; } } } /* * Sections of type SHT_GROUP are added to the ofl->ofl_osgroups list, * so that they can be updated as a group later. */ if ((shdr->sh_type == SHT_GROUP) && ((isp->is_flags & FLG_IS_DISCARD) == 0) && (aplist_append(&ofl->ofl_osgroups, osp, AL_CNT_OFL_OSGROUPS) == NULL)) return ((Os_desc *)S_ERROR); /* * If this section is a non-empty TLS section indicate that a PT_TLS * program header is required. */ if ((shflags & SHF_TLS) && shdr->sh_size && ((ofl->ofl_flags & FLG_OF_RELOBJ) == 0)) ofl->ofl_flags |= FLG_OF_TLSPHDR; /* * If a non-allocatable section is going to be put into a loadable * segment then turn on the allocate bit for this section and warn the * user that we have done so. This could only happen through the use * of a mapfile. */ if ((sgp->sg_phdr.p_type == PT_LOAD) && ((osp->os_shdr->sh_flags & SHF_ALLOC) == 0)) { ld_eprintf(ofl, ERR_WARNING, MSG_INTL(MSG_SCN_NONALLOC), ofl->ofl_name, osp->os_name, sgp->sg_name); osp->os_shdr->sh_flags |= SHF_ALLOC; } /* * Retain this sections identifier for future comparisons when placing * a section (after all sections have been processed this variable will * be used to hold the sections symbol index as we don't need to retain * the identifier any more). */ osp->os_identndx = ident; /* * Set alignment. */ set_addralign(ofl, osp, isp); if (os_attach_isp(ofl, osp, isp, 0) == 0) return ((Os_desc *)S_ERROR); DBG_CALL(Dbg_sec_created(ofl->ofl_lml, osp, sgp)); /* * Insert the new section at the offset given by iidx. If no position * for it was identified above, this will be index 0, causing the new * section to be prepended to the beginning of the section list. * Otherwise, it is the index following the section that was identified. */ if (aplist_insert(&sgp->sg_osdescs, osp, AL_CNT_SG_OSDESC, iidx) == NULL) return ((Os_desc *)S_ERROR); os_desc_hash(sgp, osp); return (osp); }