Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
kevpar opened this issue Jan 17, 2023 · 7 comments
Closed

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

kevpar opened this issue Jan 17, 2023 · 7 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@kevpar
Copy link
Contributor

kevpar commented Jan 17, 2023

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.

@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Jan 17, 2023
@gopherbot
Copy link

Change https://go.dev/cl/462054 mentions this issue: cmd/link: fix incorrect DOS header on Windows binaries

@cherrymui
Copy link
Member

cc @golang/windows

@cherrymui cherrymui added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jan 18, 2023
@cherrymui cherrymui added this to the Unplanned milestone Jan 18, 2023
@beoran
Copy link

beoran commented Jan 20, 2023

It would be good to fix this as I fear this might trip off some virus scanners.

@alexbrainman
Copy link
Member

@kevpar

I tried to test your fix.

I run GOOS=windows GOARCH=386 go test -v -c -o test.exe os command and then copied test.exe onto my FreeDOS that I installed onto my VirtualBox program.

I built test.exe twice. First test.exe I built on top of e587a76 commit and copied it into c:\after.exe on FreeDOS. Second test.exe I built on top of b08d5ee commit (the commit before e587a76) and I copied it into c:\before.exe on FreeDOS.

When I run c:\before.exe on Free DOS it outputs nothing. When I run c:\after.exe FreeDOS output Bad command or filename - "after.exe"..

I expected that FreeDOS will output This program cannot be run in DOS mode. message. Am I wrong to expect that?

image

Thank you.

Alex

@kevpar
Copy link
Contributor Author

kevpar commented Feb 12, 2023

Interesting, I'm not able to repro that. The "after" version does print This program cannot be run in DOS mode. for me.

Here's what I've tried:

Set up Go toolchains

  • Checked out b08d5ee in my Go repo (C:\Users\kevpar\src\go)
  • Set GOROOT_FINAL=C:\Users\kevpar\Scratch\go_before
  • Ran .\make.bat
  • Copied C:\Users\kevpar\src\go to C:\Users\kevpar\Scratch\go_before
  • Checked out e587a76 in my Go repo (C:\Users\kevpar\src\go)
  • Set GOROOT_FINAL=C:\Users\kevpar\Scratch\go_after
  • Ran .\make.bat
  • Copied C:\Users\kevpar\src\go to CL\Users\kevpar\Scratch\go_after

Build test binaries

  • Made a sample test program that has an empty main function.
  • Set GOARCH=amd64
  • Built the test program using C:\Users\kevpar\Scratch\go_before\bin\go.exe and output it to before.exe
  • Set GOARCH=386
  • Build the test program using C:\Users\kevpar\Scratch\go_before\bin\go.exe and output it to before2.exe
  • Set GOARCH=amd64
  • Built the test program using C:\Users\kevpar\Scratch\go_after\bin\go.exe and output it to after.exe
  • Set GOARCH=386
  • Build the test program using C:\Users\kevpar\Scratch\go_after\bin\go.exe and output it to after2.exe

Run in FreeDOS

  • Copied before.exe, before2.exe, after.exe, after2.exe into my FreeDOS VM.
  • Started the FreeDOS VM, and attempted to run the programs:
    image

Extra info

image

> C:\Users\kevpar\Scratch\go_before\bin\go.exe version
go version devel go1.21-b08d5ee5e9 Fri Jan 20 19:51:06 2023 +0000 windows/amd64
> C:\Users\kevpar\Scratch\go_after\bin\go.exe version 
go version devel go1.21-e587a7691b Fri Jan 20 20:52:48 2023 +0000 windows/amd64

@cherrymui
Copy link
Member

@alexbrainman from your screenshot it seems the after program is named afer (without the t).

@alexbrainman
Copy link
Member

@alexbrainman from your screenshot it seems the after program is named afer (without the t).

That was my problem, @cherrymui . If I run afer, it outputs

This program cannot be run in DOS mode.

exactly like @kevpar described at #57834 (comment) .

image

So all is good here.

Thank you for fixing this bug.

Alex

@golang golang locked and limited conversation to collaborators Feb 26, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants