2016-07-24 15:59:41
-
一、前言
-
Linux内核是一个整体结构,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作一个模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。
-
-
因此,内核也有一个module结构,叫做kernel_module。另外,从kernel_module开始,所有已安装模块的module结构都链在一起成为一条链,内核中的全局变量module_list就指向这条链:
-
struct module *module_list = &kernel_module;
-
-
一般来说,内核只会导出由EXPORT_PARM宏指定的符号给模块使用。为了使debugger提供更好的调试功能,需要使用kallsyms工具为内核生成__kallsyms段数据,该段描述所有不处在堆栈上的内核符号。这样debugger就能更好地解析内核符号,而不仅仅是内核指定导出的符号。
-
-
二、简介
-
在v2.6.0 的内核中,为了更好地调试内核,引入新的功能kallsyms.kallsyms把内核用到的所有函数地址和名称连接进内核文件,当内核启动后,同时加载到内存中.当发生oops,例如在内核中访问空地址时,内核就会解析eip位于哪个函数中,并打印出形如:
-
EIP is at cleanup_module+0xb/0x1d [client]的信息,
-
调用栈也用可读的方式显示出来.
-
Call Trace:
-
[<c013096d>] sys_delete_module+0x191/0x1ce
-
[<c02dd30a>] do_page_fault+0x189/0x51d
-
[<c0102bc1>] syscall_call+0x7/0xb
-
-
当然功能不仅仅于此,还可以查找某个函数例如的sys_fork的地址,然后hook它,kprobe就是这么干的。在v2.6.20 中,还可以包含所有符号的地址,应此功能更强大,就相当于内核中有了System.map了,此时查找sys_call_table的地址易如反掌。
-
-
三.sym的生成
-
1.形成过程
-
Linux内核符号表/proc/kallsyms的形成过程
-
(1)./scripts/kallsyms.c负责生成System.map
-
(2)./kernel/kallsyms.c负责生成/proc/kallsyms
-
(3)./scripts/kallsyms.c解析vmlinux(.tmp_vmlinux)生成kallsyms.S(.tmp_kallsyms.S),然后内核编译过程中将kallsyms.S(内核符号表)编入内核镜像uImage.内核启动后./kernel/kallsyms.c解析uImage形成/proc/kallsyms
-
-
-
2.内核配置
-
在2.6 内核中,为了更好地调试内核,引入了kallsyms。kallsyms抽取了内核用到的所有函数地址(全局的、静态的)和非栈数据变量地址,生成一个数据块,作为只读数据链接进kernel image,相当于内核中存了一个System.map。需要配置CONFIG_KALLSYMS。
-
-
.config
-
CONFIG_KALLSYMS=y 符号表中包含所有的函数
-
CONFIG_KALLSYMS_ALL=y 符号表中包括所有的变量(包括没有用EXPORT_SYMBOL导出的变量)
-
CONFIG_KALLSYMS_EXTRA_PASS=y
-
make menuconfig
-
General setup --->
-
[*] Configure standard kernel features (for small systems) --->
-
[*] Load all symbols for debugging/ksymoops (选中此项,才有/proc/kallsyms接口文件, oops问题,选中此选项即可,子选项可以忽略)
-
-
[*] Include all symbols in kallsyms
-
[*] Do an extra kallsyms pass
-
-
3.编译生成列表
-
内核编译的最后阶段,make会执行
-
nm -n vmlinux|scripts/kallsyms
-
nm -n vmlinux生成所有的内核符号,并按地址排序,形如
-
......
-
c0100000 T startup_32
-
c0100000 A _text
-
c01000c6 t checkCPUtype
-
c0100147 t is486
-
c010014e t is386
-
c010019f t L6
-
c01001a1 t check_x87
-
c01001ca t setup_idt
-
c01001e7 t rp_sidt
-
c01001f4 t ignore_int
-
c0100228 T calibrate_delay
-
c0100228 T stext
-
c0100228 T _stext
-
c010036b t rest_init
-
c0100410 t do_pre_smp_initcalls
-
c0100415 t run_init_process
-
......
-
v2.6.0 的行数是2.5万左右
-
-
4.处理列表
-
scripts/kallsyms则处理这个列表,并生成连接所需的S文件kallsyms.S。在linux3.12中使用/scripts/kallsyms处理此列表。v2.6.0中形如:
-
#include <asm/types.h>
-
#if BITS_PER_LONG == 64
-
#define PTR .quad
-
#define ALGN .align 8
-
#else
-
#define PTR .long
-
#define ALGN .align 4
-
#endif
-
.data
-
.globl kallsyms_addresses
-
ALGN
-
kallsyms_addresses:
-
PTR 0xc0100228
-
PTR 0xc010036b
-
PTR 0xc0100410
-
PTR 0xc0100415
-
PTR 0xc010043c
-
PTR 0xc0100614
-
...
-
.globl kallsyms_num_syms
-
ALGN
-
kallsyms_num_syms:
-
PTR 11228
-
-
.globl kallsyms_names
-
ALGN
-
kallsyms_names:
-
.byte 0x00
-
.asciz "calibrate_delay"
-
.byte 0x00
-
.asciz "stext"
-
.byte 0x00
-
.asciz "_stext"
-
...
-
生成的符号表部分如下:
-
/*
-
......
-
c1618b03 t __raw_write_unlock_irq.constprop.29
-
c1618b19 T panic
-
c1618c91 T printk
-
......
-
c16a4d6b r __func__.17404
-
c16a4d78 R kallsyms_addresses
-
c16ef0dc R kallsyms_num_syms
-
c16ef0e0 R kallsyms_names
-
c17d5468 R kallsyms_markers
-
c17d590c R kallsyms_token_table
-
c17d5c78 R kallsyms_token_index
-
......
-
*/
-
-
5.生成的符号数组解析
-
1)kallsyms_addresses数组包含所有内核函数的地址(经过排序的),v2.6.0 中相同的地址在kallsyms_addresses中只允许出现一次,到后面的版本例如相同的地址可以出现多次,这样就允许同地址函数名的出现。
-
例如:
-
kallsyms_addresses:
-
PTR 0xc0100228
-
PTR 0xc0100228
-
PTR 0xc0100228
-
PTR 0xc010036b
-
当查找某个地址时所在的函数时,v2.6.0 采用的是线性法,从头到尾地找,很低效,后来改成了折半查找,效率好多了。
-
-
2)kallsyms_num_syms是函数个数
-
-
3)kallsyms_names是函数名数组。
-
-
<1>以前的算法是:函数名数组组成的一个大串,这个大串是有许多小串组成,格式是:
-
.byte len
-
.asciz 压缩串
-
-
格式例如:
-
kallsyms_names:
-
.byte 0x00
-
.asciz "calibrate_delay"
-
.byte 0x00
-
.asciz "stext"
-
.byte 0x00
-
.asciz "_stext"
-
.byte 0x00
-
.asciz "rest_init"
-
-
len代表本函数名和前一函数名相同前缀的大小,例如
-
.byte 0x00
-
.asciz "early_param_test"
-
.byte 0x06
-
.asciz "setup_test"
-
.byte 0x06,说明串setup_test和串early_parm_test有着相同的前缀,长为6,即early_,所有setup_test最终解压后的函数名为early_setup_test.由于没有其他的辅助手段,函数名的解析过程也很低效,从头一直解析到该函数位置为止。
-
-
<2>在后来的版本中,算法有了改善,使用了偏移索引和高频字符串压缩。也就是现在常用的算法。格式是:
-
.byte len ascii字符 ascii字符...(len个ascii字符)
-
-
先建立token的概念,token就是所有函数名中,出现频率非常高的那些字符串.由于标识符命名
-
规则的限制,有许多ascii字符是未用到的,那么,可以用这些字符去替代这些高频串。例如下面的例子:
-
字符值 字符代表的串
-
190 .asciz "t.text.lock."
-
191 .asciz "text.lock."
-
192 .asciz "t.lock."
-
193 .asciz "lock."
-
210 .asciz "tex"
-
229 .asciz "t."
-
239 .asciz "loc"
-
249 .asciz "oc"
-
250 .asciz "te"
-
-
例如串.byte 0x03, 0xbe, 0xbc, 0x71的解析
-
串长3,
-
0xbe(190) .asciz "t.text.lock."
-
0xbc(189) .asciz "ir"
-
0x71(113) .asciz "q"
-
所以该串解析后的值是 t.text.lock.irq,注意实际的串值是.text.lock.irq,前面的t是类型,这是新版本加入的功能,将类型字符放在符号前。
-
-
.byte 0x02, 0x08, 0xc2
-
串长2,
-
0x08,8 .asciz "Tide_"
-
0xc2,194 .asciz "init"
-
所以该串解析后的值是 Tide_init,即ide_init
-
-
4)为了解析而设置了数据结构kallsyms_token_table和kallsyms_token_index.结构kallsyms_token_table记录每个ascii字符的替代串,kallsyms_token_index记录每个ascii字符的替代串在kallsyms_token_table中的偏移.
-
-
5)而数据结构的改变是,把函数名每256个分一组,用一个数组kallsyms_markers记录这些组在
-
kallsyms_names中的偏移,这样查找就方便多了,不必从头来。
-
-
四、符号表的查找
-
通过对以上格式的了解,我们就可以自己编写程序找到内核中的符号表,进而找到每个内核符号的地址。
-
1.首先找到kallsyms_addresses数组
-
//首先获得kallsyms_addresses数组中的printk的地址
-
printk_addr_addr = prink_addr_addr();
-
-
static void * prink_addr_addr(void){
-
unsigned int i = 0xc0000000;
-
int k = 0;
-
-
//kallsyms_addresses数组中都是保存的是内核符号的地址,所以这里查找的是地址下的保存的函数地址是否为printk的函数地址
-
for(;i < 0xf0000000; i += 4){
-
if(*((unsigned int *)i) == (unsigned int)printk){
-
//判断该地址前边都是有效的kallsyms_addresses数组函数地址
-
if(isfunaddr(*((unsigned int *)(i-4)))&&isfunaddr(*((unsigned int *)(i-8)))){
-
if(!k)
-
return (void *)i;
-
else
-
++k;
-
}
-
}
-
}
-
return NULL;
-
}
-
-
//只要该函数符号在kallsyms_addresses数组中,通过%Ps打印结构一定是...+0x0/...
-
static int isfunaddr(unsigned int addr){
-
char buff[200] = {0};
-
int i = 0;
-
-
memset(buff,0,sizeof(buff));
-
//get the %pS print format;
-
sprintf(buff,"%pS",(void *)addr);
-
-
//if is a function addr ,it's %pS print format must be: ...+0x0/...
-
if((buff[0]=='0')&&(buff[1]=='x'))
-
return 0;
-
while(buff[i++]){
-
if((buff[i] == '+')&&(buff[i+1]=='0')&&(buff[i+2]=='x')&&(buff[i+3]=='0')&&(buff[i+4]=='/'))
-
return 1;
-
}
-
return 0;
-
}
-
-
//通过printk的地址查找到kallsyms_addresses的结束地址kallsyms_addresses数组结尾处
-
funaddr_endaddr = find_funadd_endaddr(printk_addr_addr);
-
-
//一直循环查找最后一个符号不为...+0x0/...结构,即找到了
-
static void * find_funadd_endaddr(void * in){
-
unsigned int * p = in;
-
for(;isfunaddr(*p); ++p);
-
return (void *)(p-1);
-
}
-
-
//kallsyms_addresses数组尾地址+4就是符号个数kallsyms_num的地址,就能得到符号的个数
-
kallsyms_num = *((unsigned int *)funaddr_endaddr + 1);
-
-
//根据符号个数和kallsyms_addresses数组尾地址就能得到kallsyms_addresses数组的首地址
-
kallsyms_addr = (unsigned int *)funaddr_endaddr - kallsyms_num + 1;
-
-
2.找到kallsyms_name地址。
-
//kallsyms_num地址的下一项就是kallsyms_name地址
-
kallsyms_name = (void *)((unsigned int *)funaddr_endaddr + 2);
-
-
3.kallsyms_name数组的下一项是kallsyms_mark数组,但是他们的地址不是连续的。
-
//把函数名每256个分一组,用一个数组kallsyms_markers记录这些组在kallsyms_names中的偏移
-
kallsyms_mark = get_marker_addr(kallsyms_name );
-
-
//因为数组kallsyms_name中的格式是:.byte len ascii码...(len个)
-
static void * get_marker_addr(void * name){
-
int i = 0;
-
unsigned char * base = (char *)name;
-
-
//base[0]存的是.byte替代串的ascii码,base[1]存的是ascii码个数
-
//所以依次跳过kallsyms_num个name就找到了kallsyms_mark数组的地址
-
for(; i < kallsyms_num ; ++i){
-
base += (base[0] +1);
-
}
-
-
//4字节对齐
-
if((unsigned int)base%4){
-
base += (4-(unsigned int)base%4);
-
}
-
-
return (void *)base;
-
}
-
-
4.计算kallsyms_mark数组表项个数
-
//根据符号个数计算kallsyms_mark数组的个数,符号以256个为一组
-
mark_num = kallsyms_num%256 ? kallsyms_num/256 + 1:kallsyms_num/256;
-
-
5.获取结构kallsyms_token_table地址
-
//结构kallsyms_token_table记录每个ascii字符的替代串,位于kallsyms_mark数组之后
-
kallsyms_token_tab = (void *)((unsigned int *)kallsyms_mark + mark_num);
-
-
6.获得kallsyms_token_indx地址
-
//kallsyms_token_index记录每个ascii字符的替代串在kallsyms_token_table中的偏移.
-
kallsyms_token_indx = get_index_addr(kallsyms_token_tab);
-
-
//因为kallsyms_token_table里存放的都是字符类型,依次跳过合法的字符类型之后的地址就是kallsyms_token_indx地址
-
static void * get_index_addr(void * base){
-
-
char * p = (char *)base;
-
for(;is_funname_char(*p);p++){
-
for(;is_funname_char(*p); p++);
-
}
-
-
//align 4 bytes
-
if((unsigned int)p%4){
-
p += (4 -(unsigned int)p%4);
-
}
-
return (void *)p;
-
}
-
-
//检查是否为函数符号名的合法字符
-
static int is_funname_char( char p){
-
if( ((p >= 'a')&&(p <='z')) || ((p >= 'A')&&(p <='Z')) || (p == '_') || ( (p >='0')&&(p <= '9') ) || (p == '.'))
-
return 1;
-
else
-
return 0;
-
}
-
-
7.查找函数名地址
-
static void * name_2_addr(char * name){
-
char namebuff[200];
-
unsigned int i = 0;
-
unsigned int len = 0;
-
//符号名称数组
-
unsigned char * name_tab = (unsigned char *)kallsyms_name;
-
unsigned int mod_addr = 0;
-
char * buff_ptr = namebuff;
-
-
//遍历所有符号
-
for(; i < kallsyms_num; ++i){
-
memset(namebuff,0,200);
-
buff_ptr = namebuff;
-
len = *name_tab;//符号名称长度
-
name_tab++;//符号名对应的ascii码
-
-
while(len){
-
//根据符号名对应的ascii码得到符号名
-
buff_ptr = cp_from_token_tab(buff_ptr,
-
//得到该ascii码对应的字符串在kallsyms_token_table中的偏移
-
((unsigned short *)kallsyms_token_indx)[*name_tab]);
-
name_tab++;
-
len--;
-
}
-
-
//检查符号名是否一致,其中第一个字符为符号类型,忽略掉
-
if(my_strcmp(name,namebuff+1)==0){
-
//若相等返回该符号地址
-
return (void *)((unsigned int *)kallsyms_addr)[i];
-
}
-
}
-
}
-
-
static char * cp_from_token_tab(char * buff,unsigned short off)
-
{
-
int len = 0;
-
//从kallsyms_token_tab数组的偏移处取得字符串,字符串以“\0”隔开
-
char * token_tab = &(((char *)kallsyms_token_tab)[off]);
-
for(;token_tab[len]; ++len){
-
*buff = token_tab[len];
-
buff++;
-
};
-
return buff;
-
}
-
-
五、符号解析
-
//v2.6.20 当发生oops时,
-
fastcall void __kprobes do_page_fault(struct pt_regs *regs,unsigned long error_code)
-
{
-
...
-
die("Oops", regs, error_code);
-
...
-
}
-
-
void die(const char * str, struct pt_regs * regs, long err)
-
{
-
...
-
print_symbol("%s", regs->eip);//解析
-
...
-
}
-
-
-
static inline void print_symbol(const char *fmt, unsigned long addr)
-
{
-
__check_printsym_format(fmt, "");
-
__print_symbol(fmt, (unsigned long)__builtin_extract_return_addr((void *)addr));
-
}
-
-
-
void __print_symbol(const char *fmt, unsigned long address)
-
{
-
char buffer[KSYM_SYMBOL_LEN];
-
-
//取得该地址的符号信息,存入buffer中
-
sprint_symbol(buffer, address);
-
//将buffer中的符号信息打印出来
-
printk(fmt, buffer);
-
}
-
-
int sprint_symbol(char *buffer, unsigned long address)
-
{
-
return __sprint_symbol(buffer, address, 0, 1);
-
}
-
-
static int __sprint_symbol(char *buffer, unsigned long address,int symbol_offset, int add_offset)
-
{
-
char *modname;
-
const char *name;
-
unsigned long offset, size;
-
int len;
-
-
address += symbol_offset;//符号偏移是0
-
//解析地址,返回函数起始地址,大小,偏移,函数名
-
name = kallsyms_lookup(address, &size, &offset, &modname, buffer);
-
if (!name)
-
return sprintf(buffer, "0x%lx", address);
-
-
//先拷贝该地址对应的函数名给buffer[]
-
if (name != buffer)
-
strcpy(buffer, name);
-
-
len = strlen(buffer);
-
offset -= symbol_offset;
-
-
//将该函数符号的偏移地址和大小拷贝给buffer[]
-
if (add_offset)
-
len += sprintf(buffer + len, "+%#lx/%#lx", offset, size);
-
-
//若属于模块,则拷贝模块名给buffer[]
-
if (modname)
-
len += sprintf(buffer + len, " [%s]", modname);
-
-
return len;
-
}
-
-
const char *kallsyms_lookup(unsigned long addr,unsigned long *symbolsize,unsigned long *offset,char **modname, char *namebuf)
-
{
-
namebuf[KSYM_NAME_LEN - 1] = 0;
-
namebuf[0] = 0;
-
-
//检查是否为内核符号
-
if (is_ksym_addr(addr)) {
-
unsigned long pos;
-
//取得符号的大小和偏移,返回符号在kallsyms_addresses数组中的索引值
-
pos = get_symbol_pos(addr, symbolsize, offset);
-
//解析符号,获得符号名称存入namebuf
-
kallsyms_expand_symbol(get_symbol_offset(pos),namebuf, KSYM_NAME_LEN);
-
if (modname)
-
*modname = NULL;
-
return namebuf;
-
}
-
-
//若不是内核符号,则扫描内核中已安装的模块中的符号
-
return module_address_lookup(addr, symbolsize, offset, modname,namebuf);
-
}
-
-
static unsigned long get_symbol_pos(unsigned long addr,unsigned long *symbolsize,unsigned long *offset)
-
{
-
unsigned long symbol_start = 0, symbol_end = 0;
-
unsigned long i, low, high, mid;
-
-
/* This kernel should never had been booted. */
-
BUG_ON(!kallsyms_addresses);
-
-
low = 0;
-
//kallsyms_num_syms是内核函数个数
-
high = kallsyms_num_syms;
-
-
//折半查找,kallsyms_addresses数组包含所有内核函数的地址(经过排序的)
-
while (high - low > 1) {
-
mid = low + (high - low) / 2;
-
if (kallsyms_addresses[mid] <= addr)
-
low = mid;
-
else
-
high = mid;
-
}
-
-
//找到第一个对齐的符号,即相同地址中的第一个。v2.6.0中相同的地址在kallsyms_addresses中只允许出现一次,到后面的版本例如相同的地址可以出现多次,这样就允许同地址函数名的出现。
-
while (low && kallsyms_addresses[low-1] == kallsyms_addresses[low])
-
--low;
-
-
//获得函数地址小于addr最接近的一个内核函数的地址作为符号的起始地址
-
symbol_start = kallsyms_addresses[low];
-
-
//找到下一个不同的地址
-
for (i = low + 1; i < kallsyms_num_syms; i++) {
-
if (kallsyms_addresses[i] > symbol_start) {
-
symbol_end = kallsyms_addresses[i];
-
break;
-
}
-
}
-
-
/* If we found no next symbol, we use the end of the section. */
-
if (!symbol_end) {
-
if (is_kernel_inittext(addr))
-
symbol_end = (unsigned long)_einittext;
-
else if (all_var)
-
symbol_end = (unsigned long)_end;
-
else
-
symbol_end = (unsigned long)_etext;
-
}
-
-
//获得符号的大小
-
if (symbolsize)
-
*symbolsize = symbol_end - symbol_start;
-
-
//符号的偏移量
-
if (offset)
-
*offset = addr - symbol_start;
-
-
//返回在kallsyms_addresses数组中的索引值
-
return low;
-
}
-
-
//返回符号在kallsyms_names中的偏移
-
static unsigned int get_symbol_offset(unsigned long pos)
-
{
-
const u8 *name;
-
int i;
-
-
//找到该组在kallsyms_names中的偏移。pos>>8即是pos/256得到kallsyms_markers的索引,kallsyms_markers数组中存储的是每256个分一组的组在kallsyms_names的偏移。
-
//kallsyms_names是函数名组成的一个大串,这个大串是有许多小串组成,格式是:
-
//.byte len ascii码 ascii码...(len个)
-
name = &kallsyms_names[ kallsyms_markers[pos>>8] ];
-
-
//依次跳过(pos&0xFF)个偏移即是当前符号的偏移地址处,(*name) + 1存的是len
-
for(i = 0; i < (pos&0xFF); i++)
-
name = name + (*name) + 1;//
-
-
return name - kallsyms_names;//返回该符号在kallsyms_names组中偏移
-
}
-
-
static unsigned int kallsyms_expand_symbol(unsigned int off, char *result)
-
{
-
int len, skipped_first = 0;
-
const u8 *tptr, *data;
-
-
/* get the compressed symbol length from the first symbol byte */
-
data = &kallsyms_names[off];//取该sym的首地址
-
len = *data;//取sym压缩后的长度
-
data++;//指向压缩串
-
-
//指向下一个压缩串偏移
-
off += len + 1;
-
-
//为了解析而设置了数据结构kallsyms_token_table和kallsyms_token_indexkallsyms_token_table记录每个ascii字符的替代串,kallsyms_token_index记录每个ascii字符的替代串在kallsyms_token_table中的偏移.
-
while(len) {
-
//对于*data指向的字符,在token_index查找该字符所代表的解压串偏移,并从token_table中找到该解压串
-
tptr = &kallsyms_token_table[ kallsyms_token_index[*data] ];
-
data++;
-
len--;
-
while (*tptr) {
-
if(skipped_first) {//跳过类型字符,例如t,T
-
*result = *tptr;//拷贝解压串
-
result++;
-
} else
-
skipped_first = 1;
-
tptr++;
-
}
-
}
-
*result = '\0';
-
-
//返回下一个压缩串偏移
-
return off;
-
}
-
-
const char *module_address_lookup(unsigned long addr,unsigned long *size,unsigned long *offset,char **modname,char *namebuf)
-
{
-
struct module *mod;
-
const char *ret = NULL;
-
-
preempt_disable();
-
//遍历内核中的所有模块
-
list_for_each_entry_rcu(mod, &modules, list) {
-
if (mod->state == MODULE_STATE_UNFORMED)
-
continue;
-
//addr是否在模块的init部分或者core部分
-
if (within_module_init(addr, mod) ||within_module_core(addr, mod)) {
-
if (modname)
-
*modname = mod->name;//取得模块名
-
ret = get_ksymbol(mod, addr, size, offset);
-
break;
-
}
-
}
-
/* Make a copy in here where it's safe */
-
if (ret) {
-
strncpy(namebuf, ret, KSYM_NAME_LEN - 1);
-
ret = namebuf;
-
}
-
preempt_enable();
-
return ret;
-
}
-
-
static const char *get_ksymbol(struct module *mod,unsigned long addr,unsigned long *size,unsigned long *offset)
-
{
-
unsigned int i, best = 0;
-
unsigned long nextval;
-
-
/* At worse, next value is at end of module */
-
if (within_module_init(addr, mod))
-
nextval = (unsigned long)mod->module_init+mod->init_text_size;
-
else
-
nextval = (unsigned long)mod->module_core+mod->core_text_size;
-
-
/* Scan for closest preceding symbol, and next symbol. (ELF
-
starts real symbols at 1). */
-
//遍历模块的符号
-
for (i = 1; i < mod->num_symtab; i++) {
-
if (mod->symtab[i].st_shndx == SHN_UNDEF)//跳过未定义的符号
-
continue;
-
-
/* We ignore unnamed symbols: they're uninformative
-
* and inserted at a whim. */
-
if (mod->symtab[i].st_value <= addr
-
&& mod->symtab[i].st_value > mod->symtab[best].st_value
-
&& *(mod->strtab + mod->symtab[i].st_name) != '\0'
-
&& !is_arm_mapping_symbol(mod->strtab + mod->symtab[i].st_name))
-
best = i;
-
if (mod->symtab[i].st_value > addr
-
&& mod->symtab[i].st_value < nextval
-
&& *(mod->strtab + mod->symtab[i].st_name) != '\0'
-
&& !is_arm_mapping_symbol(mod->strtab + mod->symtab[i].st_name))
-
nextval = mod->symtab[i].st_value;
-
}
-
-
if (!best)
-
return NULL;
-
-
if (size)
-
*size = nextval - mod->symtab[best].st_value;
-
if (offset)
-
*offset = addr - mod->symtab[best].st_value;
-
return mod->strtab + mod->symtab[best].st_name;
-
}
-
-
六、符号属性
-
若符号在内核中是全局性的,则属性为大写字母,如T、U等。
-
b:符号在未初始化数据区(BSS)
-
c:普通符号,是未初始化区域
-
d:符号在初始化数据区
-
g:符号针对小object,在初始化数据区
-
i:非直接引用其他符号的符号
-
n:调试符号
-
r:符号在只读数据区
-
s:符号针对小object,在未初始化数据区
-
t:符号在代码段
-
u:符号未定义
|