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) } }