Bug 31981 - [gdb/tdep, mthumb] thumb_analyze_prologue overshoots
Summary: [gdb/tdep, mthumb] thumb_analyze_prologue overshoots
Status: NEW
Alias: None
Product: gdb
Classification: Unclassified
Component: tdep (show other bugs)
Version: HEAD
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2024-07-17 10:16 UTC by Tom de Vries
Modified: 2024-09-04 08:07 UTC (History)
0 users

See Also:
Host:
Target:
Build:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Tom de Vries 2024-07-17 10:16:29 UTC
I ran into some failures for test-case gdb.dwarf2/dw2-lines.exp on arm-linux with target board unix/mthumb.

The simplest way to reproduce what I think is the root cause for those failures is:
...
$ gcc src/gdb/testsuite/gdb.dwarf2/dw2-lines.c -mthumb
$ gdb -q -batch a.out -ex "b bar"
Breakpoint 1 at 0x4f2
...
with bar disassembly looking like:
...
000004ec <bar>:
 4ec:	b580      	push	{r7, lr}
 4ee:	af00      	add	r7, sp, #0

000004f0 <bar_label>:
 4f0:	2001      	movs	r0, #1
 4f2:	f7ff fff1 	bl	4d8 <foo>
...

The insn at 4f0 has nothing to do with stack setup, and shouldn't be skipped.

Interestingly, with -marm (using arm_analyze_prologue) we do get the desired result:
...
$ gcc src/gdb/testsuite/gdb.dwarf2/dw2-lines.c -marm
$ gdb -q -batch a.out -ex "b bar"
Breakpoint 1 at 0x500
...
while the disassembly is very similar:
...
000004f8 <bar>:
 4f8:	e92d4800 	push	{fp, lr}
 4fc:	e28db004 	add	fp, sp, #4

00000500 <bar_label>:
 500:	e3a00001 	mov	r0, #1
 504:	ebfffff3 	bl	4d8 <foo>
...

With arm_analyze_prologue, the mov insn is unrecognized, and so we hit this code:
...
        {
          /* The optimizer might shove anything into the prologue, if                                          
             we build up cache (cache != NULL) from scanning prologue,                                         
             we just skip what we don't recognize and scan further to                                          
             make cache as complete as possible.  However, if we skip                                          
             prologue, we'll stop immediately on unrecognized                                                  
             instruction.  */
          unrecognized_pc = current_pc;
          if (cache != NULL)
            continue;
          else
            break;
        }
...
and because cache == NULL, we break and return unrecognized_pc == 0x500.

With thumb_analyze_prologue, the movs insn is recognized.  Making it unrecognized:
...
diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
index f36ce631a08..2b244da0b5c 100644
--- a/gdb/arm-tdep.c
+++ b/gdb/arm-tdep.c
@@ -1150,7 +1150,7 @@ thumb_analyze_prologue (struct gdbarch *gdbarch,
        /* Skip register copies, i.e. saves to another register
           instead of the stack.  */
        ;
-      else if ((insn & 0xf800) == 0x2000)      /* movs Rd, #imm */
+      else if (false && (insn & 0xf800) == 0x2000)     /* movs Rd, #imm */
        /* Recognize constant loads; even with small stacks these are necessary
           on Thumb.  */
        regs[bits (insn, 8, 10)] = pv_constant (bits (insn, 0, 7));
...
makes us hit this simple :
...
      else
        {
          /* The optimizer might shove anything into the prologue,                                             
             so we just skip what we don't recognize.  */
          unrecognized_pc = start;
        }
...
which sets unrecognized_pc to 0x4f0 and gets us the desired result:
...
$ gdb -q -batch a.out -ex "b bar"
Breakpoint 1 at 0x4f0
...

It's possible that we need to add a similar:
...
          if (cache == nullptr)
            break;
...
because a subsequent unrecognized instruction would overwrite unrecognized_pc, which is not what we want.

Anyway, the question is whether indeed the movs insn can be part of actual stack setup.

If not, than AFAIU we can get away with:
...
diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
index f36ce631a08..8bafeb2e0f6 100644
--- a/gdb/arm-tdep.c
+++ b/gdb/arm-tdep.c
@@ -1150,7 +1150,7 @@ thumb_analyze_prologue (struct gdbarch *gdbarch,
        /* Skip register copies, i.e. saves to another register
           instead of the stack.  */
        ;
-      else if ((insn & 0xf800) == 0x2000)      /* movs Rd, #imm */
+      else if (cache != nullptr && (insn & 0xf800) == 0x2000)  /* movs Rd, #imm */
        /* Recognize constant loads; even with small stacks these are necessary
           on Thumb.  */
        regs[bits (insn, 8, 10)] = pv_constant (bits (insn, 0, 7));
...

If so then, we need to keep track of insn-after-last-stack-related-insn, and return that.
Comment 1 Sourceware Commits 2024-08-16 12:22:18 UTC
The master branch has been updated by Tom de Vries <vries@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=798bb5cc53edfa13673038b7d76ff09dadfaacb5

commit 798bb5cc53edfa13673038b7d76ff09dadfaacb5
Author: Tom de Vries <tdevries@suse.de>
Date:   Fri Aug 16 14:22:46 2024 +0200

    [gdb/testsuite] Fix gdb.dwarf2/dw2-fixed-point.exp on arm-linux
    
    With test-case gdb.dwarf2/dw2-fixed-point.exp on arm-linux I run into:
    ...
    (gdb) PASS: gdb.dwarf2/dw2-fixed-point.exp: set lang ada
    print pck.fp1_var^M
    $1 = 0.3125^M
    (gdb) FAIL: gdb.dwarf2/dw2-fixed-point.exp: print pck.fp1_var
    ...
    
    The problem is that the thumb prologue analyzer overshoot, setting the
    breakpoint for main after line 49:
    ...
        46  int
        47  main (void)
        48  {
        49    pck__fp1_var++;
    ...
    and consequently we see the value of pck.fp1_var after line 49 instead of
    before line 49.  This is PR tdep/31981.
    
    Work around this by removing line 49 and all similar subsequent lines, which
    turn out to be dead code.
    
    Approved-By: Luis Machado <luis.machado@arm.com>
    
    Tested on arm-linux.
Comment 2 Sourceware Commits 2024-09-04 08:07:23 UTC
The master branch has been updated by Tom de Vries <vries@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=3bd0f5c4d9924be476bdcf6a3d77d7b212d22523

commit 3bd0f5c4d9924be476bdcf6a3d77d7b212d22523
Author: Tom de Vries <tdevries@suse.de>
Date:   Wed Sep 4 10:07:19 2024 +0200

    [gdb/testsuite] Fix gdb.dwarf2/dw2-lines.exp on arm-linux
    
    With test-case gdb.dwarf2/dw2-lines.exp on arm-linux, I run into:
    ...
    (gdb) break bar_label^M
    Breakpoint 2 at 0x4004f6: file dw2-lines.c, line 29.^M
    (gdb) continue^M
    Continuing.^M
    ^M
    Breakpoint 2, bar () at dw2-lines.c:29^M
    29        foo (2);^M
    (gdb) PASS: $exp: cv=2: cdw=32: lv=2: ldw=32: continue to breakpoint: foo \(1\)
    ...
    
    The pass is incorrect because the continue lands at line 29 with "foo (2)"
    instead of line line 27 with "foo (1)".
    
    A minimal version is:
    ...
    $ gdb -q -batch dw2-lines.cv-2-cdw-32-lv-2-ldw-32 -ex "b bar_label"
    Breakpoint 1 at 0x4f6: file dw2-lines.c, line 29.
    ...
    where:
    ...
    000004ec <bar>:
     4ec:   b580            push    {r7, lr}
     4ee:   af00            add     r7, sp, #0
    
    000004f0 <bar_label>:
     4f0:   2001            movs    r0, #1
     4f2:   f7ff fff1       bl      4d8 <foo>
    
    000004f6 <bar_label_2>:
     4f6:   2002            movs    r0, #2
     4f8:   f7ff ffee       bl      4d8 <foo>
    ...
    
    So, how does this happen?  In short:
    - skip_prologue_sal calls arm_skip_prologue with pc == 0x4ec,
    - thumb_analyze_prologue returns 0x4f2
      (overshooting by 1 insn, PR tdep/31981), and
    - skip_prologue_sal decides that we're mid-line, and updates to 0x4f6.
    
    However, this is a test-case about .debug_line info, so why didn't arm_skip_prologue
    use the line info to skip the prologue?
    
    The answer is that the line info starts at bar_label, not at bar.
    
    Fixing that allows us to work around PR tdep/31981.
    
    Likewise in gdb.dwarf2/dw2-line-number-zero.exp.
    
    Instead, add a new test-case gdb.arch/skip-prologue.exp that is dedicated to
    checking quality of architecture-specific prologue analysis, without being
    written in an architecture-specific way.
    
    If fails on arm-linux for both marm and mthumb:
    ...
    FAIL: gdb.arch/skip-prologue.exp: f2: $bp_addr == $prologue_end_addr (skipped too much)
    FAIL: gdb.arch/skip-prologue.exp: f4: $bp_addr == $prologue_end_addr (skipped too much)
    ...
    and passes for:
    - x86_64-linux for {m64,m32}x{-fno-PIE/-no-pie,-fPIE/-pie}
    - aarch64-linux.
    
    Tested on arm-linux.