/*
* 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) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Processing of SHF_ORDERED sections.
*/
#include <stdio.h>
#include <fcntl.h>
#include <link.h>
#include <debug.h>
#include "msg.h"
#include "_libld.h"
/*
* Section Ordering History/Background:
*
* There are two forms of section ordering, SHF_ORDERED, and SHF_LINK_ORDER.
*
* SHF_ORDERED was invented at Sun in order to support the PowerPC port
* of Solaris 2.6, which used it for sorting tag words which describe
* the state of callee saves registers for given PC ranges. It was defined
* in the OS specific ELF section flag range. Some other values were defined
* at the same time:
* SHF_EXCLUDE - Section is to be excluded from executables or shared
* objects, and only kept in relocatable object output.
* SHN_BEFORE/SHN_AFTER - Sections are placed before/after all other
* sections, in the order they are encountered by the linker.
* Although initially conceived to support the PowerPC, the functionality
* was implemented for all platforms, and was later used to manage C++
* exceptions and stack unwinding. The PowerPC port was discontinued after
* one release, but SHF_ORDERED lives on.
*
* SHF_LINK_ORDER was invented later by the wider ELF community, and is
* therefore assigned a value in the generic ELF section flag range. It is
* essentially a simpler version of SHF_ORDERED, dispensing with some
* unnecessary features. The Solaris implementation of SHF_LINK_ORDER uses
* SHF_EXCLUDE, and SHF_BEFORE/SHN_AFTER as well, but it appears that these
* are still Solaris-only extensions not used by other implementations.
* SHF_LINK_ORDER has superseded SHF_ORDERED. The older mechanism is
* supported for the benefit of old pre-existing objects.
*
* -----
*
* SHF_ORDERED offers two distinct and separate abilities:
*
* (1) To specify the output section
* (2) To optionally be sorted relative to other sorted sections,
* using a non-sorted section as a sort key.
*
* To do this, it uses both the sh_link, and sh_info fields:
*
* sh_link
* Specifies the output section to receive this input section.
* The sh_link field of an SHF_ORDERED section forms a linked list of
* sections, all of which must have identical section header flags
* (including SHF_ORDERED). The list is terminated by a final section
* with a sh_link that points at itself. All input sections in this list
* are assigned to the output section of the final section in the list.
* Hence, if a section points at itself, the effect is that it gets
* assigned to an output section in the usual default manner (i.e. an
* output section with the same name as the input). However, it can
* point at any arbitrary other section. This is a way to put a section
* with one name into an output section with a different name. It should
* be noted that this is of little value overall, because the link-editor
* already supports a more general feature for directing input sections
* to output sections: An input section named .text%foo will be sent to
* an output section named ".text", and this works for all sections,
* not just ordered ones.
*
* sh_info
* If sh_info is in the range (1 <= value < shnum), then this input section
* is added to the group of sorted sections. The section referenced by
* sh_info must be unsorted, and is used as the sort key.
*
* If sh_info is SHN_BEFORE or SHN_AFTER, it is put in the pre/post group,
* in the order it arrives (the before/after classes are not sorted).
*
* If sh_info is "invalid" (typically 0), then this section is added to
* the group of non-sorted sections, and goes into the output file in the
* order it arrives. This is not a valuable feature, as the same effect
* can be achieved more simply by not setting SHF_ORDERED at all.
*
* SHF_LINK_ORDER is a simplification of SHF_ORDERED. It uses sh_link to specify
* the section to use as a sort key and sh_info is set to 0. The standard
* ".text%foo" mechanism is used to direct input sections to output sections,
* and unordered sections indicate that by not setting SHF_LINK_ORDER.
*/
/*
* A "keyshndx" is the section index for the unordered section that should
* be used as a sort key for a ordered section. Verify that the given
* keyshndx is valid.
*
* exit:
* Returns 0 if the keyshndx is valid. A non-zero DBG_ORDER_ code is
* returned if the keyshndx is not valid to describe the problem.
*/
inline static Word
is_keyshndx_ok(Ifl_desc *ifl, Word keyshndx)
{
if ((keyshndx == SHN_BEFORE) || (keyshndx == SHN_AFTER))
return (0);
/*
* Validate the key range.
*/
if ((keyshndx == 0) || (keyshndx >= ifl->ifl_shnum))
return (DBG_ORDER_LINK_OUTRANGE);
/*
* The section pointed to by keyshndx should not be an ordered section.
* Strictly speaking, we could test for SHF_ORDERED here instead of
* ALL_SHF_ORDER as the two ordering flags are not supposed to be
* mixed. Using ALL_SHF_ORDER costs the same and ensures that such
* mixing doesn't go undetected.
*/
if (ifl->ifl_isdesc[keyshndx]->is_shdr->sh_flags & ALL_SHF_ORDER)
return (DBG_ORDER_INFO_ORDER);
return (0);
}
/*
* The sh_link field of an SHF_ORDERED section forms a linked list of
* sections. The list is terminated by a final section with a sh_link
* that points at itself. Given the index of an SHF_ORDERED section, find
* the index of the final section in the list.
*
* entry:
* ofl - Output file descriptor
* ifl - Input file descriptor
* ndx - Section index of SHF_ORDERED section
* alt_os_name - Address of pointer to string. If the final section
* name is different than the section given by ndx, *alt_os_name
* will be updated with the name of the final section. The caller
* should initialize *alt_os_name to NULL before calling
* this routine.
*
* exit:
* On success: If the final section is different than the section
* given by ndx, then *alt_os_name is set to its name. TRUE is returned.
*
* On failure, FALSE is returned.
*/
static Boolean
validate_shf_ordered_dest(Ofl_desc *ofl, Ifl_desc *ifl, Word ndx,
const char **alt_os_name)
{
Word shnum = ifl->ifl_shnum;
Word isp1_ndx, isp2_ndx;
Is_desc *isp1, *isp2;
int error = 0;
size_t iter = 0;
/*
* Traverse the list until we find the termination, or encounter
* an invalid condition in the object that prevents ordering.
*/
isp1_ndx = ndx;
isp1 = ifl->ifl_isdesc[ndx];
do {
/*
* Obtain index of next section in list. Ensure it is in range.
*/
isp2_ndx = isp1->is_shdr->sh_link;
if ((isp2_ndx == 0) || (isp2_ndx >= shnum)) {
error = DBG_ORDER_LINK_OUTRANGE;
break;
}
isp2 = ifl->ifl_isdesc[isp2_ndx];
/* The section flags must match exactly */
if (isp1->is_shdr->sh_flags != isp2->is_shdr->sh_flags) {
/*
* The case where the next section in the list does
* not have the same ordered flag set as the original
* ordered section gets a unique error code. This
* provides more accurate/useful debugging diagnostics.
*/
error = ((isp2->is_flags & FLG_IS_ORDERED) == 0) ?
DBG_ORDER_LINK_ERROR : DBG_ORDER_FLAGS;
break;
}
/*
* The sh_info field specifies the section index of an
* unorderd section which will be used as a sort key.
* Ensure it is in range. If not, we terminate the list
* at the current node instead of continuing on.
*/
if ((error = is_keyshndx_ok(ifl, isp2->is_shdr->sh_info)) != 0)
break;
/* If the section points at itself, it terminates the list */
if (isp1_ndx == isp2_ndx)
break;
/*
* Advance to next section in list
*/
isp1_ndx = isp2_ndx;
isp1 = isp2;
/*
* If we loop more times than the input file has sections,
* we have encountered a malformed object in which the list
* of SHF_ORDERED sections has a cycle. This can only happen
* if the compiler generating the object has a bad bug.
*/
if (++iter >= shnum) {
error = DBG_ORDER_CYCLIC;
break;
}
/* CONSTANTCONDITION */
} while (1);
/*
* If we have found a problem, issue a debug diagnostic and map
* the output section to 0. This indicates that the section should
* remove the ordering flag and treat it as a standard section.
*/
if (error != 0) {
isp2_ndx = 0;
DBG_CALL(Dbg_sec_order_error(ofl->ofl_lml, ifl, ndx, error));
}
/* Report success */
if (isp2_ndx != 0) {
/*
* If the destination section is different than the input
* section, then set *alt_os_name to the destination name.
*/
if (isp2_ndx != ndx)
*alt_os_name = ifl->ifl_isdesc[isp2_ndx]->is_name;
return (TRUE);
}
/* If we get here, there is no valid destination */
return (FALSE);
}
/*
* Called when an ordered section has a problem that prevents ordering.
* The order flag is removed, and then the section is placed as an
* unsorted section.
*/
static uintptr_t
place_unordered(Ofl_desc *ofl, Is_desc *isp, Place_path_info *path_info)
{
isp->is_flags &= ~FLG_IS_ORDERED;
if (isp->is_osdesc == NULL)
return ((uintptr_t)ld_place_section(ofl, isp, path_info,
isp->is_keyident, NULL));
return ((uintptr_t)isp->is_osdesc);
}
/*
* Process ordered input section. Called from process_elf() after
* all the non-ordered sections have been placed.
*
* entry:
* ofl - Output file descriptor
* ifl - Input file descriptor
* ndx - Section index of SHF_ORDERED section
*
* exit:
*/
uintptr_t
ld_process_ordered(Ofl_desc *ofl, Ifl_desc *ifl, Place_path_info *path_info,
Word ndx)
{
Is_desc *isp2, *isp = ifl->ifl_isdesc[ndx];
Xword shflags = isp->is_shdr->sh_flags;
const char *alt_os_name = NULL;
Word keyshndx;
Os_desc *osp;
int error = 0;
/*
* Obtain the sort key section index for this ordered section.
* SHF_ORDERED uses sh_info, while SHF_LINK_ORDER uses sh_link.
* In order for this function to be called, one of SHF_ORDERED
* or SHF_LINK_ORDER must be set. Testing for one implies the
* state of the other.
*/
keyshndx = (shflags & SHF_ORDERED) ?
isp->is_shdr->sh_info : isp->is_shdr->sh_link;
/*
* Validate the sort key section index. If something is wrong,
* fall back to treating it as a non-ordered section.
*/
if ((error = is_keyshndx_ok(ifl, keyshndx)) != 0) {
DBG_CALL(Dbg_sec_order_error(ofl->ofl_lml, ifl, ndx, error));
return (place_unordered(ofl, isp, path_info));
}
/*
* If SHF_ORDERED is in effect, validate the destination section
* name given by sh_link, and set alt_os_name to the name of the
* destination if it differs from the section being processed.
*/
if ((shflags & SHF_ORDERED) &&
(validate_shf_ordered_dest(ofl, ifl, ndx, &alt_os_name) == FALSE))
return (place_unordered(ofl, isp, path_info));
/*
* Place the section into its output section. It's possible that this
* section is discarded (possibly because it's defined COMDAT), in
* which case we're done.
*/
if ((osp = isp->is_osdesc) == NULL) {
osp = ld_place_section(ofl, isp, path_info, isp->is_keyident,
alt_os_name);
if ((osp == (Os_desc *)S_ERROR) || (osp == NULL))
return ((uintptr_t)osp);
}
/*
* If the output section is not yet on the ordered list, place it on
* the list.
*/
if (aplist_test(&ofl->ofl_ordered, osp, AL_CNT_OFL_ORDERED) ==
ALE_ALLOCFAIL)
return ((uintptr_t)S_ERROR);
/*
* Output section has been found - set up its sorting information.
*/
if ((keyshndx != SHN_BEFORE) && (keyshndx != SHN_AFTER)) {
Os_desc *osp2;
isp2 = ifl->ifl_isdesc[keyshndx];
if (isp2->is_flags & FLG_IS_DISCARD) {
ld_eprintf(ofl, ERR_FATAL, MSG_INTL(MSG_FIL_BADORDREF),
ifl->ifl_name, EC_WORD(isp->is_scnndx),
isp->is_name, EC_WORD(isp2->is_scnndx),
isp2->is_name);
return (S_ERROR);
}
/*
* Indicate that this ordered input section will require a
* sort key. Propagate the key requirement through to the
* associated output section, segment and file, to trigger
* the sort key creation. See ld_sec_validate();
*/
isp2->is_flags |= FLG_IS_KEY;
osp2 = isp2->is_osdesc;
osp2->os_flags |= FLG_OS_KEY;
osp2->os_sgdesc->sg_flags |= FLG_SG_KEY;
ofl->ofl_flags |= FLG_OF_KEY;
}
return ((uintptr_t)osp);
}
/*
* Traverse all segments looking for section ordering information that hasn't
* been used. If found give a warning message to the user. Also, check if
* there are any ordered key sections, and if so set up sort key values.
*/
void
ld_sec_validate(Ofl_desc *ofl)
{
Aliste idx1;
Sg_desc *sgp;
Word key = 1;
for (APLIST_TRAVERSE(ofl->ofl_segs, idx1, sgp)) {
Sec_order *scop;
Os_desc *osp;
Aliste idx2;
for (ALIST_TRAVERSE(sgp->sg_os_order, idx2, scop)) {
if ((scop->sco_flags & FLG_SGO_USED) == 0) {
ld_eprintf(ofl, ERR_WARNING,
MSG_INTL(MSG_MAP_SECORDER),
sgp->sg_name, scop->sco_secname);
}
}
if ((sgp->sg_flags & FLG_SG_KEY) == 0)
continue;
for (APLIST_TRAVERSE(sgp->sg_osdescs, idx2, osp)) {
Aliste idx3;
Is_desc *isp;
if ((osp->os_flags & FLG_OS_KEY) == 0)
continue;
/*
* The input sections used as sort keys are required
* to be unordered, so we only have to look at the
* DEFAULT list of input sections.
*/
for (APLIST_TRAVERSE(osp->os_isdescs[OS_ISD_DEFAULT],
idx3, isp)) {
if (isp->is_flags & FLG_IS_KEY)
isp->is_keyident = key++;
}
}
}
}
static int
comp(const void *ss1, const void *ss2)
{
Is_desc *s1 = *((Is_desc **)ss1);
Is_desc *s2 = *((Is_desc **)ss2);
Is_desc *i1, *i2;
Word ndx1, ndx2;
if (s1->is_shdr->sh_flags & SHF_ORDERED)
ndx1 = s1->is_shdr->sh_info;
else
ndx1 = s1->is_shdr->sh_link;
if (s2->is_shdr->sh_flags & SHF_ORDERED)
ndx2 = s2->is_shdr->sh_info;
else
ndx2 = s2->is_shdr->sh_link;
i1 = s1->is_file->ifl_isdesc[ndx1];
i2 = s2->is_file->ifl_isdesc[ndx2];
if (i1->is_keyident > i2->is_keyident)
return (1);
if (i1->is_keyident < i2->is_keyident)
return (-1);
return (0);
}
/*
* Sort ordered input sections
*/
uintptr_t
ld_sort_ordered(Ofl_desc *ofl)
{
Aliste idx1;
Os_desc *osp;
DBG_CALL(Dbg_sec_order_list(ofl, 0));
for (APLIST_TRAVERSE(ofl->ofl_ordered, idx1, osp)) {
APlist *ap_list = osp->os_isdescs[OS_ISD_ORDERED];
Aliste apl_nitems = aplist_nitems(ap_list);
/*
* If this output section has a non-empty list of ordered
* input sections, sort their APlist in place into their
* final order.
*/
if (apl_nitems != 0)
qsort((char *)ap_list->apl_data, apl_nitems,
sizeof (Is_desc *), comp);
}
DBG_CALL(Dbg_sec_order_list(ofl, 1));
return (0);
}