## 基本信息 需求:监控bash命令监控程序 解析bash:  bash 有一个内部函数readline。bash 通过它读取每个行 shell 命令。只要监控这个函数的返回值,就可以监控系统中的所有 bash 发起的命令。 ## 开发过程 该函数位于用户空间,所以我们要编写一个uretprobe类型的ebpf程序。并且根据函数调用约定,获取返回值寄存器的值。 首先定义一个结构体,用于存储程序pid与命令行 ``` struct event { u32 pid; u8 line[80]; }; ``` 定义一个maps,用于内核态保存数据,并通过用户空间获取数据 ``` struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); } events SEC(".maps"); ``` 根据逻辑进行编写。PT_REGS_RC 是一个宏,它根据当前的架构获取寄存器状态中的返回值寄存器的值。 在 eBPF 程序中,bpf_perf_event_output 是一个关键的辅助函数,它允许 eBPF 程序将数据发送到用户空间的 perf 事件。 &events:指向 perf 事件数组的指针。这个数组是在 eBPF 程序加载时分配的,用于存储 perf 事件的输出。 &event:指向要发送的数据的指针。在这个例子中,它是指向 struct event 的指针。 ``` go SEC("uretprobe/readline") int uretprobe_readline(struct pt_regs *ctx) { struct event event; event.pid = bpf_get_current_pid_tgid(); bpf_probe_read(&event.line, sizeof(event.line), (void *)PT_REGS_RC(ctx)); bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); return 0; } ``` 编写好ebpf程序代码后,我们将通过cilium/ebpf框架编写代码用于: 1、加载ebpf程序 2、创建一个 Uretprobe,将其绑定到指定的用户态函数上。 3、获取perf event的reader,读取相应样本数据 ``` // 加载ebpf程序 objs := bpfObjects{} if err := loadBpfObjects(&objs, nil); err != nil { log.Fatalf("loading objects: %s", err) } defer objs.Close() .... // 打开链接文件,获取符号 ex, err := link.OpenExecutable(binPath) if err != nil { log.Fatalf("opening executable: %s", err) } //创建一个 Uretprobe,其绑定到符号 symbol,这里的 symbol 应该是一个函数名,比如 readline。objs.UretprobeBashReadline 可能是一个预先定义好的 BPF 程序,用于在 readline 函数返回时执行。 up, err := ex.Uretprobe(symbol, objs.UretprobeBashReadline, nil) if err != nil { log.Fatalf("creating uretprobe: %s", err) } defer up.Close() ``` ## 效果 
## 基本信息 需求:监控bash命令监控程序 解析bash:  bash 有一个内部函数readline。bash 通过它读取每个行 shell 命令。只要监控这个函数的返回值,就可以监控系统中的所有 bash 发起的命令。 ## 开发过程 该函数位于用户空间,所以我们要编写一个uretprobe类型的ebpf程序。并且根据函数调用约定,获取返回值寄存器的值。 首先定义一个结构体,用于存储程序pid与命令行 ``` struct event { u32 pid; u8 line[80]; }; ``` 定义一个maps,用于内核态保存数据,并通过用户空间获取数据 ``` struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); } events SEC(".maps"); ``` 根据逻辑进行编写。PT_REGS_RC 是一个宏,它根据当前的架构获取寄存器状态中的返回值寄存器的值。 在 eBPF 程序中,bpf_perf_event_output 是一个关键的辅助函数,它允许 eBPF 程序将数据发送到用户空间的 perf 事件。 &events:指向 perf 事件数组的指针。这个数组是在 eBPF 程序加载时分配的,用于存储 perf 事件的输出。 &event:指向要发送的数据的指针。在这个例子中,它是指向 struct event 的指针。 ``` go SEC("uretprobe/readline") int uretprobe_readline(struct pt_regs *ctx) { struct event event; event.pid = bpf_get_current_pid_tgid(); bpf_probe_read(&event.line, sizeof(event.line), (void *)PT_REGS_RC(ctx)); bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); return 0; } ``` 编写好ebpf程序代码后,我们将通过cilium/ebpf框架编写代码用于: 1、加载ebpf程序 2、创建一个 Uretprobe,将其绑定到指定的用户态函数上。 3、获取perf event的reader,读取相应样本数据 ``` // 加载ebpf程序 objs := bpfObjects{} if err := loadBpfObjects(&objs, nil); err != nil { log.Fatalf("loading objects: %s", err) } defer objs.Close() .... // 打开链接文件,获取符号 ex, err := link.OpenExecutable(binPath) if err != nil { log.Fatalf("opening executable: %s", err) } //创建一个 Uretprobe,其绑定到符号 symbol,这里的 symbol 应该是一个函数名,比如 readline。objs.UretprobeBashReadline 可能是一个预先定义好的 BPF 程序,用于在 readline 函数返回时执行。 up, err := ex.Uretprobe(symbol, objs.UretprobeBashReadline, nil) if err != nil { log.Fatalf("creating uretprobe: %s", err) } defer up.Close() .... //在 eBPF 程序中,PERF_EVENT_ARRAY 是一个特殊的数组,它用于存储 perf 事件。当 eBPF 程序中的 perf 事件触发时,比如一个内核函数被调用或者用户空间函数返回,内核会将相关的数据写入到这个数组中。用户空间的程序可以通过打开一个 perf 事件读取器来读取这个数组中的数据,从而获取事件的详细信息。 rd, err := perf.NewReader(objs.Events, os.Getpagesize()) if err != nil { log.Fatalf("creating perf event reader: %s", err) } defer rd.Close() .... //定义bpfEvent var event bpfEvent for { record, err := rd.Read() if err != nil { if errors.Is(err, perf.ErrClosed) { return } log.Printf("reading from perf event reader: %s", err) continue } if record.LostSamples != 0 { log.Printf("perf event ring buffer full, dropped %d samples", record.LostSamples) continue } // 解析perf事件原始样本为bpfevent结构体 if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil { log.Printf("parsing perf event: %s", err) continue } //打印相关信息 log.Printf("%s:%s return value: %s", binPath, symbol, unix.ByteSliceToString(event.Line[:])) } ``` ## 效果 
Difference
Hamlet: Do you see yonder cloud that's almost in shape of a camel?
Polonius: By the mass, and 'tis like a camel, indeed.
Hamlet: Methinks it is like a weasel. Polonius: It is backed like a weasel. Hamlet: Or like a whale? Polonius: Very like a whale. -- Shakespeare
Hamlet: Do you see the cloud over there that's almost the shape of a camel?
Polonius: By golly, it is like a camel, indeed.
Hamlet: I think it looks like a weasel.
Polonius: It is shaped like a weasel.
Hamlet: Or like a whale? Polonius: It's totally like a whale. -- Shakespeare