pyOCD icon indicating copy to clipboard operation
pyOCD copied to clipboard

Cannot step over software breakpoint

Open maxgerhardt opened this issue 1 year ago • 8 comments

OS: Ubuntu 22.10 PyOCD version: 0.34.3 Hardware: Dialog DA14695 (ARM Cortex-M33)

Description: I'm trying to use PyOCD to control a firmware. The firmware itself places a breakpoint instruction in its code using a

void swd_loop(void) {
        uint32_t last_num = swd_interface.cmd_num;
        uint32_t current_num;
        while (swd_interface.run_swd) {
                current_num = swd_interface.cmd_num;
                if (last_num != current_num) {
                        last_num = current_num;
                        /* Debugger put header in uart_buf, process it */
                        swd_handle_header();
                }

                /* Make sure to enter breakpoint only when debugger is attached */
                if (REG_GETF(CRG_TOP, SYS_STAT_REG, DBG_IS_ACTIVE)) {
                        __BKPT(12);
                }
        }
}

Where __BKPT(12); maps to asm volatile("bkpt #12").

I can load the firmware into the RAM and execute it just fine, waiting until it halts by itself after being started:

        while not self.adapter.halted():  # wait for setup to finish
            time.sleep(0.1)
            print("target state: " + str(self.adapter.target.get_state().name))
            self.adapter.target.halt()
            pc = self.adapter.target.read_core_register("pc")
            print("pc: 0x%X value: %s" % (pc, hex(self.adapter.memory_read(pc, 2))))
            self.adapter.target.resume()
        self.saved_break_addr = self.adapter.target.read_core_register("pc")
        print("Successfully loaded UARTBOOT and halted in swd_loop at %s." % hex(self.saved_break_addr))

Outputs

target state: RUNNING
pc: 0x25A value: 0x3801
target state: RUNNING
pc: 0x25A value: 0x3801
target state: RUNNING
pc: 0x25A value: 0x3801
target state: HALTED
pc: 0x1B0E value: 0xbe0c
Successfully loaded UARTBOOT and halted in swd_loop at 0x1b0e.

As per https://armconverter.com/?disasm&code=0c%20be the decoding of "0c be" (little endian thumb is "bkpt #0xc", so it has reached the breakpoint instruction perfectly fine.

However, it is now eternally stuck there. I cannot get it to step over the breakpoint. I am trying to use the code

        if self.target.is_halted():
            pc = self.target.read_core_register("pc")
            print("breakpoint_clear_all: Halted at " + hex(pc) + " because " + self.target.get_halt_reason().name)
            try:
                self.target.remove_breakpoint(pc)
            except Exception as exc:
                print("Removing breakpoint failed: " + repr(exc))
            self.target.resume() # keep executing

Which again outputs breakpoint_clear_all: Halted at 0x1b0e because BREAKPOINT, but it fails to go any further. Later code which writes something in memory and resumes the core just prints

Waiting for ACK, read: b'\x15' current PC 0x1b0e
Waiting for ACK, read: b'\x15' current PC 0x1b0e
Waiting for ACK, read: b'\x15' current PC 0x1b0e

The fundamental problem I'm seeing is that self.target.remove_breakpoint(pc) calls into the "breakpoint manager". It has its own list of breakpoints that were addded by the API with target.set_breakpoint(). However, since I do not call that (the firmware already has the breakpoint in it), this just does absolutely nothing.

https://github.com/pyocd/pyOCD/blob/0a4b44cad9ff06987ba3699133f82706cc245c48/pyocd/debug/breakpoints/manager.py#L142-L153

In logs:

remove bkpt at 0x1b0e
Tried to remove breakpoint 0x00001b0e that wasn't set

The question is: How can I use pyOCD to step over a bkpt instruction that was already present in the firmware and was not managed by pyOCD?

maxgerhardt avatar Apr 17 '23 17:04 maxgerhardt

And yes I did try target.set_breakpoint(pc); before removing the breakpoint to have it registered in the breakpoint manager, but it had no effect, still stuck.

maxgerhardt avatar Apr 17 '23 17:04 maxgerhardt

You're right, it's an issue. Thanks for all the details. Actually, I think there might be an issue in some cases even when pyocd manages the breakpoint… (Definitely doesn't happen when gdb is controlling things, which is the common case for most people, though of course that doesn't help with the API much!)

The short term fix is basically what pyocd should do: before you run or single step, check whether the instruction at PC is a bkpt, if so then advance the PC past the bkpt and either continue (for run) or stop (single step).

To advance past a potential breakpoint:

pc = target.read_core_register('pc')
is_bkpt = (target.read16(pc) & 0xff00) == 0xbe00 # (mask corrected as below)
if is_bkpt:
    target.write_core_register('pc', pc + 2)

Longer term, the plan is to have a DebugController class that manages the details of higher level debug functions such as this. I don't think this functionality should be in the CortexM target class.

flit avatar Apr 18 '23 15:04 flit

Ha, that indeed works! Just the parenthesis are wrong, you'd want

is_bkpt = (self.target.read16(pc) & 0xbe00) == 0xbe00

and not the and-ing of PC itself.

maxgerhardt avatar Apr 18 '23 16:04 maxgerhardt

Hahh, that's what I get for writing it in the browser… Sorry about that. (Fixed.)

flit avatar Apr 18 '23 21:04 flit

shouldn't it be this?:

 is_bkpt = (self.target.read16(pc) & 0xff00) == 0xbe00
 

But for me it also doesn't step off bkpts when i attatch gdb, an i have to do set $pc += 2 in gdb.

unsanded avatar Jun 24 '23 22:06 unsanded

@unsanded Ah, yeah, that change on the mask is correct. (I really shouldn't write code in the browser… ☹️)

What version of gdb are you using? In every version of gdb I've seen, it will always remove a breakpoint prior to stepping if the PC is currently on a breakpoint.

flit avatar Jun 25 '23 20:06 flit

❯ arm-none-eabi-gdb --version
GNU gdb (GDB) 13.1
...

But i should have been more clear. I was only talking about the bkpt instructions. They cannot be removed.

how openocd does it They put it in armv7m (which seems to be about cortex-m) But yeah, this is something that is specific to arm, and specific to debugging, so that makes it hard to give a place.

unsanded avatar Jun 29 '23 12:06 unsanded

Oh, so bkpt instructions inserted in the original code. Yeah, obviously gdb can't remove those. 😉

In any case, pyocd definitely needs to handle this for both sw breakpoints and bkpt in original code.

flit avatar Jun 30 '23 18:06 flit