Broken prologue skipping with non-returning function

Jonathan Larmour
2008-09-19T14:34:00Z

It seems the prologue analysis on ARM, but very probably more generically,
has problems if GCC is able to optimise away the prologue. I have an
example of this using GCC 4.3.x at -O1 and above.

Here's a testcase:
#include <stdio.h>
const char *args;
const char *saved1, *saved2;

void foo(void)
{
if (args)
{
saved1=saved2=args;
args=NULL;
}
for (;;) /* NOTHING */ ;
}
int main(int argc, char *argv[])
{
args = argv[0];
foo();
return 0;
}
Compile with e.g.:
arm-none-eabi-gcc --save-temps -g -O1 -c foo.c
(and linked as per my OS runtime)

The foo.s contains:
foo:
.LFB10:
.file 1 "foo.c"
.loc 1 6 0
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
.loc 1 7 0
ldr r3, .L5
ldr r2, [r3, #0]
cmp r2, #0
beq .L2
.loc 1 9 0
ldr r3, .L5+4
[snip]

We end up with a .loc for both lines 6 and 7 with no intervening
instructions. gdb's symtab.c:find_pc_sect_line() looks for when the pc
changes to something different and thus ends up returning a symtab_and_line
indicating that the line at that pc is at the 'if' and runs from the start
of the function to the ldr after the .loc 1 9 0.

Thus the outcome as a user if you set a breakpoint at foo, is that it ends
up getting set *after* the conditional branch. Which means setting a
breakpoint at foo is unreliable as you may never hit it. Bad GDB, no biscuit.

But what is the fix? For a start, I envisage this will affect more than
just ARM, so a fix in generic code should by rights be best, although OTOH
some architectures can have calling conventions that guarantee the prologue
is never empty.

How about a patch like the following to the generic symtab.c? That way if
there are two locs in succession for different lines at the same pc, we
stop at the current loc, rather than a later one (after the pc actually
changes). Note that a line of 0 seems to need ignoring as that seems do
indicate the start of the function.

--- symtab.c~ 2008-06-11 23:03:49.000000000 +0100
+++ symtab.c 2008-09-19 15:15:33.000000000 +0100
@@ -2264,7 +2264,7 @@
{
/* Leave prev pointing to the linetable entry for the last line
that started at or before PC. */
- if (item->pc > pc)
+ if (item->pc > pc || (prev && item->pc == pc && prev->pc == pc &&
prev->line > 0 && item->line > prev->line))
break;

prev = item;

I can't help but feel uneasy about the above change though, simply because
this is quite an essential part of GDB and I'm concerned about unintended
consequences. What do people think?

Jifl
--
eCosCentric Limited http://www.eCosCentric.com/ The eCos experts
Barnwell House, Barnwell Drive, Cambridge, UK. Tel: +44 1223 245571
Registered in England and Wales: Reg No 4422071.
------["Si fractum non sit, noli id reficere"]------ Opinions==mine
>>>> Visit us on stand 905 at the Embedded Systems Show 2008 <<<<
>>>> Oct 1-2, NEC, Birmingham, UK http://www.embedded.co.uk <<<<