1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00
gopher-os/tools/offsets/offsets.go
Achilleas Anagnostopoulos 4e3567f8a1 tools: update offsets tool to work with go versions 1.7 - 1.10
Older go versions (1.7.x) specify a fixed page size (_PageSize const) as
part of their runtime whereas newer go versions populate the page size at
runtime.

The kernel asm bootstrap code was written with go 1.8 in mind. As a
result it attempts to populate the page size manually which obviously
breaks compilation in go 1.7.

The offsets tool has been updated to emit the special def
"SKIP_PAGESIZE_SETUP" when running under go 1.7 which allows us to
perform conditional compilation of the page setup code inside the
bootstrap asm code.

fixup
2018-03-23 07:21:02 +00:00

235 lines
6.0 KiB
Go

package main
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
)
func exit(err error) {
fmt.Fprintf(os.Stderr, "[offsets] error: %s\n", err.Error())
os.Exit(1)
}
func genBuildScript(targetOS, targetArch, goBinary, workDir string) ([]byte, error) {
// Write a dummy program in workDir so "go build" does not complain
dummyGoProgram := []byte("package main\n func main(){}")
err := ioutil.WriteFile(fmt.Sprintf("%s/main.go", workDir), dummyGoProgram, os.ModePerm)
if err != nil {
return nil, err
}
// Run "go build -a -n" in workDir and capture the output. The -a flag
// ensures that the generated build script includes steps to always
// rebuild the runtime packages.
cmd := exec.Command(goBinary, "build", "-a", "-n")
cmd.Dir = workDir
cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s", os.Getenv("GOROOT")))
cmd.Env = append(cmd.Env, fmt.Sprintf("GOOS=%s", targetOS))
cmd.Env = append(cmd.Env, fmt.Sprintf("GOARCH=%s", targetArch))
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("failed to generate build script\nMore info:\n%s", out)
}
return out, nil
}
func patchBuildScript(script []byte, workDir, targetOS, targetArch, goBinary string) ([]byte, error) {
// Replace $WORK with the workDir location. This is required for executing
// build scripts generated by go 1.10
lines := strings.Split(
strings.Replace(string(script), "$WORK", workDir, -1),
"\n",
)
// Inject os/arch and workdir to the top of the build file
header := []string{
fmt.Sprintf("export GOROOT=%s", os.Getenv("GOROOT")),
fmt.Sprintf("export GOOS=%s", targetOS),
fmt.Sprintf("export GOARCH=%s", targetArch),
fmt.Sprintf("alias pack='%s tool pack'", goBinary),
}
lines = append(header, lines...)
// We are only interested in building the runtime as this block generates
// the asm headers. Scan the lines till we find "# runtime" comment and
// stop at next comment
var stopOnNextComment bool
for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
// Ignore empty comments
if strings.TrimSpace(lines[lineIndex]) == "#" || strings.Contains(lines[lineIndex], "# import") {
continue
}
if stopOnNextComment && strings.HasPrefix(lines[lineIndex], "#") {
return []byte(strings.Join(lines[:lineIndex], "\n")), nil
}
if lines[lineIndex] == "# runtime" {
stopOnNextComment = true
}
}
return nil, errors.New("generated build file does not specify -asmhdr when building the runtime")
}
func execBuildScript(script []byte, workDir string) error {
f, err := os.Create(fmt.Sprintf("%s/build.sh", workDir))
if err != nil {
return err
}
_, err = f.Write(script)
if err != nil {
f.Close()
return err
}
f.Close()
cmd := exec.Command("sh", f.Name())
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to execute build script\nMore info:\n%s", out)
}
return nil
}
func genAsmIncludes(workDir string) ([]byte, error) {
// Find all generated go_asm.h files and concat their conentents
var (
allHeaders, headers []byte
)
if err := filepath.Walk(workDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if filepath.Base(path) != "go_asm.h" {
return nil
}
if headers, err = ioutil.ReadFile(path); err != nil {
return err
}
allHeaders = append(allHeaders, '\n')
allHeaders = append(allHeaders, headers...)
return nil
}); err != nil {
return nil, err
}
var includes []string
includes = append(includes, "; vim: set ft=nasm :\n")
includes = append(includes, fmt.Sprintf("; generated by tools/offsets at %v\n", time.Now()))
for _, line := range strings.Split(string(allHeaders), "\n") {
line = strings.TrimPrefix(line, "#define ")
// We are only interested in the offsets for the g, m and stack structures
if strings.HasPrefix(line, "g_") || strings.HasPrefix(line, "m_") || strings.HasPrefix(line, "stack_") {
tokens := strings.Fields(line)
if len(tokens) != 2 {
continue
}
offset, err := strconv.ParseInt(tokens[1], 10, 32)
if err != nil {
continue
}
includes = append(includes,
fmt.Sprintf("GO_%s equ 0x%x ; %d",
strings.ToUpper(tokens[0]),
offset,
offset,
),
)
}
}
// In go 1.7.x, the page size is given by the _PageSize constant whereas in
// newer go versions it is specified at runtime and needs to be manually set
// by our asm bootstrap code.
if strings.Contains(runtime.Version(), "go1.7") {
includes = append(includes,
"; go 1.7 runtime uses a fixed 4k page size for our target arch so our bootstrap code does not need to do any extra work to set it up",
"%define SKIP_PAGESIZE_SETUP 1",
)
}
return []byte(strings.Join(includes, "\n")), nil
}
func runTool() error {
targetOS := flag.String("target-os", "", "a valid GOOS value for generating the asm offsets")
targetArch := flag.String("target-arch", "", "a valid GOARCH value for generating the asm offsets")
goBinary := flag.String("go-binary", "go", "the Go binary to use")
asmOutput := flag.String("out", "-", "a file to write the asm headers or - to output to STDOUT")
flag.Parse()
switch {
case *targetOS == "":
exit(errors.New("-target-os parameter missing"))
case *targetArch == "":
exit(errors.New("-target-arch parameter missing"))
}
workDir, err := ioutil.TempDir("", "offsets-tool")
if err != nil {
return err
}
defer os.RemoveAll(workDir)
buildScript, err := genBuildScript(*targetOS, *targetArch, *goBinary, workDir)
if err != nil {
return err
}
buildScript, err = patchBuildScript(buildScript, workDir, *targetOS, *targetArch, *goBinary)
if err != nil {
return err
}
if err = execBuildScript(buildScript, workDir); err != nil {
return err
}
asmIncludes, err := genAsmIncludes(workDir)
if err != nil {
return err
}
switch *asmOutput {
case "-":
fmt.Printf("%s\n", string(asmIncludes))
default:
if err = ioutil.WriteFile(*asmOutput, asmIncludes, os.ModePerm); err != nil {
return err
}
}
return nil
}
func main() {
if err := runTool(); err != nil {
exit(err)
}
}