gdb/gdb-bz539590-gnu-ifunc.patch

512 lines
19 KiB
Diff
Raw Normal View History

2010-01-12 22:15:57 +00:00
gdb/
2009-11-24 Jan Kratochvil <jan.kratochvil@redhat.com>
Transparent GNU-IFUNCs support.
* elfread.c (record_minimal_symbol): Apply also for mst_text_gnu_ifunc.
(elf_symtab_read): Set also mst_text_gnu_ifunc.
* gdbtypes.c (init_type): Support TYPE_FLAG_GNU_IFUNC.
(gdbtypes_post_init): Initialize builtin_func_func_ptr.
(objfile_type): Initialize nodebug_text_gnu_ifunc_symbol.
* gdbtypes.h (enum type_flag_value <TYPE_FLAG_GNU_IFUNC>)
(TYPE_GNU_IFUNC, struct main_type <flag_gnu_ifunc>)
(struct builtin_type <builtin_func_func_ptr>)
(struct objfile_type <nodebug_text_gnu_ifunc_symbol>): New.
* infcall.c (find_function_addr <TYPE_GNU_IFUNC (ftype)>): New.
* minsyms.c (lookup_minimal_symbol_text, prim_record_minimal_symbol)
(find_solib_trampoline_target): Support also mst_text_gnu_ifunc.
(in_gnu_ifunc_stub): New.
* parse.c (write_exp_msymbol <mst_text_gnu_ifunc>): New.
* solib-svr4.c (svr4_in_dynsym_resolve_code): Call also
in_gnu_ifunc_stub.
* symmisc.c (dump_msymbols <mst_text_gnu_ifunc>): New.
* symtab.c (search_symbols): Support also mst_text_gnu_ifunc.
* symtab.h (enum minimal_symbol_type <mst_text_gnu_ifunc>)
(in_gnu_ifunc_stub): New.
* linespec.c: Include infcall.h.
(minsym_found <MSYMBOL_TYPE (msymbol) == mst_text_gnu_ifunc>): New.
gdb/testsuite/
2009-11-24 Jan Kratochvil <jan.kratochvil@redhat.com>
Transparent GNU-IFUNCs support.
* gdb.base/gnu-ifunc-lib.c, gdb.base/gnu-ifunc.c,
gdb.base/gnu-ifunc.exp: New.
Index: gdb-7.0.50.20100115/gdb/elfread.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/elfread.c 2010-01-15 11:45:34.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/elfread.c 2010-01-15 12:17:16.000000000 +0100
@@ -184,7 +184,8 @@ record_minimal_symbol (const char *name,
2010-01-12 22:15:57 +00:00
{
struct gdbarch *gdbarch = get_objfile_arch (objfile);
- if (ms_type == mst_text || ms_type == mst_file_text)
+ if (ms_type == mst_text || ms_type == mst_file_text
+ || ms_type == mst_text_gnu_ifunc)
address = gdbarch_smash_text_address (gdbarch, address);
return prim_record_minimal_symbol_full (name, name_len, copy_name, address,
@@ -393,7 +394,10 @@ elf_symtab_read (struct objfile *objfile
2010-01-12 22:15:57 +00:00
{
if (sym->flags & (BSF_GLOBAL | BSF_WEAK))
{
- ms_type = mst_text;
+ if (sym->flags & BSF_GNU_INDIRECT_FUNCTION)
+ ms_type = mst_text_gnu_ifunc;
+ else
+ ms_type = mst_text;
}
else if ((sym->name[0] == '.' && sym->name[1] == 'L')
|| ((sym->flags & BSF_LOCAL)
Index: gdb-7.0.50.20100115/gdb/gdbtypes.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/gdbtypes.c 2010-01-15 03:22:31.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/gdbtypes.c 2010-01-15 12:16:49.000000000 +0100
@@ -1946,6 +1946,8 @@ init_type (enum type_code code, int leng
2010-01-12 22:15:57 +00:00
TYPE_NOTTEXT (type) = 1;
if (flags & TYPE_FLAG_FIXED_INSTANCE)
TYPE_FIXED_INSTANCE (type) = 1;
+ if (flags & TYPE_FLAG_GNU_IFUNC)
+ TYPE_GNU_IFUNC (type) = 1;
if (name)
TYPE_NAME (type) = obsavestring (name, strlen (name),
@@ -3846,6 +3848,8 @@ gdbtypes_post_init (struct gdbarch *gdba
2010-01-12 22:15:57 +00:00
= lookup_pointer_type (builtin_type->builtin_void);
builtin_type->builtin_func_ptr
= lookup_pointer_type (lookup_function_type (builtin_type->builtin_void));
+ builtin_type->builtin_func_func_ptr
+ = lookup_pointer_type (lookup_function_type (builtin_type->builtin_func_ptr));
/* This type represents a GDB internal function. */
builtin_type->internal_fn
@@ -3962,6 +3966,11 @@ objfile_type (struct objfile *objfile)
2010-01-12 22:15:57 +00:00
"<text variable, no debug info>", objfile);
TYPE_TARGET_TYPE (objfile_type->nodebug_text_symbol)
= objfile_type->builtin_int;
+ objfile_type->nodebug_text_gnu_ifunc_symbol
+ = init_type (TYPE_CODE_FUNC, 1, TYPE_FLAG_GNU_IFUNC,
+ "<text gnu-ifunc variable, no debug info>", objfile);
+ TYPE_TARGET_TYPE (objfile_type->nodebug_text_gnu_ifunc_symbol)
+ = objfile_type->nodebug_text_symbol;
objfile_type->nodebug_data_symbol
= init_type (TYPE_CODE_INT,
gdbarch_int_bit (gdbarch) / HOST_CHAR_BIT, 0,
Index: gdb-7.0.50.20100115/gdb/gdbtypes.h
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/gdbtypes.h 2010-01-15 11:49:26.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/gdbtypes.h 2010-01-15 12:16:49.000000000 +0100
@@ -188,6 +188,7 @@ enum type_flag_value
2010-01-12 22:15:57 +00:00
TYPE_FLAG_FIXED_INSTANCE = (1 << 15),
TYPE_FLAG_STUB_SUPPORTED = (1 << 16),
TYPE_FLAG_NOTTEXT = (1 << 17),
+ TYPE_FLAG_GNU_IFUNC = (1 << 18),
/* Used for error-checking. */
TYPE_FLAG_MIN = TYPE_FLAG_UNSIGNED
@@ -293,6 +294,12 @@ enum type_instance_flag_value
2010-01-12 22:15:57 +00:00
#define TYPE_NOTTEXT(t) (TYPE_MAIN_TYPE (t)->flag_nottext)
+/* Currently used only for TYPE_CODE_FUNC where specifies the real function
+ address is returned by this function call. TYPE_TARGET_TYPE determines the
+ final returned function type to be presented to user. */
+
+#define TYPE_GNU_IFUNC(t) (TYPE_MAIN_TYPE (t)->flag_gnu_ifunc)
+
/* Type owner. If TYPE_OBJFILE_OWNED is true, the type is owned by
the objfile retrieved as TYPE_OBJFILE. Otherweise, the type is
owned by an architecture; TYPE_OBJFILE is NULL in this case. */
@@ -447,6 +454,7 @@ struct main_type
2010-01-12 22:15:57 +00:00
unsigned int flag_vector : 1;
unsigned int flag_stub_supported : 1;
unsigned int flag_nottext : 1;
+ unsigned int flag_gnu_ifunc : 1;
unsigned int flag_fixed_instance : 1;
unsigned int flag_objfile_owned : 1;
unsigned int flag_discardable : 1;
@@ -1250,6 +1258,10 @@ struct builtin_type
2010-01-12 22:15:57 +00:00
(*) () can server as a generic function pointer. */
struct type *builtin_func_ptr;
+ /* `pointer to function returning pointer to function (returning void)' type.
+ The final void return type is not significant for it. */
+ struct type *builtin_func_func_ptr;
+
/* Special-purpose types. */
@@ -1292,6 +1304,7 @@ struct objfile_type
2010-01-12 22:15:57 +00:00
/* Types used for symbols with no debug information. */
struct type *nodebug_text_symbol;
+ struct type *nodebug_text_gnu_ifunc_symbol;
struct type *nodebug_data_symbol;
struct type *nodebug_unknown_symbol;
struct type *nodebug_tls_symbol;
Index: gdb-7.0.50.20100115/gdb/infcall.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/infcall.c 2010-01-01 08:31:36.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/infcall.c 2010-01-15 12:16:49.000000000 +0100
2010-01-12 22:15:57 +00:00
@@ -286,6 +286,27 @@ find_function_addr (struct value *functi
else
error (_("Invalid data type for function to be called."));
+ if (TYPE_GNU_IFUNC (ftype))
+ {
+ struct type *func_func_ptr;
+
+ funaddr += gdbarch_deprecated_function_start_offset (gdbarch);
+
+ /* Cast FUNADDR to drop TYPE_GNU_IFUNC and being able to call gnu-ifunc
+ FUNADDR without causing deadlock by this block of code. */
+
+ func_func_ptr = builtin_type (gdbarch)->builtin_func_func_ptr;
+ function = value_from_pointer (func_func_ptr, funaddr);
+
+ /* gnu-ifuncs have no arguments. */
+ function = call_function_by_hand (function, 0, NULL);
+
+ funaddr = value_as_address (function);
+
+ /* This is `int' as the return type of the final function. */
+ value_type = TYPE_TARGET_TYPE (value_type);
+ }
+
if (retval_type != NULL)
*retval_type = value_type;
return funaddr + gdbarch_deprecated_function_start_offset (gdbarch);
Index: gdb-7.0.50.20100115/gdb/linespec.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/linespec.c 2010-01-12 06:48:56.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/linespec.c 2010-01-15 12:16:49.000000000 +0100
2010-01-12 22:15:57 +00:00
@@ -40,6 +40,7 @@
#include "interps.h"
#include "mi/mi-cmds.h"
#include "target.h"
+#include "infcall.h"
/* We share this one with symtab.c, but it is not exported widely. */
@@ -1867,6 +1868,22 @@ minsym_found (int funfirstline, struct m
2010-01-12 22:15:57 +00:00
pc = gdbarch_convert_from_func_ptr_addr (gdbarch,
values.sals[0].pc,
&current_target);
+
+ /* Call gnu-ifunc to resolve breakpoint at its returned function. */
+ if (MSYMBOL_TYPE (msymbol) == mst_text_gnu_ifunc)
+ {
+ struct type *func_func_ptr;
+ struct value *function;
+
+ func_func_ptr = builtin_type (gdbarch)->builtin_func_func_ptr;
+ function = value_from_pointer (func_func_ptr, pc);
+
+ /* gnu-ifuncs have no arguments. */
+ function = call_function_by_hand (function, 0, NULL);
+
+ pc = value_as_address (function);
+ }
+
if (pc != values.sals[0].pc)
values.sals[0] = find_pc_sect_line (pc, NULL, 0);
Index: gdb-7.0.50.20100115/gdb/minsyms.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/minsyms.c 2010-01-15 03:22:31.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/minsyms.c 2010-01-15 12:16:49.000000000 +0100
2010-01-12 22:15:57 +00:00
@@ -331,8 +331,9 @@ lookup_minimal_symbol_text (const char *
msymbol = msymbol->hash_next)
{
if (strcmp (SYMBOL_LINKAGE_NAME (msymbol), name) == 0 &&
- (MSYMBOL_TYPE (msymbol) == mst_text ||
- MSYMBOL_TYPE (msymbol) == mst_file_text))
+ (MSYMBOL_TYPE (msymbol) == mst_text
+ || MSYMBOL_TYPE (msymbol) == mst_text_gnu_ifunc
+ || MSYMBOL_TYPE (msymbol) == mst_file_text))
{
switch (MSYMBOL_TYPE (msymbol))
{
@@ -698,6 +699,16 @@ lookup_minimal_symbol_by_pc (CORE_ADDR p
2010-01-12 22:15:57 +00:00
{
return lookup_minimal_symbol_by_pc_section (pc, NULL);
}
+
+/* Return non-zero iff PC is in function implementing gnu-ifunc selection. */
+
+int
+in_gnu_ifunc_stub (CORE_ADDR pc)
+{
+ struct minimal_symbol *msymbol = lookup_minimal_symbol_by_pc (pc);
+
+ return msymbol && MSYMBOL_TYPE (msymbol) == mst_text_gnu_ifunc;
+}
/* Return leading symbol character for a BFD. If BFD is NULL,
@@ -737,6 +748,7 @@ prim_record_minimal_symbol (const char *
2010-01-12 22:15:57 +00:00
switch (ms_type)
{
case mst_text:
+ case mst_text_gnu_ifunc:
case mst_file_text:
case mst_solib_trampoline:
section = SECT_OFF_TEXT (objfile);
@@ -1202,7 +1214,8 @@ find_solib_trampoline_target (struct fra
2010-01-12 22:15:57 +00:00
{
ALL_MSYMBOLS (objfile, msymbol)
{
- if (MSYMBOL_TYPE (msymbol) == mst_text
+ if ((MSYMBOL_TYPE (msymbol) == mst_text
+ || MSYMBOL_TYPE (msymbol) == mst_text_gnu_ifunc)
&& strcmp (SYMBOL_LINKAGE_NAME (msymbol),
SYMBOL_LINKAGE_NAME (tsymbol)) == 0)
return SYMBOL_VALUE_ADDRESS (msymbol);
Index: gdb-7.0.50.20100115/gdb/parse.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/parse.c 2010-01-15 03:22:31.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/parse.c 2010-01-15 12:16:49.000000000 +0100
@@ -516,6 +516,11 @@ write_exp_msymbol (struct minimal_symbol
2010-01-12 22:15:57 +00:00
write_exp_elt_type (objfile_type (objfile)->nodebug_text_symbol);
break;
+ case mst_text_gnu_ifunc:
+ write_exp_elt_type (objfile_type (objfile)
+ ->nodebug_text_gnu_ifunc_symbol);
+ break;
+
case mst_data:
case mst_file_data:
case mst_bss:
Index: gdb-7.0.50.20100115/gdb/solib-svr4.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/solib-svr4.c 2010-01-15 12:16:41.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/solib-svr4.c 2010-01-15 12:17:40.000000000 +0100
@@ -1257,7 +1257,8 @@ svr4_in_dynsym_resolve_code (CORE_ADDR p
&& pc < info->interp_text_sect_high)
|| (pc >= info->interp_plt_sect_low
&& pc < info->interp_plt_sect_high)
2010-01-12 22:15:57 +00:00
- || in_plt_section (pc, NULL));
+ || in_plt_section (pc, NULL)
+ || in_gnu_ifunc_stub (pc));
}
/* Given an executable's ABFD and target, compute the entry-point
Index: gdb-7.0.50.20100115/gdb/symmisc.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/symmisc.c 2010-01-15 03:22:32.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/symmisc.c 2010-01-15 12:16:49.000000000 +0100
@@ -294,6 +294,9 @@ dump_msymbols (struct objfile *objfile,
2010-01-12 22:15:57 +00:00
case mst_text:
ms_type = 'T';
break;
+ case mst_text_gnu_ifunc:
+ ms_type = 'i';
+ break;
case mst_solib_trampoline:
ms_type = 'S';
break;
Index: gdb-7.0.50.20100115/gdb/symtab.c
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/symtab.c 2010-01-15 03:22:31.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/symtab.c 2010-01-15 12:16:49.000000000 +0100
@@ -3257,7 +3257,7 @@ search_symbols (char *regexp, domain_enu
2010-01-12 22:15:57 +00:00
{mst_file_data, mst_solib_trampoline, mst_abs, mst_unknown};
static enum minimal_symbol_type types4[]
=
- {mst_file_bss, mst_text, mst_abs, mst_unknown};
+ {mst_file_bss, mst_text_gnu_ifunc, mst_abs, mst_unknown};
enum minimal_symbol_type ourtype;
enum minimal_symbol_type ourtype2;
enum minimal_symbol_type ourtype3;
Index: gdb-7.0.50.20100115/gdb/symtab.h
2010-01-12 22:15:57 +00:00
===================================================================
--- gdb-7.0.50.20100115.orig/gdb/symtab.h 2010-01-15 03:22:13.000000000 +0100
+++ gdb-7.0.50.20100115/gdb/symtab.h 2010-01-15 12:16:49.000000000 +0100
@@ -280,6 +280,8 @@ enum minimal_symbol_type
2010-01-12 22:15:57 +00:00
{
mst_unknown = 0, /* Unknown type, the default */
mst_text, /* Generally executable instructions */
+ mst_text_gnu_ifunc, /* Executable code returning address
+ of executable code */
mst_data, /* Generally initialized data */
mst_bss, /* Generally uninitialized data */
mst_abs, /* Generally absolute (nonrelocatable) */
@@ -1163,6 +1165,8 @@ extern struct minimal_symbol *lookup_min
2010-01-12 22:15:57 +00:00
extern struct minimal_symbol *lookup_minimal_symbol_by_pc (CORE_ADDR);
+extern int in_gnu_ifunc_stub (CORE_ADDR pc);
+
extern struct minimal_symbol
*lookup_minimal_symbol_by_pc_section (CORE_ADDR, struct obj_section *);
Index: gdb-7.0.50.20100115/gdb/testsuite/gdb.base/gnu-ifunc-lib.c
2010-01-12 22:15:57 +00:00
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-7.0.50.20100115/gdb/testsuite/gdb.base/gnu-ifunc-lib.c 2010-01-15 12:16:49.000000000 +0100
2010-01-12 22:15:57 +00:00
@@ -0,0 +1,45 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2009 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+
+typedef int (*final_t) (int arg);
+
+static int
+final (int arg)
+{
+ return arg + 1;
+}
+
+static volatile int gnu_ifunc_initialized;
+
+void
+gnu_ifunc_pre (void)
+{
+ assert (!gnu_ifunc_initialized);
+}
+
+final_t gnu_ifuncX (void) asm ("gnu_ifunc");
+asm (".type gnu_ifunc, @gnu_indirect_function");
+
+final_t
+gnu_ifuncX (void)
+{
+ gnu_ifunc_initialized = 1;
+
+ return final;
+}
Index: gdb-7.0.50.20100115/gdb/testsuite/gdb.base/gnu-ifunc.c
2010-01-12 22:15:57 +00:00
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-7.0.50.20100115/gdb/testsuite/gdb.base/gnu-ifunc.c 2010-01-15 12:16:49.000000000 +0100
2010-01-12 22:15:57 +00:00
@@ -0,0 +1,36 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2009 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+
+extern int gnu_ifunc (int arg);
+extern void gnu_ifunc_pre (void);
+
+int
+main (void)
+{
+ int i;
+
+ gnu_ifunc_pre ();
+
+ i = gnu_ifunc (1); /* break-at-call */
+ assert (i == 2);
+
+ gnu_ifunc (2); /* break-at-nextcall */
+
+ return 0; /* break-at-exit */
+}
Index: gdb-7.0.50.20100115/gdb/testsuite/gdb.base/gnu-ifunc.exp
2010-01-12 22:15:57 +00:00
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ gdb-7.0.50.20100115/gdb/testsuite/gdb.base/gnu-ifunc.exp 2010-01-15 12:16:49.000000000 +0100
2010-01-12 22:15:57 +00:00
@@ -0,0 +1,72 @@
+# Copyright (C) 2009 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+if {[skip_shlib_tests]} {
+ return 0
+}
+
+set testfile "gnu-ifunc"
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/${testfile}
+
+set libfile "${testfile}-lib"
+set libsrc ${libfile}.c
+set lib_so ${objdir}/${subdir}/${libfile}.so
+
+set lib_opts [list debug]
+set exec_opts [list debug shlib=$lib_so]
+
+if [get_compiler_info ${binfile}] {
+ return -1
+}
+
+if { [gdb_compile_shlib ${srcdir}/${subdir}/$libsrc $lib_so $lib_opts] != ""
+ || [gdb_compile ${srcdir}/${subdir}/$srcfile $binfile executable $exec_opts] != ""} {
+ untested "Could not compile either $libsrc or $srcfile."
+ return -1
+}
+
+# Start with a fresh gdb.
+
+clean_restart $testfile
+gdb_load_shlibs ${lib_so}
+
+if ![runto_main] then {
+ fail "Can't run to main"
+ return 1;
+}
+
+gdb_breakpoint [gdb_get_line_number "break-at-nextcall"]
+
+gdb_breakpoint [gdb_get_line_number "break-at-call"]
+gdb_continue_to_breakpoint "break-at-call" ".*break-at-call.*"
+
+# Test GDB will automatically indirect the call.
+
+gdb_test "p gnu_ifunc (3)" " = 4"
+
+# Test GDB will skip the gnu_ifunc resolver on first call.
+
+gdb_test "step" "\r\nfinal .*"
+
+# Test GDB will not break before the final chosen implementation.
+
+gdb_continue_to_breakpoint "break-at-nextcall" ".*break-at-nextcall.*"
+
+gdb_breakpoint "gnu_ifunc"
+
+gdb_continue_to_breakpoint "nextcall gnu_ifunc"
+
+gdb_test "frame" "#0 +final \\(.*" "nextcall gnu_ifunc skipped"