iT邦幫忙

2021 iThome 鐵人賽

DAY 3
2
Software Development

予焦啦!Hoddarla 專案起步:使用 Golang 撰寫 RISC-V 作業系統的初步探索系列 第 3

予焦啦!產出可執行檔

本節是以 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 之旅吧!各位讀者,我們明天再會!


上一篇
予焦啦!準備工具鏈
下一篇
予焦啦!檢驗核心映像檔:開機流程、OpenSBI 慣例、ELF 淺談
系列文
予焦啦!Hoddarla 專案起步:使用 Golang 撰寫 RISC-V 作業系統的初步探索33

尚未有邦友留言

立即登入留言