6 ms·
Two handy GDB breakpoint tricks
- thrtythreeforty 3y agoWhat's the equivalent of int3 on an ARM machine? Is there any attempt to provide a header that "does the right thing" on a wide variety of platforms?
- dooglius 3y agoTFA says > As far as I know there is no ARM equivalent compatible with GDB (or even LLDB). The closest instruction, brk #0x1, does not behave as needed. But doesn't elaborate on why
- zyedidia 3y agoThe reason is because the ARM `brk #0x1` instruction doesn't set the exception return address to the next instruction, so the debugger will just return to the breakpoint when it tries to resume, instead of running the next instruction. Recent versions of LLDB will recognize `brk #0xf000` and automatically step over it when returning, but I don't think GDB does this. With GDB you would have to run something manually like `jump *($pc + 4)` to resume at the next instruction. Clang's `__builtin_debugtrap()` will generate `brk #0xf000` automatically.
- zootboy 3y agoI've been using this macro with GCC / GDB for years without running into the issue you're describing: #define DEBUG_BREAK() do{__asm__("BKPT");} while(0) I can continue just fine with it. Granted, this is on the various Cortex M0/M3/M4 chips, so I can't say for sure if it works on any of the bigger, fancier ARMs.
- zyedidia 3y agoI think it's a difference between ARMv8 and ARMv6/7 (I believe BKPT on ARMv6/7 sets the exception return address to `addr(instruction)+4`).
- thrtythreeforty 3y agoFollowup: I found such a header! https://github.com/scottt/debugbreak https://github.com/scottt/debugbreak
- amluto 3y ago> On x86 it inserts an int3 instruction, which fires an interrupt, trapping in the attached debugger, or otherwise abnormally terminating the program. Because it’s an interrupt, it’s expected that the program might continue. In x86 lingo, it’s a trap, and calling it an interrupt misses the point. int3 is a “software interrupt,” but the vector 3 is universally configured by kernels as a “trap” (by setting the appropriate mode in the IDT). So the instruction completes, IP is incremented, and the kernel is notified. FRED tidies this all up. The kernel is notified that the event occurred and is given the length of the offending instruction. > However, regardless of how you get an int3 in your program, GDB does not currently understand it. GDB can’t really do better. If you have: jnz 1f int3 1: [non-error case here] Then all gdb knows is that IP points to 1. The issue may not be as bad with the int3 out of line: jnz 2f 1: [non-error case here] 2: int3 jmp 1b <— GDB sees this Then at least GDB knows what’s up. But that jmp may be omitted if gcc thinks that the instruction after int3 is unreachable. Maybe a future FRED-requiring debug API could report the address of the offending int3 and GDB could use it. But ptrace is awful, gdb is awful, and I’m not convinced this will happen.
- lifthrasiir 3y agoI sometimes used a placeholder function call to make it easier to place a breakpoint. Of course it shouldn't be ever inlined.
- gregfjohnson 3y agoSame. I typically define a function "void bp1(){}" in a common utility file, put "bp1();" where I need a breakpoint, and "b bp1" in gdb. Hacky, sure, but really convenient.
- gumby 3y agoThese actually are quite useful tricks, and simple too. Usually articles with a title like this just find something in the documentation that the author hadn’t known, but I do often read them in the hope they’ll be like this.
- o11c 3y agoDon't mess around with silly tricks like that. Do it the proper way: #include <signal.h> int main() { signal(SIGTRAP, SIG_IGN); raise(SIGTRAP); } When outside a debugger, the `raise`d signal will be ignored (of course, if you want a coredump you can remove the `signal` line). When inside a debugger, requests to ignore `SIGTRAP` have no effect, unlike other signals. That's because this is literally what `SIGTRAP` is for.
- dundarious 3y agoYes, although for usage in an assertion like in TFA, you want SIGTRAP outside of a debugger to cause a core dump, which is the default, so you must not SIG_IGN it. In priority order: - msvc: __debugbreak() - clang: __builtin_debugtrap() - linux (and probably some other posix-ish systems): raise(SIGTRAP) - gcc x86: asm("int3; nop") - otherwise: you're out of luck, just abort()/__builtin_trap()
- buserror 3y agowell you can always while (1) {} -- run the program, and control-C when you hear the CPU fan :-)
- _flux 3y agoJust catch infinite-loop in gdb and you're done!
- speps 3y agoWould anyone know how that works internally? Sounds very useful
- _flux 3y agoSadly it does not work at all, as it was a joke :/. But I did think about how it could work. It could work by peridically sampling the instructions the current thread is running and detect trivial infinite loops that way. With more effort more complicated infinite loops could be detected, but in the end not all, due to the halting problem. edit: Actually maybe halting problem is not (theoretically) involved if you have a lot of memory: take snapshots every after every instruction. Once you take a snapshot you have already taken previously, you have found a loop. However you would still might need to somehow account for (emulate and include in snapshot?) or disallow external IO, such as time of day.
- setheron 3y agoCosmopolitanC has this cool feature that launches gdb immediately and execs it.
- ratmice 3y agoInstead of using labels (e.g. to avoid unused_label) and because labels are most commonly used as the target of a goto. gdb also supports `break -probe foo:bar` when placing some `DTRACE_PROBE(foo, bar)` at the appropriate line. I think this is likely preferable to `break func:label` now.
- matheusmoreira 3y ago> As far as I know there is no ARM equivalent compatible with GDB Sad... This would've made debugging much more ergonomic on my phone.
- sim7c00 3y agothis look at hw/sw breakpoints for arm stuff.. the int3 is a exception provided by the architecture, which coders leverage. so hence its not available on arm, but there's other breakpoint / debug functionality since arm devs also need to debug stuff :> happy hacking! https://interrupt.memfault.com/blog/cortex-m-breakpoints https://interrupt.memfault.com/blog/cortex-m-breakpoints
- eqvinox 3y ago#define assert(c) while (!(c)) __builtin_trap() #define assert(c) while (!(c)) __builtin_unreachable() #define assert(c) while (!(c)) *(volatile int *)0 = 0 > Each […] has the most important property: Immediately halt the program directly on the defect. No, only 2 of these 3 have this property. __builtin_unreachable() tells the compiler that this code path can be assumed to never execute, with the intent to allow the compiler to optimize however it wants based on this assumption. Whether the program halts, goes into an infinite loop, executes "rm -rf /", or starts a nuclear war is up to the compiler. (What will happen in reality is that the compiler deduces that the loop condition will never be true and therefore it can elide the entire loop.) Sadly, other posts by this author have similar issues. P.S.: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Jumping.html#Jumping https://sourceware.org/gdb/current/onlinedocs/gdb.html/Jumpi...
- Peter0x44 3y agoProbably it should be stated explicitly, but this is implied if you add `-fsanitize=undefined`, where you will get a diagnostic if the unreachable code is reached. With `-fsanitize-trap=undefined`, you get a trap instruction there, the compiler won't delete anything. In a "release build" without sanitizers, this also serves as an optimization hint. It's a sensible idea, this snarky comment is unwarranted. Also, code after __builtin_trap() is treated as dead.
- eqvinox 3y agoIt's nice that it works if you adjust your compilation environment like that, but the thing with the "release build" is actually the wrong way around. - in a debug build (assertions enabled) with UBSAN you get a diagnostic or trap, so far so good. - in a debug build without UBSAN, you get… undefined behavior, possibly some optimizations. Optimizations would generally be set to a low level, but that doesn't preclude you from getting UB. - in a release build without assertions, … the assertion would be disabled … so you don't get any optimizations; instead you get proper "DB" for the case that should never happen. It's essentially the wrong way around. If anything this is an argument for converting assertions into this definition in a release build, which might be reasonable in some code bases. (In my main code base, we have an "assume()" macro instead; it is by policy very sparingly used to avoid risks of running into UB.) > It's a sensible idea, this snarky comment is unwarranted. It's not intended to be snarky, just as noted I've run into similar issues with posts by this author. I haven't counted it up but it feels like a 90% rate of being just dangerously half-right about something. (Considering how subjective perception works, 90% subjective is probably 40% actual, but still.) (I probably should've deleted the P.S.; I reworded my post and it was a leftover.)
- humanrebar 3y agoC++26 is slated to include std::breakpoint and a couple related features. I'm expecting we'll see more portable and consistent tech for writing recoverable assertions going forward. https://en.cppreference.com/w/cpp/utility/breakpoint https://en.cppreference.com/w/cpp/utility/breakpoint
- SomeoneFromCA 3y agoint 3 is actually is a single byte opcode operation, 0xCC. All other interrupts are two byte 0xCD <intr_num>.