There comes a time when you want to step through your code, and breakpoints become useful. If you’re a PHP developer you reach out for Xdebug for example or it’s even built into some languages like recent versions of Python.
For many languages, the tool to reach to is gdb (GNU Project Debugger) it supports a wide range of languages including C++ and in my case Rust.
Indeed its as close to “official” debugger as you can get for Rust to the point the official install of rust has a rust-gdb wrapper to prettify the output. So installing it will be easy right?
it’s surely just
brew install gdb
Well that certainly gets you a binary… not a working binary at least not today (Late April 2019)
Compiling from (the right) source
Hopefully, this will get fixed, but installing from Homebrew and even after code signing it (more on that later) resulted in the error:
During startup program terminated with signal SIGTRAP, Trace/breakpoint trap
When trying to actually run within gdb, lot’s of Googling and much head scratching it appears, that the version of source that Homebrew is using has a couple of bugs in it. This isn’t Homebrew fault, rather an issue with upstream.
So compiling from source using the latest 8.2.1 which at the time was the most recent release was my next step, and like Homebrew it also failed (which makes sense as that’s the same source they would be using).
Next up was to go bleeding edge… yep no dice, that didn’t even compile
So after looking through issues, it appears the issue was patched a little bit after 8.2.1 was released which was December 2018. Looking to early 2019 there was a series of 8.2.50 commits, before moving to 8.3 so in the end, I took a punt on the Feb 26th Snapshot the actual snapshot I used was.
From there it was pretty simple:
Important (I think) I have GCC 8 setup by default via Homebrew
brew install gcc
Several folks have mentioned in various threads about compiler failures. As I already use GCC I have no idea if those are historic issues or not.
By default we now end up with gdb installed into:
So that’s the end of the story right….
Creating a code signing certificate
Regardless of how you get a binary, before you can get it working you need to get a certificate and associate it with the binary to allow code signing otherwise you get
Unable to find Mach task port for process-id 1: (os/kern) failure (0x5).
(please check gdb is codesigned - see taskgated(8))
Creating a new certificate isn’t particularly complicated:
- Open up Keychain Access
- Select Certificate Assistant -> Create a Certificate
- give it a name gdb-cert, type is self-signed root and Certificate Type is Code Signing. Finally, tick the Let me override defaults (this is important)
- It will warn you, you are about to create a certificate (because this might have been a shock) click continue
- For serial number and validity length, you can leave as, or change. then click continue
- The other certificate information can be left blank and just click continue
- 2048 Key size and Algorithm RSA which should be defaults just click continue
- Next 3 you can just click Continue
- Specify a Location, make sure you choose the Keychain option of System
With a certificate created, restart the machine. Yes there are other ways to do this, but trust me its just easier to restart and be sure everything is working.
Right with a certificate we need to apply it to the binary, first of create an entitlements file, this is an XML file that shows associated permissions you want to grant the binary.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
With the file saved (in my case as gdb.xml) then the final setup is:
codesign --entitlements gdb.xml -fs gdb-cert /usr/local/bin/gdb
Ok surprisingly at this stage it’s working and you can start using gdb on MacOS congrats.
Ok assuming you found this after some Googling, you might reach the end of the above steps and run gdb think everything is going fine and it hangs with a message something like:
Starting program: /Users/tnash/AppProjects/password/target/debug/password
Note: this version of macOS has System Integrity Protection.
Because `startup-with-shell' is enabled, gdb has worked around this by
caching a copy of your shell. The shell used by "run" is now:
[New Thread 0xc03 of process 27325]
[New Thread 0xe03 of process 27325]
If this is you, then edit your ~/.gdbinit file to not include the line:
set startup-with-shell enable
Chances are like me you added this early on in your trying to get things working, as it was announced in several places as a potential fix. I’m not actually sure why it doesn’t work and it may work for you. I’m slightly suspicious that it might be due to using ZSH but removing the line solved the issue for me, so I dug no further.
While trying to get things working at some point you might have run
sudo DevToolsSecurity -enablein which case I would recommend
sudo DevToolsSecurity -disableas there is no reason to enable it.
Couple of other things to consider, if you haven’t already make sure you uninstalled gdb via Homebrew if you installed that way, or it’s likely to relink at some point causing chaos.
Also, that certificate you generated, will expire…
So you might want to make a note on how you made it and put in your diary to renew or it’s going to confuse the hell out of you next year.
The last gotcha is more of a comment and specific to Rust, when debugging something built from cargo it leaves an almighty mess, with lots of references to missing files and being unable to read symbols. This doesn’t seem to affect anything other than adding to any warnings your code might actually have. Using rustc -g didn’t come with such gumph but also is way less convenient so for now, I’m just going to live with it.
So there we have it, how to install a 30-year-old workhorse tool that has probably been installed on thousands if not millions of machines. Yet due to a combination of MacOS tightened security, some weird rabbit holes caused back with High Sierra and a couple of bugs in the 8.2.1 source has caused more than a little headache for me.