Cyrus Blog

FLAG{S0_H4PPY_C_U_H3R3} (>.<)

从抄作业到详解黑魔法 0x01:cgo

本文共 810 字,预计阅读时间 3 分钟。

原生 C 交互(cgo)、严格类型限制(无泛型)、协程运行(Go routine)算是 Golang 的一些重要特性了。这些特性虽然有效,但是想要用好,却如同 黑魔法 一般,危险但威力巨大。作为初学者,面向 Google 和 StackOverflow 抄作业是快速而有效的选择。但是掌握一些黑魔法的原理也是十分有必要的,毕竟这是成为底层魔法师的必经之路。

本文从 cgo 说起,之后还有反射和协程两个部分。首先, import "C"。如果之前没有使用过 cgo,可以先跑一下样例部分的代码,边读代码边看。

cgo 中 5 个重要的字符串相关函数

func C.CString(string) *C.char

该函数将 Go 字符串转为 C 字符串。转出的 C 字符串以 malloc 的方式在程序的 C 部分的堆上分配。需要注意的是:转出的 C 字符串不会受到 Go 的内存管理控制,也不会被 Go 的 GC 管辖,需要使用 C.free 释放。而 C.free 需要接收一个 unsafe.Pointer 类型的参数,所以通常用法为:

1
2
3
x := C.CString("fuck world")
// Use C.your_func(x) ...
C.free(unsafe.Pointer(x))

func C.CBytes([]byte) unsafe.Pointer

该函数将 Go byte 数组转为 C 数组。转出的 C 字符串以 malloc 的方式在程序的 C 部分的堆上分配。需要注意的是:转出的 C 字符串不会受到 Go 的内存管理控制,也不会被 Go 的 GC 管辖,需要使用 C.free 释放。函数的转出的结果为 unsafe.Pointer,可以直接用 C.free 释放,也可以向 *C.char 等类型做强制转换。

func C.GoString(*C.char) string

该函数将 *C.char 类型转为 Go 字符串。通常可能以 C.GoString((*C.char)(unsafe.Pointer(&someCCharArray))) 形式出现。注意,如果这个 someCCharArray 是在 Go 中定义的(如 []byte 类型),需要将其通过 C.CBytes() 转为 unsafe.Pointer,而非直接对指针做强制类型转换。

func C.GoStringN(*C.char, C.int) string

该函数将 *C.char 类型前 N 位转为 Go 字符串。其中 C.int 不用做类型转换,可以直接使用 Go Int 类型。

func C.GoBytes(unsafe.Pointer, C.int) []byte

该函数将 *C.char 类型前 N 位转为 Go byte 数组。其中 C.int 不用做类型转换,可以直接使用 Go Int 类型。

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

// #include <stdio.h>
// #include <stdlib.h>
// char foo[16] = {119, 114, 105, 116, 101, 0, 255, 255, 0, 246, 0, 106, 23, 204, 97, 192};
import "C"
import "fmt"
import "unsafe"

func main() {
x := C.foo

fmt.Printf("%v\n", C.foo)
fmt.Printf("%v\n", x)
fmt.Printf("%s\n", C.GoString((*C.char)(unsafe.Pointer(&C.foo))))
fmt.Printf("%s\n", C.GoString((*C.char)(unsafe.Pointer(&x))))

var bar = []byte{119, 114, 105, 116, 101, 0, 255, 255, 0, 246, 0, 106, 23, 204, 97, 192}
fmt.Printf("%s\n", string(bar))
fmt.Printf("%s\n", C.GoString((*C.char)(C.CBytes(bar))))

fmt.Printf("%s\n", C.GoStringN((*C.char)(unsafe.Pointer(&x)), 7))
}

依次输出为:

1
2
3
4
5
6
7
[119 114 105 116 101 0 -1 -1 0 -10 0 106 23 -52 97 -64]  // 正常
[119 114 105 116 101 0 -1 -1 0 -10 0 106 23 -52 97 -64] // 正常
write // 正常
write // 正常
writeˇˇˆjÃa¿ // 有多余
write // 正常
writeˇ // 其中第6位为0

参考

cgo - The Go Programming Language