使用BPF/eBPF追踪调用栈时用户态函数显示[unknown]的原因排查
实验环境
┌──[root@vms99.liruilongs.github.io]-[/usr/share/bcc/tools] └─$hostnamectl Static hostname: vms99.liruilongs.github.io Icon name: computer-vm Chassis: vm Machine ID: ea70bf6266cb413c84266d4153276342 Boot ID: 0d01838b0095494c82d1befb174a317d Virtualization: vmware Operating System: Rocky Linux 8.9 (Green Obsidian) CPE OS Name: cpe:/o:rocky:rocky:8:GA Kernel: Linux 4.18.0-513.9.1.el8_9.x86_64 Architecture: x86-64 ┌──[root@vms99.liruilongs.github.io]-[/usr/share/bcc/tools] └─$
问题描述
使用BPF/eBPF工具追踪调用栈时,所有用户态函数均显示为[unknown]。执行profile工具的输出如下:
┌──[root@vms99.liruilongs.github.io]-[/usr/share/bcc/tools] └─$profile Sampling at 49 Hertz of all threads by user + kernel stack... Hit Ctrl-C to end. ^C _raw_spin_unlock_irqrestore _raw_spin_unlock_irqrestore prepare_to_swait_event rcu_gp_kthread kthread ret_from_fork - rcu_sched (14) 1 kmem_cache_alloc_node kmem_cache_alloc_node __alloc_skb __ip_append_data.isra.50 ip_append_data.part.51 ip_send_unicast_reply tcp_v4_send_reset tcp_v4_rcv ip_protocol_deliver_rcu ip_local_deliver_finish ip_local_deliver ip_rcv __netif_receive_skb_core process_backlog __napi_poll net_rx_action __softirqentry_text_start do_softirq_own_stack do_softirq.part.16 __local_bh_enable_ip ip_finish_output2 ip_output __ip_queue_xmit __tcp_transmit_skb tcp_connect tcp_v4_connect __inet_stream_connect inet_stream_connect __sys_connect __x64_sys_connect do_syscall_64 entry_SYSCALL_64_after_hwframe [unknown] - haproxy (1203) 1 show_vma_header_prefix show_vma_header_prefix show_map_vma show_map seq_read vfs_read ksys_read do_syscall_64 entry_SYSCALL_64_after_hwframe [unknown] [unknown] - awk (39726) 1 ............. ┌──[root@vms99.liruilongs.github.io]-[/usr/share/bcc/tools] └─$
请问该现象是程序缺少调试信息导致,还是有其他原因?
另外,用Python编写的锁演示程序也出现同样问题:
┌──[root@vms99.liruilongs.github.io]-[~] └─$cat lock_demo.py import threading import time lock = threading.Lock() def worker(id): print(f"Worker {id} started") with lock: print(f"Worker {id} acquired lock") time.sleep(2) # 模拟长时间的计算或 I/O print(f"Worker {id} released lock") threads = [] for i in range(5): t = threading.Thread(target=worker, args=(i,)) threads.append(t) t.start() for t in threads: t.join() print("All workers finished")
执行threadsnoop工具的输出:
┌──[root@vms99.liruilongs.github.io]-[~] └─$threadsnoop TIME(ms) PID COMM FUNC 0 51671 b'python3' b'[unknown]' 0 51671 b'python3' b'[unknown]' 0 51671 b'python3' b'[unknown]' 0 51671 b'python3' b'[unknown]' 0 51671 b'python3' b'[unknown]'
执行offcputime工具的输出:
┌──[root@vms99.liruilongs.github.io]-[~/FlameGraph] └─$offcputime -p `pgrep -f lock_demo.py` Tracing off-CPU time (us) of PID 51397 by user + kernel stack... Hit Ctrl-C to end. ^C ....................... finish_task_switch __sched_text_start schedule futex_wait_queue_me futex_wait do_futex __x64_sys_futex do_syscall_64 entry_SYSCALL_64_after_hwframe [unknown] - python3 (51402) 157 finish_task_switch __sched_text_start schedule futex_wait_queue_me futex_wait do_futex __x64_sys_futex do_syscall_64 entry_SYSCALL_64_after_hwframe [unknown] - python3 (51400) 213 finish_task_switch __sched_text_start schedule futex_wait_queue_me futex_wait do_futex __x64_sys_futex do_syscall_64 entry_SYSCALL_64_after_hwframe [unknown] - python3 (51397) 267 finish_task_switch __sched_text_start schedule do_nanosleep hrtimer_nanosleep common_nsleep_timens __x64_sys_clock_nanosleep do_syscall_64 entry_SYSCALL_64_after_hwframe [unknown] [unknown] - python3 (51400) 2002609 finish_task_switch __sched_text_start schedule do_nanosleep hrtimer_nanosleep common_nsleep_timens __x64_sys_clock_nanosleep do_syscall_64 entry_SYSCALL_64_after_hwframe [unknown] [unknown] - python3 (51402) 2003178 .................. ┌──[root@vms99.liruilongs.github.io]-[~/FlameGraph] └─$
原因分析
这种用户态函数显示为[unknown]的情况,通常由以下几种原因导致:
程序缺少符号表或调试信息
大部分发行版的默认软件包(比如haproxy、python)都是经过stripped处理的,去掉了符号表和调试信息,导致eBPF工具无法将内存地址解析为对应的函数名。对于Python这类解释型语言,本身的字节码运行在解释器上,用户态的函数实际是Python代码,而eBPF追踪的是底层C实现的解释器栈,默认情况下也无法映射到Python函数名。内核配置限制
检查内核是否开启了CONFIG_KALLSYMS_ALL和CONFIG_FRAME_POINTER,这两个配置对用户态栈追踪至关重要。如果CONFIG_FRAME_POINTER未开启,eBPF工具可能无法正确遍历用户态调用栈。
可以通过以下命令查看配置:grep -E "CONFIG_KALLSYMS_ALL|CONFIG_FRAME_POINTER" /boot/config-$(uname -r)若
CONFIG_FRAME_POINTER设为n,则需要重新编译内核开启该选项。eBPF工具的栈追踪限制
部分eBPF工具在处理用户态栈时,依赖于进程的/proc/[pid]/maps和符号解析能力。如果进程的内存区域没有正确的符号映射(比如动态加载的模块、JIT代码),也会导致显示[unknown]。对于Python,要追踪到Python层面的函数,需要专门的工具或者扩展,而不是通用的profile或offcputime。权限或安全机制限制
即使使用root用户执行,内核的安全机制(比如SELinux)可能会限制eBPF工具访问进程的内存或符号信息。可以临时关闭SELinux测试:setenforce 0
解决建议
- 对于编译型程序(如haproxy),安装带调试信息的包(通常包名后缀为
-debuginfo),例如:dnf install haproxy-debuginfo - 对于Python程序,使用专门针对Python的eBPF工具,比如
pyspy,来追踪Python层面的函数调用栈。 - 检查并确保内核开启了
CONFIG_FRAME_POINTER和CONFIG_KALLSYMS_ALL,如果未开启则重新编译内核。 - 临时关闭SELinux验证是否是安全策略导致的限制。
内容的提问来源于stack exchange,提问作者liruilong

