如何在运行时发现所有包类型?
How to discover all package types at runtime?
据我所知(参见 here, and here) there is no type discovery mechanism in the reflect package,它期望您已经拥有要检查的类型或值的实例。
是否有任何 other 方法来发现 运行ning go 包中的所有导出类型(尤其是结构)?
这是我希望拥有的(但它不存在):
import "time"
import "fmt"
func main() {
var types []reflect.Type
types = reflect.DiscoverTypes(time)
fmt.Println(types)
}
最终目标是能够发现满足特定条件的包的所有结构,然后能够实例化这些结构的新实例。
顺便说一句,识别类型的注册函数不是我的用例的有效方法。
无论您认为这是个好主意,这就是我想要此功能的原因(因为我知道您会问):
我写了一个 code generation utility 加载源文件并构建 AST 来扫描嵌入指定类型的类型。该实用程序的输出是一组基于发现的类型的 go 测试函数。我使用 go generate
调用此实用程序来创建测试函数,然后 运行 go test
来执行生成的测试函数。每次测试更改(或添加新类型)时,我都必须重新运行 go generate before re-运行ning go test
。这就是注册功能不是有效选项的原因。我想避免 go generate
步骤,但这需要我的实用程序成为由 运行ning 包导入的库。库代码需要在 init()
期间以某种方式扫描 运行ning 命名空间以查找嵌入预期库类型的类型。
没有。
如果您想 'know' 您的类型,您必须注册它们。
不幸的是,我认为这是不可能的。包在 Go 中不是 "actionable",你不能在它上面 "call a function"。您也不能在类型上调用函数,但可以在类型的实例上调用 reflect.TypeOf
并获得 reflect.Type
,这是类型的运行时抽象。只是没有这样的包机制,没有 reflect.Package
.
话虽如此,您可以 file an issue 关于缺少(以及添加的实用性)reflect.PackageOf
等
(2019 年更新见底部)
警告:未经测试且不可靠。每当发布新版本的 Go 时都会中断。
通过稍微修改一下 Go 的运行时,可以获得运行时知道的所有类型。在自己的包中包含一个小的汇编文件,包含:
TEXT yourpackage·typelinks(SB), NOSPLIT, [=10=]-0
JMP reflect·typelinks(SB)
在yourpackage
中声明函数原型(无函数体):
func typelinks() []*typeDefDummy
除了类型定义:
type typeDefDummy struct {
_ uintptr // padding
_ uint64 // padding
_ [3]uintptr // padding
StrPtr *string
}
然后只需调用类型链接,遍历切片并读取每个 StrPtr 的名称。寻找以 yourpackage
开头的那些。请注意,如果在不同路径中有两个名为 yourpackage
的包,则此方法将无法明确工作。
can I somehow hook into the reflect package to instantiate new instances of those names?
是的,假设 d
是类型 *typeDefDummy
的值(注意星号,非常重要):
t := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&d)))
现在 t
是一个 reflect.Type
值,您可以使用它来实例化 reflect.Value
s。
编辑:我成功测试并执行了这段代码,uploaded it as a gist.
调整包名称并根据需要包含路径。
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), [=15=]-0
JMP reflect·typelinks(SB)
TEXT tl·add(SB), [=15=]-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.5 中,您可以使用新包 types and importer 检查二进制包和源包。例如:
package main
import (
"fmt"
"go/importer"
)
func main() {
pkg, err := importer.Default().Import("time")
if err != nil {
fmt.Printf("error: %s\n", err.Error())
return
}
for _, declName := range pkg.Scope().Names() {
fmt.Println(declName)
}
}
您可以使用包 go/build to extract all the packages installed. Or you can configure the Lookup
导入器检查环境外的二进制文件。
在 1.5 之前,唯一简单的方法是使用包 ast 来编译源代码。
感谢@thwd 和@icio,按照您的指示,它今天在 1.13.6 上仍然有效。
按照您的方式进行 tl.s 将是:
TEXT ·typelinks(SB), [=10=]-0
JMP reflect·typelinks(SB)
是的,没有包名,里面也没有 "add" 函数。
然后按照@icio 的方式将 "add" 函数更改为:
func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
return unsafe.Pointer(uintptr(p) + x)
}
那么现在一切正常了。 :)
go 1.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(在线回复无效,但在本地有效)
- go 1.11 dwarf debugging symbols增加运行时类型信息后,可通过此地址获取运行时类型
- 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:%s\n", 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
}
据我所知(参见 here, and here) there is no type discovery mechanism in the reflect package,它期望您已经拥有要检查的类型或值的实例。
是否有任何 other 方法来发现 运行ning go 包中的所有导出类型(尤其是结构)?
这是我希望拥有的(但它不存在):
import "time"
import "fmt"
func main() {
var types []reflect.Type
types = reflect.DiscoverTypes(time)
fmt.Println(types)
}
最终目标是能够发现满足特定条件的包的所有结构,然后能够实例化这些结构的新实例。
顺便说一句,识别类型的注册函数不是我的用例的有效方法。
无论您认为这是个好主意,这就是我想要此功能的原因(因为我知道您会问):
我写了一个 code generation utility 加载源文件并构建 AST 来扫描嵌入指定类型的类型。该实用程序的输出是一组基于发现的类型的 go 测试函数。我使用 go generate
调用此实用程序来创建测试函数,然后 运行 go test
来执行生成的测试函数。每次测试更改(或添加新类型)时,我都必须重新运行 go generate before re-运行ning go test
。这就是注册功能不是有效选项的原因。我想避免 go generate
步骤,但这需要我的实用程序成为由 运行ning 包导入的库。库代码需要在 init()
期间以某种方式扫描 运行ning 命名空间以查找嵌入预期库类型的类型。
没有。
如果您想 'know' 您的类型,您必须注册它们。
不幸的是,我认为这是不可能的。包在 Go 中不是 "actionable",你不能在它上面 "call a function"。您也不能在类型上调用函数,但可以在类型的实例上调用 reflect.TypeOf
并获得 reflect.Type
,这是类型的运行时抽象。只是没有这样的包机制,没有 reflect.Package
.
话虽如此,您可以 file an issue 关于缺少(以及添加的实用性)reflect.PackageOf
等
(2019 年更新见底部)
警告:未经测试且不可靠。每当发布新版本的 Go 时都会中断。
通过稍微修改一下 Go 的运行时,可以获得运行时知道的所有类型。在自己的包中包含一个小的汇编文件,包含:
TEXT yourpackage·typelinks(SB), NOSPLIT, [=10=]-0
JMP reflect·typelinks(SB)
在yourpackage
中声明函数原型(无函数体):
func typelinks() []*typeDefDummy
除了类型定义:
type typeDefDummy struct {
_ uintptr // padding
_ uint64 // padding
_ [3]uintptr // padding
StrPtr *string
}
然后只需调用类型链接,遍历切片并读取每个 StrPtr 的名称。寻找以 yourpackage
开头的那些。请注意,如果在不同路径中有两个名为 yourpackage
的包,则此方法将无法明确工作。
can I somehow hook into the reflect package to instantiate new instances of those names?
是的,假设 d
是类型 *typeDefDummy
的值(注意星号,非常重要):
t := reflect.TypeOf(*(*interface{})(unsafe.Pointer(&d)))
现在 t
是一个 reflect.Type
值,您可以使用它来实例化 reflect.Value
s。
编辑:我成功测试并执行了这段代码,uploaded it as a gist.
调整包名称并根据需要包含路径。
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), [=15=]-0
JMP reflect·typelinks(SB)
TEXT tl·add(SB), [=15=]-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.5 中,您可以使用新包 types and importer 检查二进制包和源包。例如:
package main
import (
"fmt"
"go/importer"
)
func main() {
pkg, err := importer.Default().Import("time")
if err != nil {
fmt.Printf("error: %s\n", err.Error())
return
}
for _, declName := range pkg.Scope().Names() {
fmt.Println(declName)
}
}
您可以使用包 go/build to extract all the packages installed. Or you can configure the Lookup
导入器检查环境外的二进制文件。
在 1.5 之前,唯一简单的方法是使用包 ast 来编译源代码。
感谢@thwd 和@icio,按照您的指示,它今天在 1.13.6 上仍然有效。
按照您的方式进行 tl.s 将是:
TEXT ·typelinks(SB), [=10=]-0
JMP reflect·typelinks(SB)
是的,没有包名,里面也没有 "add" 函数。
然后按照@icio 的方式将 "add" 函数更改为:
func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
return unsafe.Pointer(uintptr(p) + x)
}
那么现在一切正常了。 :)
go 1.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(在线回复无效,但在本地有效)
- go 1.11 dwarf debugging symbols增加运行时类型信息后,可通过此地址获取运行时类型
- 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:%s\n", 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
}