diff --git a/tools/offsets/offsets.go b/tools/offsets/offsets.go new file mode 100644 index 0000000..c749906 --- /dev/null +++ b/tools/offsets/offsets.go @@ -0,0 +1,192 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "io/ioutil" + "os" + "os/exec" + "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("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) { + lines := strings.Split(string(script), "\n") + + // Inject os/arch and workdir to the top of the build file + header := []string{ + fmt.Sprintf("export GOOS=%s", targetOS), + fmt.Sprintf("export GOARCH=%s", targetArch), + fmt.Sprintf("WORK=%q", workDir), + 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 lines[lineIndex] == "#" { + 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) { + headers, err := ioutil.ReadFile(fmt.Sprintf("%s/runtime/_obj/go_asm.h", workDir)) + if 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(headers), "\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, + ), + ) + } + } + + 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) + } +}