core files on Darwin/macOS
Posted on
Core dumps are seldom overseen as valuable debugging tool, and so I thought to write a very brief refresher.
What actually are core dumps? Core dumps are snapshots of a process image (virtual memory, registers state, ...), usually created when the process terminates abnormally.
When a process misbehaves on macOS, its bad deeds end up being handled by the private CrashReport framework; this is part of the service that manages to show the "Application Whatever has crashed" dialog box.
Among other things, CrashReport generates a - surprise surprise - Crash Report that can be easily accessed via Console.app. By default, system related crash reports are stored in /Library/Logs/DiagnosticReports/ while user/application reports are stored in /Users/<USERNAME>/Library/Logs/DiagnosticReports/.
All this, again, is the default behavior, and the generated crash reports are meant to be human readable.
What if we need something different? Something that could be consumed by a debugger like lldb so that the conditions that led to the crash can be easily investigated?
In such a case we need to enable the generation of the cores by calling ulimit -c unlimited from the terminal. By default core dumps' size is capped to 0, and hence completely disabled.
By default core dumps are saved in /cores which may not be accessible by the user. This brings us to another couple of interesting sysctl tunables: kern.coredump and kern.corefile.
kern.coredump can be either 1 or 0 to enable or disable the generation of core dumps. It defaults to 1.
kern.corefile contains the path of the core file to generate. It defaults to /cores/core.%P (%P is replaced with the PID).
Let's go over this with an example, from the Terminal:
ulimit -c unlimitedto remove any limit to the core dumpsmkdir $HOME/coresto create a suitable directory for the coressudo sysctl kern.corefile=$HOME/cores/core.%Pto set the location of the cores
All these changes are ephemeral.
Now we're ready to experiment with a small test program. For this example I've decided to use Swift and call the source fatal.swift:
import Foundation
func do_something(_ with: Int) {
guard with == 1 else {
fatalError("Oops, something nasty happened")
}
// do something...
print("...")
}
do_something(0)
Let's compile it with swiftc -g fatal.swift and then run it: ./fatal. You'll see something like this:
Fatal error: Oops, something nasty happened: file fatal.swift, line 5
zsh: illegal hardware instruction (core dumped) ./fatal
Of course, the important part here is "core dumped": the core file will be generated in $HOME/cores, ready to be loaded by lldb.