mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
218 lines
4.4 KiB
Go
218 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"debug/elf"
|
|
"encoding/binary"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
const pathToKernel = "src/gopheros/"
|
|
|
|
type redirect struct {
|
|
src string
|
|
dst string
|
|
|
|
srcVMA uint64
|
|
dstVMA uint64
|
|
}
|
|
|
|
func exit(err error) {
|
|
fmt.Fprintf(os.Stderr, "[redirects] error: %s\n", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
func collectGoFiles(root string) ([]string, error) {
|
|
var goFiles []string
|
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if info.IsDir() {
|
|
return err
|
|
}
|
|
|
|
if filepath.Ext(path) == ".go" && !strings.Contains(path, "_test") {
|
|
goFiles = append(goFiles, path)
|
|
}
|
|
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return goFiles, nil
|
|
}
|
|
|
|
func findRedirects(goFiles []string) ([]*redirect, error) {
|
|
var redirects []*redirect
|
|
|
|
for _, goFile := range goFiles {
|
|
fset := token.NewFileSet()
|
|
|
|
f, err := parser.ParseFile(fset, goFile, nil, parser.ParseComments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %s", goFile, err)
|
|
}
|
|
|
|
cmap := ast.NewCommentMap(fset, f, f.Comments)
|
|
cmap.Filter(f)
|
|
for astNode := range cmap {
|
|
fnDecl, ok := astNode.(*ast.FuncDecl)
|
|
if !ok || fnDecl.Doc == nil {
|
|
continue
|
|
}
|
|
|
|
for _, comment := range fnDecl.Doc.List {
|
|
if !strings.Contains(comment.Text, "go:redirect-from") {
|
|
continue
|
|
}
|
|
|
|
// build qualified name to fn
|
|
fqName := fmt.Sprintf("%s.%s",
|
|
goFile[strings.Index(goFile, "gopheros"):strings.LastIndexByte(goFile, '/')],
|
|
fnDecl.Name,
|
|
)
|
|
|
|
fields := strings.Fields(comment.Text)
|
|
if len(fields) != 2 || fields[0] != "//go:redirect-from" {
|
|
return nil, fmt.Errorf("malformed go:redirect-from syntax for %q\n-> %q", fqName, comment.Text)
|
|
}
|
|
|
|
redirects = append(redirects, &redirect{
|
|
src: fields[1],
|
|
dst: fqName,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return redirects, nil
|
|
}
|
|
|
|
func elfRedirectTableOffset(imgFile string) (uint64, error) {
|
|
f, err := elf.Open(imgFile)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer f.Close()
|
|
|
|
redirectsSection := f.Section(".goredirectstbl")
|
|
if redirectsSection == nil {
|
|
return 0, fmt.Errorf("%s: missing .goredirectstbl section", imgFile)
|
|
}
|
|
|
|
return redirectsSection.Offset, nil
|
|
}
|
|
|
|
func elfWriteRedirectTable(redirects []*redirect, imgFile string) error {
|
|
redirectTableOffset, err := elfRedirectTableOffset(imgFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Open kernel image file and seek to table offset
|
|
f, err := os.OpenFile(imgFile, os.O_WRONLY, os.ModeType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
if _, err = f.Seek(int64(redirectTableOffset), io.SeekStart); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, redirect := range redirects {
|
|
binary.Write(f, binary.LittleEndian, redirect.srcVMA)
|
|
binary.Write(f, binary.LittleEndian, redirect.dstVMA)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func elfResolveRedirectSymbols(redirects []*redirect, imgFile string) error {
|
|
f, err := elf.Open(imgFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
symbols, err := f.Symbols()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, redirect := range redirects {
|
|
for _, symbol := range symbols {
|
|
if symbol.Name == redirect.src {
|
|
redirect.srcVMA = symbol.Value
|
|
}
|
|
if symbol.Name == redirect.dst {
|
|
redirect.dstVMA = symbol.Value
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case redirect.srcVMA == 0:
|
|
return fmt.Errorf("%s: could not locate address of %q", imgFile, redirect.src)
|
|
case redirect.dstVMA == 0:
|
|
return fmt.Errorf("%s: could not locate address of %q", imgFile, redirect.dst)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
if matches, _ := filepath.Glob(pathToKernel); len(matches) != 1 {
|
|
exit(errors.New("this tool must be run from the gopher-os root folder"))
|
|
}
|
|
|
|
if len(flag.Args()) == 0 {
|
|
exit(errors.New("missing command"))
|
|
}
|
|
|
|
cmd := flag.Arg(0)
|
|
var imgFile string
|
|
switch cmd {
|
|
case "count":
|
|
case "populate-table":
|
|
if len(flag.Args()) != 2 {
|
|
exit(errors.New("populate-table requires the path to the kernel image as an argument"))
|
|
}
|
|
imgFile = flag.Arg(1)
|
|
default:
|
|
exit(fmt.Errorf("unknown command %q", cmd))
|
|
}
|
|
|
|
goFiles, err := collectGoFiles(pathToKernel)
|
|
if err != nil {
|
|
exit(err)
|
|
}
|
|
|
|
redirects, err := findRedirects(goFiles)
|
|
if err != nil {
|
|
exit(err)
|
|
}
|
|
|
|
if cmd == "count" {
|
|
fmt.Printf("%d", len(redirects))
|
|
return
|
|
}
|
|
|
|
if err = elfResolveRedirectSymbols(redirects, imgFile); err != nil {
|
|
exit(err)
|
|
}
|
|
|
|
if err = elfWriteRedirectTable(redirects, imgFile); err != nil {
|
|
exit(err)
|
|
}
|
|
}
|