【转】linux内核kallsyms机制分析

  1. 一、前言
  2. Linux内核是一个整体结构,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作一个模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。
  3. 因此,内核也有一个module结构,叫做kernel_module。另外,从kernel_module开始,所有已安装模块的module结构都链在一起成为一条链,内核中的全局变量module_list就指向这条链:
  4. struct module *module_list = &kernel_module;
  5. 一般来说,内核只会导出由EXPORT_PARM宏指定的符号给模块使用。为了使debugger提供更好的调试功能,需要使用kallsyms工具为内核生成__kallsyms段数据,该段描述所有不处在堆栈上的内核符号。这样debugger就能更好地解析内核符号,而不仅仅是内核指定导出的符号。
  6. 二、简介
  7. 在v2.6.0 的内核中,为了更好地调试内核,引入新的功能kallsyms.kallsyms把内核用到的所有函数地址和名称连接进内核文件,当内核启动后,同时加载到内存中.当发生oops,例如在内核中访问空地址时,内核就会解析eip位于哪个函数中,并打印出形如:
  8. EIP is at cleanup_module+0xb/0x1d [client]的信息,
  9. 调用栈也用可读的方式显示出来.
  10. Call Trace:
  11. [<c013096d>] sys_delete_module+0x191/0x1ce
  12. [<c02dd30a>] do_page_fault+0x189/0x51d
  13. [<c0102bc1>] syscall_call+0x7/0xb
  14. 当然功能不仅仅于此,还可以查找某个函数例如的sys_fork的地址,然后hook它,kprobe就是这么干的。在v2.6.20 中,还可以包含所有符号的地址,应此功能更强大,就相当于内核中有了System.map了,此时查找sys_call_table的地址易如反掌。
  15. 三.sym的生成
  16. 1.形成过程
  17. Linux内核符号表/proc/kallsyms的形成过程
  18. (1)./scripts/kallsyms.c负责生成System.map
  19. (2)./kernel/kallsyms.c负责生成/proc/kallsyms
  20. (3)./scripts/kallsyms.c解析vmlinux(.tmp_vmlinux)生成kallsyms.S(.tmp_kallsyms.S),然后内核编译过程中将kallsyms.S(内核符号表)编入内核镜像uImage.内核启动后./kernel/kallsyms.c解析uImage形成/proc/kallsyms
  21. 2.内核配置
  22. 在2.6 内核中,为了更好地调试内核,引入了kallsyms。kallsyms抽取了内核用到的所有函数地址(全局的、静态的)和非栈数据变量地址,生成一个数据块,作为只读数据链接进kernel image,相当于内核中存了一个System.map。需要配置CONFIG_KALLSYMS。
  23. .config
  24. CONFIG_KALLSYMS=y 符号表中包含所有的函数
  25. CONFIG_KALLSYMS_ALL=y 符号表中包括所有的变量(包括没有用EXPORT_SYMBOL导出的变量)
  26. CONFIG_KALLSYMS_EXTRA_PASS=y
  27. make menuconfig
  28. General setup --->
  29. [*] Configure standard kernel features (for small systems) --->
  30. [*] Load all symbols for debugging/ksymoops (选中此项,才有/proc/kallsyms接口文件, oops问题,选中此选项即可,子选项可以忽略)
  31. [*] Include all symbols in kallsyms
  32. [*] Do an extra kallsyms pass
  33. 3.编译生成列表
  34. 内核编译的最后阶段,make会执行
  35. nm -n vmlinux|scripts/kallsyms
  36. nm -n vmlinux生成所有的内核符号,并按地址排序,形如
  37. ......
  38. c0100000 T startup_32
  39. c0100000 A _text
  40. c01000c6 t checkCPUtype
  41. c0100147 t is486
  42. c010014e t is386
  43. c010019f t L6
  44. c01001a1 t check_x87
  45. c01001ca t setup_idt
  46. c01001e7 t rp_sidt
  47. c01001f4 t ignore_int
  48. c0100228 T calibrate_delay
  49. c0100228 T stext
  50. c0100228 T _stext
  51. c010036b t rest_init
  52. c0100410 t do_pre_smp_initcalls
  53. c0100415 t run_init_process
  54. ......
  55. v2.6.0 的行数是2.5万左右
  56. 4.处理列表
  57. scripts/kallsyms则处理这个列表,并生成连接所需的S文件kallsyms.S。在linux3.12中使用/scripts/kallsyms处理此列表。v2.6.0中形如:
  58. #include <asm/types.h>
  59. #if BITS_PER_LONG == 64
  60. #define PTR .quad
  61. #define ALGN .align 8
  62. #else
  63. #define PTR .long
  64. #define ALGN .align 4
  65. #endif
  66. .data
  67. .globl kallsyms_addresses
  68. ALGN
  69. kallsyms_addresses:
  70. PTR 0xc0100228
  71. PTR 0xc010036b
  72. PTR 0xc0100410
  73. PTR 0xc0100415
  74. PTR 0xc010043c
  75. PTR 0xc0100614
  76. ...
  77. .globl kallsyms_num_syms
  78. ALGN
  79. kallsyms_num_syms:
  80. PTR 11228
  81. .globl kallsyms_names
  82. ALGN
  83. kallsyms_names:
  84. .byte 0x00
  85. .asciz "calibrate_delay"
  86. .byte 0x00
  87. .asciz "stext"
  88. .byte 0x00
  89. .asciz "_stext"
  90. ...
  91. 生成的符号表部分如下:
  92. /*
  93. ......
  94. c1618b03 t __raw_write_unlock_irq.constprop.29
  95. c1618b19 T panic
  96. c1618c91 T printk
  97. ......
  98. c16a4d6b r __func__.17404
  99. c16a4d78 R kallsyms_addresses
  100. c16ef0dc R kallsyms_num_syms
  101. c16ef0e0 R kallsyms_names
  102. c17d5468 R kallsyms_markers
  103. c17d590c R kallsyms_token_table
  104. c17d5c78 R kallsyms_token_index
  105. ......
  106. */
  107. 5.生成的符号数组解析
  108. 1)kallsyms_addresses数组包含所有内核函数的地址(经过排序的),v2.6.0 中相同的地址在kallsyms_addresses中只允许出现一次,到后面的版本例如相同的地址可以出现多次,这样就允许同地址函数名的出现。
  109. 例如:
  110. kallsyms_addresses:
  111. PTR 0xc0100228
  112. PTR 0xc0100228
  113. PTR 0xc0100228
  114. PTR 0xc010036b
  115. 当查找某个地址时所在的函数时,v2.6.0 采用的是线性法,从头到尾地找,很低效,后来改成了折半查找,效率好多了。
  116. 2)kallsyms_num_syms是函数个数
  117. 3)kallsyms_names是函数名数组。
  118. <1>以前的算法是:函数名数组组成的一个大串,这个大串是有许多小串组成,格式是:
  119. .byte len
  120. .asciz 压缩串
  121. 格式例如:
  122. kallsyms_names:
  123. .byte 0x00
  124. .asciz "calibrate_delay"
  125. .byte 0x00
  126. .asciz "stext"
  127. .byte 0x00
  128. .asciz "_stext"
  129. .byte 0x00
  130. .asciz "rest_init"
  131. len代表本函数名和前一函数名相同前缀的大小,例如
  132. .byte 0x00
  133. .asciz "early_param_test"
  134. .byte 0x06
  135. .asciz "setup_test"
  136. .byte 0x06,说明串setup_test和串early_parm_test有着相同的前缀,长为6,即early_,所有setup_test最终解压后的函数名为early_setup_test.由于没有其他的辅助手段,函数名的解析过程也很低效,从头一直解析到该函数位置为止。
  137. <2>在后来的版本中,算法有了改善,使用了偏移索引和高频字符串压缩。也就是现在常用的算法。格式是:
  138. .byte len ascii字符 ascii字符...(len个ascii字符)
  139. 先建立token的概念,token就是所有函数名中,出现频率非常高的那些字符串.由于标识符命名
  140. 规则的限制,有许多ascii字符是未用到的,那么,可以用这些字符去替代这些高频串。例如下面的例子:
  141. 字符值 字符代表的串
  142. 190 .asciz "t.text.lock."
  143. 191 .asciz "text.lock."
  144. 192 .asciz "t.lock."
  145. 193 .asciz "lock."
  146. 210 .asciz "tex"
  147. 229 .asciz "t."
  148. 239 .asciz "loc"
  149. 249 .asciz "oc"
  150. 250 .asciz "te"
  151. 例如串.byte 0x03, 0xbe, 0xbc, 0x71的解析
  152. 串长3,
  153. 0xbe(190) .asciz "t.text.lock."
  154. 0xbc(189) .asciz "ir"
  155. 0x71(113) .asciz "q"
  156. 所以该串解析后的值是 t.text.lock.irq,注意实际的串值是.text.lock.irq,前面的t是类型,这是新版本加入的功能,将类型字符放在符号前。
  157. .byte 0x02, 0x08, 0xc2
  158. 串长2,
  159. 0x08,8 .asciz "Tide_"
  160. 0xc2,194 .asciz "init"
  161. 所以该串解析后的值是 Tide_init,即ide_init
  162. 4)为了解析而设置了数据结构kallsyms_token_table和kallsyms_token_index.结构kallsyms_token_table记录每个ascii字符的替代串,kallsyms_token_index记录每个ascii字符的替代串在kallsyms_token_table中的偏移.
  163. 5)而数据结构的改变是,把函数名每256个分一组,用一个数组kallsyms_markers记录这些组在
  164. kallsyms_names中的偏移,这样查找就方便多了,不必从头来。
  165. 四、符号表的查找
  166. 通过对以上格式的了解,我们就可以自己编写程序找到内核中的符号表,进而找到每个内核符号的地址。
  167. 1.首先找到kallsyms_addresses数组
  168. //首先获得kallsyms_addresses数组中的printk的地址
  169. printk_addr_addr = prink_addr_addr();
  170. static void * prink_addr_addr(void){
  171. unsigned int i = 0xc0000000;
  172. int k = 0;
  173. //kallsyms_addresses数组中都是保存的是内核符号的地址,所以这里查找的是地址下的保存的函数地址是否为printk的函数地址
  174. for(;i < 0xf0000000; i += 4){
  175. if(*((unsigned int *)i) == (unsigned int)printk){
  176. //判断该地址前边都是有效的kallsyms_addresses数组函数地址
  177. if(isfunaddr(*((unsigned int *)(i-4)))&&isfunaddr(*((unsigned int *)(i-8)))){
  178. if(!k)
  179. return (void *)i;
  180. else
  181. ++k;
  182. }
  183. }
  184. }
  185. return NULL;
  186. }
  187. //只要该函数符号在kallsyms_addresses数组中,通过%Ps打印结构一定是...+0x0/...
  188. static int isfunaddr(unsigned int addr){
  189. char buff[200] = {0};
  190. int i = 0;
  191. memset(buff,0,sizeof(buff));
  192. //get the %pS print format;
  193. sprintf(buff,"%pS",(void *)addr);
  194. //if is a function addr ,it‘s %pS print format must be: ...+0x0/...
  195. if((buff[0]==‘0‘)&&(buff[1]==‘x‘))
  196. return 0;
  197. while(buff[i++]){
  198. if((buff[i] == ‘+‘)&&(buff[i+1]==‘0‘)&&(buff[i+2]==‘x‘)&&(buff[i+3]==‘0‘)&&(buff[i+4]==‘/‘))
  199. return 1;
  200. }
  201. return 0;
  202. }
  203. //通过printk的地址查找到kallsyms_addresses的结束地址kallsyms_addresses数组结尾处
  204. funaddr_endaddr = find_funadd_endaddr(printk_addr_addr);
  205. //一直循环查找最后一个符号不为...+0x0/...结构,即找到了
  206. static void * find_funadd_endaddr(void * in){
  207. unsigned int * p = in;
  208. for(;isfunaddr(*p); ++p);
  209. return (void *)(p-1);
  210. }
  211. //kallsyms_addresses数组尾地址+4就是符号个数kallsyms_num的地址,就能得到符号的个数
  212. kallsyms_num = *((unsigned int *)funaddr_endaddr + 1);
  213. //根据符号个数和kallsyms_addresses数组尾地址就能得到kallsyms_addresses数组的首地址
  214. kallsyms_addr = (unsigned int *)funaddr_endaddr - kallsyms_num + 1;
  215. 2.找到kallsyms_name地址。
  216. //kallsyms_num地址的下一项就是kallsyms_name地址
  217. kallsyms_name = (void *)((unsigned int *)funaddr_endaddr + 2);
  218. 3.kallsyms_name数组的下一项是kallsyms_mark数组,但是他们的地址不是连续的。
  219. //把函数名每256个分一组,用一个数组kallsyms_markers记录这些组在kallsyms_names中的偏移
  220. kallsyms_mark = get_marker_addr(kallsyms_name );
  221. //因为数组kallsyms_name中的格式是:.byte len ascii码...(len个)
  222. static void * get_marker_addr(void * name){
  223. int i = 0;
  224. unsigned char * base = (char *)name;
  225. //base[0]存的是.byte替代串的ascii码,base[1]存的是ascii码个数
  226. //所以依次跳过kallsyms_num个name就找到了kallsyms_mark数组的地址
  227. for(; i < kallsyms_num ; ++i){
  228. base += (base[0] +1);
  229. }
  230. //4字节对齐
  231. if((unsigned int)base%4){
  232. base += (4-(unsigned int)base%4);
  233. }
  234. return (void *)base;
  235. }
  236. 4.计算kallsyms_mark数组表项个数
  237. //根据符号个数计算kallsyms_mark数组的个数,符号以256个为一组
  238. mark_num = kallsyms_num%256 ? kallsyms_num/256 + 1:kallsyms_num/256;
  239. 5.获取结构kallsyms_token_table地址
  240. //结构kallsyms_token_table记录每个ascii字符的替代串,位于kallsyms_mark数组之后
  241. kallsyms_token_tab = (void *)((unsigned int *)kallsyms_mark + mark_num);
  242. 6.获得kallsyms_token_indx地址
  243. //kallsyms_token_index记录每个ascii字符的替代串在kallsyms_token_table中的偏移.
  244. kallsyms_token_indx = get_index_addr(kallsyms_token_tab);
  245. //因为kallsyms_token_table里存放的都是字符类型,依次跳过合法的字符类型之后的地址就是kallsyms_token_indx地址
  246. static void * get_index_addr(void * base){
  247. char * p = (char *)base;
  248. for(;is_funname_char(*p);p++){
  249. for(;is_funname_char(*p); p++);
  250. }
  251. //align 4 bytes
  252. if((unsigned int)p%4){
  253. p += (4 -(unsigned int)p%4);
  254. }
  255. return (void *)p;
  256. }
  257. //检查是否为函数符号名的合法字符
  258. static int is_funname_char( char p){
  259. if( ((p >= ‘a‘)&&(p <=‘z‘)) || ((p >= ‘A‘)&&(p <=‘Z‘)) || (p == ‘_‘) || ( (p >=‘0‘)&&(p <= ‘9‘) ) || (p == ‘.‘))
  260. return 1;
  261. else
  262. return 0;
  263. }
  264. 7.查找函数名地址
  265. static void * name_2_addr(char * name){
  266. char namebuff[200];
  267. unsigned int i = 0;
  268. unsigned int len = 0;
  269. //符号名称数组
  270. unsigned char * name_tab = (unsigned char *)kallsyms_name;
  271. unsigned int mod_addr = 0;
  272. char * buff_ptr = namebuff;
  273. //遍历所有符号
  274. for(; i < kallsyms_num; ++i){
  275. memset(namebuff,0,200);
  276. buff_ptr = namebuff;
  277. len = *name_tab;//符号名称长度
  278. name_tab++;//符号名对应的ascii码
  279. while(len){
  280. //根据符号名对应的ascii码得到符号名
  281. buff_ptr = cp_from_token_tab(buff_ptr,
  282. //得到该ascii码对应的字符串在kallsyms_token_table中的偏移
  283. ((unsigned short *)kallsyms_token_indx)[*name_tab]);
  284. name_tab++;
  285. len--;
  286. }
  287. //检查符号名是否一致,其中第一个字符为符号类型,忽略掉
  288. if(my_strcmp(name,namebuff+1)==0){
  289. //若相等返回该符号地址
  290. return (void *)((unsigned int *)kallsyms_addr)[i];
  291. }
  292. }
  293. }
  294. static char * cp_from_token_tab(char * buff,unsigned short off)
  295. {
  296. int len = 0;
  297. //从kallsyms_token_tab数组的偏移处取得字符串,字符串以“\0”隔开
  298. char * token_tab = &(((char *)kallsyms_token_tab)[off]);
  299. for(;token_tab[len]; ++len){
  300. *buff = token_tab[len];
  301. buff++;
  302. };
  303. return buff;
  304. }
  305. 五、符号解析
  306. //v2.6.20 当发生oops时,
  307. fastcall void __kprobes do_page_fault(struct pt_regs *regs,unsigned long error_code)
  308. {
  309. ...
  310. die("Oops", regs, error_code);
  311. ...
  312. }
  313. void die(const char * str, struct pt_regs * regs, long err)
  314. {
  315. ...
  316. print_symbol("%s", regs->eip);//解析
  317. ...
  318. }
  319. static inline void print_symbol(const char *fmt, unsigned long addr)
  320. {
  321. __check_printsym_format(fmt, "");
  322. __print_symbol(fmt, (unsigned long)__builtin_extract_return_addr((void *)addr));
  323. }
  324. void __print_symbol(const char *fmt, unsigned long address)
  325. {
  326. char buffer[KSYM_SYMBOL_LEN];
  327. //取得该地址的符号信息,存入buffer中
  328. sprint_symbol(buffer, address);
  329. //将buffer中的符号信息打印出来
  330. printk(fmt, buffer);
  331. }
  332. int sprint_symbol(char *buffer, unsigned long address)
  333. {
  334. return __sprint_symbol(buffer, address, 0, 1);
  335. }
  336. static int __sprint_symbol(char *buffer, unsigned long address,int symbol_offset, int add_offset)
  337. {
  338. char *modname;
  339. const char *name;
  340. unsigned long offset, size;
  341. int len;
  342. address += symbol_offset;//符号偏移是0
  343. //解析地址,返回函数起始地址,大小,偏移,函数名
  344. name = kallsyms_lookup(address, &size, &offset, &modname, buffer);
  345. if (!name)
  346. return sprintf(buffer, "0x%lx", address);
  347. //先拷贝该地址对应的函数名给buffer[]
  348. if (name != buffer)
  349. strcpy(buffer, name);
  350. len = strlen(buffer);
  351. offset -= symbol_offset;
  352. //将该函数符号的偏移地址和大小拷贝给buffer[]
  353. if (add_offset)
  354. len += sprintf(buffer + len, "+%#lx/%#lx", offset, size);
  355. //若属于模块,则拷贝模块名给buffer[]
  356. if (modname)
  357. len += sprintf(buffer + len, " [%s]", modname);
  358. return len;
  359. }
  360. const char *kallsyms_lookup(unsigned long addr,unsigned long *symbolsize,unsigned long *offset,char **modname, char *namebuf)
  361. {
  362. namebuf[KSYM_NAME_LEN - 1] = 0;
  363. namebuf[0] = 0;
  364. //检查是否为内核符号
  365. if (is_ksym_addr(addr)) {
  366. unsigned long pos;
  367. //取得符号的大小和偏移,返回符号在kallsyms_addresses数组中的索引值
  368. pos = get_symbol_pos(addr, symbolsize, offset);
  369. //解析符号,获得符号名称存入namebuf
  370. kallsyms_expand_symbol(get_symbol_offset(pos),namebuf, KSYM_NAME_LEN);
  371. if (modname)
  372. *modname = NULL;
  373. return namebuf;
  374. }
  375. //若不是内核符号,则扫描内核中已安装的模块中的符号
  376. return module_address_lookup(addr, symbolsize, offset, modname,namebuf);
  377. }
  378. static unsigned long get_symbol_pos(unsigned long addr,unsigned long *symbolsize,unsigned long *offset)
  379. {
  380. unsigned long symbol_start = 0, symbol_end = 0;
  381. unsigned long i, low, high, mid;
  382. /* This kernel should never had been booted. */
  383. BUG_ON(!kallsyms_addresses);
  384. low = 0;
  385. //kallsyms_num_syms是内核函数个数
  386. high = kallsyms_num_syms;
  387. //折半查找,kallsyms_addresses数组包含所有内核函数的地址(经过排序的)
  388. while (high - low > 1) {
  389. mid = low + (high - low) / 2;
  390. if (kallsyms_addresses[mid] <= addr)
  391. low = mid;
  392. else
  393. high = mid;
  394. }
  395. //找到第一个对齐的符号,即相同地址中的第一个。v2.6.0中相同的地址在kallsyms_addresses中只允许出现一次,到后面的版本例如相同的地址可以出现多次,这样就允许同地址函数名的出现。
  396. while (low && kallsyms_addresses[low-1] == kallsyms_addresses[low])
  397. --low;
  398. //获得函数地址小于addr最接近的一个内核函数的地址作为符号的起始地址
  399. symbol_start = kallsyms_addresses[low];
  400. //找到下一个不同的地址
  401. for (i = low + 1; i < kallsyms_num_syms; i++) {
  402. if (kallsyms_addresses[i] > symbol_start) {
  403. symbol_end = kallsyms_addresses[i];
  404. break;
  405. }
  406. }
  407. /* If we found no next symbol, we use the end of the section. */
  408. if (!symbol_end) {
  409. if (is_kernel_inittext(addr))
  410. symbol_end = (unsigned long)_einittext;
  411. else if (all_var)
  412. symbol_end = (unsigned long)_end;
  413. else
  414. symbol_end = (unsigned long)_etext;
  415. }
  416. //获得符号的大小
  417. if (symbolsize)
  418. *symbolsize = symbol_end - symbol_start;
  419. //符号的偏移量
  420. if (offset)
  421. *offset = addr - symbol_start;
  422. //返回在kallsyms_addresses数组中的索引值
  423. return low;
  424. }
  425. //返回符号在kallsyms_names中的偏移
  426. static unsigned int get_symbol_offset(unsigned long pos)
  427. {
  428. const u8 *name;
  429. int i;
  430. //找到该组在kallsyms_names中的偏移。pos>>8即是pos/256得到kallsyms_markers的索引,kallsyms_markers数组中存储的是每256个分一组的组在kallsyms_names的偏移。
  431. //kallsyms_names是函数名组成的一个大串,这个大串是有许多小串组成,格式是:
  432. //.byte len ascii码 ascii码...(len个)
  433. name = &kallsyms_names[ kallsyms_markers[pos>>8] ];
  434. //依次跳过(pos&0xFF)个偏移即是当前符号的偏移地址处,(*name) + 1存的是len
  435. for(i = 0; i < (pos&0xFF); i++)
  436. name = name + (*name) + 1;//
  437. return name - kallsyms_names;//返回该符号在kallsyms_names组中偏移
  438. }
  439. static unsigned int kallsyms_expand_symbol(unsigned int off, char *result)
  440. {
  441. int len, skipped_first = 0;
  442. const u8 *tptr, *data;
  443. /* get the compressed symbol length from the first symbol byte */
  444. data = &kallsyms_names[off];//取该sym的首地址
  445. len = *data;//取sym压缩后的长度
  446. data++;//指向压缩串
  447. //指向下一个压缩串偏移
  448. off += len + 1;
  449. //为了解析而设置了数据结构kallsyms_token_table和kallsyms_token_indexkallsyms_token_table记录每个ascii字符的替代串,kallsyms_token_index记录每个ascii字符的替代串在kallsyms_token_table中的偏移.
  450. while(len) {
  451. //对于*data指向的字符,在token_index查找该字符所代表的解压串偏移,并从token_table中找到该解压串
  452. tptr = &kallsyms_token_table[ kallsyms_token_index[*data] ];
  453. data++;
  454. len--;
  455. while (*tptr) {
  456. if(skipped_first) {//跳过类型字符,例如t,T
  457. *result = *tptr;//拷贝解压串
  458. result++;
  459. } else
  460. skipped_first = 1;
  461. tptr++;
  462. }
  463. }
  464. *result = ‘\0‘;
  465. //返回下一个压缩串偏移
  466. return off;
  467. }
  468. const char *module_address_lookup(unsigned long addr,unsigned long *size,unsigned long *offset,char **modname,char *namebuf)
  469. {
  470. struct module *mod;
  471. const char *ret = NULL;
  472. preempt_disable();
  473. //遍历内核中的所有模块
  474. list_for_each_entry_rcu(mod, &modules, list) {
  475. if (mod->state == MODULE_STATE_UNFORMED)
  476. continue;
  477. //addr是否在模块的init部分或者core部分
  478. if (within_module_init(addr, mod) ||within_module_core(addr, mod)) {
  479. if (modname)
  480. *modname = mod->name;//取得模块名
  481. ret = get_ksymbol(mod, addr, size, offset);
  482. break;
  483. }
  484. }
  485. /* Make a copy in here where it‘s safe */
  486. if (ret) {
  487. strncpy(namebuf, ret, KSYM_NAME_LEN - 1);
  488. ret = namebuf;
  489. }
  490. preempt_enable();
  491. return ret;
  492. }
  493. static const char *get_ksymbol(struct module *mod,unsigned long addr,unsigned long *size,unsigned long *offset)
  494. {
  495. unsigned int i, best = 0;
  496. unsigned long nextval;
  497. /* At worse, next value is at end of module */
  498. if (within_module_init(addr, mod))
  499. nextval = (unsigned long)mod->module_init+mod->init_text_size;
  500. else
  501. nextval = (unsigned long)mod->module_core+mod->core_text_size;
  502. /* Scan for closest preceding symbol, and next symbol. (ELF
  503. starts real symbols at 1). */
  504. //遍历模块的符号
  505. for (i = 1; i < mod->num_symtab; i++) {
  506. if (mod->symtab[i].st_shndx == SHN_UNDEF)//跳过未定义的符号
  507. continue;
  508. /* We ignore unnamed symbols: they‘re uninformative
  509. * and inserted at a whim. */
  510. if (mod->symtab[i].st_value <= addr
  511. && mod->symtab[i].st_value > mod->symtab[best].st_value
  512. && *(mod->strtab + mod->symtab[i].st_name) != ‘\0‘
  513. && !is_arm_mapping_symbol(mod->strtab + mod->symtab[i].st_name))
  514. best = i;
  515. if (mod->symtab[i].st_value > addr
  516. && mod->symtab[i].st_value < nextval
  517. && *(mod->strtab + mod->symtab[i].st_name) != ‘\0‘
  518. && !is_arm_mapping_symbol(mod->strtab + mod->symtab[i].st_name))
  519. nextval = mod->symtab[i].st_value;
  520. }
  521. if (!best)
  522. return NULL;
  523. if (size)
  524. *size = nextval - mod->symtab[best].st_value;
  525. if (offset)
  526. *offset = addr - mod->symtab[best].st_value;
  527. return mod->strtab + mod->symtab[best].st_name;
  528. }
  529. 六、符号属性
  530. 若符号在内核中是全局性的,则属性为大写字母,如T、U等。
  531. b:符号在未初始化数据区(BSS)
  532. c:普通符号,是未初始化区域
  533. d:符号在初始化数据区
  534. g:符号针对小object,在初始化数据区
  535. i:非直接引用其他符号的符号
  536. n:调试符号
  537. r:符号在只读数据区
  538. s:符号针对小object,在未初始化数据区
  539. t:符号在代码段
  540. u:符号未定义
时间: 2024-10-20 08:20:26

【转】linux内核kallsyms机制分析的相关文章

Linux内核OOM机制分析

一 应用场景描述 线上一台mongos出现OOM情况,于是花点时间想要详细了解Linux内核的OOM机制原理,便于以后再作分析 $ sudo grep mongos /var/log/messages  Apr 10 15:35:38 localhost sz[32066]: [xxxx] check_mongos.sh/ZMODEM: 211 Bytes, 229 BPS Apr 23 14:50:18 localhost sz[5794]: [xxxxx] mongos/ZMODEM: 29

Linux内核NAPI机制分析

转自:http://blog.chinaunix.net/uid-17150-id-2824051.html 简介:NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据.随着网络的接收速度的增加,NIC 触发的中断能做到不断减少,目前 NAPI 技术已经在网卡驱动层和网络层得到了广泛的应用,驱动层次上已经有 E1000 系列网卡,RTL8139 系列网卡,3c50X 系

Linux内核OOM机制的详细分析(转)

Linux 内核 有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了 防止内存耗尽而内核会把该进程杀掉.典型的情况是:某天一台机器突然ssh远程登录不了,但能ping通,说明不是网络的故障,原因是sshd进程被 OOM killer杀掉了(多次遇到这样的假死状况).重启机器后查看系统日志/var/log/messages会发现 Out of Memory: Kill process 1865(sshd)

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入

Linux内核同步机制

http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环境来提高操作系统效率.首先,看看我们最熟悉的两种机制——信号量.锁. 一.信号量 首先还是看看内核中是怎么实现的,内核中用struct semaphore数据结构表示信号量(<linux/semphone.h>中): [cpp] view plaincopyprint? struct semaph

Linux内核源代码情景分析-访问权限与文件安全性

在Linux内核源代码情景分析-从路径名到目标节点,一文中path_walk代码中,err = permission(inode, MAY_EXEC)当前进程是否可以访问这个节点,代码如下: int permission(struct inode * inode,int mask) { if (inode->i_op && inode->i_op->permission) { int retval; lock_kernel(); retval = inode->i_

linux RCU锁机制分析

openVswitch(OVS)源代码之linux RCU锁机制分析 分类: linux内核  |  标签: 云计算,openVswitch,linux内核,RCU锁机制  |  作者: yuzhihui_no1 相关  |  发布日期 : 2014-10-19  |  热度 : 1044° 前言 本来想继续顺着数据包的处理流程分析upcall调用的,但是发现在分析upcall调用时必须先了解linux中内核和用户空间通信接口Netlink机制,所以就一直耽搁了对upcall的分析.如果对ope

Linux内核源代码情景分析-系统初始化

我们跳过boot,setup,直接来到head代码,内核映像的起点是stext,也是_stext,引导和解压缩以后的整个映像放在内存从0x100000即1MB开始的区间.CPU执行内核映像的入口startup_32就在内核映像开头的地方,因此其物理地址也是0x100000. 然而,在正常运行时整个内核映像都应该在系统空间中,系统空间的虚拟地址与物理地址间有个固定的位移,这就是0xC0000000,即3GB.所以,在连接内核映像时已经在所有的符号地址加了一个偏移量0xC0000000,这样star