LoongArch-Documentation
LoongArch-Documentation copied to clipboard
关于可靠的新旧世界 ELF 文件标记
众所周知,目前准备推上游的 LoongArch 内核-用户界面 ABI 与早已出货的几种商业发行版并不兼容:
- 已有商业系统内核版本是 4.19,而从上游角度,不会有早于 5.1x 的内核存在 LoongArch 支持;
- 已有商业系统的 glibc 全部低于 2.34,
libpthread.so不是 stub,而从上游角度,LoongArch glibc 自始没有分立的libpthread.so; - 已有商业系统的
NSIG、struct sigcontext等等内容与上游接受的内容不同,
由于早期 LoongArch 生态的建设没有征询社区意见,而导致了这些现状:LoongArch 生态自始就分裂为两套互不兼容的体系,来自一个世界的 userland 无法在另一个世界的内核上正常工作。
尽管我们无法回到过去解决这些问题,但好在新旧世界的不兼容性目前仍然可控,因此可以尽早设计出适配方案,以实现新世界对旧世界闭源软件的兼容。预期未来随着 LoongArch 支持逐渐合入上游,各大商业发行版迟早都会 rebase & rebuild 到新世界,因此可以暂时不考虑旧世界上执行新世界应用的场景。
目前对于动态链接的可执行文件,可以通过 ELF interpreter 路径来区分新旧世界的程序。但对于静态链接的情况,需要有别的方式来可靠确定当前进程的 flavor,以便在需要的时刻正确截获、翻译系统调用。
这里先把问题抛出来,看看社区里大家都怎么想。我自己的方案可能一两天内整理好发出来。
cc @yetist @xry111 @scylaac @ChenghuaXu
P.S. 上面提到的 libpthread.so 的问题,gcc 如果不改,是过不了编译的:https://github.com/xen0n/gcc/commit/17d5d4feb788b83488b1fa70c1dcaf1a4d21d601 参考这里。
还涉及内核、发行版,添加 cc @sunhaiyong1978 @chenhuacai
...,以实现新世界对旧世界闭源软件的兼容。
这种兼容预期是到啥程度?新世界内核可以 chroot 运行旧世界用户态?
这种兼容预期是到啥程度?新世界内核可以 chroot 运行旧世界用户态?
应该可以直接运行,在新世界的系统上, 直接运行基于旧世界打造的闭源程序,比如wps等
这种兼容预期是到啥程度?新世界内核可以 chroot 运行旧世界用户态?
应该可以直接运行,在新世界的系统上, 直接运行基于旧世界打造的闭源程序,比如wps等
这个很有难度:
- 进上游的glibc,对内核的最低要求会写成5.16+(以首个支持LA的上游内核版本为准)。
- 进上游的glibc,对于库符号版本,会写成2.34+(以首个支持LA的上游glibc版本为准),而旧世界是2.27。这会导致旧世界库符号版本问题,就是找不到符号。
- ld.so的路径变化,这个算是最容易解决的。
- 上面提到的 NSIG、struct sigcontext 等问题。
这种兼容预期是到啥程度?新世界内核可以 chroot 运行旧世界用户态?
应该可以直接运行,在新世界的系统上, 直接运行基于旧世界打造的闭源程序,比如wps等
这个做不到,有结构体定义变了,访问字段的 offset 等等东西会不兼容,预期最彻底只能做到 chroot 级别的兼容,同一个 sysroot 不大可能同时兼容两种的(除非魔改 libc,我不认为这种能过上游)
有NSIG,chroot也不行。
有NSIG,chroot也不行。
意思是 chroot 里放一个基于 ptrace 拦截、翻译系统调用的静态 shim,然后只要内核能 100% 准确检测到旧世界程序,用这个 shim 执行起来,就可以兼容了。没有 shim 当然不可能直接兼容。
P.S. 上面提到的
libpthread.so的问题,gcc 如果不改,是过不了编译的:xen0n/gcc@17d5d4f 参考这里。
这个并不必要吧,LFS 从 glibc-2.33 升级到 2.34 的时候没有发现这个问题,我自己在 LoongArch 上编译系统也没有遇到这个问题。
glibc-2.34 提供了空的 libpthread.a 和 libpthread.so.1,理论上应该足够向下兼容。
- 已有商业系统的
NSIG、struct sigcontext等等内容与上游接受的内容不同
这个是最烦的。我的想法是引入一组和旧 ABI 兼容的系统调用 (上游很可能不会接受,但是可以写成单独的内核模块),然后用 LoongArch 的二进制翻译扩展在运行旧代码时把系统调用号直接改掉。但是并不知道可不可行,毕竟二进制翻译扩展的细节还没公开。
或者能否用 personality 机制 (man:personality(2))?
这个并不必要吧,LFS 从 glibc-2.33 升级到 2.34 的时候没有发现这个问题,我自己在 LoongArch 上编译系统也没有遇到这个问题。
在我的 Gentoo 移植工作中,这个调整是必须的,否则链接有问题。
glibc-2.34 提供了空的 libpthread.a 和 libpthread.so.1,理论上应该足够向下兼容。
不够:正因为是空的,新世界 ld.so 装载旧世界程序的时候就会想在里面找符号,就找不到,一定会死。
这个是最烦的。我的想法是引入一组和旧 ABI 兼容的系统调用 (上游很可能不会接受,但是可以写成单独的内核模块),然后用 LoongArch 的二进制翻译扩展在运行旧代码时把系统调用号直接改掉。但是并不知道可不可行,毕竟二进制翻译扩展的细节还没公开。
这个事情前面提到的 ptrace 就能做了。当然如果被执行的程序本身对特权操作的需求比较奇葩,可能会出现别的问题,但这跟新旧世界兼容无关,是拿 ptrace 干活都会碰到的通病。
或者能否用 personality 机制 (
man:personality(2))?
这个貌似也是合适的做法,但仍然绕不过如何可靠检测 personality 的问题。
这个是最烦的。我的想法是引入一组和旧 ABI 兼容的系统调用 (上游很可能不会接受,但是可以写成单独的内核模块),然后用 LoongArch 的二进制翻译扩展在运行旧代码时把系统调用号直接改掉。但是并不知道可不可行,毕竟二进制翻译扩展的细节还没公开。
这个事情前面提到的
ptrace就能做了。当然如果被执行的程序本身对特权操作的需求比较奇葩,可能会出现别的问题,但这跟新旧世界兼容无关,是拿ptrace干活都会碰到的通病。
ptrace 的问题是如果系统调用比较多 (比如频繁 read/write 文件) 会很慢。
这个并不必要吧,LFS 从 glibc-2.33 升级到 2.34 的时候没有发现这个问题,我自己在 LoongArch 上编译系统也没有遇到这个问题。
在我的 Gentoo 移植工作中,这个调整是必须的,否则链接有问题。
glibc-2.34 提供了空的 libpthread.a 和 libpthread.so.1,理论上应该足够向下兼容。
不够:正因为是空的,新世界
ld.so装载旧世界程序的时候就会想在里面找符号,就找不到,一定会死。
空的 libpthread.so.1 链接到了 libc.so.6,它应该包含过去 libpthread.so 包含的所有符号。
符号名称也和它在哪个库没关系,不应该找不到啊…… 如果找不到的话应该是动态链接的实现或者配置有问题。
其实呢,这个问题最简单的解法是“没有旧世界”。大家可能认为旧世界已经定型,一成不变了,实际上并不是。比如最近调整什么pt_regs结构啊,重命名UAPI里面的一些变量啊啥的,一直在导致旧世界不断变化。只不过这些变化都是温水煮青蛙式的,不像NSIG那样混搭根本起不来,把大家吓得不要不要的。我主张旧世界以改动pt_regs和重命名变量为契机,顺带着把旧世界直接消灭好了(NSIG一并改掉),希望大家支持。
其实呢,这个问题最简单的解法是“没有旧世界”。大家可能认为旧世界已经定型,一成不变了,实际上并不是。比如最近调整什么pt_regs结构啊,重命名UAPI里面的一些变量啊啥的,一直在导致旧世界不断变化。只不过这些变化都是温水煮青蛙式的,不像NSIG那样混搭根本起不来,把大家吓得不要不要的。我主张旧世界以改动pt_regs和重命名变量为契机,顺带着把旧世界直接消灭好了(NSIG一并改掉),希望大家支持。
这是最棒的,非常支持。
其实呢,这个问题最简单的解法是“没有旧世界”。大家可能认为旧世界已经定型,一成不变了,实际上并不是。比如最近调整什么pt_regs结构啊,重命名UAPI里面的一些变量啊啥的,一直在导致旧世界不断变化。只不过这些变化都是温水煮青蛙式的,不像NSIG那样混搭根本起不来,把大家吓得不要不要的。我主张旧世界以改动pt_regs和重命名变量为契机,顺带着把旧世界直接消灭好了(NSIG一并改掉),希望大家支持。
这是最棒的,非常支持。
支持。“学得更好,用得更好,彻底批判旧世界,创造新世界。”
合入上游之前,如果还可以调整,新的 ELF 可以通过定义新的 EI_ABIVERSION 来做出区分。
如果已经没有机会再改动 ABI 规范,可以试试通过 .note.ABI-tag 里面记载的内核最低版本要求来区分新老 ELF(可用 file 命令检查看看)。前提是商业系统不会再往上升级内核版本。
glibc中EI_ABIVERSION有特殊含义(search LIBC_ABI_MAX)。arch不可随意变更。 https://maskray.me/blog/2021-10-31-relative-relocations-and-relr#ei_abiversion
glibc中
EI_ABIVERSION有特殊含义(searchLIBC_ABI_MAX)。arch不可随意变更。 https://maskray.me/blog/2021-10-31-relative-relocations-and-relr#ei_abiversion
好吧,那如果定义 EI_OSABI,明确区分两种 ABI 呢?
e_flags 31bit咋样?因为这种差异不是因主动设计abi引起的,而是其他原因引起的不兼容,不管是放在EI_OSABI还是目前LoongArch预留的e_flags[7:3]都不合适,直接单独放1bit表示一下,目前的旧世界都是0,新世界置1区分一下。
e_flags 31bit咋样?因为这种差异不是因主动设计abi引起的,而是其他原因引起的不兼容,不管是放在EI_OSABI还是目前LoongArch预留的e_flags[7:3]都不合适,直接单独放1bit表示一下,目前的旧世界都是0,新世界置1区分一下。
我觉得能在一个符合规范、容易处理的字段里区分出来就好,然后新旧世界之间不准 interlink 应该也是要做的。
具体实现姿势我目前没太强的倾向性,先看看大家的想法呗?
如果要动 eflags ,那可以新世界的 ABI 版本 (eflags[7:6]) 从 1 开始,之前的都叫 0。不同 ABI 版本交叉链接直接报错。
如果要动 eflags ,那可以新世界的 ABI 版本 (
eflags[7:6]) 从 1 开始,之前的都叫 0。不同 ABI 版本交叉链接直接报错。
我们现在可以确认的是,所有旧世界程序都是 LP64D ABI,但旧世界表示 LP64D 的方法是让 e_flags 取 3,这就很尴尬了;如果是 0 那很好办,0 只能跟 0 interlink,非 0 只能跟非 0 interlink。但现在不是 0,那要么让出 3 这个值(3 只能跟 3 interlink),要么走别的路;任何地方的 flags 字段的低位都很珍贵,所以让出 3 也不太好。
如果要动 eflags ,那可以新世界的 ABI 版本 (
eflags[7:6]) 从 1 开始,之前的都叫 0。不同 ABI 版本交叉链接直接报错。我们现在可以确认的是,所有旧世界程序都是 LP64D ABI,但旧世界表示 LP64D 的方法是让
e_flags取 3,这就很尴尬了;如果是 0 那很好办,0 只能跟 0 interlink,非 0 只能跟非 0 interlink。但现在不是 0,那要么让出 3 这个值(3 只能跟 3 interlink),要么走别的路;任何地方的 flags 字段的低位都很珍贵,所以让出 3 也不太好。
我的意思是新世界从 1 << 6 | 3 开始,就是动 eflags 的 7:6 位。
如果要动 eflags ,那可以新世界的 ABI 版本 (
eflags[7:6]) 从 1 开始,之前的都叫 0。不同 ABI 版本交叉链接直接报错。我们现在可以确认的是,所有旧世界程序都是 LP64D ABI,但旧世界表示 LP64D 的方法是让
e_flags取 3,这就很尴尬了;如果是 0 那很好办,0 只能跟 0 interlink,非 0 只能跟非 0 interlink。但现在不是 0,那要么让出 3 这个值(3 只能跟 3 interlink),要么走别的路;任何地方的 flags 字段的低位都很珍贵,所以让出 3 也不太好。我的意思是新世界从
1 << 6 | 3开始,就是动 eflags 的 7:6 位。
哦哦,值域别撞就行,这一块我没特别的偏好,你们先讨论着就好
请问一下新世界ABI大概什么时候有结论?我看给Go的elf没有相关描述。
https://go-review.googlesource.com/c/go/+/342324/25/src/debug/elf/elf.go
请问一下新世界ABI大概什么时候有结论?我看给Go的elf没有相关描述。
https://go-review.googlesource.com/c/go/+/342324/25/src/debug/elf/elf.go
如果你问的是新的非栈机模型 ELF 重定向记录的工作,按照我的理解,没有社区提案,龙芯公司不会推进新的 ELF 重定向记录类型工作,因为他们目前的做法也是内部开会认为够用的。
这里讲的新旧世界,主要是最根本的 kernel ABI 兼容性问题:完全不能互操作的两个分裂生态。这个问题与 ELF 重定向记录类型不便实现的问题是正交的,新/旧世界搭配旧/新 ELF 重定向记录的两两组合,都是可以工作的,只是可能无法 interlink 而已。
这样是否能接受:划定4位出来作为操作系统 (内核,动态链接) 特性相关的标记,其含义取决于 e_ident[EI_OSABI] 的取值,表示 e_ident[EI_OSABI] 的细分含义。
至于是否允许这4位取不同值的目标文件相互链接,则不做统一规定。
| [31:28] 位 | [27:8] 位 | [7:6] 位 | [5:3] 位 | [2:0] 位 |
|---|---|---|---|---|
| 操作系统特性 | (保留) | ABI 版本 | ABI 扩展特性 | 基础 ABI 类型 |
对于 e_ident[EI_OSABI] == ELFOSABI_NONE ("Unix - System V") 的情况,规定上游新系统的 e_flags[31] 为1。
这样可能的好处是:子程序 ABI 和 OS ABI 之间是相对独立的,并且 OS ABI 需要分配的 e_flags 域宽也不需要随着引入新操作系统而增加。