Skip to content

cmd/link: incorrect DOS header on Go-built Windows binaries #57834

@kevpar

Description

@kevpar

What version of Go are you using (go version)?

> go version
go version go1.19.3 windows/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
> go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\kevpar\AppData\Local\go-build
set GOENV=C:\Users\kevpar\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\kevpar\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\kevpar\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.19.3
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=NUL
set GOWORK=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=C:\Users\kevpar\AppData\Local\Temp\go-build397785229=/tmp/go-build -gno-record-gcc-switches

What did you do?

  1. Created a simple Go program.
  2. Compiled with GOOS=windows as hello.exe.
  3. Tested running it under DOS (tried both DOSBox and FreeDOS).

What did you expect to see?

Program should print This program cannot be run in DOS mode. and exit.

What did you see instead?

DOSBox crashed when the program was run. On FreeDOS the program appeared to do nothing.

Details

First, off, it likely goes without saying this is a very inconsequential bug. I imagine the number of users who will run a Go-built binary on a DOS system these days is quite small. :)

Windows PE binaries contain a old DOS-style (MZ) header at the front. This is generally used to point to a short amount of code that prints out a message saying the program cannot run under DOS. Go embeds a DOS header into binaries it builds, but this header appears to be invalid.

The structure of the header, per the Windows SDK, looks like this:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

In a Go program, most of the values are correct, but the e_crlc (number of relocations) is set to 4, and e_cparhdr (size of header in 16-bit words) is set to 0. On other Windows programs I examined, these values were flipped. Having e_cparhdr set to 0 appears to mean that execution will start at offset 0 in the file, rather than 0x40 where executable code actually appears. This results in the program executing invalid instructions.

Using a e_crlc of 0 and e_cparhdr of 4 should fix the issue. This change can be made here: https://github.com/golang/go/blob/master/src/cmd/link/internal/ld/pe.go#L155

As far as I can tell, this PE header has been incorrect as long as it has existed in Go. If anyone has context on whether this has ever worked, I'd be curious to know.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.compiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions