现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

用汇编调试的方法窥探库函数的实现

2013-09-16 22:33 工业·编程 ⁄ 共 1355字 ⁄ 字号 暂无评论

这篇笔记的起因是我想要偷窥一下IntelMKL库中spmv的实现细节,有可能的话看是不是可以在汇编层面做一些优化的工作。需要分析的MKL库函数是:

void mkl_scsrmv (char *transa, MKL_INT *m, MKL_INT *k, float *alpha, char *matdescra, float *val, MKL_INT *indx,MKL_INT *pntrb, MKL_INT *pntre, float *x, float *beta, float *y);

最初的尝试是简单的从MKL静态库中提取相关的obj文件,通过objdump反汇编得到spmv核心的实现代码。与spmv相关的MKL静态库包括:

libmkl_core.a

最内层的核心运算函数

libmkl_intel_thread.a

中间层分支接口

libmkl_intel_lp64.a

最外层运算接口

反汇编的大概方法:

(1)    使用 nm –s 命令导出库文件符号表

(2)    在符号表中搜索顶层函数名称(前面标记T),找出其调用的下层函数(前面标记U)

(3)    如果没有到达最内层的核心运算代码,重复进行第(2)步;否则在符号表中搜索下层函数名称,确定其所在的obj文件

(4)    使用ar –x

命令从库文件中解压出obj文件

(5)    使用 objdump –d 命令反汇编obj文件,得到汇编代码

实际操作中发现这种方法想要dump出期望的核心运算函数的汇编代码非常困难,原因有两个:

1.      根据调用的数据格式和参数不同,以及具体硬件平台支持的如SSE等优化指令集的区别,最外层运算函数对应的核心运算函数的分支数量非常大,分支的命名不易弄懂,不易确定正确的调用路径;

2.      在寻找调用路径的过程中,如果仅从符号表不能确定,则需要反汇编出中间层调用部分的代码做参考,但是有些函数调用是用函数指针来实现的,从汇编代码中看不到被调用的函数符号,使得这个过程变得困难。

最终我采用的是使用gdb动态调试的方法,来确定最终实际被调用的核心运算函数。具体步骤如下:

1.        编写一个调用mkl_scsrmv的“壳程序”,并静态编译链接MKL库(使用-static编译)

2.        gdb启动程序,设置“display/i $pc”(每次中断打印下一条汇编指令)

3.        b main,run

4.        disassemble显示汇编指令

5.        在欲考察的函数处设置断点,格式是 b *指令地址,如b *( main+offset ) 或 b *0xNNNN

6.        c 执行到断点

7.        si进入函数(对应源代码调试的s);ni运行下一条汇编指令(对应源代码调试的n)

8.        重复4-7,直到进入最内层的核心代码,这时可以直接拷贝disassemble的结果,也可以使用函数分支的符号,到开始导出的符号表中查找对应的obj文件,之后dump出汇编代码

确定断点的位置有一些技巧,对于函数分支的命名规律的了解能节省大量的时间;另外需要关注的指令基本只有callq一种,但是要注意callq接着的不一定是函数符号,比如使用函数指针的情况下,callq后面是寄存器名,此时要确定调用的函数分支就完全依靠gdb的动态调试了。

给我留言

留言无头像?