予焦啦!产出可执行档

本节是以 Golang 上游 1a708bcf1d17171056a42ec1597ca8848c854d2a 为基准做的实验。

本日的内容资料很多,但或许值得参考的资讯并不多。本来笔者考虑将这个部分放置到附录之中,但这麽一来又会有时间上的不连续感,最後还是作罢。也当作是个纪录吧!Hoddarla 专案所需的工具链并非理所当然,而是经过这些不太优雅的手段之後,才稍微成形的。如果能让读者诸君理解这一点,那今天这篇的意义也就达到了。

予焦啦!让我们接着处理工具链的问题。回顾昨日最後的编译状况,错误讯息如下:

$ GOOS=opensbi GOARCH=riscv64 go build ethanol/ethanol.go
# syscall
syscall/syscall.go:50:16: undefined: EINVAL
syscall/syscall.go:81:11: undefined: Timespec
syscall/syscall.go:86:11: undefined: Timeval
syscall/syscall.go:91:11: undefined: Timespec
syscall/syscall.go:96:11: undefined: Timeval
# runtime
runtime/alg.go:341:2: undefined: getRandomData
runtime/alg.go:351:2: undefined: getRandomData
runtime/proc.go:142:17: undefined: sigset
runtime/runtime2.go:519:16: undefined: gsignalStack
runtime/runtime2.go:520:16: undefined: sigset
runtime/runtime2.go:597:2: undefined: mOS
runtime/sigqueue.go:54:15: undefined: _NSIG
runtime/sigqueue.go:55:15: undefined: _NSIG
runtime/sigqueue.go:56:15: undefined: _NSIG
runtime/sigqueue.go:57:15: undefined: _NSIG
runtime/alg.go:351:2: too many errors

现在的目标是,将所有未定义的符号(无论是常数、变数、函式或是物件)一个一个加上去直到定义完全,就算只是空壳也都先让它能够通过编译,制造出一个产物为止。当然,能够编译出成品不代表那个档案真的可以执行在 OpenSBI 系统之上。我们在後续章节会再回顾这些部份。

通用方法

这些冒出来的未定义符号显然不可能出现在其它已经建立好的系统组合当中,也就是说,搜寻这些符号的时候,我们可以留意在其它系统组合之中的个别的定义。以下数小节就开始我们的搜寻之旅。

package syscall

syscall 组件(package)之中,仅有 3 个符号未定义,以下小节逐个分析。

EINVAL

在原始码中搜寻这个符号,会发现很多地方都只是使用到这个值,比方说像是位在 ./src/syscall/syscall_linux_riscv64.go 档案中的 Pipe 函式:

152 func Pipe(p []int) (err error) {
153         if len(p) != 2 {
154                 return EINVAL
155         }
...

Pipe 预期输入参数应该刚好包含两个整数,所以在除此之外的状况回传无效状况的错误

然而对於我们来说,更重要的是找到这个值如何定义。於是我们又看到很多 z 开头的生成程序码档案:

...
./src/syscall/zerrors_linux_ppc64le.go: EINVAL          = Errno(0x16)
./src/syscall/zerrors_freebsd_386.go:   EINVAL          = Errno(0x16)
./src/syscall/zerrors_openbsd_amd64.go: EINVAL          = Errno(0x16)
./src/syscall/zerrors_linux_ppc64.go:   EINVAL          = Errno(0x16)
./src/syscall/zerrors_linux_386.go:     EINVAL          = Errno(0x16)
./src/syscall/zerrors_linux_mips.go:    EINVAL          = Errno(0x16)
./src/syscall/zerrors_solaris_amd64.go: EINVAL          = Errno(0x16)
./src/syscall/zerrors_linux_arm64.go:   EINVAL          = Errno(0x16)
./src/syscall/zerrors_dragonfly_amd64.go:       EINVAL          = Errno(0x16)
./src/syscall/zerrors_linux_riscv64.go: EINVAL          = Errno(0x16)
./src/syscall/zerrors_darwin_amd64.go:  EINVAL          = Errno(0x16)
./src/syscall/zerrors_linux_mipsle.go:  EINVAL          = Errno(0x16)
...

为了打造 opensbi/riscv64 系统组合的 zerror 档,我们眼前有两个选择。一个是参考其它系统组合的正统作法,另外一个则是直接创个 zerrors_opensbi_riscv64.go 再将缺失的符号定义於其中。以结论而言,笔者後来采取的是後者。前者的作法适用於这些大部份的 Unix-like 作业系统,许多符号与定义都来自 C 语言世界的系统标头档案,如 /usr/include 或是 /include/sys 底下的内容,并且使用 Golang 的自动生成框架。笔者这里选择後者,以 src/syscall/tables_js.go 作为蓝本,仅修改需要的部份如下:

js/wasm 比较特殊,是 Javascript 语言和 Web Assembly 的组合。某种程度来说,opensbi/riscv64 系统组合可以考虑借用该组合的一些简洁性,将自己加入到已经颇为成熟的 Golang 之中。

7,8c7,8
< //go:build amd64 && openbsd
< // +build amd64,openbsd
---
> //go:build riscv64 && opensbi
> // +build riscv64,opensbi

为什麽看起来这两行的资讯类似,却需要同时存在呢?因为 Golang 社群打算在 1.17 版转换这个编译器指令的格式,目前是前後版本并存的状态。

修改之後的错误变成:

$ GOOS=opensbi GOARCH=riscv64 go build ethanol/ethanol.go
# runtime
src/runtime/alg.go:341:2: undefined: getRandomData
src/runtime/alg.go:351:2: undefined: getRandomData
src/runtime/proc.go:142:17: undefined: sigset
src/runtime/runtime2.go:519:16: undefined: gsignalStack
src/runtime/runtime2.go:520:16: undefined: sigset
src/runtime/runtime2.go:597:2: undefined: mOS
src/runtime/sigqueue.go:54:15: undefined: _NSIG
src/runtime/sigqueue.go:55:15: undefined: _NSIG
src/runtime/sigqueue.go:56:15: undefined: _NSIG
src/runtime/sigqueue.go:57:15: undefined: _NSIG
src/runtime/alg.go:351:2: too many errors
...

看来 syscall 组件已经完成所有符号的定义了!我们一样继续这个搜寻-替换-重编译的循环解决所有符号未定义问题。然後,我们将各个组件解决了的问题浓缩如下:

package runtime

第一阶段

  • 复制 src/runtime/os_js.gosrc/runtime/os_opensbi.go
  • opensbisrc/runtime/cputicks.go 的编译相依性中除去
  • opensbisrc/runtime/stubs2.go 的编译相依性中除去

由於 runtime 组件并非工具链的一环,我们不需要重编工具链。直接重新编译之後,错误变为:

$ GOOS=opensbi GOARCH=riscv64 go build ethanol/ethanol.go
# runtime
go/src/runtime/chan.go:200:2: undefined: lock
go/src/runtime/chan.go:203:3: undefined: unlock
go/src/runtime/chan.go:210:28: undefined: unlock
go/src/runtime/chan.go:226:3: undefined: unlock
go/src/runtime/chan.go:231:3: undefined: unlock
go/src/runtime/chan.go:360:2: undefined: lock
go/src/runtime/chan.go:362:3: undefined: unlock
go/src/runtime/chan.go:416:2: undefined: unlock
go/src/runtime/chan.go:508:2: undefined: lock
go/src/runtime/chan.go:514:3: undefined: unlock
go/src/runtime/chan.go:514:3: too many errors

第二阶段

  • 复制 src/runtime/lock_js.gosrc/runtime/lock_opensbi.go
$ GOOS=opensbi GOARCH=riscv64 go build ethanol/ethanol.go
# runtime
go/src/runtime/debuglog.go:75:18: undefined: sysAlloc
go/src/runtime/debuglog.go:717:12: undefined: sysAlloc
go/src/runtime/extern.go:236:7: undefined: gogetenv
go/src/runtime/heapdump.go:713:3: undefined: sysFree
go/src/runtime/heapdump.go:731:4: undefined: sysFree
go/src/runtime/heapdump.go:734:8: undefined: sysAlloc
go/src/runtime/malloc.go:570:19: undefined: sysReserve
go/src/runtime/malloc.go:656:8: undefined: sysReserve
go/src/runtime/malloc.go:674:4: undefined: sysFree
go/src/runtime/malloc.go:799:15: undefined: sysReserve
go/src/runtime/malloc.go:799:15: too many errors

第三阶段

  • 复制 src/runtime/mem_js.gosrc/runtime/mem_opensbi.go
# runtime
go/src/runtime/extern.go:236:7: undefined: gogetenv
go/src/runtime/mgcpacer.go:840:7: undefined: gogetenv
go/src/runtime/proc.go:222:7: undefined: _cgo_setenv
go/src/runtime/proc.go:225:7: undefined: _cgo_unsetenv
go/src/runtime/proc.go:714:21: undefined: gogetenv
go/src/runtime/proc.go:1245:5: undefined: netpollinited
go/src/runtime/proc.go:1246:11: undefined: netpoll
go/src/runtime/proc.go:1706:5: undefined: netpollinited
go/src/runtime/proc.go:1707:3: undefined: netpollBreak
go/src/runtime/proc.go:2753:5: undefined: netpollinited
go/src/runtime/proc.go:2753:5: too many errors

第四阶段

  • 修改 src/runtime/netpoll_stub.go 以支援 opensbi
  • 修改 src/runtime/env_posix.go 以支援 opensbi
$ GOOS=opensbi GOARCH=riscv64 go build ethanol/ethanol.go
# internal/poll
src/internal/poll/fd_mutex.go:201:11: undefined: FD
src/internal/poll/fd_mutex.go:211:11: undefined: FD
src/internal/poll/fd_mutex.go:220:11: undefined: FD
src/internal/poll/fd_mutex.go:230:11: undefined: FD
src/internal/poll/fd_mutex.go:238:11: undefined: FD
src/internal/poll/fd_mutex.go:248:11: undefined: FD
# syscall
src/syscall/syscall.go:81:11: undefined: Timespec
src/syscall/syscall.go:86:11: undefined: Timeval
src/syscall/syscall.go:91:11: undefined: Timespec
src/syscall/syscall.go:96:11: undefined: Timeval
src/syscall/tables_opensbi.go:107:18: undefined: Errno
src/syscall/tables_opensbi.go:108:18: undefined: Errno
src/syscall/tables_opensbi.go:109:18: undefined: Errno
src/syscall/tables_opensbi.go:110:18: undefined: Errno
src/syscall/tables_opensbi.go:111:18: undefined: Errno
src/syscall/tables_opensbi.go:112:18: undefined: Errno
src/syscall/tables_opensbi.go:112:18: too many errors

package syscall

syscall 组件的部份又出现了,先排除这个部份。

  • 复制 src/syscall/syscall_js.gosrc/runtime/syscall_opensbi.go
# internal/poll
src/internal/poll/fd_mutex.go:201:11: undefined: FD
src/internal/poll/fd_mutex.go:211:11: undefined: FD
src/internal/poll/fd_mutex.go:220:11: undefined: FD
src/internal/poll/fd_mutex.go:230:11: undefined: FD
src/internal/poll/fd_mutex.go:238:11: undefined: FD
src/internal/poll/fd_mutex.go:248:11: undefined: FD
# syscall
src/syscall/syscall_opensbi.go:29:9: undefined: readInt
src/syscall/syscall_opensbi.go:285:12: undefined: Getcwd
src/syscall/syscall_opensbi.go:293:9: undefined: jsProcess
src/syscall/syscall_opensbi.go:297:9: undefined: jsProcess
src/syscall/syscall_opensbi.go:301:9: undefined: jsProcess
src/syscall/syscall_opensbi.go:305:9: undefined: jsProcess
src/syscall/syscall_opensbi.go:309:8: undefined: recoverErr
src/syscall/syscall_opensbi.go:310:11: undefined: jsProcess
src/syscall/syscall_opensbi.go:319:9: undefined: jsProcess
src/syscall/syscall_opensbi.go:323:9: undefined: jsProcess
src/syscall/syscall_opensbi.go:323:9: too many errors

第二阶段

  • 修改 src/syscall/dirent.go 以支援 opensbi
  • 修改 src/syscall/syscall_opensbi.go 里面的 Getwd 函式、recoverErr 函式以及所有用到 jsProcess 函式的部份,反正这些都不需要
# internal/poll
src/internal/poll/fd_mutex.go:201:11: undefined: FD
src/internal/poll/fd_mutex.go:211:11: undefined: FD
src/internal/poll/fd_mutex.go:220:11: undefined: FD
src/internal/poll/fd_mutex.go:230:11: undefined: FD
src/internal/poll/fd_mutex.go:238:11: undefined: FD
src/internal/poll/fd_mutex.go:248:11: undefined: FD
# time
src/time/zoneinfo.go:92:16: undefined: initLocal
src/time/zoneinfo.go:653:13: undefined: syscall.Getenv
src/time/zoneinfo.go:667:34: undefined: zoneSources
src/time/zoneinfo_read.go:402:13: undefined: open
src/time/zoneinfo_read.go:406:8: undefined: closefd
src/time/zoneinfo_read.go:418:12: undefined: preadn
src/time/zoneinfo_read.go:426:12: undefined: preadn
src/time/zoneinfo_read.go:489:13: undefined: preadn
src/time/zoneinfo_read.go:499:13: undefined: preadn
src/time/zoneinfo_read.go:563:12: undefined: open
src/time/zoneinfo_read.go:563:12: too many errors

package internal/poll

  • 复制 src/internal/poll/fd_plan9.go 作为src/internal/poll/fd_opensbi.go,并将函数都清为空函数

之所以选择 plan9 作业系统的 FD 实作,只是因为该作业系统在 Golang 里面的份量比较小,之後如果需要调整的话会比较容易。

# time
src/time/zoneinfo.go:92:16: undefined: initLocal
src/time/zoneinfo.go:653:13: undefined: syscall.Getenv
src/time/zoneinfo.go:667:34: undefined: zoneSources
src/time/zoneinfo_read.go:402:13: undefined: open
src/time/zoneinfo_read.go:406:8: undefined: closefd
src/time/zoneinfo_read.go:418:12: undefined: preadn
src/time/zoneinfo_read.go:426:12: undefined: preadn
src/time/zoneinfo_read.go:489:13: undefined: preadn
src/time/zoneinfo_read.go:499:13: undefined: preadn
src/time/zoneinfo_read.go:563:12: undefined: open
src/time/zoneinfo_read.go:563:12: too many errors

还记得上一章结尾前的 okgoos 吗?若是没有加入,就会节外生枝的发现,新增了这个档案之後即使能够解消 FD 未定义的问题,但若是重新编译工具链,反而会出现问题:

/home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_poll_runtime.go:132:15: (*FD).SetDeadline redeclared in this block                     /home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_opensbi.go:32:6: previous declaration                                  /home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_poll_runtime.go:137:15: (*FD).SetReadDeadline redeclared in this block
        /home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_opensbi.go:36:6: previous declaration
/home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_poll_runtime.go:142:15: (*FD).SetWriteDeadline redeclared in this block
        /home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_opensbi.go:40:6: previous declaration
/home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_poll_runtime.go:146:6: setDeadlineImpl redeclared in this block
        /home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_opensbi.go:44:53: previous declaration
/home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_posix.go:57:15: (*FD).RawControl redeclared in this block
        /home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_opensbi.go:48:6: previous declaration
/home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_unix.go:18:6: FD redeclared in this block
        /home/noner/FOSS/hoddarla/ithome/go/src/internal/poll/fd_opensbi.go:11:6: previous declaration

会变成有一堆重复定义的问题!刚才才新增的 fd_opensbi.go 中的部份函数与许多其它的 fd_poll_runtime.gofd_unix.gofd_posix.go 重复定义了。这都是因为 opensbi 不被认识为正式的 Golang 作业系统,不具有排他性,因此无法将其它三者排除在外。

package time

第一阶段

  • 复制 src/time/zoneinfo_plan9.go 作为src/time/zoneinfo_opensbi.go
  • 复制 src/time/sys_plan9.go 作为src/time/sys_opensbi.go
# time
src/time/sys_opensbi.go:21:13: undefined: syscall.Open
src/time/sys_opensbi.go:29:9: undefined: syscall.Read
src/time/sys_opensbi.go:33:2: undefined: syscall.Close
src/time/sys_opensbi.go:41:15: undefined: syscall.Seek
src/time/sys_opensbi.go:45:13: undefined: syscall.Read
src/time/zoneinfo.go:653:13: undefined: syscall.Getenv
src/time/zoneinfo_opensbi.go:126:11: undefined: syscall.Getenv

第二阶段

  • 根据 src/syscall/syscall_plan9.go,修改 src/syscall/syscall_opensbi.go,新增 ReadOpenCloseSeek 等空函数
  • 复制 src/syscall/env_windows.go 作为 src/syscall/env_opensbi.go,并将其中的函数都改为空函数
# os
src/os/exec.go:128:28: undefined: ProcessState
src/os/exec.go:139:10: undefined: ProcessState
src/os/exec.go:144:10: undefined: ProcessState
src/os/exec.go:149:10: undefined: ProcessState
src/os/exec.go:155:10: undefined: ProcessState
src/os/exec.go:162:10: undefined: ProcessState
src/os/exec.go:171:10: undefined: ProcessState
src/os/file.go:66:11: undefined: NewFile
src/os/file.go:67:11: undefined: NewFile
src/os/types.go:17:2: undefined: file
src/os/file.go:67:11: too many errors

package os

第一阶段

  • 复制 src/os/file_plan9.go 作为 src/os/file_opensbi.go,并将其中函数清空
  • 复制 src/os/exec_plan9.go 作为 src/os/exec_opensbi.go,并将其中函数清空
  • 修改 src/os/rawconn.go,使之除了 plan9 之外也不会编译到 opensbi
# os
src/os/dir.go:41:23: f.readdir undefined (type *File has no field or method readdir, but does have Readdir)
src/os/dir.go:70:22: f.readdir undefined (type *File has no field or method readdir, but does have Readdir)
src/os/dir.go:98:25: f.readdir undefined (type *File has no field or method readdir, but does have Readdir)
src/os/executable.go:19:9: undefined: executable
src/os/file.go:268:10: undefined: syscall.Mkdir
src/os/file.go:300:10: undefined: syscall.Chdir
src/os/getwd.go:29:14: undefined: statNolog
src/os/getwd.go:35:13: undefined: statNolog
src/os/getwd.go:62:13: undefined: statNolog
src/os/getwd.go:70:15: undefined: statNolog
src/os/getwd.go:70:15: too many errors

第二阶段

  • 复制 src/os/executable_plan9.go 作为 src/os/executable_opensbi.go,并将函数清空。
  • 复制 src/os/stat_plan9.go 作为 src/os/stat_opensbi.go,并将函数清空。
  • 复制 src/os/dir_plan9.go 作为 src/os/dir_opensbi.go,并将函数清空。
  • 复制 src/syscall/dir_plan9.go 作为 src/syscall/dir_opensbi.go,并将函数清空,虽然是在处理 os 组件,但是会需要用到 syscall 组件,因此补上。
# os
src/os/file.go:268:10: undefined: syscall.Mkdir
src/os/file.go:300:10: undefined: syscall.Chdir
src/os/path.go:30:15: undefined: IsPathSeparator
src/os/path.go:35:16: undefined: IsPathSeparator
src/os/path.go:41:18: undefined: fixRootDirectory
src/os/path.go:75:51: undefined: IsPathSeparator
src/os/removeall_noat.go:70:37: undefined: PathSeparator
src/os/sys.go:9:9: undefined: hostname
src/os/tempfile.go:49:64: undefined: PathSeparator
src/os/tempfile.go:61:6: undefined: IsPathSeparator
src/os/tempfile.go:61:6: too many errors

第三阶段

  • 复制 src/syscall/fs_js.go 作为 src/syscall/fs_opensbi.go,并将函数清空。
  • 复制 src/os/path_plan9.go 作为 src/os/path_opensbi.go,并将函数清空。
  • 复制 src/os/sys_plan9.go 作为 src/os/sys_opensbi.go,并将函数清空。
# command-line-arguments
2021/05/30 11:42:15 unknown thread-local storage offset for 8

工具链本身

第一阶段

os 组件的第三阶段解决了符号定义问题之後,出现的新问题并非其他组件的未定义符号,而是工具链本身的警告讯息。稍加检索之後,可以发现该讯息位於 src/cmd/link/internal/ld/sym.go 之中的 computeTLSOffset 函数,它仅由一个 switch-case 结构构成。而我们先前所见的警告讯息,是进入 default 选项之後造成的。在这里新增 opensbi 进来即可。

由於改动在工具链中,需要重新编译。编译完之後的结果,仍然有问题

# command-line-arguments
/home/noner/FOSS/hoddarla/ithome/go/pkg/tool/linux_amd64/link: unknown -H option: 8

第二阶段

循线追查的结果,这个在 cmd/link/internal/riscv64/obj.go 之中的 archinit 函数。原本 riscv64 架构在这里只接受 linux 作业系统,因此我们正在加入的 opensbi 会在 defult 选项中受到警告。将之加入,则

# command-line-arguments
2021/05/30 15:53:26 duplicated definition of symbol runtime.cputicks, from runtime and runtime

第三阶段

看来又回到 runtime 组件的问题了。这次是 cputicks,我们除了在原本的 src/runtime/os_opensbi.go 里面存有定义之外,在 src/runtime/asm_riscv64.go 之中也有实作,因此我们可以将前者的实作改为仅有标头的宣告。

# command-line-arguments
runtime.notetsleepg: relocation target runtime.nanotime1 not defined
runtime.notetsleepg: relocation target runtime.scheduleTimeoutEvent not defined
runtime.notetsleepg: relocation target runtime.clearTimeoutEvent not defined
runtime.checkTimeouts: relocation target runtime.nanotime1 not defined
runtime.beforeIdle: relocation target runtime.clearTimeoutEvent not defined   
runtime.beforeIdle: relocation target runtime.scheduleTimeoutEvent not defined
...

package runtime

  • src/runtime/os_plan9.go 为灵感,编辑 src/runtime/os_opensbi.go,补足没有定义的部分。
  • 编辑 src/runtime/stubs.go,使之不会为了 opensbi 编成 nanotime1 函数。
  • 编辑 src/runtime/timestubs2.go
  • 编辑 src/runtime/mem_opensbi.go
# command-line-arguments
panic: unknown platform

goroutine 1 [running]:
cmd/link/internal/ld.asmb2(0xc00018e000)
        /home/noner/FOSS/hoddarla/ithome/go/src/cmd/link/internal/ld/asmb.go:92 +0x325
cmd/link/internal/ld.Main(_, {0x8, 0x20, 0x1, 0x2, 0x1, 0x0, {0x0, 0x0}, {0x66fb63, ...}, ...})
        /home/noner/FOSS/hoddarla/ithome/go/src/cmd/link/internal/ld/main.go:355 +0x13a5
main.main()
        /home/noner/FOSS/hoddarla/ithome/go/src/cmd/link/main.go:69 +0xe1b

工具链问题

在我们解决完这一轮的 runtime 组件问题之後,又有工具链问题浮现!这次是 unknown platform?一样我们直接搜寻资料夹,发现是在 src/cmd/link/internal/ld/asmb.go 里面的 asmb2 函数,要决定输出的二进位档格式。这里我们就加入 linux 的阵营,一样使用广为使用的 ELF 档吧!别忘了重新编译工具链:

# command-line-arguments 
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4fa6ad]

goroutine 1 [running]:
cmd/link/internal/loader.(*Loader).SymType(0x57e936, 0xc0001420c0)
        /home/noner/FOSS/hoddarla/ithome/go/src/cmd/link/internal/loader/loader.go:818 +0x4d
cmd/link/internal/ld.Entryvalue(0xc000120000)
        /home/noner/FOSS/hoddarla/ithome/go/src/cmd/link/internal/ld/lib.go:2444 +0x97
...

这里的关键字是 Entryvalue。今天的目标是产出可执行档,但 opensbi/riscv64 系统组合的可执行档的进入点在哪里呢?

关於 Golang 编成的 ELF 档的解析,可以参考拙作

考察 ELF 进入点:以 linux/riscv64 系统组合为例

先为这个系统组合编成可执行档,

$ GOOS=linux GOARCH=riscv64 go build ethanol/ethanol.go

再以 readelf 工具观察之

$ riscv64-buildroot-linux-musl-readelf -h hw                                            [4/1889]
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current) 
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           RISC-V
  Version:                           0x1
  Entry point address:               0x77898
  ...

可知,是在 0x77898 的某个函数。再以 objdump 工具考察,

0000000000077898 <_rt0_riscv64_linux>:
   77898:       00013503                ld      a0,0(sp)
   7789c:       00810593                addi    a1,sp,8
   778a0:       00000f97                auipc   t6,0x0
   778a4:       008f8067                jr      8(t6) # 778a8 <main>

检索 src/runtime 资料夹下可发现,这位在 rt0_linux_riscv64.s 档案中。我们将之复制为 rt0_opensbi_riscv64.s,并更换内部函数名称,再行编译的话:

$ GOOS=opensbi GOARCH=riscv64 go build ethanol/ethanol.go 

成功了!确实已经成功产出一个 ELF 档,当然其中很多函数都是拼凑出来的空壳,但至少我们已经有一个工具链环境足堪使用。

小结

予焦啦!本篇其实没有什麽紮实功夫,不过就是创造一些空壳好让编译能够顺利进行而已。笔者本来也在考虑要不要乾脆将之列为附录章节,但这麽一来,整个 Hoddarla 专案的顺序就不明显了。但如果说从中没有什麽东西可以学习的话,也不正确。至少笔者曾试着从我们解决的未定义符号当中看出意义,或者就以之为关键字,理解 Golang 怎麽样兼容不同的系统组合,相关的函数又通常用以解决甚麽问题等等。除此之外,也能够建立一些与 Golang 本身相关的开发技巧。

github 上,至本日为止的状态已经更新。

无论如何,这样逞着匹夫之勇的笔者修修补补之後,搞定了一个可以服务於 opensbi/riscv64 的 Golang 工具链。让我们开始以这个基本装备继续 Hoddarla 之旅吧!各位读者,我们明天再会!


<<:  【Day 3】Git x GitHub x 版本控制的基础:吴宝春的成功秘诀

>>:  Day2:在Anaconda上安装Tensorflow以及Keras

Day 9 : 案例分享(3.2) 会计模组-日记帐 Odoo的会计核心运用

案例说明及适用场景 如果说一般的传票(日记帐分录),是通用性的功能,那 日记帐 应算是Odoo特有的...

Day 19 - Maybe Monad

yo, what's up 在之前我们都是用 Identity 作为例子,但其功用并不大,所以今天要...

iOS APP 开发 OC 第六天, 面向过程&面向对象

tags: OC 30 day 什麽是面向对象? 实现需求之一: Ex:要如何将大象放入冰箱? 打开...

[ Day 30 ] - 初学者升级啦~完赛心得

初学者升级啦 YA!30天了~代表我连续 30 天学习 JS 了!(拍手~) 第一次参加铁人赛,其实...

Day01.从防疫特助到管道的故事谈Blue Prism

Blue Prism(蓝色棱镜),(後续也会穿插着简称BP)是属於RPA Robotic Proce...