记一次页面对齐导致的异常
背景
在平凡无奇的一天,某个daily build 又挂了,看起来也很正常, 这次是tidb-monitring docker构建失败
排查过程
- 怀疑是build程序有问题,先拿一台物理机重现,手工下载了源文件,运行docker build 一切正常,重现失败
- 直接重跑流水线,该问题又再次出现,这次去对应的物理机上重试,发现问题能重现,是一台arm构建机
- 手工重试每个步骤: 直接docker run busybox, 程序退出 报137错误。在其他arm机器上重试,又能成功
- 137 也就是segment fault错误,怀疑是可执行程序异常,想直接运行二进制试试,先把文件从镜像里面拉出来,由于容器未启动,无法使用docker cp命令,因此采取docker image save的方式导出tar包。取出里面的busybox程序运行,报137错误,将该二进制传输到其他机器,正常。
- busybox是静态链接的,排除发行版环境导致的问题
[root@k8s-arm-node-4-103 del]# file busybox
busybox: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped
分析两台机器的不同点 先查看cpu
# dmidecode -t processor
...
Processor Information
Type: Central Processor
Family: ARM
Version: Kunpeng 920-4826
两台机器分别是 Kunpeng 920-4826 vs HUAWEI Kunpeng 920 5220,两者看起来差别并不大,可能是也可能不是这个原因
- Segment fault 一般是异常内存访问导致的,打开core dump分析。打开core dump->运行程序->程序崩溃,但是并没有产生core dump
- 手工写一个一定会segment fault的程序运行,产生了core dump
- 使用gdb调试
[root@k8s-arm-node-4-103 del]# gdb busybox
...
Reading symbols from /root/del/busybox...(no debugging symbols found)...done.
(gdb) r
Starting program: /root/del/busybox
During startup program terminated with signal SIGSEGV, Segmentation fault.
(gdb) bt
No stack.
(gdb) info register pc
The program has no registers now.
(gdb)
发现根本看不到程序执行的痕迹,调试我编译的可执行程序在segment fault的时候能够看到stack trace以及正常的PC
- 在程序入口打断点试试,依旧不行,连程序最初的入口都没执行到,程序根本没到执行阶段,在执行前就遇到问题了
[root@k8s-arm-node-4-103 del]# readelf -h busybox|grep Entry
Entry point address: 0x400740
[root@k8s-arm-node-4-103 del]# gdb busybox
...
Reading symbols from /root/del/busybox...(no debugging symbols found)...done.
(gdb) b *(0x400740)
Breakpoint 1 at 0x400740
(gdb) r
Starting program: /root/del/busybox
During startup program terminated with signal SIGSEGV, Segmentation fault.
(gdb) info register pc
The program has no registers now.
(gdb)
- 怀疑是安全系统拦截了,检查selinux,是未启用的,也可能是其他的安全机制例如apparmor
- 到这儿卡组了一下下。程序根本就没执行就出错,开始怀疑是加载过程,但是这是静态链接的,加载过程比较简单,不太会有链接问题,而且有加载过程的话加载器会报错
- 突然想到,如果直接用用户态的动态连接器去加载它,会发生什么,报错会不会更友好?通过随便一个动态链接的程序找到系统的动态加载器 /lib/ld-linux-aarch64.so.1
[root@k8s-arm-node-4-103 del]# readelf -l a.out
Elf file type is EXEC (Executable file)
Entry point 0x400510
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001b 0x000000000000001b R 1
[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000009a4 0x00000000000009a4 R E 10000
调用,这个报错也很有迷惑性,还以为只能加载动态链接的
[root@k8s-arm-node-4-103 del]# /lib/ld-linux-aarch64.so.1 busybox
busybox: error while loading shared libraries: busybox: cannot open shared object file
当然,需要使用绝对地址
[root@k8s-arm-node-4-103 del]# /lib/ld-linux-aarch64.so.1 /root/del/busybox
/root/del/busybox: error while loading shared libraries: /root/del/busybox: ELF load command alignment not page-aligned
真相大白:ELF load command alignment not page-aligned
还是用户态的加载器报错友好呀,内核加载器就扔个segment fault 差点我引入歧途 看看页对齐情况
[root@k8s-arm-node-4-103 del]# readelf -l busybox
Elf file type is EXEC (Executable file)
Entry point 0x400740
There are 5 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000012b2f8 0x000000000012b2f8 R E 1000
LOAD 0x000000000012bdf0 0x000000000052cdf0 0x000000000052cdf0
0x0000000000000611 0x0000000000005070 RW 1000
TLS 0x000000000012bdf0 0x000000000052cdf0 0x000000000052cdf0
0x0000000000000000 0x0000000000000008 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x000000000012bdf0 0x000000000052cdf0 0x000000000052cdf0
0x0000000000000210 0x0000000000000210 R 1
[root@k8s-arm-node-4-103 del]# getconf PAGESIZE
65536
[root@172 ~]# getconf PAGESIZE
4096
busybox可执行程序是按照 4k对齐的,但是出问题的机器页面是64k,其他机器是4k
触发原因
我们的代码没有任何改动,而是依赖的dockerhub基础镜像有变动 Dockerhub官方busybox进行了覆盖更新,覆盖后的会出问题, 已经向dockerhub/busybox项目提了issue
解决方案
- builder可以调小页面,大页面对build性能没多大帮助
- 发现有不少机器都是64k页面对齐的,更新builder能构建出镜像,但是镜像运行时的兼容性存疑
- 更换基础镜像
- 外部基础镜像也存在不规范更新的情况,建议以后全部采用内部基础镜像