据我所知(见这里和这里),在反射包中没有类型发现机制,它期望您已经拥有想要检查的类型或值的实例。
是否有其他方法来发现运行中的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
}
不存在
如果你想"知道"你的类型,你必须注册它们