Android查看Linux kernel预处理后代码

本文最后更新于 2024年10月14日 上午

一、问题背景

在debug一个kernel panic的问题时,pc指向anon_vma_interval_tree_iter_first这个函数:

1
2
3
4
[2024-09-23 16:56:38.143][38750.649924] pstate: 20c00005 (nzCv daif +PAN +UAO -TCO BTYPE=--)
[2024-09-23 16:56:38.149][38750.656016] pc : anon_vma_interval_tree_iter_first+0x1c/0x94
[2024-09-23 16:56:38.153][38750.661746] lr : rmap_walk_ksm+0xe4/0x1c4
[2024-09-23 16:56:38.157][38750.665807] sp : ffff800013473870

根据vmlinux查询到问题代码出现在interval_tree.c第71行:

1
2
3
4
5
6
7
8
9
10
11
12
13
data/mjx/test/buildsystem/android12/kernel/mm/interval_tree.c:71
ffff80001020c5a4: b400008a cbz x10, ffff80001020c5b4 <anon_vma_interval_tree_remove+0x22c>
ffff80001020c5a8: f9400d4a ldr x10, [x10, #24]
ffff80001020c5ac: eb09015f cmp x10, x9
ffff80001020c5b0: 9a898149 csel x9, x10, x9, hi // hi = pmore
ffff80001020c5b4: f940050a ldr x10, [x8, #8]
ffff80001020c5b8: b400008a cbz x10, ffff80001020c5c8 <anon_vma_interval_tree_remove+0x240>
ffff80001020c5bc: f9400d4a ldr x10, [x10, #24]
ffff80001020c5c0: eb09015f cmp x10, x9
ffff80001020c5c4: 9a898149 csel x9, x10, x9, hi // hi = pmore
ffff80001020c5c8: f9400d0a ldr x10, [x8, #24]
ffff80001020c5cc: eb09015f cmp x10, x9
ffff80001020c5d0: 540000a0 b.eq ffff80001020c5e4 <anon_vma_interval_tree_remove+0x25c> // b.none

可是查看源码时,发现这是一个宏定义:

1
2
3
INTERVAL_TREE_DEFINE(struct anon_vma_chain, rb, unsigned long, rb_subtree_last,
avc_start_pgoff, avc_last_pgoff,
static inline, __anon_vma_interval_tree)

宏定义会自动展开不同的函数,但是宏定义非常复杂,很难手动展开代码:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#define INTERVAL_TREE_DEFINE(ITSTRUCT, ITRB, ITTYPE, ITSUBTREE,           \
ITSTART, ITLAST, ITSTATIC, ITPREFIX) \
\
/* Callbacks for augmented rbtree insert and remove */ \
\
RB_DECLARE_CALLBACKS_MAX(static, ITPREFIX ## _augment, \
ITSTRUCT, ITRB, ITTYPE, ITSUBTREE, ITLAST) \
\
/* Insert / remove interval nodes from the tree */ \
\
ITSTATIC void ITPREFIX ## _insert(ITSTRUCT *node, \
struct rb_root_cached *root) \
{ \
struct rb_node **link = &root->rb_root.rb_node, *rb_parent = NULL; \
ITTYPE start = ITSTART(node), last = ITLAST(node); \
ITSTRUCT *parent; \
bool leftmost = true; \
\
while (*link) { \
rb_parent = *link; \
parent = rb_entry(rb_parent, ITSTRUCT, ITRB); \
if (parent->ITSUBTREE < last) \
parent->ITSUBTREE = last; \
if (start < ITSTART(parent)) \
link = &parent->ITRB.rb_left; \
else { \
link = &parent->ITRB.rb_right; \
leftmost = false; \
} \
} \
\
node->ITSUBTREE = last; \
rb_link_node(&node->ITRB, rb_parent, link); \
rb_insert_augmented_cached(&node->ITRB, root, \
leftmost, &ITPREFIX ## _augment); \
} \
\
ITSTATIC void ITPREFIX ## _remove(ITSTRUCT *node, \
struct rb_root_cached *root) \
{ \
rb_erase_augmented_cached(&node->ITRB, root, &ITPREFIX ## _augment); \
} \
\
/* \
* Iterate over intervals intersecting [start;last] \
* \
* Note that a node's interval intersects [start;last] iff: \
* Cond1: ITSTART(node) <= last \
* and \
* Cond2: start <= ITLAST(node) \
*/ \
\
static ITSTRUCT * \
ITPREFIX ## _subtree_search(ITSTRUCT *node, ITTYPE start, ITTYPE last) \
{ \
while (true) { \
/* \
* Loop invariant: start <= node->ITSUBTREE \
* (Cond2 is satisfied by one of the subtree nodes) \
*/ \
if (node->ITRB.rb_left) { \
ITSTRUCT *left = rb_entry(node->ITRB.rb_left, \
ITSTRUCT, ITRB); \
if (start <= left->ITSUBTREE) { \
/* \
* Some nodes in left subtree satisfy Cond2. \
* Iterate to find the leftmost such node N. \
* If it also satisfies Cond1, that's the \
* match we are looking for. Otherwise, there \
* is no matching interval as nodes to the \
* right of N can't satisfy Cond1 either. \
*/ \
node = left; \
continue; \
} \
} \
if (ITSTART(node) <= last) { /* Cond1 */ \
if (start <= ITLAST(node)) /* Cond2 */ \
return node; /* node is leftmost match */ \
if (node->ITRB.rb_right) { \
node = rb_entry(node->ITRB.rb_right, \
ITSTRUCT, ITRB); \
if (start <= node->ITSUBTREE) \
continue; \
} \
} \
return NULL; /* No match */ \
} \
} \
\
ITSTATIC ITSTRUCT * \
ITPREFIX ## _iter_first(struct rb_root_cached *root, \
ITTYPE start, ITTYPE last) \
{ \
ITSTRUCT *node, *leftmost; \
\
if (!root->rb_root.rb_node) \
return NULL; \
\
/* \
* Fastpath range intersection/overlap between A: [a0, a1] and \
* B: [b0, b1] is given by: \
* \
* a0 <= b1 && b0 <= a1 \
* \
* ... where A holds the lock range and B holds the smallest \
* 'start' and largest 'last' in the tree. For the later, we \
* rely on the root node, which by augmented interval tree \
* property, holds the largest value in its last-in-subtree. \
* This allows mitigating some of the tree walk overhead for \
* for non-intersecting ranges, maintained and consulted in O(1). \
*/ \
node = rb_entry(root->rb_root.rb_node, ITSTRUCT, ITRB); \
if (node->ITSUBTREE < start) \
return NULL; \
\
leftmost = rb_entry(root->rb_leftmost, ITSTRUCT, ITRB); \
if (ITSTART(leftmost) > last) \
return NULL; \
\
return ITPREFIX ## _subtree_search(node, start, last); \
} \
\
ITSTATIC ITSTRUCT * \
ITPREFIX ## _iter_next(ITSTRUCT *node, ITTYPE start, ITTYPE last) \
{ \
struct rb_node *rb = node->ITRB.rb_right, *prev; \
\
while (true) { \
/* \
* Loop invariants: \
* Cond1: ITSTART(node) <= last \
* rb == node->ITRB.rb_right \
* \
* First, search right subtree if suitable \
*/ \
if (rb) { \
ITSTRUCT *right = rb_entry(rb, ITSTRUCT, ITRB); \
if (start <= right->ITSUBTREE) \
return ITPREFIX ## _subtree_search(right, \
start, last); \
} \
\
/* Move up the tree until we come from a node's left child */ \
do { \
rb = rb_parent(&node->ITRB); \
if (!rb) \
return NULL; \
prev = &node->ITRB; \
node = rb_entry(rb, ITSTRUCT, ITRB); \
rb = node->ITRB.rb_right; \
} while (prev == rb); \
\
/* Check if the node intersects [start;last] */ \
if (last < ITSTART(node)) /* !Cond1 */ \
return NULL; \
else if (start <= ITLAST(node)) /* Cond2 */ \
return node; \
} \
}

所以这个时候可以借助编译器,保留预处理后的中间文件,这样可以查看展开后的代码。

二、定位分析

我们先看下传统编译模型下,源码的编译步骤:

compile

C/C++ 代码编译过程

对于单文件,我们可以简单的使用gcc -E 获得预处理文件,使用gcc -S获得汇编文件,其他文件输出详见GCC Options Controlling the Kind of Output

但是在实际中,项目是由很多个文件组成的,文件间是有依赖关系的;手动确定依赖关系,并输入gcc来编译获得预处理文件,速度慢流程复杂,不具有实际使用意义。

所以需要找个一个方便且能自动帮我们确定依赖关系,直接输出预处理文件的方法。

三、解决方案

经过搜索得知,发现gcc的Debugging-Options有一个选项-save-temps,意如其名,保存临时文件,预处理和汇编都是生成object的中间临时文件。

1
2
3
4
5
-save-temps
Store the usual "temporary" intermediate files permanently;
place them in the current directory and name them based on the source file.
Thus, compiling foo.c with -c -save-temps would produce files foo.i and foo.s, as well as foo.o.
This creates a preprocessed foo.i output file even though the compiler now normally uses an integrated preprocessor.

在使用CMake时,需要设置CMAKE_C_FLAGSCMAKE_CXX_FLAGS,前者是对c文件生效,后者是对cpp文件生效。

1
2
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -save-temps")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -save-temps")

进一步查找,发现-save-temps还可以跟一个参数-save-temps=obj,表示生成预处理文件的位置和.o同目录,这样会更便于查看。

而且这个参数是gcc/clang都支持的。

到这一步,对于所有的CMake+gcc/clang构建系统,都可以方便快捷的生成预处理文件了。

于是找到所使用的Makefile,路径是android12/kernel/mm/Makefile,增加下面的修改:

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/mm/Makefile b/mm/Makefile
index 8de8651..0644233 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -33,6 +33,7 @@ KCOV_INSTRUMENT_failslab.o := n

CFLAGS_init-mm.o += $(call cc-disable-warning, override-init)
CFLAGS_init-mm.o += $(call cc-disable-warning, initializer-overrides)
+CFLAGS_interval_tree.o += -save-temps=obj

mmu-y := nommu.o
mmu-$(CONFIG_MMU) := highmem.o memory.o mincore.o \

此时使用make bootimage命令编译安卓内核后,可以在out目录的KERNEL_OBJ的内核中间文件中找到interval_tree.iinterval_tree.s中间文件,此时可以查看预处理后的展开的宏定义:

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
static inline __attribute__((__gnu_inline__)) __attribute__((__unused__)) __attribute__((__no_instrument_function__)) struct anon_vma_chain * __anon_vma_interval_tree_iter_first(struct rb_root_cached *root, unsigned long start, unsigned long last) {
struct anon_vma_chain *node, *leftmost;
if (!root->rb_root.rb_node) return ((void *)0);
node = ( {
void *__mptr = (void *)(root->rb_root.rb_node);
do {
extern void __compiletime_assert_418(void) ;
if (!(!(!__builtin_types_compatible_p(typeof(*(root->rb_root.rb_node)), typeof(((struct anon_vma_chain *)0)->rb)) && !__builtin_types_compatible_p(typeof(*(root->rb_root.rb_node)), typeof(void))))) __compiletime_assert_418();
}
while (0);
((struct anon_vma_chain *)(__mptr - __builtin_offsetof(struct anon_vma_chain, rb)));
}
);
if (node->rb_subtree_last < start) return ((void *)0);
leftmost = ( {
void *__mptr = (void *)(root->rb_leftmost);
do {
extern void __compiletime_assert_419(void) ;
if (!(!(!__builtin_types_compatible_p(typeof(*(root->rb_leftmost)), typeof(((struct anon_vma_chain *)0)->rb)) && !__builtin_types_compatible_p(typeof(*(root->rb_leftmost)), typeof(void))))) __compiletime_assert_419();
}
while (0);
((struct anon_vma_chain *)(__mptr - __builtin_offsetof(struct anon_vma_chain, rb)));
}
);
if (avc_start_pgoff(leftmost) > last) return ((void *)0);
return __anon_vma_interval_tree_subtree_search(node, start, last);
}

Android查看Linux kernel预处理后代码
https://www.shangyexin.com/2024/10/14/preprocessing/
作者
Yasin
发布于
2024年10月14日
许可协议