gdb/ 2009-11-24 Jan Kratochvil 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_GNU_IFUNC, struct main_type ) (struct builtin_type ) (struct objfile_type ): New. * infcall.c (find_function_addr ): 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 ): New. * solib-svr4.c (svr4_in_dynsym_resolve_code): Call also in_gnu_ifunc_stub. * symmisc.c (dump_msymbols ): New. * symtab.c (search_symbols): Support also mst_text_gnu_ifunc. * symtab.h (enum minimal_symbol_type ) (in_gnu_ifunc_stub): New. * linespec.c: Include infcall.h. (minsym_found ): New. gdb/testsuite/ 2009-11-24 Jan Kratochvil 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 =================================================================== --- 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, { 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 { 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 =================================================================== --- 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 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 = 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) "", 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, + "", 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 =================================================================== --- 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 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 #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 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 (*) () 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 /* 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 =================================================================== --- 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 @@ -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 =================================================================== --- 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 @@ -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 pc = gdbarch_convert_from_func_ptr_addr (gdbarch, values.sals[0].pc, ¤t_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 =================================================================== --- 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 @@ -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 { 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 * 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 { 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 =================================================================== --- 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 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 =================================================================== --- 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) - || 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 =================================================================== --- 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, 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 =================================================================== --- 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 {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 =================================================================== --- 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 { 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 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 =================================================================== --- /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 @@ -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 . */ + +#include + +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 =================================================================== --- /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 @@ -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 . */ + +#include + +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 =================================================================== --- /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 @@ -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 . + +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"