BPF(Berkeley Packet Filter)是一種虛擬機指令集,最初用于網絡數據包過濾,后來被擴展為eBPF(extended BPF)并廣泛應用于Linux內核的各個子系統。下面將詳細介紹BPF匯編語言并通過多個示例展示其用法。
BPF匯編是一種基于寄存器的匯編語言,主要特點包括:
11個64位寄存器:r0-r10
r0用于存儲返回值,r10是棧幀指針
支持加ZAI(load)/存儲、算術運算、跳轉等指令
每條指令固定為8字節長度
基本指令格式
op:16, dst_reg:4, src_reg:4, off:16, imm:32
op:操作碼
dst_reg:目標寄存器
src_reg:源寄存器
off:偏移量
imm:立即數
BPF匯編示例詳解
示例1:簡單的返回值
// 返回常量值42
mov r0, 42 // 將立即數42存入r0寄存器
exit // 退出程序,返回r0的值
這個最簡單的BPF程序只是返回常量值42。mov指令將立即數移動到寄存器,exit指令結束程序執行。
示例2:條件判斷
// 如果第一個參數大于10,返回1,否則返回0
ldxw r0, [r1+0] // 從r1指向的內存加ZAI(load)32位值到r0 (r1是第一個參數)
jgt r0, 10, 1 // 如果r0 > 10,跳過下一條指令
mov r0, 0 // 返回0
exit
mov r0, 1 // 返回1
exit
這個程序展示了條件跳轉:
ldxw從第一個參數加ZAI(load)32位值
jgt進行大于比較,條件滿足則跳過下一條指令
根據比較結果返回0或1
示例3:數組元素訪問
// 訪問數組的第二個元素并返回
mov r2, 1 // 數組索引1 (第二個元素)
ldxdw r0, [r1+r28] // 從基址r1 + 索引r28加ZAI(load)64位值
exit
這個示例展示了:
數組索引計算(r2*8,因為64位元素大小為8字節)
基址加偏移的內存訪問模式
示例4:循環求和
// 對數組前5個元素求和
mov r0, 0 // 初始化累加器r0=0
mov r2, 0 // 初始化索引r2=0
loop:
jge r2, 5, exit_loop // 如果r2 >= 5,跳出循環
ldxdw r3, [r1+r2*8] // 加ZAI(load)array[r2]
add r0, r3 // r0 += array[r2]
add r2, 1 // r2++
ja loop // 無條件跳轉回循環開始
exit_loop:
exit
這個更復雜的示例展示了:
循環結構實現
使用jge進行循環條件檢查
ja無條件跳轉
數組遍歷和累加操作
示例5:哈希表查找
// 假設r1指向哈希表,r2是鍵,查找對應的值
mov r0, 0 // 默認返回0(未找到)
mov r3, 0 // 初始化哈希索引
hash_loop:
mov r4, [r1+r3*16+0] // 加ZAI(load)鍵
jeq r4, r2, found // 如果鍵匹配,跳轉到found
add r3, 1 // 遞增索引
jlt r3, 10, hash_loop // 如果r3 < 10繼續循環
exit // 未找到,返回0
found:
ldxdw r0, [r1+r3*16+8] // 加ZAI(load)對應的值
exit
這個示例展示了:
哈希表遍歷(假設每個條目16字節:8字節鍵+8字節值)
嵌套的條件跳轉
更復雜的內存訪問模式
BPF指令主要分為以下幾類:
加ZAI(load)/存儲指令
ldxb, ldxh, ldxw, ldxdw:從內存加ZAI(load)不同大小的數據
stb, sth, stw, stdw:存儲數據到內存
lddw:加ZAI(load)64位立即數到寄存器
算術運算指令
add, sub, mul, div:加減乘除
and, or, xor, lsh, rsh, arsh:位操作
neg:取負
mov:寄存器移動
跳轉指令
ja:無條件跳轉
jeq, jne, jgt, jge, jlt, jle:條件跳轉
jset:測試位跳轉
call:函數調用
exit:退出程序
其他指令
be16, be32, be64:字節序轉換
le16, le32, le64:小端序轉換
BPF匯編編程技巧
寄存器使用:
r0:返回值
r1-r5:函數參數/臨時寄存器
r6-r9:被調用者保存寄存器
r10:棧幀指針
內存訪問:
必須檢查邊界,否則會被驗證器拒絕
使用r10訪問棧空間(如[r10-8])
循環結構:
必須有明確的退出條件
循環次數通常需要有限制
總結
BPF匯編雖然看起來簡單,但能表達復雜的過濾和監控邏輯。通過組合基本指令,可以實現各種內核態的高效程序。現代eBPF還支持更多高級特性如映射訪問、輔助函數調用等,但底層仍然是基于這種匯編指令集。