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 unlimited
to remove any limit to the core dumpsmkdir $HOME/cores
to create a suitable directory for the coressudo sysctl kern.corefile=$HOME/cores/core.%P
to 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.