您的位置:首页 > 编程语言

阅读多平台开源c代码小技巧

2018-03-06 11:33 148 查看

阅读多平台开源c代码小技巧

linux开源软件特别的较大的程序,一般会进行多平台支持,对使用者来说确实方便了不少,但也增加了阅读难度,增大了学习难度,为了兼容多个平台,代码中少不了一堆的宏及平台相关的特性实现,阅读代码跳转函数时,需小心的甄别平台,甚至需进行调试才能确定执行流程。

本文介绍了一种方法,通过Makefile依赖原理,删除与我们关注的平台无关的代码,同时不影响编译调试,提高阅读代码效率,以GDB为例,通过本方法,删除70%~80%非当前关注平台的源代码,且能编译出可运行的GDB程序。

原理

原理比较简单,源码编译生成执行文件前,只有指定平台相关的文件才会参与编译,非指定平台的代码不会参与编译,编译过程中会读文件,而读文件时则会更新文件的访问时间,编译完成后,如访问时间戳未更新的话,则认为是非关注的文件,不影响编译而可以删除。

时间戳

为实现不关心的代码删除,先了解文件时间戳的概念 。

在windows下,一个文件有:创建时间、修改时间、访问时间。

而在Linux下,一个文件也有三种时间,分别是:访问时间、修改时间、状态改动时间。

1. 访问时间(Access time),读一次这个文件的内容,这个时间就会更新。比如对这个文件运用 more、cat等命令。ls、stat命令都不会修改文件的访问时间。Makefile检查依赖的时候不会更新访问时间戳,但编译的时候会更新访问时间。

2. 修改时间(Modify time),修改时间是文件内容最后一次被修改时间。比如:vi后保存文件。ls -l列出的时间就是这个时间。

3. 状态改动时间(Change time)。是该文件的i节点最后一次被修改的时间,通过chmod、chown命令修改一次文件属性,这个时间就会更新。

注意:所谓的linux下,不能是虚拟机windows共享目录,windows共享目录时间戳更新方式与linux下完全不同.

sw@t3swing:gdb-5.2.1$ date +%s

1502951841

sw@t3swing:gdb-5.2.1$ touch COPYING

sw@t3swing:gdb-5.2.1$ stat -c %X COPYING

1502951864

date命令用例显示或者设置系统时间

选项+%s表示以1970年以来的秒数来显示.

touch命令如文件不存在,则创建文件,存在则更新时间戳(3个时间都会更新).

stat命令可以查看文件状态,包括大小,读取时间等信息.

选项-c %X表示查看文件最后读取时间,且以1970年以来的秒数来显示

先做一个简单的验证,看一下下面的测试例子。

sw@t3swing :awk$ touch a.txt

sw@t3swing :awk$ date +%s

1502961586

sw@t3swing:awk$ touch b.txt

sw@t3swing:awk$ stat -c %X *.txt

1502961577

1502961591

sw@t3swing:awk$ ls

a.txt b.txt

sw@t3swing:awk$ find . -type f|awk ‘{cmd=”if [[ $(stat -c %X “$1”) -lt 1502961586 ]]; then rm “\$1”;fi”;system(cmd); }’

sw@t3swing:awk\$ ls

b.txt

sw@t3swing:awk\$

上面例子流程是:

先创一个文件a.txt

获取现在的时间T

再创一个文件b.txt

然后查找所有的文件,并删除在时间T前的文件

可以看到成功删除了a.txt,即可以做到删除指定时间的文件。find命令比较强大,应该也可以单独实现上述的效果,但awk比较直接灵活,用单条命令就比较方便的实现需要的功能,注意awk的命令中的cmd,命令与变量$1的组合使用了双引号,与c语言的字符串组合写法类似。

访问时间戳更新问题

有了上面的验证,应该就可以完成开源软件去除非关注文件的工作了,但不幸的是现在的linux 系统,mount的时候一般默认加了relatime选项( linux 2.6.30后的版本,基本把这个作为默认选项),此选项启用时,访问时间的更新很不靠谱(并非每次读文件时都更新读时间戳)。 可以用如下命令看一下mount是否加了relatime选项 .

sw@t3swing:~$ cat /proc/mounts |grep relatime

none /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0

none /proc proc rw,nosuid,nodev,noexec,relatime 0 0

none /dev devtmpfs rw,relatime,size=250116k,nr_inodes=62529,mode=755 0 0

当mount时增加relatime(或 noatime)选项是,cat grep等命令并不是每次更新访问时间,实际上连续执行cat命令,第一次有可能会更新,后面的都不会更新,增加这个选项的原因是,更新Access time会降低文件系统效率,特别是频繁读取的情况,更为明显,再说访问时间重要性也没那么大,所以就造成了这种情况,由于这个原因,对我们实现去除无关代码是非常不利的.

这个地方坑了我半天,但方法还是有的,在relatime模式下,我发现,对文件copy操作后,第一次对新的文件执行cat等操作必定会更新其访问时间.(ubuntu系统)

sw@t3swing:~$ cp a.txt new.txt

sw@t3swing:~$ stat -c %x new.txt

2017-09-05 18:23:08.980065942 +0800

sw@t3swing:~$ cat new.txt >/dev/null

sw@t3swing:~$ stat -c %x new.txt

2017-09-05 18:24:12.939930774 +0800

注意:

* 需在linux目录下(非windows共享目录)完成该工作。

* 确保新copy的目录中执行删除文件操作,规避访问时间不更新问题.

删除gdb不关注代码步骤

下载gdb,配置好平台相关参数,并编译通过(make命令),生成gdb可执行文件,并能执行。

make clean,保证工程干净,copy一份做备份,或者直接在备份的源码中操作。可能不会一次执行成功,而且会删除其他平台代码(不关注,但不是说没用),所以备份时必要的。

date +%s记录下当前时间,在此时间之前的c文件都将删除。

这里在备份代码中验证,进入备份源码目录,make clean,再make,编译出可执行文件,让需编译的文件读取时间戳更新,再make clean。

执行如下脚本,删除不关注文件,注意替换时间,两条语句选一条使用,第二条语句会打印出已删除的文件。

find . -name *.c |awk ‘{cmd=”if [[ $(stat -c %X “$1”) -lt 1502961254 ]]; then rm “$1”;fi”;system(cmd); }’

find . -name *.c |awk ‘{cmd=”if [[ $(stat -c %X “$1”) -lt 1502961254 ]]; then rm “$1”;echo \”rm “$1”\”; fi”;system(cmd); }’

再make,make的时候可能存在编译不通过的情况,遇到依赖的文件,从原工程copy过来即可,直到编译没有错误为止,这种错误不会很多,gdb5.2编译的时候只有一个文件找不到,其他都能顺利编译通过。

运行可执行程序进行验证,经过验证,编译出的gdb程序可以运行。

注意: date +%s记录下当前时间后,必须确保执行一次make clean与make,否则会删错

可以通过下面命令看一下去除无关文件的结果,gdb-5.2.1是源代码,gdb-bak是经过删除后的代码,

sw@t3swing:gdb-5.2.1$ find . -name *.c |wc

1334 1334 29396

sw@t3swing:gdb-5.2.1$ find . -name *.c |xargs cat |wc

1075774 3995237 31013585

sw@t3swi
4000
ng:gdb-bak$ find . -name *.c |wc

285 285 5346

sw@t3swing:gdb-bak$ find . -name *.c |xargs cat |wc

286718 1060433 8154118

可以看到,GDB5.2的源码共1334个c代码文件,大概107万行,经过删除后,共285个c文件,大概28万行,删除了70%~80%的代码,其中还包含除gdb之外的几个可执行程序,真正需关注的代码可能就几万行,阅读难度没想象的高,实际阅读的时候,使用source insight进行代码调转时,较少看到一个函数在多个文件中实现的情况,为阅读代码节省不少时间。

存在问题

大家可以看到,通过上面的方式删除不关注的代码,只删除了c文件,而头文件则没有删除,更不用说里面的宏了,这就是这种方法的局限性,这种方式解决不了。这里对两个问题进行说明。

为何只删除c文件,而不删头文件,实际上平台相关的头文件也非常多

通过这种方式实现不了,看了一下makefile,各平台的头文件都存在依赖,也就是说,编译某个平台,其他平台的头文件Makefile也会检查,删除一个头文件,就会导致编译不过,除非修改Makefile,否则不能删除其他平台的头文件,个人现在的做法是,加入到source insight工程时,手动删除不需关注的平台代码。

删除平台不相关的c文件后,为何会存在编译不通过的问题

理论上说,剩余的文件应该都是可执行程序需要的文件,不应该出现缺文件的问题,情况与上一条类似,也是Makefile导致的,Makefile检测时间戳时,不会导致文件的读时间戳更新,而Makefile却依赖该文件,简单说就是Makefile依赖了这个c文件,但这个c文件又不参与编译,所以被误删了,这种情况一般不会太多,个人是手动还原回去的。

这个小技巧对阅读开源代码有一些帮助,但不完善,有兴趣的朋友可以探讨完善。

另外

宏定义确定

宏的用户过于灵活,可以定义成整形,字串,语句或者是函数,可以在代码中定义也可以通过编译参数-D传递,阅读源码时,确定宏是否定义很重要,如何确定一个宏是否定义,方法:

通常的可以搜索相关的宏,查看相关调用,可以全局搜索排查

find . -name “*.c” |xargs grep SELECT_ARCHITECTURES

宏可能通过编译参数传递的,可以改动一下当前文件(如加个空格),或使用touch命令更新当前文件的时间戳,看编译时的编译参数.如-DSELECT_ARCHITECTURES=&bfd_i386_arch

可以通过错误语法来检测,如下就是一种方式.

#ifndef SELECT_ARCHITECTURES

)

#endif

上面的方式都是在编译阶段确认,如知道宏的类型,也可以在代码中打印出宏的具体值

符号找不到

符号包含全局量,函数等,有时候定义的方式比较绕,不容易找到,符号如果有使用,且编译通过,那么符号肯定存在,可以先导出一份符号表来确认.

readelf -s ~/gdb > sym.txt

为了兼容多平台情况(面向对象),很多函数都使用指针的形式调用,由于初始化过程可能比较曲折,可以先把函数地址打印出来,然后到符号表中查找,通过地址找到函数名.

查找符号也可以在.o文件中查找,

find . -name “*.o” |awk ‘{cmd = “readelf -s “$1”|grep bfd_elf32_i386_vec;if [[ $? == 0 ]]; then ls -l “$1”;fi;”; system(cmd); }’

sw@t3swing:gdb-bak$ readelf -s gdb/gdb |grep 80caaa0

2789: 080caaa0 45 FUNC GLOBAL DEFAULT 14 find_default_create_infer

#define TARGET_LITTLE_SYM bfd_elf32_i386_vec
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码阅读