hooks.c revision 481a1eb415945720c0e1c545bd7679fd4cdb48ff
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 if (arch_translate_address_dyn(proc, u.a, &u.a) < 0) { 339 fprintf(stderr, "Couldn't OPD-translate the address returned" 340 " by the IFUNC resolver.\n"); 341 goto done; 342 } 343 344 assert(bp->os.ret_libsym != NULL); 345 346 struct library *lib = bp->os.ret_libsym->lib; 347 assert(lib != NULL); 348 349 /* Look if we already have a symbol with this address. 350 * Otherwise create a new one. */ 351 struct library_symbol *libsym 352 = library_each_symbol(lib, NULL, libsym_at_address, &u.a); 353 if (libsym == NULL) { 354 libsym = malloc(sizeof *libsym); 355 char *name = strdup(bp->os.ret_libsym->name); 356 357 if (libsym == NULL 358 || name == NULL 359 || library_symbol_init(libsym, u.a, name, 1, 360 LS_TOPLT_NONE) < 0) { 361 free(libsym); 362 free(name); 363 goto fail; 364 } 365 366 /* Snip the .IFUNC token. */ 367 *strrchr(name, '.') = 0; 368 369 own_libsym = 1; 370 library_add_symbol(lib, libsym); 371 } 372 373 nbp = malloc(sizeof *bp); 374 if (nbp == NULL || breakpoint_init(nbp, proc, u.a, libsym) < 0) 375 goto fail; 376 377 /* If there already is a breakpoint at that address, that is 378 * suspicious, but whatever. */ 379 struct breakpoint *pre_bp = insert_breakpoint(proc, nbp); 380 if (pre_bp == NULL) 381 goto fail; 382 if (pre_bp == nbp) { 383 /* PROC took our breakpoint, so these resources are 384 * not ours anymore. */ 385 nbp = NULL; 386 own_libsym = 0; 387 } 388 389done: 390 free(nbp); 391 if (own_libsym) { 392 library_symbol_destroy(libsym); 393 free(libsym); 394 } 395 fetch_arg_done(fetch); 396} 397 398static int 399create_ifunc_ret_bp(struct breakpoint **ret, 400 struct breakpoint *bp, struct process *proc) 401{ 402 *ret = create_default_return_bp(proc); 403 if (*ret == NULL) 404 return -1; 405 static struct bp_callbacks cbs = { 406 .on_hit = ifunc_ret_hit, 407 }; 408 breakpoint_set_callbacks(*ret, &cbs); 409 410 (*ret)->os.ret_libsym = bp->libsym; 411 412 return 0; 413} 414 415int 416os_breakpoint_init(struct process *proc, struct breakpoint *bp) 417{ 418 if (bp->libsym != NULL && bp->libsym->os.is_ifunc) { 419 static struct bp_callbacks cbs = { 420 .get_return_bp = create_ifunc_ret_bp, 421 }; 422 breakpoint_set_callbacks(bp, &cbs); 423 } 424 return 0; 425} 426 427void 428os_breakpoint_destroy(struct breakpoint *bp) 429{ 430} 431 432int 433os_breakpoint_clone(struct breakpoint *retp, struct breakpoint *bp) 434{ 435 return 0; 436} 437