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.
23269245Smarkj * Copyright 2013 Voxer Inc. All rights reserved.
24178479Sjb * Use is subject to license terms.
25178479Sjb */
26178479Sjb
27178479Sjb#include <unistd.h>
28178479Sjb#include <fcntl.h>
29178479Sjb#include <dlfcn.h>
30178479Sjb#include <link.h>
31178479Sjb#include <sys/dtrace.h>
32178479Sjb
33178479Sjb#include <stdarg.h>
34178479Sjb#include <stdio.h>
35178479Sjb#include <stdlib.h>
36178479Sjb#include <string.h>
37178479Sjb#include <errno.h>
38211554Srpaulo#include <libelf.h>
39211554Srpaulo#include <gelf.h>
40178479Sjb
41178479Sjb/*
42178479Sjb * In Solaris 10 GA, the only mechanism for communicating helper information
43178479Sjb * is through the DTrace helper pseudo-device node in /devices; there is
44178479Sjb * no /dev link. Because of this, USDT providers and helper actions don't
45178479Sjb * work inside of non-global zones. This issue was addressed by adding
46178479Sjb * the /dev and having this initialization code use that /dev link. If the
47178479Sjb * /dev link doesn't exist it falls back to looking for the /devices node
48178479Sjb * as this code may be embedded in a binary which runs on Solaris 10 GA.
49178479Sjb *
50178479Sjb * Users may set the following environment variable to affect the way
51178479Sjb * helper initialization takes place:
52178479Sjb *
53178479Sjb *	DTRACE_DOF_INIT_DEBUG		enable debugging output
54178479Sjb *	DTRACE_DOF_INIT_DISABLE		disable helper loading
55178479Sjb *	DTRACE_DOF_INIT_DEVNAME		set the path to the helper node
56178479Sjb */
57178479Sjb
58178572Sjbstatic const char *devnamep = "/dev/dtrace/helper";
59211554Srpaulo#if defined(sun)
60178479Sjbstatic const char *olddevname = "/devices/pseudo/dtrace@0:helper";
61211554Srpaulo#endif
62178479Sjb
63178479Sjbstatic const char *modname;	/* Name of this load object */
64178479Sjbstatic int gen;			/* DOF helper generation */
65211554Srpaulo#if defined(sun)
66178479Sjbextern dof_hdr_t __SUNW_dof;	/* DOF defined in the .SUNW_dof section */
67211554Srpaulo#endif
68212462Srpaulostatic boolean_t dof_init_debug = B_FALSE;	/* From DTRACE_DOF_INIT_DEBUG */
69178479Sjb
70178479Sjbstatic void
71178479Sjbdprintf(int debug, const char *fmt, ...)
72178479Sjb{
73178479Sjb	va_list ap;
74178479Sjb
75210767Srpaulo	if (debug && !dof_init_debug)
76178479Sjb		return;
77178479Sjb
78178479Sjb	va_start(ap, fmt);
79178479Sjb
80178479Sjb	if (modname == NULL)
81178479Sjb		(void) fprintf(stderr, "dtrace DOF: ");
82178479Sjb	else
83178479Sjb		(void) fprintf(stderr, "dtrace DOF %s: ", modname);
84178479Sjb
85178479Sjb	(void) vfprintf(stderr, fmt, ap);
86178479Sjb
87178479Sjb	if (fmt[strlen(fmt) - 1] != '\n')
88178479Sjb		(void) fprintf(stderr, ": %s\n", strerror(errno));
89178479Sjb
90178479Sjb	va_end(ap);
91178479Sjb}
92178479Sjb
93211554Srpaulo#if !defined(sun)
94211554Srpaulostatic void
95211554Srpaulofixsymbol(Elf *e, Elf_Data *data, size_t idx, int nprobes, char *buf,
96211554Srpaulo    dof_sec_t *sec, int *fixedprobes, char *dofstrtab)
97211554Srpaulo{
98211554Srpaulo	GElf_Sym sym;
99211554Srpaulo	char *s;
100211554Srpaulo	unsigned char *funcname;
101211554Srpaulo	dof_probe_t *prb;
102211554Srpaulo	int j = 0;
103211554Srpaulo	int ndx;
104211554Srpaulo
105211554Srpaulo	while (gelf_getsym(data, j++, &sym) != NULL) {
106228547Sdim		prb = (dof_probe_t *)(void *)(buf + sec->dofs_offset);
107211554Srpaulo
108211554Srpaulo		for (ndx = nprobes; ndx; ndx--, prb += 1) {
109211554Srpaulo			funcname = dofstrtab + prb->dofpr_func;
110211554Srpaulo			s = elf_strptr(e, idx, sym.st_name);
111211554Srpaulo			if (strcmp(s, funcname) == 0) {
112211554Srpaulo				dprintf(1, "fixing %s() symbol\n", s);
113211554Srpaulo				prb->dofpr_addr = sym.st_value;
114211554Srpaulo				(*fixedprobes)++;
115211554Srpaulo			}
116211554Srpaulo		}
117211554Srpaulo		if (*fixedprobes == nprobes)
118211554Srpaulo			break;
119211554Srpaulo	}
120211554Srpaulo}
121211554Srpaulo#endif
122211554Srpaulo
123178572Sjb#if defined(sun)
124178479Sjb#pragma init(dtrace_dof_init)
125178572Sjb#else
126178572Sjbstatic void dtrace_dof_init(void) __attribute__ ((constructor));
127178572Sjb#endif
128178572Sjb
129178479Sjbstatic void
130178479Sjbdtrace_dof_init(void)
131178479Sjb{
132211554Srpaulo#if defined(sun)
133178479Sjb	dof_hdr_t *dof = &__SUNW_dof;
134211554Srpaulo#else
135211554Srpaulo	dof_hdr_t *dof = NULL;
136211554Srpaulo#endif
137178479Sjb#ifdef _LP64
138178479Sjb	Elf64_Ehdr *elf;
139178479Sjb#else
140178479Sjb	Elf32_Ehdr *elf;
141178479Sjb#endif
142178479Sjb	dof_helper_t dh;
143211554Srpaulo	Link_map *lmp;
144178572Sjb#if defined(sun)
145178479Sjb	Lmid_t lmid;
146178572Sjb#else
147178572Sjb	u_long lmid = 0;
148269245Smarkj	dof_sec_t *sec, *secstart, *dofstrtab, *dofprobes;
149269245Smarkj	dof_provider_t *dofprovider;
150211554Srpaulo	size_t i;
151178572Sjb#endif
152178479Sjb	int fd;
153178479Sjb	const char *p;
154211554Srpaulo#if !defined(sun)
155211554Srpaulo	Elf *e;
156211554Srpaulo	Elf_Scn *scn = NULL;
157269245Smarkj	Elf_Data *symtabdata = NULL, *dynsymdata = NULL, *dofdata = NULL;
158269245Smarkj	dof_hdr_t *dof_next = NULL;
159211554Srpaulo	GElf_Shdr shdr;
160211554Srpaulo	int efd, nprobes;
161211554Srpaulo	char *s;
162269245Smarkj	char *dofstrtabraw;
163211554Srpaulo	size_t shstridx, symtabidx = 0, dynsymidx = 0;
164211554Srpaulo	unsigned char *buf;
165269245Smarkj	int fixedprobes;
166211554Srpaulo#endif
167178479Sjb
168178479Sjb	if (getenv("DTRACE_DOF_INIT_DISABLE") != NULL)
169178479Sjb		return;
170178479Sjb
171210767Srpaulo	if (getenv("DTRACE_DOF_INIT_DEBUG") != NULL)
172210767Srpaulo		dof_init_debug = B_TRUE;
173210767Srpaulo
174178479Sjb	if (dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &lmp) == -1 || lmp == NULL) {
175178479Sjb		dprintf(1, "couldn't discover module name or address\n");
176178479Sjb		return;
177178479Sjb	}
178178479Sjb
179178572Sjb#if defined(sun)
180178479Sjb	if (dlinfo(RTLD_SELF, RTLD_DI_LMID, &lmid) == -1) {
181178479Sjb		dprintf(1, "couldn't discover link map ID\n");
182178479Sjb		return;
183178479Sjb	}
184178572Sjb#endif
185178479Sjb
186211554Srpaulo
187178479Sjb	if ((modname = strrchr(lmp->l_name, '/')) == NULL)
188178479Sjb		modname = lmp->l_name;
189178479Sjb	else
190178479Sjb		modname++;
191211554Srpaulo#if !defined(sun)
192211554Srpaulo	elf_version(EV_CURRENT);
193211554Srpaulo	if ((efd = open(lmp->l_name, O_RDONLY, 0)) < 0) {
194211554Srpaulo		dprintf(1, "couldn't open file for reading\n");
195211554Srpaulo		return;
196211554Srpaulo	}
197211554Srpaulo	if ((e = elf_begin(efd, ELF_C_READ, NULL)) == NULL) {
198211554Srpaulo		dprintf(1, "elf_begin failed\n");
199211554Srpaulo		close(efd);
200211554Srpaulo		return;
201211554Srpaulo	}
202211554Srpaulo	elf_getshdrstrndx(e, &shstridx);
203211554Srpaulo	dof = NULL;
204211554Srpaulo	while ((scn = elf_nextscn(e, scn)) != NULL) {
205211554Srpaulo		gelf_getshdr(scn, &shdr);
206211554Srpaulo		if (shdr.sh_type == SHT_SYMTAB) {
207211554Srpaulo			symtabidx = shdr.sh_link;
208211554Srpaulo			symtabdata = elf_getdata(scn, NULL);
209211554Srpaulo		} else if (shdr.sh_type == SHT_DYNSYM) {
210211554Srpaulo			dynsymidx = shdr.sh_link;
211211554Srpaulo			dynsymdata = elf_getdata(scn, NULL);
212211554Srpaulo		} else if (shdr.sh_type == SHT_PROGBITS) {
213211554Srpaulo			s = elf_strptr(e, shstridx, shdr.sh_name);
214211554Srpaulo			if  (s && strcmp(s, ".SUNW_dof") == 0) {
215269245Smarkj				dofdata = elf_getdata(scn, NULL);
216269245Smarkj				dof = dofdata->d_buf;
217211554Srpaulo			}
218211554Srpaulo		}
219211554Srpaulo	}
220211554Srpaulo	if (dof == NULL) {
221211554Srpaulo		dprintf(1, "SUNW_dof section not found\n");
222211554Srpaulo		elf_end(e);
223211554Srpaulo		close(efd);
224211554Srpaulo		return;
225211554Srpaulo	}
226269245Smarkj
227269245Smarkj	while ((char *) dof < (char *) dofdata->d_buf + dofdata->d_size) {
228269245Smarkj		fixedprobes = 0;
229269245Smarkj		dof_next = (void *) ((char *) dof + dof->dofh_filesz);
230211554Srpaulo#endif
231178479Sjb
232178479Sjb	if (dof->dofh_ident[DOF_ID_MAG0] != DOF_MAG_MAG0 ||
233178479Sjb	    dof->dofh_ident[DOF_ID_MAG1] != DOF_MAG_MAG1 ||
234178479Sjb	    dof->dofh_ident[DOF_ID_MAG2] != DOF_MAG_MAG2 ||
235178479Sjb	    dof->dofh_ident[DOF_ID_MAG3] != DOF_MAG_MAG3) {
236178479Sjb		dprintf(0, ".SUNW_dof section corrupt\n");
237178479Sjb		return;
238178479Sjb	}
239178479Sjb
240178479Sjb	elf = (void *)lmp->l_addr;
241178479Sjb
242178479Sjb	dh.dofhp_dof = (uintptr_t)dof;
243178572Sjb	dh.dofhp_addr = elf->e_type == ET_DYN ? (uintptr_t) lmp->l_addr : 0;
244178479Sjb
245178479Sjb	if (lmid == 0) {
246178479Sjb		(void) snprintf(dh.dofhp_mod, sizeof (dh.dofhp_mod),
247178479Sjb		    "%s", modname);
248178479Sjb	} else {
249178479Sjb		(void) snprintf(dh.dofhp_mod, sizeof (dh.dofhp_mod),
250178479Sjb		    "LM%lu`%s", lmid, modname);
251178479Sjb	}
252178479Sjb
253178479Sjb	if ((p = getenv("DTRACE_DOF_INIT_DEVNAME")) != NULL)
254178572Sjb		devnamep = p;
255178479Sjb
256178572Sjb	if ((fd = open64(devnamep, O_RDWR)) < 0) {
257178572Sjb		dprintf(1, "failed to open helper device %s", devnamep);
258211554Srpaulo#if defined(sun)
259178479Sjb		/*
260178479Sjb		 * If the device path wasn't explicitly set, try again with
261178479Sjb		 * the old device path.
262178479Sjb		 */
263178479Sjb		if (p != NULL)
264178479Sjb			return;
265178479Sjb
266178572Sjb		devnamep = olddevname;
267178479Sjb
268178572Sjb		if ((fd = open64(devnamep, O_RDWR)) < 0) {
269178572Sjb			dprintf(1, "failed to open helper device %s", devnamep);
270178479Sjb			return;
271178479Sjb		}
272211554Srpaulo#else
273211554Srpaulo		return;
274211554Srpaulo#endif
275178479Sjb	}
276211554Srpaulo#if !defined(sun)
277211554Srpaulo	/*
278211554Srpaulo	 * We need to fix the base address of each probe since this wasn't
279211554Srpaulo	 * done by ld(1). (ld(1) needs to grow support for parsing the
280211554Srpaulo	 * SUNW_dof section).
281211554Srpaulo	 *
282211554Srpaulo	 * The complexity of this is not that great. The first for loop
283211554Srpaulo	 * iterates over the sections inside the DOF file. There are usually
284211554Srpaulo	 * 10 sections here. We asume the STRTAB section comes first and the
285211554Srpaulo	 * PROBES section comes after. Since we are only interested in fixing
286211554Srpaulo	 * data inside the PROBES section we quit the for loop after processing
287211554Srpaulo	 * the PROBES section. It's usually the case that the first section
288211554Srpaulo	 * is the STRTAB section and the second section is the PROBES section,
289211554Srpaulo	 * so this for loop is not meaningful when doing complexity analysis.
290211554Srpaulo	 *
291211554Srpaulo	 * After finding the probes section, we iterate over the symbols
292211554Srpaulo	 * in the symtab section. When we find a symbol name that matches
293211554Srpaulo	 * the probe function name, we fix it. If we have fixed all the
294211554Srpaulo	 * probes, we exit all the loops and we are done.
295211554Srpaulo	 * The number of probes is given by the variable 'nprobes' and this
296211554Srpaulo	 * depends entirely on the user, but some optimizations were done.
297211554Srpaulo	 *
298211554Srpaulo	 * We are assuming the number of probes is less than the number of
299211554Srpaulo	 * symbols (libc can have 4k symbols, for example).
300211554Srpaulo	 */
301269245Smarkj	secstart = sec = (dof_sec_t *)(dof + 1);
302211554Srpaulo	buf = (char *)dof;
303211554Srpaulo	for (i = 0; i < dof->dofh_secnum; i++, sec++) {
304269245Smarkj		if (sec->dofs_type != DOF_SECT_PROVIDER)
305269245Smarkj			continue;
306269245Smarkj
307269245Smarkj		dofprovider = (void *) (buf + sec->dofs_offset);
308269245Smarkj		dofstrtab = secstart + dofprovider->dofpv_strtab;
309269245Smarkj		dofprobes = secstart + dofprovider->dofpv_probes;
310269245Smarkj
311269245Smarkj		if (dofstrtab->dofs_type != DOF_SECT_STRTAB) {
312269245Smarkj			fprintf(stderr, "WARNING: expected STRTAB section, but got %d\n",
313269245Smarkj					dofstrtab->dofs_type);
314211554Srpaulo			break;
315269245Smarkj		}
316269245Smarkj		if (dofprobes->dofs_type != DOF_SECT_PROBES) {
317269245Smarkj			fprintf(stderr, "WARNING: expected PROBES section, but got %d\n",
318269245Smarkj			    dofprobes->dofs_type);
319269245Smarkj			break;
320269245Smarkj		}
321269245Smarkj
322269245Smarkj		dprintf(1, "found provider %p\n", dofprovider);
323269245Smarkj		dofstrtabraw = (char *)(buf + dofstrtab->dofs_offset);
324269245Smarkj		nprobes = dofprobes->dofs_size / dofprobes->dofs_entsize;
325269245Smarkj		fixsymbol(e, symtabdata, symtabidx, nprobes, buf, dofprobes, &fixedprobes,
326269245Smarkj				dofstrtabraw);
327269245Smarkj		if (fixedprobes != nprobes) {
328269245Smarkj			/*
329269245Smarkj			 * If we haven't fixed all the probes using the
330269245Smarkj			 * symtab section, look inside the dynsym
331269245Smarkj			 * section.
332269245Smarkj			 */
333269245Smarkj			fixsymbol(e, dynsymdata, dynsymidx, nprobes, buf, dofprobes,
334269245Smarkj					&fixedprobes, dofstrtabraw);
335269245Smarkj		}
336269245Smarkj		if (fixedprobes != nprobes) {
337269245Smarkj			fprintf(stderr, "WARNING: number of probes "
338269245Smarkj			    "fixed does not match the number of "
339269245Smarkj			    "defined probes (%d != %d, "
340269245Smarkj			    "respectively)\n", fixedprobes, nprobes);
341269245Smarkj			fprintf(stderr, "WARNING: some probes might "
342269245Smarkj			    "not fire or your program might crash\n");
343269245Smarkj		}
344211554Srpaulo	}
345211554Srpaulo#endif
346178479Sjb	if ((gen = ioctl(fd, DTRACEHIOC_ADDDOF, &dh)) == -1)
347178479Sjb		dprintf(1, "DTrace ioctl failed for DOF at %p", dof);
348211554Srpaulo	else {
349178479Sjb		dprintf(1, "DTrace ioctl succeeded for DOF at %p\n", dof);
350211554Srpaulo#if !defined(sun)
351211554Srpaulo		gen = dh.gen;
352211554Srpaulo#endif
353211554Srpaulo	}
354178479Sjb
355178479Sjb	(void) close(fd);
356269245Smarkj
357211554Srpaulo#if !defined(sun)
358269245Smarkj		/* End of while loop */
359269245Smarkj		dof = dof_next;
360269245Smarkj	}
361269245Smarkj
362211554Srpaulo	elf_end(e);
363211554Srpaulo	(void) close(efd);
364211554Srpaulo#endif
365178479Sjb}
366178479Sjb
367178572Sjb#if defined(sun)
368178479Sjb#pragma fini(dtrace_dof_fini)
369178572Sjb#else
370178572Sjbstatic void dtrace_dof_fini(void) __attribute__ ((destructor));
371178572Sjb#endif
372178572Sjb
373178479Sjbstatic void
374178479Sjbdtrace_dof_fini(void)
375178479Sjb{
376178479Sjb	int fd;
377178479Sjb
378178572Sjb	if ((fd = open64(devnamep, O_RDWR)) < 0) {
379178572Sjb		dprintf(1, "failed to open helper device %s", devnamep);
380178479Sjb		return;
381178479Sjb	}
382178479Sjb
383211554Srpaulo	if ((gen = ioctl(fd, DTRACEHIOC_REMOVE, &gen)) == -1)
384178479Sjb		dprintf(1, "DTrace ioctl failed to remove DOF (%d)\n", gen);
385178479Sjb	else
386178479Sjb		dprintf(1, "DTrace ioctl removed DOF (%d)\n", gen);
387178479Sjb
388178479Sjb	(void) close(fd);
389178479Sjb}
390