1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <unistd.h>
27#include <fcntl.h>
28#include <dlfcn.h>
29#include <link.h>
30#include <sys/dtrace.h>
31
32#include <stdarg.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <errno.h>
37#include <libelf.h>
38#include <gelf.h>
39
40/*
41 * In Solaris 10 GA, the only mechanism for communicating helper information
42 * is through the DTrace helper pseudo-device node in /devices; there is
43 * no /dev link. Because of this, USDT providers and helper actions don't
44 * work inside of non-global zones. This issue was addressed by adding
45 * the /dev and having this initialization code use that /dev link. If the
46 * /dev link doesn't exist it falls back to looking for the /devices node
47 * as this code may be embedded in a binary which runs on Solaris 10 GA.
48 *
49 * Users may set the following environment variable to affect the way
50 * helper initialization takes place:
51 *
52 *	DTRACE_DOF_INIT_DEBUG		enable debugging output
53 *	DTRACE_DOF_INIT_DISABLE		disable helper loading
54 *	DTRACE_DOF_INIT_DEVNAME		set the path to the helper node
55 */
56
57static const char *devnamep = "/dev/dtrace/helper";
58#if defined(sun)
59static const char *olddevname = "/devices/pseudo/dtrace@0:helper";
60#endif
61
62static const char *modname;	/* Name of this load object */
63static int gen;			/* DOF helper generation */
64#if defined(sun)
65extern dof_hdr_t __SUNW_dof;	/* DOF defined in the .SUNW_dof section */
66#endif
67static boolean_t dof_init_debug = B_FALSE;	/* From DTRACE_DOF_INIT_DEBUG */
68
69static void
70dprintf(int debug, const char *fmt, ...)
71{
72	va_list ap;
73
74	if (debug && !dof_init_debug)
75		return;
76
77	va_start(ap, fmt);
78
79	if (modname == NULL)
80		(void) fprintf(stderr, "dtrace DOF: ");
81	else
82		(void) fprintf(stderr, "dtrace DOF %s: ", modname);
83
84	(void) vfprintf(stderr, fmt, ap);
85
86	if (fmt[strlen(fmt) - 1] != '\n')
87		(void) fprintf(stderr, ": %s\n", strerror(errno));
88
89	va_end(ap);
90}
91
92#if !defined(sun)
93static void
94fixsymbol(Elf *e, Elf_Data *data, size_t idx, int nprobes, char *buf,
95    dof_sec_t *sec, int *fixedprobes, char *dofstrtab)
96{
97	GElf_Sym sym;
98	char *s;
99	unsigned char *funcname;
100	dof_probe_t *prb;
101	int j = 0;
102	int ndx;
103
104	while (gelf_getsym(data, j++, &sym) != NULL) {
105		prb = (dof_probe_t *)(void *)(buf + sec->dofs_offset);
106
107		for (ndx = nprobes; ndx; ndx--, prb += 1) {
108			funcname = dofstrtab + prb->dofpr_func;
109			s = elf_strptr(e, idx, sym.st_name);
110			if (strcmp(s, funcname) == 0) {
111				dprintf(1, "fixing %s() symbol\n", s);
112				prb->dofpr_addr = sym.st_value;
113				(*fixedprobes)++;
114			}
115		}
116		if (*fixedprobes == nprobes)
117			break;
118	}
119}
120#endif
121
122#if defined(sun)
123#pragma init(dtrace_dof_init)
124#else
125static void dtrace_dof_init(void) __attribute__ ((constructor));
126#endif
127
128static void
129dtrace_dof_init(void)
130{
131#if defined(sun)
132	dof_hdr_t *dof = &__SUNW_dof;
133#else
134	dof_hdr_t *dof = NULL;
135#endif
136#ifdef _LP64
137	Elf64_Ehdr *elf;
138#else
139	Elf32_Ehdr *elf;
140#endif
141	dof_helper_t dh;
142	Link_map *lmp;
143#if defined(sun)
144	Lmid_t lmid;
145#else
146	u_long lmid = 0;
147	dof_sec_t *sec;
148	size_t i;
149#endif
150	int fd;
151	const char *p;
152#if !defined(sun)
153	Elf *e;
154	Elf_Scn *scn = NULL;
155	Elf_Data *symtabdata = NULL, *dynsymdata = NULL;
156	GElf_Shdr shdr;
157	int efd, nprobes;
158	char *s;
159	size_t shstridx, symtabidx = 0, dynsymidx = 0;
160	unsigned char *dofstrtab = NULL;
161	unsigned char *buf;
162	int fixedprobes = 0;
163#endif
164
165	if (getenv("DTRACE_DOF_INIT_DISABLE") != NULL)
166		return;
167
168	if (getenv("DTRACE_DOF_INIT_DEBUG") != NULL)
169		dof_init_debug = B_TRUE;
170
171	if (dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &lmp) == -1 || lmp == NULL) {
172		dprintf(1, "couldn't discover module name or address\n");
173		return;
174	}
175
176#if defined(sun)
177	if (dlinfo(RTLD_SELF, RTLD_DI_LMID, &lmid) == -1) {
178		dprintf(1, "couldn't discover link map ID\n");
179		return;
180	}
181#endif
182
183
184	if ((modname = strrchr(lmp->l_name, '/')) == NULL)
185		modname = lmp->l_name;
186	else
187		modname++;
188#if !defined(sun)
189	elf_version(EV_CURRENT);
190	if ((efd = open(lmp->l_name, O_RDONLY, 0)) < 0) {
191		dprintf(1, "couldn't open file for reading\n");
192		return;
193	}
194	if ((e = elf_begin(efd, ELF_C_READ, NULL)) == NULL) {
195		dprintf(1, "elf_begin failed\n");
196		close(efd);
197		return;
198	}
199	elf_getshdrstrndx(e, &shstridx);
200	dof = NULL;
201	while ((scn = elf_nextscn(e, scn)) != NULL) {
202		gelf_getshdr(scn, &shdr);
203		if (shdr.sh_type == SHT_SYMTAB) {
204			symtabidx = shdr.sh_link;
205			symtabdata = elf_getdata(scn, NULL);
206		} else if (shdr.sh_type == SHT_DYNSYM) {
207			dynsymidx = shdr.sh_link;
208			dynsymdata = elf_getdata(scn, NULL);
209		} else if (shdr.sh_type == SHT_PROGBITS) {
210			s = elf_strptr(e, shstridx, shdr.sh_name);
211			if  (s && strcmp(s, ".SUNW_dof") == 0) {
212				dof = elf_getdata(scn, NULL)->d_buf;
213			}
214		}
215	}
216	if (dof == NULL) {
217		dprintf(1, "SUNW_dof section not found\n");
218		elf_end(e);
219		close(efd);
220		return;
221	}
222#endif
223
224	if (dof->dofh_ident[DOF_ID_MAG0] != DOF_MAG_MAG0 ||
225	    dof->dofh_ident[DOF_ID_MAG1] != DOF_MAG_MAG1 ||
226	    dof->dofh_ident[DOF_ID_MAG2] != DOF_MAG_MAG2 ||
227	    dof->dofh_ident[DOF_ID_MAG3] != DOF_MAG_MAG3) {
228		dprintf(0, ".SUNW_dof section corrupt\n");
229		return;
230	}
231
232	elf = (void *)lmp->l_addr;
233
234	dh.dofhp_dof = (uintptr_t)dof;
235	dh.dofhp_addr = elf->e_type == ET_DYN ? (uintptr_t) lmp->l_addr : 0;
236
237	if (lmid == 0) {
238		(void) snprintf(dh.dofhp_mod, sizeof (dh.dofhp_mod),
239		    "%s", modname);
240	} else {
241		(void) snprintf(dh.dofhp_mod, sizeof (dh.dofhp_mod),
242		    "LM%lu`%s", lmid, modname);
243	}
244
245	if ((p = getenv("DTRACE_DOF_INIT_DEVNAME")) != NULL)
246		devnamep = p;
247
248	if ((fd = open64(devnamep, O_RDWR)) < 0) {
249		dprintf(1, "failed to open helper device %s", devnamep);
250#if defined(sun)
251		/*
252		 * If the device path wasn't explicitly set, try again with
253		 * the old device path.
254		 */
255		if (p != NULL)
256			return;
257
258		devnamep = olddevname;
259
260		if ((fd = open64(devnamep, O_RDWR)) < 0) {
261			dprintf(1, "failed to open helper device %s", devnamep);
262			return;
263		}
264#else
265		return;
266#endif
267	}
268#if !defined(sun)
269	/*
270	 * We need to fix the base address of each probe since this wasn't
271	 * done by ld(1). (ld(1) needs to grow support for parsing the
272	 * SUNW_dof section).
273	 *
274	 * The complexity of this is not that great. The first for loop
275	 * iterates over the sections inside the DOF file. There are usually
276	 * 10 sections here. We asume the STRTAB section comes first and the
277	 * PROBES section comes after. Since we are only interested in fixing
278	 * data inside the PROBES section we quit the for loop after processing
279	 * the PROBES section. It's usually the case that the first section
280	 * is the STRTAB section and the second section is the PROBES section,
281	 * so this for loop is not meaningful when doing complexity analysis.
282	 *
283	 * After finding the probes section, we iterate over the symbols
284	 * in the symtab section. When we find a symbol name that matches
285	 * the probe function name, we fix it. If we have fixed all the
286	 * probes, we exit all the loops and we are done.
287	 * The number of probes is given by the variable 'nprobes' and this
288	 * depends entirely on the user, but some optimizations were done.
289	 *
290	 * We are assuming the number of probes is less than the number of
291	 * symbols (libc can have 4k symbols, for example).
292	 */
293	sec = (dof_sec_t *)(dof + 1);
294	buf = (char *)dof;
295	for (i = 0; i < dof->dofh_secnum; i++, sec++) {
296		if (sec->dofs_type == DOF_SECT_STRTAB)
297			dofstrtab = (unsigned char *)(buf + sec->dofs_offset);
298		else if (sec->dofs_type == DOF_SECT_PROBES && dofstrtab)
299			break;
300
301	}
302	nprobes = sec->dofs_size / sec->dofs_entsize;
303	fixsymbol(e, symtabdata, symtabidx, nprobes, buf, sec, &fixedprobes,
304	    dofstrtab);
305	if (fixedprobes != nprobes) {
306		/*
307		 * If we haven't fixed all the probes using the
308		 * symtab section, look inside the dynsym
309		 * section.
310		 */
311		fixsymbol(e, dynsymdata, dynsymidx, nprobes, buf, sec,
312		    &fixedprobes, dofstrtab);
313	}
314	if (fixedprobes != nprobes) {
315		fprintf(stderr, "WARNING: number of probes "
316		    "fixed does not match the number of "
317		    "defined probes (%d != %d, "
318		    "respectively)\n", fixedprobes, nprobes);
319		fprintf(stderr, "WARNING: some probes might "
320		    "not fire or your program might crash\n");
321	}
322#endif
323	if ((gen = ioctl(fd, DTRACEHIOC_ADDDOF, &dh)) == -1)
324		dprintf(1, "DTrace ioctl failed for DOF at %p", dof);
325	else {
326		dprintf(1, "DTrace ioctl succeeded for DOF at %p\n", dof);
327#if !defined(sun)
328		gen = dh.gen;
329#endif
330	}
331
332	(void) close(fd);
333#if !defined(sun)
334	elf_end(e);
335	(void) close(efd);
336#endif
337}
338
339#if defined(sun)
340#pragma fini(dtrace_dof_fini)
341#else
342static void dtrace_dof_fini(void) __attribute__ ((destructor));
343#endif
344
345static void
346dtrace_dof_fini(void)
347{
348	int fd;
349
350	if ((fd = open64(devnamep, O_RDWR)) < 0) {
351		dprintf(1, "failed to open helper device %s", devnamep);
352		return;
353	}
354
355	if ((gen = ioctl(fd, DTRACEHIOC_REMOVE, &gen)) == -1)
356		dprintf(1, "DTrace ioctl failed to remove DOF (%d)\n", gen);
357	else
358		dprintf(1, "DTrace ioctl removed DOF (%d)\n", gen);
359
360	(void) close(fd);
361}
362