Add a TRY_CATCH to get_prev_frame_always to better manage errors during unwind.

https://sourceware.org/ml/gdb-patches/2014-05/msg00737.html

Currently a MEMORY_ERROR raised during unwinding a frame will cause the
unwind to stop with an error message, for example:

  (gdb) bt
  #0  breakpt () at amd64-invalid-stack-middle.c:27
  #1  0x00000000004008f0 in func5 () at amd64-invalid-stack-middle.c:32
  #2  0x0000000000400900 in func4 () at amd64-invalid-stack-middle.c:38
  #3  0x0000000000400910 in func3 () at amd64-invalid-stack-middle.c:44
  #4  0x0000000000400928 in func2 () at amd64-invalid-stack-middle.c:50
  Cannot access memory at address 0x2aaaaaab0000

However, frame #4 is marked as being the end of the stack unwind, so a
subsequent request for the backtrace looses the error message, such as:

  (gdb) bt
  #0  breakpt () at amd64-invalid-stack-middle.c:27
  #1  0x00000000004008f0 in func5 () at amd64-invalid-stack-middle.c:32
  #2  0x0000000000400900 in func4 () at amd64-invalid-stack-middle.c:38
  #3  0x0000000000400910 in func3 () at amd64-invalid-stack-middle.c:44
  #4  0x0000000000400928 in func2 () at amd64-invalid-stack-middle.c:50

When fetching the backtrace, or requesting the stack depth using the MI
interface the situation is even worse, the first time a request is made
we encounter the memory error and so the MI returns an error instead of
the correct result, for example:

  (gdb) -stack-info-depth
  ^error,msg="Cannot access memory at address 0x2aaaaaab0000"

Or,

  (gdb) -stack-list-frames
  ^error,msg="Cannot access memory at address 0x2aaaaaab0000"

However, once one of these commands has been used gdb has, internally,
walked the stack and figured that out that frame #4 is the bottom of the
stack, so the second time an MI command is tried you'll get the "expected"
result:

  (gdb) -stack-info-depth
  ^done,depth="5"

Or,

  (gdb) -stack-list-frames
  ^done,stack=[frame={level="0", .. snip lots .. }]

After this patch the MEMORY_ERROR encountered during the frame unwind is
attached to frame #4 as the stop reason, and is displayed in the CLI each
time the backtrace is requested.  In the MI, catching the error means that
the "expected" result is returned the first time the MI command is issued.
So, from the CLI the results of the backtrace will be:

  (gdb) bt
  #0  breakpt () at amd64-invalid-stack-middle.c:27
  #1  0x00000000004008f0 in func5 () at amd64-invalid-stack-middle.c:32
  #2  0x0000000000400900 in func4 () at amd64-invalid-stack-middle.c:38
  #3  0x0000000000400910 in func3 () at amd64-invalid-stack-middle.c:44
  #4  0x0000000000400928 in func2 () at amd64-invalid-stack-middle.c:50
  Backtrace stopped: Cannot access memory at address 0x2aaaaaab0000

Each and every time that the backtrace is requested, while the MI output
will similarly be consistently:

  (gdb) -stack-info-depth
  ^done,depth="5"

Or,

  (gdb) -stack-list-frames
  ^done,stack=[frame={level="0", .. snip lots .. }]

gdb/ChangeLog:

	* frame.c (struct frame_info): Add stop_string field.
	(get_prev_frame_always_1): Renamed from get_prev_frame_always.
	(get_prev_frame_always): Old content moved into
	get_prev_frame_always_1.  Call get_prev_frame_always_1 inside
	TRY_CATCH, handle MEMORY_ERROR exceptions.
	(frame_stop_reason_string): New function definition.
	* frame.h (unwind_stop_reason_to_string): Extend comment to
	mention frame_stop_reason_string.
	(frame_stop_reason_string): New function declaration.
	* stack.c (frame_info): Switch to frame_stop_reason_string.
	(backtrace_command_1): Switch to frame_stop_reason_string.
	* unwind_stop_reason.def: Add UNWIND_MEMORY_ERROR.
	(LAST_ENTRY): Changed to UNWIND_MEMORY_ERROR.
	* guile/lib/gdb.scm: Add FRAME_UNWIND_MEMORY_ERROR to export list.

gdb/doc/ChangeLog:

	* guile.texi (Frames In Guile): Mention FRAME_UNWIND_MEMORY_ERROR.
	* python.texi (Frames In Python): Mention
	gdb.FRAME_UNWIND_MEMORY_ERROR.

gdb/testsuite/ChangeLog:

	* gdb.arch/amd64-invalid-stack-middle.exp: Update expected results.
	* gdb.arch/amd64-invalid-stack-top.exp: Likewise.
This commit is contained in:
Andrew Burgess
2014-05-28 23:34:43 +01:00
parent 70e38b8e98
commit 53e8a631a0
12 changed files with 141 additions and 90 deletions

View File

@ -145,6 +145,10 @@ struct frame_info
/* The reason why we could not set PREV, or UNWIND_NO_REASON if we
could. Only valid when PREV_P is set. */
enum unwind_stop_reason stop_reason;
/* A frame specific string describing the STOP_REASON in more detail.
Only valid when PREV_P is set, but even then may still be NULL. */
const char *stop_string;
};
/* A frame stash used to speed up frame lookups. Create a hash table
@ -1798,14 +1802,12 @@ get_prev_frame_if_no_cycle (struct frame_info *this_frame)
return prev_frame;
}
/* Return a "struct frame_info" corresponding to the frame that called
THIS_FRAME. Returns NULL if there is no such frame.
/* Helper function for get_prev_frame_always, this is called inside a
TRY_CATCH block. Return the frame that called THIS_FRAME or NULL if
there is no such frame. This may throw an exception. */
Unlike get_prev_frame, this function always tries to unwind the
frame. */
struct frame_info *
get_prev_frame_always (struct frame_info *this_frame)
static struct frame_info *
get_prev_frame_always_1 (struct frame_info *this_frame)
{
struct gdbarch *gdbarch;
@ -1955,6 +1957,50 @@ get_prev_frame_always (struct frame_info *this_frame)
return get_prev_frame_if_no_cycle (this_frame);
}
/* Return a "struct frame_info" corresponding to the frame that called
THIS_FRAME. Returns NULL if there is no such frame.
Unlike get_prev_frame, this function always tries to unwind the
frame. */
struct frame_info *
get_prev_frame_always (struct frame_info *this_frame)
{
volatile struct gdb_exception ex;
struct frame_info *prev_frame = NULL;
TRY_CATCH (ex, RETURN_MASK_ERROR)
{
prev_frame = get_prev_frame_always_1 (this_frame);
}
if (ex.reason < 0)
{
if (ex.error == MEMORY_ERROR)
{
this_frame->stop_reason = UNWIND_MEMORY_ERROR;
if (ex.message != NULL)
{
char *stop_string;
size_t size;
/* The error needs to live as long as the frame does.
Allocate using stack local STOP_STRING then assign the
pointer to the frame, this allows the STOP_STRING on the
frame to be of type 'const char *'. */
size = strlen (ex.message) + 1;
stop_string = frame_obstack_zalloc (size);
memcpy (stop_string, ex.message, size);
this_frame->stop_string = stop_string;
}
prev_frame = NULL;
}
else
throw_exception (ex);
}
return prev_frame;
}
/* Construct a new "struct frame_info" and link it previous to
this_frame. */
@ -2576,6 +2622,20 @@ unwind_stop_reason_to_string (enum unwind_stop_reason reason)
}
}
const char *
frame_stop_reason_string (struct frame_info *fi)
{
gdb_assert (fi->prev_p);
gdb_assert (fi->prev == NULL);
/* Return the specific string if we have one. */
if (fi->stop_string != NULL)
return fi->stop_string;
/* Return the generic string if we have nothing better. */
return unwind_stop_reason_to_string (fi->stop_reason);
}
/* Return the enum symbol name of REASON as a string, to use in debug
output. */