dt_link.c revision 92e807e650499591f2549dc94c6d20b81e94e394
/*
* 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
* 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"
#define ELF_TARGET_ALL
#include <elf.h>
#include <sys/sysmacros.h>
#include <unistd.h>
#include <strings.h>
#include <alloca.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <wait.h>
#include <assert.h>
#include <dt_impl.h>
#include <dt_provider.h>
#include <dt_program.h>
#include <dt_string.h>
#define ESHDR_NULL 0
#define ESHDR_SHSTRTAB 1
#define ESHDR_DOF 2
#define ESHDR_STRTAB 3
#define ESHDR_SYMTAB 4
#define ESHDR_REL 5
#define ESHDR_NUM 6
static const char DTRACE_SHSTRTAB32[] = "\0"
".shstrtab\0" /* 1 */
".SUNW_dof\0" /* 11 */
".strtab\0" /* 21 */
".symtab\0" /* 29 */
#ifdef __sparc
".rela.SUNW_dof"; /* 37 */
#else
".rel.SUNW_dof"; /* 37 */
#endif
static const char DTRACE_SHSTRTAB64[] = "\0"
".shstrtab\0" /* 1 */
".SUNW_dof\0" /* 11 */
".strtab\0" /* 21 */
".symtab\0" /* 29 */
".rela.SUNW_dof"; /* 37 */
static const char DOFSTR[] = "__SUNW_dof";
static const char DOFLAZYSTR[] = "___SUNW_dof";
typedef struct dt_link_pair {
void *dlp_str; /* buffer for string table */
void *dlp_sym; /* buffer for symbol table */
typedef struct dof_elf32 {
#ifdef __sparc
#else
#endif
char *de_strtab; /* string table */
} dof_elf32_t;
static int
{
char *strtab;
int i, j, nrel;
#ifdef __sparc
#else
#endif
/*LINTED*/
/*
* First compute the size of the string table and the number of
* relocations present in the DOF.
*/
for (i = 0; i < dof->dofh_secnum; i++) {
continue;
/*LINTED*/
/*LINTED*/
}
if (dtp->dt_lazyload) {
} else {
}
}
}
}
count = 0;
strtabsz = 1;
/*
* The first symbol table entry must be zeroed and is always ignored.
*/
sym++;
/*
* Take a second pass through the DOF sections filling in the
* memory we allocated.
*/
for (i = 0; i < dof->dofh_secnum; i++) {
continue;
/*LINTED*/
/*LINTED*/
for (j = 0; j < nrel; j++) {
dofr[j].dofr_offset;
R_386_32);
/*
* Add 4 bytes to hit the low half of this 64-bit
* big-endian address.
*/
#else
#endif
rel++;
sym++;
count++;
}
}
/*
* Add a symbol for the DOF itself. We use a different symbol for
* lazily and actively loaded DOF to make them easy to distinguish.
*/
sym++;
if (dtp->dt_lazyload) {
sizeof (DOFLAZYSTR));
strtabsz += sizeof (DOFLAZYSTR);
} else {
}
return (0);
}
typedef struct dof_elf64 {
char *de_strtab;
} dof_elf64_t;
static int
{
char *strtab;
int i, j, nrel;
/*LINTED*/
/*
* First compute the size of the string table and the number of
* relocations present in the DOF.
*/
for (i = 0; i < dof->dofh_secnum; i++) {
continue;
/*LINTED*/
/*LINTED*/
}
if (dtp->dt_lazyload) {
} else {
}
}
}
}
count = 0;
strtabsz = 1;
/*
* The first symbol table entry must be zeroed and is always ignored.
*/
sym++;
/*
* Take a second pass through the DOF sections filling in the
* memory we allocated.
*/
for (i = 0; i < dof->dofh_secnum; i++) {
continue;
/*LINTED*/
/*LINTED*/
for (j = 0; j < nrel; j++) {
dofr[j].dofr_offset;
dofr[j].dofr_offset;
#else
#endif
rel++;
sym++;
count++;
}
}
/*
* Add a symbol for the DOF itself. We use a different symbol for
* lazily and actively loaded DOF to make them easy to distinguish.
*/
sym++;
if (dtp->dt_lazyload) {
sizeof (DOFLAZYSTR));
strtabsz += sizeof (DOFLAZYSTR);
} else {
}
return (0);
}
/*
* Write out an ELF32 file prologue consisting of a header, section headers,
* and a section header string table. The DOF data will follow this prologue
* and complete the contents of the given ELF file.
*/
static int
{
struct {
} elf_file;
int ret = 0;
return (-1); /* errno is set for us */
/*
* If there are no relocations, we only need enough sections for
* the shstrtab and the DOF.
*/
#if defined(_BIG_ENDIAN)
#elif defined(_LITTLE_ENDIAN)
#endif
#if defined(__sparc)
#endif
shp->sh_addralign = sizeof (char);
shp->sh_addralign = sizeof (char);
}
} else {
#ifdef __sparc
#else
#endif
}
}
return (ret);
}
/*
* Write out an ELF64 file prologue consisting of a header, section headers,
* and a section header string table. The DOF data will follow this prologue
* and complete the contents of the given ELF file.
*/
static int
{
struct {
} elf_file;
int ret = 0;
return (-1); /* errno is set for us */
/*
* If there are no relocations, we only need enough sections for
* the shstrtab and the DOF.
*/
#if defined(_BIG_ENDIAN)
#elif defined(_LITTLE_ENDIAN)
#endif
#if defined(__sparc)
#endif
shp->sh_addralign = sizeof (char);
shp->sh_addralign = sizeof (char);
}
} else {
}
}
return (ret);
}
static int
{
int i, ret = -1;
GElf_Sym s;
return (0);
ret = 0;
s = *sym;
}
}
if (ret == 0)
*sym = s;
return (ret);
}
#if defined(__sparc)
#define DT_OP_RET 0x81c7e008
#define DT_OP_NOP 0x01000000
#define DT_OP_CALL 0x40000000
#define DT_OP_CLR_O0 0x90102000
/*ARGSUSED*/
static int
{
return (-1);
/*LINTED*/
/*
* We only know about some specific relocation types.
*/
return (-1);
/*
* We may have already processed this object file in an earlier linker
* invocation. Check to see if the present instruction sequence matches
* the one we would install.
*/
if (isenabled) {
if (ip[0] == DT_OP_CLR_O0)
return (0);
} else {
return (0);
if (DT_IS_RETL(ip[0]))
return (0);
} else {
return (0);
}
}
}
/*
* We only expect call instructions with a displacement of 0.
*/
if (ip[0] != DT_OP_CALL) {
dt_dprintf("found %x instead of a call instruction at %llx\n",
return (-1);
}
if (isenabled) {
/*
* It would necessarily indicate incorrect usage if an is-
* enabled probe were tail-called so flag that as an error.
* It's also potentially (very) tricky to handle gracefully,
* but could be done if this were a desired use scenario.
*/
dt_dprintf("tail call to is-enabled probe at %llx\n",
return (-1);
}
ip[0] = DT_OP_CLR_O0;
} else {
/*
* If the call is followed by a restore, it's a tail call so
* change the call to a ret. If the call if followed by a mov
* of a register into %o7, it's a tail call in leaf context
* so change the call to a retl-like instruction that returns
* to that register value + 8 (rather than the typical %o7 +
* 8); the delay slot instruction is left, but should have no
* effect. Otherwise we change the call to be a nop. In the
* first and the last case we adjust the offset to land on what
* was once the delay slot of the call so we correctly get all
* the arguments as they would have been passed in a normal
* function call.
*/
} else {
}
}
return (0);
}
#define DT_OP_NOP 0x90
#define DT_OP_CALL 0xe8
#define DT_OP_REX_RAX 0x48
#define DT_OP_XOR_EAX_0 0x33
#define DT_OP_XOR_EAX_1 0xc0
static int
{
/*
* On x86, the first byte of the instruction is the call opcode and
* the next four bytes are the 32-bit address; the relocation is for
* the address operand. We back up the offset to the first byte of
* the instruction. For is-enabled probes, we later advance the offset
* so that it hits the first nop in the instruction sequence.
*/
(*off) -= 1;
/*
* We only know about some specific relocation types. Luckily
* these types have the same values on both 32-bit and 64-bit
* x86 architectures.
*/
return (-1);
/*
* We may have already processed this object file in an earlier linker
* invocation. Check to see if the present instruction sequence matches
* the one we would install. For is-enabled probes, we advance the
* offset to the first nop instruction in the sequence.
*/
if (!isenabled) {
return (0);
if (ip[0] == DT_OP_REX_RAX &&
(*off) += 3;
return (0);
}
} else {
(*off) += 2;
return (0);
}
}
/*
* We only expect a call instrution with a 32-bit displacement.
*/
if (ip[0] != DT_OP_CALL) {
dt_dprintf("found %x instead of a call instruction at %llx\n",
return (-1);
}
/*
* Establish the instruction sequence -- all nops for probes, and an
* instruction to clear the return value register (%eax/%rax) followed
* by nops for is-enabled probes. For is-enabled probes, we advance
* the offset to the first nop. This isn't stricly necessary but makes
* for more readable disassembly when the probe is enabled.
*/
if (!isenabled) {
ip[0] = DT_OP_REX_RAX;
(*off) += 3;
} else {
ip[0] = DT_OP_XOR_EAX_0;
(*off) += 2;
}
return (0);
}
#else
#endif
/*PRINTFLIKE5*/
static int
const char *format, ...)
{
if (fd >= 0)
}
}
static int
{
static const char dt_prefix[] = "__dtrace";
static const char dt_enabled[] = "enabled";
static const char dt_symprefix[] = "$dtrace";
static const char dt_symfmt[] = "%s%d.%s";
char *s, *p, *r;
char pname[DTRACE_PROVNAMELEN];
}
}
case ELF_K_ELF:
break;
case ELF_K_AR:
"permitted; use the contents of the archive instead: %s",
obj));
default:
"invalid file type: %s", obj));
}
obj));
}
eclass = ELFCLASS64;
#if defined(__sparc)
#endif
} else {
eclass = ELFCLASS32;
#if defined(__sparc)
#endif
}
"incorrect ELF class for object file: %s", obj));
}
"incorrect ELF machine type for object file: %s", obj));
}
/*
* We use this token as a relatively unique handle for this file on the
* system in order to disambiguate potential conflicts between files of
* the same name which contain identially named local symbols.
*/
"failed to generate unique key for object file: %s", obj));
}
goto err;
/*
* Skip any non-relocation sections.
*/
continue;
goto err;
/*
* Grab the section, section header and section data for the
* symbol table that this relocation section references.
*/
goto err;
/*
* Ditto for that symbol table's string table.
*/
goto err;
/*
* Grab the section, section header and section data for the
* target section for the relocations. For the relocations
* we're looking for -- this will typically be the text of the
* object file.
*/
goto err;
/*
* We're looking for relocations to symbols matching this form:
*
* __dtrace[enabled]_<prov>___<probe>
*
* For the generated object, we need to record the location
* identified by the relocation, and create a new relocation
* in the generated object that will be resolved at link time
* to the location of the function in which the probe is
* embedded. In the target object, we change the matched symbol
* so that it will be ignored at link time, and we modify the
* target (text) section to replace the call instruction with
* one or more nops.
*
* If the function containing the probe is locally scoped
* (static), we create an alias used by the relocation in the
* generated object. The alias, a new symbol, will be global
* (so that the relocation from the generated object can be
* resolved), and hidden (so that it is converted to a local
* symbol at link time). Such aliases have this form:
*
* $dtrace<key>.<function>
*
* We take a first pass through all the relocations to
* populate our string table and count the number of extra
* symbols we'll require.
*/
nsym = 0;
continue;
} else {
continue;
}
goto err;
}
continue;
goto err;
}
continue;
goto err;
}
/*
* If this symbol isn't of type function, we've really
* driven off the rails or the object file is corrupt.
*/
"expected %s to be of type function", s));
}
objkey, s) + 1;
goto err;
}
objkey, s);
nsym++;
(void) dt_strtab_insert(strtab, p);
}
}
/*
* If needed, allocate the additional space for the symbol
* table and string table copying the old data into the new
* buffers, and marking the buffers as dirty. We inject those
* newly allocated buffers into the libelf data structures, but
* are still responsible for freeing them once we're done with
* the elf handle.
*/
if (nsym > 0) {
/*
* The first byte of the string table is reserved for
* the \0 entry.
*/
goto err;
goto err;
}
goto err;
}
} else {
}
/*
* Now that the tables have been allocated, perform the
* modifications described above.
*/
continue;
} else {
continue;
}
goto err;
continue;
s += sizeof (dt_prefix) - 1;
/*
* Check to see if this is an 'is-enabled' check as
* opposed to a normal probe.
*/
if (strncmp(s, dt_enabled,
sizeof (dt_enabled) - 1) == 0) {
s += sizeof (dt_enabled) - 1;
eprobe = 1;
*eprobesp = 1;
dt_dprintf("is-enabled probe\n");
} else {
eprobe = 0;
dt_dprintf("normal probe\n");
}
if (*s++ != '_')
goto err;
p - s >= sizeof (pname))
goto err;
pname[p - s] = '\0';
goto err;
goto err;
/*
* If a NULL relocation name is passed to
* dt_probe_define(), the function name is used for the
* relocation. The relocation needs to use a mangled
* name if the symbol is locally scoped; the function
* name may need to change if we've found the global
* alias for the locally scoped symbol (we prefer
* global symbols to locals in dt_symtab_lookup()).
*/
r = NULL;
STT_FUNC);
dt_symprefix, objkey, s);
isym++;
} else if (strncmp(s, dt_symprefix,
strlen(dt_symprefix)) == 0) {
r = s;
goto err;
s++;
}
"no such provider %s", pname));
}
"no such probe %s", p));
}
goto err;
}
"failed to allocate space for probe"));
}
mod = 1;
/*
* This symbol may already have been marked to
* be ignored by another relocation referencing
* the same symbol or if this object file has
* already been processed by an earlier link
* invocation.
*/
}
}
}
goto err;
}
return (0);
err:
"an error was encountered while processing %s", obj));
}
int
{
/*
* A NULL program indicates a special use in which we just link
* together a bunch of object files specified in objv and then
* unlink(2) those object files.
*/
const char *fmt = "%s -o %s -r";
for (i = 0; i < objc; i++)
for (i = 0; i < objc; i++)
}
if (WIFSIGNALED(status)) {
"failed to link %s: %s failed due to signal %d",
}
if (WEXITSTATUS(status) != 0) {
"failed to link %s: %s exited with status %d\n",
}
for (i = 0; i < objc; i++) {
}
return (0);
}
for (i = 0; i < objc; i++) {
return (-1); /* errno is set for us */
}
/*
* If there are is-enabled probes then we need to force use of DOF
* version 2.
*/
return (-1); /* errno is set for us */
/*
* Create a temporary file and then unlink it if we're going to
* combine it with drti.o later. We can still refer to it in child
*/
}
/*
* If -xlinktype=DOF has been selected, just write out the DOF.
* Otherwise proceed to the default of generating and linking ELF.
*/
switch (dtp->dt_linktype) {
case DT_LTYP_DOF:
if (ret != 0) {
}
return (0);
case DT_LTYP_ELF:
break; /* fall through to the rest of dtrace_program_link() */
default:
}
if (!dtp->dt_lazyload)
else
}
if (!dtp->dt_lazyload) {
"%s/64/drti.o", _dtrace_libdir);
} else {
"%s/drti.o", _dtrace_libdir);
}
drti) + 1;
goto done;
}
if (WIFSIGNALED(status)) {
"failed to link %s: %s failed due to signal %d",
goto done;
}
if (WEXITSTATUS(status) != 0) {
"failed to link %s: %s exited with status %d\n",
goto done;
}
} else {
}
done:
return (ret);
}