xref: /DragonOS/kernel/src/debug/kallsyms.c (revision 1496ba7b24a5e6954291ca9643b9f3cec567479a)
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         return -1;
57     }
58 
59     char symbol_name[512] = {0};
60     int retval = sscanf(str, "%llx %c %512c", &entry->vaddr, &entry->type, symbol_name);
61 
62     // 如果当前行不符合要求
63     if (retval != 3) {
64         return -1;
65     }
66     // malloc一块内存,然后把str的内容拷贝进去,接着修改symbol指针
67     size_t len = strlen(symbol_name);
68     if (len >= 1 && symbol_name[len - 1] == '\n') {
69         symbol_name[len - 1] = '\0';
70         len--;
71     }
72     entry->symbol = strdup(symbol_name);
73     entry->symbol_length = len + 1; // +1的原因是.asciz指令会在字符串末尾自动添加结束符\0
74     return 0;
75 }
76 
77 /**
78  * @brief 接收标准输入流的数据,解析nm命令输出的内容
79  *
80  * @param filp
81  */
82 void read_map(FILE *filp)
83 {
84     // 循环读入数据直到输入流结束
85     while (!feof(filp))
86     {
87         // 给符号表扩容
88         if (entry_count >= table_size)
89         {
90             table_size += 100;
91             // 由于使用了realloc,因此符号表原有的内容会被自动的copy过去
92             symbol_table = (struct kernel_symbol_entry_t *)realloc(symbol_table, sizeof(struct kernel_symbol_entry_t) * table_size);
93         }
94 
95         // 若成功读取符号表的内容,则将计数器+1
96         if (read_symbol(filp, &symbol_table[entry_count]) == 0)
97             ++entry_count;
98     }
99 
100     // 查找符号表中的text和etext标签
101     for (uint64_t i = 0; i < entry_count; ++i)
102     {
103         if (text_vaddr == 0ULL && strcmp(symbol_table[i].symbol, "_text") == 0)
104             text_vaddr = symbol_table[i].vaddr;
105         if (etext_vaddr == 0ULL && strcmp(symbol_table[i].symbol, "_etext") == 0)
106             etext_vaddr = symbol_table[i].vaddr;
107         if (text_vaddr != 0ULL && etext_vaddr != 0ULL)
108             break;
109     }
110 }
111 
112 /**
113  * @brief 输出最终的kallsyms汇编代码文件
114  * 直接输出到stdout,通过命令行的 > 命令,写入文件
115  */
116 void generate_result()
117 {
118     printf(".section .rodata\n\n");
119     printf(".global kallsyms_address\n");
120     printf(".align 8\n\n");
121 
122     printf("kallsyms_address:\n"); // 地址数组
123 
124     uint64_t last_vaddr = 0;
125     uint64_t total_syms_to_write = 0; // 真正输出的符号的数量
126 
127     // 循环写入地址数组
128     for (uint64_t i = 0; i < entry_count; ++i)
129     {
130         // 判断是否为text段的符号
131         if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
132             continue;
133 
134         if (symbol_table[i].vaddr == last_vaddr)
135             continue;
136 
137         // 输出符号地址
138         printf("\t.quad\t%#llx\n", symbol_table[i].vaddr);
139         ++total_syms_to_write;
140 
141         last_vaddr = symbol_table[i].vaddr;
142     }
143 
144     putchar('\n');
145 
146     // 写入符号表的表项数量
147     printf(".global kallsyms_num\n");
148     printf(".align 8\n");
149     printf("kallsyms_num:\n");
150     printf("\t.quad\t%lld\n", total_syms_to_write);
151 
152     putchar('\n');
153 
154     // 循环写入符号名称的下标索引
155     printf(".global kallsyms_names_index\n");
156     printf(".align 8\n");
157     printf("kallsyms_names_index:\n");
158     uint64_t position = 0;
159     last_vaddr = 0;
160     for (uint64_t i = 0; i < entry_count; ++i)
161     {
162         // 判断是否为text段的符号
163         if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
164             continue;
165 
166         if (symbol_table[i].vaddr == last_vaddr)
167             continue;
168 
169         // 输出符号名称的偏移量
170         printf("\t.quad\t%lld\n", position);
171         position += symbol_table[i].symbol_length;
172         last_vaddr = symbol_table[i].vaddr;
173     }
174 
175     putchar('\n');
176 
177     // 输出符号名
178     printf(".global kallsyms_names\n");
179     printf(".align 8\n");
180     printf("kallsyms_names:\n");
181 
182     last_vaddr = 0;
183     for (uint64_t i = 0; i < entry_count; ++i)
184     {
185         // 判断是否为text段的符号
186         if (!symbol_to_write(symbol_table[i].vaddr, text_vaddr, etext_vaddr))
187             continue;
188 
189         if (symbol_table[i].vaddr == last_vaddr)
190             continue;
191 
192         // 输出符号名称
193         printf("\t.asciz\t\"%s\"\n", symbol_table[i].symbol);
194 
195         last_vaddr = symbol_table[i].vaddr;
196     }
197 
198     putchar('\n');
199 
200 }
201 int main(int argc, char **argv)
202 {
203     read_map(stdin);
204 
205     generate_result();
206 }