好的,上週我們遇上了要印系統時間時會segfault炸掉的窘境,當時有預告了這跟COMPAT_SRC_DIRS
有關。
於是我們要來簡介一下今天的主題〝No time to die.〞,有請 Billie Eilish 獻唱 :
https://youtu.be/BboMpayJomw
玩笑開到這邊。這是個非常麻煩的問題,如果是在2000年前出生的各位,應該會有印象一個名詞:y2k bug(千禧蟲)。這是因為很多系統使用西元紀元末兩碼來表示年份,過了兩千年的那剎那,可能會被時光回溯回1900年。然而當時除了極少數code寫得真的很糟的系統外、沒有太嚴重的災情發生;但這問題的本質不僅會以這種表達方式出現,UNIX系統賴以維生的epoch時間戳,它是個32bit寬、從1970年的元旦凌晨0分0秒開始、每秒會跳動一個單位的計量值,這也替它訂下了一個死線:2038年1月19日3時14分07秒,一旦超過它、時間就會overflow回1970年,人稱y2038問題。當時的人心想,怎麼可能我這套作法到60多年後還在用? — — 嘿對,今年2021/09/13,我們還剩17年又3個月可以拯救世界。
為了解決y2038於是Linux作業系統與上面的libc決定了一個簡單暴力的修復方式:那我們用64bit的資料型態來裝不就好了?這對於64bit架構的程式碼來說不是什麼大問題,但是對於還是32bit的平台、就麻煩了。因為通常設計API傳遞參數時,我們都會盡量使用原生的資料型態,例如LP64、ILP32,這樣可以直接以寄存器存取,但是現在一旦改動、就必須更換相關的參數傳遞方式,這部份需要compiler的支援(long long在ILP32下會直接吃掉2個register)。此外,在32bit平台上包含有過去32bit寬time_t的複合性資料結構,也會因此有了structue layout的改動,這對用於kernel、user space交換資料的部份會產生不穩合的、出錯的狀況。
這一系列麻煩的封裝在kernel端,是使用許多compat macro包起來的,其中這邊最相關的是 __SC3264() 這一個將32/64平台一起桶裝出一個共用interface的macro。
但是對我們來說我們作userspace的porting、現在使用QEMU的linux-user來作驗證,我們僅需要知道以下資訊:
(1) musl這一段的compat switch如何進行
(2) qemu-riscv32 能提供什麼?
第一點我們可以透過觀察程式碼結構得知,目前musl已將所有32bit平台中,對time相關程式碼封裝在 compat/time32/
之下,32bit平台須新增一 arch/<platform>/arch.mak
的檔案,並且於其中宣告 COMPAT_SRC_DIRS=compat/time32/
以供最上層的 Makefile
將相關程式碼拉近 source set中。但這樣還不夠,如果我們此時嘗試編譯,便會發現編譯器回報有大量API有redifinition,快速再翻閱程式碼我們可以發現在 arch/<platform>/bits/alltypes.h.in
之中(※ spoiler alert again !),有一個 _REDIR_TIME64
macro,在被定義時、include/time.h
對應的API redirection會被啟動、相關的資料結構也會被啟用/置換。
其二,我們現在有了對應的API set,但是因為我們是從64平台移植過來,對應的system call number有變化、必須在 arch/<platform>/syscall_arch.h
當中補上對應的syscallno,然而我們需要的是哪些呢?通常來說,我們會需要去觀察kernel source tree透過 make install-headers 產生完成的 uapi headers來判斷。但是此時因為我們使用了qemu的linux-user來當平台,且musl是直接寫死/標定kernel組態的方式,我們可以直接觀察 qemu linux-user 在 riscv32 平台上面的 syscall no表: linux-user/riscv/syscall32_nr.h
,於其中我們會發現我們缺了clock_gettime64 403
這一組,事不宜遲地填回musl的 arch/riscv32/sycall_arch.h
之中。
緊接著我們就可以開始重新編譯看看了~
一切順暢地嚕完後,當我們要把時間印出來時,確還是又sefault掉了;此時我們可以透過 qemu-riscv32 -g <port> ./test_time
來啟動gdb stub來debug。單步執行我們發現取時間的邏輯是非常順利地結束了,可是卻在printf_core當中,處理 format string 時,fmt_u()
此一function中出現不正常的存取超界。
此時的我們,已經離打通musl-libc rv32 porting更進一步了~ 欲知後續如何,且待下回分解。