[patch] Backtrace prints wrong argument value

Luis Machado
2007-04-25T18:54:59Z

[-- Attachment #1: Type: text/plain, Size: 2848 bytes --]

Hi Folks,

GDB sometimes acts in an unexpected way while showing backtrace argument
values in certain optimized binaries. I'll go through the example:

The code used to reproduce this follows:

unsigned * __attribute__((noinline))
start_sequence (unsigned * x, unsigned * y)
{
return (unsigned *)0xdeadbeef;
};

unsigned __attribute__((noinline))
gen_movsd (unsigned * operand0, unsigned * operand1)
{
return *start_sequence(operand0, operand1);
}

int main(void)
{
unsigned x, y;

x = 13;
y = 14;
return (int)gen_movsd (&x, &y);
}

Basically we have two functions, gen_movsd and start_sequence. Since the
argument values for those two functions are not really used, GCC
optimizes some of them.

Loading the binary on GDB, we step through the code until we reach the
"start_sequence" function. It shows the following frame info:

#0 - start_sequence (x=<value optimized out>, y=0xfffff9b1b34)

"x" was originally passed on R3, and it's now lost since the same
register was used for the return value (0xdeadbeef) of the
"start_sequence" function. This is OK.

If we call a backtrace on GDB, that's what we have:

#0 - start_sequence (x=<value optimized out>, y=0xfffff9b1b34)
#1 - gen_movsd (operand0=0xdeadbeef, operand1=0xfffff9b1b34)

Notice that on frame #1, "operand0" has a "0xdeadbeef" value, which
happens to be the return value from the "start_sequence" function from
frame #0. This is clearly incorrect.

Stepping a little bit further through the code until we exit from
"start_sequence" and fall back into "gen_movsd", we have the following
as the frame info:

#0 - gen_movsd (operand0=<value optimized out>, operand1=<value
optimized out>)

This last frame info is just correct, since both values aren't available
anymore.

What is causing this incorrect value to be printed on frame levels above
0 is an adjustment to the PC on the "frame_unwind_address_in_block"
function. The purpose of this adjustment to PC is to make it point to
the branch instruction rather than the instruction right after the
branch instruction ( this is achieved with a --pc decrement).

This breaks the code that finds the right DWARF expression in the
location lists for each argument due to an off-by-1 error. Thus, GDB
selects the incorrect DWARF expression and shows the wrong value on
backtrace.

This patch fixes the problem by adjusting the PC to prevent the off-by-1
problem, thus making GDB select the correct DWARF expression.

Another point is that the PC decrement operation on
"frame_unwind_address_in_block", as it is now, doesn't make sense if
the branch instruction is longer than 1 byte. To make sure that PC
points to the branch instruction we must go back the length of the last
instruction executed. On ppc this length is fixed, but on other
architectures it is not.

Comments and suggestions are welcome.

Best regards,
Luis

[-- Attachment #2: wrong_backtrace_argument.diff --]
[-- Type: text/x-patch, Size: 1292 bytes --]

2007-04-25 Luis Machado <luisgpm@br.ibm.com>

* dwarf2loc.c (loclist_read_variable): Corrected the PC value
before calling the find_location_expression function

Index: gdb/dwarf2loc.c
===================================================================
--- gdb.orig/dwarf2loc.c 2007-04-25 10:29:05.000000000 -0700
+++ gdb/dwarf2loc.c 2007-04-25 11:04:55.000000000 -0700
@@ -528,9 +528,20 @@
gdb_byte *data;
size_t size;

+/* If the current Frame Level is 0, we pass the unmodified PC, but if the
+ Frame Level is greater than 0, we must adjust the PC by 1 since it was
+ decremented by get_frame_address_in_block() to be more intuitive to the
+ user (to point at the branch instruction rather than the instruction right
+ after the branch. Due to this adjusting, the Location List has an off-by-1
+ error when checking the PC. This could cause wrong argument values to be
+ shown. */
+
data = find_location_expression (dlbaton, &size,
- frame ? get_frame_address_in_block (frame)
- : 0);
+ frame ?
+ (frame_relative_level(frame)? (get_frame_address_in_block(frame) + sizeof(CORE_ADDR))
+ : get_frame_address_in_block(frame))
+ : 0);
+
if (data == NULL)
{
val = allocate_value (SYMBOL_TYPE (symbol));