hooks.c revision b3d6180e6d1cfc188a44b60dda6a7ac4faf0fdce
1/*
2 * This file is part of ltrace.
3 * Copyright (C) 2012, 2013 Petr Machata
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18 * 02110-1301 USA
19 */
20
21#define _POSIX_C_SOURCE 200809L
22#include <sys/types.h>
23#include <alloca.h>
24#include <errno.h>
25#include <pwd.h>
26#include <stdbool.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include "backend.h"
33#include "breakpoint.h"
34#include "dict.h"
35#include "fetch.h"
36#include "library.h"
37#include "options.h"
38#include "prototype.h"
39#include "sysdep.h"
40#include "type.h"
41#include "value.h"
42#include "vect.h"
43
44static char *
45append(const char *str1, const char *str2)
46{
47	char *ret = malloc(strlen(str1) + strlen(str2) + 2);
48	if (ret == NULL)
49		return ret;
50	strcpy(stpcpy(ret, str1), str2);
51	return ret;
52}
53
54static void
55add_dir(struct vect *dirs, const char *str1, const char *str2)
56{
57	char *dir = append(str1, str2);
58	if (dir != NULL
59	    && VECT_PUSHBACK(dirs, &dir) < 0)
60		fprintf(stderr,
61			"Couldn't store candidate config directory %s%s: %s.\n",
62			str1, str2, strerror(errno));
63}
64
65static enum callback_status
66add_dir_component_cb(struct opt_F_t *entry, void *data)
67{
68	struct vect *dirs = data;
69	if (opt_F_get_kind(entry) == OPT_F_DIR)
70		add_dir(dirs, entry->pathname, "/ltrace");
71	return CBS_CONT;
72}
73
74static void
75destroy_opt_F_cb(struct opt_F_t *entry, void *data)
76{
77	opt_F_destroy(entry);
78}
79
80static char *g_home_dir = NULL;
81
82int
83os_get_config_dirs(int private, const char ***retp)
84{
85	/* Vector of char *.  Contains first pointers to local paths,
86	 * then NULL, then pointers to system paths, then another
87	 * NULL.  SYS_START points to the beginning of the second
88	 * part.  */
89	static struct vect dirs;
90	static ssize_t sys_start = 0;
91
92again:
93	if (sys_start != 0) {
94		if (sys_start == -1)
95			return -1;
96
97		if (retp != NULL) {
98			if (private)
99				*retp = VECT_ELEMENT(&dirs, const char *, 0);
100			else
101				*retp = VECT_ELEMENT(&dirs, const char *,
102						     (size_t)sys_start);
103		}
104
105		return 0;
106	}
107
108	VECT_INIT(&dirs, char *);
109
110	char *home = getenv("HOME");
111	if (home == NULL) {
112		struct passwd *pwd = getpwuid(getuid());
113		if (pwd != NULL)
114			home = pwd->pw_dir;
115	}
116
117	size_t home_len = home != NULL ? strlen(home) : 0;
118
119	/* The values coming from getenv and getpwuid may not be
120	 * persistent.  */
121	if (home != NULL) {
122		g_home_dir = strdup(home);
123		if (g_home_dir != NULL) {
124			home = g_home_dir;
125		} else {
126			char *tmp = alloca(home_len + 1);
127			strcpy(tmp, home);
128			home = tmp;
129		}
130	}
131
132	char *xdg_home = getenv("XDG_CONFIG_HOME");
133	if (xdg_home == NULL && home != NULL) {
134		xdg_home = alloca(home_len + sizeof "/.config");
135		sprintf(xdg_home, "%s/.config", home);
136	}
137	if (xdg_home != NULL)
138		add_dir(&dirs, xdg_home, "/ltrace");
139	if (home != NULL)
140		add_dir(&dirs, home, "/.ltrace");
141
142	char *delim = NULL;
143	if (VECT_PUSHBACK(&dirs, &delim) < 0) {
144	fail:
145		/* This can't work :(  */
146		fprintf(stderr,
147			"Couldn't initialize list of config directories: %s.\n",
148			strerror(errno));
149		VECT_DESTROY(&dirs, const char *, dict_dtor_string, NULL);
150		sys_start = -1;
151		return -1;
152	}
153	sys_start = vect_size(&dirs);
154
155	/* """preference-ordered set of base directories to search for
156	 * configuration files in addition to the $XDG_CONFIG_HOME
157	 * base directory. The directories in $XDG_CONFIG_DIRS should
158	 * be seperated with a colon ':'."""  */
159	char *xdg_sys = getenv("XDG_CONFIG_DIRS");
160	if (xdg_sys != NULL) {
161		struct vect v;
162		VECT_INIT(&v, struct opt_F_t);
163		if (parse_colon_separated_list(xdg_sys, &v) < 0
164		    || VECT_EACH(&v, struct opt_F_t, NULL,
165				 add_dir_component_cb, &dirs) != NULL)
166			fprintf(stderr,
167				"Error processing $XDG_CONFIG_DIRS '%s': %s\n",
168				xdg_sys, strerror(errno));
169		VECT_DESTROY(&v, struct opt_F_t, destroy_opt_F_cb, NULL);
170	}
171
172	/* PKGDATADIR is passed via -D when compiling.  */
173	const char *pkgdatadir = PKGDATADIR;
174	if (pkgdatadir != NULL)
175		add_dir(&dirs, pkgdatadir, "");
176
177	if (VECT_PUSHBACK(&dirs, &delim) < 0)
178		goto fail;
179
180	goto again;
181}
182
183int
184os_get_ltrace_conf_filenames(struct vect *retp)
185{
186	char *homepath = NULL;
187	char *syspath = NULL;
188
189#define FN ".ltrace.conf"
190	if (g_home_dir == NULL)
191		os_get_config_dirs(0, NULL);
192
193	if (g_home_dir != NULL) {
194		homepath = malloc(strlen(g_home_dir) + 1 + sizeof FN);
195		if (homepath == NULL
196		    || sprintf(homepath, "%s/%s", g_home_dir, FN) < 0) {
197		fail:
198			free(syspath);
199			free(homepath);
200			return -1;
201		}
202	}
203
204	/* SYSCONFDIR is passed via -D when compiling.  */
205	const char *sysconfdir = SYSCONFDIR;
206	if (sysconfdir != NULL && *sysconfdir != '\0') {
207		/* No +1, we skip the initial period.  */
208		syspath = malloc(strlen(sysconfdir) + sizeof FN);
209		if (syspath == NULL
210		    || sprintf(syspath, "%s/%s", sysconfdir, FN + 1) < 0)
211			goto fail;
212	}
213
214	if (VECT_PUSHBACK(retp, &homepath) < 0
215	    || VECT_PUSHBACK(retp, &syspath) < 0)
216		goto fail;
217
218	return 0;
219}
220
221static struct prototype *
222void_prototype(void)
223{
224	static struct prototype ret;
225	if (ret.return_info == NULL) {
226		prototype_init(&ret);
227		ret.return_info = type_get_voidptr();
228		ret.own_return_info = 0;
229	}
230	return &ret;
231}
232
233int
234os_library_symbol_init(struct library_symbol *libsym)
235{
236	libsym->os = (struct os_library_symbol_data){};
237	return 0;
238}
239
240void
241os_library_symbol_destroy(struct library_symbol *libsym)
242{
243}
244
245int
246os_library_symbol_clone(struct library_symbol *retp,
247			struct library_symbol *libsym)
248{
249	retp->os = libsym->os;
250	return 0;
251}
252
253enum plt_status
254os_elf_add_func_entry(struct process *proc, struct ltelf *lte,
255		      const GElf_Sym *sym,
256		      arch_addr_t addr, const char *name,
257		      struct library_symbol **ret)
258{
259	if (GELF_ST_TYPE(sym->st_info) == STT_FUNC)
260		return PLT_DEFAULT;
261
262	bool ifunc = false;
263#ifdef STT_GNU_IFUNC
264	ifunc = GELF_ST_TYPE(sym->st_info) == STT_GNU_IFUNC;
265#endif
266
267	if (ifunc) {
268#define S ".IFUNC"
269		char *tmp_name = malloc(strlen(name) + sizeof S);
270		struct library_symbol *tmp = malloc(sizeof *tmp);
271		if (tmp_name == NULL || tmp == NULL) {
272		fail:
273			free(tmp_name);
274			free(tmp);
275			return PLT_FAIL;
276		}
277		sprintf(tmp_name, "%s%s", name, S);
278#undef S
279
280		if (library_symbol_init(tmp, addr, tmp_name, 1,
281					LS_TOPLT_NONE) < 0)
282			goto fail;
283		tmp->proto = void_prototype();
284		tmp->os.is_ifunc = 1;
285
286		*ret = tmp;
287		return PLT_OK;
288	}
289
290	*ret = NULL;
291	return PLT_OK;
292}
293
294static enum callback_status
295libsym_at_address(struct library_symbol *libsym, void *addrp)
296{
297	arch_addr_t addr = *(arch_addr_t *)addrp;
298	return CBS_STOP_IF(addr == libsym->enter_addr);
299}
300
301static void
302ifunc_ret_hit(struct breakpoint *bp, struct process *proc)
303{
304	struct fetch_context *fetch = fetch_arg_init(LT_TOF_FUNCTION, proc,
305						     type_get_voidptr());
306	if (fetch == NULL)
307		return;
308
309	struct breakpoint *nbp = NULL;
310	int own_libsym = 0;
311
312	struct value value;
313	value_init(&value, proc, NULL, type_get_voidptr(), 0);
314	size_t sz = value_size(&value, NULL);
315	union {
316		uint64_t u64;
317		uint32_t u32;
318		arch_addr_t a;
319	} u;
320
321	if (fetch_retval(fetch, LT_TOF_FUNCTIONR, proc,
322			 value.type, &value) < 0
323	    || sz > 8 /* Captures failure as well.  */
324	    || value_extract_buf(&value, (void *) &u, NULL) < 0) {
325	fail:
326		fprintf(stderr,
327			"Couldn't trace the function "
328			"indicated by IFUNC resolver.\n");
329		goto done;
330	}
331
332	assert(sz == 4 || sz == 8);
333	/* XXX double casts below:  */
334	if (sz == 4)
335		u.a = (arch_addr_t)(uintptr_t)u.u32;
336	else
337		u.a = (arch_addr_t)(uintptr_t)u.u64;
338
339	assert(bp->os.ret_libsym != NULL);
340
341	struct library *lib = bp->os.ret_libsym->lib;
342	assert(lib != NULL);
343
344	/* Look if we already have a symbol with this address.
345	 * Otherwise create a new one.  */
346	struct library_symbol *libsym
347		= library_each_symbol(lib, NULL, libsym_at_address, &u.a);
348	if (libsym == NULL) {
349		libsym = malloc(sizeof *libsym);
350		char *name = strdup(bp->os.ret_libsym->name);
351
352		if (libsym == NULL
353		    || name == NULL
354		    || library_symbol_init(libsym, u.a, name, 1,
355					   LS_TOPLT_NONE) < 0) {
356			free(libsym);
357			free(name);
358			goto fail;
359		}
360
361		/* Snip the .IFUNC token.  */
362		*strrchr(name, '.') = 0;
363
364		own_libsym = 1;
365		library_add_symbol(lib, libsym);
366	}
367
368	nbp = malloc(sizeof *bp);
369	if (nbp == NULL || breakpoint_init(nbp, proc, u.a, libsym) < 0)
370		goto fail;
371
372	/* If there already is a breakpoint at that address, that is
373	 * suspicious, but whatever.  */
374	struct breakpoint *pre_bp = insert_breakpoint(proc, nbp);
375	if (pre_bp == NULL)
376		goto fail;
377	if (pre_bp == nbp) {
378		/* PROC took our breakpoint, so these resources are
379		 * not ours anymore.  */
380		nbp = NULL;
381		own_libsym = 0;
382	}
383
384done:
385	free(nbp);
386	if (own_libsym) {
387		library_symbol_destroy(libsym);
388		free(libsym);
389	}
390	fetch_arg_done(fetch);
391}
392
393static int
394create_ifunc_ret_bp(struct breakpoint **ret,
395		    struct breakpoint *bp, struct process *proc)
396{
397	*ret = create_default_return_bp(proc);
398	if (*ret == NULL)
399		return -1;
400	static struct bp_callbacks cbs = {
401		.on_hit = ifunc_ret_hit,
402	};
403	breakpoint_set_callbacks(*ret, &cbs);
404
405	(*ret)->os.ret_libsym = bp->libsym;
406
407	return 0;
408}
409
410int
411os_breakpoint_init(struct process *proc, struct breakpoint *bp)
412{
413	if (bp->libsym != NULL && bp->libsym->os.is_ifunc) {
414		static struct bp_callbacks cbs = {
415			.get_return_bp = create_ifunc_ret_bp,
416		};
417		breakpoint_set_callbacks(bp, &cbs);
418	}
419	return 0;
420}
421
422void
423os_breakpoint_destroy(struct breakpoint *bp)
424{
425}
426
427int
428os_breakpoint_clone(struct breakpoint *retp, struct breakpoint *bp)
429{
430	return 0;
431}
432