Skip to content

Commit 4f3a462

Browse files
al45taircthielen
andauthored
Add a blog post entry about the new on-crash backtracing support (#432)
* Add a blog post entry about on-crash backtraces. Describe the new on-crash backtrace support in Swift 5.9. * Added a section about concurrency support. * Add detail on static linking, frame pointers and installation. Added some more details for people interested in statically linked binaries, as well as some notes about how the runtime locates the backtracer. * More editing. Removed some sections that seem like they don't belong here, and re-worded some other parts to make them clearer. * Update the publication date. * Update following review comments. * More changes following review comments. * More review feedback. * Fix a quote. * Editorial pass * Update colors for the terminal output. Add a set of colors to `_light.scss` and `_dark.scss`. Use them. --------- Co-authored-by: Christopher Thielen <[email protected]>
1 parent 905cf86 commit 4f3a462

File tree

4 files changed

+346
-0
lines changed

4 files changed

+346
-0
lines changed

_data/authors.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,13 @@ erickinnear:
391391
github: ekinnear
392392
about: "Eric Kinnear is a member of the team at Apple working on Foundation networking, HTTP, and other internet technologies."
393393

394+
al45tair:
395+
name: Alastair Houghton
396+
397+
gravatar: 4481fa93eb8710047ed4e11a3ac533c8
398+
github: al45tair
399+
about: "Alastair Houghton works on the Swift and Objective-C language runtimes at Apple."
400+
394401
FranzBusch:
395402
name: Franz Busch
396403
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
---
2+
layout: post
3+
published: true
4+
date: 2023-11-08 10:00:00
5+
title: On-Crash Backtraces in Swift
6+
author: [al45tair]
7+
---
8+
9+
The new Swift 5.9 release contains a number of helpful, new features for debugging code, including an out-of-process, interactive
10+
crash handler to inspect crashes in real time, the ability to trigger
11+
the debugger for just-in-time debugging, along with concurrency-aware
12+
backtracing to make it easier to understand control flow in a program
13+
that uses structured concurrency.
14+
15+
### Out-of-Process Crash Handling
16+
17+
Prior to Swift 5.9, all you would get when your program fails is a message
18+
from the parent process (often the shell) telling you that the child
19+
process crashed:
20+
21+
<div class="language-plaintext highlighter-rouge"><div class="highlight">
22+
<pre class="terminal" style='color:var(--color-term-normal); background:var(--color-term-background)'>
23+
<span class='shell'>$ </span><span class='cmd'>./crash</span>
24+
I&#39;m going to crash now
25+
zsh: segmentation fault ./crash
26+
</pre>
27+
</div></div>
28+
29+
On Apple platforms or on Windows, you could look at the crash logs
30+
captured by the operating system's built-in crash reporter, but on
31+
Linux that's typically all you had to go on.
32+
33+
Now, instead of the opaque message above, the result looks something like this:
34+
35+
<div class="language-plaintext highlighter-rouge"><div class="highlight">
36+
<pre class="terminal" style='color:var(--color-term-normal); background:var(--color-term-background)'>
37+
<span class='shell'>$ </span><span class='cmd'>./crash</span>
38+
I&#39;m going to crash now
39+
40+
💣 <span style='color:var(--color-term-bright-red)'>Program crashed: Bad pointer dereference at 0x0000000000000004</span>
41+
42+
Thread 0 crashed:
43+
44+
<span style='color:var(--color-term-gray)'>0</span> <span style='color:var(--color-term-bright-cyan)'>reallyCrashMe()</span><span style='color:var(--color-term-white)'> + 404</span> in <span style='color:var(--color-term-bright-magenta)'>crash</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crash.swift:4:15</span>
45+
46+
<span style='color:var(--color-term-gray)'> 2</span>│ print(&quot;I&#39;m going to crash now&quot;)
47+
<span style='color:var(--color-term-gray)'> 3</span>│ let ptr = UnsafeMutablePointer&lt;Int&gt;(bitPattern: 4)!
48+
<span style='background:var(--color-term-highlight-background)'><span style='color:var(--color-term-bright-white)'><span
49+
style='color:var(--color-term-gray)'> 4<span style='color: var(--color-term-bright-white)'>│ ptr.pointee = 42</span></span></span> </span>
50+
<span style='color:var(--color-term-gray)'> </span>│ <span style='color:var(--color-term-bright-red)'>▲</span>
51+
<span style='color:var(--color-term-gray)'> 5</span>│ }
52+
<span style='color:var(--color-term-gray)'> 6</span>│
53+
54+
<span style='color:var(--color-term-gray)'>1</span> <span style='color:var(--color-term-bright-cyan)'>crashMe()</span><span style='color:var(--color-term-white)'> + 12</span> in <span style='color:var(--color-term-bright-magenta)'>crash</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crash.swift:8:3</span>
55+
56+
<span style='color:var(--color-term-gray)'> 6</span>│
57+
<span style='color:var(--color-term-gray)'> 7</span>│ func crashMe() {
58+
<span style='background:var(--color-term-highlight-background)'><span style='color:var(--color-term-bright-white)'><span style='color:var(--color-term-gray)'> 8<span style='color: var(--color-term-bright-white)'>│ reallyCrashMe()</span></span></span> </span>
59+
<span style='color:var(--color-term-gray)'> </span>│ <span style='color:var(--color-term-bright-red)'>▲</span>
60+
<span style='color:var(--color-term-gray)'> 9</span>│ }
61+
<span style='color:var(--color-term-gray)'> 10</span>│
62+
63+
<span style='color:var(--color-term-gray)'>2</span> <span style='color:var(--color-term-bright-cyan)'>main</span><span style='color:var(--color-term-white)'> + 12</span> in <span style='color:var(--color-term-bright-magenta)'>crash</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crash.swift:11:1</span>
64+
65+
<span style='color:var(--color-term-gray)'> 9</span>│ }
66+
<span style='color:var(--color-term-gray)'> 10</span>│
67+
<span style='background:var(--color-term-highlight-background)'><span style='color:var(--color-term-bright-white)'><span style='color:var(--color-term-gray)'> 11<span style='color: var(--color-term-bright-white)'>│ crashMe()</span></span></span> </span>
68+
<span style='color:var(--color-term-gray)'> </span>│ <span style='color:var(--color-term-bright-red)'>▲</span>
69+
<span style='color:var(--color-term-gray)'> 12</span>│
70+
71+
Press space to interact, D to debug, or any other key to quit (30s)
72+
73+
</pre>
74+
</div></div>
75+
76+
Or if you run the same program within a pipeline (not attached to
77+
a terminal), you'll see a report like this:
78+
79+
```
80+
*** Program crashed: Bad pointer dereference at 0x0000000000000004 ***
81+
82+
Thread 0 crashed:
83+
84+
0 0x00000001045a3df0 reallyCrashMe() + 404 in crash at /Users/alastair/Source/crashDotSwift/crash.swift:4:15
85+
1 [ra] 0x00000001045a3ea4 crashMe() + 12 in crash at /Users/alastair/Source/crashDotSwift/crash.swift:8:3
86+
2 [ra] 0x00000001045a3c50 main + 12 in crash at /Users/alastair/Source/crashDotSwift/crash.swift:11:1
87+
3 [ra] [system] 0x000000018705d058 start + 2224 in dyld
88+
89+
90+
Registers:
91+
92+
x0 0x0000000000000001 1
93+
x1 0x0000000000000000 0
94+
x2 0x0000000000000000 0
95+
x3 0x000060000016c1c0 c0 c1 28 fd 79 96 00 00 fb 07 00 00 00 00 00 00 ÀÁ(ýy···û·······
96+
97+
...
98+
99+
x26 0x0000000000000000 0
100+
x27 0x0000000000000000 0
101+
x28 0x0000000000000000 0
102+
fp 0x000000016b85f310 20 f3 85 6b 01 00 00 00 a4 3e 5a 04 01 00 00 00 ó·k····¤>Z·····
103+
lr 0x72268001045a3d44 8225402511295135044
104+
sp 0x000000016b85f280 a0 f2 85 6b 01 00 00 00 00 00 00 00 00 00 00 00  ò·k············
105+
pc 0x00000001045a3df0 28 01 00 f9 fd 7b 49 a9 ff 83 02 91 c0 03 5f d6 (··ùý{I©ÿ···À·_Ö
106+
107+
108+
Images (42 omitted):
109+
110+
0x00000001045a0000–0x00000001045a4000 6776aba03ad432b68bc57220ac4e6ef8 crash /Users/alastair/Source/crashDotSwift/crash
111+
0x0000000187057000–0x00000001870ea874 ee3f4181cec538c2b8a84d310be33491 dyld /usr/lib/dyld
112+
113+
```
114+
115+
This new feature greatly improves the on-crash debugging experience on Linux, where it is on by default. It is useful on macOS as well, but must be manually
116+
enabled. It is not presently supported on Windows.
117+
118+
### Interactive Backtraces
119+
120+
You might be wondering about the message on the last line of the
121+
in-terminal backtrace above, where it says:
122+
123+
```
124+
Press space to interact, D to debug, or any other key to quit (30s)
125+
```
126+
127+
Often when developing a program at the terminal, you might find that
128+
the program crashes, but you aren't able to reproduce the problem.
129+
Without a suitable crash log, that can be very frustrating --- you know
130+
your program has a bug, but you don't know what it was or how to
131+
reproduce it.
132+
133+
The idea behind this feature is that it leaves the program suspended
134+
(by default for 30 seconds, but this is configurable) and provides you
135+
with the opportunity to either attach a debugger, or perform some
136+
additional inspection of the crashed process.
137+
138+
If you tap the spacebar when this prompt appears, you will be
139+
presented with a simple command prompt that allows you to change the
140+
backtracer settings, generate a new backtrace, list loaded images,
141+
display register and memory contents, and get a listing of all of the
142+
threads in the process. Typing `help` at the prompt will bring up a
143+
list of available commands:
144+
145+
<div class="language-plaintext highlighter-rouge"><div class="highlight">
146+
<pre class="terminal" style="color:var(--color-term-normal); background:var(--color-term-background)">
147+
<span style='color:var(--color-term-gray)'>&gt;&gt;&gt; </span>help
148+
Available commands:
149+
150+
backtrace Display a backtrace.
151+
bt Synonym for backtrace.
152+
debug Attach the debugger.
153+
exit Exit interaction, allowing program to crash normally.
154+
help Display help.
155+
images List images loaded by the program.
156+
mem Synonym for memory.
157+
memory Inspect memory.
158+
process Show information about the process.
159+
quit Synonym for exit.
160+
reg Synonym for registers.
161+
registers Display the registers.
162+
set Set or show options.
163+
thread Show or set the current thread.
164+
threads Synonym for process.
165+
</pre>
166+
</div></div>
167+
168+
If you use the `debug` command or press `D` at the prompt, the
169+
backtracer will help you to attach a debugger to your program.
170+
Exactly what happens here is platform-dependent.
171+
172+
If you press any other key, or if the 30 second timer runs down, the
173+
program will be allowed to crash normally.
174+
175+
By default, the interactive feature will trigger if your program's
176+
standard input and output are both attached to a terminal. In many
177+
cases, this means you'll get the right behavior automatically if
178+
you're running in a CI system or as part of an automated script,
179+
as those tend to run with the program's output redirected to a
180+
pipe or a file.
181+
182+
In situations where you do not want this feature enabled, you can
183+
explicitly disable it by setting the environment variable
184+
`SWIFT_BACKTRACE` to `interactive=no`. You can also disable the color
185+
output with `color=no`, as well as combine multiple options like `interactive=no,color=no`.
186+
187+
### Structured Concurrency Support
188+
189+
The backtracer is concurrency-aware and will correctly step back
190+
through asynchronous frames. For example, given the program:
191+
192+
```swift
193+
func level(n: Int) async {
194+
if n < 5 {
195+
await level(n: n + 1)
196+
} else {
197+
let ptr = UnsafeMutablePointer<Int>(bitPattern: 4)!
198+
ptr.pointee = 42
199+
}
200+
}
201+
202+
@main
203+
struct CrashAsync {
204+
static func main() async {
205+
await level(n: 1)
206+
}
207+
}
208+
```
209+
210+
the backtrace will look like:
211+
212+
<div class="language-plaintext highlighter-rouge"><div class="highlight">
213+
<pre class="terminal" style="color:var(--color-term-normal); background:var(--color-term-background)">
214+
<span class='shell'>$ </span><span class='cmd'>./crashAsync</span>
215+
216+
💣 <span style='color:var(--color-term-bright-red)'>Program crashed: Bad pointer dereference at 0x0000000000000004</span>
217+
218+
Thread 1 crashed:
219+
220+
<span style='color:var(--color-term-gray)'>0</span> <span style='color:var(--color-term-bright-cyan)'>level(n:)</span><span style='color:var(--color-term-white)'> + 308</span> in <span style='color:var(--color-term-bright-magenta)'>crashAsync</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crashAsync.swift:6:17</span>
221+
222+
<span style='color:var(--color-term-gray)'> 4</span>│ } else {
223+
<span style='color:var(--color-term-gray)'> 5</span>│ let ptr = UnsafeMutablePointer&lt;Int&gt;(bitPattern: 4)!
224+
<span style='background:var(--color-term-highlight-background)'><span style='color:var(--color-term-bright-white)'><span style='color:var(--color-term-gray)'> 6<span style='color:var(--color-term-bright-white)'>│ ptr.pointee = 42</span></span></span> </span>
225+
<span style='color:var(--color-term-gray)'> </span>│ <span style='color:var(--color-term-bright-red)'>▲</span>
226+
<span style='color:var(--color-term-gray)'> 7</span>│ }
227+
<span style='color:var(--color-term-gray)'> 8</span>│ }
228+
229+
<span style='color:var(--color-term-gray)'>1</span> <span style='color:var(--color-term-bright-cyan)'>level(n:)</span> in <span style='color:var(--color-term-bright-magenta)'>crashAsync</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crashAsync.swift:3</span>
230+
231+
<span style='color:var(--color-term-gray)'> 1</span>│ func level(n: Int) async {
232+
<span style='color:var(--color-term-gray)'> 2</span>│ if n &lt; 5 {
233+
<span style='background:var(--color-term-highlight-background)'><span style='color:var(--color-term-bright-white)'><span style='color:var(--color-term-gray)'> 3<span style='color:var(--color-term-bright-white)'>│ await level(n: n + 1)</span></span></span> </span>
234+
<span style='color:var(--color-term-gray)'> </span>│ <span style='color:var(--color-term-bright-red)'>▲</span>
235+
<span style='color:var(--color-term-gray)'> 4</span>│ } else {
236+
<span style='color:var(--color-term-gray)'> 5</span>│ let ptr = UnsafeMutablePointer&lt;Int&gt;(bitPattern: 4)!
237+
238+
<span style='color:var(--color-term-gray)'>2</span> <span style='color:var(--color-term-bright-cyan)'>level(n:)</span> in <span style='color:var(--color-term-bright-magenta)'>crashAsync</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crashAsync.swift:3</span>
239+
<span style='color:var(--color-term-gray)'>3</span> <span style='color:var(--color-term-bright-cyan)'>level(n:)</span> in <span style='color:var(--color-term-bright-magenta)'>crashAsync</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crashAsync.swift:3</span>
240+
<span style='color:var(--color-term-gray)'>4</span> <span style='color:var(--color-term-bright-cyan)'>level(n:)</span> in <span style='color:var(--color-term-bright-magenta)'>crashAsync</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crashAsync.swift:3</span>
241+
<span style='color:var(--color-term-gray)'>5</span> <span style='color:var(--color-term-bright-cyan)'>static CrashAsync.main()</span> in <span style='color:var(--color-term-bright-magenta)'>crashAsync</span> at <span style='color:var(--color-term-yellow)'>/Users/alastair/Source/crashDotSwift/crashAsync.swift:13</span>
242+
243+
<span style='color:var(--color-term-gray)'> 11</span>│ struct CrashAsync {
244+
<span style='color:var(--color-term-gray)'> 12</span>│ static func main() async {
245+
<span style='background:var(--color-term-highlight-background)'><span style='color:var(--color-term-bright-white)'><span style='color:var(--color-term-gray)'> 13<span style='color:var(--color-term-bright-white)'>│ await level(n: 1)</span></span></span> </span>
246+
<span style='color:var(--color-term-gray)'> </span>│ <span style='color:var(--color-term-bright-red)'>▲</span>
247+
<span style='color:var(--color-term-gray)'> 14</span>│ }
248+
<span style='color:var(--color-term-gray)'> 15</span>│ }
249+
250+
Press space to interact, D to debug, or any other key to quit (30s)
251+
252+
</pre>
253+
</div></div>
254+
255+
On Apple platforms, this feature has no special requirements, but for
256+
other platforms the backtracer needs to be able to look up symbols
257+
to determine whether or not a given frame is asynchronous. If the
258+
necessary symbols are not available, the backtrace will follow the
259+
normal program stack rather than the async activation chain. This
260+
will usually result in it showing frames from the concurrency runtime,
261+
which are unlikely to be helpful when debugging most types of problem.
262+
263+
### Improving Readability
264+
265+
The new backtracer also has a number of options to improve readability.
266+
267+
You can configure the maximum number of frames that the backtracer
268+
will generate (the default is 64), but since you might also want to
269+
see frames at the top of the stack, the backtracer also has a setting
270+
for the number of frames to capture there (by default 16). This is
271+
particularly handy if your program crashes due to excessive recursion,
272+
as you'll usually see both the recursion and the cause of it, without
273+
being overwhelmed by thousands and thousands of frames.
274+
275+
The backtracer also skips over system frames and Swift thunks by
276+
default. These are usually not relevant except to compiler or runtime
277+
engineers, and generally result in more confusing output for most
278+
developers.
279+
280+
Additionally, the backtracer will automatically demangle both Swift
281+
and C++ mangled names.
282+
283+
### Summary
284+
285+
The new on-crash debugging options in Swift 5.9 help you
286+
debug your programs when they misbehave. The backtracer has a
287+
number of helpful features including:
288+
289+
* Out-of-process crash handling
290+
* Smart, in-line display of program source, where available
291+
* The option to pause and inspect your crashed program, or even
292+
trigger the debugger for just-in-time debugging
293+
* Support for Swift Concurrency
294+
* Support for C++ name mangling in addition to Swift
295+
* Colorized output for readability
296+
* Expanded configuration options ([see
297+
documentation](https://github.com/apple/swift/blob/main/docs/Backtracing.rst)).
298+
299+
The new feature is enabled by default on Linux and can be enabled for macOS ([see
300+
documentation](https://github.com/apple/swift/blob/main/docs/Backtracing.rst)).
301+
There is no Windows support at present.

assets/stylesheets/core/colors/_dark.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,23 @@
7575
--color-syntax-type-declarations: rgb(107, 223, 255);
7676
--color-syntax-urls: rgb(102, 153, 255);
7777
--color-tutorial-background: var(--color-fill-tertiary);
78+
79+
--color-term-normal: var(--color-text);
80+
--color-term-background: var(--color-code-background);
81+
--color-term-red: rgb(194,54,33);
82+
--color-term-green: rgb(37,188,36);
83+
--color-term-yellow: rgb(173,173,39);
84+
--color-term-blue: rgb(73,46,225);
85+
--color-term-magenta: rgb(211,56,211);
86+
--color-term-cyan: rgb(51,187,200);
87+
--color-term-white: rgb(203,204,205);
88+
--color-term-gray: rgb(129,131,131);
89+
--color-term-bright-red: rgb(252,57,31);
90+
--color-term-bright-green: rgb(49,231,34);
91+
--color-term-bright-yellow: rgb(234,236,35);
92+
--color-term-bright-blue: rgb(88,51,255);
93+
--color-term-bright-magenta: rgb(249,53,248);
94+
--color-term-bright-cyan: rgb(20,240,240);
95+
--color-term-bright-white: rgb(233,235,235);
96+
--color-term-highlight-background: rgb(30,30,30);
7897
}

assets/stylesheets/core/colors/_light.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,23 @@
182182
--color-tutorial-hero-text: #{dark-color(figure-gray)};
183183
--color-tutorial-hero-background: #{dark-color(fill)};
184184
--color-evolution-secondary-fill: var(--color-figure-light-gray);
185+
186+
--color-term-normal: var(--color-text);
187+
--color-term-background: var(--color-code-background);
188+
--color-term-red: rgb(194,54,33);
189+
--color-term-green: rgb(37,188,36);
190+
--color-term-yellow: rgb(173,173,39);
191+
--color-term-blue: rgb(73,46,225);
192+
--color-term-magenta: rgb(211,56,211);
193+
--color-term-cyan: rgb(51,187,200);
194+
--color-term-white: rgb(175,175,175);
195+
--color-term-gray: rgb(129,131,131);
196+
--color-term-bright-red: rgb(252,57,31);
197+
--color-term-bright-green: rgb(49,231,34);
198+
--color-term-bright-yellow: rgb(234,236,35);
199+
--color-term-bright-blue: rgb(88,51,255);
200+
--color-term-bright-magenta: rgb(249,53,248);
201+
--color-term-bright-cyan: rgb(20,200,200);
202+
--color-term-bright-white: rgb(255,255,255);
203+
--color-term-highlight-background: rgb(195,195,195);
185204
}

0 commit comments

Comments
 (0)