[HN Gopher] Two handy GDB breakpoint tricks
___________________________________________________________________
Two handy GDB breakpoint tricks
Author : goranmoomin
Score : 195 points
Date : 2024-01-28 23:20 UTC (23 hours ago)
(HTM) web link (nullprogram.com)
(TXT) w3m dump (nullprogram.com)
| thrtythreeforty wrote:
| What'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 wrote:
| TFA 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 wrote:
| The 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 wrote:
| I'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 wrote:
| I 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`).
| amluto wrote:
| > 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 wrote:
| I sometimes used a placeholder function call to make it easier to
| place a breakpoint. Of course it shouldn't be ever inlined.
| gregfjohnson wrote:
| Same. 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 wrote:
| These 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 wrote:
| Don'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 wrote:
| Yes, 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 wrote:
| well you can always while (1) {} -- run the program, and
| control-C when you hear the CPU fan :-)
| _flux wrote:
| Just catch infinite-loop
|
| in gdb and you're done!
| speps wrote:
| Would anyone know how that works internally? Sounds very
| useful
| _flux wrote:
| Sadly 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.
| IshKebab wrote:
| QuakeC automatically detected "infinite" loops (actually
| >100k loops). It was very useful!
| JoshTriplett wrote:
| (deleted)
| Lyrex wrote:
| Are you sure? The article makes the point that the nop is
| actually required for this to work in GDB because the
| instruction pointer might otherwise point at an entirely
| different scope.
|
| I have to admit I didn't try it out though. Maybe this
| changed in the meantime and it is not needed anymore.
| tom_ wrote:
| See also this comment in the Unreal Engine code about
| putting a nop in before as well: https://github.com/EpicG
| ames/UnrealEngine/blob/26677ca1b3c97...
| // Q: Why is there a __nop() before __debugbreak()?
| // A: VS' debug engine has a bug where it will silently
| swallow explicit // breakpoint interrupts when
| single-step debugging either line-by-line or //
| over call instructions. This can hide legitimate reasons
| to trap. Asserts // for example, which can appear
| as if the did not fire, leaving a programmer //
| unknowingly debugging an undefined process.
|
| (This comment has been there for at least a couple of
| years, and I don't know if it still applies to the newest
| version of Visual Studio.)
| fulafel wrote:
| What if you're a library and don't want to alter global signal
| handling?
| eqvinox wrote:
| If you're about to fail an assertion (which is semantically
| similar to a SEGV), you probably shouldn't care about messing
| up signal handling anymore ;)
|
| FWIW the actual answer is that libraries should be good at
| reporting / returning errors to the calling application in a
| reasonable manner _instead of tripping assert()s or crashing
| the entire process_. Which makes the question moot because
| ideally the library has few asserts to begin with.
| Peter0x44 wrote:
| Asserts are to make your code more sensitive to defects,
| for example, checking function invariants. Ideally the
| library has _more_ , not less, since they show care and
| thought has gone in. They should never be used to handle
| errors.
|
| Another feature of asserts is that `-DNDEBUG` disables
| them.
| eqvinox wrote:
| Sorry, yeah, I'm just living in a world where asserts
| being misused to "handle errors" is unfortunately rather
| common. My argument is specifically against that kind of
| assertion, I should've been more clear.
| tom_ wrote:
| The annoying aspect of this is that you can end up with a deep
| call stack. For example, on macOS: * frame
| #0: 0x00007ff818dd6fce libsystem_kernel.dylib`__pthread_kill +
| 10 frame #1: 0x00007ff818e0d1ff
| libsystem_pthread.dylib`pthread_kill + 263 frame
| #2: 0x00007ff818d1b2c8 libsystem_c.dylib`raise + 26
| frame #3: 0x0000000100003f6e sigtrap`main + 14
| frame #4: 0x000000010001552e dyld`start + 462
|
| (I vaguely remember it being similar on Linux.)
|
| Regular POSIX programmers might scoff at the very idea that
| this is an issue, but I always found it rather tedious having
| to piss about just to get back to the actual place where the
| break occurred so that you can inspect the state. The whole
| point of an assert is that you don't expect the condition to
| happen, so the last thing I want is to make things any more
| fiddly than necessary when it all goes wrong.
| setheron wrote:
| CosmopolitanC has this cool feature that launches gdb immediately
| and execs it.
| ratmice wrote:
| Instead 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 wrote:
| > 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 wrote:
| this 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
| eqvinox wrote:
| #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/Jumpi...
| Peter0x44 wrote:
| Probably 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 wrote:
| It'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 wrote:
| C++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
| SomeoneFromCA wrote:
| int 3 is actually is a single byte opcode operation, 0xCC. All
| other interrupts are two byte 0xCD <intr_num>.
___________________________________________________________________
(page generated 2024-01-29 23:02 UTC)