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

歡迎訪問 生活随笔!

生活随笔

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

生活经验

Golang反射机制的实现分析——reflect.Type类型名称

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

? ? ? ? 現在越來越多的java、php或者python程序員轉向了Golang。其中一個比較重要的原因是,它和C/C++一樣,可以編譯成機器碼運行,這保證了執行的效率。在上述解釋型語言中,它們都支持了“反射”機制,讓程序員可以很方便的構建一些動態邏輯。這是C/C++相對薄弱的環節,而Golang卻有良好的支持。本系列,我們將通過反匯編Golang的編譯結果,探究其反射實現的機制。(轉載請指明出于breaksoftware的csdn博客)

? ? ? ? 為了防止編譯器做優化,例子中的源碼都通過下面的指令編譯

go build -gcflags "-N -l" [xxxxxx].go

類型名稱

基本類型

package mainimport ("fmt""reflect"
)func main() {t := reflect.TypeOf(1)s := t.Name()fmt.Println(s)
}

? ? ? ? 這段代碼最終將打印出1的類型——int。

? ? ? ? main函數的入口地址是main.main。我們使用gdb在這個位置下斷點,然后反匯編。略去一部分函數準備工作,我們看到

   0x0000000000487c6f <+31>:    mov    %rbp,0xa0(%rsp)0x0000000000487c77 <+39>:    lea    0xa0(%rsp),%rbp0x0000000000487c7f <+47>:    lea    0xfb5a(%rip),%rax        # 0x4977e00x0000000000487c86 <+54>:    mov    %rax,(%rsp)0x0000000000487c8a <+58>:    lea    0x40097(%rip),%rax        # 0x4c7d28 <main.statictmp_0>0x0000000000487c91 <+65>:    mov    %rax,0x8(%rsp)0x0000000000487c96 <+70>:    callq  0x46f210 <reflect.TypeOf>

? ? ? ? 第3~4行,這段代碼將地址0x4977e0壓棧。之后在5~6行,又將0x4c7d28壓棧。64位系統下,程序的壓棧不像32位系統使用push指令,而是使用mov指令間接操作rsp寄存器指向的棧空間。

? ? ? ? 第7行,調用了reflect.TypeOf方法,在Golang的源碼中,該方法的相關定義位于\src\reflect\type.go中

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {eface := *(*emptyInterface)(unsafe.Pointer(&i))return toType(eface.typ)
}// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {if t == nil {return nil}return t
}

? ? ? ??reflect.emptyInterface是一個保存數據類型信息和裸指針的結構體,它位于\src\reflect\value.go

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

? ? ? ? 之前壓棧的兩個地址0x4977e0和0x4c7d28分別對應于type和word。

(gdb) x/16xb $rsp
0xc42003fed0:   0xe0    0x77    0x49    0x00    0x00    0x00    0x00    0x00
0xc42003fed8:   0x28    0x7d    0x4c    0x00    0x00    0x00    0x00    0x00

? ? ? ? 這樣在內存上便構成了一個emptyInterface結構。下面我們查看它們的內存,0x4c7d28保存的值0x01即是我們傳入reflect.TypeOf的值。

0x4977e0:       0x08    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x4c7d28 <main.statictmp_0>:    0x01    0x00    0x00    0x00    0x00    0x00    0x00    0x00

? ? ? ? reflect.rtype定義位于src\reflect\type.go

// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {size       uintptr……str        nameOff  // string formptrToThis  typeOff  // type for pointer to this type, may be zero
}

? ? ? ? 在reflect.TypeOf方法中,我們看到reflect.toType隱式的將reflect.rtype轉換成了reflect.Type類型,而reflect.Type類型和它完全不一樣

type Type interface {Align() intFieldAlign() intMethod(int) Method……
}

? ? ? ? 從Golang的源碼的角度去解析似乎進入了死胡同,我們繼續轉向匯編層面,查看reflect.TypeOf的實現

   0x000000000046f210 <+0>:     mov    0x8(%rsp),%rax0x000000000046f215 <+5>:     test   %rax,%rax0x000000000046f218 <+8>:     je     0x46f22c <reflect.TypeOf+28>0x000000000046f21a <+10>:    lea    0xaddbf(%rip),%rcx        # 0x51cfe0 <go.itab.*reflect.rtype,reflect.Type>0x000000000046f221 <+17>:    mov    %rcx,0x18(%rsp)0x000000000046f226 <+22>:    mov    %rax,0x20(%rsp)0x000000000046f22b <+27>:    retq   0x000000000046f22c <+28>:    xor    %eax,%eax0x000000000046f22e <+30>:    mov    %rax,%rcx0x000000000046f231 <+33>:    jmp    0x46f221 <reflect.TypeOf+17>

? ? ? ? 之前介紹過,在調用reflect.TypeOf前,已經在棧上構建了一個emptyInterface結構體。由于此函數只關注類型,而不關注值,所以此時只是使用了typ字段——rsp+0x08地址的值。

? ? ? ? 比較有意思的是這個過程獲取了一個內存地址0x51cfe0,目前我們尚不知它是干什么的。之后我們會再次關注它。

   0x0000000000487c9b <+75>:    mov    0x10(%rsp),%rax0x0000000000487ca0 <+80>:    mov    0x18(%rsp),%rcx0x0000000000487ca5 <+85>:    mov    %rax,0x38(%rsp)0x0000000000487caa <+90>:    mov    %rcx,0x40(%rsp)0x0000000000487caf <+95>:    mov    0xc0(%rax),%rax0x0000000000487cb6 <+102>:   mov    %rcx,(%rsp)0x0000000000487cba <+106>:   callq  *%rax

? ? ? ? 從reflect.TypeOf調用中返回后,rax寄存器保存的是0x51cfe0,然后在第5行計算了該地址偏移0xC0的地址中保存的值。最后在第7行調用了該地址所指向的函數。

(gdb) x/64bx 0x51cfe0+0xc0 
0x51d0a0 <go.itab.*reflect.rtype,reflect.Type+192>:     0x80    0xcc    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0a8 <go.itab.*reflect.rtype,reflect.Type+200>:     0xf0    0xd6    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0b0 <go.itab.*reflect.rtype,reflect.Type+208>:     0x60    0xd7    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0b8 <go.itab.*reflect.rtype,reflect.Type+216>:     0xe0    0xbe    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0c0 <go.itab.*reflect.rtype,reflect.Type+224>:     0xd0    0xd7    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0c8 <go.itab.*reflect.rtype,reflect.Type+232>:     0x80    0xd8    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0d0 <go.itab.*reflect.rtype,reflect.Type+240>:     0x90    0xcb    0x46    0x00    0x00    0x00    0x00    0x00
0x51d0d8 <go.itab.*reflect.rtype,reflect.Type+248>:     0x60    0xb9    0x46    0x00    0x00    0x00    0x00    0x00

? ? ? ? 使用反匯編指令看下0x46cc80處的函數,可以看到它是reflect.(*rtype).Name()

(gdb) disassemble 0x46cc80
Dump of assembler code for function reflect.(*rtype).Name:

? ? ? ? 我們再看0x51d0a0附近的內存中的值,發現其很有規律。其實它們都是reflect.(*rtype)下的函數地址。

(gdb) disassemble 0x46b960
Dump of assembler code for function reflect.(*rtype).Size:(gdb) disassemble 0x46cb90
Dump of assembler code for function reflect.(*rtype).PkgPath:

? ? ? ? 這些方法也是reflect.Type接口暴露的方法。當我們調用Type暴露的方法的時候,實際底層調用的rtype對應的同名方法。

type Type interface {Align() intFieldAlign() int……Name() stringPkgPath() stringSize() uintptr……
}

? ? ? ? 從reflect.TypeOf調用返回后,就調用reflect.(*rtype).Name()。它的相關實現是

func (t *rtype) Name() string {if t.tflag&tflagNamed == 0 {return ""}s := t.String()……return s[i+1:]
}func (t *rtype) String() string {s := t.nameOff(t.str).name()if t.tflag&tflagExtraStar != 0 {return s[1:]}return s
}type name struct {bytes *byte
}func (t *rtype) nameOff(off nameOff) name {return name{(*byte)(resolveNameOff(unsafe.Pointer(t), int32(off)))}
}

? ? ? ? 這段代碼表示,變量的類型值和rtype的地址和rtype.str字段有關。而這個rtype就是reflect.TypeOf調用前構建的emptyInterface的rtype。我們使用gdb查看該結構體

$4 = {size = 0x8, ptrdata = 0x0, hash = 0xf75371fa, tflag = 0x7, align = 0x8, fieldAlign = 0x8, kind = 0x82, alg = 0x529a70, gcdata = 0x4c6cd8, str = 0x3a3, ptrToThis = 0xac60
}

? ? ? ? 最后我們就要看相對復雜的resolveNameOff實現。

func resolveNameOff(ptrInModule unsafe.Pointer, off nameOff) name {if off == 0 {return name{}}base := uintptr(ptrInModule)for md := &firstmoduledata; md != nil; md = md.next {if base >= md.types && base < md.etypes {res := md.types + uintptr(off)if res > md.etypes {println("runtime: nameOff", hex(off), "out of range", hex(md.types), "-", hex(md.etypes))throw("runtime: name offset out of range")}return name{(*byte)(unsafe.Pointer(res))}}}// No module found. see if it is a run time name.reflectOffsLock()res, found := reflectOffs.m[int32(off)]reflectOffsUnlock()if !found {println("runtime: nameOff", 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: name offset base pointer out of range")}return name{(*byte)(res)}
}

? ? ? ? 我們先忽略17行之后的代碼。從6~15行,程序會遍歷模塊信息,并檢測rtype地址是否在該區間之內(base >= md.types && base < md.etypes)。如果在此區間,則返回相對于該區間起始地址的off偏移地址。

? ? ? ? 所以,rtype.str字段的偏移不是相對于rtype的起始地址。而是相對于rtype起始地址所在的區間的保存type信息區塊([md.types, md.etypes))起始地址。

? ? ? ? 和rtype信息一樣,firstmoduledata的信息也是全局初始化的。我們使用IDA協助查看它位置。

? ? ? ? 可以看到這些數據都存儲在elf的.noptrdata節中,該節中數據是Golang構建程序時保存全局數據的地方。所以這種“反射”是編譯器在編譯的過程中,暗中幫我們構建了和變量等有關的信息。

? ? ? ? 我們再看下模塊起始地址0x488000偏移rtype.str=0x3a3的地址空間。

? ? ? ? 這樣我們就看到int字段的來源了。

自定義結構類型

package mainimport ("fmt""reflect"
)type t20190107 struct {v string
}func main() {i2 := t20190107{"s20190107"}t2 := reflect.TypeOf(i2)s2 := t2.Name()fmt.Println(s2)
}

? ? ? ? 這段代碼故意構建一個名字很特殊的結構體,我們看下反匯編的結果。

   0x0000000000487c6f <+31>:    mov    %rbp,0xc0(%rsp)0x0000000000487c77 <+39>:    lea    0xc0(%rsp),%rbp0x0000000000487c7f <+47>:    movq   $0x0,0x58(%rsp)0x0000000000487c88 <+56>:    movq   $0x0,0x60(%rsp)0x0000000000487c91 <+65>:    lea    0x2f868(%rip),%rax        # 0x4b75000x0000000000487c98 <+72>:    mov    %rax,0x58(%rsp)0x0000000000487c9d <+77>:    movq   $0x9,0x60(%rsp)0x0000000000487ca6 <+86>:    mov    %rax,0x98(%rsp)0x0000000000487cae <+94>:    movq   $0x9,0xa0(%rsp)0x0000000000487cba <+106>:   lea    0x196ff(%rip),%rax        # 0x4a13c00x0000000000487cc1 <+113>:   mov    %rax,(%rsp)0x0000000000487cc5 <+117>:   lea    0x98(%rsp),%rax0x0000000000487ccd <+125>:   mov    %rax,0x8(%rsp)0x0000000000487cd2 <+130>:   callq  0x40c7e0 <runtime.convT2E>

? ? ? ? 第5行,我們獲取了0x4b7500空間地址,我們看下其值,就是我們初始化結構體的字面量“s20190107"

? ? ? ? 第10行,我們又獲取了0x4a13c0地址。依據之前的經驗,該地址保存的是reflect.rtype類型數據。但是由于之后調用的runtime.convT2E,所以其類型是runtime._type。

func convT2E(t *_type, elem unsafe.Pointer) (e eface) {if raceenabled {raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&t)), funcPC(convT2E))}if msanenabled {msanread(elem, t.size)}x := mallocgc(t.size, t, true)// TODO: We allocate a zeroed object only to overwrite it with actual data.// Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.typedmemmove(t, x, elem)e._type = te.data = xreturn
}

? ? ? ? 其實runtime._type和reflect.rtype的定義是一樣的

type _type struct {size       uintptr……str       nameOffptrToThis typeOff
}type rtype struct {size       uintptr……	str        nameOff  // string formptrToThis  typeOff  // type for pointer to this type, may be zero
}

? ? ? ? 而reflect.emptyInterface和runtime.eface也一樣

type eface struct {_type *_typedata  unsafe.Pointer
}type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

? ? ? ? 這讓我們對基本類型的分析結果和經驗在此處依然適用。

? ? ? ? 使用gdb把_type信息打印出來,可以發現這次類型名稱的偏移量0x6184比較大。

$3 = {size = 0x10, ptrdata = 0x8, hash = 0xe1c71878, tflag = 0x7, align = 0x8, fieldalign = 0x8, kind = 0x19, alg = 0x529a90, gcdata = 0x4c6dc4, str = 0x6184, ptrToThis = 0xae80
}

? ? ? ??runtime.convT2E第8行在垃圾回收器上構建了一段內存,并將裸指針指向的數據保存到該地址空間中。然后在第12~13行重新構建了eface結構體。

? ? ? ? 之后進入reflect.TypeOf邏輯,這和之前分析的流程一致。我們最后看下保存的類型數據的全局區域

總結

  • 編譯器在編譯過程中,將變量對應的類型信息(runtime._type或reflect.rtype)保存在.rodata節中。
  • 字面量直接使用reflect.TypeOf方法獲取rtype類型函數地址列表
  • 變量使用runtime.convT2*類型轉換函數,使用垃圾回收器上分配的空間存儲變量值,然后調用reflect.TypeOf方法
  • 遍歷保存在.noptrdata節中的模塊信息,確認類型信息的存儲地址位于的模塊區域。然后以該區塊中保存type信息的區塊起始地址為基準,使用rtype.str字段表示的偏移量計算出名稱在內存中的位置。

?

總結

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

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