本人小白,记录一下golang的学习过程,首先,让我们看看cgo库是如何运行的,抱怨一句:昨天不知道怎么了,我go语言环境崩了,搞了我一天,麻了。
package main
/*
#include <stdio.h>
#include <stdlib.h>
static void Hello(const char* s) {
puts(s);
}
void check(){
printf("this is in c code\n");
}
*/
import "C"
import "unsafe"
func main() {
C.check()
msg := C.CString("Hello, World\n")
defer C.free(unsafe.Pointer(msg)) //释放内存
C.Hello(msg)
}
最开始我们执行了一个check函数,放入ida之后发现,ida能够识别check函数,而且ida的字符串窗口确实有this is in c code 的字符串,但是交叉引用找不到引用的函数,而check函数一定被使用了,所以猜想是call+寄存器之类的指令。
然后开始调试,从这个地方开始将check函数的地址放入rax中,在断点的cgocall里执行了check函数,
这里就验证了之前的猜想,确实是call rax,但是,对这个check函数,我发现了一个新的问题,希望有大佬教教。
左图是golang编译的check函数,右图是c编译的check函数,为了保障严谨,两个都是同一个版本的gcc,同一个版本的ida,而且我golang源码中也用的是printf,为什么变成了puts
那么,有意思的就来了,整个函数开始危险了,稍微改改check,内存泄露了,如下图,
回到golang中的hello函数,对于函数的执行,与check大同小异,只是defer的free值得跟进
该位置原本是储存hello,world字符串的,free过后是真的free了。
第二步,对于golang中C函数的嵌套
package main
/*
#include <stdio.h>
#include <stdlib.h>
int dfs(int a ){
printf("%d",a);
return 6666;
}
int check(){
printf("this is in c code\n");
return 8848;
}
*/
import "C"
import "fmt"
func main() {
fmt.Println(C.dfs(C.check()))
}
对每个单独的函数的执行流程大致已经清楚了,就不再关注单个函数了
以dfs函数为例,大概是
先call main__Cfunc_dfs_abi0,
然后f7走到call runtime_cgocall,
接着f7走到call runtime_asmcgocall_abi0
最后f7才走到最喜欢的call rax,大概是这么个流程
对于嵌套函数的参数传递,不像C的简单明了,直接放在rax,或者rdx中,(因为这里很简单)
golang写在了内存中,最后是通过调用指针的方式读取,就算不free,也可能被覆盖,不太懂其内存机制
(2290就是8848)
接下来我们看看go编写的dll,
package main
import "C"
import "fmt"
//export touful
func touful() {
fmt.Println("this is in go-dll code")
}
func main() {
}
touful函数的作用只是输出,用c来调用一下
#include <windows.h>
#include<stdio.h>
#include<stdlib.h>
typedef void (*TOUFUL)();
void DynamicUse()
{
HMODULE module = LoadLibrary("lib.dll");
if (module == NULL)
{
printf("加载DLLTest1.dll动态库失败\n");
return;
}
TOUFUL touful_me=(TOUFUL)GetProcAddress(module,"touful");
touful_me();
printf("动态调用");
}
int main (){
DynamicUse();
return 0;
}
这是直接调试的dll代码,经典go的输出结构
这是ida动调c程序的识别
就算一直跟进,我也看不明白了,这全是指令,一个函数名也没有,本人能力有限,跟不下去了
总的来说,go和c的混合编程非常爽,太nb啦!!!