1178479Sjb/*
2178479Sjb * CDDL HEADER START
3178479Sjb *
4178479Sjb * The contents of this file are subject to the terms of the
5210767Srpaulo * Common Development and Distribution License (the "License").
6210767Srpaulo * You may not use this file except in compliance with the License.
7178479Sjb *
8178479Sjb * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9178479Sjb * or http://www.opensolaris.org/os/licensing.
10178479Sjb * See the License for the specific language governing permissions
11178479Sjb * and limitations under the License.
12178479Sjb *
13178479Sjb * When distributing Covered Code, include this CDDL HEADER in each
14178479Sjb * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15178479Sjb * If applicable, add the following below this CDDL HEADER, with the
16178479Sjb * fields enclosed by brackets "[]" replaced with your own identifying
17178479Sjb * information: Portions Copyright [yyyy] [name of copyright owner]
18178479Sjb *
19178479Sjb * CDDL HEADER END
20178479Sjb */
21178479Sjb/*
22210767Srpaulo * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23178479Sjb * Use is subject to license terms.
24178479Sjb */
25178479Sjb
26178479Sjb#include <unistd.h>
27178479Sjb#include <fcntl.h>
28178479Sjb#include <dlfcn.h>
29178479Sjb#include <link.h>
30178479Sjb#include <sys/dtrace.h>
31178479Sjb
32178479Sjb#include <stdarg.h>
33178479Sjb#include <stdio.h>
34178479Sjb#include <stdlib.h>
35178479Sjb#include <string.h>
36178479Sjb#include <errno.h>
37211554Srpaulo#include <libelf.h>
38211554Srpaulo#include <gelf.h>
39178479Sjb
40178479Sjb/*
41178479Sjb * In Solaris 10 GA, the only mechanism for communicating helper information
42178479Sjb * is through the DTrace helper pseudo-device node in /devices; there is
43178479Sjb * no /dev link. Because of this, USDT providers and helper actions don't
44178479Sjb * work inside of non-global zones. This issue was addressed by adding
45178479Sjb * the /dev and having this initialization code use that /dev link. If the
46178479Sjb * /dev link doesn't exist it falls back to looking for the /devices node
47178479Sjb * as this code may be embedded in a binary which runs on Solaris 10 GA.
48178479Sjb *
49178479Sjb * Users may set the following environment variable to affect the way
50178479Sjb * helper initialization takes place:
51178479Sjb *
52178479Sjb *	DTRACE_DOF_INIT_DEBUG		enable debugging output
53178479Sjb *	DTRACE_DOF_INIT_DISABLE		disable helper loading
54178479Sjb *	DTRACE_DOF_INIT_DEVNAME		set the path to the helper node
55178479Sjb */
56178479Sjb
57178572Sjbstatic const char *devnamep = "/dev/dtrace/helper";
58211554Srpaulo#if defined(sun)
59178479Sjbstatic const char *olddevname = "/devices/pseudo/dtrace@0:helper";
60211554Srpaulo#endif
61178479Sjb
62178479Sjbstatic const char *modname;	/* Name of this load object */
63178479Sjbstatic int gen;			/* DOF helper generation */
64211554Srpaulo#if defined(sun)
65178479Sjbextern dof_hdr_t __SUNW_dof;	/* DOF defined in the .SUNW_dof section */
66211554Srpaulo#endif
67212462Srpaulostatic boolean_t dof_init_debug = B_FALSE;	/* From DTRACE_DOF_INIT_DEBUG */
68178479Sjb
69178479Sjbstatic void
70178479Sjbdprintf(int debug, const char *fmt, ...)
71178479Sjb{
72178479Sjb	va_list ap;
73178479Sjb
74210767Srpaulo	if (debug && !dof_init_debug)
75178479Sjb		return;
76178479Sjb
77178479Sjb	va_start(ap, fmt);
78178479Sjb
79178479Sjb	if (modname == NULL)
80178479Sjb		(void) fprintf(stderr, "dtrace DOF: ");
81178479Sjb	else
82178479Sjb		(void) fprintf(stderr, "dtrace DOF %s: ", modname);
83178479Sjb
84178479Sjb	(void) vfprintf(stderr, fmt, ap);
85178479Sjb
86178479Sjb	if (fmt[strlen(fmt) - 1] != '\n')
87178479Sjb		(void) fprintf(stderr, ": %s\n", strerror(errno));
88178479Sjb
89178479Sjb	va_end(ap);
90178479Sjb}
91178479Sjb
92211554Srpaulo#if !defined(sun)
93211554Srpaulostatic void
94211554Srpaulofixsymbol(Elf *e, Elf_Data *data, size_t idx, int nprobes, char *buf,
95211554Srpaulo    dof_sec_t *sec, int *fixedprobes, char *dofstrtab)
96211554Srpaulo{
97211554Srpaulo	GElf_Sym sym;
98211554Srpaulo	char *s;
99211554Srpaulo	unsigned char *funcname;
100211554Srpaulo	dof_probe_t *prb;
101211554Srpaulo	int j = 0;
102211554Srpaulo	int ndx;
103211554Srpaulo
104211554Srpaulo	while (gelf_getsym(data, j++, &sym) != NULL) {
105229088Sdim		prb = (dof_probe_t *)(void *)(buf + sec->dofs_offset);
106211554Srpaulo
107211554Srpaulo		for (ndx = nprobes; ndx; ndx--, prb += 1) {
108211554Srpaulo			funcname = dofstrtab + prb->dofpr_func;
109211554Srpaulo			s = elf_strptr(e, idx, sym.st_name);
110211554Srpaulo			if (strcmp(s, funcname) == 0) {
111211554Srpaulo				dprintf(1, "fixing %s() symbol\n", s);
112211554Srpaulo				prb->dofpr_addr = sym.st_value;
113211554Srpaulo				(*fixedprobes)++;
114211554Srpaulo			}
115211554Srpaulo		}
116211554Srpaulo		if (*fixedprobes == nprobes)
117211554Srpaulo			break;
118211554Srpaulo	}
119211554Srpaulo}
120211554Srpaulo#endif
121211554Srpaulo
122178572Sjb#if defined(sun)
123178479Sjb#pragma init(dtrace_dof_init)
124178572Sjb#else
125178572Sjbstatic void dtrace_dof_init(void) __attribute__ ((constructor));
126178572Sjb#endif
127178572Sjb
128178479Sjbstatic void
129178479Sjbdtrace_dof_init(void)
130178479Sjb{
131211554Srpaulo#if defined(sun)
132178479Sjb	dof_hdr_t *dof = &__SUNW_dof;
133211554Srpaulo#else
134211554Srpaulo	dof_hdr_t *dof = NULL;
135211554Srpaulo#endif
136178479Sjb#ifdef _LP64
137178479Sjb	Elf64_Ehdr *elf;
138178479Sjb#else
139178479Sjb	Elf32_Ehdr *elf;
140178479Sjb#endif
141178479Sjb	dof_helper_t dh;
142211554Srpaulo	Link_map *lmp;
143178572Sjb#if defined(sun)
144178479Sjb	Lmid_t lmid;
145178572Sjb#else
146178572Sjb	u_long lmid = 0;
147211554Srpaulo	dof_sec_t *sec;
148211554Srpaulo	size_t i;
149178572Sjb#endif
150178479Sjb	int fd;
151178479Sjb	const char *p;
152211554Srpaulo#if !defined(sun)
153211554Srpaulo	Elf *e;
154211554Srpaulo	Elf_Scn *scn = NULL;
155211554Srpaulo	Elf_Data *symtabdata = NULL, *dynsymdata = NULL;
156211554Srpaulo	GElf_Shdr shdr;
157211554Srpaulo	int efd, nprobes;
158211554Srpaulo	char *s;
159211554Srpaulo	size_t shstridx, symtabidx = 0, dynsymidx = 0;
160211554Srpaulo	unsigned char *dofstrtab = NULL;
161211554Srpaulo	unsigned char *buf;
162211554Srpaulo	int fixedprobes = 0;
163211554Srpaulo#endif
164178479Sjb
165178479Sjb	if (getenv("DTRACE_DOF_INIT_DISABLE") != NULL)
166178479Sjb		return;
167178479Sjb
168210767Srpaulo	if (getenv("DTRACE_DOF_INIT_DEBUG") != NULL)
169210767Srpaulo		dof_init_debug = B_TRUE;
170210767Srpaulo
171178479Sjb	if (dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &lmp) == -1 || lmp == NULL) {
172178479Sjb		dprintf(1, "couldn't discover module name or address\n");
173178479Sjb		return;
174178479Sjb	}
175178479Sjb
176178572Sjb#if defined(sun)
177178479Sjb	if (dlinfo(RTLD_SELF, RTLD_DI_LMID, &lmid) == -1) {
178178479Sjb		dprintf(1, "couldn't discover link map ID\n");
179178479Sjb		return;
180178479Sjb	}
181178572Sjb#endif
182178479Sjb
183211554Srpaulo
184178479Sjb	if ((modname = strrchr(lmp->l_name, '/')) == NULL)
185178479Sjb		modname = lmp->l_name;
186178479Sjb	else
187178479Sjb		modname++;
188211554Srpaulo#if !defined(sun)
189211554Srpaulo	elf_version(EV_CURRENT);
190211554Srpaulo	if ((efd = open(lmp->l_name, O_RDONLY, 0)) < 0) {
191211554Srpaulo		dprintf(1, "couldn't open file for reading\n");
192211554Srpaulo		return;
193211554Srpaulo	}
194211554Srpaulo	if ((e = elf_begin(efd, ELF_C_READ, NULL)) == NULL) {
195211554Srpaulo		dprintf(1, "elf_begin failed\n");
196211554Srpaulo		close(efd);
197211554Srpaulo		return;
198211554Srpaulo	}
199211554Srpaulo	elf_getshdrstrndx(e, &shstridx);
200211554Srpaulo	dof = NULL;
201211554Srpaulo	while ((scn = elf_nextscn(e, scn)) != NULL) {
202211554Srpaulo		gelf_getshdr(scn, &shdr);
203211554Srpaulo		if (shdr.sh_type == SHT_SYMTAB) {
204211554Srpaulo			symtabidx = shdr.sh_link;
205211554Srpaulo			symtabdata = elf_getdata(scn, NULL);
206211554Srpaulo		} else if (shdr.sh_type == SHT_DYNSYM) {
207211554Srpaulo			dynsymidx = shdr.sh_link;
208211554Srpaulo			dynsymdata = elf_getdata(scn, NULL);
209211554Srpaulo		} else if (shdr.sh_type == SHT_PROGBITS) {
210211554Srpaulo			s = elf_strptr(e, shstridx, shdr.sh_name);
211211554Srpaulo			if  (s && strcmp(s, ".SUNW_dof") == 0) {
212211554Srpaulo				dof = elf_getdata(scn, NULL)->d_buf;
213211554Srpaulo			}
214211554Srpaulo		}
215211554Srpaulo	}
216211554Srpaulo	if (dof == NULL) {
217211554Srpaulo		dprintf(1, "SUNW_dof section not found\n");
218211554Srpaulo		elf_end(e);
219211554Srpaulo		close(efd);
220211554Srpaulo		return;
221211554Srpaulo	}
222211554Srpaulo#endif
223178479Sjb
224178479Sjb	if (dof->dofh_ident[DOF_ID_MAG0] != DOF_MAG_MAG0 ||
225178479Sjb	    dof->dofh_ident[DOF_ID_MAG1] != DOF_MAG_MAG1 ||
226178479Sjb	    dof->dofh_ident[DOF_ID_MAG2] != DOF_MAG_MAG2 ||
227178479Sjb	    dof->dofh_ident[DOF_ID_MAG3] != DOF_MAG_MAG3) {
228178479Sjb		dprintf(0, ".SUNW_dof section corrupt\n");
229178479Sjb		return;
230178479Sjb	}
231178479Sjb
232178479Sjb	elf = (void *)lmp->l_addr;
233178479Sjb
234178479Sjb	dh.dofhp_dof = (uintptr_t)dof;
235178572Sjb	dh.dofhp_addr = elf->e_type == ET_DYN ? (uintptr_t) lmp->l_addr : 0;
236178479Sjb
237178479Sjb	if (lmid == 0) {
238178479Sjb		(void) snprintf(dh.dofhp_mod, sizeof (dh.dofhp_mod),
239178479Sjb		    "%s", modname);
240178479Sjb	} else {
241178479Sjb		(void) snprintf(dh.dofhp_mod, sizeof (dh.dofhp_mod),
242178479Sjb		    "LM%lu`%s", lmid, modname);
243178479Sjb	}
244178479Sjb
245178479Sjb	if ((p = getenv("DTRACE_DOF_INIT_DEVNAME")) != NULL)
246178572Sjb		devnamep = p;
247178479Sjb
248178572Sjb	if ((fd = open64(devnamep, O_RDWR)) < 0) {
249178572Sjb		dprintf(1, "failed to open helper device %s", devnamep);
250211554Srpaulo#if defined(sun)
251178479Sjb		/*
252178479Sjb		 * If the device path wasn't explicitly set, try again with
253178479Sjb		 * the old device path.
254178479Sjb		 */
255178479Sjb		if (p != NULL)
256178479Sjb			return;
257178479Sjb
258178572Sjb		devnamep = olddevname;
259178479Sjb
260178572Sjb		if ((fd = open64(devnamep, O_RDWR)) < 0) {
261178572Sjb			dprintf(1, "failed to open helper device %s", devnamep);
262178479Sjb			return;
263178479Sjb		}
264211554Srpaulo#else
265211554Srpaulo		return;
266211554Srpaulo#endif
267178479Sjb	}
268211554Srpaulo#if !defined(sun)
269211554Srpaulo	/*
270211554Srpaulo	 * We need to fix the base address of each probe since this wasn't
271211554Srpaulo	 * done by ld(1). (ld(1) needs to grow support for parsing the
272211554Srpaulo	 * SUNW_dof section).
273211554Srpaulo	 *
274211554Srpaulo	 * The complexity of this is not that great. The first for loop
275211554Srpaulo	 * iterates over the sections inside the DOF file. There are usually
276211554Srpaulo	 * 10 sections here. We asume the STRTAB section comes first and the
277211554Srpaulo	 * PROBES section comes after. Since we are only interested in fixing
278211554Srpaulo	 * data inside the PROBES section we quit the for loop after processing
279211554Srpaulo	 * the PROBES section. It's usually the case that the first section
280211554Srpaulo	 * is the STRTAB section and the second section is the PROBES section,
281211554Srpaulo	 * so this for loop is not meaningful when doing complexity analysis.
282211554Srpaulo	 *
283211554Srpaulo	 * After finding the probes section, we iterate over the symbols
284211554Srpaulo	 * in the symtab section. When we find a symbol name that matches
285211554Srpaulo	 * the probe function name, we fix it. If we have fixed all the
286211554Srpaulo	 * probes, we exit all the loops and we are done.
287211554Srpaulo	 * The number of probes is given by the variable 'nprobes' and this
288211554Srpaulo	 * depends entirely on the user, but some optimizations were done.
289211554Srpaulo	 *
290211554Srpaulo	 * We are assuming the number of probes is less than the number of
291211554Srpaulo	 * symbols (libc can have 4k symbols, for example).
292211554Srpaulo	 */
293211554Srpaulo	sec = (dof_sec_t *)(dof + 1);
294211554Srpaulo	buf = (char *)dof;
295211554Srpaulo	for (i = 0; i < dof->dofh_secnum; i++, sec++) {
296211554Srpaulo		if (sec->dofs_type == DOF_SECT_STRTAB)
297211554Srpaulo			dofstrtab = (unsigned char *)(buf + sec->dofs_offset);
298211554Srpaulo		else if (sec->dofs_type == DOF_SECT_PROBES && dofstrtab)
299211554Srpaulo			break;
300211554Srpaulo
301211554Srpaulo	}
302211554Srpaulo	nprobes = sec->dofs_size / sec->dofs_entsize;
303211554Srpaulo	fixsymbol(e, symtabdata, symtabidx, nprobes, buf, sec, &fixedprobes,
304211554Srpaulo	    dofstrtab);
305211554Srpaulo	if (fixedprobes != nprobes) {
306211554Srpaulo		/*
307211554Srpaulo		 * If we haven't fixed all the probes using the
308211554Srpaulo		 * symtab section, look inside the dynsym
309211554Srpaulo		 * section.
310211554Srpaulo		 */
311211554Srpaulo		fixsymbol(e, dynsymdata, dynsymidx, nprobes, buf, sec,
312211554Srpaulo		    &fixedprobes, dofstrtab);
313211554Srpaulo	}
314211554Srpaulo	if (fixedprobes != nprobes) {
315211554Srpaulo		fprintf(stderr, "WARNING: number of probes "
316211554Srpaulo		    "fixed does not match the number of "
317211554Srpaulo		    "defined probes (%d != %d, "
318211554Srpaulo		    "respectively)\n", fixedprobes, nprobes);
319211554Srpaulo		fprintf(stderr, "WARNING: some probes might "
320211554Srpaulo		    "not fire or your program might crash\n");
321211554Srpaulo	}
322211554Srpaulo#endif
323178479Sjb	if ((gen = ioctl(fd, DTRACEHIOC_ADDDOF, &dh)) == -1)
324178479Sjb		dprintf(1, "DTrace ioctl failed for DOF at %p", dof);
325211554Srpaulo	else {
326178479Sjb		dprintf(1, "DTrace ioctl succeeded for DOF at %p\n", dof);
327211554Srpaulo#if !defined(sun)
328211554Srpaulo		gen = dh.gen;
329211554Srpaulo#endif
330211554Srpaulo	}
331178479Sjb
332178479Sjb	(void) close(fd);
333211554Srpaulo#if !defined(sun)
334211554Srpaulo	elf_end(e);
335211554Srpaulo	(void) close(efd);
336211554Srpaulo#endif
337178479Sjb}
338178479Sjb
339178572Sjb#if defined(sun)
340178479Sjb#pragma fini(dtrace_dof_fini)
341178572Sjb#else
342178572Sjbstatic void dtrace_dof_fini(void) __attribute__ ((destructor));
343178572Sjb#endif
344178572Sjb
345178479Sjbstatic void
346178479Sjbdtrace_dof_fini(void)
347178479Sjb{
348178479Sjb	int fd;
349178479Sjb
350178572Sjb	if ((fd = open64(devnamep, O_RDWR)) < 0) {
351178572Sjb		dprintf(1, "failed to open helper device %s", devnamep);
352178479Sjb		return;
353178479Sjb	}
354178479Sjb
355211554Srpaulo	if ((gen = ioctl(fd, DTRACEHIOC_REMOVE, &gen)) == -1)
356178479Sjb		dprintf(1, "DTrace ioctl failed to remove DOF (%d)\n", gen);
357178479Sjb	else
358178479Sjb		dprintf(1, "DTrace ioctl removed DOF (%d)\n", gen);
359178479Sjb
360178479Sjb	(void) close(fd);
361178479Sjb}
362