1 /**
2 * @file kallsyms.c
3 * @author longjin (longjin@RinGoTek.cn)
4 * @brief 内核栈跟踪
5 * @version 0.1
6 * @date 2022-06-22
7 *
8 * @copyright Copyright (c) 2022
9 *
10 */
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15
16 /**
17 * @brief 判断符号是否需要被输出(只输出text段内的符号)
18 *
19 */
20 #define symbol_to_write(vaddr, tv, etv) \
21 ((vaddr < tv || vaddr > etv) ? 0 : 1)
22
23 /**
24 * @brief 使用nm命令提取出来的信息存到这个结构体之中
25 *
26 */
27 struct kernel_symbol_entry_t
28 {
29 uint64_t vaddr;
30 char type;
31 char *symbol;
32 int symbol_length;
33 };
34
35 struct kernel_symbol_entry_t *symbol_table;
36 // 符号表最大能容纳的entry数量
37 uint64_t table_size = 0;
38 // 符号表当前的entry数量
39 uint64_t entry_count = 0;
40 // 符号表中,text和etext的下标
41 uint64_t text_vaddr, etext_vaddr;
42
43 /**
44 * @brief 读取一个符号到entry之中
45 *
46 * @param filp stdin的文件指针
47 * @param entry 待填写的entry
48 * @return int 返回码
49 */
read_symbol(FILE * filp,struct kernel_symbol_entry_t * entry)50 int read_symbol(FILE *filp, struct kernel_symbol_entry_t *entry)
51 {
52 // 本函数假设nm命令输出的结果中,每行最大512字节
53 char str[512] = {0};
54 char *s = fgets(str, sizeof(str), filp);
55 if (s != str)
56 {
57 return -1;
58 }
59
60 char symbol_name[512] = {0};
61 int retval = sscanf(str, "%llx %c %512c", &entry->vaddr, &entry->type, symbol_name);
62
63 // 如果当前行不符合要求
64 if (retval != 3)
65 {
66 return -1;
67 }
68 // malloc一块内存,然后把str的内容拷贝进去,接着修改symbol指针
69 size_t len = strlen(symbol_name);
70 if (len >= 1 && symbol_name[len - 1] == '\n')
71 {
72 symbol_name[len - 1] = '\0';
73 len--;
74 }
75 // 转义双引号
76 for (int i = 0; i < len; i++)
77 {
78 if (symbol_name[i] == '"')
79 {
80 char temp[len - i];
81 memcpy(temp, symbol_name + i, len - i);
82 symbol_name[i] = '\\';
83 memcpy(symbol_name + i + 1, temp, len - i);
84 i++;
85 }
86 }
87 entry->symbol = strdup(symbol_name);
88 entry->symbol_length = len + 1; // +1的原因是.asciz指令会在字符串末尾自动添加结束符\0
89 return 0;
90 }
91
92 /**
93 * @brief 接收标准输入流的数据,解析nm命令输出的内容
94 *
95 * @param filp
96 */
read_map(FILE * filp)97 void read_map(FILE *filp)
98 {
99 // 循环读入数据直到输入流结束
100 while (!feof(filp))
101 {
102 // 给符号表扩容
103 if (entry_count >= table_size)
104 {
105 table_size += 100;
106 // 由于使用了realloc,因此符号表原有的内容会被自动的copy过去
107 symbol_table = (struct kernel_symbol_entry_t *)realloc(symbol_table, sizeof(struct kernel_symbol_entry_t) * table_size);
108 }
109
110 // 若成功读取符号表的内容,则将计数器+1
111 if (read_symbol(filp, &symbol_table[entry_count]) == 0)
112 ++entry_count;
113 }
114
115 // 查找符号表中的text和etext标签
116 for (uint64_t i = 0; i < entry_count; ++i)
117 {
118 if (text_vaddr == 0ULL && strcmp(symbol_table[i].symbol, "_text") == 0)
119 text_vaddr = symbol_table[i].vaddr;
120 if (etext_vaddr == 0ULL && strcmp(symbol_table[i].symbol, "_etext") == 0)
121 etext_vaddr = symbol_table[i].vaddr;
122 if (text_vaddr != 0ULL && etext_vaddr != 0ULL)
123 break;
124 }
125 }
126
127 /**
128 * @brief 输出最终的kallsyms汇编代码文件
129 * 直接输出到stdout,通过命令行的 > 命令,写入文件
130 */
generate_result()131 void generate_result()
132 {
133 printf(".section .rodata\n\n");
134 printf(".global kallsyms_address\n");
135 printf(".align 8\n\n");
136
137 printf("kallsyms_address:\n"); // 地址数组
138
139 uint64_t last_vaddr = 0;
140 uint64_t total_syms_to_write = 0; // 真正输出的符号的数量
141
142 // 循环写入地址数组
143 for (uint64_t i = 0; i < entry_count; ++i)
144 {
145 // 判断是否为text段的符号
146 if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
147 continue;
148
149 if (symbol_table[i].vaddr == last_vaddr)
150 continue;
151
152 // 输出符号地址
153 printf("\t.quad\t%#llx\n", symbol_table[i].vaddr);
154 ++total_syms_to_write;
155
156 last_vaddr = symbol_table[i].vaddr;
157 }
158
159 putchar('\n');
160
161 // 写入符号表的表项数量
162 printf(".global kallsyms_num\n");
163 printf(".align 8\n");
164 printf("kallsyms_num:\n");
165 printf("\t.quad\t%lld\n", total_syms_to_write);
166
167 putchar('\n');
168
169 // 循环写入符号名称的下标索引
170 printf(".global kallsyms_names_index\n");
171 printf(".align 8\n");
172 printf("kallsyms_names_index:\n");
173 uint64_t position = 0;
174 last_vaddr = 0;
175 for (uint64_t i = 0; i < entry_count; ++i)
176 {
177 // 判断是否为text段的符号
178 if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
179 continue;
180
181 if (symbol_table[i].vaddr == last_vaddr)
182 continue;
183
184 // 输出符号名称的偏移量
185 printf("\t.quad\t%lld\n", position);
186 position += symbol_table[i].symbol_length;
187 last_vaddr = symbol_table[i].vaddr;
188 }
189
190 putchar('\n');
191
192 // 输出符号名
193 printf(".global kallsyms_names\n");
194 printf(".align 8\n");
195 printf("kallsyms_names:\n");
196
197 last_vaddr = 0;
198 for (uint64_t i = 0; i < entry_count; ++i)
199 {
200 // 判断是否为text段的符号
201 if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
202 continue;
203
204 if (symbol_table[i].vaddr == last_vaddr)
205 continue;
206
207 // 输出符号名称
208 printf("\t.asciz\t\"%s\"\n", symbol_table[i].symbol);
209
210 last_vaddr = symbol_table[i].vaddr;
211 }
212
213 putchar('\n');
214 }
main(int argc,char ** argv)215 int main(int argc, char **argv)
216 {
217 read_map(stdin);
218
219 generate_result();
220 }