如何在运行时发现所有包类型



据我所知(见这里和这里),在反射包中没有类型发现机制,它期望您已经拥有想要检查的类型或值的实例。

是否有其他方法来发现运行中的go包中所有导出的类型(特别是结构)?

这是我希望我拥有的东西(但它不存在):

import "time"
import "fmt"
func main() {
    var types []reflect.Type
    types = reflect.DiscoverTypes(time)
    fmt.Println(types)
}

最终目标是能够发现包中满足特定条件的所有结构,然后能够实例化这些结构的新实例。

顺便说一句,标识类型的注册函数不是,对于我的用例来说,这是一种有效的方法。

无论你认为这是一个好主意与否,以下是我想要这个功能的原因(因为我知道你会问):

我已经编写了一个代码生成实用程序,它加载源文件并构建AST来扫描嵌入指定类型的类型。该实用程序的输出是一组基于所发现类型的go测试函数。我使用go generate调用这个实用程序来创建测试函数,然后运行go test来执行生成的测试函数。每次测试更改(或添加新类型)时,我必须在重新运行go test之前重新运行go generate。这就是为什么注册函数不是一个有效的选项。我想避免go generate步骤,但这将要求我的实用程序成为由运行包导入的库。库代码需要在init()期间以某种方式扫描正在运行的名称空间,以查找嵌入预期库类型的类型。

在Go 1.5中,您可以使用新的包类型和导入器来检查二进制包和源包。例如:

package main
import (
    "fmt"
    "go/importer"
)
func main() {
    pkg, err := importer.Default().Import("time")
    if err != nil {
        fmt.Printf("error: %sn", err.Error())
        return
    }
    for _, declName := range pkg.Scope().Names() {
        fmt.Println(declName)
    }
}

你可以使用go/build命令提取所有安装的包。或者您可以配置Lookup导入器来检查环境之外的二进制文件。

在1.5之前,唯一简单的方法是使用包ast来编译源代码。

(见底部2019年更新)

警告:未经测试和黑客攻击。当Go的新版本发布时可能会中断。

通过稍微改动Go的运行时,可以得到运行时所知道的所有类型。在您自己的包中包含一个小程序集文件,其中包含:

TEXT yourpackage·typelinks(SB), NOSPLIT, $0-0
    JMP reflect·typelinks(SB)

yourpackage中,声明函数原型(不带函数体):

func typelinks() []*typeDefDummy

在类型定义旁边:

type typeDefDummy struct {
    _      uintptr           // padding
    _      uint64            // padding
    _      [3]uintptr        // padding
    StrPtr *string           
}

然后调用typelinks,遍历切片并读取每个StrPtr中的名称。寻找那些以yourpackage开头的。请注意,如果在不同的路径中有两个名为yourpackage的包,则此方法将无法明确地工作。

我能否以某种方式钩入反射包以实例化这些名称的新实例?

是的,假设d*typeDefDummy类型的值(注意星号,非常重要):

t := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&d)))

现在t是一个reflect.Type的值,你可以用它来实例化reflect.Value


编辑:我成功地测试并执行了这段代码,并将其作为要点上传。

调整包名,必要时包括路径。

2019

更新

自从我最初发布这个答案以来,很多事情都发生了变化。以下是2019年Go 1.11如何实现相同功能的简短描述。

$GOPATH/src/tl/tl.go

package tl
import (
    "unsafe"
)
func Typelinks() (sections []unsafe.Pointer, offset [][]int32) {
    return typelinks()
}
func typelinks() (sections []unsafe.Pointer, offset [][]int32)
func Add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
    return add(p, x, whySafe)
}
func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer

$GOPATH/src/tl/tl.s

TEXT tl·typelinks(SB), $0-0
    JMP reflect·typelinks(SB)
TEXT tl·add(SB), $0-0
    JMP reflect·add(SB)

main.go

package main
import (
    "fmt"
    "reflect"
    "tl"
    "unsafe"
)
func main() {
    sections, offsets := tl.Typelinks()
    for i, base := range sections {
        for _, offset := range offsets[i] {
            typeAddr := tl.Add(base, uintptr(offset), "")
            typ := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&typeAddr)))
            fmt.Println(typ)
        }
    }
}

黑客快乐!

使用Go 1.18更新2022

与Go 1.18接受的答案不再工作,但我可以适应它使用go:linkname。使用这个指令和unsafe包,现在可以访问这些内部函数,而无需任何额外的汇编代码。

package main
import (
    "fmt"
    "reflect"
    "unsafe"
)
//go:linkname typelinks reflect.typelinks
func typelinks() (sections []unsafe.Pointer, offset [][]int32)
//go:linkname add reflect.add
func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer
func main() {
    sections, offsets := typelinks()
    for i, base := range sections {
        for _, offset := range offsets[i] {
            typeAddr := add(base, uintptr(offset), "")
            typ := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&typeAddr)))
            fmt.Println(typ)
        }
    }
}

不幸的是,我认为这是不可能的。在Go语言中,包不是"可操作的",你不能对它"调用函数"。您也不能在类型上调用函数,但是您可以在类型的实例上调用reflect.TypeOf并获得reflect.Type,这是类型的运行时抽象。对于包没有这样的机制,没有reflect.Package .

话虽如此,你可以提交一个关于reflect.PackageOf的缺失(和添加的实用性)的问题。

谢谢@thwd和@icio,遵循你的方向,它今天在1.13.6上仍然工作。

走你的路,我将会:

TEXT ·typelinks(SB), $0-0
    JMP reflect·typelinks(SB)

yes,没有包名,也没有"add"函数。

然后按照@icio的方式将"add"函数更改为:

func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
    return unsafe.Pointer(uintptr(p) + x)
}

现在都工作了。

go1.16版本(测试于go版本go1.16.7 linux/amd64)

  • 只能生成代码和字符串。你必须将生成的代码粘贴到某个地方,然后再次编译
import (
  "fmt"
  "go/ast"
  "golang.org/x/tools/go/packages"
  "reflect"
  "time"
  "unicode"
)
func printTypes(){
  config := &packages.Config{
    Mode:  packages.NeedSyntax,
  }
  pkgs, _ := packages.Load(config, "package_name")
  pkg := pkgs[0]
  for _, s := range pkg.Syntax {
    for n, o := range s.Scope.Objects {
      if o.Kind == ast.Typ {
        // check if type is exported(only need for non-local types)
        if unicode.IsUpper([]rune(n)[0]) {
          // note that reflect.ValueOf(*new(%s)) won't work with interfaces
          fmt.Printf("ProcessType(new(package_name.%s)),n", n)
        }
      }
    }
  }
}

可能用例的完整示例:https://pastebin.com/ut0zNEAc(在在线repls中不工作,但在本地工作)

  • 在go 1.11 dwarf调试符号添加了运行时类型信息后,您可以通过使用此地址获取运行时类型
  • DW_AT_go_runtime_type
  • gort您可以看到更多内容
package main
import (
    "debug/dwarf"
    "fmt"
    "log"
    "os"
    "reflect"
    "runtime"
    "unsafe"
    "github.com/go-delve/delve/pkg/dwarf/godwarf"
    "github.com/go-delve/delve/pkg/proc"
)
func main() {
    path, err := os.Executable()
    if err != nil {
        log.Fatalln(err)
    }
    bi := proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH)
    err = bi.LoadBinaryInfo(path, 0, nil)
    if err != nil {
        log.Fatalln(err)
    }
    mds, err := loadModuleData(bi, new(localMemory))
    if err != nil {
        log.Fatalln(err)
    }
    types, err := bi.Types()
    if err != nil {
        log.Fatalln(err)
    }
    for _, name := range types {
        dwarfType, err := findType(bi, name)
        if err != nil {
            continue
        }
        typeAddr, err := dwarfToRuntimeType(bi, mds, dwarfType, name)
        if err != nil {
            continue
        }
        typ := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&typeAddr)))
        log.Printf("load type name:%s type:%sn", name, typ)
    }
}
// delve counterpart to runtime.moduledata
type moduleData struct {
    text, etext   uint64
    types, etypes uint64
    typemapVar    *proc.Variable
}
//go:linkname findType github.com/go-delve/delve/pkg/proc.(*BinaryInfo).findType
func findType(bi *proc.BinaryInfo, name string) (godwarf.Type, error)
//go:linkname loadModuleData github.com/go-delve/delve/pkg/proc.loadModuleData
func loadModuleData(bi *proc.BinaryInfo, mem proc.MemoryReadWriter) ([]moduleData, error)
//go:linkname imageToModuleData github.com/go-delve/delve/pkg/proc.(*BinaryInfo).imageToModuleData
func imageToModuleData(bi *proc.BinaryInfo, image *proc.Image, mds []moduleData) *moduleData
type localMemory int
func (mem *localMemory) ReadMemory(data []byte, addr uint64) (int, error) {
    buf := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: uintptr(addr), Len: len(data), Cap: len(data)}))
    copy(data, buf)
    return len(data), nil
}
func (mem *localMemory) WriteMemory(addr uint64, data []byte) (int, error) {
    return 0, fmt.Errorf("not support")
}
func dwarfToRuntimeType(bi *proc.BinaryInfo, mds []moduleData, typ godwarf.Type, name string) (typeAddr uint64, err error) {
    if typ.Common().Index >= len(bi.Images) {
        return 0, fmt.Errorf("could not find image for type %s", name)
    }
    img := bi.Images[typ.Common().Index]
    rdr := img.DwarfReader()
    rdr.Seek(typ.Common().Offset)
    e, err := rdr.Next()
    if err != nil {
        return 0, fmt.Errorf("could not find dwarf entry for type:%s err:%s", name, err)
    }
    entryName, ok := e.Val(dwarf.AttrName).(string)
    if !ok || entryName != name {
        return 0, fmt.Errorf("could not find name for type:%s entry:%s", name, entryName)
    }
    off, ok := e.Val(godwarf.AttrGoRuntimeType).(uint64)
    if !ok || off == 0 {
        return 0, fmt.Errorf("could not find runtime type for type:%s", name)
    }
    md := imageToModuleData(bi, img, mds)
    if md == nil {
        return 0, fmt.Errorf("could not find module data for type %s", name)
    }
    typeAddr = md.types + off
    if typeAddr < md.types || typeAddr >= md.etypes {
        return off, nil
    }
    return typeAddr, nil
}

不存在

如果你想"知道"你的类型,你必须注册它们

相关内容

  • 没有找到相关文章