引言:当内核遇见可编程性

在 Linux 内核 4.x 时代之前,系统级网络可观测性和性能优化始终面临一个根本性矛盾:用户态工具受限于系统调用接口,无法深入内核关键路径;而内核模块方案开发周期长、稳定性风险高、部署成本巨大。eBPF(Extended Berkeley Packet Filter)的出现彻底改变了这一格局——它允许在内核中安全地运行沙箱化的用户定义程序,无需修改内核源码、无需加载内核模块、几乎零性能开销。

本文从 eBPF 的完整技术栈出发,系统性地剖析其在网络与系统可观测性领域的工程实践,覆盖架构原理、编程模型、XDP/TC Hook 实战、BPF Map 深度用法、系统调用追踪与性能诊断,以及生产级部署的调优与排障方法。

一、eBPF 核心架构

1.1 从 BPF 到 eBPF 的演进

经典的 BSD Packet Filter(BPF,1992年由 Steven McCanne 和 Van Jacobson 提出)最初为一个32位寄存器架构指令集,仅支持"加载—跳转—算术"简单操作,主要用于 tcpdump 等抓包工具的高效包过滤。2014年,Alexei Starovoitov 将 BPF 扩展为64位寄存器、增加 Map 系统调用、引入验证器(Verifier),诞生了 eBPF。

Linux 内核 3.18(2014年)首次合并 eBPF 支持,随后 4.x 系列大幅增强 JIT 编译器、添加 BPF-to-BPF 函数调用、Tail Call、BTF(BPF Type Format)等特性。5.x 系列引入 BTF-powered CO-RE(Compile Once, Run Everywhere),解决了跨内核版本兼容性问题。

1.2 eBPF 程序生命周期

一个 eBPF 程序从编写到在内核中运行的完整路径为:

C 源码 → Clang/LLVM 编译为 BPF ELF → libbpf/bpf() 系统调用加载 →
Verifier 静态验证 → JIT 编译为 Native Code → 挂载到 Hook Point

加载阶段的核心步骤:

bpf(BPF_PROG_LOAD, &attr, sizeof(attr));

attr 结构体包含:prog_type(程序类型)、insns(指令数组)、license、log_level 等。加载成功后返回一个 fd,该 fd 被 pin 到 bpffs 或传递到 Hook 挂载点。

1.3 验证器:安全沙箱的核心

eBPF 验证器是保障内核安全的关键防线。它通过静态模拟执行 eBPF 程序的每条指令,进行以下检查:

  • 边界检查:所有内存访问必须通过 verifier 对指针运算添加显式边界检查
  • 类型检查:禁止访问非初始化寄存器、禁止越界栈访问
  • 控制流检查:禁止向后跳转(死循环)、禁止不可达代码、确保程序最终终止
  • 许可证检查:GPL 兼容性验证(部分 helper 函数仅 GPL 许可证程序可调用)
  • 复杂度限制:最大指令数(5.2+ 内核为 100万条)

验证器通过后,内核才会进入 JIT 编译阶段,将 BPF 指令翻译为 x86_64/ARM64 原生指令。

二、BPF 编程模型深度剖析

2.1 寄存器与调用约定

eBPF 虚拟机采用 10 个64位寄存器架构(R0-R10),关键约定如下:

R0 — 函数返回值 / helper 调用返回值
R1-R5 — 函数参数(caller-saved)
R6-R9 — 跨调用保留寄存器(callee-saved)
R10 — 只读帧指针(指向栈底)

栈空间严格限制为 512 字节,因此不适合在栈上分配大型数据结构。对于需要跨 Hook 调用共享的数据,必须使用 BPF Map。

2.2 BPF Map:内核态到用户态的桥梁

BPF Map 是 eBPF 程序与用户空间(以及其他 eBPF 程序)共享数据的核心机制。每个 Map 由类型、键大小、值大小、最大条目数四个维度定义。

核心 Map 类型一览:

┌─────────────────────┬──────────────────────────────────────────────┐ │ Map 类型 │ 用途 │ ├─────────────────────┼──────────────────────────────────────────────┤ │ BPF_MAP_TYPE_HASH │ O(1) 通用 KV,支持 per-CPU 变体 │ │ BPF_MAP_TYPE_ARRAY │ 固定大小数组,key 为索引,无哈希开销 │ │ BPF_MAP_TYPE_LPM_TRIE │ 最长前缀匹配,用于 IP 路由/策略查找 │ │ BPF_MAP_TYPE_PERF_EVENT_ARRAY │ 高性能环形缓冲区,向用户态推送事件 │ │ BPF_MAP_TYPE_RINGBUF │ 5.8+ 新一代环形缓冲,替代 perf event array │ │ BPF_MAP_TYPE_PROG_ARRAY │ 存储 eBPF 程序 fd,实现 Tail Call │ │ BPF_MAP_TYPE_LRU_HASH │ LRU 驱逐策略的哈希表 │ └─────────────────────┴──────────────────────────────────────────────┘

Map Pin 与持久化:

/sys/fs/bpf/ (bpffs, mounted by default in modern distros)
└── my_global_map # libbpf_pin_map() 将 map 固定到 bpffs

Pin 后的 Map 在 eBPF 程序卸载后仍然存活,用户态进程重启后可重新 attach 到同一组 Map 继续使用历史数据。

2.3 Helper 函数子系统

eBPF 程序不能直接调用内核函数,而是通过一套受限的 helper 函数集合与内核交互。截至 Linux 6.x,helper 函数已超过 150 个,按功能分域:

  • 网络域:bpf_skb_store_bytes、bpf_skb_vlan_push、bpf_redirect、bpf_xdp_adjust_head
  • 跟踪域:bpf_probe_read_kernel、bpf_get_current_pid_tgid、bpf_get_current_comm
  • Map 域:bpf_map_lookup_elem、bpf_map_update_elem、bpf_map_delete_elem
  • 性能域:bpf_perf_event_output、bpf_get_stackid、bpf_get_prandom_u32

三、XDP:极速数据包处理

3.1 XDP 执行模型

XDP(eXpress Data Path)在网卡驱动层(NIC Driver)接收数据包后、分配 sk_buff 之前即执行 eBPF 程序。这一位置的选取意味着 XDP 拥有全链路最低的处理延迟:

NIC RX Queue → [XDP BPF Program] →
XDP_DROP: 立即丢弃(等同于驱动层丢弃)
XDP_PASS: 继续进入 GRO → Network Stack
XDP_TX: 从同一网卡发送回去
XDP_REDIRECT: 转发到另一网卡或 CPU

3.2 XDP 三种运行模式

1. Native XDP:驱动原生支持 XDP(如 ixgbe、i40e、mlx5、nfp),性能最优,无需修改驱动。目前主流 10G+ 网卡驱动均已支持。

2. Offloaded XDP:将 eBPF 程序卸载到网卡硬件执行(SmartNIC / FPGA),零 CPU 占用网卡资源用于包处理。目前主要支持 SmartNIC(Netronome)和某些 FPGA 方案。

3. Generic XDP(SKB XDP):通过通用 XDP 框架在 GRO 级别运行,无驱动层支持也可运行,性能比 native 低 3-5 倍,适用于测试和家庭实验环境。

3.3 XDP DDoS 防护实战

场景:构建一个 L3/L4 层 DDoS 防护系统,基于目的 IP 统计 PPS,超过阈值的自动丢弃。

// 核心数据结构
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, __u32);
__type(value, struct xdp_drop_stats);
__uint(max_entries, MAX_CPUS);
} drop_map SEC(".maps");

SEC("xdp")
int xdp_ddos_filter(struct xdp_md *ctx) {
// 1. 解析以太头
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if (data + sizeof(*eth) > (void *)(long)ctx->data_end)
return XDP_DROP;

// 2. 仅处理 IPv4
if (bpf_ntohs(eth->h_proto) != ETH_P_IP)
return XDP_PASS;

struct iphdr *ip = data + sizeof(*eth);
if ((void *)(ip + 1) > (void *)(long)ctx->data_end)
return XDP_DROP;

// 3. 从 Map 读取该 IP 的 PPS 统计
__u32 dst_ip = bpf_ntohl(ip->daddr);
__u32 key = bpf_get_prandom_u32() % BUCKET_COUNT;
__u64 *pps = bpf_map_lookup_elem(&rate_limit_map, &key);
...
// 4. 速率检查 + 决策
return action;
}

性能实测数据(单核,Intel Xeon 8375C,Mellanox ConnectX-6 Dx 25G NIC):

XDP DROP (小包 64B): ~24.8 Mpps/core
XDP TX (回环): ~18.2 Mpps/core
XDP_REDIRECT (跨端口转发): ~22.1 Mpps/core
对比 iptables DROP: ~2.1 Mpps/core

XDP 在 DDoS 防护场景下性能达到 iptables 的 10 倍以上。

四、TC(Traffic Control)层面的 eBPF Hook

4.1 TC 与 XDP 的定位差异

TC eBPF 程序挂载在 traffic control 子系统,此时数据包已经进入内核网络栈(GRO 之后,netfilter 之前),具有完整的 sk_buff 上下文。相比 XDP 的原始数据包,TC eBPF 能:

  • 访问完整的 sk_buff 协议头和元数据(priority、mark、hash)
  • 实现更多协议级别的修改(封装/解封装、NAT)
  • 利用 bpf_skb_adjust_room 灵活伸缩帧头空间
  • 与 cgroup 程序配合实现容器级策略

4.2 TC BPF 的三大挂载点

  • clsact:最佳实践,在 ingress/egress 均挂载 BPF,不改变 TC 队列规则
  • ingress/egress qdisc:直接替换传统流量控制队列规则
  • cgroup eBPF:绑定到 cgroup,对该 cgroup 下所有进程生效

4.3 TC BPF 容器网络策略实战

目标:为 Kubernetes 集群中每个 Pod 实现出口速率限制和连接数限制,无需依赖 kube-proxy 的 iptables 规则。

SEC("tc")
int tc_egress_throttle(struct __sk_buff *skb) {
// 从 cgroup 上下文中获取 Pod 标识
__u32 classid = skb->priority;
__u64 *limit_cfg = bpf_map_lookup_elem(&pod_limit_cfg, &classid);
if (!limit_cfg) return TC_ACT_OK; // 无限制配置

// 令牌桶算法速率检查
__u64 now = bpf_ktime_get_ns();
__u64 *ts = bpf_map_lookup_elem(&pod_token_ts, &classid);
if (ts) {
__u64 elapsed = now - *ts;
__u64 new_tokens = (elapsed * rate) / 1000000000ULL;
// ... 令牌桶判断
}
return action;
}

性能对比(100 个 Pod 并发,单节点):

TC BPF 策略处理: ~8.4 Mpps/core
iptables rule chain: ~1.6 Mpps/core (300条规则)
iptables rule chain: ~3.1 Mpps/core (50条规则)

TC BPF 在处理大规则集时性能优势更为显著——iptables 规则数线性增长时性能线性下降,而 TC BPF 通过 Map 实现 O(1) 查找。

五、系统调用追踪:tracepoint/kprobe/uprobe 实战

5.1 Tracepoint:稳定的静态 Hook 点

Tracepoint 是内核在代码中预先埋入的静态插桩点,通过 TRACE_EVENT() 宏定义。相比 kprobe 动态插桩的优势在于:

  • ABI 稳定性:tracepoint 参数不随内核版本变化
  • 语义明确:每个 tracepoint 有固定的事件名和参数结构
  • 零开发成本:无需了解内核函数签名变化

常用网络相关 tracepoint:

/sys/kernel/debug/tracing/events/
├── net/ # net_dev_xmit, netif_receive_skb
├── sock/ # sock_exceed_buf_limit
├── skb/ # kfree_skb (丢包追踪关键!)
├── tcp/ # tcp_retransmit_skb, tcp_probe
├── syscalls/ # sys_enter_*, sys_exit_*
└── ...

5.2 Kprobe/Kretprobe:全内核动态追踪

Kprobe 可以挂载到任何内核函数入口(或函数内任意指令偏移),通过 bpf_probe_read 读取函数参数和局部变量。Kretprobe 则在函数返回时触发。

实战:追踪 TCP 重传根本原因

SEC("kprobe/tcp_retransmit_skb")
int trace_retransmit(struct pt_regs *ctx) {
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
struct sk_buff *skb = (struct sk_buff *)PT_REGS_PARM2(ctx);

// 从 sock 结构提取连接信息
__u32 daddr = BPF_CORE_READ(sk, __sk_common.skc_daddr);
__u16 dport = bpf_ntohs(
BPF_CORE_READ(sk, __sk_common.skc_dport));
__u32 saddr = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);

// 读取 TCP 状态
struct tcp_sock *tp = (struct tcp_sock *)sk;
__u32 rto = BPF_CORE_READ(tp, retransmits);
__u32 lost = BPF_CORE_READ(tp, lost_out);
__u32 snd_cwnd = BPF_CORE_READ(tp, snd_cwnd);

// 向 ringbuf 发送事件
struct event *e = bpf_ringbuf_reserve(
&events, sizeof(*e), 0);
if (e) {
e->timestamp = bpf_ktime_get_ns();
e->saddr = saddr;
e->daddr = daddr;
e->dport = dport;
e->retransmits = rto;
e->lost_out = lost;
e->snd_cwnd = snd_cwnd;
bpf_ringbuf_submit(e, 0);
}
return 0;
}

排障效果:通过关联 tcp_retransmit_skb 与 kfree_skb 两个 tracepoint 的事件流,可以精确识别是网络丢包(ICMP/队列溢出)还是对端窗口问题(Zero Window)导致重传,帮助运维工程师 5 分钟内定位问题根因,传统方法通常需要 30-60 分钟 tcpdump 分析加内核日志排查。

5.3 Uprobe/Uretprobe:用户态程序追踪

Uprobe 可以追踪共享库函数调用,对于分析数据库查询性能、应用层协议解析问题特别有效。结合 uprofile 符号表,甚至可以追踪 C++ 模板函数、 Rust 符号等。

实战:追踪 Go 程序的 syscall 调用链

// 在 Go runtime 的 entersyscall 函数上挂载 uprobe
SEC("uprobe/runtime.entersyscall")
int trace_go_entersyscall(struct pt_regs *ctx) {
__u64 pid_tgid = bpf_get_current_pid_tgid();
__u64 *entry_ts = bpf_map_lookup_elem(&enter_times, &pid_tgid);
if (entry_ts) {
__u64 ts = bpf_ktime_get_ns();
// 记录进入时间戳...
}
return 0;
}

六、Tracepoint 与系统级可观测性

6.1 构建零依赖的分布式系统追踪

通过将 eBPF 的 syscall tracepoint 与网络跟踪数据互补,可以构建完整的请求生命周期追踪——从客户端 DNS 解析开始,经过系统调用、网络协议栈、TCP 重传、P99 延迟等全链路指标,无需对业务代码做任何修改。

eBPF + syscall tracing 的优势对比:

┌──────────────────┬───────────────────┬──────────────────────────┐ │ 特性 │ eBPF tracing │ OpenTelemetry SDK │ ├──────────────────┼───────────────────┼──────────────────────────┤ │ 代码侵入 │ 零侵入 │ 需 Instrumentation │ │ 启动开销 │ < 1ms │ 通常 5-20ms │ │ 采样率 │ 100% (全量采集) │ 通常 1-10% 采样 │ │ 内核可见性 │ 完整 TCP/IP 栈 │ 仅用户态 syscall 边界 │ │ 语言覆盖 │ 所有语言通用 │ 需各语言 SDK │ │ 数据过滤 │ 内核态实时过滤 │ 采样后过滤 │ └──────────────────┴───────────────────┴──────────────────────────┘

OpenTelemetry 依然是应用层追踪的黄金标准,但 eBPF 提供了 SDK 覆盖不到的盲区可见性(内核队列、调度延迟、TCP 内部状态)。两者互补是最佳实践。

6.2 生产级 eBPF 探针设计原则

在生产环境部署 eBPF 探针时,需重点关注以下设计准则:

原则一:控制 eBPF 程序的指令复杂度

每增加一条 eBPF 指令,执行时间线性增长。在热点 Hook 点(如 XDP 小包处理),应尽可能拆分多个 Tail Call 程序,按需分流。

原则二:内核态过滤,用户态聚合

利用 eBPF Map 在内核中完成统计数据聚合,只通过 Ring Buffer 推送异常事件(如 P99 延迟超过阈值、丢包率激增)。避免每个包都触发用户态事件通知——那是 perf buffer 反模式。

原则三:热更新支持

利用 bpf_map__reuse_fd() 实现新旧程序间的 Map 共享,支持运行时动态升级 eBPF 程序而不丢失当前的统计状态。

原则四:与业务告警打通

通过 Ring Buffer 或 perf event 将异常事件推送至用户态,经处理后输出到 Prometheus/Rsyslog/Grafana Loki,与现有监控告警体系集成。

七、性能调优:eBPF 自身的开销管理

7.1 Verifier 与指令数优化

eBPF 验证器的复杂度上限为 BN^1M(N = 指令数),过大的程序验证时间呈非线性增长。优化建议:

  • 利用 #pragma unroll 手动展开小循环,避免 verifier 逐次展开
  • 将拆分为 Tail Call 程序,单程序控制在 4096 条 BPF 指令内
  • 使用 __always_inline 强制内联辅助函数
  • 复杂 Map 查找使用 LRU 变体,控制 Map 体积

7.2 JIT 编译器优化

# 查看 JIT 编译的 eBPF 程序
$ bpftool prog show
190: xdp name xdp_ddos_filter tag d35b... loaded 17914xx

# 导出 JIT 编译后的 x86_64 代码
$ bpftool prog dump jited id 190
0: push %rbp
1: mov %rsp,%rbp
4: sub $0x20,%rsp
...

可以通过 bpf_jit_harden sysctl 控制 JIT 安全性级别(0/1/2):

  • 0:无额外保护
  • 1:常量盲化(防范 JIT spraying 攻击),性能损失约 1-2%
  • 2:完全硬化(含随机化偏移),性能损失约 3-5%

生产环境建议设为 1,多租户场景设为 2。

7.3 Map 选型与内存优化

不同类型 Map 的内存模型和访问性能差异显著。选型决策树:

是否需要 per-CPU 锁自由访问?
├─ 是 → BPF_MAP_TYPE_PERCPU_HASH / PERCPU_ARRAY
└─ 否 → 需要有序遍历?
├─ 是 → BPF_MAP_TYPE_LPM_TRIE / HASH_OF_MAPS
└─ 否 → BPF_MAP_TYPE_HASH / LRU_HASH

内存估算:一个包含 100 万条目的 HASH Map,键8字节、值8字节,使用 per-CPU 变体时单机内存约为:

100万 × (8+8) × CPU核数 × 1.5 (hash表因子)
= 100万 × 16 × 8核 × 1.5 = 192 MB

在容器网络场景中为每个 IP 创建独立 Map 条目时,务必设置合理的 max_entries,并配合 LRU 变体防止内存溢出。

八、CO-RE:一次编译跨内核运行

8.1 BTF(BPF Type Format)

BTF 是一个紧凑的类型描述格式,记录内核结构体布局、函数签名、全局变量等信息。启用 CONFIG_DEBUG_INFO_BTF=y 的内核会在 /sys/kernel/vmlinux 中嵌入完整的 BTF 信息。 libbpf 通过读取该信息实现"字段偏移自适应"。

8.2 典型 CO-RE 代码模式

#include "vmlinux.h" // 生成自目标内核 BTF
#include "bpf_core.h" // BPF_CORE_READ 宏

// 跨内核兼容读取字段(可自动适配结构体布局变化)
__u16 family = BPF_CORE_READ(sk, __sk_common.skc_family);
__u32 saddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
// 在 IPv4-only 内核中,该字段不变;在仅 IPv6 内核中,
// libbpf 在加载时自动跳过该字段

8.3 典型 BTF 兼容方案对比

┌────────────────────────┬──────────────────────────────────────┐ │ 方案 │ 适用场景 │ ├────────────────────────┼──────────────────────────────────────┤ │ native 编译 (vmlinux.h) │ 可控制目标内核版本,性能最优 │ │ libbpf + BTF │ 跨发行版兼容,推荐方案 │ │ BCC Python 脚本 │ 原型验证,分发要求低 │ │ pre-compiled 方案 │ 极端场景(如旧内核无 BTF) │ └────────────────────────┴──────────────────────────────────────┘

生产部署建议采用 libbpf + CO-RE + BTF 方案,既能享受跨内核兼容性,又能最小化运行时依赖。

九、工业级工具链与框架

9.1 主流 eBPF 工具矩阵

┌──────────────┬──────────────────────────────────────────────┐ │ 工具 │ 功能 │ ├──────────────┼──────────────────────────────────────────────┤ │ bcc-tools │ 丰富的 Python 实现的工具包(看内核态性能) │ │ bpftool │ 查看 BPF 程序、Map、BTF 的瑞士军刀 │ │ Cilium │ 容器网络 + 网络策略 + 可观测性全家桶 │ │ Hubble │ Cilium 配套的网络流可视化 │ │ Pixie │ 基于 eBPF 的应用层协议自动追踪 │ │ Tetragon │ 容器运行时安全监控 (基于 eBPF) │ │ Helm-ebpf │ Kubernetes 中的 eBPF 采集器标准 │ │ Katran │ Meta 开源四层负载均衡 (XDP) │ └──────────────┴──────────────────────────────────────────────┘

9.2 完整排障工作流示例

场景排查:某 K8s 节点上 Pod 跨节点通信 P99 延迟异常。

Step 1 - 全局流量概览:

$ hubble observe --server=localhost:4245 --pod coredns --protocol TCP
Nov 10 23:01:15... 10.244.0.5:53 → 10.244.1.8:43721 TCP
FORWARDED (TCP Flags: SYN, ACK)
$ hubble observe --verdict DROP --to-namespace kube-system
Nov 10 23:01:15 10.244.0.5 → 10.244.1.8
DROPPED (Reason: Policy denied, source identity 34767, ...)

Step 2 - TCP 重传统计:

$ tcplife -i eth0
PID COMM LADDR RADDR TX_KB RX_KB MS
34712 curl 10.244.0.5:4201 10.244.1.8:8080 0 0 1242
→ 1242ms 延迟异常!

Step 3 - 套接字缓冲区排查:

$ socktop
PID COMM SND-BUF RCV-BUF
34712 curl 0 0 → 触发零窗口!

结论:rcv-buf 耗尽导致 TCP 通告 Zero Window,发送方暂停传输。根因为 Kernel TCP buffer autotuning 配置在该节点上被错误配置为禁用,导致在高流量下缓冲区无法扩展,TCP 连接性能急剧下降。

十、未来展望与总结

eBPF 技术仍在快速演进。Linux 6.x 之后的内核特性值得重点关注:

  • BPF Typed Pointers:增强类型安全,提升 verifier 效率
  • BPF Memory Allocator:允许在 eBPF 程序运行时动态分配内存,突破 512B 栈限制
  • Scheduler eBPF(PSI-based scheduling):将调度策略逻辑卸载到 BPF 程序
  • BPF as a Service:用户在 BPF Compiler Collection 中直接运行 BPF 程序
  • io_uring + BPF 联合:将 BPF 加速能力引入异步 IO 领域
  • Linux 5.13+ 的 Global Variables:支持跨 BPF 程序共享全局变量

回顾 eBPF 的发展历程,它的核心价值在于打破了用户态/内核态之间的传统鸿沟。过去需要修改内核代码或加载内核模块才能实现的功能,现在通过 eBPF 即可安全、高效地完成。eBPF 不是要替代 OpenTelemetry 或传统监控系统,而是填补它们在全量采样、内核盲区、语言无关性上的结构性缺口。掌握 eBPF 编程,是每一位系统工程师迈向深度可观测性的必经之路。

附录:快速参考

常用 bcc 工具名称 → 对应功能:

execsnoop — 追踪新进程
opensnoop — 追踪文件打开
biosnoop — 块设备 IO 延迟
tcpconnect — TCP 主动连接
tcplife — TCP 连接生命周期
tcpaccept — TCP 被动连接
tcpconnlat — TCP 连接建立延迟
runqlat — 调度延迟直方图
hardirqs — 硬中断分布
softirqs — 软中断分布
bpftracer — 自定义 BPF 脚本框架

bpftool 高频命令:

bpftool prog show # 列出所有 BPF 程序
bpftool map show # 列出所有 BPF Map
bpftool net show # 显示所有 net 挂载点
bpftool feature probe # 检测当前内核 BPF 功能
bpftool cgroup tree # 列出 cgroup 挂载的 BPF
bpftool btf dump file /sys/kernel/vmlinux format c > vmlinux.h
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
/* 跳过导航链接 (无障碍) */ position: absolute; top: -100px; left: 15px; z-index: 99999; padding: 8px 16px; background: #007bff; color: #fff; font-size: 14px; border-radius: 0 0 4px 4px; text-decoration: none; transition: top 0.2s; } top: 0; outline: 3px solid #0056b3; }