unwind.c revision 69112edd987c28fa551d4f8d9362a84a45365f17
/*
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sgs.h>
#include <debug.h>
#include <_libld.h>
#include <dwarf.h>
#include <stdlib.h>
/*
* A EH_FRAME_HDR consists of the following:
*
* Encoding Field
* --------------------------------
* unsigned byte version
* unsigned byte eh_frame_ptr_enc
* unsigned byte fde_count_enc
* unsigned byte table_enc
* encoded eh_frame_ptr
* encoded fde_count
* [ binary search table ]
*
* The binary search table entries each consists of:
*
* encoded initial_func_loc
* encoded FDE_address
*
* The entries in the binary search table are sorted
* in a increasing order by the initial location.
*
*
* version
*
* Version of the .eh_frame_hdr format. This value shall be 1.
*
* eh_frame_ptr_enc
*
* The encoding format of the eh_frame_ptr field. For shared
* libraries the encoding must be
* DW_EH_PE_sdata4|DW_EH_PE_pcrel or
* DW_EH_PE_sdata4|DW_EH_PE_datarel.
*
*
* fde_count_enc
*
* The encoding format of the fde_count field. A value of
* DW_EH_PE_omit indicates the binary search table is not
* present.
*
* table_enc
*
* The encoding format of the entries in the binary search
* table. A value of DW_EH_PE_omit indicates the binary search
* table is not present. For shared libraries the encoding
* must be DW_EH_PE_sdata4|DW_EH_PE_pcrel or
* DW_EH_PE_sdata4|DW_EH_PE_datarel.
*
*
* eh_frame_ptr
*
* The encoded value of the pointer to the start of the
* .eh_frame section.
*
* fde_count
*
* The encoded value of the count of entries in the binary
* search table.
*
* binary search table
*
* A binary search table containing fde_count entries. Each
* entry of the table consist of two encoded values, the
* initial location of the function to which an FDE applies,
* and the address of the FDE. The entries are sorted in an
* increasing order by the initial location value.
*
*/
/*
* EH_FRAME sections
* =================
*
* The call frame information needed for unwinding the stack is output in
* an ELF section(s) of type SHT_AMD64_UNWIND (amd64) or SHT_PROGBITS (other).
* In the simplest case there will be one such section per object file and it
* will be named ".eh_frame". An .eh_frame section consists of one or more
* subsections. Each subsection contains a CIE (Common Information Entry)
* followed by varying number of FDEs (Frame Descriptor Entry). A FDE
* corresponds to an explicit or compiler generated function in a
* compilation unit, all FDEs can access the CIE that begins their
* subsection for data.
*
* If an object file contains C++ template instantiations, there shall be
* a separate CIE immediately preceding each FDE corresponding to an
* instantiation.
*
* Using the preferred encoding specified below, the .eh_frame section can
* be entirely resolved at link time and thus can become part of the
* text segment.
*
* .eh_frame Section Layout
* ------------------------
*
* EH_PE encoding below refers to the pointer encoding as specified in the
* enhanced LSB Chapter 7 for Eh_Frame_Hdr.
*
* Common Information Entry (CIE)
* ------------------------------
* CIE has the following format:
*
* Length
* in
* Field Byte Description
* ----- ------ -----------
* 1. Length 4 Length of CIE (not including
* this 4-byte field).
*
* 2. CIE id 4 Value Zero (0) for .eh_frame
* (used to distinguish CIEs and
* FDEs when scanning the section)
*
* 3. Version 1 Value One (1)
*
* 4. CIE Augmentation string Null-terminated string with legal
* values being "" or 'z' optionally
* followed by single occurrences of
* 'P', 'L', or 'R' in any order.
* String The presence of character(s) in the
* string dictates the content of
* field 8, the Augmentation Section.
* Each character has one or two
* associated operands in the AS.
* Operand order depends on
* position in the string ('z' must
* be first).
*
* 5. Code Align Factor uleb128 To be multiplied with the
* "Advance Location" instructions in
* the Call Frame Instructions
*
* 6. Data Align Factor sleb128 To be multiplied with all offset
* in the Call Frame Instructions
*
* 7. Ret Address Reg 1 A "virtual" register representation
* of the return address. In Dwarf V2,
* this is a byte, otherwise it is
* uleb128. It is a byte in gcc 3.3.x
*
* 8. Optional CIE varying Present if Augmentation String in
* Augmentation Section field 4 is not 0.
*
* z:
* size uleb128 Length of the remainder of the
* Augmentation Section
*
* P:
* personality_enc 1 Encoding specifier - preferred
* value is a pc-relative, signed
* 4-byte
*
*
* personality routine (encoded) Encoded pointer to personality
* routine (actually to the PLT
* entry for the personality
* routine)
* R:
* code_enc 1 Non-default encoding for the
* code-pointers (FDE members
* "initial_location" and "address_range"
* and the operand for DW_CFA_set_loc)
* - preferred value is pc-relative,
* signed 4-byte.
* L:
* lsda_enc 1 FDE augmentation bodies may contain
* LSDA pointers. If so they are
* encoded as specified here -
* preferred value is pc-relative,
* signed 4-byte possibly indirect
* thru a GOT entry.
*
*
* 9. Optional Call Frame varying
* Instructions
*
* The size of the optional call frame instruction area must be computed
* based on the overall size and the offset reached while scanning the
* preceding fields of the CIE.
*
*
* Frame Descriptor Entry (FDE)
* ----------------------------
* FDE has the following format:
*
* Length
* in
* Field Byte Description
* ----- ------ -----------
* 1. Length 4 Length of remainder of this FDE
*
* 2. CIE Pointer 4 Distance from this field to the
* nearest preceding CIE
* (uthe value is subtracted from the
* current address). This value
* can never be zero and thus can
* be used to distinguish CIE's and
* FDE's when scanning the
* .eh_frame section
*
* 3. Initial Location varying Reference to the function code
* corresponding to this FDE.
* If 'R' is missing from the CIE
* Augmentation String, the field is an
* 8-byte absolute pointer. Otherwise,
* the corresponding EH_PE encoding in the
* CIE Augmentation Section is used to
* interpret the reference.
*
* 4. Address Range varying Size of the function code corresponding
* to this FDE.
* If 'R' is missing from the CIE
* Augmentation String, the field is an
* 8-byte unsigned number. Otherwise,
* the size is determined by the
* corresponding EH_PE encoding in the
* CIE Augmentation Section (the
* value is always absolute).
*
* 5. Optional FDE varying present if CIE augmentation
* Augmentation Section string is non-empty.
*
*
* 'z':
* length uleb128 length of the remainder of the
* FDE augmentation section
*
*
* 'L' (and length > 0):
* LSDA varying LSDA pointer, encoded in the
* format specified by the
* corresponding operand in the CIE's
* augmentation body.
*
* 6. Optional Call varying
* Frame Instructions
*
* The size of the optional call frame instruction area must be computed
* based on the overall size and the offset reached while scanning the
* preceding fields of the FDE.
*
* The overall size of a .eh_frame section is given in the ELF section
* header. The only way to determine the number of entries is to scan
* the section till the end and count.
*
*/
static uint_t
extract_uint(const uchar_t *data, uint64_t *ndx, int do_swap)
{
uint_t r;
uchar_t *p = (uchar_t *)&r;
data += *ndx;
if (do_swap)
UL_ASSIGN_BSWAP_WORD(p, data);
else
UL_ASSIGN_WORD(p, data);
(*ndx) += 4;
return (r);
}
/*
* Create an unwind header (.eh_frame_hdr) output section.
* The section is created and space reserved, but the data
* is not copied into place. That is done by a later call
* to ld_unwind_populate(), after active relocations have been
* processed.
*
* When GNU linkonce processing is in effect, we can end up in a situation
* where the FDEs related to discarded sections remain in the eh_frame
* section. Ideally, we would remove these dead entries from eh_frame.
* However, that optimization has not yet been implemented. In the current
* implementation, the number of dead FDEs cannot be determined until
* active relocations are processed, and that processing follows the
* call to this function. This means that we are unable to detect dead FDEs
* here, and the section created by this routine is sized for maximum case
* where all FDEs are valid.
*/
uintptr_t
ld_unwind_make_hdr(Ofl_desc *ofl)
{
int bswap = (ofl->ofl_flags1 & FLG_OF1_ENCDIFF) != 0;
Shdr *shdr;
Elf_Data *elfdata;
Is_desc *isp;
size_t size;
Xword fde_cnt;
Aliste idx1;
Os_desc *osp;
/*
* we only build a unwind header if we have
* some unwind information in the file.
*/
if (ofl->ofl_unwind == NULL)
return (1);
/*
* Allocate and initialize the Elf_Data structure.
*/
if ((elfdata = libld_calloc(sizeof (Elf_Data), 1)) == NULL)
return (S_ERROR);
elfdata->d_type = ELF_T_BYTE;
elfdata->d_align = ld_targ.t_m.m_word_align;
elfdata->d_version = ofl->ofl_dehdr->e_version;
/*
* Allocate and initialize the Shdr structure.
*/
if ((shdr = libld_calloc(sizeof (Shdr), 1)) == NULL)
return (S_ERROR);
shdr->sh_type = ld_targ.t_m.m_sht_unwind;
shdr->sh_flags = SHF_ALLOC;
shdr->sh_addralign = ld_targ.t_m.m_word_align;
shdr->sh_entsize = 0;
/*
* Allocate and initialize the Is_desc structure.
*/
if ((isp = libld_calloc(1, sizeof (Is_desc))) == NULL)
return (S_ERROR);
isp->is_name = MSG_ORIG(MSG_SCN_UNWINDHDR);
isp->is_shdr = shdr;
isp->is_indata = elfdata;
if ((ofl->ofl_unwindhdr = ld_place_section(ofl, isp, NULL,
ld_targ.t_id.id_unwindhdr, NULL)) == (Os_desc *)S_ERROR)
return (S_ERROR);
/*
* Scan through all of the input Frame information, counting each FDE
* that requires an index. Each fde_entry gets a corresponding entry
* in the binary search table.
*/
fde_cnt = 0;
for (APLIST_TRAVERSE(ofl->ofl_unwind, idx1, osp)) {
Aliste idx2;
int os_isdescs_idx;
OS_ISDESCS_TRAVERSE(os_isdescs_idx, osp, idx2, isp) {
uchar_t *data;
uint64_t off = 0;
data = isp->is_indata->d_buf;
size = isp->is_indata->d_size;
while (off < size) {
uint_t length, id;
uint64_t ndx = 0;
/*
* Extract length in lsb format. A zero length
* indicates that this CIE is a terminator and
* that processing for unwind information is
* complete.
*/
length = extract_uint(data + off, &ndx, bswap);
if (length == 0)
break;
/*
* Extract CIE id in lsb format.
*/
id = extract_uint(data + off, &ndx, bswap);
/*
* A CIE record has a id of '0', otherwise
* this is a FDE entry and the 'id' is the
* CIE pointer.
*/
if (id == 0) {
uint_t cieversion;
/*
* The only CIE version supported
* is '1' - quick sanity check
* here.
*/
cieversion = data[off + ndx];
ndx += 1;
/* BEGIN CSTYLED */
if (cieversion != 1) {
eprintf(ofl->ofl_lml, ERR_FATAL,
MSG_INTL(MSG_UNW_BADCIEVERS),
isp->is_file->ifl_name,
isp->is_name, off);
return (S_ERROR);
}
/* END CSTYLED */
} else {
fde_cnt++;
}
off += length + 4;
}
}
}
/*
* section size:
* byte version +1
* byte eh_frame_ptr_enc +1
* byte fde_count_enc +1
* byte table_enc +1
* 4 bytes eh_frame_ptr +4
* 4 bytes fde_count +4
* [4 bytes] [4bytes] * fde_count ...
*/
size = 12 + (8 * fde_cnt);
if ((elfdata->d_buf = libld_calloc(size, 1)) == NULL)
return (S_ERROR);
elfdata->d_size = size;
shdr->sh_size = (Xword)size;
return (1);
}
/*
* the comparator function needs to calculate
* the actual 'initloc' of a bintab entry - to
* do this we initialize the following global to point
* to it.
*/
static Addr framehdr_addr;
static int
bintabcompare(const void *p1, const void *p2)
{
uint_t *bintab1, *bintab2;
uint_t ent1, ent2;
bintab1 = (uint_t *)p1;
bintab2 = (uint_t *)p2;
assert(bintab1 != 0);
assert(bintab2 != 0);
ent1 = *bintab1 + framehdr_addr;
ent2 = *bintab2 + framehdr_addr;
if (ent1 > ent2)
return (1);
if (ent1 < ent2)
return (-1);
return (0);
}
uintptr_t
ld_unwind_populate_hdr(Ofl_desc *ofl)
{
uchar_t *hdrdata;
uint_t *binarytable;
uint_t hdroff;
Aliste idx;
Addr hdraddr;
Os_desc *hdrosp;
Os_desc *osp;
Os_desc *first_unwind;
uint_t fde_count;
uint_t *uint_ptr;
int bswap = (ofl->ofl_flags1 & FLG_OF1_ENCDIFF) != 0;
/*
* Are we building the unwind hdr?
*/
if ((hdrosp = ofl->ofl_unwindhdr) == 0)
return (1);
hdrdata = hdrosp->os_outdata->d_buf;
hdraddr = hdrosp->os_shdr->sh_addr;
hdroff = 0;
/*
* version == 1
*/
hdrdata[hdroff++] = 1;
/*
* The encodings are:
*
* eh_frameptr_enc sdata4 | pcrel
* fde_count_enc udata4
* table_enc sdata4 | datarel
*/
hdrdata[hdroff++] = DW_EH_PE_sdata4 | DW_EH_PE_pcrel;
hdrdata[hdroff++] = DW_EH_PE_udata4;
hdrdata[hdroff++] = DW_EH_PE_sdata4 | DW_EH_PE_datarel;
/*
* Header Offsets
* -----------------------------------
* byte version +1
* byte eh_frame_ptr_enc +1
* byte fde_count_enc +1
* byte table_enc +1
* 4 bytes eh_frame_ptr +4
* 4 bytes fde_count +4
*/
/* LINTED */
binarytable = (uint_t *)(hdrdata + 12);
first_unwind = 0;
fde_count = 0;
for (APLIST_TRAVERSE(ofl->ofl_unwind, idx, osp)) {
uchar_t *data;
size_t size;
uint64_t off = 0;
uint_t cieRflag = 0, ciePflag = 0;
Shdr *shdr;
/*
* remember first UNWIND section to
* point to in the frame_ptr entry.
*/
if (first_unwind == 0)
first_unwind = osp;
data = osp->os_outdata->d_buf;
shdr = osp->os_shdr;
size = shdr->sh_size;
while (off < size) {
uint_t length, id;
uint64_t ndx = 0;
/*
* Extract length in lsb format. A zero length
* indicates that this CIE is a terminator and that
* processing of unwind information is complete.
*/
length = extract_uint(data + off, &ndx, bswap);
if (length == 0)
goto done;
/*
* Extract CIE id in lsb format.
*/
id = extract_uint(data + off, &ndx, bswap);
/*
* A CIE record has a id of '0'; otherwise
* this is a FDE entry and the 'id' is the
* CIE pointer.
*/
if (id == 0) {
char *cieaugstr;
uint_t cieaugndx;
ciePflag = 0;
cieRflag = 0;
/*
* We need to drill through the CIE
* to find the Rflag. It's the Rflag
* which describes how the FDE code-pointers
* are encoded.
*/
/*
* burn through version
*/
ndx++;
/*
* augstr
*/
cieaugstr = (char *)(&data[off + ndx]);
ndx += strlen(cieaugstr) + 1;
/*
* calign & dalign
*/
(void) uleb_extract(&data[off], &ndx);
(void) sleb_extract(&data[off], &ndx);
/*
* retreg
*/
ndx++;
/*
* we walk through the augmentation
* section now looking for the Rflag
*/
for (cieaugndx = 0; cieaugstr[cieaugndx];
cieaugndx++) {
/* BEGIN CSTYLED */
switch (cieaugstr[cieaugndx]) {
case 'z':
/* size */
(void) uleb_extract(&data[off],
&ndx);
break;
case 'P':
/* personality */
ciePflag = data[off + ndx];
ndx++;
/*
* Just need to extract the
* value to move on to the next
* field.
*/
(void) dwarf_ehe_extract(
&data[off + ndx],
&ndx, ciePflag,
ofl->ofl_dehdr->e_ident,
shdr->sh_addr, off + ndx);
break;
case 'R':
/* code encoding */
cieRflag = data[off + ndx];
ndx++;
break;
case 'L':
/* lsda encoding */
ndx++;
break;
}
/* END CSTYLED */
}
} else {
uint_t bintabndx;
uint64_t initloc;
uint64_t fdeaddr;
initloc = dwarf_ehe_extract(&data[off],
&ndx, cieRflag, ofl->ofl_dehdr->e_ident,
shdr->sh_addr, off + ndx);
/*
* Ignore FDEs with initloc set to 0.
* initloc will not be 0 unless this FDE was
* abandoned due to GNU linkonce processing.
* The 0 value occurs because we don't resolve
* sloppy relocations for unwind header target
* sections.
*/
if (initloc != 0) {
bintabndx = fde_count * 2;
fde_count++;
/*
* FDEaddr is adjusted
* to account for the length & id which
* have already been consumed.
*/
fdeaddr = shdr->sh_addr + off;
binarytable[bintabndx] =
(uint_t)(initloc - hdraddr);
binarytable[bintabndx + 1] =
(uint_t)(fdeaddr - hdraddr);
}
}
/*
* the length does not include the length
* itself - so account for that too.
*/
off += length + 4;
}
}
done:
/*
* Do a quicksort on the binary table. If this is a cross
* link from a system with the opposite byte order, xlate
* the resulting values into LSB order.
*/
framehdr_addr = hdraddr;
qsort((void *)binarytable, (size_t)fde_count,
(size_t)(sizeof (uint_t) * 2), bintabcompare);
if (bswap) {
uint_t *btable = binarytable;
uint_t cnt;
for (cnt = fde_count * 2; cnt-- > 0; btable++)
*btable = ld_bswap_Word(*btable);
}
/*
* Fill in:
* first_frame_ptr
* fde_count
*/
hdroff = 4;
/* LINTED */
uint_ptr = (uint_t *)(&hdrdata[hdroff]);
*uint_ptr = first_unwind->os_shdr->sh_addr -
(hdrosp->os_shdr->sh_addr + hdroff);
if (bswap)
*uint_ptr = ld_bswap_Word(*uint_ptr);
hdroff += 4;
/* LINTED */
uint_ptr = (uint_t *)&hdrdata[hdroff];
*uint_ptr = fde_count;
if (bswap)
*uint_ptr = ld_bswap_Word(*uint_ptr);
/*
* If relaxed relocations are active, then there is a chance
* that we didn't use all the space reserved for this section.
* For details, see the note at head of ld_unwind_make_hdr() above.
*
* Find the PT_SUNW_UNWIND program header, and change the size values
* to the size of the subset of the section that was actually used.
*/
if (ofl->ofl_flags1 & FLG_OF1_RLXREL) {
Word phnum = ofl->ofl_nehdr->e_phnum;
Phdr *phdr = ofl->ofl_phdr;
for (; phnum-- > 0; phdr++) {
if (phdr->p_type == PT_SUNW_UNWIND) {
phdr->p_memsz = 12 + (8 * fde_count);
phdr->p_filesz = phdr->p_memsz;
break;
}
}
}
return (1);
}
/*
* Append an .eh_frame section to our output list if not already present.
*
* Usually, there is a single .eh_frame output section. However, there can
* be more if there are incompatible section flags on incoming sections.
* If this does happen, the frame_ptr field of the eh_frame_hdr section
* will point at the base of the first output section, and the other
* sections will not be accessible via frame_ptr. However, the .eh_frame_hdr
* will be able to access all the data in the different .eh_frame sections,
* because the entries in sorted table are all encoded as DW_EH_PE_datarel.
*/
uintptr_t
ld_unwind_register(Os_desc *osp, Ofl_desc * ofl)
{
Aliste idx;
Os_desc *_osp;
/*
* Check to see if this output section is already
* on the list.
*/
for (APLIST_TRAVERSE(ofl->ofl_unwind, idx, _osp))
if (osp == _osp)
return (1);
/*
* Append output section to unwind list
*/
if (aplist_append(&ofl->ofl_unwind, osp, AL_CNT_OFL_UNWIND) == NULL)
return (S_ERROR);
return (1);
}