[v2,03/15] Calling ifunc functions when target has no debug info but resolver has

Message ID 20180325191943.8246-4-palves@redhat.com
State New
Headers show
Series
  • Fixing GNU ifunc support
Related show

Commit Message

Pedro Alves March 25, 2018, 7:19 p.m.
In v2:
  - Added testsuite tweak.

After the previous patch, on Fedora 27 (glibc 2.26), if you try
calling strlen in the inferior, you now get:

  (top-gdb) p strlen ("hello")
  '__strlen_avx2' has unknown return type; cast the call to its declared return type

This is correct, because __strlen_avx2 is written in assembly.

We can improve on this though -- if the final ifunc resolved/target
function has no debug info, but the ifunc _resolver_ does have debug
info, we can try extracting the final function's type from the type
that the resolver returns.  E.g.,:

  typedef size_t (*strlen_t) (const char*);

  size_t my_strlen (const char *) { /* some implementation */ }
  strlen_t strlen_resolver (unsigned long hwcap) { return my_strlen; }

  extern size_t strlen (const char *s);
  __typeof (strlen) strlen __attribute__ ((ifunc ("strlen_resolver")));

In the strlen example above, the resolver returns strlen_t, which is a
typedef for pointer to a function that returns size_t.  "strlen_t" is
the type of both the user-visible "strlen", and of the the target
function that implements it.

This patch teaches GDB to extract that type.

This is done for actual inferior function calls (in infcall.c), and
for ptype (in eval_call).  By the time we get to either of these
places, we've already lost the original symbol/minsym, and only have
values and types to work with.  Hence the changes to c-exp.y and
evaluate_var_msym_value, to ensure that we propagate the ifunc
minsymbol's info.

The change to make ifunc symbols have no/unknown return type exposes a
latent problem -- gdb.compile/compile-ifunc.exp calls a no-debug-info
function, but we did not warn about it.  The test is fixed by this
commit too.

gdb/ChangeLog:
yyyy-mm-dd  Pedro Alves  <palves@redhat.com>

	* blockframe.c (gnu_ifunc_target_type): New function.
	* eval.c (evaluate_var_msym_value): For GNU ifunc types, always
	return a value with a memory address.
	(eval_call): For calls to GNU ifunc functions, try to find the
	type of the target function from the type that the resolver
	returns.
	* gdbtypes.c (objfile_type): Don't install a return type for ifunc
	symbols.
	* infcall.c (find_function_return_type): Rename to ...
	(find_function_type): ... this, and return the function's type
	instead of the function's return type.
	(find_function_addr): Add 'function_type' parameter.  For calls to
	GNU ifunc functions, try to find the type of the target function
	from the type that the resolver returns, and return it via
	FUNCTION_TYPE.
	(call_function_by_hand_dummy): Adjust to use the function type
	returned by find_function_addr.
	(find_function_addr): Add 'function_type' parameter and move
	description here.
	* symtab.h (find_gnu_ifunc_target_type): New declaration.

gdb/testsuite/ChangeLog:
yyyy-mm-dd  Pedro Alves  <palves@redhat.com>

	* gdb.compile/compile-ifunc.exp: Also expect "function has unknown
	return type" warnings.
---
 gdb/blockframe.c                            | 34 +++++++++++++++++
 gdb/eval.c                                  | 25 ++++++++-----
 gdb/gdbtypes.c                              |  4 --
 gdb/infcall.c                               | 58 +++++++++++++++++++----------
 gdb/infcall.h                               |  9 ++++-
 gdb/symtab.h                                |  6 +++
 gdb/testsuite/gdb.compile/compile-ifunc.exp |  9 +++--
 7 files changed, 106 insertions(+), 39 deletions(-)

-- 
2.14.3

Comments

Simon Marchi April 1, 2018, 4:22 a.m. | #1
On 2018-03-25 03:19 PM, Pedro Alves wrote:
> In v2:

>   - Added testsuite tweak.

> 

> After the previous patch, on Fedora 27 (glibc 2.26), if you try

> calling strlen in the inferior, you now get:

> 

>   (top-gdb) p strlen ("hello")

>   '__strlen_avx2' has unknown return type; cast the call to its declared return type

> 

> This is correct, because __strlen_avx2 is written in assembly.

> 

> We can improve on this though -- if the final ifunc resolved/target

> function has no debug info, but the ifunc _resolver_ does have debug

> info, we can try extracting the final function's type from the type

> that the resolver returns.  E.g.,:

> 

>   typedef size_t (*strlen_t) (const char*);

> 

>   size_t my_strlen (const char *) { /* some implementation */ }

>   strlen_t strlen_resolver (unsigned long hwcap) { return my_strlen; }

> 

>   extern size_t strlen (const char *s);

>   __typeof (strlen) strlen __attribute__ ((ifunc ("strlen_resolver")));

> 

> In the strlen example above, the resolver returns strlen_t, which is a

> typedef for pointer to a function that returns size_t.  "strlen_t" is

> the type of both the user-visible "strlen", and of the the target

> function that implements it.

> 

> This patch teaches GDB to extract that type.

> 

> This is done for actual inferior function calls (in infcall.c), and

> for ptype (in eval_call).  By the time we get to either of these

> places, we've already lost the original symbol/minsym, and only have

> values and types to work with.  Hence the changes to c-exp.y and

> evaluate_var_msym_value, to ensure that we propagate the ifunc

> minsymbol's info.

> 

> The change to make ifunc symbols have no/unknown return type exposes a

> latent problem -- gdb.compile/compile-ifunc.exp calls a no-debug-info

> function, but we did not warn about it.  The test is fixed by this

> commit too.


As usual, what you did seems to make sense, but I'm a bit lost.  I noted
some random comments.

> diff --git a/gdb/blockframe.c b/gdb/blockframe.c

> index 9be8871f756..db02b35742d 100644

> --- a/gdb/blockframe.c

> +++ b/gdb/blockframe.c

> @@ -323,6 +323,40 @@ find_pc_partial_function (CORE_ADDR pc, const char **name, CORE_ADDR *address,

>    return find_pc_partial_function_gnu_ifunc (pc, name, address, endaddr, NULL);

>  }

>  

> +/* See symtab.h.  */

> +

> +struct type *

> +find_gnu_ifunc_target_type (CORE_ADDR resolver_funaddr)

> +{

> +  /* See if we can figure out the function's return type from the type

> +     that the resolver returns.  */

> +  symbol *sym = find_pc_function (resolver_funaddr);

> +  if (sym != NULL

> +      && SYMBOL_CLASS (sym) == LOC_BLOCK

> +      && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == resolver_funaddr)

> +    {


This looks a lot like the "find_function_type" function.  Maybe it should use it?

> @@ -864,7 +878,11 @@ call_function_by_hand_dummy (struct value *function,

>        }

>    }

>  

> -  funaddr = find_function_addr (function, &values_type);

> +  struct type *ftype = check_typedef (value_type (function));

> +  if (TYPE_CODE (ftype) == TYPE_CODE_PTR)

> +    ftype = check_typedef (TYPE_TARGET_TYPE (ftype));


Are these last operations necessary to do here?  It seems to me like find_function_addr
will do pretty much the same work and ignore the input value of ftype anyway.

> +

> +  funaddr = find_function_addr (function, &values_type, &ftype);

>    if (values_type == NULL)

>      values_type = default_return_type;

>    if (values_type == NULL)

> diff --git a/gdb/infcall.h b/gdb/infcall.h

> index a3861fb1bf3..bea1494b50d 100644

> --- a/gdb/infcall.h

> +++ b/gdb/infcall.h

> @@ -25,8 +25,15 @@

>  struct value;

>  struct type;

>  

> +/* Determine a function's address and its return type from its value.

> +   If the function is a GNU ifunc, then return the address of the

> +   target function, and set *FUNCTION_TYPE to the target function's

> +   type, and *RETVAL_TYPE to the target function's return type..

> +   Calls error() if the function is not valid for calling.  */

> +

>  extern CORE_ADDR find_function_addr (struct value *function, 

> -				     struct type **retval_type);

> +				     struct type **retval_type,

> +				     struct type **function_type = NULL);


Isn't the function's return value type always the target type of the function's
type?  If so, it seems a bit redundant to have both retval_type and function_type.
The callers easily call TYPE_TARGET_TYPE on *function_type.  Or maybe do you see
some situations where we're able to determine the reval_type but not the
function_type, in which case the retval_type would still be relevant?

Simon
Pedro Alves April 10, 2018, 9:48 p.m. | #2
On 04/01/2018 05:22 AM, Simon Marchi wrote:

>> +/* See symtab.h.  */

>> +

>> +struct type *

>> +find_gnu_ifunc_target_type (CORE_ADDR resolver_funaddr)

>> +{

>> +  /* See if we can figure out the function's return type from the type

>> +     that the resolver returns.  */

>> +  symbol *sym = find_pc_function (resolver_funaddr);

>> +  if (sym != NULL

>> +      && SYMBOL_CLASS (sym) == LOC_BLOCK

>> +      && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == resolver_funaddr)

>> +    {

> 

> This looks a lot like the "find_function_type" function.  Maybe it should use it?


Good idea.  That lives in infcall.c currently, but I moved it along.

> 

>> @@ -864,7 +878,11 @@ call_function_by_hand_dummy (struct value *function,

>>        }

>>    }

>>  

>> -  funaddr = find_function_addr (function, &values_type);

>> +  struct type *ftype = check_typedef (value_type (function));

>> +  if (TYPE_CODE (ftype) == TYPE_CODE_PTR)

>> +    ftype = check_typedef (TYPE_TARGET_TYPE (ftype));

> 

> Are these last operations necessary to do here?  It seems to me like find_function_addr

> will do pretty much the same work and ignore the input value of ftype anyway.


You're right, I did this.

> 

>> +

>> +  funaddr = find_function_addr (function, &values_type, &ftype);

>>    if (values_type == NULL)

>>      values_type = default_return_type;

>>    if (values_type == NULL)

>> diff --git a/gdb/infcall.h b/gdb/infcall.h

>> index a3861fb1bf3..bea1494b50d 100644

>> --- a/gdb/infcall.h

>> +++ b/gdb/infcall.h

>> @@ -25,8 +25,15 @@

>>  struct value;

>>  struct type;

>>  

>> +/* Determine a function's address and its return type from its value.

>> +   If the function is a GNU ifunc, then return the address of the

>> +   target function, and set *FUNCTION_TYPE to the target function's

>> +   type, and *RETVAL_TYPE to the target function's return type..

>> +   Calls error() if the function is not valid for calling.  */

>> +

>>  extern CORE_ADDR find_function_addr (struct value *function, 

>> -				     struct type **retval_type);

>> +				     struct type **retval_type,

>> +				     struct type **function_type = NULL);

> 

> Isn't the function's return value type always the target type of the function's

> type?  If so, it seems a bit redundant to have both retval_type and function_type.

> The callers easily call TYPE_TARGET_TYPE on *function_type.  Or maybe do you see

> some situations where we're able to determine the reval_type but not the

> function_type, in which case the retval_type would still be relevant?


Yeah, find_function_addr handles a case where the function's type is not
really a function, but an integer:

  else if (TYPE_CODE (ftype) == TYPE_CODE_INT)
    {

This is reached when you call a function by address, like

  (gdb) print 0x1()

In this case, the caller wouldn't be able to use
TYPE_TARGET_TYPE on function_type.  Instead it gets a 
NULL retval_type.

I think it may be possible to handle that case by making
it return the type we use for non-debug functions in function_type:

  objfile_type->nodebug_text_symbol
    = init_type (objfile, TYPE_CODE_FUNC, TARGET_CHAR_BIT,
		 "<text variable, no debug info>");

but that type is currently tied to an objfile, seemingly for no
good reason.  But I'm not sure.  It'd needs experimentation
and reworking nodebug_text_symbol, which I'd prefer to leave
for another day.

Here's what I'm squashing into the patch locally.

From 2e89a1ef27e7eac203092346da942d16bc5a606c Mon Sep 17 00:00:00 2001
From: Pedro Alves <palves@redhat.com>

Date: Tue, 10 Apr 2018 22:46:49 +0100
Subject: [PATCH] find_function_type

---
 gdb/blockframe.c | 47 ++++++++++++++++++++++++++---------------------
 gdb/infcall.c    | 24 ++++--------------------
 gdb/infcall.h    |  2 +-
 gdb/symtab.h     |  5 +++++
 4 files changed, 36 insertions(+), 42 deletions(-)

diff --git a/gdb/blockframe.c b/gdb/blockframe.c
index db02b35742d..e6938a341ae 100644
--- a/gdb/blockframe.c
+++ b/gdb/blockframe.c
@@ -325,32 +325,37 @@ find_pc_partial_function (CORE_ADDR pc, const char **name, CORE_ADDR *address,
 
 /* See symtab.h.  */
 
+struct type *
+find_function_type (CORE_ADDR pc)
+{
+  struct symbol *sym = find_pc_function (pc);
+
+  if (sym != NULL && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == pc)
+    return SYMBOL_TYPE (sym);
+
+  return NULL;
+}
+
+/* See symtab.h.  */
+
 struct type *
 find_gnu_ifunc_target_type (CORE_ADDR resolver_funaddr)
 {
-  /* See if we can figure out the function's return type from the type
-     that the resolver returns.  */
-  symbol *sym = find_pc_function (resolver_funaddr);
-  if (sym != NULL
-      && SYMBOL_CLASS (sym) == LOC_BLOCK
-      && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == resolver_funaddr)
+  struct type *resolver_type = find_function_type (resolver_funaddr);
+  if (resolver_type != NULL)
     {
-      struct type *resolver_type = SYMBOL_TYPE (sym);
-      if (TYPE_CODE (resolver_type) == TYPE_CODE_FUNC)
+      /* Get the return type of the resolver.  */
+      struct type *resolver_ret_type
+	= check_typedef (TYPE_TARGET_TYPE (resolver_type));
+
+      /* If we found a pointer to function, then the resolved type
+	 is the type of the pointed-to function.  */
+      if (TYPE_CODE (resolver_ret_type) == TYPE_CODE_PTR)
 	{
-	  /* Get the return type of the resolver.  */
-	  struct type *resolver_ret_type
-	    = check_typedef (TYPE_TARGET_TYPE (resolver_type));
-
-	  /* If we found a pointer to function, then the resolved type
-	     is the type of the pointed-to function.  */
-	  if (TYPE_CODE (resolver_ret_type) == TYPE_CODE_PTR)
-	    {
-	      struct type *resolved_type
-		= TYPE_TARGET_TYPE (resolver_ret_type);
-	      if (TYPE_CODE (check_typedef (resolved_type)) == TYPE_CODE_FUNC)
-		return resolved_type;
-	    }
+	  struct type *resolved_type
+	    = TYPE_TARGET_TYPE (resolver_ret_type);
+	  if (TYPE_CODE (check_typedef (resolved_type)) == TYPE_CODE_FUNC)
+	    return resolved_type;
 	}
     }
 
diff --git a/gdb/infcall.c b/gdb/infcall.c
index d89d8ca7e8c..b233e369f27 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -229,20 +229,6 @@ value_arg_coerce (struct gdbarch *gdbarch, struct value *arg,
   return value_cast (type, arg);
 }
 
-/* Return the type of a function with its first instruction exactly at
-   the PC address.  Return NULL otherwise.  */
-
-static struct type *
-find_function_type (CORE_ADDR pc)
-{
-  struct symbol *sym = find_pc_function (pc);
-
-  if (sym != NULL && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == pc)
-    return SYMBOL_TYPE (sym);
-
-  return NULL;
-}
-
 /* See infcall.h.  */
 
 CORE_ADDR
@@ -737,13 +723,12 @@ call_function_by_hand_dummy (struct value *function,
 			     void *dummy_dtor_data)
 {
   CORE_ADDR sp;
-  struct type *values_type, *target_values_type;
+  struct type *target_values_type;
   unsigned char struct_return = 0, hidden_first_param_p = 0;
   CORE_ADDR struct_addr = 0;
   struct infcall_control_state *inf_status;
   struct cleanup *inf_status_cleanup;
   struct infcall_suspend_state *caller_state;
-  CORE_ADDR funaddr;
   CORE_ADDR real_pc;
   CORE_ADDR bp_addr;
   struct frame_id dummy_id;
@@ -878,11 +863,10 @@ call_function_by_hand_dummy (struct value *function,
       }
   }
 
-  struct type *ftype = check_typedef (value_type (function));
-  if (TYPE_CODE (ftype) == TYPE_CODE_PTR)
-    ftype = check_typedef (TYPE_TARGET_TYPE (ftype));
+  type *ftype;
+  type *values_type;
+  CORE_ADDR funaddr = find_function_addr (function, &values_type, &ftype);
 
-  funaddr = find_function_addr (function, &values_type, &ftype);
   if (values_type == NULL)
     values_type = default_return_type;
   if (values_type == NULL)
diff --git a/gdb/infcall.h b/gdb/infcall.h
index bea1494b50d..8b2195019c9 100644
--- a/gdb/infcall.h
+++ b/gdb/infcall.h
@@ -28,7 +28,7 @@ struct type;
 /* Determine a function's address and its return type from its value.
    If the function is a GNU ifunc, then return the address of the
    target function, and set *FUNCTION_TYPE to the target function's
-   type, and *RETVAL_TYPE to the target function's return type..
+   type, and *RETVAL_TYPE to the target function's return type.
    Calls error() if the function is not valid for calling.  */
 
 extern CORE_ADDR find_function_addr (struct value *function, 
diff --git a/gdb/symtab.h b/gdb/symtab.h
index 22b52019ee3..83ff6f226d8 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -1675,6 +1675,11 @@ extern int find_pc_partial_function_gnu_ifunc (CORE_ADDR pc, const char **name,
 extern int find_pc_partial_function (CORE_ADDR, const char **, CORE_ADDR *,
 				     CORE_ADDR *);
 
+/* Return the type of a function with its first instruction exactly at
+   the PC address.  Return NULL otherwise.  */
+
+extern struct type *find_function_type (CORE_ADDR pc);
+
 /* See if we can figure out the function's actual type from the type
    that the resolver returns.  RESOLVER_FUNADDR is the address of the
    ifunc resolver.  */
-- 
2.14.3
Pedro Alves April 10, 2018, 9:54 p.m. | #3
On 04/10/2018 10:48 PM, Pedro Alves wrote:
> 

> Here's what I'm squashing into the patch locally.


And here's the result.

I've also pushed the refreshed patches to users/palves/ifunc.

From 7a6a9fdd7504b3098e9a7019dff945e579bde371 Mon Sep 17 00:00:00 2001
From: Pedro Alves <palves@redhat.com>

Date: Tue, 10 Apr 2018 22:48:46 +0100
Subject: [PATCH] Calling ifunc functions when target has no debug info but
 resolver has

After the previous patch, on Fedora 27 (glibc 2.26), if you try
calling strlen in the inferior, you now get:

  (top-gdb) p strlen ("hello")
  '__strlen_avx2' has unknown return type; cast the call to its declared return type

This is correct, because __strlen_avx2 is written in assembly.

We can improve on this though -- if the final ifunc resolved/target
function has no debug info, but the ifunc _resolver_ does have debug
info, we can try extracting the final function's type from the type
that the resolver returns.  E.g.,:

  typedef size_t (*strlen_t) (const char*);

  size_t my_strlen (const char *) { /* some implementation */ }
  strlen_t strlen_resolver (unsigned long hwcap) { return my_strlen; }

  extern size_t strlen (const char *s);
  __typeof (strlen) strlen __attribute__ ((ifunc ("strlen_resolver")));

In the strlen example above, the resolver returns strlen_t, which is a
typedef for pointer to a function that returns size_t.  "strlen_t" is
the type of both the user-visible "strlen", and of the the target
function that implements it.

This patch teaches GDB to extract that type.

This is done for actual inferior function calls (in infcall.c), and
for ptype (in eval_call).  By the time we get to either of these
places, we've already lost the original symbol/minsym, and only have
values and types to work with.  Hence the changes to c-exp.y and
evaluate_var_msym_value, to ensure that we propagate the ifunc
minsymbol's info.

The change to make ifunc symbols have no/unknown return type exposes a
latent problem -- gdb.compile/compile-ifunc.exp calls a no-debug-info
function, but we did not warn about it.  The test is fixed by this
commit too.

gdb/ChangeLog:
yyyy-mm-dd  Pedro Alves  <palves@redhat.com>

	* blockframe.c (find_gnu_ifunc_target_type): New function.
	(find_function_type): New.
	* eval.c (evaluate_var_msym_value): For GNU ifunc types, always
	return a value with a memory address.
	(eval_call): For calls to GNU ifunc functions, try to find the
	type of the target function from the type that the resolver
	returns.
	* gdbtypes.c (objfile_type): Don't install a return type for ifunc
	symbols.
	* infcall.c (find_function_return_type): Delete.
	(find_function_addr): Add 'function_type' parameter.  For calls to
	GNU ifunc functions, try to find the type of the target function
	from the type that the resolver returns, and return it via
	FUNCTION_TYPE.
	(call_function_by_hand_dummy): Adjust to use the function type
	returned by find_function_addr.
	(find_function_addr): Add 'function_type' parameter and move
	description here.
	* symtab.h (find_function_type, find_gnu_ifunc_target_type): New
	declarations.

gdb/testsuite/ChangeLog:
yyyy-mm-dd  Pedro Alves  <palves@redhat.com>

	* gdb.compile/compile-ifunc.exp: Also expect "function has unknown
	return type" warnings.
---
 gdb/blockframe.c                            | 39 +++++++++++++++++
 gdb/eval.c                                  | 25 ++++++-----
 gdb/gdbtypes.c                              |  4 --
 gdb/infcall.c                               | 66 +++++++++++++++--------------
 gdb/infcall.h                               |  9 +++-
 gdb/symtab.h                                | 11 +++++
 gdb/testsuite/gdb.compile/compile-ifunc.exp |  9 ++--
 7 files changed, 112 insertions(+), 51 deletions(-)

diff --git a/gdb/blockframe.c b/gdb/blockframe.c
index 9be8871f756..e6938a341ae 100644
--- a/gdb/blockframe.c
+++ b/gdb/blockframe.c
@@ -323,6 +323,45 @@ find_pc_partial_function (CORE_ADDR pc, const char **name, CORE_ADDR *address,
   return find_pc_partial_function_gnu_ifunc (pc, name, address, endaddr, NULL);
 }
 
+/* See symtab.h.  */
+
+struct type *
+find_function_type (CORE_ADDR pc)
+{
+  struct symbol *sym = find_pc_function (pc);
+
+  if (sym != NULL && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == pc)
+    return SYMBOL_TYPE (sym);
+
+  return NULL;
+}
+
+/* See symtab.h.  */
+
+struct type *
+find_gnu_ifunc_target_type (CORE_ADDR resolver_funaddr)
+{
+  struct type *resolver_type = find_function_type (resolver_funaddr);
+  if (resolver_type != NULL)
+    {
+      /* Get the return type of the resolver.  */
+      struct type *resolver_ret_type
+	= check_typedef (TYPE_TARGET_TYPE (resolver_type));
+
+      /* If we found a pointer to function, then the resolved type
+	 is the type of the pointed-to function.  */
+      if (TYPE_CODE (resolver_ret_type) == TYPE_CODE_PTR)
+	{
+	  struct type *resolved_type
+	    = TYPE_TARGET_TYPE (resolver_ret_type);
+	  if (TYPE_CODE (check_typedef (resolved_type)) == TYPE_CODE_FUNC)
+	    return resolved_type;
+	}
+    }
+
+  return NULL;
+}
+
 /* Return the innermost stack frame that is executing inside of BLOCK and is
    at least as old as the selected frame. Return NULL if there is no
    such frame.  If BLOCK is NULL, just return NULL.  */
diff --git a/gdb/eval.c b/gdb/eval.c
index b6fbfcf6c95..ad66f7c32c1 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -734,17 +734,13 @@ value *
 evaluate_var_msym_value (enum noside noside,
 			 struct objfile *objfile, minimal_symbol *msymbol)
 {
-  if (noside == EVAL_AVOID_SIDE_EFFECTS)
-    {
-      type *the_type = find_minsym_type_and_address (msymbol, objfile, NULL);
-      return value_zero (the_type, not_lval);
-    }
+  CORE_ADDR address;
+  type *the_type = find_minsym_type_and_address (msymbol, objfile, &address);
+
+  if (noside == EVAL_AVOID_SIDE_EFFECTS && !TYPE_GNU_IFUNC (the_type))
+    return value_zero (the_type, not_lval);
   else
-    {
-      CORE_ADDR address;
-      type *the_type = find_minsym_type_and_address (msymbol, objfile, &address);
-      return value_at_lazy (the_type, address);
-    }
+    return value_at_lazy (the_type, address);
 }
 
 /* Helper for returning a value when handling EVAL_SKIP.  */
@@ -797,6 +793,15 @@ eval_call (expression *exp, enum noside noside,
       else if (TYPE_CODE (ftype) == TYPE_CODE_FUNC
 	       || TYPE_CODE (ftype) == TYPE_CODE_METHOD)
 	{
+	  if (TYPE_GNU_IFUNC (ftype))
+	    {
+	      CORE_ADDR address = value_address (argvec[0]);
+	      type *resolved_type = find_gnu_ifunc_target_type (address);
+
+	      if (resolved_type != NULL)
+		ftype = resolved_type;
+	    }
+
 	  type *return_type = TYPE_TARGET_TYPE (ftype);
 
 	  if (return_type == NULL)
diff --git a/gdb/gdbtypes.c b/gdb/gdbtypes.c
index b3a037971e0..2efd1264ff8 100644
--- a/gdb/gdbtypes.c
+++ b/gdb/gdbtypes.c
@@ -5443,10 +5443,6 @@ objfile_type (struct objfile *objfile)
   objfile_type->nodebug_text_gnu_ifunc_symbol
     = init_type (objfile, TYPE_CODE_FUNC, TARGET_CHAR_BIT,
 		 "<text gnu-indirect-function variable, no debug info>");
-  /* Ifunc resolvers return a function address.  */
-  TYPE_TARGET_TYPE (objfile_type->nodebug_text_gnu_ifunc_symbol)
-    = init_integer_type (objfile, gdbarch_addr_bit (gdbarch), 1,
-			 "__IFUNC_RESOLVER_RET");
   TYPE_GNU_IFUNC (objfile_type->nodebug_text_gnu_ifunc_symbol) = 1;
   objfile_type->nodebug_got_plt_symbol
     = init_pointer_type (objfile, gdbarch_addr_bit (gdbarch),
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 9f02674bdf2..b233e369f27 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -229,26 +229,12 @@ value_arg_coerce (struct gdbarch *gdbarch, struct value *arg,
   return value_cast (type, arg);
 }
 
-/* Return the return type of a function with its first instruction exactly at
-   the PC address.  Return NULL otherwise.  */
-
-static struct type *
-find_function_return_type (CORE_ADDR pc)
-{
-  struct symbol *sym = find_pc_function (pc);
-
-  if (sym != NULL && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == pc
-      && SYMBOL_TYPE (sym) != NULL)
-    return TYPE_TARGET_TYPE (SYMBOL_TYPE (sym));
-
-  return NULL;
-}
-
-/* Determine a function's address and its return type from its value.
-   Calls error() if the function is not valid for calling.  */
+/* See infcall.h.  */
 
 CORE_ADDR
-find_function_addr (struct value *function, struct type **retval_type)
+find_function_addr (struct value *function,
+		    struct type **retval_type,
+		    struct type **function_type)
 {
   struct type *ftype = check_typedef (value_type (function));
   struct gdbarch *gdbarch = get_type_arch (ftype);
@@ -275,17 +261,33 @@ find_function_addr (struct value *function, struct type **retval_type)
   if (TYPE_CODE (ftype) == TYPE_CODE_FUNC
       || TYPE_CODE (ftype) == TYPE_CODE_METHOD)
     {
-      value_type = TYPE_TARGET_TYPE (ftype);
-
       if (TYPE_GNU_IFUNC (ftype))
 	{
-	  funaddr = gnu_ifunc_resolve_addr (gdbarch, funaddr);
+	  CORE_ADDR resolver_addr = funaddr;
 
-	  /* Skip querying the function symbol if no RETVAL_TYPE has been
-	     asked for.  */
-	  if (retval_type)
-	    value_type = find_function_return_type (funaddr);
+	  /* Resolve the ifunc.  Note this may call the resolver
+	     function in the inferior.  */
+	  funaddr = gnu_ifunc_resolve_addr (gdbarch, resolver_addr);
+
+	  /* Skip querying the function symbol if no RETVAL_TYPE or
+	     FUNCTION_TYPE have been asked for.  */
+	  if (retval_type != NULL || function_type != NULL)
+	    {
+	      type *target_ftype = find_function_type (funaddr);
+	      /* If we don't have debug info for the target function,
+		 see if we can instead extract the target function's
+		 type from the type that the resolver returns.  */
+	      if (target_ftype == NULL)
+		target_ftype = find_gnu_ifunc_target_type (resolver_addr);
+	      if (target_ftype != NULL)
+		{
+		  value_type = TYPE_TARGET_TYPE (check_typedef (target_ftype));
+		  ftype = target_ftype;
+		}
+	    }
 	}
+      else
+	value_type = TYPE_TARGET_TYPE (ftype);
     }
   else if (TYPE_CODE (ftype) == TYPE_CODE_INT)
     {
@@ -320,6 +322,8 @@ find_function_addr (struct value *function, struct type **retval_type)
 
   if (retval_type != NULL)
     *retval_type = value_type;
+  if (function_type != NULL)
+    *function_type = ftype;
   return funaddr + gdbarch_deprecated_function_start_offset (gdbarch);
 }
 
@@ -719,15 +723,13 @@ call_function_by_hand_dummy (struct value *function,
 			     void *dummy_dtor_data)
 {
   CORE_ADDR sp;
-  struct type *values_type, *target_values_type;
+  struct type *target_values_type;
   unsigned char struct_return = 0, hidden_first_param_p = 0;
   CORE_ADDR struct_addr = 0;
   struct infcall_control_state *inf_status;
   struct cleanup *inf_status_cleanup;
   struct infcall_suspend_state *caller_state;
-  CORE_ADDR funaddr;
   CORE_ADDR real_pc;
-  struct type *ftype = check_typedef (value_type (function));
   CORE_ADDR bp_addr;
   struct frame_id dummy_id;
   struct frame_info *frame;
@@ -738,9 +740,6 @@ call_function_by_hand_dummy (struct value *function,
   char name_buf[RAW_FUNCTION_ADDRESS_SIZE];
   bool stack_temporaries = thread_stack_temporaries_enabled_p (inferior_ptid);
 
-  if (TYPE_CODE (ftype) == TYPE_CODE_PTR)
-    ftype = check_typedef (TYPE_TARGET_TYPE (ftype));
-
   if (!target_has_execution)
     noprocess ();
 
@@ -864,7 +863,10 @@ call_function_by_hand_dummy (struct value *function,
       }
   }
 
-  funaddr = find_function_addr (function, &values_type);
+  type *ftype;
+  type *values_type;
+  CORE_ADDR funaddr = find_function_addr (function, &values_type, &ftype);
+
   if (values_type == NULL)
     values_type = default_return_type;
   if (values_type == NULL)
diff --git a/gdb/infcall.h b/gdb/infcall.h
index a3861fb1bf3..8b2195019c9 100644
--- a/gdb/infcall.h
+++ b/gdb/infcall.h
@@ -25,8 +25,15 @@
 struct value;
 struct type;
 
+/* Determine a function's address and its return type from its value.
+   If the function is a GNU ifunc, then return the address of the
+   target function, and set *FUNCTION_TYPE to the target function's
+   type, and *RETVAL_TYPE to the target function's return type.
+   Calls error() if the function is not valid for calling.  */
+
 extern CORE_ADDR find_function_addr (struct value *function, 
-				     struct type **retval_type);
+				     struct type **retval_type,
+				     struct type **function_type = NULL);
 
 /* Perform a function call in the inferior.
 
diff --git a/gdb/symtab.h b/gdb/symtab.h
index f9d52e76979..83ff6f226d8 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -1675,6 +1675,17 @@ extern int find_pc_partial_function_gnu_ifunc (CORE_ADDR pc, const char **name,
 extern int find_pc_partial_function (CORE_ADDR, const char **, CORE_ADDR *,
 				     CORE_ADDR *);
 
+/* Return the type of a function with its first instruction exactly at
+   the PC address.  Return NULL otherwise.  */
+
+extern struct type *find_function_type (CORE_ADDR pc);
+
+/* See if we can figure out the function's actual type from the type
+   that the resolver returns.  RESOLVER_FUNADDR is the address of the
+   ifunc resolver.  */
+
+extern struct type *find_gnu_ifunc_target_type (CORE_ADDR resolver_funaddr);
+
 extern void clear_pc_function_cache (void);
 
 /* Expand symtab containing PC, SECTION if not already expanded.  */
diff --git a/gdb/testsuite/gdb.compile/compile-ifunc.exp b/gdb/testsuite/gdb.compile/compile-ifunc.exp
index ed700e416bb..979e39147e7 100644
--- a/gdb/testsuite/gdb.compile/compile-ifunc.exp
+++ b/gdb/testsuite/gdb.compile/compile-ifunc.exp
@@ -37,7 +37,9 @@ with_test_prefix "nodebug" {
     }
 
     gdb_test "compile code resultvar = gnu_ifunc (10);" \
-	"warning: variable has unknown type; assuming int"
+	[multi_line \
+	     "warning: variable has unknown type; assuming int" \
+	     "warning: function has unknown return type; assuming int"]
 
     gdb_test "p (int) resultvar" " = 11"
 
@@ -52,10 +54,9 @@ with_test_prefix "debug" {
     if ![runto_main] {
 	return -1
     }
-
     # gnu_ifunc (10): error: too many arguments to function 'gnu_ifunc'
-    gdb_test_no_output "compile code resultvar = gnu_ifunc_alias (10);"
-
+    gdb_test "compile code resultvar = gnu_ifunc_alias (10);" \
+	"warning: function has unknown return type; assuming int"
     gdb_test "p resultvar" " = 11"
 
 }
-- 
2.14.3

Patch

diff --git a/gdb/blockframe.c b/gdb/blockframe.c
index 9be8871f756..db02b35742d 100644
--- a/gdb/blockframe.c
+++ b/gdb/blockframe.c
@@ -323,6 +323,40 @@  find_pc_partial_function (CORE_ADDR pc, const char **name, CORE_ADDR *address,
   return find_pc_partial_function_gnu_ifunc (pc, name, address, endaddr, NULL);
 }
 
+/* See symtab.h.  */
+
+struct type *
+find_gnu_ifunc_target_type (CORE_ADDR resolver_funaddr)
+{
+  /* See if we can figure out the function's return type from the type
+     that the resolver returns.  */
+  symbol *sym = find_pc_function (resolver_funaddr);
+  if (sym != NULL
+      && SYMBOL_CLASS (sym) == LOC_BLOCK
+      && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == resolver_funaddr)
+    {
+      struct type *resolver_type = SYMBOL_TYPE (sym);
+      if (TYPE_CODE (resolver_type) == TYPE_CODE_FUNC)
+	{
+	  /* Get the return type of the resolver.  */
+	  struct type *resolver_ret_type
+	    = check_typedef (TYPE_TARGET_TYPE (resolver_type));
+
+	  /* If we found a pointer to function, then the resolved type
+	     is the type of the pointed-to function.  */
+	  if (TYPE_CODE (resolver_ret_type) == TYPE_CODE_PTR)
+	    {
+	      struct type *resolved_type
+		= TYPE_TARGET_TYPE (resolver_ret_type);
+	      if (TYPE_CODE (check_typedef (resolved_type)) == TYPE_CODE_FUNC)
+		return resolved_type;
+	    }
+	}
+    }
+
+  return NULL;
+}
+
 /* Return the innermost stack frame that is executing inside of BLOCK and is
    at least as old as the selected frame. Return NULL if there is no
    such frame.  If BLOCK is NULL, just return NULL.  */
diff --git a/gdb/eval.c b/gdb/eval.c
index 021503e9dd9..90f8c7d8b32 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -735,17 +735,13 @@  value *
 evaluate_var_msym_value (enum noside noside,
 			 struct objfile *objfile, minimal_symbol *msymbol)
 {
-  if (noside == EVAL_AVOID_SIDE_EFFECTS)
-    {
-      type *the_type = find_minsym_type_and_address (msymbol, objfile, NULL);
-      return value_zero (the_type, not_lval);
-    }
+  CORE_ADDR address;
+  type *the_type = find_minsym_type_and_address (msymbol, objfile, &address);
+
+  if (noside == EVAL_AVOID_SIDE_EFFECTS && !TYPE_GNU_IFUNC (the_type))
+    return value_zero (the_type, not_lval);
   else
-    {
-      CORE_ADDR address;
-      type *the_type = find_minsym_type_and_address (msymbol, objfile, &address);
-      return value_at_lazy (the_type, address);
-    }
+    return value_at_lazy (the_type, address);
 }
 
 /* Helper for returning a value when handling EVAL_SKIP.  */
@@ -798,6 +794,15 @@  eval_call (expression *exp, enum noside noside,
       else if (TYPE_CODE (ftype) == TYPE_CODE_FUNC
 	       || TYPE_CODE (ftype) == TYPE_CODE_METHOD)
 	{
+	  if (TYPE_GNU_IFUNC (ftype))
+	    {
+	      CORE_ADDR address = value_address (argvec[0]);
+	      type *resolved_type = find_gnu_ifunc_target_type (address);
+
+	      if (resolved_type != NULL)
+		ftype = resolved_type;
+	    }
+
 	  type *return_type = TYPE_TARGET_TYPE (ftype);
 
 	  if (return_type == NULL)
diff --git a/gdb/gdbtypes.c b/gdb/gdbtypes.c
index b3a037971e0..2efd1264ff8 100644
--- a/gdb/gdbtypes.c
+++ b/gdb/gdbtypes.c
@@ -5443,10 +5443,6 @@  objfile_type (struct objfile *objfile)
   objfile_type->nodebug_text_gnu_ifunc_symbol
     = init_type (objfile, TYPE_CODE_FUNC, TARGET_CHAR_BIT,
 		 "<text gnu-indirect-function variable, no debug info>");
-  /* Ifunc resolvers return a function address.  */
-  TYPE_TARGET_TYPE (objfile_type->nodebug_text_gnu_ifunc_symbol)
-    = init_integer_type (objfile, gdbarch_addr_bit (gdbarch), 1,
-			 "__IFUNC_RESOLVER_RET");
   TYPE_GNU_IFUNC (objfile_type->nodebug_text_gnu_ifunc_symbol) = 1;
   objfile_type->nodebug_got_plt_symbol
     = init_pointer_type (objfile, gdbarch_addr_bit (gdbarch),
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 9f02674bdf2..d89d8ca7e8c 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -229,26 +229,26 @@  value_arg_coerce (struct gdbarch *gdbarch, struct value *arg,
   return value_cast (type, arg);
 }
 
-/* Return the return type of a function with its first instruction exactly at
+/* Return the type of a function with its first instruction exactly at
    the PC address.  Return NULL otherwise.  */
 
 static struct type *
-find_function_return_type (CORE_ADDR pc)
+find_function_type (CORE_ADDR pc)
 {
   struct symbol *sym = find_pc_function (pc);
 
-  if (sym != NULL && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == pc
-      && SYMBOL_TYPE (sym) != NULL)
-    return TYPE_TARGET_TYPE (SYMBOL_TYPE (sym));
+  if (sym != NULL && BLOCK_START (SYMBOL_BLOCK_VALUE (sym)) == pc)
+    return SYMBOL_TYPE (sym);
 
   return NULL;
 }
 
-/* Determine a function's address and its return type from its value.
-   Calls error() if the function is not valid for calling.  */
+/* See infcall.h.  */
 
 CORE_ADDR
-find_function_addr (struct value *function, struct type **retval_type)
+find_function_addr (struct value *function,
+		    struct type **retval_type,
+		    struct type **function_type)
 {
   struct type *ftype = check_typedef (value_type (function));
   struct gdbarch *gdbarch = get_type_arch (ftype);
@@ -275,17 +275,33 @@  find_function_addr (struct value *function, struct type **retval_type)
   if (TYPE_CODE (ftype) == TYPE_CODE_FUNC
       || TYPE_CODE (ftype) == TYPE_CODE_METHOD)
     {
-      value_type = TYPE_TARGET_TYPE (ftype);
-
       if (TYPE_GNU_IFUNC (ftype))
 	{
-	  funaddr = gnu_ifunc_resolve_addr (gdbarch, funaddr);
+	  CORE_ADDR resolver_addr = funaddr;
 
-	  /* Skip querying the function symbol if no RETVAL_TYPE has been
-	     asked for.  */
-	  if (retval_type)
-	    value_type = find_function_return_type (funaddr);
+	  /* Resolve the ifunc.  Note this may call the resolver
+	     function in the inferior.  */
+	  funaddr = gnu_ifunc_resolve_addr (gdbarch, resolver_addr);
+
+	  /* Skip querying the function symbol if no RETVAL_TYPE or
+	     FUNCTION_TYPE have been asked for.  */
+	  if (retval_type != NULL || function_type != NULL)
+	    {
+	      type *target_ftype = find_function_type (funaddr);
+	      /* If we don't have debug info for the target function,
+		 see if we can instead extract the target function's
+		 type from the type that the resolver returns.  */
+	      if (target_ftype == NULL)
+		target_ftype = find_gnu_ifunc_target_type (resolver_addr);
+	      if (target_ftype != NULL)
+		{
+		  value_type = TYPE_TARGET_TYPE (check_typedef (target_ftype));
+		  ftype = target_ftype;
+		}
+	    }
 	}
+      else
+	value_type = TYPE_TARGET_TYPE (ftype);
     }
   else if (TYPE_CODE (ftype) == TYPE_CODE_INT)
     {
@@ -320,6 +336,8 @@  find_function_addr (struct value *function, struct type **retval_type)
 
   if (retval_type != NULL)
     *retval_type = value_type;
+  if (function_type != NULL)
+    *function_type = ftype;
   return funaddr + gdbarch_deprecated_function_start_offset (gdbarch);
 }
 
@@ -727,7 +745,6 @@  call_function_by_hand_dummy (struct value *function,
   struct infcall_suspend_state *caller_state;
   CORE_ADDR funaddr;
   CORE_ADDR real_pc;
-  struct type *ftype = check_typedef (value_type (function));
   CORE_ADDR bp_addr;
   struct frame_id dummy_id;
   struct frame_info *frame;
@@ -738,9 +755,6 @@  call_function_by_hand_dummy (struct value *function,
   char name_buf[RAW_FUNCTION_ADDRESS_SIZE];
   bool stack_temporaries = thread_stack_temporaries_enabled_p (inferior_ptid);
 
-  if (TYPE_CODE (ftype) == TYPE_CODE_PTR)
-    ftype = check_typedef (TYPE_TARGET_TYPE (ftype));
-
   if (!target_has_execution)
     noprocess ();
 
@@ -864,7 +878,11 @@  call_function_by_hand_dummy (struct value *function,
       }
   }
 
-  funaddr = find_function_addr (function, &values_type);
+  struct type *ftype = check_typedef (value_type (function));
+  if (TYPE_CODE (ftype) == TYPE_CODE_PTR)
+    ftype = check_typedef (TYPE_TARGET_TYPE (ftype));
+
+  funaddr = find_function_addr (function, &values_type, &ftype);
   if (values_type == NULL)
     values_type = default_return_type;
   if (values_type == NULL)
diff --git a/gdb/infcall.h b/gdb/infcall.h
index a3861fb1bf3..bea1494b50d 100644
--- a/gdb/infcall.h
+++ b/gdb/infcall.h
@@ -25,8 +25,15 @@ 
 struct value;
 struct type;
 
+/* Determine a function's address and its return type from its value.
+   If the function is a GNU ifunc, then return the address of the
+   target function, and set *FUNCTION_TYPE to the target function's
+   type, and *RETVAL_TYPE to the target function's return type..
+   Calls error() if the function is not valid for calling.  */
+
 extern CORE_ADDR find_function_addr (struct value *function, 
-				     struct type **retval_type);
+				     struct type **retval_type,
+				     struct type **function_type = NULL);
 
 /* Perform a function call in the inferior.
 
diff --git a/gdb/symtab.h b/gdb/symtab.h
index f9d52e76979..22b52019ee3 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -1675,6 +1675,12 @@  extern int find_pc_partial_function_gnu_ifunc (CORE_ADDR pc, const char **name,
 extern int find_pc_partial_function (CORE_ADDR, const char **, CORE_ADDR *,
 				     CORE_ADDR *);
 
+/* See if we can figure out the function's actual type from the type
+   that the resolver returns.  RESOLVER_FUNADDR is the address of the
+   ifunc resolver.  */
+
+extern struct type *find_gnu_ifunc_target_type (CORE_ADDR resolver_funaddr);
+
 extern void clear_pc_function_cache (void);
 
 /* Expand symtab containing PC, SECTION if not already expanded.  */
diff --git a/gdb/testsuite/gdb.compile/compile-ifunc.exp b/gdb/testsuite/gdb.compile/compile-ifunc.exp
index ed700e416bb..979e39147e7 100644
--- a/gdb/testsuite/gdb.compile/compile-ifunc.exp
+++ b/gdb/testsuite/gdb.compile/compile-ifunc.exp
@@ -37,7 +37,9 @@  with_test_prefix "nodebug" {
     }
 
     gdb_test "compile code resultvar = gnu_ifunc (10);" \
-	"warning: variable has unknown type; assuming int"
+	[multi_line \
+	     "warning: variable has unknown type; assuming int" \
+	     "warning: function has unknown return type; assuming int"]
 
     gdb_test "p (int) resultvar" " = 11"
 
@@ -52,10 +54,9 @@  with_test_prefix "debug" {
     if ![runto_main] {
 	return -1
     }
-
     # gnu_ifunc (10): error: too many arguments to function 'gnu_ifunc'
-    gdb_test_no_output "compile code resultvar = gnu_ifunc_alias (10);"
-
+    gdb_test "compile code resultvar = gnu_ifunc_alias (10);" \
+	"warning: function has unknown return type; assuming int"
     gdb_test "p resultvar" " = 11"
 
 }