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

fatal error: unreachable method called. linker bug? #55

Closed
devanwang opened this issue Jun 30, 2022 · 19 comments
Closed

fatal error: unreachable method called. linker bug? #55

devanwang opened this issue Jun 30, 2022 · 19 comments
Labels
wontfix This will not be worked on

Comments

@devanwang
Copy link

devanwang commented Jun 30, 2022

hi, 大佬,下面是复现步骤:
在example下新建两个目录p和t,在p下新建p.go,在t下新建t.go
p.go:

package p

import "fmt"

type Intf interface {
	Print(string)
}

type Stru struct {
}

func (Stru *Stru) Print(s string) {
	fmt.Println(s)
}

t.go:

package t

import (
	"fmt"

	"github.com/pkujhd/goloader/examples/p"
)

func Test(param p.Intf) p.Intf {
	param.Print("Intf")
	fmt.Println("done!")
	return param
}

修改loader.go函数声明和调用:

runFunc := *(*func(p.Intf) p.Intf)(unsafe.Pointer(&funcPtrContainer))
r := runFunc(&p.Stru{})
fmt.Println("r:", r)

在t目录中执行go build -x -n -v t.go 2>&1 | sed -n "/^# import config/,/EOF$/p" |grep -v EOF > importcfg && go tool compile -importcfg importcfg t.go,生成t.o文件
在loader目录中执行go build loader.go && ./loader -o ../t/t.o -run main.Test,报错fatal error: unreachable method called. linker bug?

@pkujhd
Copy link
Owner

pkujhd commented Jun 30, 2022

@devanwang 因为你编译的t.go不是一个main包,所以go build -x -n -v t.go 2>&1 | sed -n "/^# import config/,/EOF$/p" |grep -v EOF > importcfg 这个得到的信息是错的.

@pkujhd
Copy link
Owner

pkujhd commented Jun 30, 2022

正确的信息是类似这样的

# import config
packagefile fmt=/Users/pkujhd/programs/go/pkg/darwin_amd64/fmt.a
packagefile github.com/pkujhd/goloader/examples/p=/Users/pkujhd/Library/Caches/go-build/90/903b1feecaa67440b2c6c3b2edee39fffec4ad91db2874c894b6213cc3975991-d
packagefile runtime=/Users/pkujhd/programs/go/pkg/darwin_amd64/runtime.a

@devanwang
Copy link
Author

@pkujhd 大佬,我把t.go的package改了main,cat importcfg结果如下:

# import config
packagefile fmt=/usr/local/go/pkg/linux_amd64/fmt.a
packagefile github.com/pkujhd/goloader/examples/p=/root/.cache/go-build/7c/7ca25758e4aed09361504e8dfac5576615f44fefc3d1960788eab0500ed48243-d
packagefile runtime=/usr/local/go/pkg/linux_amd64/runtime.a

然后重新编译t.o,执行loader还是同样的报错

@pkujhd
Copy link
Owner

pkujhd commented Jun 30, 2022

给下你的运行环境和golang的版本

@devanwang
Copy link
Author

devanwang commented Jun 30, 2022

Linux version 5.4.119-1-tlinux4-0005 (root@VM_197_173_centos) (gcc version 8.3.1 20191121 (Red Hat 8.3.1-5)
go version go1.18.3 linux/amd64

@devanwang
Copy link
Author

如果在t.go里调用reflect

package main

import (
	"fmt"
	"reflect"

	"github.com/pkujhd/goloader/examples/p"
)

func Test(param p.Intf) p.Intf {
	fmt.Println(reflect.TypeOf(param))
	return param
}

会报错 Load error: unresolve external:reflect.(*rtype).FieldByIndex

@pkujhd
Copy link
Owner

pkujhd commented Jul 1, 2022

如果在t.go里调用reflect

package main

import (
	"fmt"
	"reflect"

	"github.com/pkujhd/goloader/examples/p"
)

func Test(param p.Intf) p.Intf {
	fmt.Println(reflect.TypeOf(param))
	return param
}

会报错 Load error: unresolve external:reflect.(*rtype).FieldByIndex

你的case可以在loader中注册掉这个interface暂时避免

var x p.Intf = &p.Stru{}
goloader.RegTypes(symPtr, x, x.Print)

@devanwang
Copy link
Author

var x p.Intf = &p.Stru{}
goloader.RegTypes(symPtr, x, x.Print)

加了这个不会报linker bug了,unresolve external:reflect.xxx还是会有

@pkujhd
Copy link
Owner

pkujhd commented Jul 1, 2022

reflect

var x p.Intf = &p.Stru{}
goloader.RegTypes(symPtr, x, x.Print)

加了这个不会报linker bug了,unresolve external:reflect.xxx还是会有

这个reflect的函数loader里没有使用,你需要注册

@devanwang
Copy link
Author

devanwang commented Jul 1, 2022

在t.go里只加了一行reflect.TypeOf(param),每次都报不同的错,没法加啊
Load error: unresolve external:reflect.(*rtype).Implements
Load error: unresolve external:reflect.(*rtype).FieldByNameFunc
Load error: unresolve external:reflect.(*rtype).ConvertibleTo
Load error: unresolve external:reflect.(*rtype).FieldAlign
Load error: unresolve external:reflect.(*rtype).MethodByName
Load error: unresolve external:reflect.(*rtype).FieldByIndex

我看原理会解析loader bin的symbols, 我在loader里同样加了reflect.TypeOf(param)代码,还是不行

@pkujhd
Copy link
Owner

pkujhd commented Jul 1, 2022

Refelect 包会用到一系列的函数,都要注册,或者就在你的加载器里有类似的代码,会移动处理加载器里的符号

@devanwang
Copy link
Author

不是很懂,大佬能不能给个具体例子,针对t.go里的reflect.TypeOf函数,我要在loader写什么?

@pkujhd
Copy link
Owner

pkujhd commented Jul 2, 2022

fmt.Println(reflect.TypeOf(param))

就是你在loader里也调用过相应的函数,就会自动注册
就是在loader里加一行
fmt.Println(reflect.TypeOf(param))

@devanwang
Copy link
Author

这个不行,在loader里加了同样的reflect.TypeOf(param)还是会报如下错误:
Load error: unresolve external:reflect.(*rtype).Implements
Load error: unresolve external:reflect.(*rtype).FieldByNameFunc
Load error: unresolve external:reflect.(*rtype).ConvertibleTo
Load error: unresolve external:reflect.(*rtype).FieldAlign
Load error: unresolve external:reflect.(*rtype).MethodByName
Load error: unresolve external:reflect.(*rtype).FieldByIndex

大佬你那边可以试下

@devanwang
Copy link
Author

@pkujhd 查nm找到原因了,loader里的reflect.TypeOf被内联优化了,t.o的没有被内联,加上-gcflags -l就可以了,但这样编译器就禁止内联了,不知道有啥好办法没有

@pkujhd
Copy link
Owner

pkujhd commented Jul 6, 2022

@pkujhd 查nm找到原因了,loader里的reflect.TypeOf被内联优化了,t.o的没有被内联,加上-gcflags -l就可以了,但这样编译器就禁止内联了,不知道有啥好办法没有

这个没啥办法,内联了要么你loader的时候重新提供一份,要么loader就是禁止inline的

@pkujhd pkujhd added the bug Something isn't working label Jul 25, 2022
@pkujhd
Copy link
Owner

pkujhd commented Jul 25, 2022

@devanwang ,这个问题查了下,是因为loader中产生的type,由于连接优化,剔除了Intf.Print, 因此上在加载一个扩展的时候,两边的类型虽然名字相同但是实际类型却不一样,无法正确运行,如果你需要在runtime和dynamic library之间传递interface,那么需要使得两边的类型一致(即需要手动注册相应的函数,保证runtime里边产生的type是一样的).
故此问题只能和inline一样. 编写时规避

@pkujhd pkujhd added wontfix This will not be worked on and removed bug Something isn't working labels Jul 25, 2022
@pkujhd pkujhd closed this as completed Jul 28, 2022
@pkujhd
Copy link
Owner

pkujhd commented Jul 28, 2022

记录下这个case的调试信息:

relocateType symbolName relocateSymbolName address
5 main.Test.stkobj runtime.gcbits.02 1094664412
1 main..stmp_0 go.string."done!" 1107140612
24 main.Test type.github.com/pkujhd/goloader/examples/p.Intf 7043456
23 main.Test type.string 6948512
23 main.Test type.*os.File 7288448
14 main.Test go.string."Intf" 1107140608
10 main.Test  1094664360
14 main.Test type.string 6948512
14 main.Test main..stmp_0 1094664448
14 main.Test os.Stdout 9954648
14 main.Test go.itab.*os.File,io.Writer 7911456
7 main.Test fmt.Fprintln 4919424
7 main.Test runtime.morestack_noctxt 4594432
1 go.itab.*os.File,io.Writer type.io.Writer 7044224
1 go.itab.*os.File,io.Writer type.*os.File 7288448
32769 go.itab.*os.File,io.Writer os.(*File).Write 4893696

以上是这个case所有需要relocate的列表, main.Test需要重定向type.github.com/pkujhd/goloader/examples/p.Intf,这个type已经在load中存在,但是对应的stuc.Print不存在,就会导致运行错误

记录信息:

可以采用-dynlink作为编译参数,这个参数是golang为plugin的模式添加的编译参数,会产生一个go.plugin.tabs的symbol来储存需要导出的函数,plugin模式用它来初始化moduledata的ptabEntry, 然后plugin模块读取这个array来产生导出的符号列表,
这是一个interface{]的array,所以当把interface转成func()时,会导入相应的符号,就不会产生上述才问题.

但是这个问题在goversion>=1.12的schedule这个case的时候会导致找不到符号runtime.gosched_m·f, 这个符号在runtime.a里,但是生成的loader里边没有,生成的.o里也没有,暂时无法处理;而不包含-dynlink的编译成的.o,runtime.gosched_m·f,这个符号就在当前的.o里.

now blocking...

eh-steve pushed a commit to eh-steve/goloader that referenced this issue Nov 7, 2022
eh-steve pushed a commit to eh-steve/goloader that referenced this issue Nov 7, 2022
@eh-steve
Copy link

eh-steve commented Nov 7, 2022

I think this is an artefact of the main loader's go compiler/linker setting some of the method offsets in the uncommon type of the *p.Stru to -1 as part of its reachability analysis (since no code in loader.go explicitly calls (*p.Stru).Print()). These "unreachable" method offsets are then baked into the firstmodule's itabs (go.itab.*github.com/pkujhd/goloader/examples/p.Stru,github.com/pkujhd/goloader/examples/p.Intf) during the first itab init and the method text pointers are set to point at the address of runtime.unreachableMethod, and when the method is later called from a dynamic module, it is indeed unreachable (from the first module's perspective).

PR #66 adds a functionality to patch the first module's itabs to point at newly included method offsets if they're now available in a dynamic module, which prevents these fatal error: unreachable method called. linker bug? errors.

I've made a separate branch here with this commit eh-steve@78fc98a demonstrating the solution.

If you check out that branch and run either:

cd ./examples/jit
go build .
./jit

or (using old loader)

cd ./examples/issue55/t/
go build -o t.o .
cd ../p/
go build -o p.o .
cd ../../loader
go build .
 ./loader -o ../issue55/t/t.o -o ../issue55/p/p.o -run "github.com/pkujhd/goloader/examples/issue55/t.Test"

It should be working (without the main binary having registered any extra types!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

3 participants