Bug 28683 - advance/until commands do not handle SIGTRAMP_FRAME on ARM Cortex-M
Summary: advance/until commands do not handle SIGTRAMP_FRAME on ARM Cortex-M
Status: WAITING
Alias: None
Product: gdb
Classification: Unclassified
Component: breakpoints (show other bugs)
Version: HEAD
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-12-10 20:28 UTC by tomas.vanek
Modified: 2023-01-10 22:37 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed: 2022-10-21 00:00:00


Attachments
until_break_command modified for Cortex-M SIGTRAMP_FRAME (794 bytes, patch)
2021-12-14 17:27 UTC, tomas.vanek
Details | Diff
testing code for STM32F4 or QEMU (1.26 KB, text/plain)
2023-01-10 22:30 UTC, tomas.vanek
Details
testing image for STM32F4 or QEMU (29.32 KB, application/x-executable)
2023-01-10 22:32 UTC, tomas.vanek
Details

Note You need to log in before you can comment on or make changes to this bug.
Description tomas.vanek 2021-12-10 20:28:27 UTC
=== environment ====================================================
The bug can be replicated with any of listed GDB versions:

 $ arm-none-eabi-gdb -v
 GNU gdb (GDB) 12.0.50.20211210-git
 ...
 (gdb) show configuration
 This GDB was configured as follows:
   configure --host=i686-pc-linux-gnu --target=arm-none-eabi
             --with-auto-load-dir=$debugdir:$datadir/auto-load
             --with-auto-load-safe-path=$debugdir:$datadir/auto-load
             --with-expat
             --with-gdb-datadir=/usr/local/share/gdb (relocatable)
             --with-jit-reader-dir=/usr/local/lib/gdb (relocatable)
             --without-libunwind-ia64
             --with-lzma
             --without-babeltrace
             --without-intel-pt
             --without-mpfr
             --without-xxhash
             --with-python=/usr
             --with-python-libdir=/usr/lib
             --without-debuginfod
             --without-guile
             --disable-source-highlight
             --with-separate-debug-dir=/usr/local/lib/debug (relocatable)


 $ gdb-multiarch -v
 GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2

 Windows> arm-none-eabi-gdb.exe
 GNU gdb (GNU Tools for Arm Embedded Processors 9-2019-q4-major) 8.3.0.20190709-git

OpenOCD compiled from latest git master used as GDB server.

STM32G474, a Cortex-M4 device. Any Cortex-M device can be used.
A test application (an ordinary blink) with a standard startup is loaded
to the device flash.

=== how to reproduce ===============================================
start GDB server
 $ openocd -f interface/cmsis-dap.cfg -f target/stm32g4x.cfg

start GDB in second terminal
 $ arm-none-eabi-gdb blink.elf

 (gdb) target extended-remote localhost:3333

Reset the device and halt it:
 (gdb) monitor reset halt
 target halted due to debug-request, current mode: Thread
 xPSR: 0x01000000 pc: 0x08000e14 msp: 0x20020000

Step by one instruction to re-read GDB register cache:
 (gdb) stepi

Check registers, LR should be 0xffffffff after reset:
 (gdb) info registers
 ...
 sp             0x20020000          0x20020000
 lr             0xffffffff          -1
 pc             0x8000e16           0x8000e16
 xPSR           0x1000000           16777216
 ...

 (gdb) set debug remote

Issue 'advance' command:
 (gdb) advance main
 [remote] Sending packet: $mfffffffe,2#fa
 [remote] Packet received: 0000
 [remote] Sending packet: $mfffffffe,2#fa
 [remote] Packet received: 0000
 [remote] Sending packet: $m8000526,2#30
 [remote] Packet received: 2046
 [remote] Sending packet: $Z1,8000526,2#7a
 [remote] Packet received: OK
 [remote] packet_ok: Packet Z1 (hardware-breakpoint) is supported
 [remote] Sending packet: $Z0,fffffffe,2#43
 [remote] Packet received: E0E
 [remote] packet_ok: Packet Z0 (software-breakpoint) is supported
 Warning:
 Cannot insert breakpoint 0.
 Cannot access memory at address 0xfffffffe
 
 Command aborted.
 (gdb)


Relevant messages from OpenOCD:
 Error: Failed to read memory at 0xfffff000
 Error: can't add breakpoint: unknown reason

=== expected behavior ==============================================

GDB should not set breakpoint at 0xfffffffe.
The LR reg does not contain an execution address but a magic value.

=== analysis =======================================================

The offending breakpoint should be set at return address to caller function.
The magic value 0xffffffff in LR indicates there is no caller (return
to this address would lock up the CPU).
Similar behaviour of 'advance' and 'until' is observed in an exception handler
routine. In this case LR contains e.g. 0xfffffff1 and GDB tries to se
a breakpoint at 0xfffffff0. It should use a return value stacked
by exception instead.

See gdb/breakpoint.c:11162 void until_break_command (const char *arg, ...)
------------------------------------------
frame = get_selected_frame (NULL);
frame_gdbarch = get_frame_arch (frame);
...
caller_frame_id = frame_unwind_caller_id (frame);
...
if (frame_id_p (caller_frame_id))
  {
    ...
    sal2 = find_pc_line (frame_unwind_caller_pc (frame), 0);
    sal2.pc = frame_unwind_caller_pc (frame);
    caller_gdbarch = frame_unwind_caller_arch (frame);

    breakpoint_up caller_breakpoint
      = set_momentary_breakpoint (caller_gdbarch, sal2,
                                  caller_frame_id, bp_until);
-----------------------------------------
The breakpoint address is directly derived from the current frame
by frame_unwind_caller_pc () without any effort to find out the
type of previous frame, to skip not suitable frames etc...

Compare it to the code used for command 'finish' which honours
SIGTRAMP_FRAME correctly.

gdb/infcmd.c:1706
-----------------------------------------
/* Skip frames for "finish".  */
 
static struct frame_info *
skip_finish_frames (struct frame_info *frame)
{
  struct frame_info *start;
 
  do
    {
      start = frame;
 
      frame = skip_tailcall_frames (frame);
      if (frame == NULL)
        break;
 
      frame = skip_unwritable_frames (frame);
      if (frame == NULL)
        break;
    }
  while (start != frame);
 
  return frame;
}
-----------------------------------------

And the relevant part of finish_command ():

-----------------------------------------
frame = skip_finish_frames (frame);

if (frame == NULL)
  error (_("Cannot find the caller frame."));

finish_forward (sm, frame);
-----------------------------------------

where finish_forward () sets set_momentary_breakpoint()
at address get_frame_pc (frame);

It looks like 'finish' command was adapted to handle SIGTRAMP_FRAMEs
but 'advance' and 'until' were missed.
Comment 1 tomas.vanek 2021-12-10 20:51:13 UTC
At first I thought that the solution proposed for
https://sourceware.org/bugzilla/show_bug.cgi?id=28549
would help here as well.
But no luck, even if LR 0xffffffff is recognised as SIGTRAMP,
'advance'/'until' does not care of it.
Comment 2 tomas.vanek 2021-12-14 17:27:09 UTC
Created attachment 13855 [details]
until_break_command modified for Cortex-M SIGTRAMP_FRAME
Comment 3 Luis Machado 2022-10-13 09:21:55 UTC
Hi Tomas. Is this addressed by your other patch?
Comment 4 tomas.vanek 2022-10-14 11:26:22 UTC
(In reply to Luis Machado from comment #3)
> Hi Tomas. Is this addressed by your other patch?

No, as the comment 1 explains.

I will check if the problem still exist in current dev version.
Comment 5 tomas.vanek 2022-10-14 15:02:13 UTC
GNU gdb (GDB) 13.0.50.20221012-git has the same problem.

The proposed patch depends on the lockup unwinder
https://sourceware.org/bugzilla/attachment.cgi?id=13853

Without it the caller frame is missdetected as arm stub @ 0xfffffffe
and gdb wants to set temporary breakpoint at that address.

(gdb) adv main
[frame] get_prev_frame_always_1: enter
  [frame] get_prev_frame_always_1: this_frame=0
  [frame] get_prev_frame_raw:   -> {level=1,type=<unknown>,unwinder=<unknown>,pc=<unknown>,id=<not computed>,func=<unknown>}
  [frame] compute_frame_id: enter
    [frame] compute_frame_id: fi=1
    [frame] frame_unwind_find_by_frame: enter
      [frame] frame_unwind_find_by_frame: this_frame=1
      [frame] frame_unwind_arch: next_frame=0 -> armv7e-m
      [frame] frame_unwind_try_unwinder: trying unwinder "dummy"
      [frame] frame_unwind_try_unwinder: no
      [frame] frame_unwind_try_unwinder: trying unwinder "dwarf2 tailcall"
      [frame] frame_unwind_try_unwinder: no
      [frame] frame_unwind_try_unwinder: trying unwinder "inline"
      [frame] frame_unwind_register_value: enter
        [frame] frame_unwind_register_value: frame=0, regnum=15(pc)
        [frame] frame_unwind_register_value: enter
          [frame] frame_unwind_register_value: frame=0, regnum=14(lr)
          [frame] frame_id_p: l={stack=<sentinel>,!code,special=0x0000000000000000} -> 1
          [frame] frame_id_p: l={stack=<sentinel>,!code,special=0x0000000000000000} -> 1
          [frame] operator==: l={stack=<sentinel>,!code,special=0x0000000000000000}, r={stack=<sentinel>,!code,special=0x0000000000000000} -> 1
          [frame] frame_unwind_register_value: enter
            [frame] frame_unwind_register_value: frame=-1, regnum=14(lr)
            [frame] frame_unwind_register_value:   -> register=14 bytes=[ffffffff]
          [frame] frame_unwind_register_value: exit
          [frame] frame_id_p: l={stack=<sentinel>,!code,special=0x0000000000000000} -> 1
          [frame] operator==: l={stack=<sentinel>,!code,special=0x0000000000000000}, r={stack=<sentinel>,!code,special=0x0000000000000000} -> 1
          [frame] get_prev_frame_always_1: enter
            [frame] get_prev_frame_always_1: this_frame=-1
            [frame] get_prev_frame_always_1:   -> {level=0,type=NORMAL_FRAME,unwinder="arm prologue",pc=0x4e2,id={stack=0x20010000,code=0x00000000000004e0,!special},func=0x4e0} // cached
          [frame] get_prev_frame_always_1: exit
          [frame] value_fetch_lazy_register: (frame=0, regnum=14(lr), ...) -> register=14 bytes=[ffffffff]
          [frame] frame_unwind_register_value:   -> register=14 bytes=[ffffffff]
        [frame] frame_unwind_register_value: exit
        [frame] frame_unwind_register_value:   -> computed bytes=[feffffff]
      [frame] frame_unwind_register_value: exit
      [frame] frame_unwind_pc: this_frame=0 -> 0xfffffffe
      [frame] frame_unwind_try_unwinder: no
      [frame] frame_unwind_try_unwinder: trying unwinder "jit"
      [frame] frame_unwind_try_unwinder: no
      [frame] frame_unwind_try_unwinder: trying unwinder "arm m exception"
      [frame] frame_unwind_try_unwinder: no
      [frame] frame_unwind_try_unwinder: trying unwinder "arm stub"
      [frame] frame_unwind_try_unwinder: yes
    [frame] frame_unwind_find_by_frame: exit
    [frame] frame_unwind_register_value: enter
      [frame] frame_unwind_register_value: frame=0, regnum=13(sp)
      [frame] frame_unwind_register_value:   -> computed bytes=[00000120]
    [frame] frame_unwind_register_value: exit
    [frame] frame_unwind_register_value: enter
      [frame] frame_unwind_register_value: frame=0, regnum=91(msp)
      [frame] frame_unwind_register_value:   -> computed bytes=[00000120]
    [frame] frame_unwind_register_value: exit
    [frame] frame_unwind_register_value: enter
      [frame] frame_unwind_register_value: frame=0, regnum=92(psp)
      [frame] frame_unwind_register_value:   -> computed bytes=[00000000]
    [frame] frame_unwind_register_value: exit
    [frame] frame_unwind_register_value: enter
      [frame] frame_unwind_register_value: frame=0, regnum=13(sp)
      [frame] frame_unwind_register_value:   -> computed bytes=[00000120]
    [frame] frame_unwind_register_value: exit
    [frame] frame_id_p: l={stack=0x20010000,code=0x00000000fffffffe,!special} -> 1
    [frame] compute_frame_id:   -> {stack=0x20010000,code=0x00000000fffffffe,!special}
  [frame] compute_frame_id: exit
[frame] get_prev_frame_always_1: exit
[frame] frame_id_p: l={stack=0x20010000,code=0x00000000fffffffe,!special} -> 1
[frame] frame_id_p: l={stack=0x20010000,code=0x00000000fffffffe,!special} -> 1
[frame] frame_id_p: l={!stack,!code,!special} -> 0
[frame] frame_id_p: l={!stack,!code,!special} -> 0
[frame] frame_id_p: l={!stack,!code,!special} -> 0
[frame] frame_id_p: l={!stack,!code,!special} -> 0
[frame] frame_id_p: l={!stack,!code,!special} -> 0
[frame] frame_id_p: l={!stack,!code,!special} -> 0
[frame] frame_id_p: l={!stack,!code,!special} -> 0
[frame] frame_id_p: l={!stack,!code,!special} -> 0
Note: automatically using hardware breakpoints for read-only addresses.
[frame] frame_id_p: l={!stack,!code,!special} -> 0
Warning:
Cannot insert breakpoint 0.
Cannot access memory at address 0xfffffffe
 
Command aborted.
Comment 6 tomas.vanek 2022-10-16 15:28:23 UTC
Luis,

I have the patch ready and tested.
As I wrote it depends on
[PATCH v2] gdb/arm: Terminate frame unwinding in M-profile lockup state

Should I wait until the submitted one is merged or is there a faster way?
Comment 7 Luis Machado 2022-10-17 07:14:01 UTC
Hi Tomas,

You can send it to the mailing list and point out the dependency with the other patch. That should allow us to discuss it while the other patch is under review.
Comment 8 Luis Machado 2022-10-21 10:02:14 UTC
Patch under review.
Comment 9 tomas.vanek 2023-01-10 22:30:29 UTC
Created attachment 14571 [details]
testing code for STM32F4 or QEMU
Comment 10 tomas.vanek 2023-01-10 22:32:55 UTC
Created attachment 14572 [details]
testing image for STM32F4 or QEMU

Use:
 qemu-system-arm -M netduinoplus2 -nographic -s -S -kernel tick_blink.elf
Comment 11 tomas.vanek 2023-01-10 22:37:20 UTC
The bug can be replicated with QEMU but unlike OpenOCD QEMU silently ignores setting a breakpoint at invalid address. You should
 set debug remote 1
and watch for Z0 packets in remote protocol log.