内核调试技巧-逆向寻踪,揭开 LACP 协议流程的神秘面纱
作者:wqiangwang,騰訊 TEG 后臺開發工程師
本文通過“Kni 映射到內核的接口未能發送 LACP 報文導致 bond 不能聚合”這個問題,來探索內核調試中,對于正在運行的內核,通過 systemtap 獲取關鍵數據結構的值的通用方法。
背景
DPDK 支持物理端口 通過 kni 映射到內核的虛擬接口作為內核的標準 net device,借助內核完善的生態處理相對復雜的網絡協議,如 tcp 等,這樣以后,無需在用戶態實現這些協議。在 NGW 網關產品中,同樣的,從物理端口接收的 LACP 報文則通過 kni 注入給內核;內核向外發送的 LACP 報文則通過 kni 處理并從物理口出。借助內核成熟的 LACP 協議和生態 而無需用戶態自己實現 LACP,即可完成 bond 聚合。
但在升級 DPDK-20.11 版本時,出現 bond 未能聚合。通過 tcpdump 抓包發現,原來對端交換機已經發送了 LACP 報文,而本端一直未發送 LACP 報文,所以未能聚合:
分析
1、 既然是本端一直未發送 LACP 報文, 則內核協議棧沒有調用 dev_queue_xmit 函數向外發送 skb->protocol 為 0x8809 的 skb。剛好手上有一臺相同內核版本且 bond 已經聚合的設備,為了縮短 bond 內核模塊的代碼學習時間,因此看下這臺設備當調用 dev_queue_xmit 函數發送 skb->protocol 為 0x8809 的 skb 的 backtrace。編寫 systemtap 腳本如下:
2、 對于本設備,systemtap probe 函數 bond_3ad_state_machine_handler 發現其調用時 ad_lacpdu_send 未調用??匆幌麓a,發現 ad_lacpdu_send 若執行,需滿足的條件離不開 port 的訪問,為此必須知道 port 的地址:
這里出現了難點,從上述的 backtrace 調用??芍?#xff0c;ad_tx_machine 已經被編譯器優化,不能通過 systemtap 直接獲取$port 入參或者 pointer_arg(1)提取入參,因此從匯編入手獲取標識 port 的寄存器的值。先用 systemtap 看下行號和匯編指令的對應關系:
匯編的幾個[test 和 je/jne]指令剛好和上面的 ad_tx_machine 源碼中 if 的邏輯是一致的。同時上述的 if 條件都滿足的話,則進入 bond_3ad_state_machine_handler+3079 開始的邏輯:
而 ad_lacpdu_send 作為 callee 是沒有被優化的,仍以函數調用,按照 x64 傳參規則,%rdi 作為第一個參數即為 port 的標識。從 mov %r12,%rdi 可看出,獲取%r12 也就獲取了 port。那么問題來了,應該 probe 什么位置,調用 print_regs 函數獲取%r12 呢。
3、 從上面的匯編[890-929],[3079,3303]這兩個連續區間來看,沒有任何寫指令操作%r12。而匯編[890-929]區間為 ad_lacpdu_send 能否執行的判斷邏輯,所以選擇匯編 890 位置為 probe 位置調用 print_regs 函數來獲取寄存器。同時匯編 890 位置對應的源碼位置為 bond_3ad.c:1261:
因此編寫如下 systemtap 腳本:
運行結果如下:
至此找到 port 的地址了,為了驗證 port 的地址的正確性,此時可以通過 crash 或者 systemtap 繼續校驗。這里通過 systemtap 提取 port 的 adctor_system 成員的 mac 地址來校驗。
因此編寫如下 systemtap 腳本,驗證 ffff888995109838 是 port:
運行結果如下,其 mac 地址和系統中記錄的 bond 信息一致:
4、 既然已經知道了 port,那看下為什么 port 的成員在上述 if 條件中失敗了,再回顧下這個代碼,顯然需要訪問下 port 的 sm_tx_timer_counter, ntt, sm_vars 三個成員:
因此編寫如下 systemtap 腳本:
運行輸出如下,一目了然,原來 sm_vars & AD_PORT_LACP_ENABLED (0x2)為假。
Tips:從藍色方框,結合下方參數可以再次驗證了 port 的地址獲取正確。
5、 為什么 sm_vars & AD_PORT_LACP_ENABLED 為假呢,要知道初始化的時候是置位了 AD_PORT_LACP_ENABLED 標志的
顯然是存在邏輯,復位了 sm_vars 的 AD_PORT_LACP_ENABLED 標志位,搜索下代碼:
原來獲取 port 的 speed 和 duplex 影響了 actor_oper_port_key 的值,而 sm_vars 跟 actor_oper_port_key 直接相關,即源頭是 speed 和 duplex 的問題。從系統記錄的 bond 信息也可以看到 speed 和 duplex 是 unknown 的:
5、 為什么 speed 和 duplex 是 unknown 呢。搜下代碼,推斷 bond 的 slave 口獲取其 speed 和 duplex 失敗了:
編寫腳本驗證查看__ethtool_get_link_ksettings 的返回值,由于編譯器優化,systemtap 提示源碼 385 行 不能輸出變量$res 的值:
同樣的方法使用 print_regs,獲取%eax 即__ethtool_get_link_ksettings 返回值:
%eax 是%rax 的低 32 位,因此%eax = 0xffffffa1,這個值是-95,查看 errno 得知,標識的意思是 operation not supported
6、 而 slave 口是前面說的 kni 映射到內核的接口,通過__ethtool_get_link_ksettings 的源碼可知,原來 kni 映射到內核的接口沒有注冊 ethtool_ops 的 get_link_ksettings/get_settings 方法。
至此該問題的根因分析非常明確了。
解決
1、 kni 映射到內核的接口注冊并實現 ethtool_ops 的 get_settings 方法
2、 get_settings 設置 speed 和 duplex
如下:
方法
1、 根據當前問題點,通過 backtrace 梳理流程,然后找到問題點的核心數據結構。
2、 當核心數據結構不能直接訪問時,則反匯編,查看可通過哪些寄存器間接獲取。
3、 對標識核心數據結構的寄存器,確定正確探測位置,確保探測位置和問題點之間寄存器不會改寫,輸出寄存器的值,systemtap 可調用 print_regs 方法。
4、 遞歸迭代步驟 1-3。
備注:
內核版本:4.14.105-1-tlinux3-0007
Dpdk 版本:20.11
最后
所謂工欲善其事必先利其器,systemtap 是我們分析內核,學習內核,非常好的工具,這玩意需多寫多練,才熟能生巧。歡迎各位一起切磋,一起玩 systemtap。
總結
以上是生活随笔為你收集整理的内核调试技巧-逆向寻踪,揭开 LACP 协议流程的神秘面纱的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: strstrsubstr、AfxGetA
- 下一篇: C语言——项目规范