NeuTracer 教程
主页
  • 背景介绍
  • 项目架构
  • 数据收集
  • 服务端
  • 异常检测
项目测试
主页
  • 背景介绍
  • 项目架构
  • 数据收集
  • 服务端
  • 异常检测
项目测试
  • 详细介绍

    • 背景介绍
    • 项目架构
    • 数据收集
    • 服务端
    • 异常检测

其他模块

主要包括 系统调用模块,总线模块,以及nvlink模块。这些模块通过 eBPF 技术实现对系统调用、总线通信和 NVLink 连接的实时监控和分析,可以对前面几个模块收集到的信息进行进一步的补充。这些模块的用户态程序实现逻辑基本一致,attach_bpf() 初始化 eBPF 程序并设置事件收集,将 eBPF 程序附加到系统调用点,start_trace() 启动监控线程和 ring buffer 处理,stop_trace() 停止监控并清理资源。process_event() 处理从 eBPF 接收的事件,ring_buffer_thread() 后台线程循环处理事件。因此不在赘述,下面给出各个模块ebpf部分的说明。

系统调用追踪

syscall_snoop.bpf.c 用于实时追踪和分析 Linux 内核中的系统调用活动。该程序能够捕获每个系统调用的进入与退出事件,详细记录调用进程的标识、系统调用号、参数、返回值、时间戳。该程序挂载到两个核心的原始系统调用跟踪点:raw_syscalls/sys_enter 用于捕获系统调用入口,raw_syscalls/sys_exit 用于捕获系统调用返回。程序采用 syscall_event 结构体向用户空间传递事件信息,内容包括线程ID、进程ID、系统调用号、唯一调用ID、纳秒级时间戳、参数、返回值、事件类型和进程名称。

enum event_type {
    EVENT_SYSCALL_ENTER = 0,
    EVENT_SYSCALL_EXIT = 1,
};

struct syscall_event {
    enum event_type type;
    u32 pid;
    u32 tgid;
    u32 syscall_id;
    u64 call_id;
    char comm[TASK_COMM_LEN];
    u64 timestamp;
    u64 args[6];    // 参数,enter时有效
    u64 ret_val;    // 返回值,exit时有效
};

事件通过环形缓冲区(events)高效传递到用户空间。程序内部维护调用ID映射(call_id_map)以唯一标识每次系统调用,支持多进程并发追踪。进程过滤映射(snoop_proc)和系统调用过滤映射(traced_syscalls)可灵活配置追踪范围。

struct call_id_key_t {
    u32 pid;
    u32 syscall_id;
};

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 65536);
    __type(key, struct call_id_key_t);
    __type(value, u64); // call_id
} call_id_map SEC(".maps");

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 512);
    __type(key, u32);
    __type(value, u32);
} traced_syscalls SEC(".maps");

系统调用进入追踪流程:首先检查系统调用号和进程是否在追踪范围内,分配并递增唯一调用ID,采集参数、时间戳和进程信息,最后通过环形缓冲区提交进入事件。

系统调用退出追踪流程:同样进行过滤,查找对应的调用ID,采集返回值、时间戳和进程信息,提交退出事件,确保每次系统调用的完整闭环追踪。该机制为系统调用行为分析、异常检测和性能监控提供了坚实基础。

总线追踪

pcie_snoop.bpf.c 用于追踪和分析 Linux 内核中的 PCIe 配置空间访问与 DMA Fence 同步事件。该程序能够捕获每一次 PCIe 配置读写操作的详细参数、进程上下文和时间戳,同时对 DMA Fence 生命周期事件进行全流程监控。

本模块挂载于 PCIe 配置空间访问的关键内核探针(kprobe/kretprobe)和 DMA Fence 相关 tracepoint。PCIe 部分支持对 pci_bus_read_config_{byte,word,dword} 及 pci_bus_write_config_{byte,word,dword} 的入口和返回进行追踪,完整记录总线号、设备号、偏移、访问类型、数据值等参数。DMA Fence 部分则追踪 dma_fence_destroy、dma_fence_enable_signal、dma_fence_signaled、dma_fence_emit、dma_fence_wait_start、dma_fence_wait_end 等事件,记录同步对象的 context、seqno、进程信息及等待时长。

struct pcie_event {
    enum event_type type;
    enum pcie_op_type op_type;
    u32 pid;
    u32 tgid;
    char comm[TASK_COMM_LEN];
    u64 timestamp;
    
    // PCIe 访问参数
    u32 bus;
    u32 devfn;
    u32 offset;
    u32 size;
    u32 value;  // 写入值或读取结果
};

struct dma_fence_event {
    enum dma_fence_event_type type;
    u32 pid;
    u32 tgid;
    char comm[TASK_COMM_LEN];
    u64 timestamp;
    
    // DMA Fence 信息
    u64 context;
    u32 seqno;
    char driver_name[32];
    char timeline_name[32];
    u64 duration_ns;  // 仅用于 wait_end 事件
};

事件通过环形缓冲区(events)高效传递到用户空间。进程过滤映射(snoop_proc)可灵活配置追踪目标,DMA Fence 等待时长通过 fence_wait_start 哈希映射实现精准统计。

struct fence_wait_key {
    u32 pid;
    u32 tgid;
    u32 context;
    u32 seqno;
};

// 修改映射定义
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 4096);
    __type(key, struct fence_wait_key);  // 使用组合键
    __type(value, u64); // 时间戳
} fence_wait_start SEC(".maps");

PCIe 访问追踪流程:在每次配置空间读写入口,采集访问参数、进程上下文和时间戳,提交进入事件;在返回点采集操作结果,提交退出事件,确保 PCIe 配置访问的全流程可观测。

// 通用辅助函数: 用于处理 PCI 读取操作的入口点
static inline void handle_pci_bus_read_enter(struct pcie_event *event, 
                                            struct pci_bus *bus,
                                            unsigned int devfn, 
                                            int where, 
                                            int size,
                                            enum pcie_op_type op_type) {
    u8 bus_num = 0;
    if (bus) {
        bus_num = BPF_CORE_READ(bus, number);
    }
    
    event->op_type = op_type;
    event->bus = bus_num;
    event->devfn = devfn;
    event->offset = where;
    event->size = size;
    event->value = 0;  // 读取时入口值未知
}

// 通用辅助函数: 用于处理 PCI 写入操作的入口点
static inline void handle_pci_bus_write_enter(struct pcie_event *event, 
                                             struct pci_bus *bus,
                                             unsigned int devfn, 
                                             int where, 
                                             int size,
                                             u32 val,
                                             enum pcie_op_type op_type) {
    u8 bus_num = 0;
    if (bus) {
        bus_num = BPF_CORE_READ(bus, number);
    }
    
    event->op_type = op_type;
    event->bus = bus_num;
    event->devfn = devfn;
    event->offset = where;
    event->size = size;
    event->value = val;  // 写入的值
}

DMA Fence 追踪流程:每次 Fence 相关事件发生时,采集同步对象标识、进程信息和时间戳,wait_start/wait_end 事件映射实现等待时长统计。

SEC("tracepoint/dma_fence/dma_fence_signaled")
int trace_dma_fence_signaled(struct trace_event_raw_dma_fence *ctx) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = (u32)pid_tgid;
    u32 tgid = (u32)(pid_tgid >> 32);

    if (bpf_map_lookup_elem(&snoop_proc, &pid) == NULL &&
        bpf_map_lookup_elem(&snoop_proc, &tgid) == NULL)
        return 0;

    struct dma_fence_event *event = create_dma_fence_event(DMA_FENCE_SIGNALED, pid, tgid);
    if (!event)
        return 0;

    event->context = ctx->context;
    event->seqno = ctx->seqno;
    bpf_ringbuf_submit(event, 0);
    return 0;
}

NVLink 追踪

nvlink_snoop.bpf.c 用于实时追踪和分析 Linux 内核中 NVLink 相关的内存操作函数调用。该程序能够捕获每一次 NVLink 设备上的字符串拷贝(strcpy)、内存拷贝(memcpy)和内存填充(memset)操作的详细信息,包括目标地址、源地址(或填充值)、操作大小、返回值、进程上下文和时间戳。

struct nvlink_event {
    enum event_type type;
    enum nvlink_func_id func_id;
    u32 pid;
    u32 tgid;
    char comm[TASK_COMM_LEN];
    u64 timestamp;
    
    // 函数参数
    u64 dst_addr;     // 目标地址
    u64 src_addr;     // 源地址 (对于memset是值)
    u64 size;         // 大小/长度
    
    // 返回值 (对于strcpy和memcpy是目标指针,对于memset是目标指针)
    u64 ret_val;
};

本模块通过 kprobe/kretprobe 挂载于 nvlink_strcpy、nvlink_memcpy 和 nvlink_memset的入口和返回点,完整记录每次内存操作的参数和结果。

// nvlink_strcpy 追踪
SEC("kprobe/nvlink_strcpy")
int BPF_KPROBE(trace_nvlink_strcpy_enter, char *dst, const char *src) {
    bpf_printk("enter the strcpy");
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = (u32)pid_tgid;
    u32 tgid = (u32)(pid_tgid >> 32);

    if (bpf_map_lookup_elem(&snoop_proc, &pid) == NULL &&
        bpf_map_lookup_elem(&snoop_proc, &tgid) == NULL)
        return 0;

    struct nvlink_event *event = create_nvlink_event(EVENT_NVLINK_ENTER, 
                                                NVLINK_FUNC_STRCPY, pid, tgid);
    if (!event)
        return 0;

    event->dst_addr = (u64)dst;
    event->src_addr = (u64)src;
    // 尝试获取字符串长度 (受限于BPF安全检查可能无法完全获取)
    event->size = 0;
    
    bpf_ringbuf_submit(event, 0);
    return 0;
}

NVLink 操作追踪流程:每次内存操作函数被调用时,采集操作参数、进程信息和时间戳,提交进入事件;函数返回时,采集返回值并提交退出事件,实现对 NVLink 关键内存操作的全流程可观测,为高性能计算、GPU 互联调优和链路异常分析提供坚实基础。

由于服务器上的GPU走的是PCIe总线,所以NVLink的追踪模块目前并未启用。