Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot step over software breakpoint #1537

Open
maxgerhardt opened this issue Apr 17, 2023 · 8 comments · May be fixed by #1604
Open

Cannot step over software breakpoint #1537

maxgerhardt opened this issue Apr 17, 2023 · 8 comments · May be fixed by #1604

Comments

@maxgerhardt
Copy link

maxgerhardt commented Apr 17, 2023

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.

def remove_breakpoint(self, addr: int) -> None:
"""@brief Remove a breakpoint at a specific location."""
try:
LOG.debug("remove bkpt at 0x%x", addr)
# Clear Thumb bit in case it is set.
addr = addr & ~1
# Remove bp from dict.
del self._updated_breakpoints[addr]
except KeyError:
LOG.debug("Tried to remove breakpoint 0x%08x that wasn't set" % addr)

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
Copy link
Author

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.

@flit
Copy link
Member

flit commented Apr 18, 2023

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.

@maxgerhardt
Copy link
Author

maxgerhardt commented Apr 18, 2023

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.

@flit
Copy link
Member

flit commented Apr 18, 2023

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

@unsanded
Copy link
Contributor

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.

@flit
Copy link
Member

flit commented Jun 25, 2023

@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.

@unsanded
Copy link
Contributor

❯ 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.

@flit
Copy link
Member

flit commented Jun 30, 2023

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 added a commit to flit/pyOCD that referenced this issue Jul 8, 2023
Resuming over software breakpoints is not implemented yet because it
requires an event when the core is halted which is currently not
available outside the gdbserver.

For pyocd#1537
flit added a commit to flit/pyOCD that referenced this issue Jul 15, 2023
Resuming over software breakpoints is not implemented yet because it
requires an event when the core is halted which is currently not
available outside the gdbserver.

For pyocd#1537
flit added a commit to flit/pyOCD that referenced this issue Jul 29, 2023
Resuming over software breakpoints is not implemented yet because it
requires an event when the core is halted which is currently not
available outside the gdbserver.

For pyocd#1537
flit added a commit to flit/pyOCD that referenced this issue Aug 5, 2023
Resuming over software breakpoints is not implemented yet because it
requires an event when the core is halted which is currently not
available outside the gdbserver.

For pyocd#1537
@flit flit linked a pull request Aug 5, 2023 that will close this issue
flit added a commit to flit/pyOCD that referenced this issue Aug 9, 2023
Resuming over software breakpoints is not implemented yet because it
requires an event when the core is halted which is currently not
available outside the gdbserver.

For pyocd#1537
flit added a commit to flit/pyOCD that referenced this issue Nov 2, 2023
Resuming over software breakpoints is not implemented yet because it
requires an event when the core is halted which is currently not
available outside the gdbserver.

For pyocd#1537
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants