I used to joke that the day setting up a cross-compilation environment was easy we'd be one short step away from having true artificial general intelligence. For the most part neither has happened yet. However, I must admit that Go has come pretty close to making it easy, but it's also kind of opaque unless you go all-in on Go to the exclusion of all other languages. It's not really a language that you can just toy around with, kind of like FORTH.
Long-time readers know that I'm all about XMPP as a command and control channel for my exocortex. However, when it comes to embedded environments (like my networking hardware) I've had to resort to some pretty nasty hacks to get any kind of monitoring on those machines. I periodically hunt for any kind of command-line XMPP client to play with, in the somewhat futile hope that I'll find one that'll run on OpenWRT without needing to fill up what little storage space there is on the unit with lots of dependencies. I did find one implemented as a couple of shell scripts but even after lots of hacking I was never able to get it to work.
Then, one day, I got lucky and found go-sendxmpp, a command line tool that logs into an XMPP server and can send and receive messages. You can even have it follow a source of data and send whatever comes down the pike. It was even pretty easy to compile on Windbringer to play around with, but then the question of how to cross-compile it for another hardware platform came up. My home wireless routers have ARMv7 processor cores, and the wireless bridge on the batphone has a MIPS core. Windbringer is a 64-bit Intel machine. So... how do you compile Go code on one hardware platform for a different one?
As it turns out this is surprisingly easy to do. Rather than setting up an entirely different toolchain you can set a couple of environment variables and the Go compiler will produce a fully operational executable for that hardware platform. First, though, you have to figure out what hardware platform you're building for. I SSH'd into the closest wireless router and queried the Linux kernel directly:
root@wednesday:~# head /proc/cpuinfo processor : 0model name : ARMv7 Processor rev 1 (v7l)BogoMIPS : 1332.00Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpd32 CPU implementer : 0x41CPU architecture: 7CPU variant : 0x4CPU part : 0xc09CPU revision : 1
ARMv7. Now we know what we're dealing with. Now to check out go-sendxmpp, following the instructions to do so:
{15:05:59 @ Sun Feb 21}[drwho @ windbringer ~] () $ go get salsa.debian.org/mdosch/go-sendxmpp
Here's the tricky part: Actually compiling. This is the command I used:
{15:06:01 @ Sun Feb 21}[drwho @ windbringer ~] () $ GOOS=linux GOARCH=arm GOARM=7 go build salsa.debian.org/mdosch/go-sendxmpp
GOOS=linux
- Compiling for a Linux.GOARCH=arm
- Compiling for an ARM processor core.GOARM=7
- It's running a version 7 core.
Because you are cross-compiling the Go compiler won't put its output in the place one might expect (i.e., $GOPATH/bin), it'll put it in the directory you happen to be sitting in:
{15:15:45@SunFeb21}[drwho@windbringer~]()$ls-ltr|tail-5drwxr-xr-xdrwhousers464KBSatFeb2021:12:512021graphicsdrwxr-xr-xdrwhousers68KBSatFeb2021:22:202021mp3drwxr-xr-xdrwhousers16KBSatFeb2021:23:542021videodrwxr-xr-xdrwhodrwho4KBSunFeb2115:05:522021go.rwxr-xr-xdrwhodrwho6.3MBSunFeb2115:07:242021go-sendxmpp
You can be sure that it's a cross-compiled binary by either trying to run it, or by asking the OS what it thinks the file is:
{15:16:37@SunFeb21}[drwho @ windbringer ~]()$./go-sendxmppbash:./go-sendxmpp:cannotexecutebinaryfile:Execformaterror{15:16:39@SunFeb21}[drwho @ windbringer ~]()$filego-sendxmppgo-sendxmpp:ELF32-bitLSBexecutable,ARM,EABI5version1(SYSV),staticallylinked,GoBuildID=m-ECc4RTyAZ9b4c3szLA/bwh366SB79bzn4dTX_7c/hDEoh_ptPwsELUe-BPO5jS1D_A3In21_1O8hny4b,notstripped
"ARM, EABI5 version 1." That's what we want to see. Let's copy that file over to my router and create a config file with login credentials for my XMPP server:
root@wednesday:~#./go-sendxmpp2021/02/2123:17:36Norecipientspecified.#Yay!Itran!root@wednesday:~#ls-alFdrwxr-xr-x1rootroot520Jan2920:34./drwxr-xr-x1rootroot608Dec311969../-rw-r--r-- 1 root root 187 Sep 2 2019 .iftoprc-rw------- 1 root root 295 Dec 13 19:51 .lesshst-rw------- 1 root root 84 Jan 29 20:35 .sendxmpprc-rwxr-xr-x1rootroot6587517Jan2919:38go-sendxmpp*-rwxr-xr-x1rootroot2525Jul52019monitoring.sh*root@wednesday:~#cat.sendxmpprcusername:wednesday@xmpp.example.compassword:AreTheyMadeFromRealGirlScouts?
Let's test by sending a message:
root@wednesday:~#./go-sendxmppdrwho@exocortex.virtadpt.netNobodygetsoutoftheBermudaTriangle,notevenforavacation.^D
As a proof of concept, let's cross-compile for MIPS. Again, we'll politely ask the Linux kernel running on my wireless bridge what architecture its processor core is running:
root@cyclopsis:~# head /proc/cpuinfo system type : MediaTek MT7620N ver:2 eco:6machine : Nexx WT3020 (8M)processor : 0cpu model : MIPS 24KEc V5.0BogoMIPS : 385.84wait instruction : yesmicrosecond timers : yestlb_entries : 32extra interrupt vector : yeshardware watchpoint : yes, count: 4, address/irw mask: [0x0ffc, 0x0ffc, 0x0ffb, 0x0ffb]
Good, we know what we're dealing with. A little bit of googling shows that, to build for this particular hardware platform you have to refer to it as mipsle. Let's use that knowledge to cross-compile:
{15:27:05@SunFeb21}[drwho@windbringer~]()$rmgo-sendxmpp{15:27:32@SunFeb21}[drwho@windbringer~]()$GOOS=linuxGOARCH=mipslegobuildsalsa.debian.org/mdosch/go-sendxmpp{15:28:01@SunFeb21}[drwho@windbringer~]()$ls-alFgo-sendxmpp.rwxr-xr-xdrwhodrwho6.9MBSunFeb2115:28:012021go-sendxmpp*
Give it a quick test:
{15:30:29@SunFeb21}[drwho @ windbringer ~]()$./go-sendxmppbash:./go-sendxmpp:cannotexecutebinaryfile:Execformaterror{15:30:57@SunFeb21}[drwho @ windbringer ~]()$filego-sendxmppgo-sendxmpp:ELF32-bitLSBexecutable,MIPS,MIPS32version1(SYSV),staticallylinked,GoBuildID=87xk9hqtqJFJiL0sjm3R/j6N70-Ma-o38Ga7sYKPWDQHhGko2R4KuTJOVapg5/jdEGRR3j9gq7Az6-T3VA,notstripped
Fantastic. However, in my case there's a problem - the wireless bridge doesn't have enough storage capacity to hold the executable.
root@cyclopsis:~# df -h /Filesystem Size Used Available Use% Mounted onoverlayfs:/overlay 3.9M 900.0K 3.0M 23% /
Crap.
There are some funky tricks you can use to get the file smaller, but in this particular case it's not really worthwhile because the wireless bridge in question has four megabytes of storage on board (for the record, my wristwatch has an order of magnitude more). That isn't to say that your environment will be quite so constrained, I just happened to have a tiny mobile router on hand when I built the wireless bridge.
As far as I can tell, this particular trick should be generalizable to cross-compiling pretty much anything written in Go. I can't say for sure, I'm not a Go expert by any means, but the research I've done points to this being an officially supported technique for building for other platforms. I don't know which versions of Go it'll work for; I've tried it on v1.15.5, v1.15.0, and v1.14.0 and it seems to work as expected.
Good luck.