最近在升级RHEL9的时候,遇到一个CPU忙的问题,从perf的数据看是busy在vdso这个库里。但是这个库没有实体文件对应,从网上找了两个方法从内存里dump信息:第一个:https://zhuanlan.zhihu.com/p/632111280dd if=/proc/1/mem of=/tmp/linux-vdso.so skip=140721601826816 ibs=1 count=4096
实测,这个方法不行,最后出现objdump失败:
# objdump -T /tmp/linux-vdso.soobjdump: /tmp/linux-vdso.so: file format not recognized
第二个方法是:
https://github.com/vext01/dump_vdso/blob/master/dump_vdso.c
使用C程序实时dump,得出的数据是OK的。但是还是对应不上。根据perf的数据看busy的函数地址是0x589,但是这个dump出来的vdso里和0x589对应的函数是:.eh_frame_hdr,这里明显不是一个函数,而是一个frame header。这个方法不可信(这个验证不成功的原因是这个程序是64位的,而发现问题的是32位程序,请参见最后一个方法)
.eh_frame_hdr : { *(.eh_frame_hdr) } :text:eh_frame
第三个方法是是使用gdb实时调试一个程序:
info proc mapping0xf7fc0000 0xf7fc4000 0x4000 0x0 r--p [vvar]0xf7fc4000 0xf7fc6000 0x2000 0x0 r-xp [vdso]0xf7fc6000 0xf7fc7000 0x1000 0x0 r--p /usr/lib/ld-linux.so.20xf7fc7000 0xf7fec000 0x25000 0x1000 r-xp /usr/lib/ld-linux.so.20xf7fec000 0xf7ffb000 0xf000 0x26000 r--p /usr/lib/ld-linux.so.2
根据上面的地址,推算出来地址,得到一个函数是__kernel_vsyscall,也说明是在调用系统函数调用:
(gdb) disass 0xf7fc4589Dump of assembler code for function __kernel_vsyscall: 0xf7fc4580 <+0>: push %ecx 0xf7fc4581 <+1>: push %edx 0xf7fc4582 <+2>: push %ebp 0xf7fc4583 <+3>: mov %esp,%ebp 0xf7fc4585 <+5>: sysenter 0xf7fc4587 <+7>: int $0x80 0xf7fc4589 <+9>: pop %ebp 0xf7fc458a <+10>: pop %edx 0xf7fc458b <+11>: pop %ecx 0xf7fc458c <+12>: ret 0xf7fc458d <+13>: int3
最后从strace看,是有很多的函数调用:
fcntl64(54356579, F_GETFD) = -1 EBADF (Bad file descriptor)fcntl64(54356580, F_GETFD) = -1 EBADF (Bad file descriptor)fcntl64(54356581, F_GETFD) = -1 EBADF (Bad file descriptor)fcntl64(54356582, F_GETFD) = -1 EBADF (Bad file descriptor)fcntl64(54356583, F_GETFD) = -1 EBADF (Bad file descriptor)fcntl64(54356584, F_GETFD) = -1 EBADF (Bad file descriptor)fcntl64(54356585, F_GETFD) = -1 EBADF (Bad file descriptor)fcntl64(54356586, F_GETFD) = -1 EBADF (Bad file descriptor)
看了程序,这里有一个循环设置flag的操作,是在fork的时候,为了安全将子进程的所有fd都重新设置一遍。因为rlim_max值太大,导致CPU太忙。
if (getrlimit(RLIMIT_NOFILE, &rlp) != -1) { for (fd = STDERR_FILENO + 1; fd < rlp.rlim_max; fd++) { if ((flags = fcntl(fd, F_GETFD)) != -1) { fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } } }
然后看了一下这个rlp.rlim_max的值是:1073741816
# sysctl -a | grep fs.nr_open; uname -afs.nr_open = 1073741816Linux a 5.14.0-611.16.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Sun Dec 7 05:52:24 EST 2025 x86_64 x86_64 x86_64 GNU/Linux
这个数值就非常的大,导致程序有点忙了。
然后有返回到RHEL8上,查了一下这个值,没有这么大:
# sysctl -a | grep fs.nr_open; uname -afs.nr_open = 1048576Linux -a 4.18.0-553.85.1.el8_10.x86_64 #1 SMP Thu Nov 13 12:55:12 EST 2025 x86_64 x86_64 x86_64 GNU/Linux
关于eh_frame-and-eh_frame_hdr的资料:
https://linuxvox.com/blog/what-do-the-eh-frame-and-eh-frame-hdr-sections-store-exactly
第二个方法失效的原因是,我的程序是32位,而那个开源软件是64位!32和62有差别。所以,第四个方法:使用copilot重新修改了一下代码,放到了自己的branch下:https://github.com/mzhan017/dump-vdso/tree/master./dump-vdso # default: save to file vdso.so./dump-vdso -s # just print symbols and addresses