Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loader 是so调用 函数失败 #24

Closed
Hello-xBugs opened this issue Mar 31, 2021 · 32 comments
Closed

loader 是so调用 函数失败 #24

Hello-xBugs opened this issue Mar 31, 2021 · 32 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@Hello-xBugs
Copy link

loader 是so加载.o文件成功但是掉用 .o文件函k数失败了,loader可以是so吗?

@pkujhd
Copy link
Owner

pkujhd commented Mar 31, 2021

你想说的是你的loader是一个动态库,是用plugin加载的?然后用loader加载一个.o?
loader的这个东西应该是比较稳定的,按理说不太需要动态模式加载这种需求

@Hello-xBugs
Copy link
Author

我想测试把loader用c-shard编译成so,然后注入到其他程序中加载.o,但是这样调用失败了

@pkujhd
Copy link
Owner

pkujhd commented Mar 31, 2021

你想编译成C的.so,然后让C程序加载.so,然后去读取一个go的.o 执行?把错误贴出来吧

@Hello-xBugs
Copy link
Author

Hello-xBugs commented Mar 31, 2021

是的,直接在.o文件函数的时候失败了,运行runFunc() 不能成功执行.o文件里的函数,loader也不往下执行了,runFunc()没有错误返回!

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

是的,直接在.o文件函数的时候失败了,运行runFunc() 不能成功执行.o文件里的函数,loader也不往下执行了,runFunc()没有错误返回!
给一个你错误的工程.

@Hello-xBugs
Copy link
Author

package main

import (
"io/ioutil"
)

func MyTest() {
ioutil.WriteFile("/tmp/1234", []byte(""), 0666)
}

我把上面的代码编译成o文件,让loader编译的so加载该o文件掉用函数main.MyTest()失败了。(我修改了exe, err := os.Executable()了这个获取exe的路径,让其获取我的so路径,这样注册符号那些就成功了,但是掉用main.MyTest失败)

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

你的loader是如何编译的,loader不是让C++去用dlopen调用的么?有完整的测试工程的话,发一个出来,需要复现错误

@Hello-xBugs
Copy link
Author

unexpected fault address 0x7fa25e925762
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0x7fa25e925762 pc=0x404f9000]

goroutine 17 [running, locked to thread]:
runtime.throw(0x7fa215b8d4c0, 0x5)
/usr/local/go/src/runtime/panic.go:605 +0x97 fp=0xc420049218 sp=0xc4200491f8 pc=0x7fa2158e8377
runtime.sigpanic()
/usr/local/go/src/runtime/signal_unix.go:374 +0x22b fp=0xc420049268 sp=0xc420049218 pc=0x7fa2158fe51b
main.MyTest2()
/root/elfloader/src/goloader/examples/testnil/testnil.go:7 fp=0xc420049270 sp=0xc420049268 pc=0x404f9000
main.mytest1()
/root/elfloader/src/goloader/examples/loader/loader.go:98 +0x123e fp=0xc420049e98 sp=0xc420049270 pc=0x7fa215b8a5fe
main._cgoexpwrap_815366bb35a3_mytest1()
command-line-arguments/_obj/_cgo_gotypes.go:45 +0x22 fp=0xc420049ea8 sp=0xc420049e98 pc=0x7fa215b893a2
runtime.call32(0x0, 0x7fffd19a7508, 0x7fffd19a75af, 0x0)
/usr/local/go/src/runtime/asm_amd64.s:509 +0x3d fp=0xc420049ed8 sp=0xc420049ea8 pc=0x7fa215913ccd
runtime.cgocallbackg1(0x0)
/usr/local/go/src/runtime/cgocall.go:305 +0x1a0 fp=0xc420049f58 sp=0xc420049ed8 pc=0x7fa2158bf030
runtime.cgocallbackg(0x0)
/usr/local/go/src/runtime/cgocall.go:187 +0x95 fp=0xc420049fc0 sp=0xc420049f58 pc=0x7fa2158bedf5
runtime.cgocallback_gofunc(0x0, 0x0, 0x0, 0x0)
/usr/local/go/src/runtime/asm_amd64.s:762 +0x9a fp=0xc420049fe0 sp=0xc420049fc0 pc=0x7fa2159152da
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc420049fe8 sp=0xc420049fe0 pc=0x7fa215916531

报了这个错误

@Hello-xBugs
Copy link
Author

main.mytest1() 是我loader的导出函数,main.MyTest2()是我o文件的函数

@Hello-xBugs
Copy link
Author

loader里加入
import "C"

//export mytest1
然后 go build buildmode=c-shared -o libloader.so loader.go

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

unexpected fault address 0x7fa25e925762
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0x7fa25e925762 pc=0x404f9000]

goroutine 17 [running, locked to thread]:
runtime.throw(0x7fa215b8d4c0, 0x5)
/usr/local/go/src/runtime/panic.go:605 +0x97 fp=0xc420049218 sp=0xc4200491f8 pc=0x7fa2158e8377
runtime.sigpanic()
/usr/local/go/src/runtime/signal_unix.go:374 +0x22b fp=0xc420049268 sp=0xc420049218 pc=0x7fa2158fe51b
main.MyTest2()
/root/elfloader/src/goloader/examples/testnil/testnil.go:7 fp=0xc420049270 sp=0xc420049268 pc=0x404f9000
main.mytest1()
/root/elfloader/src/goloader/examples/loader/loader.go:98 +0x123e fp=0xc420049e98 sp=0xc420049270 pc=0x7fa215b8a5fe
main._cgoexpwrap_815366bb35a3_mytest1()
command-line-arguments/_obj/_cgo_gotypes.go:45 +0x22 fp=0xc420049ea8 sp=0xc420049e98 pc=0x7fa215b893a2
runtime.call32(0x0, 0x7fffd19a7508, 0x7fffd19a75af, 0x0)
/usr/local/go/src/runtime/asm_amd64.s:509 +0x3d fp=0xc420049ed8 sp=0xc420049ea8 pc=0x7fa215913ccd
runtime.cgocallbackg1(0x0)
/usr/local/go/src/runtime/cgocall.go:305 +0x1a0 fp=0xc420049f58 sp=0xc420049ed8 pc=0x7fa2158bf030
runtime.cgocallbackg(0x0)
/usr/local/go/src/runtime/cgocall.go:187 +0x95 fp=0xc420049fc0 sp=0xc420049f58 pc=0x7fa2158bedf5
runtime.cgocallback_gofunc(0x0, 0x0, 0x0, 0x0)
/usr/local/go/src/runtime/asm_amd64.s:762 +0x9a fp=0xc420049fe0 sp=0xc420049fc0 pc=0x7fa2159152da
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc420049fe8 sp=0xc420049fe0 pc=0x7fa215916531

报了这个错误

这错误是某个symbol没有找到地址造成的,你应该没有检查Load的返回的错误,实际上Load没有成功

你给出的例子里边应该是没有register os.WriteFile函数造成的

另外你现在的使用上还会有其他的问题,goloader需要读取当前的可执行程序的符号表,来确定全局变量的地址,但是因为你的loader是一个.so, 所以使用os.Executable()得到的path是你执行的C程序的PATH,从而导致有些全局变量无法relocate地址

如果你有用到全局变量, 可以将RegSymbol的参数增加一个,传入loader.so的路径

@pkujhd pkujhd added the enhancement New feature or request label Apr 1, 2021
@Hello-xBugs
Copy link
Author

请问是否能重现错误,如果不能重现,我抽空整理下测试代码?

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

我能看到的错误,就是Loader返回了错误,os.WriteFile没有找到, 如果无视这个错误, 程序就会崩掉,但是和你提供的error不同

pkujhd@pkujhd a % ./a.out
Load error: unresolve external:os.WriteFile
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x48 pc=0x1014875e7]

goroutine 17 [running, locked to thread]:
main.loader()
	/Users/pkujhd/Code/go/src/github.com/pkujhd/goloader/examples/dlloader/dlloader.go:49 +0x5e7
pkujhd@pkujhd a % 

@Hello-xBugs
Copy link
Author

这个错误是因为path找的是elf文件的路径,我是把os.Executable() 这里强制修改了,强制修改成loader.so的绝对路径,这样load注册函数就不会返回错误了,exe="/tmp/loader.so",这样就没有这个错误了。您可以试一下

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

@tooBugs
你用的方案里是有问题,因为读不到全局变量地址。这个错误我在看代码的时候就已经知道了,比如os.stdout就找不到地址

但是你给我的testcase里,我测不出这种情况,因为编译出来的那个.o刚好不用啥全局变量

@Hello-xBugs
Copy link
Author

是我不能 把os.Executable()这里修改成loader.so的绝对路径吗?这样会造成全局变量地址reloc失败,我编译的o文件里也没有全局变量的,我只是把loader.so里的os.Executable修改成 我loader.so本身的绝对路径,这样就不会出现unresolve external这个问题。即把register.go 里面exe, err := os.Executable()替换成exe="/tmp/loader.so"。不可以这样吗?

@Hello-xBugs
Copy link
Author

是不是 必须是ELF的路径才行?

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

并不是你些的golang的代码中没有全局变量就是没有的,比如fmt.Println(xxx), 这个实际会使用os.Stdout。

当前的版本RegSymbol会调用os.Executable()得到当前程序的path,在你的环境里,会读到的是加载loader.so的ELF的C/C++编译的程序的path,这个里边是没有golang运行的符号表的,当你把他改成loader.so的path的时候,就正常了
这里使用绝对路径和相对路径都是可以的.

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

是不是 必须是ELF的路径才行?

不能是ELF的路径,应为这个是C/C++编译的,里边只有C/C++的符号表

@Hello-xBugs
Copy link
Author

是的 我已经把os.Executable()修改了成loader.so的path了,注册函数这些都是没问题了,也找到o文件的函数地址了,就是不能调用该函数,我也在内存里看了,该地址确实是o文件函数的地址

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

为啥会不能调用这个函数呢?你想表达的是不能在C里边调用golang的函数?

在loader里边调用是没有问题的,可以正常执行, 我已经测试过了

如果你想的是在C里边调用,那应该会出错,因为C和golang的运行环境不同,中间应该要有胶水代码才行

@Hello-xBugs
Copy link
Author

我的想法是能够让loader编译成.so,这样我C的代码就可以加载该loader.so,从而再让loader.so加载o文件并执行里面的函数。能支持这种情况吗?

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

我的想法是能够让loader编译成.so,这样我C的代码就可以加载该loader.so,从而再让loader.so加载o文件并执行里面的函数。能支持这种情况吗?

这样是可行的,我已经测试过了,可以正常执行,需要修改RegSymbol,让他去读loader.so的symbol就可以了.

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

pkujhd@pkujhd cpp % cat elf.cpp 
#include <stdio.h>  
#include <dlfcn.h>
#include <string.h>
#include "../dlloader.h"

GoString buildGoString(const char* p, size_t n){
    //typedef struct { const char *p; ptrdiff_t n; } _GoString_;
    //typedef _GoString_ GoString;
    return {p, static_cast<ptrdiff_t>(n)};
}

typedef void (*loaderFunc)(GoString, GoString, GoString);

int main(int argc, char **argv) {  
	void *handle;  
	loaderFunc func;
	char *error;
	const char * objfile = "./test.o";
	const char * run = "main.main";
	const char * loaderso = "../dlloader";
  
	handle = dlopen ("../dlloader", RTLD_LAZY);
	if (!handle) {  
		fprintf (stderr, "%s \n", dlerror());
	}  
  
	func = (loaderFunc)dlsym(handle, "loader");
	if ((error = dlerror()) != NULL)  {  
		fprintf (stderr, "%s \n", error);
		return 0;
	}

	GoString objfileGoStr = buildGoString(objfile, strlen(objfile));
	GoString runGoStr = buildGoString(run, strlen(run));
	GoString loadersoGoStr = buildGoString(loaderso, strlen(loaderso));
	(*func)(objfileGoStr, runGoStr, loadersoGoStr);
	dlclose(handle);  
	return 0;  
}  
pkujhd@pkujhd cpp % g++ -o elf -I. -ldl -w elf.cpp 
pkujhd@pkujhd cpp % cat test.go 
package main

import (
	"fmt"
	"io/ioutil"
)
func main() {
	fmt.Println("WWWW")
	ioutil.WriteFile("./1234", []byte(""), 0666)
}
pkujhd@pkujhd cpp % go tool compile test.go        
pkujhd@pkujhd cpp % ls -l
total 144
-rwxr-xr-x  1 pkujhd  staff  49768  4  1 19:00 elf
-rw-r--r--  1 pkujhd  staff   1059  4  1 18:59 elf.cpp
-rw-r--r--  1 pkujhd  staff    128  4  1 13:47 test.go
-rw-r--r--  1 pkujhd  staff   8492  4  1 19:00 test.o
pkujhd@pkujhd cpp % ./elf
WWWW
pkujhd@pkujhd cpp % ls -l
total 144
-rw-r--r--  1 pkujhd  staff      0  4  1 19:01 1234
-rwxr-xr-x  1 pkujhd  staff  49768  4  1 19:00 elf
-rw-r--r--  1 pkujhd  staff   1059  4  1 18:59 elf.cpp
-rw-r--r--  1 pkujhd  staff    128  4  1 13:47 test.go
-rw-r--r--  1 pkujhd  staff   8492  4  1 19:00 test.o
pkujhd@pkujhd cpp % 

@Hello-xBugs
Copy link
Author

RegSymbol只修改path成loader.so的路径是吗?没有其他修改了吧,我再golang 1.9的情况下还是不行,请问您那边是golang哪个版本

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

我是在macosx上的golang1.16.2上测试的,你的有问题的话,告知下我你的测试环境

@Hello-xBugs
Copy link
Author

我的golang版本 是1.9 在centos6 x64上运行

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

我的golang版本 是1.9 在centos6 x64上运行

Linux上在编译.so的时候将赋值TLS的语句改写了,所以函数头出问题了

@pkujhd
Copy link
Owner

pkujhd commented Apr 1, 2021

@tooBugs
替换下regTLS函数,应该可以了,但是只能在Linux上用,
另外都是临时更改,也不会发布到trunk

func regTLS(symPtr map[string]uintptr, offset int) {
	funcptr := getFunctionPtr(regTLS)
	tlsptr := *(*uint32)(adduintptr(funcptr, offset-2))
	tlsptr = *(*uint32)(adduintptr(funcptr, (int)(tlsptr) + 7))
	symPtr[TLSNAME] = uintptr(tlsptr)
}

@pkujhd pkujhd added question Further information is requested and removed enhancement New feature or request labels Apr 1, 2021
@pkujhd pkujhd changed the title loader 是so掉用 函数失败 loader 是so调用 函数失败 Apr 1, 2021
@Hello-xBugs
Copy link
Author

是的,非常感谢。已经可以了,我打算把源码了解的更深入些,请问有相关资料或说明吗?

@pkujhd
Copy link
Owner

pkujhd commented Apr 2, 2021

是的,非常感谢。已经可以了,我打算把源码了解的更深入些,请问有相关资料或说明吗?

基本没有,只能读golang源代码的linker,因为本质上就是一个dl.

@pkujhd pkujhd closed this as completed Apr 2, 2021
@pkujhd pkujhd added the enhancement New feature or request label Jun 23, 2021
@pkujhd
Copy link
Owner

pkujhd commented Jun 23, 2021

@tooBugs , 7701df1 already support loader is a dynamic library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants