- 论坛徽章:
- 0
|
/* 作者 老董 dz0212@foxmail.com */
"学习编写伟大代码的方法是阅读代码,阅读大量的代码;高品质的代码...如果不这样,我们就会不断地重复别人已经完成的工作,重复过去已经发生过的成功和错误"
--Diomidis Spinellis 《CODE Reading》
0x01引言
网上那些大忽悠,让初学者去阅读*nix内核代码,庞大的内核代码,初学者看起来困难重重,感觉没一个入口,与其在旁边瞎转悠,最后放弃,不如迂回前进。革命胜利是从农村包围城市,直到解放全中国。unix系统提供了大量的系统工具,这些系统工具,代码量适中,从这入手学习unix代码是最好的方法。本人喜欢OpenBSD,端庄朴素手感好,还是从OpenBSD提供的ifconfig代码来分析。
先粗后细,读线索读关键函数
0x02 ifconfig代码组织和编译
有四个文件,
Makefile
brconfig.h
brconfig.c
ifconfig.c
先看Makefile
$ more Makefile
# $OpenBSD: Makefile,v 1.13 2012/09/07 00:33:24 deraadt Exp $
PROG= ifconfig
SRCS= ifconfig.c brconfig.c
MAN= ifconfig.8
LDADD= -lutil
DPADD= ${LIBUTIL}
CPPFLAGS+=-DINET6
.include <bsd.prog.mk>
这个文件看起来太费劲,又包含了bsd.prog.mk这个文件,它的位置在/usr/share/mk,看下去,你会发现又包含了好多其它的文件。与其雾里看花,不如直奔主题,看它具体做了什么;
“检查大型编译过程的各个步骤时,可以使用make程序的-n开关进行预演”
--Diomidis Spinellis 《CODE Reading》
$ make -n
cc -O2 -pipe -DINET6 -c ifconfig.c
cc -O2 -pipe -DINET6 -c brconfig.c
cc -O2 -o ifconfig ifconfig.o brconfig.o -lutil
呵呵 编译链接过程是不是很清楚了。“合理饮食,适当运动”,电视里传来了
养身节目专家的声音,太有道理了,如果人都没了,看这代码有毛用?
好了自己写一个Makefile,呵呵,看清楚不是Makelove
$ vi Makefile
all:
cc -g -pipe -DINET6 -c ifconfig.c
cc -g -pipe -DINET6 -c brconfig.c
cc -g -o ifconfig ifconfig.o brconfig.o -lutil
为什么要用-g参数?过一会我们要用gdb调试
0x03 ifconfig.c代码分析
"第一次分析一个程序时,main是一个好的起点。" --Diomidis Spinellis《CODE Reading》
分析源代码从不用任何参数的情况开始,从最简单开始,也符合unix的哲学
分析方法:
(1)从main函数开始,根据命令在屏幕上的显示信息,分析出所有相关的输出函数;
(2)分析这些输出函数周边代码,搞清楚输出信息是如何获得的;
完成以上两步,代码的大框架就完全搞清楚了。
-> main()
前边代码看不懂不要紧一直往下看,直到下边这行注释
/* If no args at all, print all interfaces. */如果没有参数,显示所有网卡接口
if (argc < 2) {
aflag = 1;
printif(NULL, 0);
exit(0);
}
很清楚应从printif()函数入手。
-> printif()
“有时想了解程序在某一方面的功能,运行它可能比阅读源代码更为恰当。”
--Diomidis Spinellis 《CODE Reading》
里边的代码翻遍了,看不出明显的输出,呵呵,gdb该出面了
$ make all
$ gdb ./ifconfig
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Breakpoint 2 at 0x1c0015e4: file ifconfig.c, line 568.
Starting program: /home/walkman/ifconfig/ifconfig
main () at ifconfig.c:568
568 {
(gdb) next
main () at ifconfig.c:569
......
(gdb)
581 printif(NULL,0);
(gdb) step 此处要使用它,进入printif函数
printif (ifname=0x0, ifaliases=0) at ifconfig.c:929
921 char *oname = NULL;
(gdb) next
923 int count = 0, noinet = 1;
.......
979 status(1, (struct sockaddr_dl *)ifa->ifa_addr, /*执行到这个函数时,显示接口信息*/
(gdb) next
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 33196
priority: 0
groups: lo
......
(gdb) next
999 (*p->af_status)(0); /*显示网卡与ip相关的信息*/
(gdb)
inet 127.0.0.1 netmask 0xff000000
现在该总结出,路线了 main()->printif()->status()和(*p->af_status)(0)
0x04 分析printif()
先粗略浏览此函数,分析输出的信息是如何获得的,判断出关键的语句。
......
939 if (getifaddrs(&ifap) != 0) /*1 需要重点分析的语句*/
940 err(1, "getifaddrs");
941
942 namep = NULL;
943 for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
944 if (oname) {
945 if (nlen && isdigit(oname[nlen - 1])) {
946 /* must have exact match */
947 if (strcmp(oname, ifa->ifa_name) != 0)
948 continue;
949 } else {
950 /* partial match OK if it ends w/ digit */
951 if (strncmp(oname, ifa->ifa_name, nlen) != 0 ||
952 !isdigit(ifa->ifa_name[nlen]))
953 continue;
954 }
955 }
......
971 strlcpy(name, ifa->ifa_name, sizeof(name));
972 strlcpy(ifrp->ifr_name, ifa->ifa_name, sizeof(ifrp->ifr_name));
973
974 if (ifa->ifa_addr->sa_family == AF_LINK) {
975 namep = ifa->ifa_name;
976 if (getinfo(ifrp, 0) < 0)
977 continue;
978 ifdata = ifa->ifa_data;
979 status(1, (struct sockaddr_dl *)ifa->ifa_addr,
980 ifdata->ifi_link_state);/*需要重点分析此前后的语句段*/
981 count++;
982 noinet = 1;
983 continue;
984 }
985
986 if (!namep || !strcmp(namep, ifa->ifa_name)) {
987 const struct afswtch *p;
988
989 if (ifa->ifa_addr->sa_family == AF_INET &&
990 ifaliases == 0 && noinet == 0)
991 continue;
992 if ((p = afp) != NULL) {
993 if (ifa->ifa_addr->sa_family == p->af_af)
994 (*p->af_status)(1);
995 } else {
996 for (p = afs; p->af_name; p++) {
997 if (ifa->ifa_addr->sa_family ==
998 p->af_af)
999 (*p->af_status)(0);/*需要重点分析此前后的语句段*/
1000 }
1001 }
1002 count++;
1003 if (ifa->ifa_addr->sa_family == AF_INET)
1004 noinet = 0;
1005 continue;
1006 }
1007 }
上边程序段,943-1007是个循环体,其循环控制变量与939行对比,可知939行getifaddrs()函数获得输出信息,应重点分析。
|
|