xref: /DragonOS/kernel/src/debug/kallsyms.c (revision c635d8a9cfe25bc11779f323ef0c7d7a0f597d4a)
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  */
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  */
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  */
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 }
215 int main(int argc, char **argv)
216 {
217     read_map(stdin);
218 
219     generate_result();
220 }