kallsyms.c 5.7 KB

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