| 1 | /* Auxiliary vector support for GDB, the GNU debugger. |
| 2 | |
| 3 | Copyright (C) 2004-2014 Free Software Foundation, Inc. |
| 4 | |
| 5 | This file is part of GDB. |
| 6 | |
| 7 | This program is free software; you can redistribute it and/or modify |
| 8 | it under the terms of the GNU General Public License as published by |
| 9 | the Free Software Foundation; either version 3 of the License, or |
| 10 | (at your option) any later version. |
| 11 | |
| 12 | This program is distributed in the hope that it will be useful, |
| 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | GNU General Public License for more details. |
| 16 | |
| 17 | You should have received a copy of the GNU General Public License |
| 18 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| 19 | |
| 20 | #include "defs.h" |
| 21 | #include "target.h" |
| 22 | #include "gdbtypes.h" |
| 23 | #include "command.h" |
| 24 | #include "inferior.h" |
| 25 | #include "valprint.h" |
| 26 | #include "gdb_assert.h" |
| 27 | #include "gdbcore.h" |
| 28 | #include "observer.h" |
| 29 | #include "filestuff.h" |
| 30 | |
| 31 | #include "auxv.h" |
| 32 | #include "elf/common.h" |
| 33 | |
| 34 | #include <unistd.h> |
| 35 | #include <fcntl.h> |
| 36 | |
| 37 | |
| 38 | /* This function handles access via /proc/PID/auxv, which is a common |
| 39 | method for native targets. */ |
| 40 | |
| 41 | static LONGEST |
| 42 | procfs_xfer_auxv (gdb_byte *readbuf, |
| 43 | const gdb_byte *writebuf, |
| 44 | ULONGEST offset, |
| 45 | ULONGEST len) |
| 46 | { |
| 47 | char *pathname; |
| 48 | int fd; |
| 49 | LONGEST n; |
| 50 | |
| 51 | pathname = xstrprintf ("/proc/%d/auxv", ptid_get_pid (inferior_ptid)); |
| 52 | fd = gdb_open_cloexec (pathname, writebuf != NULL ? O_WRONLY : O_RDONLY, 0); |
| 53 | xfree (pathname); |
| 54 | if (fd < 0) |
| 55 | return -1; |
| 56 | |
| 57 | if (offset != (ULONGEST) 0 |
| 58 | && lseek (fd, (off_t) offset, SEEK_SET) != (off_t) offset) |
| 59 | n = -1; |
| 60 | else if (readbuf != NULL) |
| 61 | n = read (fd, readbuf, len); |
| 62 | else |
| 63 | n = write (fd, writebuf, len); |
| 64 | |
| 65 | (void) close (fd); |
| 66 | |
| 67 | return n; |
| 68 | } |
| 69 | |
| 70 | /* This function handles access via ld.so's symbol `_dl_auxv'. */ |
| 71 | |
| 72 | static LONGEST |
| 73 | ld_so_xfer_auxv (gdb_byte *readbuf, |
| 74 | const gdb_byte *writebuf, |
| 75 | ULONGEST offset, |
| 76 | ULONGEST len) |
| 77 | { |
| 78 | struct minimal_symbol *msym; |
| 79 | CORE_ADDR data_address, pointer_address; |
| 80 | struct type *ptr_type = builtin_type (target_gdbarch ())->builtin_data_ptr; |
| 81 | size_t ptr_size = TYPE_LENGTH (ptr_type); |
| 82 | size_t auxv_pair_size = 2 * ptr_size; |
| 83 | gdb_byte *ptr_buf = alloca (ptr_size); |
| 84 | LONGEST retval; |
| 85 | size_t block; |
| 86 | |
| 87 | msym = lookup_minimal_symbol ("_dl_auxv", NULL, NULL); |
| 88 | if (msym == NULL) |
| 89 | return -1; |
| 90 | |
| 91 | if (MSYMBOL_SIZE (msym) != ptr_size) |
| 92 | return -1; |
| 93 | |
| 94 | /* POINTER_ADDRESS is a location where the `_dl_auxv' variable |
| 95 | resides. DATA_ADDRESS is the inferior value present in |
| 96 | `_dl_auxv', therefore the real inferior AUXV address. */ |
| 97 | |
| 98 | pointer_address = SYMBOL_VALUE_ADDRESS (msym); |
| 99 | |
| 100 | /* The location of the _dl_auxv symbol may no longer be correct if |
| 101 | ld.so runs at a different address than the one present in the |
| 102 | file. This is very common case - for unprelinked ld.so or with a |
| 103 | PIE executable. PIE executable forces random address even for |
| 104 | libraries already being prelinked to some address. PIE |
| 105 | executables themselves are never prelinked even on prelinked |
| 106 | systems. Prelinking of a PIE executable would block their |
| 107 | purpose of randomizing load of everything including the |
| 108 | executable. |
| 109 | |
| 110 | If the memory read fails, return -1 to fallback on another |
| 111 | mechanism for retrieving the AUXV. |
| 112 | |
| 113 | In most cases of a PIE running under valgrind there is no way to |
| 114 | find out the base addresses of any of ld.so, executable or AUXV |
| 115 | as everything is randomized and /proc information is not relevant |
| 116 | for the virtual executable running under valgrind. We think that |
| 117 | we might need a valgrind extension to make it work. This is PR |
| 118 | 11440. */ |
| 119 | |
| 120 | if (target_read_memory (pointer_address, ptr_buf, ptr_size) != 0) |
| 121 | return -1; |
| 122 | |
| 123 | data_address = extract_typed_address (ptr_buf, ptr_type); |
| 124 | |
| 125 | /* Possibly still not initialized such as during an inferior |
| 126 | startup. */ |
| 127 | if (data_address == 0) |
| 128 | return -1; |
| 129 | |
| 130 | data_address += offset; |
| 131 | |
| 132 | if (writebuf != NULL) |
| 133 | { |
| 134 | if (target_write_memory (data_address, writebuf, len) == 0) |
| 135 | return len; |
| 136 | else |
| 137 | return -1; |
| 138 | } |
| 139 | |
| 140 | /* Stop if trying to read past the existing AUXV block. The final |
| 141 | AT_NULL was already returned before. */ |
| 142 | |
| 143 | if (offset >= auxv_pair_size) |
| 144 | { |
| 145 | if (target_read_memory (data_address - auxv_pair_size, ptr_buf, |
| 146 | ptr_size) != 0) |
| 147 | return -1; |
| 148 | |
| 149 | if (extract_typed_address (ptr_buf, ptr_type) == AT_NULL) |
| 150 | return 0; |
| 151 | } |
| 152 | |
| 153 | retval = 0; |
| 154 | block = 0x400; |
| 155 | gdb_assert (block % auxv_pair_size == 0); |
| 156 | |
| 157 | while (len > 0) |
| 158 | { |
| 159 | if (block > len) |
| 160 | block = len; |
| 161 | |
| 162 | /* Reading sizes smaller than AUXV_PAIR_SIZE is not supported. |
| 163 | Tails unaligned to AUXV_PAIR_SIZE will not be read during a |
| 164 | call (they should be completed during next read with |
| 165 | new/extended buffer). */ |
| 166 | |
| 167 | block &= -auxv_pair_size; |
| 168 | if (block == 0) |
| 169 | return retval; |
| 170 | |
| 171 | if (target_read_memory (data_address, readbuf, block) != 0) |
| 172 | { |
| 173 | if (block <= auxv_pair_size) |
| 174 | return retval; |
| 175 | |
| 176 | block = auxv_pair_size; |
| 177 | continue; |
| 178 | } |
| 179 | |
| 180 | data_address += block; |
| 181 | len -= block; |
| 182 | |
| 183 | /* Check terminal AT_NULL. This function is being called |
| 184 | indefinitely being extended its READBUF until it returns EOF |
| 185 | (0). */ |
| 186 | |
| 187 | while (block >= auxv_pair_size) |
| 188 | { |
| 189 | retval += auxv_pair_size; |
| 190 | |
| 191 | if (extract_typed_address (readbuf, ptr_type) == AT_NULL) |
| 192 | return retval; |
| 193 | |
| 194 | readbuf += auxv_pair_size; |
| 195 | block -= auxv_pair_size; |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | return retval; |
| 200 | } |
| 201 | |
| 202 | /* This function is called like a to_xfer_partial hook, but must be |
| 203 | called with TARGET_OBJECT_AUXV. It handles access to AUXV. */ |
| 204 | |
| 205 | LONGEST |
| 206 | memory_xfer_auxv (struct target_ops *ops, |
| 207 | enum target_object object, |
| 208 | const char *annex, |
| 209 | gdb_byte *readbuf, |
| 210 | const gdb_byte *writebuf, |
| 211 | ULONGEST offset, |
| 212 | ULONGEST len) |
| 213 | { |
| 214 | gdb_assert (object == TARGET_OBJECT_AUXV); |
| 215 | gdb_assert (readbuf || writebuf); |
| 216 | |
| 217 | /* ld_so_xfer_auxv is the only function safe for virtual |
| 218 | executables being executed by valgrind's memcheck. Using |
| 219 | ld_so_xfer_auxv during inferior startup is problematic, because |
| 220 | ld.so symbol tables have not yet been relocated. So GDB uses |
| 221 | this function only when attaching to a process. |
| 222 | */ |
| 223 | |
| 224 | if (current_inferior ()->attach_flag != 0) |
| 225 | { |
| 226 | LONGEST retval; |
| 227 | |
| 228 | retval = ld_so_xfer_auxv (readbuf, writebuf, offset, len); |
| 229 | if (retval != -1) |
| 230 | return retval; |
| 231 | } |
| 232 | |
| 233 | return procfs_xfer_auxv (readbuf, writebuf, offset, len); |
| 234 | } |
| 235 | |
| 236 | /* Read one auxv entry from *READPTR, not reading locations >= ENDPTR. |
| 237 | Return 0 if *READPTR is already at the end of the buffer. |
| 238 | Return -1 if there is insufficient buffer for a whole entry. |
| 239 | Return 1 if an entry was read into *TYPEP and *VALP. */ |
| 240 | static int |
| 241 | default_auxv_parse (struct target_ops *ops, gdb_byte **readptr, |
| 242 | gdb_byte *endptr, CORE_ADDR *typep, CORE_ADDR *valp) |
| 243 | { |
| 244 | const int sizeof_auxv_field = gdbarch_ptr_bit (target_gdbarch ()) |
| 245 | / TARGET_CHAR_BIT; |
| 246 | const enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch ()); |
| 247 | gdb_byte *ptr = *readptr; |
| 248 | |
| 249 | if (endptr == ptr) |
| 250 | return 0; |
| 251 | |
| 252 | if (endptr - ptr < sizeof_auxv_field * 2) |
| 253 | return -1; |
| 254 | |
| 255 | *typep = extract_unsigned_integer (ptr, sizeof_auxv_field, byte_order); |
| 256 | ptr += sizeof_auxv_field; |
| 257 | *valp = extract_unsigned_integer (ptr, sizeof_auxv_field, byte_order); |
| 258 | ptr += sizeof_auxv_field; |
| 259 | |
| 260 | *readptr = ptr; |
| 261 | return 1; |
| 262 | } |
| 263 | |
| 264 | /* Read one auxv entry from *READPTR, not reading locations >= ENDPTR. |
| 265 | Return 0 if *READPTR is already at the end of the buffer. |
| 266 | Return -1 if there is insufficient buffer for a whole entry. |
| 267 | Return 1 if an entry was read into *TYPEP and *VALP. */ |
| 268 | int |
| 269 | target_auxv_parse (struct target_ops *ops, gdb_byte **readptr, |
| 270 | gdb_byte *endptr, CORE_ADDR *typep, CORE_ADDR *valp) |
| 271 | { |
| 272 | struct target_ops *t; |
| 273 | |
| 274 | for (t = ops; t != NULL; t = t->beneath) |
| 275 | if (t->to_auxv_parse != NULL) |
| 276 | return t->to_auxv_parse (t, readptr, endptr, typep, valp); |
| 277 | |
| 278 | return default_auxv_parse (ops, readptr, endptr, typep, valp); |
| 279 | } |
| 280 | |
| 281 | |
| 282 | /* Per-inferior data key for auxv. */ |
| 283 | static const struct inferior_data *auxv_inferior_data; |
| 284 | |
| 285 | /* Auxiliary Vector information structure. This is used by GDB |
| 286 | for caching purposes for each inferior. This helps reduce the |
| 287 | overhead of transfering data from a remote target to the local host. */ |
| 288 | struct auxv_info |
| 289 | { |
| 290 | LONGEST length; |
| 291 | gdb_byte *data; |
| 292 | }; |
| 293 | |
| 294 | /* Handles the cleanup of the auxv cache for inferior INF. ARG is ignored. |
| 295 | Frees whatever allocated space there is to be freed and sets INF's auxv cache |
| 296 | data pointer to NULL. |
| 297 | |
| 298 | This function is called when the following events occur: inferior_appeared, |
| 299 | inferior_exit and executable_changed. */ |
| 300 | |
| 301 | static void |
| 302 | auxv_inferior_data_cleanup (struct inferior *inf, void *arg) |
| 303 | { |
| 304 | struct auxv_info *info; |
| 305 | |
| 306 | info = inferior_data (inf, auxv_inferior_data); |
| 307 | if (info != NULL) |
| 308 | { |
| 309 | xfree (info->data); |
| 310 | xfree (info); |
| 311 | set_inferior_data (inf, auxv_inferior_data, NULL); |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | /* Invalidate INF's auxv cache. */ |
| 316 | |
| 317 | static void |
| 318 | invalidate_auxv_cache_inf (struct inferior *inf) |
| 319 | { |
| 320 | auxv_inferior_data_cleanup (inf, NULL); |
| 321 | } |
| 322 | |
| 323 | /* Invalidate current inferior's auxv cache. */ |
| 324 | |
| 325 | static void |
| 326 | invalidate_auxv_cache (void) |
| 327 | { |
| 328 | invalidate_auxv_cache_inf (current_inferior ()); |
| 329 | } |
| 330 | |
| 331 | /* Fetch the auxv object from inferior INF. If auxv is cached already, |
| 332 | return a pointer to the cache. If not, fetch the auxv object from the |
| 333 | target and cache it. This function always returns a valid INFO pointer. */ |
| 334 | |
| 335 | static struct auxv_info * |
| 336 | get_auxv_inferior_data (struct target_ops *ops) |
| 337 | { |
| 338 | struct auxv_info *info; |
| 339 | struct inferior *inf = current_inferior (); |
| 340 | |
| 341 | info = inferior_data (inf, auxv_inferior_data); |
| 342 | if (info == NULL) |
| 343 | { |
| 344 | info = XCNEW (struct auxv_info); |
| 345 | info->length = target_read_alloc (ops, TARGET_OBJECT_AUXV, |
| 346 | NULL, &info->data); |
| 347 | set_inferior_data (inf, auxv_inferior_data, info); |
| 348 | } |
| 349 | |
| 350 | return info; |
| 351 | } |
| 352 | |
| 353 | /* Extract the auxiliary vector entry with a_type matching MATCH. |
| 354 | Return zero if no such entry was found, or -1 if there was |
| 355 | an error getting the information. On success, return 1 after |
| 356 | storing the entry's value field in *VALP. */ |
| 357 | int |
| 358 | target_auxv_search (struct target_ops *ops, CORE_ADDR match, CORE_ADDR *valp) |
| 359 | { |
| 360 | CORE_ADDR type, val; |
| 361 | gdb_byte *data; |
| 362 | gdb_byte *ptr; |
| 363 | struct auxv_info *info; |
| 364 | |
| 365 | info = get_auxv_inferior_data (ops); |
| 366 | |
| 367 | data = info->data; |
| 368 | ptr = data; |
| 369 | |
| 370 | if (info->length <= 0) |
| 371 | return info->length; |
| 372 | |
| 373 | while (1) |
| 374 | switch (target_auxv_parse (ops, &ptr, data + info->length, &type, &val)) |
| 375 | { |
| 376 | case 1: /* Here's an entry, check it. */ |
| 377 | if (type == match) |
| 378 | { |
| 379 | *valp = val; |
| 380 | return 1; |
| 381 | } |
| 382 | break; |
| 383 | case 0: /* End of the vector. */ |
| 384 | return 0; |
| 385 | default: /* Bogosity. */ |
| 386 | return -1; |
| 387 | } |
| 388 | |
| 389 | /*NOTREACHED*/ |
| 390 | } |
| 391 | |
| 392 | |
| 393 | /* Print the contents of the target's AUXV on the specified file. */ |
| 394 | int |
| 395 | fprint_target_auxv (struct ui_file *file, struct target_ops *ops) |
| 396 | { |
| 397 | CORE_ADDR type, val; |
| 398 | gdb_byte *data; |
| 399 | gdb_byte *ptr; |
| 400 | struct auxv_info *info; |
| 401 | int ents = 0; |
| 402 | |
| 403 | info = get_auxv_inferior_data (ops); |
| 404 | |
| 405 | data = info->data; |
| 406 | ptr = data; |
| 407 | if (info->length <= 0) |
| 408 | return info->length; |
| 409 | |
| 410 | while (target_auxv_parse (ops, &ptr, data + info->length, &type, &val) > 0) |
| 411 | { |
| 412 | const char *name = "???"; |
| 413 | const char *description = ""; |
| 414 | enum { dec, hex, str } flavor = hex; |
| 415 | |
| 416 | switch (type) |
| 417 | { |
| 418 | #define TAG(tag, text, kind) \ |
| 419 | case tag: name = #tag; description = text; flavor = kind; break |
| 420 | TAG (AT_NULL, _("End of vector"), hex); |
| 421 | TAG (AT_IGNORE, _("Entry should be ignored"), hex); |
| 422 | TAG (AT_EXECFD, _("File descriptor of program"), dec); |
| 423 | TAG (AT_PHDR, _("Program headers for program"), hex); |
| 424 | TAG (AT_PHENT, _("Size of program header entry"), dec); |
| 425 | TAG (AT_PHNUM, _("Number of program headers"), dec); |
| 426 | TAG (AT_PAGESZ, _("System page size"), dec); |
| 427 | TAG (AT_BASE, _("Base address of interpreter"), hex); |
| 428 | TAG (AT_FLAGS, _("Flags"), hex); |
| 429 | TAG (AT_ENTRY, _("Entry point of program"), hex); |
| 430 | TAG (AT_NOTELF, _("Program is not ELF"), dec); |
| 431 | TAG (AT_UID, _("Real user ID"), dec); |
| 432 | TAG (AT_EUID, _("Effective user ID"), dec); |
| 433 | TAG (AT_GID, _("Real group ID"), dec); |
| 434 | TAG (AT_EGID, _("Effective group ID"), dec); |
| 435 | TAG (AT_CLKTCK, _("Frequency of times()"), dec); |
| 436 | TAG (AT_PLATFORM, _("String identifying platform"), str); |
| 437 | TAG (AT_HWCAP, _("Machine-dependent CPU capability hints"), hex); |
| 438 | TAG (AT_FPUCW, _("Used FPU control word"), dec); |
| 439 | TAG (AT_DCACHEBSIZE, _("Data cache block size"), dec); |
| 440 | TAG (AT_ICACHEBSIZE, _("Instruction cache block size"), dec); |
| 441 | TAG (AT_UCACHEBSIZE, _("Unified cache block size"), dec); |
| 442 | TAG (AT_IGNOREPPC, _("Entry should be ignored"), dec); |
| 443 | TAG (AT_BASE_PLATFORM, _("String identifying base platform"), str); |
| 444 | TAG (AT_RANDOM, _("Address of 16 random bytes"), hex); |
| 445 | TAG (AT_EXECFN, _("File name of executable"), str); |
| 446 | TAG (AT_SECURE, _("Boolean, was exec setuid-like?"), dec); |
| 447 | TAG (AT_SYSINFO, _("Special system info/entry points"), hex); |
| 448 | TAG (AT_SYSINFO_EHDR, _("System-supplied DSO's ELF header"), hex); |
| 449 | TAG (AT_L1I_CACHESHAPE, _("L1 Instruction cache information"), hex); |
| 450 | TAG (AT_L1D_CACHESHAPE, _("L1 Data cache information"), hex); |
| 451 | TAG (AT_L2_CACHESHAPE, _("L2 cache information"), hex); |
| 452 | TAG (AT_L3_CACHESHAPE, _("L3 cache information"), hex); |
| 453 | TAG (AT_SUN_UID, _("Effective user ID"), dec); |
| 454 | TAG (AT_SUN_RUID, _("Real user ID"), dec); |
| 455 | TAG (AT_SUN_GID, _("Effective group ID"), dec); |
| 456 | TAG (AT_SUN_RGID, _("Real group ID"), dec); |
| 457 | TAG (AT_SUN_LDELF, _("Dynamic linker's ELF header"), hex); |
| 458 | TAG (AT_SUN_LDSHDR, _("Dynamic linker's section headers"), hex); |
| 459 | TAG (AT_SUN_LDNAME, _("String giving name of dynamic linker"), str); |
| 460 | TAG (AT_SUN_LPAGESZ, _("Large pagesize"), dec); |
| 461 | TAG (AT_SUN_PLATFORM, _("Platform name string"), str); |
| 462 | TAG (AT_SUN_HWCAP, _("Machine-dependent CPU capability hints"), hex); |
| 463 | TAG (AT_SUN_IFLUSH, _("Should flush icache?"), dec); |
| 464 | TAG (AT_SUN_CPU, _("CPU name string"), str); |
| 465 | TAG (AT_SUN_EMUL_ENTRY, _("COFF entry point address"), hex); |
| 466 | TAG (AT_SUN_EMUL_EXECFD, _("COFF executable file descriptor"), dec); |
| 467 | TAG (AT_SUN_EXECNAME, |
| 468 | _("Canonicalized file name given to execve"), str); |
| 469 | TAG (AT_SUN_MMU, _("String for name of MMU module"), str); |
| 470 | TAG (AT_SUN_LDDATA, _("Dynamic linker's data segment address"), hex); |
| 471 | TAG (AT_SUN_AUXFLAGS, |
| 472 | _("AF_SUN_ flags passed from the kernel"), hex); |
| 473 | } |
| 474 | |
| 475 | fprintf_filtered (file, "%-4s %-20s %-30s ", |
| 476 | plongest (type), name, description); |
| 477 | switch (flavor) |
| 478 | { |
| 479 | case dec: |
| 480 | fprintf_filtered (file, "%s\n", plongest (val)); |
| 481 | break; |
| 482 | case hex: |
| 483 | fprintf_filtered (file, "%s\n", paddress (target_gdbarch (), val)); |
| 484 | break; |
| 485 | case str: |
| 486 | { |
| 487 | struct value_print_options opts; |
| 488 | |
| 489 | get_user_print_options (&opts); |
| 490 | if (opts.addressprint) |
| 491 | fprintf_filtered (file, "%s ", paddress (target_gdbarch (), val)); |
| 492 | val_print_string (builtin_type (target_gdbarch ())->builtin_char, |
| 493 | NULL, val, -1, file, &opts); |
| 494 | fprintf_filtered (file, "\n"); |
| 495 | } |
| 496 | break; |
| 497 | } |
| 498 | ++ents; |
| 499 | if (type == AT_NULL) |
| 500 | break; |
| 501 | } |
| 502 | |
| 503 | return ents; |
| 504 | } |
| 505 | |
| 506 | static void |
| 507 | info_auxv_command (char *cmd, int from_tty) |
| 508 | { |
| 509 | if (! target_has_stack) |
| 510 | error (_("The program has no auxiliary information now.")); |
| 511 | else |
| 512 | { |
| 513 | int ents = fprint_target_auxv (gdb_stdout, ¤t_target); |
| 514 | |
| 515 | if (ents < 0) |
| 516 | error (_("No auxiliary vector found, or failed reading it.")); |
| 517 | else if (ents == 0) |
| 518 | error (_("Auxiliary vector is empty.")); |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | |
| 523 | extern initialize_file_ftype _initialize_auxv; /* -Wmissing-prototypes; */ |
| 524 | |
| 525 | void |
| 526 | _initialize_auxv (void) |
| 527 | { |
| 528 | add_info ("auxv", info_auxv_command, |
| 529 | _("Display the inferior's auxiliary vector.\n\ |
| 530 | This is information provided by the operating system at program startup.")); |
| 531 | |
| 532 | /* Set an auxv cache per-inferior. */ |
| 533 | auxv_inferior_data |
| 534 | = register_inferior_data_with_cleanup (NULL, auxv_inferior_data_cleanup); |
| 535 | |
| 536 | /* Observers used to invalidate the auxv cache when needed. */ |
| 537 | observer_attach_inferior_exit (invalidate_auxv_cache_inf); |
| 538 | observer_attach_inferior_appeared (invalidate_auxv_cache_inf); |
| 539 | observer_attach_executable_changed (invalidate_auxv_cache); |
| 540 | } |