Golang underlying data representaion 有些沙雕出题人就是喜欢用 go 来恶心人,做个总结
简单记录一下 Golang 各种数据类型的存储形式、参数传递、返回值传递等等
本文的数据均在 64位 环境下得出.
为了方便观察代码,关掉编译优化
1 2 3 -gcflags "-N -l" go build -o hw.exe -gcflags "-N -l" HelloWorld.go
函数调用 记住最重要的一点,无论是 x86 还是 x86-64, 都采用栈传递参数,类似 stdcall
返回值传递不通过 eax/rax 等寄存器,也是通过栈。位置是最后一个参数之上。例如最后一个参数的地址是 rsp + 0x8
, 则:
第一个返回值: rsp + 0x10
第二个返回值: rsp + 0x18
。。。。。。
返回值(一个或多个)
1 2 3 4 5 6 7 8 9 10 11 12 func testfunc (a int ) (i,j int ) { i = a + 1 j = a + 2 return }func main () { var a, b int a, b = testfunc(123 ) a = a + 1 b = b + 2 fmt.Println(a, b) }
调用代码如下
1 2 3 4 mov qword ptr [rsp], 123 ; 参数 call main_testfunc mov rax, [rsp+8] ; 返回值1 mov rcx, [rsp+10h] ; 返回值2
方法调用 方法还有一个额外的参数,可以理解为 this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Circle struct { radius float64 }func main () { var c1 Circle c1.radius = 10.00 fmt.Println("area is: " , c1.getArea(10 )) }func (c Circle) getArea (cnt int ) float64 { return 3.14 * c.radius * c.radius * float64 (cnt) }
1 2 3 4 movsd qword ptr [rsp], xmm0 ; Circle struct mov qword ptr [rsp+8], 0Ah ; 10 call main_Circle_getArea movsd xmm0, qword ptr [rsp+10h] ; 获取返回值
可以看出 this 相当于第一个参数。
字符串 1 var stringTest = "flag{}"
内存格式
1 2 3 4 5 6 7 struct String { char * strPtr; int64 size ; } debug060:000000 C0000D9F40 dq offset aFlagListenobje ; "flag{}" debug060:000000 C0000D9F48 dq 6
Slice 切片 make 1 slice1 := make ([]int , len )
make 函数是一个 builtin 函数,貌似由编译器处理?
slice 结构
1 2 3 4 5 struct slice { dq dataPtr; dq len; dq cap; }
slice 结构构造代码由编译器生成,相当于内联 inline make 函数。
1 2 3 4 5 6 7 8 go: slice1 := make([]int, len) asm: call runtime_makeslice mov [rsp+28h], rdx // rdx = makeslice 创建的数组基地址 mov [rsp+30h], rax // rax = len mov [rsp+38h], rcx // rcx = cap
make 最底层调用 runtime_makeslice 分配 Array
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func makeslice (et *_type, len , cap int ) unsafe .Pointer { mem, overflow := math.MulUintptr(et.size, uintptr (cap )) if overflow || mem > maxAlloc || len < 0 || len > cap { mem, overflow := math.MulUintptr(et.size, uintptr (len )) if overflow || mem > maxAlloc || len < 0 { panicmakeslicelen() } panicmakeslicecap() } return mallocgc(mem, et, true ) }
makeslice 返回的是一个指向实际数据的指针(不含管理slice的结构体)相当于 malloc(sizeof(Type) * len)
在访问slice中元素时,一般会检测下标是否小于len, 如果越界则调用runtime_panicIndex
append / copy 1 slice1 = append (slice1, 123 )
append 的时候会检测目标 slice1.len + 1 与 slice1.cap 的大小关系
若 slice1.len + 1 > slice1.cap 则调用 runtime_growslice 扩容
copy 就是复制一个新的切片
切片截取
myvar 的数据结构是新一个新的切片 struct. myvar.dataPtr = &slice1.dataPtr[1] myvar.len = 20 - 1 myvar.cap = slice1.cap - 1 dataPtr 相当于传的一个引用
range 略,不会调用新函数,逻辑比较简单,佷容易理解
map 赋值 runtime_mapassign_*
1 func mapassign (t *maptype, h *hmap, key unsafe.Pointer) unsafe .Pointer {}
3个参数,第三个参数是key的指针,rsp + 0x8 * 2, 返回值是 key 对应的数据指针.
访问 1 func mapaccess1 (t *maptype, h *hmap, key unsafe.Pointer) unsafe .Pointer {}
和赋值同理,区别是这个不会为不存在的 key 创建 key pair.
并发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "fmt" "time" )func say (s string ) { for i := 0 ; i < 5 ; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } }func main () { go say("world" ) say("hello" ) }
1 2 3 4 5 6 7 8 call runtime_newproc rsp=>000000C00010FF58 debug062:000000C00010FF58 dq 10h ; rsp here debug062:000000C00010FF60 dq offset off_515DD0 ;pointer to main_say debug062:000000C00010FF68 dq offset aWorld ; "world" 这里开始就是参数了 debug062:000000C00010FF70 dq 5
其它
convXXX 类似 assert 的功能,逆向时忽略即可
目前就这么多,以后遇到再补吧。
参考 [1] https://www.runoob.com/go/go-slice.html “Go 语言切片(Slice)”
[2] https://golang.org/src/runtime/slice.go “src /runtime /slice.go”