日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Golang反射机制的实现分析——reflect.Type方法查找和调用

發布時間:2023/11/27 生活经验 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Golang反射机制的实现分析——reflect.Type方法查找和调用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

? ? ? ? 在《Golang反射機制的實現分析——reflect.Type類型名稱》一文中,我們分析了Golang獲取類型基本信息的流程。本文將基于上述知識和經驗,分析方法的查找和調用。(轉載請指明出于breaksoftware的csdn博客)

方法

package mainimport ("fmt""reflect"
)type t20190107 struct {v int
}func (t t20190107) F() int {return t.v
}func main() {i := t20190107{678}t := reflect.TypeOf(i)for it := 0; it < t.NumMethod(); it++ {fmt.Println(t.Method(it).Name)}f, _ := t.MethodByName("F")fmt.Println(f.Name)r := f.Func.Call([]reflect.Value{reflect.ValueOf(i)})[0].Int()fmt.Println(r)
}

? ? ? ? 這段代碼,我們構建了一個實現了F()方法的結構體。然后使用反射機制,通過遍歷和名稱查找方式,找到方法并調用它。

? ? ? ? 調用reflect.TypeOf之前的邏輯,我們已經在上節中講解了。本文不再贅述。

   0x00000000004b0226 <+134>:   callq  0x491150 <reflect.TypeOf>0x00000000004b022b <+139>:   mov    0x18(%rsp),%rax0x00000000004b0230 <+144>:   mov    0x10(%rsp),%rcx……0x00000000004b026a <+202>:   mov    0xe0(%rsp),%rax0x00000000004b0272 <+210>:   mov    0xd8(%rax),%rax0x00000000004b0279 <+217>:   mov    0xe8(%rsp),%rcx0x00000000004b0281 <+225>:   mov    %rcx,(%rsp)0x00000000004b0285 <+229>:   callq  *%rax0x00000000004b0287 <+231>:   mov    0x8(%rsp),%rax0x00000000004b028c <+236>:   mov    %rax,0x90(%rsp)0x00000000004b0294 <+244>:   mov    0x78(%rsp),%rcx0x00000000004b0299 <+249>:   cmp    %rax,%rcx

? ? ? ? 這段邏輯對應于上面go代碼中的第19行for循環邏輯。

? ? ? ? 匯編代碼的第9行,調用了一個保存于寄存器中的地址。依據之前的分析經驗,這個地址是rtype.NumMethod()方法地址。

(gdb) disassemble $rax
Dump of assembler code for function reflect.(*rtype).NumMethod:

? ? ? ? 看下Golang的代碼,可以發現其區分了類型是否是“接口”。“接口”類型的計算比較特殊,而其他類型則調用rtype.exportedMethods()方法。

func (t *rtype) NumMethod() int {if t.Kind() == Interface {tt := (*interfaceType)(unsafe.Pointer(t))return tt.NumMethod()}if t.tflag&tflagUncommon == 0 {return 0 // avoid methodCache synchronization}return len(t.exportedMethods())
}

? ? ? ? 因為我們這個例子是struct類型,所以調用的是下面的方法

var methodCache sync.Map // map[*rtype][]methodfunc (t *rtype) exportedMethods() []method {methodsi, found := methodCache.Load(t)if found {return methodsi.([]method)}

? ? ? ??methodCache是個全局變量,它以rtype為key,保存了其對應的方法信息。這個緩存在初始時沒有數據,所以我們第一次對某rtype調用該方法,是找不到其對應的緩存的。

	ut := t.uncommon()if ut == nil {return nil}

? ? ? ??rtype.uncommon()根據變量類型,在內存中尋找uncommonType信息。

func (t *rtype) uncommon() *uncommonType {if t.tflag&tflagUncommon == 0 {return nil}switch t.Kind() {case Struct:return &(*structTypeUncommon)(unsafe.Pointer(t)).ucase Ptr:……}
}

? ? ? ? 這段邏輯,我們只要看下匯編將該地址如何轉換的

   0x000000000048d4df <+143>:   cmp    $0x19,%rcx0x000000000048d4e3 <+147>:   jne    0x48d481 <reflect.(*rtype).uncommon+49>0x000000000048d4e5 <+149>:   add    $0x50,%rax0x000000000048d4e9 <+153>:   mov    %rax,0x10(%rsp)0x000000000048d4ee <+158>:   retq  

? ? ? ? rax寄存器之前保存的是rtype的地址0x4d1320,于是uncommonType的信息保存于0x4d1320+0x50位置。

type uncommonType struct {pkgPath nameOff // import path; empty for built-in types like int, stringmcount  uint16  // number of methods_       uint16  // unusedmoff    uint32  // offset from this uncommontype to [mcount]method_       uint32  // unused
}

? ? ? ? 依據其結構體,我們可以得出各個變量的值:mcount=0x1,moff=0x28。此處mcount的值正是測試結構體的方法個數1。

? ? ? ? 獲取完uncommonType信息,我們需要通過其找到方法信息

	allm := ut.methods()
func (t *uncommonType) methods() []method {return (*[1 << 16]method)(add(unsafe.Pointer(t), uintptr(t.moff)))[:t.mcount:t.mcount]
}

? ? ? ? 這個計算比較簡單,只是在uncommonType的地址0x4d1370基礎上偏移t.moff=0x28即可。我們查看下其內存

(gdb) x/16xb 0x4d1370+0x28
0x4d1398:       0x07    0x00    0x00    0x00    0x40    0x18    0x01    0x00
0x4d13a0:       0x80    0xf8    0x0a    0x00    0x80    0xf1    0x0a    0x00
// Method on non-interface type
type method struct {name nameOff // name of methodmtyp typeOff // method type (without receiver)ifn  textOff // fn used in interface call (one-word receiver)tfn  textOff // fn used for normal method call
}

? ? ? ? 和method結構對應上就是method{nameOff=0x07, typeOff=0x011840, ifn=0x0af880, tfn=0x0af180}。

? ? ? ? 獲取方法信息后,exportedMethods篩選出可以對外訪問的方法,然后將結果保存到methodCache中。這樣下次就不用再找一遍了。

	……methodsi, _ = methodCache.LoadOrStore(t, methods)return methodsi.([]method)
}

? ? ? ? 獲取到方法個數后,我們就可以使用rtype.Method()方法獲取方法信息了。和其他rtype方法一樣,Method也是通過指針偏移算出來的。

   0x00000000004b02a3 <+259>:   mov    0xe0(%rsp),%rax0x00000000004b02ab <+267>:   mov    0xb0(%rax),%rax0x00000000004b02b2 <+274>:   mov    0x78(%rsp),%rcx0x00000000004b02b7 <+279>:   mov    0xe8(%rsp),%rdx0x00000000004b02bf <+287>:   mov    %rcx,0x8(%rsp)0x00000000004b02c4 <+292>:   mov    %rdx,(%rsp)0x00000000004b02c8 <+296>:   callq  *%rax
func (t *rtype) Method(i int) (m Method) {if t.Kind() == Interface {tt := (*interfaceType)(unsafe.Pointer(t))return tt.Method(i)}methods := t.exportedMethods()if i < 0 || i >= len(methods) {panic("reflect: Method index out of range")}p := methods[i]pname := t.nameOff(p.name)m.Name = pname.name()fl := flag(Func)mtyp := t.typeOff(p.mtyp)ft := (*funcType)(unsafe.Pointer(mtyp))in := make([]Type, 0, 1+len(ft.in()))in = append(in, t)for _, arg := range ft.in() {in = append(in, arg)}out := make([]Type, 0, len(ft.out()))for _, ret := range ft.out() {out = append(out, ret)}mt := FuncOf(in, out, ft.IsVariadic())m.Type = mttfn := t.textOff(p.tfn)fn := unsafe.Pointer(&tfn)m.Func = Value{mt.(*rtype), fn, fl}m.Index = ireturn m
}

? ? ? ? Method方法構建了一個Method結構體,其中方法名稱、入參、出參等都不再分析。我們關注下函數地址的獲取,即第27行。

? ? ? ??textOff底層調用的是

func (t *_type) textOff(off textOff) unsafe.Pointer {base := uintptr(unsafe.Pointer(t))var md *moduledatafor next := &firstmoduledata; next != nil; next = next.next {if base >= next.types && base < next.etypes {md = nextbreak}}if md == nil {reflectOffsLock()res := reflectOffs.m[int32(off)]reflectOffsUnlock()if res == nil {println("runtime: textOff", hex(off), "base", hex(base), "not in ranges:")for next := &firstmoduledata; next != nil; next = next.next {println("\ttypes", hex(next.types), "etypes", hex(next.etypes))}throw("runtime: text offset base pointer out of range")}return res}res := uintptr(0)// The text, or instruction stream is generated as one large buffer.  The off (offset) for a method is// its offset within this buffer.  If the total text size gets too large, there can be issues on platforms like ppc64 if// the target of calls are too far for the call instruction.  To resolve the large text issue, the text is split// into multiple text sections to allow the linker to generate long calls when necessary.  When this happens, the vaddr// for each text section is set to its offset within the text.  Each method's offset is compared against the section// vaddrs and sizes to determine the containing section.  Then the section relative offset is added to the section's// relocated baseaddr to compute the method addess.if len(md.textsectmap) > 1 {for i := range md.textsectmap {sectaddr := md.textsectmap[i].vaddrsectlen := md.textsectmap[i].lengthif uintptr(off) >= sectaddr && uintptr(off) <= sectaddr+sectlen {res = md.textsectmap[i].baseaddr + uintptr(off) - uintptr(md.textsectmap[i].vaddr)break}}} else {// single text sectionres = md.text + uintptr(off)}if res > md.etext {println("runtime: textOff", hex(off), "out of range", hex(md.text), "-", hex(md.etext))throw("runtime: text offset out of range")}return unsafe.Pointer(res)
}

? ? ? ? 我們又看到模塊信息了,這在《Golang反射機制的實現分析——reflect.Type類型名稱》一文中也介紹過。

? ? ? ? 通過rtype的地址確定哪個模塊,然后查看模塊的代碼塊信息。

? ? ? ? 第33行顯示,如果該模塊中的代碼塊多于1個,則通過偏移量查找其所處的代碼塊,然后通過虛擬地址的偏移差算出代碼的真實地址。

? ? ? ? 如果代碼塊只有一個,則只要把模塊中text字段表示的代碼塊起始地址加上偏移量即可。

? ? ? ? 在我們的例子中,只有一個代碼塊。所以使用下面的方式。

? ? ? ? 之前我們通過內存分析的偏移量tfn=0x0af180,而此模塊記錄的代碼塊起始地址是0x401000。則反匯編這塊地址

(gdb) disassemble 0x401000+0x0af180
Dump of assembler code for function main.t20190107.F:0x00000000004b0180 <+0>:     movq   $0x0,0x10(%rsp)0x00000000004b0189 <+9>:     mov    0x8(%rsp),%rax0x00000000004b018e <+14>:    mov    %rax,0x10(%rsp)0x00000000004b0193 <+19>:    retq  

? ? ? ? 如此我們便取到了函數地址。

? ? ? ??rtype.MethodByName方法實現比較簡單,它只是遍歷并通過函數名匹配方法信息,然后返回

func (t *rtype) MethodByName(name string) (m Method, ok bool) {if t.Kind() == Interface {tt := (*interfaceType)(unsafe.Pointer(t))return tt.MethodByName(name)}ut := t.uncommon()if ut == nil {return Method{}, false}utmethods := ut.methods()for i := 0; i < int(ut.mcount); i++ {p := utmethods[i]pname := t.nameOff(p.name)if pname.isExported() && pname.name() == name {return t.Method(i), true}}return Method{}, false
}

? ? ? ? 反射出來的函數使用Call方法調用。其底層就是調用上面確定的函數地址。

func (v Value) Call(in []Value) []Value {v.mustBe(Func)v.mustBeExported()return v.call("Call", in)
}func (v Value) call(op string, in []Value) []Value {// Get function pointer, type.……if v.flag&flagMethod != 0 {rcvr = vrcvrtype, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift)} else if v.flag&flagIndir != 0 {fn = *(*unsafe.Pointer)(v.ptr)} else {fn = v.ptr}……// Call.call(frametype, fn, args, uint32(frametype.size), uint32(retOffset))……
}

總結

  • 通過rtype中的kind信息確定保存方法信息的偏移量。
  • 相對于rtype起始地址,使用上面偏移量獲取方法信息組。
  • 通過方法信息中的偏移量和模塊信息中記錄的代碼塊起始地址,確定方法的地址。
  • 通過反射調用方法比直接調用方法要復雜很多

總結

以上是生活随笔為你收集整理的Golang反射机制的实现分析——reflect.Type方法查找和调用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。