[HN Gopher] Building static binaries with Go on Linux
       ___________________________________________________________________
        
       Building static binaries with Go on Linux
        
       Author : ingve
       Score  : 158 points
       Date   : 2024-07-30 22:07 UTC (1 days ago)
        
 (HTM) web link (eli.thegreenplace.net)
 (TXT) w3m dump (eli.thegreenplace.net)
        
       | SpecialistK wrote:
       | Very interesting and well described! If I were to have one
       | nitpick, it would be the use of "Unix" when it's more specific to
       | Linux. Ex. "The libc typically used on Unix systems is glibc"
       | However I'm sure all of the concepts still apply on BSD, Solaris,
       | etc.
        
         | Onavo wrote:
         | Go is also famous for bypassing libc and issuing syscalls
         | directly on quite a few platforms.
        
           | galangalalgol wrote:
           | I thought I'd read they backed off of that and started using
           | posix as an abstraction layer.
        
             | bradfitz wrote:
             | Go used to do raw syscalls on macOS but changed. Same with
             | some BSDs.
             | 
             | Go always did ~libc on Windows (and Solaris) and still
             | does.
             | 
             | Go still does raw syscalls on Linux, as that's a stable
             | ABI.
        
               | pjmlp wrote:
               | libc is a UNIX concept, as the OS API surface, as
               | described by POSIX.
               | 
               | On any other no-UNIX derived OS, it is the C compiler
               | standard library, covering only the ISO C specifies.
               | 
               | All the remaining OS services are exposed by other
               | libraries, which in Windows case, the bare minimum is
               | user, kernel and gdi dlls for the Win32 personality.
               | 
               | Systems like IBM i, IBM z, ClearPath MCP, .... also have
               | similar set of libraries, as do non-POSIX RTOS for
               | embedded.
        
       | sbstp wrote:
       | I feel like there's a lot of potential between Go and
       | Cosmopolitan libc. Go itself does not use libc much, as shown in
       | the blog, but some great libraries like SQLite3 need it (unless
       | you use https://pkg.go.dev/modernc.org/sqlite).
       | 
       | The ability to build a single static binary that works on Linux,
       | Mac and Windows using Go would be life changing for the internal
       | tools I develop at work.
        
         | latchkey wrote:
         | > _The ability to build a single static binary that works on
         | Linux, Mac and Windows using Go would be life changing for the
         | internal tools I develop at work._
         | 
         | Just curious, life changing in what way? Obviously, 1 is better
         | than 3, but I'm wondering if there is some other interesting
         | reason.
        
         | oguz-ismail wrote:
         | I recall reading about new Macs with ARM chips not supporting
         | static binaries. Is it not true?
        
           | zamadatix wrote:
           | That's been Apple's stance on full static linking MacOS in
           | general, many years prior to the move to ARM e.g. https://dev
           | eloper.apple.com/library/archive/qa/qa1118/_index...
           | 
           | You're welcome to ignore it of course, it's just unofficial
           | and a large pain.
        
             | oguz-ismail wrote:
             | >You're welcome to ignore it of course
             | 
             | How do you mean? Like, is it possible to run such binaries
             | on M1? If so I'd really like to know how
        
               | telotortium wrote:
               | You can always disassemble libc and look for the system
               | call numbers used by the syscall assembly instructions.
               | It's just that these numbers (and associated arguments
               | and return values) are not stable and can and do change
               | upon kernel updates (in which case libc will be updated
               | to keep the libc interface stable). I believe Linux is
               | the only major OS these days to guarantee binary
               | compatibility of the syscall interface.
        
               | oguz-ismail wrote:
               | I know this works on Macs with Intel chips. But the ones
               | with ARM chips just won't execute fully static binaries,
               | and I'm wondering if there's a workaround.
        
               | saagarjha wrote:
               | Nope.
        
               | oguz-ismail wrote:
               | Thanks. That sucks
        
               | saagarjha wrote:
               | If you can convince Apple to change this code let me
               | know: https://github.com/apple-oss-
               | distributions/xnu/blob/94d3b452...
        
               | telotortium wrote:
               | I'm guessing not. According to man ld on macOS, the
               | -static flag, to produce a fully static executable, is
               | only used to build the kernel. I don't believe fully-
               | static executables were ever officially supported on
               | macOS, although they would work.
        
               | jcelerier wrote:
               | It only works if you don't ever upgrade macOS. Even a
               | patch update sometimes can break it.
        
               | zamadatix wrote:
               | For clarity it's not the chip/ARM that causes the
               | limitation, you can recompile the kernel (it's open
               | source) to remove the block and it'll work fine - it's
               | just a ton of work.
               | 
               | Alternatively, Linux :).
        
           | cyberax wrote:
           | All Macs don't support static binaries. That's because the
           | syscall interface on macOS is not stable, only libc is
           | guaranteed to be stable.
        
         | fsmv wrote:
         | The same exact binary working isn't going to happen without
         | runtime performance penalties because the syscall numbers are
         | different on different platforms. Also I believe on windows
         | it's not possible to avoid linking some system libraries to use
         | windows.h stuff, there is no stable ABI.
        
           | pjmlp wrote:
           | Linux is the exception among modern OSes to have a stable
           | syscall ABI, everyone else offers only the proper OS API as
           | entry point into OS services.
           | 
           | Once upon at time, static linking was the only thing OSes
           | were capable of, all of them moved away from that, and there
           | is no turning back outside embedded bare metal deployments,
           | just become some people are obsessed with static linking.
        
           | actionfromafar wrote:
           | Though msvcrt.dll has a stable subset of functions available
           | on all Windows versions.
        
         | spease wrote:
         | Hmmm...what about compiling to wasm, and/or then converting the
         | wasm to C?
         | 
         | https://github.com/wasm3/wasm3/releases/tag/v0.4.8
         | 
         | https://github.com/WebAssembly/wabt/tree/main/wasm2c
        
           | daenney wrote:
           | Has been done and works pretty great:
           | https://github.com/ncruces/go-sqlite3.
           | 
           | Though doesn't convert the WASM to C, it runs the WASM in
           | Wazero instead.
        
             | spease wrote:
             | How does that solve what the person I replied to was asking
             | for?
        
               | ncruces wrote:
               | If I understood you, it doesn't help much, no, but
               | neither does what you suggested.
               | 
               | You're suggesting compiling Go to Wasm (presumably using
               | the wasip1 target?), then converting that to C using
               | wabt, then using Cosmopolitan to create an APE... is that
               | it?
               | 
               | Well, that's not going to work.
               | 
               | First of all, Go's wasip1 target doesn't even support
               | cgo, so if you want SQLite, you're dead right there.
               | 
               | Then, even if you used say TinyGo (which might support
               | cgo, not sure), WASI just isn't a great target to compile
               | SQLite into. WASI is a pretty limited syscall layer.
               | You'd end up with no file locking, no shared memory. Also
               | no threads.
               | 
               | Then, on top of that, you'd layer Cosmopolitan issues.
               | Having written a portable SQLite VFS from scratch, I am
               | not impressed with how they just paper over file locking
               | semantics incompatibilities between OSes, and then
               | confidently ship a forking webserver with SQLite bundled
               | in. It takes a certain temerity, and not running many
               | SQLite torture tests.
               | 
               | Wasm as an intermediate target is great for (single
               | threaded) CPU stuff. WASI is great if you can fit it, but
               | otherwise, it's not, not really.
        
       | wwarner wrote:
       | There's also Filippo Valsorda linking directly to Rust via .a
       | files. https://words.filippo.io/rustgo/
        
       | bradfitz wrote:
       | Careful. We (Tailscale) tried to use static Go binaries a year or
       | two ago built with Zig (zig cc) and the SQLite performance was
       | atrocious. It passed all our tests but it didn't survive
       | deploying to prod. It was a very quick (and uneventful) rollback
       | at least.
       | 
       | (Needless to say, we have better load testing tooling now)
       | 
       | I forget the details, but something about the libc allocator used
       | by SQLite-with-Zig-libc being ... not good.
        
         | eliben wrote:
         | I wonder if it's Zig or musl that's to blame; did you end up
         | statically linking with musl using musl-gcc, or did you forego
         | static linking entirely?
        
           | jcelerier wrote:
           | It's well-known that musl is in general much, much slower
           | than glibc. People keep rediscovering that for some reason,
           | likely because they hear of stuff like old echoes of Usenet
           | posts ranting against glob "bloat", not being aware that a
           | lot of what people call bloat is specialization of a lot of
           | performance-sensitive algorithms to leverage SIMD, special-
           | casing, math optimisations, etc. When you check the glibc
           | mailing lists, it's obvious performance is a predominant
           | concern.
        
         | tuxxi wrote:
         | I've experienced the same performance issues with cgo + a
         | library compiled with zig cc. IIRC it seemed like an issue with
         | the zig tooling not plumbing the optimization flags through the
         | ancient autotools build system for our required dependency.
         | After a while fiddling, we just rolled it back too.
         | 
         | I haven't tried this in about a year, so maybe the tooling
         | doesn't have these issues now.
        
         | JackYoustra wrote:
         | I've heard you can just swap out allocators pretty easily - did
         | something prevent this? Or perhaps its not as straightforward
         | as I've thought...
        
         | dylanh wrote:
         | This sounds like the musl allocator. Using mimalloc or jemalloc
         | would probably fair a lot better.
        
           | tarruda wrote:
           | Is this a problem in distributions that use musl as the
           | system libc (Alpine) ?
        
       | daenney wrote:
       | In the specific case of SQLite, you can use it through WASM now
       | [1]. It uses the dependency and Cgo-free Wazero runtime.
       | 
       | Performance so far has been better than the modernc transpile and
       | it's probably sufficient for a lot of use cases.
       | 
       | [1] https://github.com/ncruces/go-sqlite3
        
         | acheong08 wrote:
         | Why not https://pkg.go.dev/modernc.org/sqlite
        
           | kyrra wrote:
           | Performance is likely part of it. The WASM solution looks
           | faster than modernc: https://github.com/cvilsmeier/go-sqlite-
           | bench
        
             | ncruces wrote:
             | Author here.
             | 
             | Faster was a by product. Maintainability was the goal.
             | 
             | API coverage, ergonomics, extensibility all rank higher in
             | my book than performance.
             | 
             | An example I'd like to cite is sqlite-vec. Alex was able to
             | build a Cgo-free version of it, on his own, which works
             | fine with my bindings. This would be much harder to do with
             | modernc.
             | 
             | https://github.com/asg017/sqlite-vec-go-bindings
             | 
             | I'm also adding support for building off the bedrock branch
             | (begin concurrent, wal2). You just build the branch with
             | wasi-sdk, then embed the resulting blob.
        
           | daenney wrote:
           | The WASM solution doesn't rely on a custom libc or transpiler
           | to convert C code to Go. The transpile is an amazing feat of
           | engineering, but it's hard to debug.
           | 
           | I can wrap my head around the small amount of wrapping the
           | go-sqlite3 WASM library does. If I had to I can maintain that
           | should the maintainer lose interest. I can't say the same for
           | the modernc transpile. You can also apply the WASM trick to
           | other libraries with much less effort.
           | 
           | And as noted, it seems to be performing better. As the wasm
           | runtime improves it should pull further ahead.
        
       | acatton wrote:
       | The secret with sqlite is to use "-tags
       | sqlite_omit_load_extension", if you don't use any extension.
       | (which is 99% of the users)
       | 
       | This is explained in https://www.arp242.net/static-go.html
        
       | nrvn wrote:
       | Producing static PIE binaries is a bigger challenge still.
       | $ go build -ldflags '-linkmode external -s -w -extldflags "--
       | static-pie"' -buildmode=pie -tags 'osusergo,netgo,static_build'
       | main.go
       | 
       | For anyone curious to delve into what is this and why:
       | https://www.leviathansecurity.com/blog/aslr-protection-for-s...
        
       | moondev wrote:
       | What's the best way to include the go runtime itself, as in
       | ability to invoke the "go" program from the program itself . I'm
       | not talking about embedding it or downloading it. I want it
       | included within the program.
        
         | diggan wrote:
         | How are you supposed to include it within the program without
         | somehow "embedding" it? Or am I missing some vital
         | understanding of what "include" vs "embedding" means here?
        
           | moondev wrote:
           | By embedding it, I mean using the embed feature to pack the
           | golang binary into the program. What I am going after is
           | similar to kubectl and kustomize. The kustomize source code
           | included with kubectl, it's not a binary packed in and
           | extracted
        
       ___________________________________________________________________
       (page generated 2024-07-31 23:01 UTC)