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 }