2/14/2011

博客转移了

现在上blogger太麻烦,故转移到新的博客了,欢迎访问!

12/10/2010

交叉编译

最近朋友给一块arm板子,让我帮忙交叉编译无线网卡驱动,因为交叉编译都没有,所以从交叉编译器到编译驱动一条龙服务啊,简单记录一下。

1) 首先交叉编译器,我用crosstool-ng交叉编译工具,还比较顺利,事先查清楚板子上arm指令集,gcc 和
glibc的版本号就可以了,arm指令集可以到这里对照着查:http://en.wikipedia.org/wiki/ARM_architecture

2) 然后编译内核,make ARCH=arm menuconfig,作一下简单配置,记得选上 wireless,接下来编译内核:make
ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- (记得export PATH
并指定刚刚编出来的交叉编译起)

如果编译过程中出现如下错误:

arm-9tdmi-linux-gnu-ld: no machine record defined

可能是 binutils 版本导致的,可以暂时注释 文件中下面两行:

ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")


3) 最后准备编译无线网卡驱动,由于我机器上的内核是2.6.36,而板子上的是2.6.29.4,所以采用如下方法编译:

make KLIB=/usr/src/linux-2.6.29.4 KLIB_BUILD=/usr/src/linux-2.6.29.4

9/01/2010

Linux 平台下函数调用的堆栈分析

先给一个很简单的函数调用的程序,如下:

#include <stdio.h>

int func(int a)
{
int i = a;
return i;
}

int main()
{
int i = 2;
func(i);
i = 1;
printf("i=%d\n", i);
return 0;
}

这个程序运行的结果是打印"i=1",我们要做的实验就是通过改变堆栈 ebp 的值,使得调用函数func 后返回到main 函数中跳过"i =
1;",从而该程序运行结果变为"i=2"。

先看看main 和 func两个函数的汇编代码:

(gdb) disassemble main
Dump of assembler code for function main:
0x080483c1 <main+0>: lea 0x4(%esp),%ecx
0x080483c5 <main+4>: and $0xfffffff0,%esp
0x080483c8 <main+7>: pushl -0x4(%ecx)
0x080483cb <main+10>: push %ebp
0x080483cc <main+11>: mov %esp,%ebp
0x080483ce <main+13>: push %ecx
0x080483cf <main+14>: sub $0x24,%esp
0x080483d2 <main+17>: movl $0x2,-0x8(%ebp)
0x080483d9 <main+24>: mov -0x8(%ebp),%eax
0x080483dc <main+27>: mov %eax,(%esp) /* argument */
0x080483df <main+30>: call 0x80483b0 <f>
0x080483e4 <main+35>: movl $0x1,-0x8(%ebp) /* return address */
0x080483eb <main+42>: mov -0x8(%ebp),%eax
0x080483ee <main+45>: mov %eax,0x4(%esp)
0x080483f2 <main+49>: movl $0x80484d0,(%esp)
0x080483f9 <main+56>: call 0x8048300 <printf@plt>
0x080483fe <main+61>: mov $0x0,%eax
0x08048403 <main+66>: add $0x24,%esp
0x08048406 <main+69>: pop %ecx
0x08048407 <main+70>: pop %ebp
0x08048408 <main+71>: lea -0x4(%ecx),%esp
0x0804840b <main+74>: ret
End of assembler dump.

(gdb) disassemble func
Dump of assembler code for function func:
0x080483b0 <func+0>: push %ebp
0x080483b1 <func+1>: mov %esp,%ebp
0x080483b3 <func+3>: sub $0x10,%esp
0x080483b6 <func+6>: mov 0x8(%ebp),%eax
0x080483b9 <func+9>: mov %eax,-0x4(%ebp)
0x080483bc <func+12>: mov -0x4(%ebp),%eax
0x080483bf <func+15>: leave
0x080483c0 <func+16>: ret
End of assembler dump.

当调用func函数时,该程序进程的堆栈发生了哪些变化呢?
首先需要传给func 的参数依次入栈,然后是函数的返回地址入栈,也就是call指令的下一条地址,进入func函数后,先把当前的 ebp
入栈,也就是上一次的栈帧地址入栈,其次分配栈空间,为函数的局部变量腾出地方。此时堆栈情况大致如下:
_________________
| int i = 2 |
| ---------------------|
| argument |
| ---------------------|
| return address |
| ---------------------|
| previous frame |
| pointer address |
| ---------------------|
| int i = a |
|________________|

我们只要把这里的 return address 改为0x080483eb即可,也就是跳过0x080483e4处的赋值语句,这样就变量 i
的值就没有改变,因此可达到实验要求的效果。怎么改呢?很明显,可以通过func函数的局部变量 i,取它的地址,就可以访问堆栈了,可以在gdb
中做这样的测试:

p /x *(&i) -----> 局部变量 i 的值
p /x *(&i+1) -----> 之前的栈帧地址
p /x *(&i+2) -----> 函数返回地址
p /x *(&i+3) -----> 函数参数


以下完整代码如下:

#include <stdio.h>

int func(int a)
{
int i = a;
*(&i+2) = *(&i+2)+7; /* 0x080483eb -0x080483e4 = 0x7 */
return i;
}

int main()
{
int i = 2;
func(i);
i = 1;
printf("i=%d\n", i);
return 0;
}


注:以上代码均在x86, Linux 2.6.34, gcc 4.2.3的环境中调试运行。

8/20/2010

常见协议的报文格式

/*
* TCP/IP 协议类型
*/
#define IPPROTO_IP 0 // IP
#define IPPROTO_ICMP 1 // ICMP
#define IPPROTO_TCP 6 // TCP
#define IPPROTO_UDP 17 // UDP

/*
* 其它定义
*/
#define ETH_ALEN 6 // 以太网地址大小
#define ETH_HLEN 14 // 以太网头部大小
#define ETH_DATA_LEN 1500 // 最大帧负载数据大小
#define ETH_FRAME_LEN 1514 // 最大帧大小,头部+负载数据

/*
* 常见协议定义
*/

/*
* 14字节的以太网包头
*/
typedef struct _ETHDR
{
UCHAR eh_dst[ETH_ALEN]; // 目的MAC地址
UCHAR eh_src[ETH_ALEN]; // 源MAC地址
USHORT eh_type; //
下层协议类型,如IP(ETHERTYPE_IP)、ARP(ETHERTYPE_ARP)等
} ETHDR, *PETHDR;

/*
* 28字节的ARP头
*/
typedef struct _ARPHDR
{
USHORT ar_hrd; // 硬件地址类型,以太网中为ARPHRD_ETHER
USHORT ar_pro; // 协议地址类型,ETHERTYPE_IP
UCHAR ar_hln; // 硬件地址长度,MAC地址的长度为6
UCHAR ar_pln; // 协议地址长度,IP地址的长度为4
USHORT ar_op; //
ARP操作代码,ARPOP_REQUEST为请求,ARPOP_REPLY为响应
UCHAR ar_sha[ETH_ALEN]; // 源MAC地址
ULONG ar_sip; // 源IP地址
UCHAR ar_tha[ETH_ALEN]; // 目的MAC地址
ULONG ar_tip; // 目的IP地址
} ARPHDR, *PARPHDR;

/*
* 20字节的IP头
*/
typedef struct _IPHDR
{
UCHAR h_lenver; // 版本号和头长度(各占4位)
UCHAR tos; // 服务类型
USHORT total_len; // 封包总长度,即整个IP报的长度
USHORT ident; // 封包标识,惟一标识发送的每一个数据报
USHORT frag_and_flags; // 标志
UCHAR ttl; // 生存时间,就是TTL
UCHAR protocol; // 协议,可能是TCP、UDP、ICMP等
USHORT checksum; // 校验和
ULONG saddr; // 源IP地址
ULONG daddr; // 目标IP地址
} IPHDR, *PIPHDR;

/*
* 20字节的TCP头
*/
typedef struct _TCPHDR
{
USHORT srceport; // 16位源端口号
USHORT dstport; // 16位目的端口号
ULONG seq; // 32位序列号
ULONG ack; // 32位确认号
UCHAR dataoffset; // 高4位表示数据偏移
UCHAR flags; // 6位标志位
//FIN - 0x01
//SYN - 0x02
//RST - 0x04
//PSH - 0x08
//ACK - 0x10
//URG - 0x20
//ACE - 0x40
//CWR - 0x80

USHORT window; // 16位窗口大小
USHORT checksum; // 16位校验和
USHORT urgptr; // 16位紧急数据偏移量
} TCPHDR, *PTCPHDR;

/*
* 伪TCP头,计算校验和时使用
*/
typedef struct _PSDTCPHDR
{
ULONG saddr;
ULONG daddr;
char mbz;
char ptcl;
USHORT tcpl;
} PSDTCPHDR, *PPSDTCPHDR;

/*
* 8字节的UDP头
*/
typedef struct _UDPHDR
{
USHORT srcport; // 源端口号
USHORT dstport; // 目的端口号
USHORT len; // 封包长度
USHORT checksum; // 校验和
} UDPHDR, *PUDPHDR;

/*
*伪UDP头,计算校验和时使用
*/
typedef struct _PSDUDPHDR
{
ULONG saddr;
ULONG daddr;
char mbz;
char ptcl;
USHORT udpl;
} PSDUDPHDR, *PPSDUDPHDR;

/*
* 12字节的ICMP头
*/
typedef struct _ICMPHDR
{
UCHAR type; //类型
UCHAR code; //代码
USHORT checksum; //校验和
USHORT id; //标识符
USHORT sequence; //序列号
ULONG timestamp; //时间戳
} ICMPHDR, *PICMPHDR;

/*
* 6字节的PPPOE头+2字节协议
*/
typedef struct _PPPOEHDR
{
UCHAR ver_type; //版本+类型 一般为0x11
UCHAR code; //编码
USHORT sessionid; //session id
USHORT len; //长度
USHORT protocol; //协议
} PPPOEHDR, *PPPPOEHDR;

5/19/2010

时钟的指针

几个关于时钟的问题:

  1. 随便给你一个时间,比如9点25分,如何计算时针、分针之间的角度?
  2. 在24小时之中,时钟的时针、分针和秒针完全重合在一起的时候有几次?都分别是什么时间?你怎样算出来的?
  3. 有时候时针分针互换后合理的时间(比如12:00),但有时候不行(比如3:00),24小时中有多少次可以互换的机会?

思路:

分针一分钟转过的角度为 360/60 = 6
时针一小时转过的角度为 360/12 = 30
时针一分钟转过的角度为 360/12/60 = 0.5
x时y分,时针与分针之间的夹角为 |x*30 + y*0.5 - y*6|


3次或2次,或者一次,分别为时钟的12点和24点出现
假设时针的角速度是ω(ω=π/6每小时),则分针的角速度为12ω,秒针的角速度为72ω。
分针与时针再次重合的时间为t,则有12ωt-ωt=2π,t=12/11小时,换算成时分秒为1小时5分27.3秒,显然秒针不与时针分针重合,同样可以算出其它10次分针与时针重合时秒针都不能与它们重合。只有在正12点和24点时才会重合。 

4/01/2010

[GNOME]OAFIID:Gnome_ClockApplet

前几天遇到一个奇怪的问题,Gnome-panel 上显示时间的Clock 不见了,我从"Add to Panel"重新添加Clock 则报错:"OAFIID:Gnome_ClockApplet",我用的是Gentoo,自然想当然的运行revdep-rebuild,但rebuild 之
后问题依旧,上网搜了一下,也没有具体给出解决办法。于是搜ClockApplet,终于找到Clock 程序的配置文件
:/usr/lib/bonobo/servers/GNOME_ClockApplet_Factory.server,打开后,第三行指出了具体的库文件:location="/usr/lib/gnome-panel/libclock-applet.so",这下就好办了。

ldd /usr/lib/gnome-panel/libclock-applet.so

果然,大部分链接都没问题,但出现以下结果:
    libssl3.so.12 => not found
    libsmime3.so.12 => not found
    libnssutil3.so.12 => not found
    libnss3.so.12 => not found
   
然后运行: qfile libssl3.so.12 libsmime3.so.12 libnssutil3.so.12 libnss3.so.12

dev-libs/nss (/usr/lib/libsmime3.so.12)
dev-libs/nss (/usr/lib/libnssutil3.so.12)
dev-libs/nss (/usr/lib/libnss3.so.12)
dev-libs/nss (/usr/lib/libssl3.so.12)

这样就找出了有问题的库文件对应的包名称,赶紧 emerge -q dev-libs/nss 吧,完成之后再次添加Clock 程
序就正常了 :-)

3/26/2010

PHY 与 MAC

网卡工作在OSI model 的最后两层,物理层(Physical Layer )和数据链路层(Data Link Layer)。

物理层的芯片称之为PHY。物理层定义了数据传送 与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口。

以太网卡中数据链路层的芯片称之为MAC控制器, 数据链路层则提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能。

MAC和PHY之间的关系是PCI总线接MAC总 线,MAC接PHY,PHY接网线(当然也不是直接接上的,还有一个变压装置),很多网卡的这两个部分是做到一起的。

PHY和MAC之间互连的界面是IEEE定义的标准:MII/GigaMII(Media Independed Interfade,介质独立界面)。MII界面传递了网络的所有数据和数据的控制。

参考:http://en.wikipedia.org/wiki/PHY_%28chip%29