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

阅读多平台开源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\$

上面例子流程是:

  1. 先创一个文件a.txt
  2. 获取现在的时间T
  3. 再创一个文件b.txt
  4. 然后查找所有的文件,并删除在时间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不关注代码步骤

  1. 下载gdb,配置好平台相关参数,并编译通过(make命令),生成gdb可执行文件,并能执行。
  2. make clean,保证工程干净,copy一份做备份,或者直接在备份的源码中操作。可能不会一次执行成功,而且会删除其他平台代码(不关注,但不是说没用),所以备份时必要的。
  3. date +%s记录下当前时间,在此时间之前的c文件都将删除。
  4. 这里在备份代码中验证,进入备份源码目录,make clean,再make,编译出可执行文件,让需编译的文件读取时间戳更新,再make clean。
  5. 执行如下脚本,删除不关注文件,注意替换时间,两条语句选一条使用,第二条语句会打印出已删除的文件。

    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); }’

  6. 再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@t3swing: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文件,而头文件则没有删除,更不用说里面的宏了,这就是这种方法的局限性,这种方式解决不了。这里对两个问题进行说明。

  1. 为何只删除c文件,而不删头文件,实际上平台相关的头文件也非常多
    通过这种方式实现不了,看了一下makefile,各平台的头文件都存在依赖,也就是说,编译某个平台,其他平台的头文件Makefile也会检查,删除一个头文件,就会导致编译不过,除非修改Makefile,否则不能删除其他平台的头文件,个人现在的做法是,加入到source insight工程时,手动删除不需关注的平台代码。
  2. 删除平台不相关的c文件后,为何会存在编译不通过的问题
    理论上说,剩余的文件应该都是可执行程序需要的文件,不应该出现缺文件的问题,情况与上一条类似,也是Makefile导致的,Makefile检测时间戳时,不会导致文件的读时间戳更新,而Makefile却依赖该文件,简单说就是Makefile依赖了这个c文件,但这个c文件又不参与编译,所以被误删了,这种情况一般不会太多,个人是手动还原回去的。

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

另外

宏定义确定

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

  1. 通常的可以搜索相关的宏,查看相关调用,可以全局搜索排查
    find . -name “*.c” |xargs grep SELECT_ARCHITECTURES
  2. 宏可能通过编译参数传递的,可以改动一下当前文件(如加个空格),或使用touch命令更新当前文件的时间戳,看编译时的编译参数.如-DSELECT_ARCHITECTURES=&bfd_i386_arch
  3. 可以通过错误语法来检测,如下就是一种方式.
    #ifndef SELECT_ARCHITECTURES
    )
    #endif
  4. 上面的方式都是在编译阶段确认,如知道宏的类型,也可以在代码中打印出宏的具体值

符号找不到

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值