Is `go build` deterministic?

1,398 views
Skip to first unread message

Rio

unread,
Dec 3, 2014, 9:43:29 PM12/3/14
to golan...@googlegroups.com
In other words, given the same source files and same compiling flags and environment variables (GOOS, GOARCH and friends), is the binary produced by `go build` (of same version) the same on different machines? 

Caleb Spare

unread,
Dec 3, 2014, 9:48:08 PM12/3/14
to Rio, golang-nuts
The binaries will not be the same. For instance, machine-specific paths (like you see in a stack trace) are baked in.

That's not the same as being nondeterministic, though.

On Wed, Dec 3, 2014 at 6:43 PM, Rio <m...@riobard.com> wrote:
In other words, given the same source files and same compiling flags and environment variables (GOOS, GOARCH and friends), is the binary produced by `go build` (of same version) the same on different machines? 

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Riobard Zhan

unread,
Dec 3, 2014, 9:51:26 PM12/3/14
to Caleb Spare, golang-nuts
What if paths are kept the same, too? Or are there other
machine-specific data involved?

brainman

unread,
Dec 3, 2014, 10:14:34 PM12/3/14
to golan...@googlegroups.com
Windows executable has build time stamp in it.

Alex

Riobard Zhan

unread,
Dec 3, 2014, 10:17:56 PM12/3/14
to brainman, golang-nuts
Ah, I see. So the binaries are not predictable…

Thanks a lot!

On Wed, Dec 3, 2014 at 11:44 PM, brainman <alex.b...@gmail.com> wrote:
> Windows executable has build time stamp in it.
>
> Alex
>
> --
> You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/8zXtSj8-YjQ/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

Dave Cheney

unread,
Dec 3, 2014, 11:03:10 PM12/3/14
to golan...@googlegroups.com
To ask a slightly different question, what would it enable you to do if multiple go builds produced identical output ?

Riobard Zhan

unread,
Dec 3, 2014, 11:11:13 PM12/3/14
to Dave Cheney, golang-nuts
Hi Dave,

I was hoping to establish some kind of correspondence between the
source and the binaries, so I could say if you have a particular
binary, you can compare its SHA1 hash against a known list and be
certain that the binary is compiled from a particular version of
source code.

Or is my approach fundamentally flawed? Code signing is definitely a
better approach, but I was hoping maybe I could avoid the complexity
of the signing if the build process is totally deterministic (I'm not
even sure if that's the right terminology).

However since the built output differs each time, there goes my plan
into the toilet :(

Dave Cheney

unread,
Dec 3, 2014, 11:15:33 PM12/3/14
to Riobard Zhan, golang-nuts
I think, but I really have never looked to check, go binaries support
something called the build-id, which is something redhat/fedora use
(apologies if I have mischaracterised this) to do something like this,
or at least tie a final binary back to a set of debug symbols.

Sorry, that's a pretty vague hint. Hopefully someone else can do better.

Qian Qiao

unread,
Dec 3, 2014, 11:27:09 PM12/3/14
to Dave Cheney, Riobard Zhan, golang-nuts
On Wed Dec 03 2014 at 11:15:07 PM Dave Cheney <da...@cheney.net> wrote:
I think, but I really have never looked to check, go binaries support
something called the build-id, which is something redhat/fedora use
(apologies if I have mischaracterised this) to do something like this,
or at least tie a final binary back to a set of debug symbols.

Sorry, that's a pretty vague hint. Hopefully someone else can do better.


-- Joe 

andrewc...@gmail.com

unread,
Dec 3, 2014, 11:29:59 PM12/3/14
to golan...@googlegroups.com
It means signed tarballs of the compiler and original source are enough to check if a binary has been tampered with.

You could use it to detect miscompiles across Go versions. One use I can think of is after translating the compiler source to Go in 1.5, if there were no other code changes, you could easily verify bugs in the compiler via sha1sums.

Riobard Zhan

unread,
Dec 3, 2014, 11:39:51 PM12/3/14
to Qian Qiao, Dave Cheney, golang-nuts
Thanks Qian and Dave for the pointer!

Just checked the build-id idea. I _think_ it's similar to what I did
with boot2docker-cli
(https://github.com/boot2docker/boot2docker-cli/blob/master/Makefile#L4).
Basically I inject the current git hash into the binary during the
build process.

However, it's not completely reliable as nothing stops somebody else
from injecting the same hash into a different binary. Ditto for the
build-id.

Or did I miss anything?

minux

unread,
Dec 4, 2014, 1:12:28 AM12/4/14
to Riobard Zhan, Dave Cheney, golang-nuts
On Wed, Dec 3, 2014 at 11:10 PM, Riobard Zhan <m...@riobard.com> wrote:
I was hoping to establish some kind of correspondence between the
source and the binaries, so I could say if you have a particular
binary, you can compare its SHA1 hash against a known list and be
certain that the binary is compiled from a particular version of
source code.
I think pure Go build (i.e. no cgo, not even from the standard packages, net and os/user)
with the same GOROOT should produce identical binaries (at least on Unix, as brainman
has said, windows binaries have timestamp in the header.)

I did put some efforts into making cgo build also deterministic, but perhaps due to lack
of tests, that functionality bit-rotted (basically the problem is that the WORK directory
leaks into cgo binaries)

Dave Cheney

unread,
Dec 4, 2014, 1:19:51 AM12/4/14
to minux, Riobard Zhan, golang-nuts
It's an interesting idea to compare the binary outputs of the 1.4 and
1.5 compilers. I'm not sure how feasible it will be, all it takes is
one bug fix, one "wow, how did that ever work" during conversion, and
the whole plan is scuttled.

Andrew Wilkins

unread,
Dec 4, 2014, 1:44:48 AM12/4/14
to golan...@googlegroups.com, m...@riobard.com, da...@cheney.net
On Thursday, 4 December 2014 14:12:28 UTC+8, minux wrote:

On Wed, Dec 3, 2014 at 11:10 PM, Riobard Zhan <m...@riobard.com> wrote:
I was hoping to establish some kind of correspondence between the
source and the binaries, so I could say if you have a particular
binary, you can compare its SHA1 hash against a known list and be
certain that the binary is compiled from a particular version of
source code.
I think pure Go build (i.e. no cgo, not even from the standard packages, net and os/user)
with the same GOROOT should produce identical binaries (at least on Unix, as brainman
has said, windows binaries have timestamp in the header.)

You also need to strip debug info (unless you have all the source files in the same locations?)
This is how bootstrapped llgo is/was verified against gccgo:

Marc-Antoine Ruel

unread,
Dec 4, 2014, 9:26:58 AM12/4/14
to Dave Cheney, minux, Riobard Zhan, golang-nuts
[For inspiration only]

For chromium, we wrote a silly python script, which could easily be written in Go instead. You can't use it as-is because it's tuned for chromium but it gives the idea. At first we had to fight a lot of epoch and __DATE__ hence the line based diff in diff_binary(). It was surprisingly useful in the process so that's the first thing I recommend you to implement.

Here's an example output:

The way we currently do is:
  1. checkout
  2. clean
  3. build
  4. mv output to .1
  5. clean
  6. build
  7. diff output output.1
Things that were necessary:
  • Continuous integration testing to catch regressions. Don't even start without this.
  • Whitelist so we can catch regressions as we are making progress. The end goal is to have an empty whitelist but it's a long road.
  • An owner who cares to make progress. For example NaCl was and OSX is still non-trivial in our case.
  • Sadly, post-build step to remove non determinism. E.g. timestamp in Android's APK and GUIDs in PEs&PDBs. Hopefully not necessary for you guys.
Thanks,

M-A

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Nate Finch

unread,
Dec 4, 2014, 3:47:51 PM12/4/14
to golan...@googlegroups.com, da...@cheney.net, mi...@golang.org, m...@riobard.com
Binary equivalence for repeated builds would be very useful... it would let you know for sure that when you're testing an old build of your product, that it definitely is the same as the one in production (or, conversely, that the one the customer is using is definitely the same as the released version).

I believe this could be useful in our CI for Juju... I remember talking about whether two builds from two different machines could be simply diffed to see if they were identical.  I forget exactly why, though.

It seems like it should be trivial to make this possible (ideally without requiring the same GOPATH, but even if it does require that, that's easily worked around).

And why do we need timestamps in the windows build?  That seems like something easy enough to discard (but I don't know the details obviously).

minux

unread,
Dec 4, 2014, 4:10:38 PM12/4/14
to Nate Finch, golang-nuts, Dave Cheney, m...@riobard.com
On Thu, Dec 4, 2014 at 3:47 PM, Nate Finch <nate....@gmail.com> wrote:
Binary equivalence for repeated builds would be very useful... it would let you know for sure that when you're testing an old build of your product, that it definitely is the same as the one in production (or, conversely, that the one the customer is using is definitely the same as the released version).
OK, it seems there are enough people besides me to care about repeatable builds. I've filed an issue:
golang.org/issue/9206, and will work on it when 1.5 windows reopens and that issue is accepted. 

It seems like it should be trivial to make this possible (ideally without requiring the same GOPATH, but even if it does require that, that's easily worked around).
I don't think GOPATH or GOROOT could be eliminated, due to the need for debugging information and
accurate stack traces.

And why do we need timestamps in the windows build?  That seems like something easy enough to discard (but I don't know the details obviously).
It's in the PE header, even if we don't generate them, when windows finally gets external linking support,
the external linker will set it. To compile windows binaries, just ignore the header(s).

Qian Qiao

unread,
Dec 4, 2014, 7:44:44 PM12/4/14
to minux, Nate Finch, golang-nuts, Dave Cheney, m...@riobard.com


On Thu Dec 04 2014 at 4:10:32 PM minux <mi...@golang.org> wrote:
<snip>

It seems like it should be trivial to make this possible (ideally without requiring the same GOPATH, but even if it does require that, that's easily worked around).
I don't think GOPATH or GOROOT could be eliminated, due to the need for debugging information and
accurate stack traces.

Is it possible to leave the variables in instead of using absolute paths in this case? Or will GDB or any other debuggers choke in this case?

-- Joe

minux

unread,
Dec 4, 2014, 7:52:58 PM12/4/14
to Qian Qiao, Nate Finch, golang-nuts, Dave Cheney, Riobard Zhan
I think allowing file paths to reference environment variable might pose a security issue for the debugger.
Not to mention that you might install multiple version of the same package in GOPATH, and GOPATH is
a list of paths, not a single path.

Qian Qiao

unread,
Dec 4, 2014, 8:14:31 PM12/4/14
to minux, Nate Finch, golang-nuts, Dave Cheney, Riobard Zhan
Good points.

I suppose the following compilation flow would generate a binary with more consistent debug info:
1. moves all the relevant source files into a staging directory
2. create a chroot jail at the staging directory
3. do the actual compilation using GOPATH="/"

It just feel very hack-ish and complicated, I'm sure there are better ways.

-- Joe

Mike Rosset

unread,
Dec 4, 2014, 8:39:46 PM12/4/14
to Qian Qiao, minux, Nate Finch, golang-nuts, Dave Cheney, Riobard Zhan
You can also use something like git tag -s and git tag -v on checkout. This will ensure source consistency and trust.

--
Reply all
Reply to author
Forward
0 new messages