From 0e30163f12712261b56fbbc145e47222ca7b535a Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Mon, 28 Mar 2011 20:29:51 +0000 Subject: [PATCH] gdb/ Support resolution of STT_GNU_IFUNC via breakpoints. * breakpoint.c (print_it_typical): Support bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return. (bpstat_what): Rename parameter to bs_head, new variable bs, adjust the loop. Support bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return. New comment after the loop. New loop for bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return breakpoints. (bptype_string, print_one_breakpoint_location): Support bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return. (user_settable_breakpoint): Return true also for bp_gnu_ifunc_resolver. (allocate_bp_location): Support bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return. (set_breakpoint_location_function): New parameter explicit_loc, describe it. Call find_pc_partial_function_gnu_ifunc with new variable IS_GNU_IFUNC and adjust the address for STT_GNU_IFUNC if EXPLICIT_LOC is not set. (set_raw_breakpoint): Set EXPLICIT_LOC for set_breakpoint_location_function. (clone_momentary_breakpoint): Use true for EXPLICIT_LOC of set_breakpoint_location_function. (mention): Support bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return. (add_location_to_breakpoint): Set EXPLICIT_LOC for set_breakpoint_location_function. (update_breakpoint_locations): Remove static. (breakpoint_re_set_one): Support bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return. * breakpoint.h (enum bptype): New fields bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return. (update_breakpoint_locations): New declaration. * elfread.c: Include gdbthread.h and regcache.h. (elf_gnu_ifunc_resolver_stop, elf_gnu_ifunc_resolver_return_stop): New functions. (elf_gnu_ifunc_fns): Install them. * minsyms.c (stub_gnu_ifunc_resolver_stop) (stub_gnu_ifunc_resolver_return_stop): New functions. (stub_gnu_ifunc_fns): Install them. * symtab.h (struct gnu_ifunc_fns): New fields gnu_ifunc_resolver_stop and gnu_ifunc_resolver_return_stop. (gnu_ifunc_resolver_stop, gnu_ifunc_resolver_return_stop): New. --- gdb/ChangeLog | 45 +++++++++++++++++++ gdb/breakpoint.c | 98 +++++++++++++++++++++++++++++++++++++----- gdb/breakpoint.h | 16 +++++++ gdb/elfread.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++ gdb/minsyms.c | 20 +++++++++ gdb/symtab.h | 9 ++++ 6 files changed, 286 insertions(+), 11 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 08952867b29..03885dd7beb 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,48 @@ +2011-03-28 Jan Kratochvil + + Support resolution of STT_GNU_IFUNC via breakpoints. + * breakpoint.c (print_it_typical): Support bp_gnu_ifunc_resolver and + bp_gnu_ifunc_resolver_return. + (bpstat_what): Rename parameter to bs_head, new variable bs, adjust + the loop. Support bp_gnu_ifunc_resolver and + bp_gnu_ifunc_resolver_return. New comment after the loop. New loop + for bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return + breakpoints. + (bptype_string, print_one_breakpoint_location): Support + bp_gnu_ifunc_resolver and bp_gnu_ifunc_resolver_return. + (user_settable_breakpoint): Return true also for + bp_gnu_ifunc_resolver. + (allocate_bp_location): Support bp_gnu_ifunc_resolver and + bp_gnu_ifunc_resolver_return. + (set_breakpoint_location_function): New parameter explicit_loc, + describe it. Call find_pc_partial_function_gnu_ifunc with new + variable IS_GNU_IFUNC and adjust the address for STT_GNU_IFUNC if + EXPLICIT_LOC is not set. + (set_raw_breakpoint): Set EXPLICIT_LOC for + set_breakpoint_location_function. + (clone_momentary_breakpoint): Use true for EXPLICIT_LOC of + set_breakpoint_location_function. + (mention): Support bp_gnu_ifunc_resolver and + bp_gnu_ifunc_resolver_return. + (add_location_to_breakpoint): Set EXPLICIT_LOC for + set_breakpoint_location_function. + (update_breakpoint_locations): Remove static. + (breakpoint_re_set_one): Support bp_gnu_ifunc_resolver and + bp_gnu_ifunc_resolver_return. + * breakpoint.h (enum bptype): New fields bp_gnu_ifunc_resolver and + bp_gnu_ifunc_resolver_return. + (update_breakpoint_locations): New declaration. + * elfread.c: Include gdbthread.h and regcache.h. + (elf_gnu_ifunc_resolver_stop, elf_gnu_ifunc_resolver_return_stop): New + functions. + (elf_gnu_ifunc_fns): Install them. + * minsyms.c (stub_gnu_ifunc_resolver_stop) + (stub_gnu_ifunc_resolver_return_stop): New functions. + (stub_gnu_ifunc_fns): Install them. + * symtab.h (struct gnu_ifunc_fns): New fields gnu_ifunc_resolver_stop + and gnu_ifunc_resolver_return_stop. + (gnu_ifunc_resolver_stop, gnu_ifunc_resolver_return_stop): New. + 2011-03-28 Jan Kratochvil STT_GNU_IFUNC reader implementation. diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 1a9d9639d46..c300df96ffa 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -3504,6 +3504,8 @@ print_it_typical (bpstat bs) case bp_tracepoint: case bp_fast_tracepoint: case bp_jit_event: + case bp_gnu_ifunc_resolver: + case bp_gnu_ifunc_resolver_return: default: result = PRINT_UNKNOWN; break; @@ -4378,7 +4380,7 @@ handle_jit_event (void) /* Decide what infrun needs to do with this bpstat. */ struct bpstat_what -bpstat_what (bpstat bs) +bpstat_what (bpstat bs_head) { struct bpstat_what retval; /* We need to defer calling `solib_add', as adding new symbols @@ -4386,12 +4388,13 @@ bpstat_what (bpstat bs) and hence may clear unprocessed entries in the BS chain. */ int shlib_event = 0; int jit_event = 0; + bpstat bs; retval.main_action = BPSTAT_WHAT_KEEP_CHECKING; retval.call_dummy = STOP_NONE; retval.is_longjmp = 0; - for (; bs != NULL; bs = bs->next) + for (bs = bs_head; bs != NULL; bs = bs->next) { /* Extract this BS's action. After processing each BS, we check if its action overrides all we've seem so far. */ @@ -4521,6 +4524,16 @@ bpstat_what (bpstat bs) out already. */ internal_error (__FILE__, __LINE__, _("bpstat_what: tracepoint encountered")); + break; + case bp_gnu_ifunc_resolver: + /* Step over it (and insert bp_gnu_ifunc_resolver_return). */ + this_action = BPSTAT_WHAT_SINGLE; + break; + case bp_gnu_ifunc_resolver_return: + /* The breakpoint will be removed, execution will restart from the + PC of the former breakpoint. */ + this_action = BPSTAT_WHAT_KEEP_CHECKING; + break; default: internal_error (__FILE__, __LINE__, _("bpstat_what: unhandled bptype %d"), (int) bptype); @@ -4529,6 +4542,9 @@ bpstat_what (bpstat bs) retval.main_action = max (retval.main_action, this_action); } + /* These operations may affect the bs->breakpoint_at state so they are + delayed after MAIN_ACTION is decided above. */ + if (shlib_event) { if (debug_infrun) @@ -4558,6 +4574,23 @@ bpstat_what (bpstat bs) handle_jit_event (); } + for (bs = bs_head; bs != NULL; bs = bs->next) + { + struct breakpoint *b = bs->breakpoint_at; + + if (b == NULL) + continue; + switch (b->type) + { + case bp_gnu_ifunc_resolver: + gnu_ifunc_resolver_stop (b); + break; + case bp_gnu_ifunc_resolver_return: + gnu_ifunc_resolver_return_stop (b); + break; + } + } + return retval; } @@ -4715,6 +4748,8 @@ bptype_string (enum bptype type) {bp_fast_tracepoint, "fast tracepoint"}, {bp_static_tracepoint, "static tracepoint"}, {bp_jit_event, "jit events"}, + {bp_gnu_ifunc_resolver, "STT_GNU_IFUNC resolver"}, + {bp_gnu_ifunc_resolver_return, "STT_GNU_IFUNC resolver return"}, }; if (((int) type >= (sizeof (bptypes) / sizeof (bptypes[0]))) @@ -4849,6 +4884,8 @@ print_one_breakpoint_location (struct breakpoint *b, case bp_fast_tracepoint: case bp_static_tracepoint: case bp_jit_event: + case bp_gnu_ifunc_resolver: + case bp_gnu_ifunc_resolver_return: if (opts.addressprint) { annotate_field (4); @@ -5124,7 +5161,8 @@ user_settable_breakpoint (const struct breakpoint *b) || b->type == bp_catchpoint || b->type == bp_hardware_breakpoint || is_tracepoint (b) - || is_watchpoint (b)); + || is_watchpoint (b) + || b->type == bp_gnu_ifunc_resolver); } /* Return true if this breakpoint was set by the user, false if it is @@ -5620,6 +5658,8 @@ allocate_bp_location (struct breakpoint *bpt) case bp_longjmp_master: case bp_std_terminate_master: case bp_exception_master: + case bp_gnu_ifunc_resolver: + case bp_gnu_ifunc_resolver_return: loc->loc_type = bp_loc_software_breakpoint; break; case bp_hardware_breakpoint: @@ -5726,9 +5766,12 @@ set_raw_breakpoint_without_location (struct gdbarch *gdbarch, return b; } -/* Initialize loc->function_name. */ +/* Initialize loc->function_name. EXPLICIT_LOC says no indirect function + resolutions should be made as the user specified the location explicitly + enough. */ + static void -set_breakpoint_location_function (struct bp_location *loc) +set_breakpoint_location_function (struct bp_location *loc, int explicit_loc) { gdb_assert (loc->owner != NULL); @@ -5736,8 +5779,33 @@ set_breakpoint_location_function (struct bp_location *loc) || loc->owner->type == bp_hardware_breakpoint || is_tracepoint (loc->owner)) { - find_pc_partial_function (loc->address, &(loc->function_name), - NULL, NULL); + int is_gnu_ifunc; + + find_pc_partial_function_gnu_ifunc (loc->address, &loc->function_name, + NULL, NULL, &is_gnu_ifunc); + + if (is_gnu_ifunc && !explicit_loc) + { + struct breakpoint *b = loc->owner; + + gdb_assert (loc->pspace == current_program_space); + if (gnu_ifunc_resolve_name (loc->function_name, + &loc->requested_address)) + { + /* Recalculate ADDRESS based on new REQUESTED_ADDRESS. */ + loc->address = adjust_breakpoint_address (loc->gdbarch, + loc->requested_address, + b->type); + } + else if (b->type == bp_breakpoint && b->loc == loc + && loc->next == NULL && b->related_breakpoint == b) + { + /* Create only the whole new breakpoint of this type but do not + mess more complicated breakpoints with multiple locations. */ + b->type = bp_gnu_ifunc_resolver; + } + } + if (loc->function_name) loc->function_name = xstrdup (loc->function_name); } @@ -5812,7 +5880,8 @@ set_raw_breakpoint (struct gdbarch *gdbarch, b->loc->section = sal.section; b->line_number = sal.line; - set_breakpoint_location_function (b->loc); + set_breakpoint_location_function (b->loc, + sal.explicit_pc || sal.explicit_line); breakpoints_changed (); @@ -6929,7 +6998,7 @@ clone_momentary_breakpoint (struct breakpoint *orig) copy = set_raw_breakpoint_without_location (orig->gdbarch, orig->type); copy->loc = allocate_bp_location (copy); - set_breakpoint_location_function (copy->loc); + set_breakpoint_location_function (copy->loc, 1); copy->loc->gdbarch = orig->loc->gdbarch; copy->loc->requested_address = orig->loc->requested_address; @@ -7029,6 +7098,7 @@ mention (struct breakpoint *b) do_cleanups (ui_out_chain); break; case bp_breakpoint: + case bp_gnu_ifunc_resolver: if (ui_out_is_mi_like_p (uiout)) { say_where = 0; @@ -7039,6 +7109,8 @@ mention (struct breakpoint *b) else printf_filtered (_("Breakpoint")); printf_filtered (_(" %d"), b->number); + if (b->type == bp_gnu_ifunc_resolver) + printf_filtered (_(" at gnu-indirect-function resolver")); say_where = 1; break; case bp_hardware_breakpoint: @@ -7098,6 +7170,7 @@ mention (struct breakpoint *b) case bp_longjmp_master: case bp_std_terminate_master: case bp_exception_master: + case bp_gnu_ifunc_resolver_return: break; } @@ -7158,7 +7231,8 @@ add_location_to_breakpoint (struct breakpoint *b, gdb_assert (loc->pspace != NULL); loc->section = sal->section; - set_breakpoint_location_function (loc); + set_breakpoint_location_function (loc, + sal->explicit_pc || sal->explicit_line); return loc; } @@ -10399,7 +10473,7 @@ update_static_tracepoint (struct breakpoint *b, struct symtab_and_line sal) return sal; } -static void +void update_breakpoint_locations (struct breakpoint *b, struct symtabs_and_lines sals) { @@ -10531,6 +10605,7 @@ breakpoint_re_set_one (void *bint) case bp_tracepoint: case bp_fast_tracepoint: case bp_static_tracepoint: + case bp_gnu_ifunc_resolver: /* Do not attempt to re-set breakpoints disabled during startup. */ if (b->enable_state == bp_startup_disabled) return 0; @@ -10701,6 +10776,7 @@ breakpoint_re_set_one (void *bint) case bp_exception: case bp_exception_resume: case bp_jit_event: + case bp_gnu_ifunc_resolver_return: break; } diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h index d5af928f7d3..2cb56b74488 100644 --- a/gdb/breakpoint.h +++ b/gdb/breakpoint.h @@ -149,6 +149,19 @@ enum bptype /* Event for JIT compiled code generation or deletion. */ bp_jit_event, + + /* Breakpoint is placed at the STT_GNU_IFUNC resolver. When hit GDB + inserts new bp_gnu_ifunc_resolver_return at the caller. + bp_gnu_ifunc_resolver is still being kept here as a different thread + may still hit it before bp_gnu_ifunc_resolver_return is hit by the + original thread. */ + bp_gnu_ifunc_resolver, + + /* On its hit GDB now know the resolved address of the target + STT_GNU_IFUNC function. Associated bp_gnu_ifunc_resolver can be + deleted now and the breakpoint moved to the target function entry + point. */ + bp_gnu_ifunc_resolver_return, }; /* States of enablement of breakpoint. */ @@ -890,6 +903,9 @@ extern int breakpoint_thread_match (struct address_space *, extern void until_break_command (char *, int, int); +extern void update_breakpoint_locations (struct breakpoint *b, + struct symtabs_and_lines sals); + extern void breakpoint_re_set (void); extern void breakpoint_re_set_thread (struct breakpoint *); diff --git a/gdb/elfread.c b/gdb/elfread.c index 115251c036f..68bed7ebca7 100644 --- a/gdb/elfread.c +++ b/gdb/elfread.c @@ -41,6 +41,8 @@ #include "gdbtypes.h" #include "value.h" #include "infcall.h" +#include "gdbthread.h" +#include "regcache.h" extern void _initialize_elfread (void); @@ -948,6 +950,111 @@ elf_gnu_ifunc_resolve_addr (struct gdbarch *gdbarch, CORE_ADDR pc) return address; } +/* Handle inferior hit of bp_gnu_ifunc_resolver, see its definition. */ + +static void +elf_gnu_ifunc_resolver_stop (struct breakpoint *b) +{ + struct breakpoint *b_return; + struct frame_info *prev_frame = get_prev_frame (get_current_frame ()); + struct frame_id prev_frame_id = get_stack_frame_id (prev_frame); + CORE_ADDR prev_pc = get_frame_pc (prev_frame); + int thread_id = pid_to_thread_id (inferior_ptid); + + gdb_assert (b->type == bp_gnu_ifunc_resolver); + + for (b_return = b->related_breakpoint; b_return != b; + b_return = b_return->related_breakpoint) + { + gdb_assert (b_return->type == bp_gnu_ifunc_resolver_return); + gdb_assert (b_return->loc != NULL && b_return->loc->next == NULL); + gdb_assert (frame_id_p (b_return->frame_id)); + + if (b_return->thread == thread_id + && b_return->loc->requested_address == prev_pc + && frame_id_eq (b_return->frame_id, prev_frame_id)) + break; + } + + if (b_return == b) + { + struct symtab_and_line sal; + + /* No need to call find_pc_line for symbols resolving as this is only + a helper breakpointer never shown to the user. */ + + init_sal (&sal); + sal.pspace = current_inferior ()->pspace; + sal.pc = prev_pc; + sal.section = find_pc_overlay (sal.pc); + sal.explicit_pc = 1; + b_return = set_momentary_breakpoint (get_frame_arch (prev_frame), sal, + prev_frame_id, + bp_gnu_ifunc_resolver_return); + + /* Add new b_return to the ring list b->related_breakpoint. */ + gdb_assert (b_return->related_breakpoint == b_return); + b_return->related_breakpoint = b->related_breakpoint; + b->related_breakpoint = b_return; + } +} + +/* Handle inferior hit of bp_gnu_ifunc_resolver_return, see its definition. */ + +static void +elf_gnu_ifunc_resolver_return_stop (struct breakpoint *b) +{ + struct gdbarch *gdbarch = get_frame_arch (get_current_frame ()); + struct type *func_func_type = builtin_type (gdbarch)->builtin_func_func; + struct type *value_type = TYPE_TARGET_TYPE (func_func_type); + struct regcache *regcache = get_thread_regcache (inferior_ptid); + struct value *value; + CORE_ADDR resolved_address, resolved_pc; + struct symtab_and_line sal; + struct symtabs_and_lines sals; + + gdb_assert (b->type == bp_gnu_ifunc_resolver_return); + + value = allocate_value (value_type); + gdbarch_return_value (gdbarch, func_func_type, value_type, regcache, + value_contents_raw (value), NULL); + resolved_address = value_as_address (value); + resolved_pc = gdbarch_convert_from_func_ptr_addr (gdbarch, + resolved_address, + ¤t_target); + + while (b->related_breakpoint != b) + { + struct breakpoint *b_next = b->related_breakpoint; + + switch (b->type) + { + case bp_gnu_ifunc_resolver: + break; + case bp_gnu_ifunc_resolver_return: + delete_breakpoint (b); + break; + default: + internal_error (__FILE__, __LINE__, + _("handle_inferior_event: Invalid " + "gnu-indirect-function breakpoint type %d"), + (int) b->type); + } + b = b_next; + } + gdb_assert (b->type == bp_gnu_ifunc_resolver); + + gdb_assert (current_program_space == b->pspace); + elf_gnu_ifunc_record_cache (b->addr_string, resolved_pc); + + sal = find_pc_line (resolved_pc, 0); + sals.nelts = 1; + sals.sals = &sal; + + b->type = bp_breakpoint; + update_breakpoint_locations (b, sals); +} + struct build_id { size_t size; @@ -1502,6 +1609,8 @@ static const struct gnu_ifunc_fns elf_gnu_ifunc_fns = { elf_gnu_ifunc_resolve_addr, elf_gnu_ifunc_resolve_name, + elf_gnu_ifunc_resolver_stop, + elf_gnu_ifunc_resolver_return_stop }; void diff --git a/gdb/minsyms.c b/gdb/minsyms.c index 80f9497922d..b054e3ff987 100644 --- a/gdb/minsyms.c +++ b/gdb/minsyms.c @@ -729,12 +729,32 @@ stub_gnu_ifunc_resolve_name (const char *function_name, function_name); } +/* See elf_gnu_ifunc_resolver_stop for its real implementation. */ + +static void +stub_gnu_ifunc_resolver_stop (struct breakpoint *b) +{ + internal_error (__FILE__, __LINE__, + _("elf_gnu_ifunc_resolver_stop cannot be reached.")); +} + +/* See elf_gnu_ifunc_resolver_return_stop for its real implementation. */ + +static void +stub_gnu_ifunc_resolver_return_stop (struct breakpoint *b) +{ + internal_error (__FILE__, __LINE__, + _("elf_gnu_ifunc_resolver_return_stop cannot be reached.")); +} + /* See elf_gnu_ifunc_fns for its real implementation. */ static const struct gnu_ifunc_fns stub_gnu_ifunc_fns = { stub_gnu_ifunc_resolve_addr, stub_gnu_ifunc_resolve_name, + stub_gnu_ifunc_resolver_stop, + stub_gnu_ifunc_resolver_return_stop, }; /* A placeholder for &elf_gnu_ifunc_fns. */ diff --git a/gdb/symtab.h b/gdb/symtab.h index bc48d3c9b22..abe5e8610ef 100644 --- a/gdb/symtab.h +++ b/gdb/symtab.h @@ -1055,10 +1055,19 @@ struct gnu_ifunc_fns /* See elf_gnu_ifunc_resolve_name for its real implementation. */ int (*gnu_ifunc_resolve_name) (const char *function_name, CORE_ADDR *function_address_p); + + /* See elf_gnu_ifunc_resolver_stop for its real implementation. */ + void (*gnu_ifunc_resolver_stop) (struct breakpoint *b); + + /* See elf_gnu_ifunc_resolver_return_stop for its real implementation. */ + void (*gnu_ifunc_resolver_return_stop) (struct breakpoint *b); }; #define gnu_ifunc_resolve_addr gnu_ifunc_fns_p->gnu_ifunc_resolve_addr #define gnu_ifunc_resolve_name gnu_ifunc_fns_p->gnu_ifunc_resolve_name +#define gnu_ifunc_resolver_stop gnu_ifunc_fns_p->gnu_ifunc_resolver_stop +#define gnu_ifunc_resolver_return_stop \ + gnu_ifunc_fns_p->gnu_ifunc_resolver_return_stop extern const struct gnu_ifunc_fns *gnu_ifunc_fns_p; -- 2.30.2