If I build a Go program (even the playground's "Hello world") with a value of GOAMD64 that's too high for the machine I'm trying to run the program on, the program will dump core on startup. This happens even for GOAMD64 values that work on the machine I build the program on, but not on a machine with an older CPU; I can compile with GOAMD64=v3 on one machine where it works, copy the binary to another, and it dumps core on startup instead of running. On the original test machine, GOAMD64=v4 produces a binary that doesn't even run on the machine itself.
Gdb says that this is happening in:
#0 0x000000000045c6c0 in runtime.write (fd=2, p=0x4b1680 <bad_cpu_msg>, n=84, ~r0=<optimized out>) at <autogenerated>:1
#1 0x0000000000457bc4 in runtime.rt0_go () at /h/281/cks/src/go/src/runtime/asm_amd64.s:194
Delve agrees with this trace and shows the relevant runtime source as:
0 0x000000000045c6c0 in runtime.write
1 0x0000000000457bc4 in runtime.rt0_go
Stopped at: 0x45c6c0
=> 1: no source available
Frame 1: /h/281/cks/src/go/src/runtime/asm_amd64.s:194 (PC: 457bc4)
189: bad_cpu: // show that the program requires a certain microarchitecture level.
190: MOVQ $2, 0(SP)
191: MOVQ $bad_cpu_msg<>(SB), AX
192: MOVQ AX, 8(SP)
193: MOVQ $84, 16(SP)
=> 194: CALL runtime·write(SB)
195: MOVQ $1, 0(SP)
196: CALL runtime·exit(SB)
197: CALL runtime·abort(SB)
Delve further reports the crash (dis)assembly and fault address as:
The rt0_go code is trying to call runtime.write. It's actually calling runtime.write.abi0. That is a wrapper that calls into runtime.write using the internal ABI. Part of that wrapper sets up r14 by loading it from TLS. But the TLS pointer has not been set up yet, so boom.
How did this used to work? The previous code called runtime.exit.abi0 for these kinds of errors, but that is implemented in assembly, so no wrapper needed.
I don't think we can call into Go code at this point in startup. We could delay the report until after settls runs, maybe?
For some reason it works fine on Darwin - something is different enough about TLS that it survives the bogus load. And nothing in runtime.write happens to use the G register for anything. At least I think, there's a lot of strangeness across platforms here.
Possibly, yes. I think there is less of an issue because 386 isn't using the register ABI yet, so there's no explicit use of TLS to set up the G register. But the stack check in the preamble of write might be confused.
Also the processors that don't support the 386 minimum are very old at this point.