移植和使用内核函数跟踪系统KFT

来源:互联网 发布:江苏三六五网络招聘 编辑:程序博客网 时间:2024/04/27 13:30

移植和使用内核函数跟踪系统KFT

作者:刘旭晖 Raymond转载请注明出处

Emailcolorant@163.com

BLOGhttp://blog.csdn.net/colorant/

 

以前在2.4内核中使用过KFI来跟踪内核的函数调用,分析性能和帮助理解源码,感觉在某些情况下,还是很好用的,最近在新的项目中,需要在短时间内阅读,维护和修改大量的驱动代码。所以又想到了它,因为当前使用的内核没有集成KFI的后续项目KFT的代码,所以做了一些移植和使用的脚本编程工作,记录在这里。

关于KFT的基本原理,以前写过一篇基于KFI分析的文档(用KFIGraphviz跟踪优化内核代码),现在看来原理上基本是一致的,贴在这里:http://blog.csdn.net/colorant/archive/2008/07/09/2627493.aspx

对内核函数跟踪机制不了解的可以先看那篇文章,这里对原理不再做分析。

本人的能力和时间有限,可能下文中有些理解、分析不一定准确,欢迎联系指正。

 

1         相关说明

 

1.1        网站资源

KFT的官方网址:http://elinux.org/Kernel_Function_Trace

Graphviz的官方网址 http://www.graphviz.org/

一篇分析函数跟踪机制和使用Graphviz进行分析的文章,Visualize function calls with Graphviz : http://www-128.ibm.com/developerworks/linux/library/l-graphvis/?S_TACT=105AGX52&S_CMP=cn-a-l

 

KFT2.6.21内核的patch http://elinux.org/upload/3/33/Kft-all-in-one-2.6.21.patch

 

1.2        工作环境

由于是跟踪内核,所以KFTkernel的关联性应该还是比较密切的,KFT的主页上有2.6.8 2.6.112.6.12 及我所使用的2.6.21等几个版本的patch

至于我的环境:

Ø       硬件平台:基于ARM的嵌入式板子

Ø       软件环境:Linux 2.6.21 ,自制文件系统

 

2         移植

理论上,我的内核版本和patch所针对的版本是精确匹配的,所以本来希望patch打上以后就能用。很可惜,打完patch以后,内核配置选上KFT以后,内核build不通过:

 

kernel/built-in.o: In function `__cyg_profile_func_exit':

utsname_sysctl.c:(.text+0x2a68c): undefined reference to `cmpxchg'

kernel/built-in.o: In function `__cyg_profile_func_enter':

utsname_sysctl.c:(.text+0x2ad74): undefined reference to `cmpxchg'

make: *** [.tmp_vmlinux1] Error 1

 

仔细看了一下,cmpxchg的目的应该是做一次原子性的比较和交换的动作,ARM本身不支持这样的指令。所以,在我的内核中,使用软件关中断的方式,实现了一个类似的函数,仿造那部分代码在kft.c中添加了一个cmpxchg函数如下:

 

static inline int __noinstrument cmpxchg(int *v, int old, int new)

{

        int ret;

        unsigned long flags;

 

        raw_local_irq_save(flags);

        ret = *v;

        if (likely(ret == old))

                *v = new;

        raw_local_irq_restore(flags);

 

        return ret;

}

 

然后,kernel可以Build通过了,只是,不幸的是,下载后的kernel无法启动了,Uncompressing Linux............................................................................................... done, booting the kernel. 到这一步就停止了,

大致猜想,原因是某部分的内核代码不能使用gcc-finstrument-functions参数来编译,这一点可以在KFTpatch中的很多代码上可以看到,使用了__noinstrument 禁止跟踪,这些修改,有些是为了防止KFT本身的函数循环调用,有些是因为某些函数必须禁止跟踪,还有些是未知的原因,可能导致内核崩溃。

 

没有合适的硬件调试器方便跟踪kernel启动的早期阶段(还没有打印输出),暂时没有精力去跟踪到底那部分出了问题,我只能假设基于ARM平台,在我使用的内核代码中,还有一些部分必须禁止跟踪,而KFTpatch中没有处理这一部分。

 

所以,暂时放弃完美的解决这一问题的企图,曲线救国吧,修改总的Makefile 把编译选项-finstrument-functions 去掉。这样可以使得KFT核心模块本身被编译,但是,所有的Kernel代码不调用相关函数,毫无疑问这样是可以正常把kernel跑起来的。

 

然后,修改底层驱动的Makefile,仅对部分模块使用-finstrument-functions参数进行编译。这样同样可以实现对这些模块涉及到的函数进行跟踪的目的。基本能满足我的要求,缺点是,由于不是所有函数都跟踪,可能出现跟踪路径不完整。(在发生进程切换的场合,似乎有时候还会导致函数调用关系的错误关联,这和KFT的跟踪机制有关,不能怪它,谁叫我没能完整的跟踪所有函数,这个问题怎么解决有点头大,好在这种错误情况一眼就能看出来,大不了重跟踪一次)

 

3         使用

 

标准的基本使用方法,可以参考前面列的文章,除此之外,我所面临的问题是需要能够支持对以模块的形式插入内核的驱动的跟踪。

这里涉及到几个问题:

3.1        如何triggerstop

因为模块在未插入内核之前,没有办法得到所需的函数地址,(Buildin的函数是通过 addr2sym 查询System.map来转化得到),所以我所能想到的办法不外乎:

如果确实需要设定由哪个函数进行触发,那么插入一次模块,查询模块的符号表(下一节描述),手工将trigger函数改为对应的地址,重启系统,以完全相同的步骤插入模块,希望模块加载进来以后,保持函数地址和上次一致。

如果不关心由哪个函数触发(例如我想了解一下模块插入以后,相关的驱动初始化流程),那么可以在插入模块前,加载一个基于timetrigger,(只是为了存在一个trigger,没有trigger KFT没法启动)然后,用echo “start” > /proc/kft 进行强制触发。我的trigger

 

new

begin

trigger start time 5000

end

 

Stop也类似了,手工得到函数地址,或者,强制停止。

3.2        如何获得log分析所需的地址和符号表映射

符号映射表通常由System.map得到,我的使用场合下,需要动态的得到模块插入以后总的符号表。

为此,我通过cat /proc/kallsyms > /tmp/kallsyms.bin 来得到运行时的完整符号表。

这个途径得到的符号表包括所有已经插入的模块的符号,但是,和System.map比较存在一些问题:

 

Ø       符号表没有完整按照地址排序

Ø       符号表有额外的带$特殊字符的符号存在

Ø       模块部分的符号表有4列输出,比System.map多了一列模块名的显示

 

这几个区别会导致后面的地址转换成符号的查询算法出现问题,不能正常工作,所以,写了一个简单的脚本来处理得到的符号表,使其满足所需的格式,脚本如下:

 

#! /bin/sh

name=$1

nameout=$name".map"

awk '{print $1,$2,$3}' $name  | sed /"$"[a-z]/d | sort > $nameout

 

基本上就是3步:只输出前3列,去除$a等符号,排序。

 

3.3        处理__INIT函数

__init修饰符的函数,在system.map/proc/kallsyms中都没有生成符号表,如果要以相关的函数作为触发,我所能想到的只能是写一个dummy函数来触发了。例如下面这个函数:

 

static int __init u2d_init(void)

{

    return platform_driver_register(&u2d_driver);

}

修改成下面的代码:

void dummy_u2d_init(void)

{

    printk("just for kft trigger");

    return;

}

static int __init u2d_init(void)

{

    dummy_u2d_init();

    return platform_driver_register(&u2d_driver);

}

这样,以dummy_u2d_init作为触发函数,即可。

不知道是否有更好的解决办法。

 

3.4        绘制函数调用图

得到log,按照kft的标准用法,使用kd等工具已经可以对数据进行分析,不过我的目的之一依然是希望得到函数调用图,所以参照以前的做法,修改了转换脚本以及mtjones 所写的graphviz所需Dot文件的生成程序。

上传到了google doc

http://docs.google.com/Doc?id=dxf836w_10vzx9v6dx

3.4.1          使用:

使用上述脚本,程序,完成画图所需的工作的大致流程如下:

 

../sym2map.sh kallsyms.bin

../mykftres.py kft.log kallsyms.bin.map > kft.call

../mypvtrace/pvtrace kft.call

dot -Tjpg graph.dot -o kft.jpg

../addr2sym < kft.log -m kallsyms.bin.map > kft.lst

../kd -c -r kft.lst > kft.ctree

 

下图是实际跟踪usb gadge filestorage的模块的初始化过程得到的函数调用图中的一小部分:

 

example image

4         遗留问题

Ø       __init 宏修饰的函数没法从/proc/kallsym中获得,这些函数就没法查找定位了

Ø       部分 static函数,如果只被一个函数调用,那么该static函数可能会被优化成内联函数,无法跟踪。

Ø       完全使能-finstrument-functions编译选项导致内核无法启动

原创粉丝点击