Keep learning, keep living...

0%

crash脚本工具crash-lua

crash是一个用于分析Linux内核转储文件(vmcore)的工具。正在运行的内核安装上debuginfo包之后,直接运行crash也可以直接分析运行中的内核,这对于分析一些内核问题极为有用。

在我们的某个场景中,需要分析NFQUEUE队列中的数据包内容。队列在内核中的表示是结构体nfqnl_instance,数据包sk_buff通过nf_queue_entry结构链在队列nfqnl_instance中。

要找到对应的nfqnl_instance,需要执行多条crash命令:

  • 加载内核模块nfnetlink_queue:

    1
    mod -s nfnetlink_queue
  • 查看索引数字:

    1
    p nfnl_queue_net_id
  • 获取net.gen地址:

    1
    net.gen init_net
  • 通过上述步骤获取的地址进一步获取net_generic.ptr:

1
struct net_generic.ptr 0xffff9312763e5680
  • 从上述步骤获取地址读取若干地址:

    1
    rd -x 0xffffa08279cda518 12
  • 根据第二步中获取的索引数字找到nfnl_queue_net的地址,并进一步查看其结构:

    1
    struct nfnl_queue_net ffffa0852b249900
  • 从中找到nfqnl_instance结构地址并查看:

    1
    struct nfqnl_instance 0xffff931109f736c0
  • 从中通过链表找到所有的nf_queue_entry进行查看:

    1
    struct nf_queue_entry 0xffff931529496d80

整个过程是有顺序依赖的,后序步骤的命令需要前边步骤所返回的地址。每次分析都需要从头手动执行一遍效率太低,因而期望可以有批量执行命令脚本的能力。

crash支持通过-i参数或者<符号读入批量命令进行执行,但这种方式需要把所有的命令提前写入文件。对于我们这种后序命令依赖前序命令的结果,这种方式就不奏效了。

经过查阅,发现crash工具支持so扩展,可以通过C语言开发crash扩展,可以参考:

我们可以在自定义扩展中依次调用crash命令,并解析命令输出,进而拼接出后序命令。当然,用C语言解析命令字符串输出的这种逻辑开发效率较低,我们可以将执行命令的能力导出到Lua语言中,由Lua脚本来解析命令输出,从而给crash添加上执行脚本的能力。

上述查找nfqnl_instance的逻辑可以通过Lua脚本来完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
require "crash"

local result = crash.exec_command("mod -s nfnetlink_queue")
print(result)

local result = crash.exec_command("p nfnl_queue_net_id")
local idx = result:match(".*=%s*(%d+)%s*$")
print("index: " .. idx)

local result = crash.exec_command("struct net.gen init_net")
print(result)

local start_pos = result:find("=") + 1
local result = result:sub(start_pos):match("^%s*(.*)")

print(result)

command = string.format("struct net_generic.ptr %s", result)

local result = crash.exec_command(command)
print(result)

local start_pos = result:find("=") + 1
local result = result:sub(start_pos):match("^%s*(.*)")
print(result)

command = string.format("rd -x %s %s", result, idx)
local result = crash.exec_command(command)
local result = result:match("([0-9a-f]+)[,%s]*$")
print(result)

command = string.format("struct nfnl_queue_net %s", result)
local result = crash.exec_command(command)
print(result)

扩展的实现位于:

参考: