llvm / llvm/llvm-project

LLDB cannot step off of an inferior's embedded trap instruction if there is an LLDB breakpoint at the same location

Open
#180,924 2 comments 0 reactions 0 assignees View on GitHub
lldb
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

https://github.com/llvm/llvm-project/issues/56268 was fixed, but in testing that I found some strange interactions when a breakpoint is placed by LLDB on top of a breakpoint instruction in the inferior program.

# Reproducer Program

```c
int main() {
__builtin_debugtrap();
return 0;
}
```

```
$ ./bin/clang /tmp/test.c -o /tmp/test.o -g
```
```
$ ./bin/llvm-objdump -d /tmp/test.o
<...>
0000000000000714 :
714: d10043ff sub sp, sp, #0x10
718: 2a1f03e0 mov w0, wzr
71c: b9000fff str wzr, [sp, #0xc]
720: d43e0000 brk #0xf000
724: 910043ff add sp, sp, #0x10
728: d65f03c0 ret
```
Note that the `brk` is some way into the `main` function, but with the help of debug info, we know that it's the first instruction after the prologue so that's where `b main` ends up.
```
<...>
.Ltmp1:
.loc 0 2 3 prologue_end // /tmp/test.c:2:3
brk #0xf000
```

# Scenario 1: No LLDB breakpoint

We hit the inferior `brk` and can continue. This is expected behaviour.
```
$ ./bin/lldb /tmp/test.o
(lldb) target create "/tmp/test.o"
Current executable set to '/tmp/test.o' (aarch64).
(lldb) run
Process 116373 launched: '/tmp/test.o' (aarch64)
Process 116373 stopped
* thread #1, name = 'test.o', stop reason = signal SIGTRAP
frame #0: 0x0000aaaaaaaa0724 test.o`main at test.c:3:3
1 int main() {
2 __builtin_debugtrap();
-> 3 return 0;
4 }
(lldb) c
Process 116373 resuming
Process 116373 exited with status = 0 (0x00000000)
```
# Scenario 2: break on `main`, run, continue from LLDB breakpoint
```
$ ./bin/lldb /tmp/test.o
(lldb) target create "/tmp/test.o"
Current executable set to '/tmp/test.o' (aarch64).
(lldb) b main
Breakpoint 1: where = test.o`main + 12 at test.c:2:3, address = 0x0000000000000720
(lldb) run
Process 116408 launched: '/tmp/test.o' (aarch64)
Process 116408 stopped
* thread #1, name = 'test.o', stop reason = breakpoint 1.1
frame #0: 0x0000aaaaaaaa0720 test.o`main at test.c:2:3
1 int main() {
-> 2 __builtin_debugtrap();
3 return 0;
4 }
```
The initial stop is due to the LLDB placed breakpoint. This is expected.
```
(lldb) c
Process 116408 resuming
Process 116408 stopped
* thread #1, name = 'test.o', stop reason = breakpoint 1.1
frame #0: 0x0000aaaaaaaa0720 test.o`main at test.c:2:3
1 int main() {
-> 2 __builtin_debugtrap();
3 return 0;
4 }
```
Continuing sends us into an endless loop of some kind. We don't detect the inferior's `brk` and step past it. The logs show the breakpoint being hit over and over again. This may be part of the single step logic. Which is usually: disable breakpoint, single step until PC changes, re-enable breakpoint.
```
(lldb) log enable lldb break
(lldb) c
<...>
intern-state Hit breakpoint location: 1.1:
module = /tmp/test.o
compile unit = test.c
function = main
location = /tmp/test.c:2:3
address = 0x0000aaaaaaaa0720
resolved = true
hardware = false
hit count = 37479 <<<<<<<<<<< hit count is constantly increasing
, stopping.

intern-state GDBRemoteCommunicationClient::SendGDBStoppointTypePacket() add at addr = 0xaaaaaaaa0720
Process 116408 stopped
* thread #1, name = 'test.o', stop reason = breakpoint 1.1
frame #0: 0x0000aaaaaaaa0720 test.o`main at test.c:2:3
1 int main() {
-> 2 __builtin_debugtrap();
3 return 0;
4 }
```
Maybe there's a niche argument that this is correct, but in 99% of cases, we'd want to continue past it. So what if we remove the LLDB breakpoint?
```
(lldb) breakpoint delete 1
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) c
Process 116408 resuming
Process 116408 stopped
* thread #1, name = 'test.o', stop reason = trace
frame #0: 0x0000aaaaaaaa0720 test.o`main at test.c:2:3
1 int main() {
-> 2 __builtin_debugtrap();
3 return 0;
4 }
```
I would expect us to hit the inferior's `brk` and know to step over it, but for some reason once we've hit an LLDB breakpoint and continued, that doesn't work even if the breakpoint is removed.

# Scenario 3: Hit the LLDB breakpoint once, remove it, then continue

```
$ ./bin/lldb /tmp/test.o
(lldb) target create "/tmp/test.o"
Current executable set to '/tmp/test.o' (aarch64).
(lldb) b main
Breakpoint 1: where = test.o`main + 12 at test.c:2:3, address = 0x0000000000000720
(lldb) run
Process 116507 launched: '/tmp/test.o' (aarch64)
Process 116507 stopped
* thread #1, name = 'test.o', stop reason = breakpoint 1.1
frame #0: 0x0000aaaaaaaa0720 test.o`main at test.c:2:3
1 int main() {
-> 2 __builtin_debugtrap();
3 return 0;
4 }
(lldb) breakpoint delete 1
1 breakpoints deleted; 0 breakpoint locations disabled.
(lldb) c
Process 116507 resuming
Process 116507 stopped
* thread #1, name = 'test.o', stop reason = signal SIGTRAP
frame #0: 0x0000aaaaaaaa0724 test.o`main at test.c:3:3
1 int main() {
2 __builtin_debugtrap();
-> 3 return 0;
4 }
(lldb) c
Process 116507 resuming
Process 116507 exited with status = 0 (0x00000000)
```
This is working as expected.

It's as if continuing after the first LLDB breakpoint hit without removing the LLDB breakpoint puts LLDB into a state where it will never check if we're on an inferior's `brk` instruction.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.