<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>Nick Ripley's blog</title>
    <link>https://www.nsrip.com</link>
    <description>
Nick Ripley's blog. Programming and other interests.
</description>
    <language>en-us</language>
    <lastBuildDate>Wed, 31 Dec 2025 14:56:10 EST</lastBuildDate>
    <docs>https://www.rssboard.org/rss-specification</docs>
    <atom:link href="https://www.nsrip.com/feed.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Two concurrency patterns which avoid goroutine leaks</title>
      <link>https://www.nsrip.com/posts/goroutineleak.html</link>
      <description>
Go has automatic memory management,
but it's still possible to leak memory.
Probably the most common kind of leak I've seen is leaking goroutines.
You start a goroutine to do some work,
but forget to arrange for the goroutine to stop.
The goroutine holds on to memory and the garbage collector can't clean it up.
This post covers two examples from the Go standard library for doing work in goroutines such that the goroutines don't leak.
The examples embody Dave Cheney's advice:
"never start a goroutine without knowing how it will stop."
      </description>
      <pubDate>Wed, 31 Dec 2025 14:56:10 EST</pubDate>
      <guid>https://www.nsrip.com/posts/goroutineleak.html</guid>
      <content:encoded><![CDATA[
<p >
Go has automatic memory management,
but it's still possible to leak memory.
Probably the most common kind of leak I've seen is leaking goroutines.
You start a goroutine to do some work,
but forget to arrange for the goroutine to stop.
The goroutine holds on to memory and the garbage collector can't clean it up.
This post covers two examples from the Go standard library for doing work in goroutines such that the goroutines don't leak.
The examples embody Dave Cheney's <a href="https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop" target="_blank">advice</a>:
"never start a goroutine without knowing how it will stop."
</p>

<p >
The code I show here is from the Go repository and is copyrighted by the Go authors.
</p>

<div class="hover">
<h2 id="timerloop">Using <code >time.AfterFunc</code> instead of a ticker in a loop <a href="https://nsrip.com/posts/goroutineleak.html#timerloop" class="hide">&sect;</a></h2>
</div>

<p >
Sometimes your program needs to do something periodically,
like flush a buffer,
send pending log messages or accumulated metrics somewhere,
etc.
The simplest way to accomplish that is to start a goroutine.
That goroutine has a <code >time.Ticker</code>,
a second channel to signal that the goroutine should stop,
and selects on the two in a loop.
But if you don't stop that goroutine,
it'll hold on to the buffer or other memory indefinitely.
The idea in this section is this:
schedule a <i >single</i> flush, and only when new data is added.
</p>

<p >
I first saw this pattern in the <code >net/http/reverseproxy</code> package.
The reverse proxy buffers writes for efficiency.
To avoid potentially unbounded latency if the buffer doesn't fill up,
the reverse proxy periodically flushes the buffer.
The implementation used to look like this:
</p>

<pre >
type maxLatencyWriter struct {
	dst     writeFlusher
	latency time.Duration

	mu   sync.Mutex // protects Write + Flush
	done chan bool
}

func (m *maxLatencyWriter) Write(p []byte) (int, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.dst.Write(p)
}

func (m *maxLatencyWriter) flushLoop() {
	t := time.NewTicker(m.latency)
	defer t.Stop()
	for {
		select {
		case <-m.done:
			if onExitFlushLoop != nil {
				onExitFlushLoop()
			}
			return
		case <-t.C:
			m.mu.Lock()
			m.dst.Flush()
			m.mu.Unlock()
		}
	}
}

func (m *maxLatencyWriter) stop() { m.done <- true }
</pre>

<p >
One big flaw here is that if you forget to <code >defer m.stop()</code>,
then the goroutine running <code >m.flushLoop</code> will never exit.
That goroutine is going to hold on to the backing buffer for the writer.
If such a goroutine is started for every HTTP request the proxy receives,
the program will eventually run out of memory.
</p>

<p >
<a href="https://go-review.googlesource.com/c/go/+/137335" target="_blank">CL 137335</a> from Brad Fitzpatrick changed the implementation.
There were a few motivations for that change,
but one benefit of the new implementation is getting rid of that goroutine.
Here's the <a href="https://cs.opensource.google/go/go/+/refs/tags/go1.25.2:src/net/http/httputil/reverseproxy.go;l=694-741" target="_blank">new implementation</a>:
</p>

<pre >
type maxLatencyWriter struct {
	dst     io.Writer
	flush   func() error
	latency time.Duration // non-zero; negative means to flush immediately

	mu           sync.Mutex // protects t, flushPending, and dst.Flush
	t            *time.Timer
	flushPending bool
}

func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	n, err = m.dst.Write(p)
	if m.latency < 0 {
		m.flush()
		return
	}
	if m.flushPending {
		return
	}
	if m.t == nil {
		m.t = time.AfterFunc(m.latency, m.delayedFlush)
	} else {
		m.t.Reset(m.latency)
	}
	m.flushPending = true
	return
}

func (m *maxLatencyWriter) delayedFlush() {
	m.mu.Lock()
	defer m.mu.Unlock()
	if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
		return
	}
	m.flush()
	m.flushPending = false
}

func (m *maxLatencyWriter) stop() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.flushPending = false
	if m.t != nil {
		m.t.Stop()
	}
}
</pre>

<p >
The writer has a timer.
When bytes are written, the timer is set to flush later based on the desired max latency.
The timer is reset if it was already scheduled,
so the writer will only flush if there are no more write calls for a long enough time.
If and when the timer fires,
the buffer is flushed one time and then that's it.
That flush happens in a new goroutine created when the timer fires,
but we know it just does one thing and thus we know the goroutine won't leak.<sup ><a href="https://nsrip.com/posts/goroutineleak.html#footnote1" id="back1">[1]</a></sup>
Nice!
</p>

<p >
In the reverse proxy, the code still needs to ensure that <code >stop</code> is called.
This is because the writer is going to be used later without synchronization,
and thus the code needs to ensure that the flush scheduled by the timer doesn't touch it.
However, even if the writer <i >was</i> safe to use concurrently,
we're still better off because we've avoided the goroutine leak.
</p>

<div class="hover">
<h2 id="workerpool">Lazily-created worker pool <a href="https://nsrip.com/posts/goroutineleak.html#workerpool" class="hide">&sect;</a></h2>
</div>

<p >
The second example comes from the Go toolchain.
The toolchain has lots of cases where it can parallelize work.
It's not necessarily good to start a goroutine per work item, though.
For one, if there are lots of them (like one per package) and they each use a decent amount of memory,
the program can run out of memory.
And if the work items are compute heavy,
there's no benefit do doing more than "the number of processors" work items concurrently.
The typical approach here would be to create a "pool" of workers.
And in the typical implementation,
workers are goroutines that receive work items from a channel.
Usually the goroutines would be started up-front.
Then the goroutines <code >range</code> over that channel and exit when the channel is closed.
But if we don't close the channel,
the workers never stop.
</p>

<p >
Now, unlike the reverse proxy example,
I don't think I've personally seen unbounded goroutine leaks from worker pools.
They're usually created once, at the start of the program.
It's not the end of the world if they don't exit because they'll be around for the whole program run anyway.
But I still think this next example is neat,
as an example of avoiding leaks by design.
Maybe you'll find a good use for the pattern.
</p>

<p >
<a href="https://go-review.googlesource.com/c/go/+/247766" target="_blank">CL 247766</a> from Bryan Mills introduced a <code >Queue</code> struct for scheduling work with bounded parallelism.
The code is a little longer so I'll break it up.<sup ><a href="https://nsrip.com/posts/goroutineleak.html#footnote2" id="back2">[2]</a></sup>
Here are the data structures and how they're created:
</p>

<pre >
// Queue manages a set of work items to be executed in parallel. The number of
// active work items is limited, and excess items are queued sequentially.
type Queue struct {
	maxActive int
	st        chan queueState
}

type queueState struct {
	active  int // number of goroutines processing work; always nonzero when len(backlog) > 0
	backlog []func()
	idle    chan struct{} // if non-nil, closed when active becomes 0
}

// NewQueue returns a Queue that executes up to maxActive items in parallel.
//
// maxActive must be positive.
func NewQueue(maxActive int) *Queue {
	if maxActive < 1 {
		panic(fmt.Sprintf("par.NewQueue called with nonpositive limit (%d)", maxActive))
	}

	q := &Queue;{
		maxActive: maxActive,
		st:        make(chan queueState, 1),
	}
	q.st <- queueState{}
	return q
}
</pre>

<p >
Work is submitted to the queue as parameterless functions to call.
The functions are stored in a first-in first-out backlog,
implemented using a slice here.
Note that this implementation uses a buffered channel (<code >st</code>) as a kind of mutex to protect the queue state.
Here's how work is submitted to the queue:
</p>

<pre >
// Add adds f as a work item in the queue.
//
// Add returns immediately, but the queue will be marked as non-idle until after
// f (and any subsequently-added work) has completed.
func (q *Queue) Add(f func()) {
	st := <-q.st
	if st.active == q.maxActive {
		st.backlog = append(st.backlog, f)
		q.st <- st
		return
	}
	if st.active == 0 {
		// Mark q as non-idle.
		st.idle = nil
	}
	st.active++
	q.st <- st

	go func() {
		for {
			f()

			st := <-q.st
			if len(st.backlog) == 0 {
				if st.active--; st.active == 0 && st.idle != nil {
					close(st.idle)
				}
				q.st <- st
				return
			}
			f, st.backlog = st.backlog[0], st.backlog[1:]
			q.st <- st
		}
	}()
}
</pre>

<p >
There's a little more going on here.
Basically, if there are fewer than <code >maxActive</code> worker goroutines,
the function starts one.
Otherwise work items are added to the backlog.
The worker goroutines remove items from the backlog and run them.
</p>

<p >
The exit condition is very different from the "<code >range</code> over a channel" implementation.
With a channel, the program needs to close the channel for the goroutines to exit.
This invites user error,
especially if this is part of an API other people will use.
With this implementation,
we don't have that problem.
The worker goroutines will exit as soon as the backlog is ever empty.
This should happen eventually if the workers can keep up with the incoming work.
And if the worker goroutines stop and there's new work later,
we just start new ones.
Goroutines are cheap to create.
Practically speaking,
I think knowing the code is free of one kind of bug is worth at least some of the cost of making new goroutines every now and then.<sup ><a href="https://nsrip.com/posts/goroutineleak.html#footnote3" id="back3">[3]</a></sup>

</p>

<p >
The thing these examples have in common is that the exit conditions for the goroutines are clear.
In the first example, it's that we're only going to flush one time.
In the second example, it's running out of work.
There's no other function to remember to call.
In both examples, we know how the goroutines will stop when we start them.
</p>

<hr >

<ol >
<li id="footnote1">
Unless the write somehow blocks forever...
I recall having issues with network file systems in the past.
<a href="https://nsrip.com/posts/goroutineleak.html#back1">Back</a>
</li>
<li id="footnote2">
I won't show the code here,
but there's also an <code >Idle</code> method which lets the caller synchronize on all the pending work being finished.
It basically just returns the <code >idle</code> channel,
with some logic to handle if there is no active work.
<a href="https://nsrip.com/posts/goroutineleak.html#back2">Back</a>
</li>
<li id="footnote3">
If you're going to use this pattern for a long-lived process,
with unbounded amounts of work,
don't copy this-as is.
This implementation uses a slice for the backlog,
appending new items to the end and slicing the next item off the front.
This will grow the backing memory without bound.
Prefer a linked list or other implementation in this case
(see <a href="https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)" target="_blank">Wikipedia</a> for an example.)
<a href="https://nsrip.com/posts/goroutineleak.html#back3">Back</a>
</li>
</ol>

]]></content:encoded>
    </item>
    <item>
      <title>How to patch the Go toolchain</title>
      <link>https://www.nsrip.com/posts/patchruntime.html</link>
      <description>
Every now and then,
I've run into a tricky Go bug that seems to involve the compiler, runtime, or standard library.
Try as I might, I can't reproduce the bug.
I've only seen it happen in production.
I don't have enough information to diagnose the issue.
Sometimes I've wished I could get the runtime to emit some more diagnostics,
or just wanted to try out a potential fix from another Go contributor.
This post covers a few ways you can build your Go programs with changes applied to the compiler, runtime, or standard library.
      </description>
      <pubDate>Wed, 02 Jul 2025 14:16:31 EDT</pubDate>
      <guid>https://www.nsrip.com/posts/patchruntime.html</guid>
      <content:encoded><![CDATA[
<p >
Every now and then,
I've run into a tricky Go bug that seems to involve the compiler, runtime, or standard library.
Try as I might, I can't reproduce the bug.
I've only seen it happen in production.
I don't have enough information to diagnose the issue.
Sometimes I've wished I could get the runtime to emit some more diagnostics,
or just wanted to try out a potential fix from another Go contributor.
This post covers a few ways you can build your Go programs with changes applied to the compiler, runtime, or standard library.
</p>

<p >
The short version:
</p>

<ul >
<li >
The most flexible option is to build a toolchain distribution from (modified) source.
You'll need to do this if you're changing the compiler, linker, or other tools.
</li>
<li >
If you're only modifying the runtime or standard library,
use <a href="https://github.com/felixge/go-patch-overlay" target="_blank"><code >go-patch-overlay</code></a>
to apply patches at build time.
</li>
</ul>

<div class="hover">
<h2 id="toolchain">Build a toolchain from source <a href="https://nsrip.com/posts/patchruntime.html#toolchain" class="hide">&sect;</a></h2>
</div>

<p >
The first method is to build a custom Go toolchain,
and use it to build your program.
By toolchain, I mean the collection of tools (<code >go</code> command, compiler, linker, etc),
along with the runtime and standard library sources.
There are ways to change the runtime and standard library sources when you build your program.
But if you want to change the tools,
you'll need to build them ahead of time.
</p>

<p >
You first check out the Go source code:
</p>

<pre >git clone https://go.googlesource.com/go</pre>

<p >
If you're making a change to a specific Go release version,
use <code >git switch --detach {release tag}</code> to check it out.
Then you make the changes you want.
</p>

<p >
If you want to build the toolchain to use locally,
there's a script to build it:
</p>

<pre >cd src
./make.bash
</pre>

<p >
Then add <code >/path/to/go/bin</code> to your <code >PATH</code>.
After that, running the <code >go</code> command will use the toolchain you built.
</p>

<p >
What if you want to give this toolchain to someone else,
or use it in your CI?
My colleague <a href="https://github.com/gandem" target="_blank">Nayef</a> has done this at Datadog.
Here's how he's done it:
</p>

<p >
You'll need a <code >VERSION</code> file to make a distribution:
</p>

<pre >echo go1.x.y > VERSION</pre>

<p >
Replace <code >x</code> and <code >y</code> with numbers.
For example,
if you were testing a fix for <code >go1.24.4</code>,
you could do <code >go1.24.999</code>.
That'll give you a valid version that's also unlikely to collide with a real Go version.
</p>

<p >
Then you'll actually build the distribution:
</p>

<pre >cd src/
GOOS=linux GOARCH=amd64 ./make.bash -distpack 
</pre>

<p >
Set <code >GOOS</code> and <code >GOARCH</code> as needed.
You'll end up with a distribution like
</p>

<pre >
../pkg/distpack/go1.X.Y.linux-amd64.tar.gz
</pre>

<p >
To actually <i >use</i> this,
you'll essentially follow the steps in the <a href="https://go.dev/doc/install" target="_blank">Go installation docs</a>.
Copy <code >go1.X.Y.linux-amd64.tar.gz</code> to wherever you want to install it,
and then extract it.
You'll need to update the <code >PATH</code> environment variable
to include the <code >bin</code> subdirectory of the resulting directory.
Then when you run the <code >go</code> command you'll be using the custom toolchain.

</p>

<div class="hover">
<h2 id="gotip">Use the <code >gotip</code> tool <a href="https://nsrip.com/posts/patchruntime.html#gotip" class="hide">&sect;</a></h2>
</div>

<p >
If you have a CL you'd like to use (from <a href="https://go.dev/cl" target="_blank">go.dev/cl</a>),
there's a tool that can build the toolchain for you.
It's called <a href="https://pkg.go.dev/golang.org/dl/gotip" target="_blank"><code >gotip</code></a>.
This might apply to you if you've reported a bug,
and a Go maintainer has a potential fix for you to try.
The <code >gotip</code> readme explains it,
but here's a concrete example:
</p>

<p >
Say you want to try out <a href="https://go.dev/cl/12345" target="_blank">go.dev/cl/12345</a>.
The "CL number" is 12345.
You can then use <code >gotip</code> like so:
</p>

<pre >go install golang.org/dl/gotip@latest
gotip download 12345
</pre>

<p >
This will download the Go sources from that CL number,
and then build the Go toolchain from those sources.
Then you can use <code >gotip</code> to build your program in place of the normal <code >go</code> command.
</p>

<div class="hover">
<h2 id="bazel">Bazel <a href="https://nsrip.com/posts/patchruntime.html#bazel" class="hide">&sect;</a></h2>
</div>

<p >
If you build your Go code with Bazel,
then ignore the rest of this post and just refer to this section.
Full disclosure:
At the time of writing this post,
I haven't actually used this method.
I don't know if this works for compiler/linker/etc patches or just runtime/library code.
But we have some Bazel stuff at Datadog so it seemed worth mentioning here.
The <code >rules_go</code> rules support patching the tools and libraries out of the box.
It's described <a href="https://github.com/bazel-contrib/rules_go/blob/master/go/toolchains.rst#go_download_sdk" target="_blank">here</a>.
Here's an example straight from the manual:
</p>

<pre >
go_download_sdk(
    name = "go_sdk",
    goos = "linux",
    goarch = "amd64",
    version = "1.18.1",
    sdks = {
        "linux_amd64": ("go1.18.1.linux-amd64.tar.gz", "b3b815f47ababac13810fc6021eb73d65478e0b2db4b09d348eefad9581a2334"),
        "darwin_amd64": ("go1.18.1.darwin-amd64.tar.gz", "3703e9a0db1000f18c0c7b524f3d378aac71219b4715a6a4c5683eb639f41a4d"),
    },
    <b >patch_strip = 1,
    patches = [
        "//patches:cgo_issue_fix.patch",
    ]</b>
)
</pre>

<p >
I've bolded the relevant part.
You just provide a list of patches,
and they're applied when Bazel builds and installs the toolchain.
</p>

<div class="hover">
<h2 id="patch">Patch the sources from the toolchain <a href="https://nsrip.com/posts/patchruntime.html#patch" class="hide">&sect;</a></h2>
</div>

<p >
As mentioned previously,
if you want to change the compiler, linker, or other tools,
you'll need to rebuild them.
But if your changes are just to the runtime or standard library,
you have other options.
Your Go toolchain includes the runtime and standard library source code.
The code for your toolchain is under the toolchain's <code >GOROOT</code>:
</p>

<pre >
% go env GOROOT
/usr/local/go
% go1.24.4 env GOROOT
/Users/nick/sdk/go1.24.4
</pre>

<p >
There are a few ways you can modify the source code.
The first way is to edit it directly.
You can use a text editor,
or if you have a patch, you can apply it to the source code.
<b >Don't do this to your local toolchain installation!</b>
It's a pain to undo your changes.
This method is suitable for doing during a build in a Docker container.
</p>

<p >
There are a few ways to get a patch:
</p>

<ul >
<li >Somebody gives you one</li>
<li >You check out the Go sources and edit them. Then run <code >git diff > change.patch</code></li>
<li >
You get one from a CL.
You can visit the CL page, where there should be a link to download the change as a patch.
Or you can download the patch via the <a href="https://gerrit-documentation.storage.googleapis.com/Documentation/3.12.0/rest-api-changes.html#get-patch" target="_blank">API</a>
<pre >
CL_NUMBER=12345
curl -XGET "https://go-review.googlesource.com/changes/go~${CL_NUMBER}/revisions/current/patch?raw" > ${CL_NUMBER}.patch
</pre>
</li>
</ul>

<p >
Assuming you have a patch,
you can apply it to the Go toolchain sources like so:
</p>

<pre >
pushd $(go env GOROOT)
git apply /path/to/patch1.diff /path/to/patch2.diff ...
popd
go build ...
</pre>

<p >
Whatever changes you make to the sources will be reflected in the program you build.
Expect your build to be a bit slower if you change widely depended-upon packages,
like <code >runtime</code>.
</p>

<div class="hover">
<h2 id="overlay">Build-time overlay <a href="https://nsrip.com/posts/patchruntime.html#overlay" class="hide">&sect;</a></h2>
</div>

<p >
Turns out there's another way to change the Go sources at build time,
without needing to actually modify your installation.
The <code >go build</code> command accepts an <code >-overlay</code> argument.
The argument is a path to a JSON file like this:
</p>

<pre >
{
  "Replace": {
    "/path/to/source1.go": "/other/path/to/replacement.go",
    "/path/to/source2.go": "",
    ...
  }
}
</pre>

<p >
Basically, an <i >overlay</i> is a key-value map where the keys are file paths seen by the compiler,
and the values are replacement files.
This can even include new or deleted files.
The overlay feature was <a href="https://go.dev/cl/253747" target="_blank">originally added</a> to support <code >gopls</code>,
where the language server needs to tell the compiler about files whose contents haven't been written to disk.
We can use it to try out patches.
</p>

<p >
There's a tool that can generate an overlay for you from patches:
<a href="https://github.com/felixge/go-patch-overlay" target="_blank"><code >go-patch-overlay</code></a>.
My colleague Felix wrote this a few years ago.
I wrote a very similar program during a recent team research week with a few fixes and extra features,
before I knew about Felix's program,
so we incorporated my changes into his program.
You use the tool like this:
</p>

<pre >
go install github.com/felixge/go-patch-overlay@main
go build -overlay=$(go-patch-overlay /path/to/patch.diff)
</pre>

<p >
It works like this:
</p>

<ul >
<li >
The <code >go-patch-overlay</code> command reads the patch and determines which files it modifies.
</li>
<li >
Then the tool runs <code >go env GOROOT</code> to find where the toolchain sources are installed.
It copies any modified files into a new temporary directory.
</li>
<li >
Then the tool applies the patch to the sources it copied from the toolchain <code >GOROOT</code>.
</li>
<li >
Then the tool creates an overlay JSON file mapping the original sources to the modified sources.
The tool then prints the path to the JSON file to standard out.
</li>
</ul>

<p >
The file replacements in the overlay only apply to that specific <code >go build</code> invocation.
Your toolchain's sources are unchanged.
You can also pass the <code >-overlay</code> argument to <code >go run</code> and <code >go test</code>.
</p>

<p >
I personally like this method the best for runtime/standard library changes.
It's lightweight,
non-destructive,
and easy to use both locally and in CI.
</p>

<div class="hover">
<h2 id="orchestrion">Orchestrion <a href="https://nsrip.com/posts/patchruntime.html#orchestrion" class="hide">&sect;</a></h2>
</div>

<p >
There's one more tool worth mentioning here:
<a href="https://github.com/datadog/orchestrion" target="_blank">Orchestrion</a>.
Orchestrion automatically instruments Go code.
It was originally built for distributed tracing, profiling, and security monitoring instrumentation.
It works by rewriting source code at build time.
It's a really powerful tool,
kind of a low orbit ion cannon compared to the hammer of applying a patch.
I won't go into the details here.
If the changes you want to make are simple,
I'd start with patches and <code >go-patch-overlay</code>.
But keep Orchestrion in mind if you need something more powerful.
</p>

]]></content:encoded>
    </item>
    <item>
      <title>
Don't clobber the frame pointer
</title>
      <link>
https://www.nsrip.com/posts/clobberfp.html
</link>
      <description>
Recently I diagnosed and fixed two frame pointer unwinding crashes in Go.
The root causes were two flavors of the same problem:
buggy assembly code clobbered a frame pointer.
By "clobbered" I mean wrote over the value without saving &amp; restoring it.
One bug clobberd the frame pointer register.
The other bug clobbered a frame pointer saved on the stack.
This post explains the bugs,
talks a bit about ABIs and calling conventions,
and makes some recommendations for how to avoid the bugs.
</description>
      <pubDate>Tue, 24 Dec 2024 10:30:00 EDT</pubDate>
      <guid>
https://www.nsrip.com/posts/clobberfp.html
</guid>
      <content:encoded><![CDATA[
<title >Don't clobber the frame pointer</title>
<h1 >Don't clobber the frame pointer</h1>

<p ><b >
<a href="https://nsrip.com/posts/../index.html">Home</a>
-
<a href="https://nsrip.com/posts/../feed.xml">RSS</a>
</b></p>

<p ><i >Posted 2024-12-24</i></p>

<p >
Recently I diagnosed and fixed two frame pointer unwinding crashes in Go.
The root causes were two flavors of the same problem:
buggy assembly code clobbered a frame pointer.
By "clobbered" I mean wrote over the value without saving &amp; restoring it.
One bug clobbered the frame pointer register.
The other bug clobbered a frame pointer saved on the stack.
This post explains the bugs,
talks a bit about ABIs and calling conventions,
and makes some recommendations for how to avoid the bugs.
</p>

<p >
Here's the short version of what you should do when writing assembly for Go to avoid the problems discussed in this post:
<ul >
<li >
First, read the <a href="https://go.dev/doc/asm">Go assembly guide</a>.
</li>
<li >
Prefer an assembly generator like <a href="https://github.com/mmcloughlin/avo" target="_blank">Avo</a>.
Tools like Avo are aware of the underlying rules for using registers and manipulating the stack,
and generally make writing non-trivial amounts of assembly easier.
</li>
<li >
If your assembly function calls Go functions,
prefer not to use the frame pointer registers at all
(<code >BP</code> for AMD64, <code >R29</code> for ARM64).
Leaving a non-frame pointer value in those registers prior to calling a Go function can crash the runtime execution tracer and profilers.
</li>
<li >
Otherwise, if your function has a non-zero frame size,
the assembler will correctly make a stack frame for you,
growing the stack if needed,
and properly save and restore the frame pointer.
The means the frame pointer will be safe to use.
Consider giving your function a small, non-zero frame size even if you don't <i >need</i> to if you want to use the frame pointer.
<sup ><a href="https://nsrip.com/posts/clobberfp.html#footnote1" id="back1">[1]</a></sup>
</li>
<li >
Otherwise, if you need to use the frame pointer and don't want to have a frame,
save the original frame pointer on the stack yourself and restore it when the function returns.
Take care on ARM64:
Go compiled functions will save the frame pointer one word below their stack frame,
so the first 8 bytes of stack frame are off-limits,
and the stack pointer must be 16-byte aligned.
</li>
</ul>
</p>


<div class="hover">
<h2 id="register">Clobbering a register <a href="https://nsrip.com/posts/clobberfp.html#register" class="hide">&sect;</a></h2>
</div>

<p >
The first issue was reported at <a href="https://github.com/golang/go/issues/69629" target="_blank">go.dev/issue/69629</a>.
After upgrading to Go 1.23, <i >and</i> building with <a href="https://go.dev/doc/pgo" target="_blank">profile-guided optimization</a> (PGO),
the block profiler consistently crashed when the program was under load.
The crash happened when collecting a call stack via frame pointer unwinding.
This affected the program when built for the <code >amd64</code> architecture,
but not when it was built for <code >arm64</code>.
I had <a href="https://go.dev/cl/533258" target="_blank">contributed</a> frame pointer unwinding for the block and mutex profilers for Go 1.23,
so I took a look at the issue.
</p>

<p >
I won't go into the full investigation here.
There were many dead ends.
Thanks to the issue reporter, Liz Fong-Jones (@lizthegrey), for being so willing to collect and share information.
The key clue turned out to be the address of the faulting memory access, <code >0x30bc5b31</code>,
which was the same in every occurrence of the crash.
The program was reading 8 bytes above a frame pointer,
so the supposed frame pointer was <code >0x30bc5b29</code>.
In a moment of desperation (or inspiration?)
I put this value into GitHub code search and turned up several
<a href="https://www.jandrewrogers.com/2015/05/27/metrohash/" target="_blank">Metrohash</a> implementations.
</p>

<p >
The <a href="https://pkg.go.dev/github.com/dgryski/go-metro" target="_blank">github.com/dgryski/go-metro</a> implementation uses assembly.
Turns out the affected program was using this library!
<a href="https://github.com/dgryski/go-metro/blob/adc40b04c140ccfcbb2d243f40ca26a196eccd0d/metro_amd64.s#L8-L21" target="_blank">Here's</a>
some assembly from the library:
</p>

<pre >
// func Hash64(buffer_base uintptr, buffer_len int64, buffer_cap int64, seed uint64) uint64
TEXT ·Hash64(SB),4,$0-40
	MOVQ seed+24(FP), AX
	MOVQ buffer_base+0(FP), BX
	MOVQ buffer_len+8(FP), CX
	MOVQ $3603962101, DX
	IMULQ DX, AX
	MOVQ $5961697176435608501, DX
	ADDQ DX, AX
	CMPQ CX, $32
	JLT after32
	MOVQ AX, DX
	MOVQ AX, DI
	MOVQ AX, SI
	MOVQ AX, BP
</pre>

<p >
The <code >MOVQ AX, BP</code> instruction is a problem.
The <code >BP</code> register is used as a "frame pointer" register for AMD64.
Frame pointers form a linked list used to efficiently collect backtraces,
and the head of the list lives in that register.
The function also has no call frame (see the <code >$0</code> in the signature),
so the assembler won't add any code to save and restore the frame pointer.
That <code >MOVQ</code> instruction overwrites <code >BP</code> with a value used for the hash function,
without saving the old value.
So the old value is gone forever, and <code >BP</code> now holds an invalid frame pointer.
<a href="https://github.com/dgryski/go-metro/blob/adc40b04c140ccfcbb2d243f40ca26a196eccd0d/metro_amd64.s#L117-L118" target="_blank">Later</a> in the function,
there is a <code >MOVQ $817650473, BP</code> instruction.
This is the last write to <code >BP</code> before the function returns.
And <code >817650473 == 0x30bc5b29</code>, our mystery value.
</p>

<p >
Because this function leaves a non-frame pointer value in the frame pointer register,
any unwinding that passes through it between when when this function returns
and when its caller returns (restoring the caller's caller's presumably valid frame pointer) will fail.
The affected program crashed with PGO enabled because the function which called this buggy hash function was inlined,
extending the scope where the frame pointer register was invalid.
I <a href="https://github.com/dgryski/go-metro/issues/9" target="_blank">reported</a> the problem.
</p>

<p >
The assembly in the <code >go-metro</code> repo is not written by hand.
It was generated programmatically with <a href="https://github.com/Maratyszcza/PeachPy" target="_blank">PeachPy</a>.
With PeachPy, you write Python scripts which generate assembly.
The <code >go-metro</code> PeachPy code doesn't explicitly use the frame pointer register.
Rather, PeachPy treats it as a general-purpose register and uses it in the generated code.
So this is really a bug in PeachPy.
</p>

<p >
Despite this bug in PeachPy,
writing assembly with generator tools is a really good idea.
Assembly generators handle register allocation,
so you can ask for registers and refer to them by a useful names,
rather than having to remember that <code >CX</code> is your loop counter and <code >DX</code> is the hash seed, etc.
The generator can learn which registers are special and save you from breaking things.
The generator can automatically allocate the right size stack frame based on locals your functions uses.
You can encapsulate repeated logic in functions used by the generator,
which are way better than preprocessor macros since they actually have type checking,
and they end up inlined in the assembly output so you don't pay for an actual function call.
</p>

<p >
While PeachPy doesn't appear to be actively maintained,
the <a href="https://github.com/mmcloughlin/avo" target="_blank">Avo</a> assembly generator is.
And Avo <i >does</i> know that the frame pointer register needs to be saved.
Avo specifically targets Go assembly and only supports AMD64 as of this writing.
I recommend using Avo for Go assembly when you can.
I <a href="https://github.com/dgryski/go-metro/pull/10" target="_blank">opened a PR</a> to port <code >go-metro</code>'s assembly generation to Avo.
</p>

<p >
Also worth noting:
<code >go vet</code> has a check for overwriting the frame pointer register without saving it.
The check unfortunately misses this bug, though.
The check was written to be simple and conservative to avoid false positives,
and opted to stop checking a function at the first branch.
But in this case, the frame pointer is clobbered after the first branch.
I opened <a href="https://github.com/golang/go/issues/69838" target="_blank">go.dev/issue/69838</a> to track improving this check.
</p>

<div class="hover">
<h2 id="stack">Clobbering a value on the stack <a href="https://nsrip.com/posts/clobberfp.html#stack" class="hide">&sect;</a></h2>
</div>

<p >
I found the next bug while looking for occurrences of the previous bug at Datadog.
One of our programs was crashing with stack dumps like this:
</p>

<pre >SIGSEGV: segmentation violation
PC=0x69f0e0 m=30 sigcode=1 addr=0xc3ffa97ffc05

goroutine 0 [idle]:
        runtime.fpTracebackPCs(...)
        runtime.traceStack()
        runtime.traceLocker.stack(...)
        runtime.traceLocker.GoStop()
        runtime.traceLocker.GoPreempt(...)
        runtime.goschedImpl(...)
        runtime.gopreempt_m()
        runtime.mcall()

goroutine 321 [running]:
        runtime.asyncPreempt2()
        runtime.asyncPreempt()
        github.com/apache/arrow/go/v13/arrow/memory.memory_memset_neon()
        github.com/apache/arrow/go/v13/arrow/memory.Set(...)
[ ... ]
</pre>

<p >
Seeing the execution tracer and asynchronous preemption gave me flashbacks to
<a href="https://nsrip.com/posts/oneinstruction.html">"the one-instruction window"</a>.
The <code >memory_memset_neon</code> function jumped out at me.
I recognized "neon" as the <a href="https://developer.arm.com/Architectures/Neon">Neon SIMD extension for ARM</a>.
I smelled assembly and took a look at the code.
</p>

<p >
The <code >memory_memset_neon</code> function calls
<a href="https://github.com/apache/arrow-go/blob/25b3fb03afb0d3f4a52965572e8c36e655df88c1/arrow/memory/memory_neon_arm64.s#L7">this</a>
<code >_memset_neon</code> function,
implemented in assembly.
Here's the beginning of the function:
</p>

<pre >// func _memset_neon(buf unsafe.Pointer, len, c uintptr)
TEXT ·_memset_neon(SB), $0-24

        MOVD	buf+0(FP), R0
        MOVD	len+8(FP), R1
        MOVD	c+16(FP), R2

        WORD $0xa9bf7bfd // stp    x29, x30, [sp, #-16]!
        WORD $0x8b010008 // add    x8, x0, x1
        WORD $0xeb00011f // cmp    x8, x0
        WORD $0x910003fd // mov    x29, sp
        BLS	LBB0_7
[ ... ]
</pre>

<p >
This assembly is extra odd since it's (partly) automatically translated from the output of a C compiler.
The <code >WORD</code> instructions are the bytecode for machine instructions.
This is <i >usually</i> used for instructions the Go assembler doesn't yet support,
but the <a href="https://github.com/minio/c2goasm" target="_blank">assembly translator</a> probably does it for simplicity.
The original instructions are in the comments.
</p>

<p >
The first <code >stp</code> instruction is key here.
It decrements the stack pointer by 16,
then saves the current values of <code >x29</code> and <code >x30</code> on the stack
starting at the newly decremented stack pointer.
The <code >x29</code> register is the frame pointer register for ARM64.
So, it looks like this function saves the frame pointer as it should, right?
</p>

<p >
Turns out it's wrong in a non-obvious way.
The Go compiler saves the caller's frame pointer one word <i >below</i> the function's stack frame.
<sup ><a href="https://nsrip.com/posts/clobberfp.html#footnote2" id="back2">[2]</a></sup>
See <a href="https://nsrip.com/posts/oneinstruction.html">"the one-instruction window"</a> for some diagrams that illustrate how the frame is laid out.
So, the top word of a function's stack frame on entry actually belongs to the caller.
That <code >stp</code> instruction is actually saving <code >x30</code>
(the <a href="https://en.wikipedia.org/wiki/Link_register" target="_blank">link register</a>)
where the caller saved the frame pointer.
When the function returns,
it will restore its caller's frame pointer to the frame pointer register,
and that frame pointer now points to an invalid frame pointer (an instruction address from <code >x30</code>).
That invalid frame pointer will be there until the caller returns.
This includes if the caller is asynchronously preempted while the execution tracer is running.
When that happens, the frame pointer will be visited when collecting a backtrace,
hence the crash.
</p>

<p >
I <a href="https://github.com/apache/arrow-go/issues/150" target="_blank">reported</a> the bug,
and sent <a href="https://github.com/apache/arrow-go/pull/170" target="_blank">a PR</a> to fix it.
At the time I sent the PR,
I did what seemed like the least intrusive thing to fix the problem.
I decremented the stack pointer a little bit more so the caller's saved frame pointer wouldn't be clobbered.
</p>

<p >
However, while writing this post I realized there are probably better ways to deal with the problem.
For one, the functions don't even need stack frames
(see again the <code >$0</code>)
so the frame pointer and link register save/restore logic could just be eliminated.
But it can be nice to have the frame pointer set up so that you get complete stack traces from something like Linux <code >perf</code>.
</p>

<p >
It's <i >probably</i> okay to manually create the call frame and save the registers like the code does.
There will typically be room below the stack pointer in any function call <sup ><a href="https://nsrip.com/posts/clobberfp.html#footnote3" id="back3">[3]</a></sup>,
so we're unlikely to write out of bounds.
But it's better as a programmer not to rely on implementation details like that.
It would be just as easy to give each function a non-zero frame size,
even if they don't have any locals.
That way, the assembler can insert the frame pointer save and restore.
If the frame size is small enough,
and the <i >compiler/assembler</i> know that small frames are safe to allocate without stack growth,
extra bounds check or growth logic can be safely eliminated.
</p>

<p >
Of course, the underlying source of the bug is the assembly translator used for that code.
The assembly was translated from the output of a C compiler.
The C compiler isn't going to know about the requirements for Go assembly code, though.
And certainly not something like the top word of the stack frame being off-limits.
</p>

<p >
There are Apache Arrow clients for many programming languages besides just Go,
and it makes sense to try to share low-level,
performance-sensitive code where possible,
rather than writing the same primitives multiple times.
And calling assembly functions is much, much cheaper than calling C functions.
So I can understand why that approach was taken.
PeachPy even tried to do something similar,
supporting generating assembly for several different targets from a single script.
Unfortunately there doesn't seem to be a tool that supports that well right now.
</p>

<div class="hover">
<h2 id="abi">ABIs and calling conventions <a href="https://nsrip.com/posts/clobberfp.html#abi" class="hide">&sect;</a></h2>
</div>

<p >
These kinds of rules for assembly-level code&mdash;which registers are special,
when registers need to be saved and restored,
how to pass arguments to and return values from functions,
the exact layout of call frames and other data&mdash;are part of an "application binary interface" (ABI).
There are many ABIs,
even for a single processor architecture:
The <a href="https://wiki.osdev.org/System_V_ABI" target="_blank">System V ABI</a>
is used by Linux and some other Unix-descendent operating systems.
Microsoft has its own <a href="https://learn.microsoft.com/en-us/cpp/build/x64-software-conventions?view=msvc-170" target="_blank">ABI</a>.
Programming languages can also have their own ABIs,
on top of the operating system ABIs.
</p>

<p >
As a Go programmer, you should refer to the
<a href="https://go.dev/doc/asm">Go assembly guide</a>.
This describes the <i >stable</i> ABI for Go assembly,
which is what you should target.
</p>

<p >
If you're ever looking at assembly generated by the compiler,
or hand-written assembly in the runtime,
refer to the
<a href="https://go.dev/s/regabi" target="_blank">internal ABI guide</a>.
</p>

<p >
If you're planning to write Go assembly,
be aware of the details of the Go ABI,
but also lean on tools like Avo to handle the subtle details for you so you can focus on the code you actually want to write.
</p>

<hr >

<ol >
<li id="footnote1">
With a frame pointer, the function will show up more explicitly in profiles
collect with tools like Linux <code >perf</code>.
The compiler can also omit the stack growth check for functions with very small frames,
so it's cheap to declare a small frame even if you don't need it.
<a href="https://nsrip.com/posts/clobberfp.html#back1">Back</a>
</li>
<li id="footnote2">
Contrast this with how it works on AMD64.
There, the caller's frame pointer is saved at the top of the frame,
via a <code >PUSHQ BP</code> instruction,
and restored before any return via <code >POPQ BP</code>.
This is <i >almost</i> the same as ARM64,
in the sense that the first word of the frame is a frame pointer and the word above it is a return address.
But on AMD64 that word is the caller's frame pointer,
rather than the caller's caller's frame pointer,
and the function only writes strictly within its own frame.
Frame pointer support for ARM64 was added <a href="https://go.dev/cl/61511" target="_blank">in 2018</a>.
At the time,
the Go compiler already saved the link register (<code >R30</code>) at the <i >bottom</i> of a function's call frame.
By convention, the frame pointer is stored one word below the return address.
So, for simplicity, the frame pointer is saved one word <i >below</i> the function's stack frame.
It was done this way for ARM64 for simplicity,
since the link register is already at the bottom of the frame.
As for why <i >that</i> is done,
I haven't yet found any record of the rationale.
In a sense it's similar to pushing the return address to the stack for AMD64 <code >CALL</code>.
But again, it's different for ARM64 since the return address is in a register,
and the <i >previous</i> return address goes on the stack...
<a href="https://nsrip.com/posts/clobberfp.html#back2">Back</a>
</li>
<li id="footnote3">
Sometimes known as a <a href="https://en.wikipedia.org/wiki/Red_zone_(computing)" target="_blank">"red zone"</a>.
<a href="https://nsrip.com/posts/clobberfp.html#back3">Back</a>
</li>
</ol>

]]></content:encoded>
    </item>
    <item>
      <title>
Debugging a Go runtime crash: the one-instruction window
</title>
      <link>
https://www.nsrip.com/posts/oneinstruction.html
</link>
      <description>
This post is about how I investigated a frame pointer unwinding crash in the Go runtime.
The bug was specific to 64-bit ARM:
asynchronous preemption at a specific instruction,
in certain functions,
led to a kind of use-after-free bug which caused the runtime execution tracer to crash when collecting a traceback.
The Go runtime is low-level, and can be intimidating and mysterious.
But digging into a tricky bug is, in my experience,
the best way to learn how a complex system really works.
I wrote this post to remind myself how I diagnosed the bug,
and to share some of the things I learned while doing so.
</description>
      <pubDate>Wed, 7 Aug 2024 13:00:00 EDT</pubDate>
      <guid>
https://www.nsrip.com/posts/oneinstruction.html
</guid>
      <content:encoded><![CDATA[
<title >Debugging a Go runtime crash: the one-instruction window</title>
<h1 >The one-instruction window</h1>

<p ><b >
<a href="https://nsrip.com/posts/../index.html">Home</a>
-
<a href="https://nsrip.com/posts/../feed.xml">RSS</a>
</b></p>

<p ><i >Posted 2024-08-07</i></p>

<p >
This post is about how I investigated a frame pointer unwinding crash in the Go runtime.
The bug was specific to 64-bit ARM:
asynchronous preemption at a specific instruction,
in certain functions,
led to a kind of use-after-free bug which caused the runtime execution tracer to crash when collecting a traceback.
The Go runtime is low-level, and can be intimidating and mysterious.
But digging into a tricky bug is, in my experience,
the best way to learn how a complex system really works.
I wrote this post to remind myself how I diagnosed the bug,
and to share some of the things I learned while doing so.
</p>

</p>

<div class="hover">
<h2 id="investigation">The initial investigation <a href="https://nsrip.com/posts/oneinstruction.html#investigation" class="hide">&sect;</a></h2>
</div>

<p >
A few Datadog engineers reported crashes like this (lightly edited):
</p>

<pre >SIGSEGV: segmentation violation
PC=0x471c64 m=38 sigcode=1

goroutine 0 [idle]:
        runtime.fpTracebackPCs(...)
        runtime.traceStackID()
            pc=0x471c64
        runtime.traceEventLocked()
            pc=0x4712d0
        runtime.traceEvent()
            pc=0x471030
        runtime.traceGoPreempt(...)
        runtime.gopreempt_m()
            pc=0x457270
        runtime.mcall()
            pc=0x482224
        
goroutine 549321 [running]:
        runtime.asyncPreempt2()
            pc=0x44e26c
        runtime.asyncPreempt()
            pc=0x4853ec
        golang.org/x/net/http2.(*PingFrame).Header()
            pc=0x9ab404
        golang.org/x/net/http2.(*Framer).checkFrameOrder()
            pc=0x97f854
        golang.org/x/net/http2.(*Framer).ReadFrame()
            pc=0x97f5f8
        google.golang.org/grpc/internal/transport.(*http2Client).reader()

[ ... ]
</pre>

<p >
I was pinged to help because my job at Datadog is to maintain our continuous profiling capability for Go.
My colleague Felix Geisend&ouml;rfer and I had recently worked on the <a href="https://pkg.go.dev/runtime/trace" target="_blank">Go runtime execution tracer</a>,
which is where the crash was happening.
We did this work as part of building Datadog's <a href="https://blog.felixge.de/debug-go-request-latency-with-datadogs-profiling-timeline/" target="_blank">profiling timeline feature for Go</a>,
a tool for understanding latency and finding bottlenecks.
We were running execution tracing widely in production.
This was the first time we had seen any production crashes from the tracer.
Check out Felix's talk <a href="https://www.youtube.com/watch?v=0f8JnJhC0-0" target="_blank">How To Win Frames And Influence Pointers</a>
for a lot more on our work on execution tracing.
We collaborated with the Go runtime maintainers a lot for this project.
Datadog supports contributing to open-source projects like Go.
Datadog uses Go heavily,
our customers use Go,
and I personally think the Go project is awesome.
I also love debugging.
I'm very happy to be able to do this kind of work in service of contributing to such a great project.
</p>

<p >
The crashing function, <code >runtime.fpTracebackPCs</code>,
was a function Felix and I had recently contributed to the Go runtime.
Its purpose is to efficiently collect a traceback.
It does so using a technique called <i >frame pointer unwinding</i>.
We added it specifically for the runtime execution tracer,
where traceback collection was the largest source of overhead.
Frame pointer unwinding involves maintaining a linked list of return instruction addresses (PCs),
updated as functions are called and return,
which is traversed when a traceback is requested.
</p>

<p >
Frame pointer unwinding breaks when the linked list is incorrectly manipulated.
So, my approach here was to figure out:
<ul >
        <li >Which call stack the runtime was trying to collect a traceback for</li>
        <li >How the functions on that call stack manipulate frame pointers</li>
        <li >
                How the call stack itself may have been manipulated,
                for example by the runtime scheduler or garbage collector
        </li>
</ul>

</p>

<p >
The crash traceback gives a pretty clear signal for which call stack we're unwinding:
the crash happened during a call to <code >runtime.traceGoPreempt</code>,
so we were tracing a "preempt" event.
And on top of that, we see another goroutine with <code >runtime.asyncPreempt</code> on its call stack.
So, it seems likely that we failed to unwind that goroutine's call stack.
</p>

<p >
As the name implies, <code >runtime.asyncPreempt</code> is doing "asynchronous preemption".
I had to take a slight detour to figure out what that actually is.
Austin Clements gave an excellent talk,
<a href="https://www.youtube.com/watch?v=1I1WmeSjRSw" target="_blank">Pardon the Interruption</a>,
on what asynchronous preemption is and why it exists.
Basically, a goroutine is forced to yield execution so that other work can happen.
"Asynchronous" means this can happen at basically any point in its execution.
It's implemented using signals.
A signal is delivered to a goroutine to interrupt it.
Then, instead of running the next instruction,
a call to <code >runtime.asyncPreempt</code> is simulated,
as if the goroutine called the function itself.
That function then yields the goroutine's execution.
</p>

<p >
I wondered where precisely the goroutine in question was preempted.
The full crash traceback included instruction addresses,
and I also had access to the binary of the crashing process.
The function preceding <code >runtime.asyncPreempt</code> in the traceback is <code >golang.org/x/net/http2.(*PingFrame).Header</code>.
I used <code >objdump</code> to get the instructions for that function.
Here is where the preemption happened, in bold:
</p>

<pre >% objdump --disassemble-symbols='golang.org/x/net/http2.(*PingFrame).Header' ./binary

./binary:	file format elf64-littleaarch64

Disassembly of section .text:

00000000009ab3b0 <golang.org x="None" net="None" http2.(*pingframe).header="None">:
  9ab3b0: fe 0f 1d f8  	str	x30, [sp, #-48]!
  9ab3b4: fd 83 1f f8  	stur	x29, [sp, #-8]
  9ab3b8: fd 23 00 d1  	sub	x29, sp, #8
<i >[ ... body elided ... ]</i>
  9ab400: ff c3 00 91  	add	sp, sp, #48
<b >  9ab404: fd 23 00 d1   sub  x29, sp, #8 </b>
  9ab408: c0 03 5f d6  	ret
<i >[ ... ]</i>
</pre>

<p >
The <a href="https://go.googlesource.com/go/+/refs/tags/go1.22.5/src/cmd/compile/abi-internal.md" target="_blank">Go internal ABI</a>
specifies how function calls and call frames work for 64-bit ARM.
Here's what the code does:
<ul >
<li ><code >str x30, [sp, #-48]!</code>
    <ul ><li >Simultaneously allocate a 48-byte stack frame and save the link register at the bottom of the new frame</li></ul>
</li>
<li ><code >stur x29, [sp, #-8]</code>
    <ul ><li >Save the caller's frame pointer register (<code >x29</code> on ARM) in the word below the new stack frame</li></ul>
</li>
<li ><code >sub x29, sp, #8</code>
    <ul ><li >Update the frame pointer register to point to where the previous one was just saved</li></ul>
</li>
</ul>
</p>

<p >
Here's a sketch of the state of the goroutine's stack at this point:
</p>

<img src="https://nsrip.com/posts/oneinstruction-1.svg" class="center">

<p >
Note that frame pointers are saved below the function's call frame.
So the first value is the frame pointer saved by the <code >Header</code> function's caller,
and the "saved frame pointer" value is the one saved by the <code >Header</code> function itself.
</p>

<p >
Then at the end of the function, we have the following cleanup:
<ul >
<li ><code >add sp, sp, #48</code>
    <ul ><li >Free the stack frame</li></ul>
</li>
<li ><code >sub x29, sp, #8</code>
    <ul ><li >Restore the frame pointer register to its previous value <sup ><a href="https://nsrip.com/posts/oneinstruction.html#footnote1" id="back1">[1]</a></sup></li></ul>
</ul>
</p>

<p >
The function was interrupted after freeing the stack frame,
but before restoring the frame pointer register.
Now the call stack looks like this:
</p>

<img src="https://nsrip.com/posts/oneinstruction-2.svg" class="center">

<p >
So, the frame pointer register points into now-freed stack memory.
This seems problematic!
What if the preemption handler uses some of that memory?
</p>

<p >
At this point I had basically completely identified the bug.
I was quite lucky to have gotten so far with just a crash traceback and the binary.
These kinds of bugs tend to be much more difficult to make a dent in.
I could have packaged this up into a bug report and sent it to the Go team,
and one of the experienced maintainers would have probably quickly found the root cause.
</p>

<div class="hover">
<h2 id="reproducer">Reproducing the crash <a href="https://nsrip.com/posts/oneinstruction.html#reproducer" class="hide">&sect;</a></h2>
</div>

<p >
However, I suffered the all-too-common debugger's curse: tunnel vision.
We can get fixated on a possible explanation,
or a particular aspect of the bug that takes us far off the path to a solution.
This curse can also be a gift:
we can learn a lot in the struggle to understand something,
and we can sometimes find impactful ideas that go deeper than the original problem.
I learned some things in the process that will no doubt help me in the future.
But I could have just as easily burned all of my energy on a dead end.
</p>

<p >
I wanted to recreate the crash reliably under a debugger.
The <code >golang.org/x/net/http2.(*PingFrame).Header</code> showed up in basically every instance of this crash.
I was fixated on this exact function getting preempted at that exact instruction.
At the time I was debugging this,
once I found out where the preemption was happening,
I jumped straight into a debugger and tried to reproduce the crash.
It wasn't until later that I more carefully considered the state of the frame pointers at that particular point in the function.
</p>

<p >
The <code >Header</code> function just returns a struct.
<a href="https://cs.opensource.google/go/x/net/+/refs/tags/v0.27.0:http2/frame.go;l=174;bpv=0;bpt=0" target="_blank">Here's the source</a>.
I created a program with a basically identical struct,
and a function that returns the struct.
I then inspected the compiled output and confirmed the instructions were basically the same as the function from the crash.
I wrote a test which calls the function in a tight loop,
and then ran the test with the execution tracer enabled,
hoping that <i >eventually</i> the function would get preempted at the same instruction.
And... no crash.
Here's the code:
</p>

<pre >package fpcrash

import "testing"

type BigStruct struct {
        a bool
        b, c uint8
        d, e uint32
}

//go:noinline
func MakeBigStruct() BigStruct {
        return BigStruct{}
}

var sink BigStruct

func TestCrash(t *testing.T) {
        for {
                sink = MakeBigStruct()
        }
}
</pre>

<p >
I decided to try and make the preemption happen myself.
For problems like this where assembly-level stuff is involved,
I prefer the <a href="https://sourceware.org/gdb/" target="_blank">GNU Debugger</a> (GDB).
I learned that GDB can <a href="https://sourceware.org/gdb/current/onlinedocs/gdb.html/Signaling.html#Signaling" target="_blank">deliver signals</a> to the program you're debugging.
From Austin's talk, I knew that the runtime uses the SIGURG signal for asynchronous preemption.
GDB can also <a href="https://sourceware.org/gdb/current/onlinedocs/gdb.html/Set-Breaks.html#Set-Breaks" target="_blank">set breakpoints</a> at specific instruction addresses.
I disassembled the function I wanted to preempt,
found the specific instruction where I wanted the preemption to happen,
and set a breakpoint there.
When the breakpoint hit,
I then delivered the SIGURG signal that triggers asynchronous preemption.
</p>

<pre >(gdb) disas 'fpcrash.MakeBigStruct' <sup ><a href="https://nsrip.com/posts/oneinstruction.html#footnote2" id="back2">[2]</a></sup>
Dump of assembler code for function fpcrash.MakeBigStruct:
   0x0000000000100830 <+0>:     str     x30, [sp, #-32]! <sup ><a href="https://nsrip.com/posts/oneinstruction.html#footnote3" id="back3">[3]</a></sup>
   0x0000000000100834 <+4>:     stur    x29, [sp, #-8]
   0x0000000000100838 <+8>:     sub     x29, sp, #0x8
   0x000000000010083c <+12>:    stur    xzr, [sp, #12]
   0x0000000000100840 <+16>:    str     wzr, [sp, #20]
   0x0000000000100844 <+20>:    ldrb    w0, [sp, #12]
   0x0000000000100848 <+24>:    mov     x1, xzr
   0x000000000010084c <+28>:    mov     x2, x1
   0x0000000000100850 <+32>:    mov     x3, xzr
   0x0000000000100854 <+36>:    mov     x4, x3
   0x0000000000100858 <+40>:    add     sp, sp, #0x20
   0x000000000010085c <+44>:    sub     x29, sp, #0x8 <b >Here's where I want to interrupt</b>
   0x0000000000100860 <+48>:    ret
   0x0000000000100864 <+52>:    udf     #0
   0x0000000000100868 <+56>:    udf     #0
   0x000000000010086c <+60>:    udf     #0
End of assembler dump.
(gdb) b *0x000000000010085c
Breakpoint 1 at 0x10085c: file /home/ec2-user/fpcrash/fpcrash_test.go, line 13.
(gdb) r -test.trace=/dev/null
Starting program: /home/ec2-user/fpcrash/fpcrash.test -test.trace=/dev/null
<i >[ ... ]</i>
Thread 1 "fpcrash.test" hit Breakpoint 1, 0x000000000010085c in fpcrash.MakeBigStruct (~r0=..., ~r0=...) at /home/ec2-user/fpcrash/fpcrash_test.go:13
13              return BigStruct{}
(gdb) signal SIGURG
Continuing with signal SIGURG.
</pre>

<p >
This still didn't crash.
I wanted to see what the sequence of frame pointers looked like.
So after hitting the first breakpoint,
I set a breakpoint in <code >runtime.asyncPreempt2</code>
and then delivered the signal.
I hit the second breakpoint, and I could now try following the frame pointers myself.
For every frame pointer address there is a return instruction address right next to it:
</p>

<pre >Thread 1 "fpcrash.test" hit Breakpoint 1, 0x000000000010085c in fpcrash.MakeBigStruct (~r0=..., ~r0=...) at /home/ec2-user/fpcrash/fpcrash_test.go:13
13              return BigStruct{}
(gdb) b 'runtime.asyncPreempt2'
Breakpoint 2 at 0x4750c: runtime.asyncPreempt2. (2 locations)
(gdb) signal SIGURG
Continuing with signal SIGURG.

Thread 1 "fpcrash.test" hit Breakpoint 2, runtime.asyncPreempt2 () at <autogenerated >:1
<i >[ ... step forward a few instructions to have runtime.asyncPreempt2 set up its call frame ... ]</i>
(gdb) p/x $x29
$2 = 0x400009fd18
(gdb) x/g 0x400009fd18
0x400009fd18:   0x000000400009fd38
(gdb) x/a 0x400009fd18+8
0x400009fd20:   0x7a2dc &lt;runtime.asyncPreempt+156&gt;
(gdb) x/g 0x000000400009fd38
0x400009fd38:   0x400009ff18
(gdb) x/a 0x000000400009fd38+8
0x400009fd40:   0x10085c &lt;fpcrash.MakeBigStruct+44&gt;
(gdb) x/g 0x400009ff18
0x400009ff18:   0x0
</pre>

<p >
The <code >x/g</code> lines dereference the given address.
This is how I'm following the frame pointer linked list.
The <code >x/a</code> lines dereference the given address and treat the value as an instruction address.
The return addresses that make up the call stack are stored 8 bytes above the frame pointers.
So, this is how I reconstruct the call stack with frame pointer unwinding.
I reached a 0 frame pointer, which makes frame pointer stop normally.
So, this explains why it's not crashing.
But the traceback is incomplete.
We should see the <code >TestCrash</code> function, at least.
Something is indeed broken!
</p>

<p >
I spent a long time making small tweaks to my program,
under the incorrect assumption that the <code >MakeBigStruct</code> method was somehow manipulating the frame pointer incorrectly.
I would tweak the code,
trigger the preemption,
and do frame pointer unwinding.
I did the unwinding step often enough by hand that it was getting tedious.
I learned that GDB can be <a href="https://sourceware.org/gdb/current/onlinedocs/gdb.html/Python-API.html#Python-API" target="_blank">scripted with Python</a>,
so I wrote a bit of code to do the unwinding:
</p>

<pre >(gdb) py
&gt;def fpunwind():
&gt;    _ptrptr = gdb.lookup_type('void').pointer().pointer()
&gt;    fp = gdb.selected_frame().read_register('x29')
&gt;    while fp != 0:
&gt;        fp = fp.reinterpret_cast(_ptrptr)
&gt;        pc = (fp+1).dereference()
&gt;        fp = fp.dereference()
&gt;        print(fp, pc)
(gdb) py fpunwind() <i ># while at a breakpoint where I think the frame pointers are good</i>
0x40000a7f38 0x10088c &lt;fpcrash.TestCrash+28&gt;
0x40000a7f58 0xc9f38 &lt;testing.tRunner+232&gt;
0x40000a7fa8 0xcad4c &lt;testing.(*T).Run.func1+44&gt;
0x0 0x79744 &lt;runtime.goexit+4&gt;
</pre>

<p >
I finally figured out what was happening when I read back through my initial investigation notes.
The asynchronous preemption handling code was going to use previously-freed stack space.
I decided to check what the code was doing.
Sure enough, the initial preemption handler code,
run on the goroutine's stack,
<a href="https://go.googlesource.com/go/+/refs/tags/go1.21.0/src/runtime/preempt_arm64.s" target="_blank">spills a bunch of registers</a>.
The preemption handler is like a normal function,
and saves the frame pointer register,
which will be the frame pointer register at the point where the function was interrupted.
So the call stack looks like this:
</p>

<img src="https://nsrip.com/posts/oneinstruction-3.svg" class="center">

<p >
I just needed to see which registers were saved around 32 bytes from the top of the preemption handler's frame,
where the frame pointer register was pointing prior to preemption.
It turned out to be some floating point registers.
My test code didn't do any floating point operations,
so those registers must have been 0!
GDB lets you <a href="https://sourceware.org/gdb/current/onlinedocs/gdb.html/Registers.html" target="_blank">set register values</a>,
so it was easy enough to force non-zero values in the registers.
With that, I was able to recreate the crash!
</p>

<pre >Thread 1 "fpcrash.test" hit Breakpoint 1, 0x000000000010085c in fpcrash.MakeBigStruct (~r0=..., ~r0=...) at /home/ec2-user/fpcrash/fpcrash_test.go:13
13              return BigStruct{}
(gdb) set $d30.s = 0xdeadbeef
(gdb) signal SIGURG
Continuing with signal SIGURG.

Thread 1 "fpcrash.test" received signal SIGSEGV, Segmentation fault.
runtime.traceStackID (mp=&lt;optimized out&gt;, pcBuf=..., skip=&lt;optimized out&gt;, ~r0=&lt;optimized out&gt;) at /home/ec2-user/sdk/go1.21.0/src/runtime/trace.go:987
987                             nstk += 1 + fpTracebackPCs(unsafe.Pointer(curgp.sched.bp), pcBuf[2:])
(gdb) disas <i >to show where we're faulting</i>
<i >[ ... ]</i>
0x0000000000069370 &lt;+544&gt;:   b       0x69350 &ltruntime.traceStackID;+512&gt;
=&gt; 0x0000000000069374 &lt;+548&gt;:   ldr     x8, [x4, #8]
0x0000000000069378 &lt;+552&gt;:   str     x8, [x6, x0, lsl #3]
<i >[ ... ]</i>
(gdb) p/x $x4
$9 = 0xdeadbeef
</pre>

<p >
Success! I modified my test program to put non-zero values in those registers.
With that, the test crashed reliably.
I had a reproducer and was ready to file a bug report.
</p>

<p >
I didn't do this at the time,
but there's another useful GDB feature that would have helped me find where the old frame pointer was being overwritten.
GDB supports <a href="https://sourceware.org/gdb/current/onlinedocs/gdb.html/Set-Watchpoints.html" target="_blank">setting watchpoints</a> on specific memory addresses.
Under the assumption that we had a frame pointer register pointing to freed stack space,
I could have set a watchpoint on the address in the frame pointer register:
</p>

<pre >Thread 1 "fpcrash.test" hit Breakpoint 1, 0x000000000010085c in fpcrash.MakeBigStruct (~r0=..., ~r0=...) at /home/ec2-user/fpcrash/fpcrash_test.go:13
13              return BigStruct{}
(gdb) p/x $x29
$11 = 0x400009af18
(gdb) watch *0x400009af18
Hardware watchpoint 4: *0x400009af18
(gdb) signal SIGURG
Continuing with signal SIGURG.

Thread 1 "fpcrash.test" hit Hardware watchpoint 4: *0x400009af18

Old value = 634680
New value = 0
runtime.asyncPreempt () at /home/ec2-user/sdk/go1.21.0/src/runtime/preempt_arm64.s:47
47              CALL ·asyncPreempt2(SB)
(gdb) disas
<i >[ ... ]</i>
0x000000000007a2d0 &lt;+144&gt;:   stp     d28, d29, [sp, #456]
0x000000000007a2d4 &lt;+148&gt;:   stp     d30, d31, [sp, #472]
=&gt; 0x000000000007a2d8 &lt;+152&gt;:   bl      0x7b280 &lt;runtime.asyncPreempt2&gt;
</pre>

<p >
So I would have seen that the memory the frame pointer register was pointing to was overwritten when the <code >d30</code> and <code >d31</code> were saved by <code >runtime.asyncPreempt</code>.
</p>

<p >
One more thing to note here:
the floating point registers weren't special here.
For my reproducer, the frame pointer register happened to point to where those registers were saved.
But that depends on the frame size of the function that's getting preempted.
The important thing is that the frame pointer register,
prior to preemption,
was pointing to stack memory that was just freed.
Any junk could go on the stack where the frame pointer register was pointing.
</p>

<div class="hover">
<h2 id="fix">The bug report, fix, and conclusions <a href="https://nsrip.com/posts/oneinstruction.html#fix" class="hide">&sect;</a></h2>
</div>

<p >
I filed <a href="https://github.com/golang/go/issues/63830" target="_blank">an issue</a> to report this bug to the Go project.
I figured the fix would be to rearrange the frame cleanup instructions.
It seemed like the fix would need to happen in the compiler,
which I don't know anything about.
I didn't want to hold up getting the problem fixed while I ramped up on the compiler.
</p>

<p >
Keith Randall, one of compiler maintainers, quickly put together <a href="https://go-review.googlesource.com/c/go/+/538635" target="_blank">a fix</a>.
The fix was to rearrange the call frame cleanup instructions,
making sure the frame pointer is restored <i >before</i> the call frame is freed.
With that, the instructions look like this:
</p>

<pre >000000000010cc10 &lt;fpcrash.MakeBigStruct&gt;:
        10cc10:       f81e0ffe        str     x30, [sp, #-32]!
        10cc14:       f81f83fd        stur    x29, [sp, #-8]
        10cc18:       d10023fd        sub     x29, sp, #0x8
<i >[ ... ]</i>
        10cc38:       910063fd        add     x29, sp, #0x18
        10cc3c:       910083ff        add     sp, sp, #0x20
        10cc40:       d65f03c0        ret
</pre>

<p >
And if the function were preempted at the next-to-last instruction,
the call stack would look like this,
and the frame pointer register would be safe to use:
</p>

<img src="https://nsrip.com/posts/oneinstruction-4.svg" class="center">

<p >
I was surprised at first not to see my reproducer (or something similar) as a regression test in the fix.
But my reproducer might not have actually been robust enough.
It reliably crashed <i >eventually</i> on my machine each time I ran it.
It can't run forever in CI, though.
How long would the actual test wait until it could declare no bug?
And would the test catch the same general kind of bug if it was reintroduced elsewhere,
or if the runtime changed substantially?
I wonder if there is a more formal, model-based way to check for the correctness of frame pointers.
We can say certain qualities frame pointers should have:
they should point to the value 0 or another valid frame pointer,
there should be a return address stored at a fixed offset from where the frame pointer points,
and a signal handler which wants to get a traceback should not observe the frame pointers in an inconsistent state.
Perhaps we could build a tool to check these things comprehensively for a compiled binary?
</p>

<p >
I learned about several useful GDB features:
delivering signals, breaking at specific instructions, scripting with Python.
I learned about how preemption works,
improving my mental model for the control flow of Go programs.
I also understood a little more of the 64-bit ARM calling conventions.
</p>

<p >
I lost a lot of time to tunnel vision.
Once I caught the scent of a possible cause for the crash,
I jumped to a conclusion about where the problem was.
I didn't fully digest what I saw based on the assembly I was reading,
and overlooked a crucial fact
(the function freed the frame before restoring the frame pointer),
only revisiting it after hours of fruitless debugging.
Debugging is about pinpointing the difference between expectation and reality.
We should be thorough:
we should lay out how we expect the system to work,
take a full inventory of what we've observed,
and then focus on the gaps in our understanding or observations.
I like how Bryan Cantrill put it in his talk <a href="https://www.janestreet.com/tech-talks/hurricanes-butterfly/" target="_blank">"The Hurricane's Butterfly"</a>.
</p>

<blockquote >
<p >
It can be very tempting to think about debugging as the process
of forming hypotheses. That is not what debugging is. Hypotheses are... Yes, a
hypothesis is going to be a critical stage to debugging. It is the last stage of
debugging.
</p>

<p >
When you debug, you should be iterating between questions and answers, questions
and answers, questions and answers. And as you're iterating, the possible space
of hypotheses gets smaller and smaller and smaller, until you don't have a
choice.
</p>
</blockquote>

<p >
<i >
Thanks to Daniel Schwartz-Narbonne, Matt Alp, and Felix Geisend&ouml;rfer for reviewing this post.
Special thanks to Felix for suggesting visualizations for the call stack state.
</i>
</p>

<hr >

<ol >
<li id="footnote1">
You might have noticed that this instruction is not reading the value previously saved on the stack.
It happens to still be correct,
because the caller's frame pointer would have pointed to just below the caller's stack frame,
which is where <code >sp</code> now points!
<a href="https://nsrip.com/posts/oneinstruction.html#back1">Back</a>
</li>
<li id="footnote2">
Go symbol names have characters like "." and "/" that confuse GDB.
You need to enclose the names in quotation marks.
<a href="https://nsrip.com/posts/oneinstruction.html#back2">Back</a>
</li>
<li id="footnote3">
You might notice the stack frame is 32 bytes rather than 48.
I'm not totally sure where the difference comes from.
In the real code,
the <code >Header</code> function comes from one struct embedded in another.
Felix suggests that a method for the embedded struct might be generated,
with a bigger call frame to account for the other fields of the struct,
if the struct is used via an interface?
<a href="https://nsrip.com/posts/oneinstruction.html#back3">Back</a>
</li>
</ol>
]]></content:encoded>
    </item>
  </channel>
</rss>
