golang/go

darwin/arm64 binary says it is running under Rosetta 2 on macOS M1 machine

Closed this issue · 2 comments

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

$ go version
go version go1.16 darwin/amd64

Does this issue reproduce with the latest release?

Yes, Go 1.16 is the latest release.

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

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/[name]/Library/Caches/go-build"
GOENV="/Users/[name]/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/[name]/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/[name]/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.16/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.16/libexec/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.16"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/[name]/dd/gotest/darwin/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/6d/hrpbskwn3nv8qnm51fdpmh5c0000gn/T/go-build1276543076=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Consider the following program, saved on main.go:

package main

import (
	"fmt"
	"golang.org/x/sys/unix"
)

func processIsTranslated() (bool, error) {
	// https://developer.apple.com/documentation/apple_silicon/about_the_rosetta_translation_environment#3616845
	_, err := unix.Sysctl("sysctl.proc_translated")

	if err == nil {
		return true, nil
	} else if err.(unix.Errno) == unix.ENOENT {
		return false, nil
	}

	return false, err
}

func main() {
	if isTranslated, err := processIsTranslated(); err != nil {
		panic(err)
	} else if isTranslated {
		fmt.Println("Running on Rosetta 2")
	} else {
		fmt.Println("Running natively")
	}
}

As per Apple's documentation, if I am translating the code correctly to Go, this should detect whether a binary is running natively or it is being translated under Rosetta.

Build the program both for amd64 and arm64 on macOS:

GOOS=darwin GOARCH=amd64 go build -o main_darwin_amd64 main.go
GOOS=darwin GOARCH=arm64 go build -o main_darwin_arm64 main.go

What did you expect to see?

The amd64 binary should report it is running natively on a macOS with amd64 architecture and it should report it is running under Rosetta 2 on a macOS with arm64 architecture.

The arm64 binary should not be runnable on a macOS with amd64 architecture and it should report it is running natively on a macOS with arm64 architecture.

What did you see instead?

When running on a macOS with an amd64 architecture I see the following

$ uname -a
Darwin [name].local 19.6.0 Darwin Kernel Version 19.6.0: Tue Jan 12 22:13:05 PST 2021; root:xnu-6153.141.16~1/RELEASE_X86_64 x86_64 i386 MacBookPro15,2 Darwin
$ file ./main_darwin_amd64
./main_darwin_amd64: Mach-O 64-bit executable x86_64
$ ./main_darwin_amd64
Running natively
$ file ./main_darwin_arm64
./main_darwin_arm64: Mach-O 64-bit executable arm64
$ ./main_darwin_arm64
-bash: ./main_darwin_arm64: Bad CPU type in executable

When running on a macOS with an arm64 architecture (with a macOS M1 chip) I see the following

$ uname -a
Darwin [name].local 20.1.0 Darwin Kernel Version 20.1.0: Sat Oct 31 00:07:18 PDT 2020; root:xnu-7195.50.7~2/RELEASE_ARM64_T8020 arm64
$ file ./main_darwin_amd64
./main_darwin_amd64: Mach-O 64-bit executable x86_64
$ ./main_darwin_amd64
Running on Rosetta 2
$ file ./main_darwin_arm64
./main_darwin_arm64: Mach-O 64-bit executable arm64
$ ./main_darwin_arm64
Running on Rosetta 2  # ?!?

I would expect this last line to say Running natively since it is an arm64 executable on an arm64 macOS.

Compare with the following C++ program which does output the results I would expect: the arm64 version reports "Running natively" on the Mac with the M1 chip.

#include <iostream>
#include <sys/sysctl.h>

int processIsTranslated() {
  int ret = 0;
  size_t size = sizeof(ret);
  if (sysctlbyname("sysctl.proc_translated", &ret, &size, nullptr, 0) == -1) {
    if (errno == ENOENT)
      return 0;
    return -1;
  }
  return ret;
}

int main() {
  int res = processIsTranslated();
  if (res == 0) {
    std::cout << "Process is running natively" << std::endl;
  } else if (res == 1) {
    std::cout << "Process is running under Rosetta" << std::endl;
  } else {
    std::cout << "Unexpected error code: " << res << std::endl;
  }
}

Notice the return ret, however this doesn't work with unix.Sysctl since it always returns a string that does not correspond to the numbers here

Closing as it was an issue in my code. The correct implementation of the function in Go is:

func processIsTranslated() (bool, error) {
	// https://developer.apple.com/documentation/apple_silicon/about_the_rosetta_translation_environment#3616845
	ret, err := unix.SysctlUint32("sysctl.proc_translated")

	if err == nil {
		return ret == 1, nil
	} else if err.(unix.Errno) == unix.ENOENT {
		return false, nil
	}

	return false, err
}