RFC: Program Breakpoints (was: [RFC] stepping over permanent breakpoint)
Ross Morley
ross@tensilica.com
Mon Mar 23 16:50:00 GMT 2009
It was interesting to see the matter of skipping permanent breakpoints
come up on this list. This is very similar to an issue we dealt with
a couple of years ago with Xtensa and remote targets. Our solution
has been stable for at least two years, but has not yet been prepared
for submission because I've had higher priorities (and at that time we
were way out of sync with mainline GDB). The issue is actually more
complicated than the forgoing discussion suggests.
I'd like to discuss it here and present our solution. I'll attach a
patch for our current solution relative to GDB 6.8, but I do propose
to revise it to improve some areas based on our experience (in the
next few weeks) - I'll discuss that toward the end of this post.
The Problem
-----------
When the target program hits a breakpoint of any kind, GDB receives
a SIGTRAP event. As Aleksander pointed out, if the PC is not in the
breakpoint table GDB stops because it doesn't know what to do with
this event. In fact it doesn't always stop. When GDB is stepping
(for example when a software watchpoint is set) it sees the SIGTRAP
as a normal step event, so it continues stepping. If the break
instruction didn't increment the PC, it keeps executing the same
break instruction forever. This is very frustrating for users (who
reported this as a bug) because when you have a s/w watchpoint and
are stepping, execution is expected to be slow, so you wait a long
time before you realize it's hung.
Terminology
-----------
I use the term "program breakpoint" to denote a break instruction that
is part of the program, that GDB did not plant nor know about. There is
a feature called "permanent breakpoint" that is a little different in
that GDB knows about it (it's in the breakpoint table). I didn't want to
use the same term for something different (this is open to discussion).
As far as I can tell, only the HPPA arch uses permanent breakpoints.
It would be good to reconcile the two, but I haven't attempted that.
The Solution
------------
The first part is to recognize when you hit a program break.
The second is to step over it when you want to resume.
Note that the instruction that represents a program break is entirely
arch-specific, and need not be the same one GDB plants for breakpoints.
There might be several instructions or variations.
One solution we rejected was to have GDB probe the code at the PC
(if not at a known planted breakpoint). There are two problems:
1. GDB has to know which instructions to look for.
2. GDB has to probe memory every instruction when stepping, which
can seriously hurt performance when there are s/w watchpoints
(especially with remote targets).
We decided it was best to have the target agent communicate that
it is signalling a breakpoint. Note that the target agent might
have no idea whether the break was planted by GDB, so it's up to
GDB to distinguish by looking up the PC in its breakpoint table.
Currently only a SIGTRAP is communicated, and GDB then looks up the
PC in its breakpoint table to see if it hit a planted breakpoint.
Otherwise, if stepping then GDB assumes it's a single-step trap,
else GDB gives up and stops without explanation.
For Xtensa we use only the remote protocol. The target signals a "T"
stop-reply packet with a trap signal number. It does this whether
it hit a break or was stepping. I needed to distinguish the cases.
There are already extensions to the "T" packet (eg. "watch", "awatch",
"rwatch", "thread"). I added another:
TAApbreak:kind
where the suffix "kind" is a number that gives arch-specific info
about the type of breakpoint, including its size (for skipping).
A target interface function and macro is defined so the higher
levels of GDB can find out whether it was stopped by a program
breakpoint (as opposed to a normal GDB planted breakpoint).
STOPPED_BY_PROGRAM_BREAKPOINT(k) returns true or false, and
writes the "kind" argument into (int *) k.
Some questions arise which I'm sure are hitting your neurons about
now. I'll address some of them before I go on.
- What about target agents that don't (yet) implement the extension?
Behavior is exactly as before: GDB will just treat the SIGTRAP
as either a step or an unknown event.
- Why "pbreak"?
Any extension that starts with letters a-f requires a special
case in the parser in remote.c which thinks it's a hex number.
There is already a special case to handle "awatch". I wanted
to avoid that (it turns out the parser also treats "p" special
for currthread, so I will propose using "TAAtrap" - later).
- What about non-remote targets?
The target agent would have to communicate the information
another way. If not, GDB behavior is exactly as before. I'm not
sure whether this problem even exists for non-remote targets
since I haven't looked into that. We define a target interface
function and macro STOPPED_BY_PROGRAM_BREAKPOINT(k) which
currently returns 0 (false) for non-remote targets.
- Why not use a special signal instead of a remote protocol extension?
There is no clear signal that is safe to use. GDB uses just
SIGTRAP and SIGINT, and already overloads SIGTRAP for most things.
The use of a remote protocol extension to disambiguate is well
precedented.
- Isn't the size of a breakpoint constant for a given arch?
No. There can be several kinds used for different reasons.
There can be several encodings on a single break instruction
(Xtensa has the normal 3 byte and a "density" 2 byte encoding).
- Can't GDB figure out the size of the break by itself?
Of course GDB could disassemble the instruction. But that needs
another remote protocol exchange to get the code. Why not have
the debug agent just pass it along with the signal?
Having discovered that it stopped for a program breakpoint, GDB
is able to report the fact to both the CLI and MI. I also defined
a gdbarch function to report the meaning of "kind" in an arch-
specific way. This improves the debugging experience when the
target uses different kinds of breakpoints to denote different
reasons for stopping. (However, in the interest of simplicity,
my revised proposal won't report all that - it is anyway obvious
from a disassembly if not a symbol associated with the PC; GDB
itself only needs to know the size to skip the breakpoint).
When the user elects to continue after a program breakpoint,
GDB increments the PC by the size of the break that was reported
in the "kind" argument. A gdbarch function handles this. Of
course it might do nothing if the PC was already advanced.
I haven't discussed all the implementation details here.
Please see the attached 6.8 based patch for the actual code.
Note this patch does not pretend to be ready for submission!
Problems with Current Implementation and Proposed Simplification
----------------------------------------------------------------
1. "TAApbreak" requires a special case in the remote stop-reply
packet parser to differentiate it from {S,T}AA:p<currthread>.
This is really a parser issue, but I'd rather avoid the need
to rewrite it or add another special case. I propose "TAAtrap".
2. The "kind" argument of "TAApbreak:kind" can encode some more
information the user might be interested in, but the cost is
adding two gdbarch functions. Yet the user can figure out the
meaning from a disassembly. The only information GDB itself
really needs is the size, so it can skip the breakpoint.
3. Non-remote targets aren't handled. However that should be
just a matter of adding an implementation to the target
interface, and nothing breaks meanwhile.
4. There are cases in which a target OS or debug agent uses a
break to trigger an exception of some kind, and then unwinds
the stack to before the exception (eg. in the Xtensa ISS).
Then when GDB tries to skip the break, it skips real code.
Proposed Improved, Simplified Solution
--------------------------------------
The remote protocol extension would be:
TAAtrap[:size]
where ":size" is optional and may only be provided to GDB by the
target agent if the PC is in fact pointing to the instruction
that caused the break, and if omitted is taken to be 0.
GDB will skip the break by PC += size (no effect if size is 0).
Note it is not necessary to call gdbarch_breakpoint_from_pc().
The gdbarch functions to extract the size and decipher "kind"
are not needed. The target interface function
STOPPED_BY_PROGRAM_BREAKPOINT(k)
becomes
STOPPED_BY_PROGRAM_BREAKPOINT(size)
where 'size' is an address in which the size is returned.
We might also want to consider calling it "program trap" and
keep the term "breakpoint" for things that GDB knows about.
Comments are welcome. We at Tensilica would like to see this
refined and incorporated into mainline GDB.
This can certainly coexist with permanent breakpoints,
however it is (I think) a bit more general. If the people
who use permanent breakpoints would care to comment,
perhaps we can somehow reconcile these into one feature.
Thanks,
Ross Morley
Tensilca, Inc.
ross@tensilica.com
Aleksandar Ristovski wrote:
> Hello,
>
> When there is a hard-coded breakpoint in code, like in this example
> (for x86):
>
> #include <stdio.h>
>
> int main()
> {
> __asm(" int $0x03\n");
> printf("Hello World\n");
> return 0;
> }
>
> gdb on linux will appear to work correctly.
>
> However, on systems that do not need pc adjustment after break (like
> QNX) gdb will not be able to step over that breakpoint unless user
> explicitly sets a breakpoint on top of it.
>
> I think that in case of linux it is actually working by accident -
> because kernel does not back-up instruction pointer after hard-coded
> breakpoint instruction was executed. Gdb will receive SIGTRAP but will
> not really know why.
>
> Attached patch fixes this for systems where
> gdbarch_decr_pc_after_break (gdbarch) == 0
>
> I am still not sure this is the final fix. Wouldn't it be better if we
> recognized a hard-coded breakpoint as a breakpoint? There would be an
> issue since it is not in the breakpoint list, but maybe we should
> either automatically add it when we encounter it, or perhaps print
> with some "special" number (to make it clear to the user it is not one
> of the user-generated breakpoints).
>
>
> Thanks,
>
> Aleksandar Ristovski
> QNX Software Systems
>
>
>
> ChangeLog for your reference:
>
> * infrun.c (adjust_pc_after_break): On systems with adjusted PC after
> break, make sure hard-coded breakpoint is stepped-over.
>
>------------------------------------------------------------------------
>
>Index: gdb/infrun.c
>===================================================================
>RCS file: /cvs/src/src/gdb/infrun.c,v
>retrieving revision 1.361
>diff -u -p -r1.361 infrun.c
>--- gdb/infrun.c 1 Mar 2009 23:18:36 -0000 1.361
>+++ gdb/infrun.c 16 Mar 2009 17:33:22 -0000
>@@ -2089,14 +2089,29 @@ adjust_pc_after_break (struct execution_
> we have nothing to do. */
> regcache = get_thread_regcache (ecs->ptid);
> gdbarch = get_regcache_arch (regcache);
>- if (gdbarch_decr_pc_after_break (gdbarch) == 0)
>- return;
>
> /* Find the location where (if we've hit a breakpoint) the
> breakpoint would be. */
> breakpoint_pc = regcache_read_pc (regcache)
> - gdbarch_decr_pc_after_break (gdbarch);
>
>+ if (gdbarch_decr_pc_after_break (gdbarch) == 0)
>+ {
>+ /* Check if we have stopped at a permanent breakpoint. */
>+ int len;
>+ const gdb_byte *brk;
>+ gdb_byte *target_mem = alloca(32);
>+
>+ brk = gdbarch_breakpoint_from_pc (gdbarch, &breakpoint_pc, &len);
>+ if (!target_read_memory (breakpoint_pc, target_mem, len)
>+ && memcmp (target_mem, brk, len) == 0)
>+ {
>+ breakpoint_pc += len;
>+ regcache_write_pc (regcache, breakpoint_pc);
>+ }
>+ return;
>+ }
>+
> /* Check whether there actually is a software breakpoint inserted at
> that location.
>
>
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: xtensa_program_bp.patch
Type: text/x-patch
Size: 23862 bytes
Desc: not available
URL: <http://sourceware.org/pipermail/gdb/attachments/20090323/c5f4cd02/attachment.bin>
More information about the Gdb
mailing list