作者在 2010-01-04 12:40:54 发布以下内容
前段时间写网络程序练笔,写了个sniffer小程序,其中用到ip头:
一开始总搞不懂,怎么会一样呢?难道是ip头写错了?还是数据包中地址搞错了?
搞不懂,于是看汇编代码:
仔细想想,是可以想明白的,因为编译器在这里做了一步优化,operator<<()是一个函数,第一个参数是ostream对象,返回值也是ostream类型的对象,返回值再作为下一个<<操作的第一个参数,以此类推,会发现这其实是函数的多重迭代。加入operator<<()函数用f代替,原来的代码就等价于:
f( f( f( f( f( f( f( f( os, ">From: " ), inet_ntoa(pip->src) ), ", To: " ), inet_ntoa(pip->dst) ),", Size: " ),len ), "B" ), endl() )
也就是说这样一个函数调用的汇编代码如上,参数进栈方式是从右往左,这样就理所当然了,c标准函数调用,参数自右向左进栈,于是先是endl()首地址进栈,然后左边的参数是另一f函数的返回值,所以得运算f函数,再次自右向左压参数,于是"B"串的首地址进栈,以此类推。
但这只能说明那段汇编代码的写法情有可原,到底是什么让源地址、目的地址相同呢?
这就涉及到socket编程中的一点知识了,看看inet_ntoa函数:
char* FAR inet_ntoa( __in struct in_addr in );
返回的是一个字符串首地址,而这个地址是有效的,说明是socket库自动分配的内存空间,MSDN上说:
The string returned by inet_ntoa resides in memory that is allocated by Windows Sockets. The application should not make any assumptions about the way in which the memory is allocated. The string returned is guaranteed to be valid only until the next Windows Sockets function call is made within the same thread.
也就是说这两次返回的地址,如果不是NULL,那么就是相同的,而operator<<()函数调用时,这个地址已经被计算出来了,即所有的参数都已经确定,多以打印两个地址的操作引用的是同一个地址,即源地址字符串地址。
话说回来,这也不是什么重大发现,但你会发现operator<<()这个函数调用的规则 需要特别注意,如果出现错误就很难发现,还有那个socket函数,这其实是个基础知识,也需尤其留心。
typedef struct tagIPHDR {然后打印收到的ip包中的源地址和目的地址:
u_char vhl; // version and length of header
u_char tos; // type of service
u_short totlen; // total length
u_short id; // identification
u_short flag_off; // flags and fragment offset;
u_char ttl; // time to live
u_char protocol; // protocol
u_short checksum; // checksum
in_addr src; // internet address - source
in_addr dst; // internet address - destination
} IPHDR,* PIPHDR;
PIPHDR pip = (PIPHDR)buf;问题就出现了,输出的两个地址总是一样的!!!
os<<">From: "<<inet_ntoa(pip->src)
<<", To: "<<inet_ntoa(pip->dst)
<<", Size: "<<len<<"B"<<endl;
一开始总搞不懂,怎么会一样呢?难道是ip头写错了?还是数据包中地址搞错了?
搞不懂,于是看汇编代码:
PIPHDR pip = (PIPHDR)buf;
00411643 mov eax,dword ptr [buf]
00411646 mov dword ptr [pip],eax
os<<">From: "<<inet_ntoa(pip->src)
<<", To: "<<inet_ntoa(pip->dst)
<<", Size: "<<len<<"B"<<endl;
00411649 mov esi,esp
0041164B mov eax,dword ptr [__imp_std::endl (41C3FCh)]
00411650 push eax
00411651 push offset string "B" (418820h)
00411656 mov edi,esp
00411658 mov ecx,dword ptr [len]
0041165B push ecx
0041165C push offset string ", Size: " (418814h)
00411661 mov ebx,esp
00411663 mov edx,dword ptr [pip]
00411666 mov eax,dword ptr [edx+10h]
00411669 push eax
0041166A call dword ptr [__imp__inet_ntoa@4 (41C548h)]
00411670 cmp ebx,esp
00411672 call @ILT+540(__RTC_CheckEsp) (411221h)
00411677 push eax
00411678 push offset string ", To: " (41880Ch)
0041167D mov ebx,esp
0041167F mov ecx,dword ptr [pip]
00411682 mov edx,dword ptr [ecx+0Ch]
00411685 push edx
00411686 call dword ptr [__imp__inet_ntoa@4 (41C548h)]
0041168C cmp ebx,esp
0041168E call @ILT+540(__RTC_CheckEsp) (411221h)
00411693 push eax
00411694 push offset string ">From: " (418800h)
00411699 mov eax,dword ptr [this]
0041169C mov ecx,dword ptr [eax]
0041169E push ecx
0041169F call std::operator<<<std::char_traits<char> > (4111C7h)
004116A4 add esp,8
004116A7 push eax
004116A8 call std::operator<<<std::char_traits<char> > (4111C7h)
004116AD add esp,8
004116B0 push eax
004116B1 call std::operator<<<std::char_traits<char> > (4111C7h)
004116B6 add esp,8
004116B9 push eax
004116BA call std::operator<<<std::char_traits<char> > (4111C7h)
004116BF add esp,8
004116C2 push eax
004116C3 call std::operator<<<std::char_traits<char> > (4111C7h)
004116C8 add esp,8
004116CB mov ecx,eax
004116CD call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (41C404h)]
004116D3 cmp edi,esp
004116D5 call @ILT+540(__RTC_CheckEsp) (411221h)
004116DA push eax
004116DB call std::operator<<<std::char_traits<char> > (4111C7h)
004116E0 add esp,8
004116E3 mov ecx,eax
004116E5 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (41C400h)]
004116EB cmp esi,esp
004116ED call @ILT+540(__RTC_CheckEsp) (411221h)
可以看到,整个operator<<()语句的参数是从后往前进栈的!很奇怪,这完全违背了源代码的本意,这是为什么呢?00411643 mov eax,dword ptr [buf]
00411646 mov dword ptr [pip],eax
os<<">From: "<<inet_ntoa(pip->src)
<<", To: "<<inet_ntoa(pip->dst)
<<", Size: "<<len<<"B"<<endl;
00411649 mov esi,esp
0041164B mov eax,dword ptr [__imp_std::endl (41C3FCh)]
00411650 push eax
00411651 push offset string "B" (418820h)
00411656 mov edi,esp
00411658 mov ecx,dword ptr [len]
0041165B push ecx
0041165C push offset string ", Size: " (418814h)
00411661 mov ebx,esp
00411663 mov edx,dword ptr [pip]
00411666 mov eax,dword ptr [edx+10h]
00411669 push eax
0041166A call dword ptr [__imp__inet_ntoa@4 (41C548h)]
00411670 cmp ebx,esp
00411672 call @ILT+540(__RTC_CheckEsp) (411221h)
00411677 push eax
00411678 push offset string ", To: " (41880Ch)
0041167D mov ebx,esp
0041167F mov ecx,dword ptr [pip]
00411682 mov edx,dword ptr [ecx+0Ch]
00411685 push edx
00411686 call dword ptr [__imp__inet_ntoa@4 (41C548h)]
0041168C cmp ebx,esp
0041168E call @ILT+540(__RTC_CheckEsp) (411221h)
00411693 push eax
00411694 push offset string ">From: " (418800h)
00411699 mov eax,dword ptr [this]
0041169C mov ecx,dword ptr [eax]
0041169E push ecx
0041169F call std::operator<<<std::char_traits<char> > (4111C7h)
004116A4 add esp,8
004116A7 push eax
004116A8 call std::operator<<<std::char_traits<char> > (4111C7h)
004116AD add esp,8
004116B0 push eax
004116B1 call std::operator<<<std::char_traits<char> > (4111C7h)
004116B6 add esp,8
004116B9 push eax
004116BA call std::operator<<<std::char_traits<char> > (4111C7h)
004116BF add esp,8
004116C2 push eax
004116C3 call std::operator<<<std::char_traits<char> > (4111C7h)
004116C8 add esp,8
004116CB mov ecx,eax
004116CD call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (41C404h)]
004116D3 cmp edi,esp
004116D5 call @ILT+540(__RTC_CheckEsp) (411221h)
004116DA push eax
004116DB call std::operator<<<std::char_traits<char> > (4111C7h)
004116E0 add esp,8
004116E3 mov ecx,eax
004116E5 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (41C400h)]
004116EB cmp esi,esp
004116ED call @ILT+540(__RTC_CheckEsp) (411221h)
仔细想想,是可以想明白的,因为编译器在这里做了一步优化,operator<<()是一个函数,第一个参数是ostream对象,返回值也是ostream类型的对象,返回值再作为下一个<<操作的第一个参数,以此类推,会发现这其实是函数的多重迭代。加入operator<<()函数用f代替,原来的代码就等价于:
f( f( f( f( f( f( f( f( os, ">From: " ), inet_ntoa(pip->src) ), ", To: " ), inet_ntoa(pip->dst) ),", Size: " ),len ), "B" ), endl() )
也就是说这样一个函数调用的汇编代码如上,参数进栈方式是从右往左,这样就理所当然了,c标准函数调用,参数自右向左进栈,于是先是endl()首地址进栈,然后左边的参数是另一f函数的返回值,所以得运算f函数,再次自右向左压参数,于是"B"串的首地址进栈,以此类推。
但这只能说明那段汇编代码的写法情有可原,到底是什么让源地址、目的地址相同呢?
这就涉及到socket编程中的一点知识了,看看inet_ntoa函数:
char* FAR inet_ntoa( __in struct in_addr in );
返回的是一个字符串首地址,而这个地址是有效的,说明是socket库自动分配的内存空间,MSDN上说:
The string returned by inet_ntoa resides in memory that is allocated by Windows Sockets. The application should not make any assumptions about the way in which the memory is allocated. The string returned is guaranteed to be valid only until the next Windows Sockets function call is made within the same thread.
也就是说这两次返回的地址,如果不是NULL,那么就是相同的,而operator<<()函数调用时,这个地址已经被计算出来了,即所有的参数都已经确定,多以打印两个地址的操作引用的是同一个地址,即源地址字符串地址。
话说回来,这也不是什么重大发现,但你会发现operator<<()这个函数调用的规则 需要特别注意,如果出现错误就很难发现,还有那个socket函数,这其实是个基础知识,也需尤其留心。