反汇编ebpf目标文件实例sockex1_kern.c

反汇编ebpf目标文件实例sockex1_kern.o

内核5.15
/home/cl/source/stable/linux/samples/bpf/sockex1_kern.c

网上汇编的基本解释有很多,如下两个链接,这里就不多解释寄存器和编码的用法:
https://blog.csdn.net/weixin_49393427/article/details/124057792
https://zhuanlan.zhihu.com/p/487995137

本文对其中的一条特殊指令进行解释:

   1:	30 00 00 00 17 00 00 00	r0 = *(u8 *)skb[23]  

这个指令为特殊的指令(non-generic instructions),不能按照编码进行解读,如果按照编码解读则src 和 dst 寄存器都是r0,不能解释这里在干啥,r0来源也未知。
其实这个是一条特殊的指令,不能按照简单的编码去理解,这里默认r6是skb,r0是输出,跟编码里面src和dst reg都是0没有任何的关系。类似的这种指令有两条,在文档解释如下。

也终于理解为什么第一条指令需要将r6=r1。

https://www.kernel.org/doc/html/v5.17/bpf/instruction-set.html
Packet access instructions
eBPF has two non-generic instructions: (BPF_ABS | <size> | BPF_LD) and (BPF_IND | <size> | BPF_LD) which are used to access packet data.

They had to be carried over from classic BPF to have strong performance of socket filters running in eBPF interpreter. These instructions can only be used when interpreter context is a pointer to struct sk_buff and have seven implicit operands. Register R6 is an implicit input that must contain pointer to sk_buff. Register R0 is an implicit output which contains the data fetched from the packet. Registers R1-R5 are scratch registers and must not be used to store the data across BPF_ABS | BPF_LD or BPF_IND | BPF_LD instructions.

These instructions have implicit program exit condition as well. When eBPF program is trying to access the data beyond the packet boundary, the interpreter will abort the execution of the program. JIT compilers therefore must preserve this property. src_reg and imm32 fields are explicit inputs to these instructions.

For example, BPF_IND | BPF_W | BPF_LD means:

R0 = ntohl(*(u32 *) (((struct sk_buff *) R6)->data + src_reg + imm32))
and R1 - R5 are clobbered.

sockex1_kern.c 和完整的反汇编解释如下:

root@cl-Alienware-13:/home/cl/source/stable/linux/samples/bpf# cat sockex1_kern.c 
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/if_packet.h>
#include <uapi/linux/ip.h>
#include <bpf/bpf_helpers.h>
#include "bpf_legacy.h"

struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__type(key, u32);
	__type(value, long);
	__uint(max_entries, 256);
} my_map SEC(".maps");

SEC("socket1")
int bpf_prog1(struct __sk_buff *skb)
{
	int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol));
	long *value;

	if (skb->pkt_type != PACKET_OUTGOING)
		return 0;

	value = bpf_map_lookup_elem(&my_map, &index);
	if (value)
		__sync_fetch_and_add(value, skb->len);

	return 0;
}
char _license[] SEC("license") = "GPL";

反汇编程序

root@cl-Alienware-13:/home/cl/source/stable/linux/samples/bpf# llvm-objdump -S sockex1_kern.o 

sockex1_kern.o:	file format ELF64-BPF


Disassembly of section socket1:

0000000000000000 bpf_prog1:
; {
       0:	bf 16 00 00 00 00 00 00	r6 = r1   【r1是输入参数skb】
; 	int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol));
       1:	30 00 00 00 17 00 00 00	r0 = *(u8 *)skb[23]  【见前面解释,23偏移为protocol,这个指令为特殊的指令(non-generic instructions),不能按照编码进行解读, ,r6常规隐式作为skb】
       2:	63 0a fc ff 00 00 00 00	*(u32 *)(r10 - 4) = r0 【fc ff 是-4,这里r10为栈fp,这里index在栈里面r10-4的位置】
; 	if (skb->pkt_type != PACKET_OUTGOING)
       3:	61 61 04 00 00 00 00 00	r1 = *(u32 *)(r6 + 4) 【skb->pkt_type】
       4:	55 01 08 00 04 00 00 00	if r1 != 4 goto +8 <LBB0_3>  【判断 PACKET_OUTGOING 为4】
       5:	bf a2 00 00 00 00 00 00	r2 = r10  【取栈指针】
       6:	07 02 00 00 fc ff ff ff	r2 += -4  【移动到index位置,作为下面函数的第二个参数】
; 	value = bpf_map_lookup_elem(&my_map, &index);
       7:	18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00	r1 = 0 ll   【这里r1是参数my_map】
       9:	85 00 00 00 01 00 00 00	call 1  【调用函数】
; 	if (value)
      10:	15 00 02 00 00 00 00 00	if r0 == 0 goto +2 <LBB0_3>  【返回值r0为value指针】
; 		__sync_fetch_and_add(value, skb->len);
      11:	61 61 00 00 00 00 00 00	r1 = *(u32 *)(r6 + 0)  【取skb->len】
      12:	db 10 00 00 00 00 00 00	lock *(u64 *)(r0 + 0) += r1 【累加skb->len 到value】

0000000000000068 LBB0_3:
; }
      13:	b7 00 00 00 00 00 00 00	r0 = 0
      14:	95 00 00 00 00 00 00 00	exit
root@cl-Alienware-13:/home/cl/source/stable/linux/samples/bpf#