一般来说,内核只会导出由EXPORT_PARM宏指定的符号给模块使用。为了使debugger提供更好的调试功能,需要使用kallsyms工具为内核生成__kallsyms段数据,该段描述所有不处在堆栈上的内核符号。这样debugger就能更好地解析内核符号,而不仅仅是内核指定导出的符号。
刚刚编译好的内核是不带__kallsyms段的。要在内核中加入__kallsyms段,man给出了一个方法,这个方法实际上也是insmod程序里使用的方法。
在insmod里,我们已经看到,在内存中生成所有其他段之后,insmod调用obj_kallsyms生成__kallsyms段,这时这个段里的符号,还没经过重定位。但是,大小已经确定,可以使obj_load_size的计算正确。然后在obj_relocate进行重定位后,重新调用obj_kallsyms,生成内容正确的__kallsyms段。
在内核里引入__kallsyms段,也是类似的方法,以下就是man里的提示。
1) 对编译好的内核,运行kallsyms程序,保存输出文件(kallsyms只输出到stdout,见下面的源码,因此要重定向输出到指定文件)。
2) 将输出文件和内核链接,对生成文件运行kallsyms,保存输出文件。
3) 重复第2步。
4) 链接内核与第3步的输出文件,即完成。
这里比insmod里的调用多了一次。是因为第2步链接产生的文件,由于添加了__kallsyms段(insmod在调用前就添加了这个段,见add_kallsyms函数,所以,运行obj_kallsyms直接产生了第2步的效果),所以文件大小、段的数目、符号的位置都变了,运行kallsyms生成的输出文件里的内容可能还不是正确的。但是__kallsyms的大小已经固定了。在运行一次,就对了。
来看代码。作为insmod工具组的一员,它的开始和其他工具类似。首先是./modutils-2.4.0/insmod/insmod.c。
1970 #ifdef COMBINE_kallsyms
1971 { "kallsyms", &kallsyms_main },
1972 #endif
接着,在./modutils-2.4.0/insmod/kallsyms.c里。
62 #if defined(COMMON_3264) && defined(ONLY_32)
63 #define KALLSYMS_MAIN kallsyms_main_32 /* 32 bit version */
64 #elif defined(COMMON_3264) && defined(ONLY_64)
65 #define KALLSYMS_MAIN kallsyms_main_64 /* 64 bit version */
66 #else
67 #define KALLSYMS_MAIN kallsyms_main /* Not common code */
68 #endif
KALLSYMS_MAIN在同一文件里。
70 int KALLSYMS_MAIN (int argc, char **argv)
71 {
72 struct option long_opts[] = {
73 {"version", 0, 0, 'V'},
74 {"help", 0, 0, 'h'},
75 {0, 0, 0, 0}
76 };
77 char *filename = NULL;
78 int fp;
79 struct obj_file *fin, *fout;
80 int i, c;
81
82 error_file = "kallsyms";
83
84 /* To handle repeated calls from combined modprobe */
85 errors = optind = 0;
86
87 /* Process the command line. */
88 while ((c = getopt_long(argc, argv, "Vh",
89 &long_opts[0], NULL)) != EOF)
90 switch (c) {
91 case 'V':
92 fputs("kallsyms version " MODUTILS_VERSION "/n", stderr);
93 return(0);
94 case 'h': /* Print the usage message. */
95 kallsyms_usage();
96 return(0);
97 default:
98 kallsyms_usage();
99 return(1);
100 }
101
102 if (optind != argc-1) {
103 kallsyms_usage();
104 return(1);
105 }
106
107 filename = argv[optind++];
108 error_file = filename;
109 if ((fp = gzf_open(filename, O_RDONLY)) < 0) {
110 error("%s: %m", filename);
111 return 1;
112 }
113
114 if ((fin = obj_load(fp, ET_EXEC, filename)) == NULL) {
115 gzf_close(fp);
116 return 1;
117 }
118 gzf_close(fp);
119
120 error_file = "kallsyms";
121 if (obj_kallsyms(fin, &fout))
122 return 1;
123
124 /* Write the extracted data */
125 fwrite(&fout->header, sizeof(fout->header), 1, stdout);
126 for (i = 0; i < fout->header.e_shnum; ++i)
127 fwrite(&fout->sections[i]->header, fout->header.e_shentsize, 1, stdout);
128 for (i = 0; i < fout->header.e_shnum; ++i) {
129 if (fout->sections[i]->header.sh_size) {
130 fseek(stdout, fout->sections[i]->header.sh_offset, SEEK_SET);
131 fwrite(fout->sections[i]->contents,
132 fout->sections[i]->header.sh_size, 1, stdout);
133 }
134 }
135
136 return 0;
137 }
有了前面分析工具的基础,这里没有什么可多说的。注意109行,这个工具不象insmod这么强悍,它需要提供路径名,而不能只是模块名。
另外,在121行的obj_kallsyms里调用arch_new_section,通过malloc为fout分配资源。这里fout是局部变量,但在上面的代码中并没有对应的free调用。在insmod中的add_kallsyms在调用了obj_kallsyms之后,则为生成的obj_file对象调用了obj_free进行内存释放。所以,这里可能存在内存泄漏。
还有注意,kallsyms固定输出到stdout,因此,如果要保存输出文件,需要重定向输出。