上篇提到了函數:
void dump_backtrace_entry(unsigned long where, unsigned long from, unsigned long frame)
{
#ifdef CONFIG_KALLSYMS
//記住%pS是關鍵 printk 與 普通printf最大的不同
//慚愧啊,現在才知道此選項
printk("symbol<%08lx>] (%pS) from [<%08lx>] (%pS)\n", where, (void *)where, from, (void *)from);
#else
printk("Function entered at [<%08lx>] from [<%08lx>]\n", where, from);
#endif
if (in_exception_text(where))
dump_mem("", "Exception stack", frame + 4, frame + 4 + sizeof(struct pt_regs));
}
實際打印的卻類似下面的語句:
symbol<bf00c0c4>] (handler_pre+0x0/0x19c [kk]) from [<c063d174>] (kprobe_handler+0x194/0x234)
明明只是傳入了一個PC指針而已,卻可以打印出函數名字及偏移量。
查看了源碼,發現是printk的功勞。
參考: kernel/lib/vsprintf
kernel/kernel/printk
回顧下printk實現:
printk
-->vprintk
-->vsnprintf (格式話,及做更多功能)
* This function follows C99 vsnprintf, but has some extensions:
* %pS output the name of a text symbol with offset 關鍵在這兩個格式化選項
* %ps output the name of a text symbol without offset
* %pF output the name of a function pointer with its offset
* %pf output the name of a function pointer without its offset
* %pB output the name of a backtrace symbol with its offset
* %pR output the address range in a struct resource with decoded flags
* %pr output the address range in a struct resource with raw flags
* %pM output a 6-byte MAC address with colons
* %pm output a 6-byte MAC address without colons
* %pI4 print an IPv4 address without leading zeros
* %pi4 print an IPv4 address with leading zeros
* %pI6 print an IPv6 address with colons
* %pi6 print an IPv6 address without colons
* %pI6c print an IPv6 address as specified by RFC 5952
* %pU[bBlL] print a UUID/GUID in big or little endian using lower or upper
* case.
* %n is ignored
int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
...
case FORMAT_TYPE_PTR:
//格式化爲函數名+偏移量
str = pointer(fmt+1, str, end, va_arg(args, void *),
spec);
//傳入的必須是函數指針或地址 (text段)
while (isalnum(*fmt))
fmt++;
break;
}
char *pointer(const char *fmt, char *buf, char *end, void *ptr,
struct printf_spec spec)
{
......
switch (*fmt) {
case 'S':
case 's':
case 'B':
return symbol_string(buf, end, ptr, spec, *fmt);
}
char *symbol_string(char *buf, char *end, void *ptr,
struct printf_spec spec, char ext)
{
unsigned long value = (unsigned long) ptr;
// 函數名不能超過此長度,否則可能數組越界,導致奇怪問題的產生
char sym[KSYM_SYMBOL_LEN];
//真正去查找函數名的實現
kallsyms_lookup(value, NULL, NULL, NULL, sym);
return string(buf, end, sym, spec);
}
參考: kernel/kernel/kallsyms.c
呵呵,有時間好好看看kallsyms_lookup的實現