// https://elixir.bootlin.com/linux/v3.5.4/source/kernel/kallsyms.c/*
* kallsyms.c: in-kernel printing of symbolic oopses and stack traces.
*
* Rewritten and vastly simplified by Rusty Russell for in-kernel
* module loader:
* Copyright 2002 Rusty Russell <rusty@rustcorp.com.au> IBM Corporation
*
* ChangeLog:
*
* (25/Aug/2004) Paulo Marques <pmarques@grupopie.com>
* Changed the compression method from stem compression to "table lookup"
* compression (see scripts/kallsyms.c for a more complete description)
*/#include<linux/kallsyms.h>#include<linux/module.h>#include<linux/init.h>#include<linux/seq_file.h>#include<linux/fs.h>#include<linux/kdb.h>#include<linux/err.h>#include<linux/proc_fs.h>#include<linux/sched.h>/* for cond_resched */#include<linux/mm.h>#include<linux/ctype.h>#include<linux/slab.h>#include<asm/sections.h>#ifdef CONFIG_KALLSYMS_ALL#define all_var 1#else#define all_var 0#endif/*
* These will be re-linked against their real values
* during the second link stage.
*/externconstunsignedlong kallsyms_addresses[]__attribute__((weak));// 所有内核符号的地址externconst u8 kallsyms_names[]__attribute__((weak));// 函数名数组/*
* Tell the compiler that the count isn't in the small data section if the arch
* has one (eg: FRV).
*/externconstunsignedlong kallsyms_num_syms // 内核中所有符号的个数,每项格式为[length, name]__attribute__((weak,section(".rodata")));externconst u8 kallsyms_token_table[]__attribute__((weak));// 常用的字符串externconst u16 kallsyms_token_index[]__attribute__((weak));externconstunsignedlong kallsyms_markers[]__attribute__((weak));staticinlineintis_kernel_inittext(unsignedlong addr){if(addr >=(unsignedlong)_sinittext
&& addr <=(unsignedlong)_einittext)return1;return0;}staticinlineintis_kernel_text(unsignedlong addr){if((addr >=(unsignedlong)_stext && addr <=(unsignedlong)_etext)||arch_is_kernel_text(addr))return1;returnin_gate_area_no_mm(addr);}staticinlineintis_kernel(unsignedlong addr){if(addr >=(unsignedlong)_stext && addr <=(unsignedlong)_end)return1;returnin_gate_area_no_mm(addr);}staticintis_ksym_addr(unsignedlong addr){if(all_var)returnis_kernel(addr);returnis_kernel_text(addr)||is_kernel_inittext(addr);}/*
* Expand a compressed symbol data into the resulting uncompressed string,
* given the offset to where the symbol is in the compressed stream.
*/// 把off处的函数名拷贝到result指针处,并返回下一个符号的偏移。staticunsignedintkallsyms_expand_symbol(unsignedint 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];
len =*data;
data++;// len, data 指向第一个符号/*
* Update the offset to return the offset for the next symbol on
* the compressed stream.
*/
off += len +1;// off 指向下一个符号/*
* For every byte on the compressed symbol data, copy the table
* entry for that byte.
*/// 为了提高查询速度,我们将常用的字符串存储在kallsyms_token_table中// kallsyms_token_index记录每个ascii字符的替代串在kallsyms_token_table中的偏移// 比如我们需要查找的符号信息是 0x03, 0xbe, 0xbc, 0x71其中3代表了符号的长度,而后面紧跟的三个字节就是符号的内容了。// 我们需要知道be究竟代表的是什么串。我们首先需要通过kallsyms_token_index[0xbe]查到0xbe所对应的串在// kallsyms_token_table中的索引,然后将该串的首地址赋给tptr。// 从表1中我们查到0xbe(190)所对应的串为 "t.text.lock."其中第一个字母t代表了符号的类型// 然后data指向下一个要解析的字节0xbc。//while(len){
tptr =&kallsyms_token_table[kallsyms_token_index[*data]];
data++;
len--;while(*tptr){if(skipped_first){*result =*tptr;
result++;}else
skipped_first =1;
tptr++;}}*result ='\0';/* Return to offset to the next symbol. */return off;}/*
* Get symbol type information. This is encoded as a single char at the
* beginning of the symbol name.
*/staticcharkallsyms_get_symbol_type(unsignedint off){/*
* Get just the first code, look it up in the token table,
* and return the first char from this token.
*/return kallsyms_token_table[kallsyms_token_index[kallsyms_names[off +1]]];}/*
* Find the offset on the compressed stream given and index in the
* kallsyms array.
*/staticunsignedintget_symbol_offset(unsignedlong pos){const u8 *name;int i;/*
* Use the closest marker we have. We have markers every 256 positions,
* so that should be close enough.
*/
name =&kallsyms_names[kallsyms_markers[pos >>8]];/*
* Sequentially scan all the symbols up to the point we're searching
* for. Every symbol is stored in a [<len>][<len> bytes of data] format,
* so we just need to add the len to the current pointer for every
* symbol we wish to skip.
*/for(i =0; i <(pos &0xFF); i++)
name = name +(*name)+1;return name - kallsyms_names;}/* Lookup the address for this symbol. Returns 0 if not found. */// [v] 根据函数名 得到函数地址unsignedlongkallsyms_lookup_name(constchar*name){char namebuf[KSYM_NAME_LEN];unsignedlong i;unsignedint off;for(i =0, off =0; i < kallsyms_num_syms; i++){
off =kallsyms_expand_symbol(off, namebuf);if(strcmp(namebuf, name)==0)return kallsyms_addresses[i];}returnmodule_kallsyms_lookup_name(name);}EXPORT_SYMBOL_GPL(kallsyms_lookup_name);intkallsyms_on_each_symbol(int(*fn)(void*,constchar*,struct module *,unsignedlong),void*data){char namebuf[KSYM_NAME_LEN];unsignedlong i;unsignedint off;int ret;for(i =0, off =0; i < kallsyms_num_syms; i++){
off =kallsyms_expand_symbol(off, namebuf);
ret =fn(data, namebuf,NULL, kallsyms_addresses[i]);if(ret !=0)return ret;}returnmodule_kallsyms_on_each_symbol(fn, data);}EXPORT_SYMBOL_GPL(kallsyms_on_each_symbol);// 得到一个符号地址的索引。// 返回该地址的函数体大小,以及该地址对于函数首部的偏移。staticunsignedlongget_symbol_pos(unsignedlong addr,unsignedlong*symbolsize,unsignedlong*offset){unsignedlong symbol_start =0, symbol_end =0;unsignedlong i, low, high, mid;/* This kernel should never had been booted. */BUG_ON(!kallsyms_addresses);/* Do a binary search on the sorted kallsyms_addresses array. */
low =0;
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;}/*
* Search for the first aliased symbol. Aliased
* symbols are symbols with the same address.
*/while(low && kallsyms_addresses[low-1]== kallsyms_addresses[low])--low;// 此时已经确定了最后的low值
symbol_start = kallsyms_addresses[low];/* Search for next non-aliased symbol. */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 =(unsignedlong)_einittext;elseif(all_var)
symbol_end =(unsignedlong)_end;else
symbol_end =(unsignedlong)_etext;}if(symbolsize)*symbolsize = symbol_end - symbol_start;// 函数体长度if(offset)*offset = addr - symbol_start;// 地址对于函数头的偏移return low;}/*
* Lookup an address but don't bother to find any names.
*/intkallsyms_lookup_size_offset(unsignedlong addr,unsignedlong*symbolsize,unsignedlong*offset){char namebuf[KSYM_NAME_LEN];if(is_ksym_addr(addr))return!!get_symbol_pos(addr, symbolsize, offset);return!!module_address_lookup(addr, symbolsize, offset,NULL, namebuf);}// kallsyms_lookup_name可以获取到内核中任意函数的地址。// kallsyms_lookup内核函数可以对一个地址解析,返回这个地址所在的函数名以及偏移量。/*
* Lookup an address
* - modname is set to NULL if it's in the kernel.
* - We guarantee that the returned name is valid until we reschedule even if.
* It resides in a module.
* - We also guarantee that modname will be valid until rescheduled.
*/constchar*kallsyms_lookup(unsignedlong addr,unsignedlong*symbolsize,unsignedlong*offset,char**modname,char*namebuf)// namebuf 返回的函数名{
namebuf[KSYM_NAME_LEN -1]=0;
namebuf[0]=0;if(is_ksym_addr(addr)){unsignedlong pos;//通过对get_symbol_pos函数的分析,pos代表了找到的匹配的地址在地址表中kallsym_addresses数组中的索引(此处为5)
pos =get_symbol_pos(addr, symbolsize, offset);/* Grab name *//// 获取名字kallsyms_expand_symbol(get_symbol_offset(pos), namebuf);if(modname)*modname =NULL;return namebuf;}/* See if it's in a module. */returnmodule_address_lookup(addr, symbolsize, offset, modname,
namebuf);}intlookup_symbol_name(unsignedlong addr,char*symname){
symname[0]='\0';
symname[KSYM_NAME_LEN -1]='\0';if(is_ksym_addr(addr)){unsignedlong pos;
pos =get_symbol_pos(addr,NULL,NULL);/* Grab name */kallsyms_expand_symbol(get_symbol_offset(pos), symname);return0;}/* See if it's in a module. */returnlookup_module_symbol_name(addr, symname);}intlookup_symbol_attrs(unsignedlong addr,unsignedlong*size,unsignedlong*offset,char*modname,char*name){
name[0]='\0';
name[KSYM_NAME_LEN -1]='\0';if(is_ksym_addr(addr)){unsignedlong pos;
pos =get_symbol_pos(addr, size, offset);/* Grab name */kallsyms_expand_symbol(get_symbol_offset(pos), name);
modname[0]='\0';return0;}/* See if it's in a module. */returnlookup_module_symbol_attrs(addr, size, offset, modname, name);}/* Look up a kernel symbol and return it in a text buffer. */staticint__sprint_symbol(char*buffer,unsignedlong address,int symbol_offset,int add_offset){char*modname;constchar*name;unsignedlong offset, size;int len;
address += symbol_offset;
name =kallsyms_lookup(address,&size,&offset,&modname, buffer);if(!name)returnsprintf(buffer,"0x%lx", address);if(name != buffer)strcpy(buffer, name);
len =strlen(buffer);
offset -= symbol_offset;if(add_offset)
len +=sprintf(buffer + len,"+%#lx/%#lx", offset, size);if(modname)
len +=sprintf(buffer + len," [%s]", modname);return len;}/**
* sprint_symbol - Look up a kernel symbol and return it in a text buffer
* @buffer: buffer to be stored
* @address: address to lookup
*
* This function looks up a kernel symbol with @address and stores its name,
* offset, size and module name to @buffer if possible. If no symbol was found,
* just saves its @address as is.
*
* This function returns the number of bytes stored in @buffer.
*/intsprint_symbol(char*buffer,unsignedlong address){return__sprint_symbol(buffer, address,0,1);}EXPORT_SYMBOL_GPL(sprint_symbol);/**
* sprint_symbol_no_offset - Look up a kernel symbol and return it in a text buffer
* @buffer: buffer to be stored
* @address: address to lookup
*
* This function looks up a kernel symbol with @address and stores its name
* and module name to @buffer if possible. If no symbol was found, just saves
* its @address as is.
*
* This function returns the number of bytes stored in @buffer.
*/intsprint_symbol_no_offset(char*buffer,unsignedlong address){return__sprint_symbol(buffer, address,0,0);}EXPORT_SYMBOL_GPL(sprint_symbol_no_offset);/**
* sprint_backtrace - Look up a backtrace symbol and return it in a text buffer
* @buffer: buffer to be stored
* @address: address to lookup
*
* This function is for stack backtrace and does the same thing as
* sprint_symbol() but with modified/decreased @address. If there is a
* tail-call to the function marked "noreturn", gcc optimized out code after
* the call so that the stack-saved return address could point outside of the
* caller. This function ensures that kallsyms will find the original caller
* by decreasing @address.
*
* This function returns the number of bytes stored in @buffer.
*/intsprint_backtrace(char*buffer,unsignedlong address){return__sprint_symbol(buffer, address,-1,1);}/* Look up a kernel symbol and print it to the kernel messages. */void__print_symbol(constchar*fmt,unsignedlong address){char buffer[KSYM_SYMBOL_LEN];sprint_symbol(buffer, address);printk(fmt, buffer);}EXPORT_SYMBOL(__print_symbol);/* To avoid using get_symbol_offset for every symbol, we carry prefix along. */struct kallsym_iter {
loff_t pos;unsignedlong value;unsignedint nameoff;/* If iterating in core kernel symbols. */char type;char name[KSYM_NAME_LEN];char module_name[MODULE_NAME_LEN];int exported;};staticintget_ksymbol_mod(struct kallsym_iter *iter){if(module_get_kallsym(iter->pos - kallsyms_num_syms,&iter->value,&iter->type, iter->name, iter->module_name,&iter->exported)<0)return0;return1;}/* Returns space to next name. */staticunsignedlongget_ksymbol_core(struct kallsym_iter *iter){unsigned off = iter->nameoff;
iter->module_name[0]='\0';
iter->value = kallsyms_addresses[iter->pos];
iter->type =kallsyms_get_symbol_type(off);
off =kallsyms_expand_symbol(off, iter->name);return off - iter->nameoff;}staticvoidreset_iter(struct kallsym_iter *iter, loff_t new_pos){
iter->name[0]='\0';
iter->nameoff =get_symbol_offset(new_pos);
iter->pos = new_pos;}/* Returns false if pos at or past end of file. */staticintupdate_iter(struct kallsym_iter *iter, loff_t pos){/* Module symbols can be accessed randomly. */if(pos >= kallsyms_num_syms){
iter->pos = pos;returnget_ksymbol_mod(iter);}/* If we're not on the desired position, reset to new position. */if(pos != iter->pos)reset_iter(iter, pos);
iter->nameoff +=get_ksymbol_core(iter);
iter->pos++;return1;}staticvoid*s_next(struct seq_file *m,void*p, loff_t *pos){(*pos)++;if(!update_iter(m->private,*pos))returnNULL;return p;}staticvoid*s_start(struct seq_file *m, loff_t *pos){if(!update_iter(m->private,*pos))returnNULL;return m->private;}staticvoids_stop(struct seq_file *m,void*p){}staticints_show(struct seq_file *m,void*p){struct kallsym_iter *iter = m->private;/* Some debugging symbols have no name. Ignore them. */if(!iter->name[0])return0;if(iter->module_name[0]){char type;/*
* Label it "global" if it is exported,
* "local" if not exported.
*/
type = iter->exported ?toupper(iter->type):tolower(iter->type);seq_printf(m,"%pK %c %s\t[%s]\n",(void*)iter->value,
type, iter->name, iter->module_name);}elseseq_printf(m,"%pK %c %s\n",(void*)iter->value,
iter->type, iter->name);return0;}staticconststruct seq_operations kallsyms_op ={.start = s_start,.next = s_next,.stop = s_stop,.show = s_show
};staticintkallsyms_open(struct inode *inode,struct file *file){/*
* We keep iterator in m->private, since normal case is to
* s_start from where we left off, so we avoid doing
* using get_symbol_offset for every symbol.
*/struct kallsym_iter *iter;int ret;
iter =kmalloc(sizeof(*iter), GFP_KERNEL);if(!iter)return-ENOMEM;reset_iter(iter,0);
ret =seq_open(file,&kallsyms_op);if(ret ==0)((struct seq_file *)file->private_data)->private = iter;elsekfree(iter);return ret;}#ifdef CONFIG_KGDB_KDBconstchar*kdb_walk_kallsyms(loff_t *pos){staticstruct kallsym_iter kdb_walk_kallsyms_iter;if(*pos ==0){memset(&kdb_walk_kallsyms_iter,0,sizeof(kdb_walk_kallsyms_iter));reset_iter(&kdb_walk_kallsyms_iter,0);}while(1){if(!update_iter(&kdb_walk_kallsyms_iter,*pos))returnNULL;++*pos;/* Some debugging symbols have no name. Ignore them. */if(kdb_walk_kallsyms_iter.name[0])return kdb_walk_kallsyms_iter.name;}}#endif/* CONFIG_KGDB_KDB */staticconststruct file_operations kallsyms_operations ={.open = kallsyms_open,.read = seq_read,.llseek = seq_lseek,.release = seq_release_private,};staticint __init kallsyms_init(void){// /proc/kallsymsproc_create("kallsyms",0444,NULL,&kallsyms_operations);return0;}device_initcall(kallsyms_init);