Which OS and CPU architecture is this binary compiled for?

Featured image for sharing metadata for article

Let's say that we have three binaries, and we want to detect the Operating Systems and CPU architectures in use:

ls
dmd
dmd-darwin
dmd.exe
url

If we run this through the file command, we can see the following information:

$ file dmd
dmd: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=85939f5f8c4d7fca5b91d0a7998c56a23467ed27, for GNU/Linux 3.2.0, with debug_info, not stripped
$ file dmd-darwin
dmd-darwin: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64]
dmd-darwin (for architecture x86_64):   Mach-O 64-bit executable x86_64
dmd-darwin (for architecture arm64):    Mach-O 64-bit executable arm64
$ file dmd.exe
dmd.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
$ file url
url: Mach-O 64-bit executable arm64

However, let's say that we want to automagically determine this information as part of our Go binary, rather than relying on the file command being present.

Fortunately, Go's standard library includes the debug package which contains - for our use case - packages to help parse the three Operating System binaries we want to:

  • debug/elf for Executable and Linked Format (ELF) binaries used on Linux Operating Systems
  • debug/macho for Mach-O binaries used on Mac OS
  • debug/pe for Microsoft Windows Portable Executable (PE) binaries used on Windows

This allows us to write a program such as:

package main

import (
	"debug/elf"
	"debug/macho"
	"debug/pe"
	"fmt"
	"log"
	"os"
)

func main() {
	if len(os.Args) != 2 {
		log.Fatal("Need more args")
	}
	command := os.Args[1]

	err := parseMac(command)
	if err != nil {
		log.Println("Doesn't look like a Mach-O file:", err)
	}

	err = parseMacUniversalBinary(command)
	if err != nil {
		log.Println("Doesn't look like a Mach-O Universal Binary:", err)
	}

	err = parseElf(command)
	if err != nil {
		log.Println("Doesn't look like an ELF file:", err)
	}

	err = parsePE(command)
	if err != nil {
		log.Println("Doesn't look like a PE file:", err)
	}
}

func parseMac(command string) error {
	f, err := macho.Open(command)
	if err != nil {
		return err
	}

	fmt.Printf("%s is a Mach-O binary with CPU architecture %v\n", command, f.Cpu.String())

	return nil
}

func parseMacUniversalBinary(command string) error {
	f, err := macho.OpenFat(command)
	if err != nil {
		return err
	}
	fmt.Printf("%s is a Mach-O universal binary with architectures: \n", command)
	for _, fa := range f.Arches {
		fmt.Printf("- %v\n", fa.Cpu.String())
	}

	return nil
}

func parseElf(command string) error {
	f, err := elf.Open(command)
	if err != nil {
		return err
	}

	fmt.Printf("%s is an Executable and Linked Format (ELF) binary with CPU architecture %v\n", command, f.Machine.String())

	return nil
}

func parsePE(command string) error {
	f, err := pe.Open(command)
	if err != nil {
		return err
	}

	fmt.Printf("%s is a PE binary with CPU architecture 0x%x\n", command, f.Machine)

	return nil
}

Which then gives us the following output:

$ go run main.go tmp/dmd
2023/05/15 10:39:54 Doesn't look like a Mach-O file: invalid magic number in record at byte 0x0
2023/05/15 10:39:54 Doesn't look like a Mach-O Universal Binary: invalid magic number in record at byte 0x0
tmp/dmd is an Executable and Linked Format (ELF) binary with CPU architecture EM_X86_64
$ go run main.go tmp/dmd-darwin
2023/05/15 10:39:56 Doesn't look like a Mach-O file: invalid magic number in record at byte 0x0
tmp/dmd-darwin is a Mach-O universal binary with architectures:
- CpuAmd64
- CpuArm64
2023/05/15 10:39:56 Doesn't look like an ELF file: bad magic number '[202 254 186 190]' in record at byte 0x0

# note that unfortunately PE executables don't have a lookup table
$ go run main.go tmp/dmd.exe
2023/05/15 10:40:00 Doesn't look like a Mach-O file: invalid magic number in record at byte 0x0
2023/05/15 10:40:00 Doesn't look like a Mach-O Universal Binary: invalid magic number in record at byte 0x0
2023/05/15 10:40:00 Doesn't look like an ELF file: bad magic number '[77 90 144 0]' in record at byte 0x0
dmd.exe is a PE binary with CPU architecture 0x8664
$ go run main.go url
url is a Mach-O binary with CPU architecture CpuArm64
2023/05/15 10:40:06 Doesn't look like a Mach-O Universal Binary: not a fat Mach-O file in record at byte 0x0
2023/05/15 10:40:06 Doesn't look like an ELF file: bad magic number '[207 250 237 254]' in record at byte 0x0

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #go.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.