??xml version="1.0" encoding="utf-8" standalone="yes"?> 1. 安装VS2010,WDK7.60QGRMWDK_EN_7600_1Q?/p>
2. 新徏VC 控制台项?选择为空目) 3. 新徏目配置“driver” Q点M拉按?点击Q配|管理器Q?/p>
输入名称QdriverQ点ȝ定就可以了,其他的不要动哦! 完成后的效果Q?/p>
点击定按钮之后呈现出来的画?/p>
鼠标叛_新徏的driver属性,会弹Z下窗口! <我把wdk安装在E盘下> 新徏C/C++文g 不然无C/C++讄选项 <刚开始我们创Z一个空的项目所以项目里没有c++文gQ现在要做的是在空的项?源文?d一个新建项c++文g> 常规 基本q行时检查:默认?nbsp; //可? 如果?nbsp; ( 讄为此值时Q将依赖 IDE 的环境的相关讄 ) //NT式驱?nbsp; ntoskrnl.lib WDM式驱?nbsp; wdm.lib 忽略所有默认库Q?nbsp; ?(/NODEFAULTLIB) //必?nbsp; 清单文gQ? 不然会出?nbsp; >LINK : fatal error LNK1295: “/MANIFESTUAC”?#8220;/DRIVER”规范不兼容;链接时不使用“/MANIFESTUAC” 讄效应和:?/RELEASE) //可? 基址Q?x10000 //选上 最后给Z个超U简单的代码来测试一下我们配|的是否成功Q? #include "ntddk.h" NTSTATUS 如果没有报错那么恭喜你配|成功了Q? WINDOWS完成端口~程 WINDOWS完成端口~程
一般来_一个应用程序可以创建多个工作线E来处理完成端口上的通知事g。工作线E的数量依赖于程序的具体需要。但是在理想的情况下Q应该对应一个CPU
创徏一个线E。因为在完成端口理想模型中,每个U程都可以从pȝ获得一?#8220;原子”性的旉片,轮番q行q检查完成端口,U程的切换是额外的开销。在实际开
发的时候,q要考虑q些U程是否牉|到其他堵塞操作的情况。如果某U程q行堵塞操作Q系l则其挂vQ让别的U程获得q行旉。因此,如果有这L情况Q?
可以多创建几个线E来量利用旉?br>
应用完成端口Q?br>
创徏完成端口Q完成端口是一个内核对象,使用时他L要和臛_一个有效的讑֤句柄q行兌Q完成端口是一个复杂的内核对象Q创建它的函数是Q?br>
HANDLE CreateIoCompletionPort( 查看以上代码Q注意如果Overlapped操作立刻p|Q比如,q回SOCKET_ERROR或其他非WSA_IO_PENDING的错误)Q则
没有M完成通知旉会被攑ֈ完成端口队列里。反之,则一定有相应的通知旉被放到完成端口队列。更完善的关于Winsock的完成端口机Ӟ可以参?
MSDN的Microsoft PlatFormSDKQ那里有完成端口的例子。访?a target="_blank">http://msdn.microsoft.com/library/techart/msdn_servrapp.htm可以获得更多信息?/p>
Linux的EPoll模型 2、内怸提高I(y)/O性能的新Ҏ(gu)epoll Linux2.6内核epoll介绍 epoll_wait范围之后应该是一个@环,遍利所有的事gQ? 对,epoll的操作就q么单,d不过4个APIQepoll_create, epoll_ctl, epoll_wait和close? while (TRUE) OnWriteEpoll (i);//查看当前的活动连接是否有需要写出的数据?br>
}
BQ?对API函数的参数合法性的验(假设参数都是合法的,只有遇到异常的时候进行合法性检验)
CQ?处理致命错误Q退出时最好的选择Q但是有的时候可以用异常处理函数在程序退出前释放资源Q删除时文件等Q甚臛_以详l记录生异常的指o位置和环境)
DQ?处理“计划?#8221;的异常(我们可能更关心这U情况,因ؓ可以做很多的手脚Q哈哈)
接着我们看看Windows下异常处理的两种方式Q?使用{选器2 SEH异常处理
一?使用{选器
因ؓq里我要重点x的是SEH的处理方式,所以还是简单的提一下筛选器处理方式。筛选器异常处理是通过异常回调函数来指定程序处理异常。这U方式的回调函数必须是唯一的,讄新的回调函数后以前的失效。适用于进E范围。看一下这个函数的定义
Invoke SetUnhandledExecpionFilter,offset_Handler
Mov lpPrevHandler,eax
(先到q里吧有些难受,明天接着?
######题外话:惌v“o”的一句话Q觉得挺有道理:明天不一定美好,但是更美好的明天一定会到来Q祝所有的朋友?#####
上午有会Q什么也没有做,下午Q还有会Q我tm晕了Q中午不睡觉了,不把事情做不完心里不t实?br />回调函数的格式:
_Handlerproc pExecptionInfo
看看pExecptionInfoq个指针参数指向的一个数据结?br />EXCEPTION_POINTERS STRUCT
pExceptionRecord DWORD ?
ContextRecord DWORD ?
EXCEPTION_POINTERS ENDS
下面介绍 EXCEPTION_RECORD和CONTEXTl构的定?
;//===================== 以下是两个成员的详细l构=========================
EXCEPTION_RECORD STRUCT
ExceptionCode DWORD ? ;//异常?nbsp;
ExceptionFlags DWORD ? ;//异常标志
pExceptionRecord DWORD ? ;//指向另外一个EXCEPTION_RECORD的指?nbsp;
ExceptionAddress DWORD ? ;//异常发生的地址
NumberParameters DWORD ? ;//下面ExceptionInformation所含有的dword数目
ExceptionInformation DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)
EXCEPTION_RECORDENDS ;//EXCEPTION_MAXIMUM_PARAMETERS ==15
;//=============================具体解释================================
ExceptionCode 异常cd,SDK里面有很多类?你可以在windows.inc里查找STATUS_来找到更多的异常cd,下面只给出hex?具体标识定义h阅windows.inc,你最可能遇到的几U类型如?
C0000005h----d内存冲突
C0000094h----非法?
C00000FDh----堆栈溢出或者说界
80000001h----由Virtual Alloc建立h的属性页冲突
C0000025h----不可持箋异常,E序无法恢复执行,异常处理例程不应处理q个?nbsp; ?nbsp;
C0000026h----在异常处理过E中pȝ使用的代?如果pȝ从某个例E莫名奇妙的q回,则出现此代码, 如果RtlUnwind时没有Exception Record参数也同样会填入q个代码
80000003h----调试时因代码中int3中断
80000004h----处于被单步调试状?nbsp;
?也可以自己定义异总?遵@如下规则:
____________________________________________________________________
? 31~30 29~28 27~16 15~0
____________________________________________________________________
含义: 严重E度 29?nbsp; 功能代码 异常代码
0==成功 0==Mcrosoft MICROSOFT定义 用户定义
1==通知 1==客户
2==警告 28?nbsp;
3==错误 被保留必Mؓ0
ExceptionFlags 异常标志
0----可修复异?nbsp;
1----不可修复异常
2----正在展开,不要试图修复什?需要的?释放必要的资?nbsp;
pExceptionRecord 如果E序本nD异常,指向那个异常l构
ExceptionAddress 发生异常的eip地址
ExceptionInformation 附加消息,在调用RaiseException可指定或者在异常号ؓC0000005h卛_存异常时含义如下
W一个dword 0==dH?nbsp;1==写冲H?nbsp;
W二个dword d冲突地址
;//================================解释l束============================
off.
CONTEXT STRUCT ; _
ContextFlags DWORD ? ; | +0
iDr0 DWORD ? ; | +4
iDr1 DWORD ? ; | +8
iDr2 DWORD ? ; >调试寄存?nbsp; +C
iDr3 DWORD ? ; | +10
iDr6 DWORD ? ; | +14
iDr7 DWORD ? ; _| +18
FloatSave FLOATING_SAVE_AREA <> ;点寄存器区 +1C~~~88h
regGs DWORD ? ;--| +8C
regFs DWORD ? ; |/D寄存器 +90
regEs DWORD ? ; |/ +94
regDs DWORD ? ;--| +98
regEdi DWORD ? ;____________ +9C
regEsi DWORD ? ; | 通用 +A0
regEbx DWORD ? ; | ?nbsp; +A4
regEdx DWORD ? ; | ?nbsp; +A8
regEcx DWORD ? ; | ?nbsp; +AC
regEax DWORD ? ;_______|___l_ +B0
regEbp DWORD ? ;++++++++++++++++ +B4
regEip DWORD ? ; |控制 +B8
regCs DWORD ? ; |寄存 +BC
regFlag DWORD ? ; |器组 +C0
regEsp DWORD ? ; | +C4
regSs DWORD ? ;++++++++++++++++ +C8
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS
;//============================以上是两个成员的详细l构============
E序使用{选器异常处理时可以通过查看上面l构中的regEip来找C生异常的地址Q调试的时候可以改变EIP的g辑ֈ过异常E序Q{?#8220;安全”的地斏V?br />最后看一下筛选器异常处理回调函数的返回?br />EXECPTION_EXECUTE_HANDLER 1Q进E被l止Q终止前不会出现提示错误的对话框
EXECPTION_CONTINUE_SEARCH 0Q同L止程序,昄错误对话?br />EXECPTION_CONTINUE_EXECUTION -1Q系l将CONTECT讄回去Ql执行程?br />
使用{选器E序是最单的处理异常Ҏ(gu)Q不I1 不便于封装? 处理是全局性的也就是无法对每个U程或子E序讄一个私有的异常处理E序q行异常处理?br />q入正题QSEH异常处理
首先解释一下什么是SEH异常处理QSEH("Structured Exception Handling",即结构化异常处理.是操作系l提供给E序设计者的强有力的处理E序错误或异常的武器?br />下面l合冷雨飘心的一个SEH异常处理E序来说明具体的用法Q?br />;//====================================================================
;// ex. 2,by Hume,2001 U程相关的异常处?nbsp;
;//====================================================================
.386
.model flat, stdcall
option casemap :none ; case sensitive
include hd.h ;//相关的头文gQ你自己l护一个吧
;//============================
.data
szCap db "By Hume[AfO],2001...",0
szMsgOK db "It's now in the Per_Thread handler!",0
szMsgERR1 db "It would never Get here!",0
buff db 200 dup(0)
.code
_start:
;//========prog begin====================
ASSUME FS:NOTHING
push offset perThread_Handler
push fs:[0]
mov fs:[0],esp ;//建立SEH的基本ERRl构,如果不明?׃l研I一下吧
xor ecx,ecx
mov eax,200
cdq Q?/双字扩展到四个字节,因ؓ是除?br /> div ecx
;//以下永远不会被执?nbsp;
invoke MessageBox,NULL,addr szMsgERR1,addr szCap,MB_OK+MB_ICONINFORMATION
pop fs:[0]
add esp,4
invoke ExitProcess,NULL
;//============================
perThread_Handler:
invoke MessageBox,NULL,addr szMsgOK,addr szCap,MB_OK+MB_ICONINFORMATION
mov eax,1 ;//ExceptionContinueSearch,不处?由其他例E或pȝ处理
;mov eax,0 ;//ExceptionContinueExecution,表示已经修复CONTEXT,可从异常发生处l执?nbsp;
ret ;//q里如果q回0,你会陷入d@?不断跛_对话?...
;//=============================Prog Ends==============
end _start
E序本n很简单,注释也很详细。我们来看看是如何注册回调函数的
push offset perThread_Handler
push fs:[0]
mov fs:[0],esp
仅仅三个语句p决了~那么Z么要用fsq个D寄存器呢?q里又涉及一个重要的内容QTIBQThread Information BlockU程信息块)。我们来看看q个重要的数据结构(引用了《罗聪浅谈利用SEB实现反跟t》的部分内容Q?br />TEB(Thread Environment Block) ?nbsp;Windows 9x p?列中被称?nbsp;TIB(Thread Information Block)Q它记录了线E的重要信息Q而且每一个线E都会对应一?nbsp;TEB l?构?nbsp;Matt Pietrek 大牛已经l我们列Z它的l构Q我׃多说啦,见下Q(??nbsp;Matt Pietrek ?nbsp;Under The Hood - MSJ 1996Q?nbsp;
//===========================================================
// file: TIB.H
// Author: Matt Pietrek
// From: Microsoft Systems Journal "Under the Hood", May 1996
//===========================================================
#pragma pack(1)
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION_RECORD * pNext;
FARPROC pfnHandler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
typedef struct _TIB
{
PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list
PVOID pvStackUserTop; // 04h Top of user stack
PVOID pvStackUserBase; // 08h Base of user stack
union // 0Ch (NT/Win95 differences)
{
struct // Win95 fields
{
WORD pvTDB; // 0Ch TDB
WORD pvThunkSS; // 0Eh SS selector used for thunking to 16 bits
DWORD unknown1; // 10h
} WIN95;
struct // WinNT fields
{
PVOID SubSystemTib; // 0Ch
ULONG FiberData; // 10h
} WINNT;
} TIB_UNION1;
PVOID pvArbitrary; // 14h Available for application use
struct _tib *ptibSelf; // 18h Linear address of TIB structure
union // 1Ch (NT/Win95 differences)
{
struct // Win95 fields
{
WORD TIBFlags; // 1Ch
WORD Win16MutexCount; // 1Eh
DWORD DebugContext; // 20h
DWORD pCurrentPriority; // 24h
DWORD pvQueue; // 28h Message Queue selector
} WIN95;
struct // WinNT fields
{
DWORD unknown1; // 1Ch
DWORD processID; // 20h
DWORD threadID; // 24h
DWORD unknown2; // 28h
} WINNT;
} TIB_UNION2;
PVOID* pvTLSArray; // 2Ch Thread Local Storage array
union // 30h (NT/Win95 differences)
{
struct // Win95 fields
{
PVOID* pProcess; // 30h Pointer to owning process database
} WIN95;
} TIB_UNION3;
} TIB, *PTIB;
#pragma pack()
让我们抬头看看上面的 Matt Pietrek 的代码,其中有这么一行:
PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list
?意到 PEXCEPTION_REGISTRATION_RECORD q个定义Q它表示 pvExcept q个变量??nbsp;exception record list 的入口,q个入口位于整个l构?nbsp;0 偏移处。同Ӟ ?nbsp;M ?nbsp;Intel i386 Windows NT/2K/XP 内核中,每当创徏一个线E,OS 均会为每个线E分?nbsp;TEB Q??nbsp;TEB 永远攑֜ fs D选择器指定的数据D늚 0 偏移处?nbsp;
q样一来,你就明白?nbsp;SEH 注册的偏UMؓ什么是?nbsp;fs:[0] 了吧Q?nbsp;
事实?nbsp;Windows pȝ都是通过q种Ҏ(gu)来ؓ应用E序提供信息的,比如有这L例子Q?nbsp;
struct _tib *ptibSelf; // 18h Linear address of TIB structure
DWORD threadID; // 24h
Windows 提供了一?nbsp;API QGetCurrentThreadID()Q它的内部工作原理其实是q样的:Q利用了上面的这两个地址Q?nbsp;
mov eax, fs:[18h] ;因ؓ 18h 偏移处是 TIB l构的线性偏Ud址
mov eax, [eax + 24h] ;因ؓ 24h 偏移处是 threadID 的地址
ret ;?nbsp;eax 中储存的 threadID 地址q回
注:Z么要声明assume fs:nothing?因ؓmasm~译器默认将fsD寄存器定义为errorQ所以程序在使用fs前必d它启动!
接下来看看SEH的回调函?br />_Handler proc _lpExecptionRecord, _lpSEH,lp_context,lp_DispatcherContext
_lpExecptionRecord指向一个EXECPTION_RECORDl构?br />lp_context 指向一个CONTEXTl构?br />_lpSEH 指向注册回调函数时用的EXXCEPTION_REGISTRATIONl构的地址?br />q回值有四种取|
ExecptionContinueExecution ( 0 Q系l将U程环境讄为_lpContext指向的CONTEXTl构ql执行?br />ExceptionContinueSearchQ?Q:回调函数拒绝处理q个异常Q系l通过EXECPTION_REGISTRATIONl构的prev字段得到前一个回调函数的地址q调用它?br />ExecptionNestedExecption Q?Q:发生异常嵌套?br />ExecptionCollidedUnwind Q?Q:异常展开操作。这一个部分不做多Ԍ有兴的可以看看|云彬的书,其实是很重要的一部分?br />如果一个程序既有筛选器异常处理又有SEH异常处理Q而且pȝq有默认的异常处理机Ӟ那么他们被调用的先后ơ序是怎么L呢?
发生异常时系l的处理序(by Jeremy Gordon):
1.pȝ首先判断异常是否应发送给目标E序的异常处理例E?如果军_应该发?q且目标E序正在被调?则系l挂L序ƈ向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,q不是正好可以用来探调试器的存在吗?
2.如果你的E序没有被调试或者调试器未能处理异常,pȝ׃l箋查找你是否安装了U程相关的异常处理例E?如果你安装了U程相关的异常处理例E?pȝ把异常发送给你的E序seh处理例程,交由其处?
3.每个U程相关的异常处理例E可以处理或者不处理q个异常,如果他不处理q且安装了多个线E相关的异常处理例程,可交由链h的其他例E处?
4.如果q些例程均选择不处理异?如果E序处于被调试状?操作pȝ仍会再次挂vE序通知debugger.
5.如果E序未处于被调试状态或者debugger没有能够处理,q且你调用SetUnhandledExceptionFilter安装了最后异 常处理例E的?pȝ转向对它的调?
6.如果你没有安装最后异常处理例E或者他没有处理q个异常,pȝ会调用默认的pȝ处理E序,通常昄一个对话框, 你可以选择关闭或者最后将光加到调试器上的调试按?如果没有调试器能被附加于其上或者调试器也处理不?pȝp用ExitProcessl结E序.
7.不过在终l之?pȝ仍然对发生异常的U程异常处理句柄来一ơ展开,q是U程异常处理例程最后清理的Z.
说了q么多你也许会问SEH异常处理到底有什么用处呢Q呵呵,且听生慢慢道来~~~
W一道菜Q病毒程序y用SEH
q里单的说一下如何利用SEH异常处理E序来躲避下毒Y件的反病毒引擎。一个反病毒引擎在一个程序运行的时候会模拟E序的代码,当发现程序代码的疑点比较多的时候会报告成病毒。看看下面这D늨序:
start:call Set_SEH;q句其实是 push offset CONTINUE
; JMP Set_SEH
CONTINUE:mov esp, [esp+8]; [ESP+8]存储的是旧的堆栈地址?br />push offset Start_Virus ;----_ 把Start_Virus 的地址压栈Q当作返回地址
ret;----跛_Start_Virus去,是不是很magic?
Set_SEH:sub edx, edx ;Edx =0
Assume fs:nothing
push dword ptr fs:[edx];把指?nbsp;_EXCEPTIONAL_REGISTRATION_RECORD l构的指针入?br />mov fs:[edx], esp;安装一个seh
mov [edx],edx;引v一个内存读写冲H,发生异常因ؓedx=0
;如果反病毒引擎不处理异常Q不q入seh 处理E序(?nbsp;CONTINUE: Ql模
;拟下个指令,也就是jmp startQ那么就q入一个死循环Q可能会引vL?nbsp;
jmp start
Start_Virus: .....
是不是很单呢Q就是让反病毒引擎不处理q个Zؓ的异常时q入d@环~Q!
W二道菜QTEB反跟t初?br />如果你的记性够好的话一定记得上面介l过的TEBQTIBQ线E信息块l构中有q么一句:
PVOID* pProcess; // 30h Pointer to owning process database
q?个偏Ud址处的内容非常有用Q它指向本线E的拥有者的 PDB(Process Database) 的线性地址。当你用动态调试器Q例 ?nbsp;OllyDbg 的时候,调试器是把调试的对象作ؓ一个子U程q行跟踪的,在这U情况下Q被调试的对象的“拥有?#8221;是调试器本w,也就是说Q它 ?nbsp;TEB ?nbsp;30h 处的偏移指向的内容肯定不?nbsp;0 Q这P我们可以利用这一点,判断 30h 偏移指向的内容,来判断是否有调试器跟t?nbsp;
最后给Z?nbsp;Anti-Debug 的例子程序,?nbsp;MASM ~译完成后,L OllyDbg 来加载调试一下,看看与正常的q行l果有什么不同?nbsp;
;*********************************************************
;E序名称Q演C利?nbsp;TEB l构q行 Anti-Debug
; L OllyDbg q行调试
;适用OSQWindows NT/2K/XP
;作者:|聪
;日期Q?003-2-9
;出处Q?img alt="::URL::" src="http://www.blogcn.com/images/aurl.gif" align="absBottom" border="0" hspace="2" />http://www.LuoCong.comQ老罗的缤U天圎ͼ
;注意事项Q如Ʋ{载,请保持本E序的完_q注明:
;转蝲?#8220;老罗的缤U天?#8221;Q?img alt="::URL::" src="http://www.blogcn.com/images/aurl.gif" align="absBottom" border="0" hspace="2" />http://www.LuoCong.comQ?/a>
;*********************************************************
.386
.model flat, stdcall
option casemap:none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/user32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/user32.lib
.data
szCaption db "Anti-Debug Demo by LC, 2003-2-9", 0
szDebugged db "Hah, let me guess... U r dEBUGGINg me! ", 0
szFine db "Good boy, no dEBUGGEr detected!", 0
.code
main:
assume fs:nothing
mov eax, fs:[30h] ;指向 PDB(Process Database)
movzx eax, byte ptr [eax + 2h]Q无W号数带零扩?br /> or al, al
jz _Fine
_Debugged:
push MB_OK or MB_ICONHAND
push offset szCaption
push offset szDebugged
jmp _Output
_Fine:
push MB_OK or MB_ICONINformATION
push offset szCaption
push offset szFine
_Output:
push NULL
call MessageBoxA
invoke ExitProcess, 0
end main
W三道菜Q利用SEH执行shellcode
假设异常处理例程入口00401053,E序刚开始执行时esp?012ffc4,以前的fs:[0]?012ffe0
建立了TIBl构的第一个成员后堆栈的情况如?
内存低地址
| E0 |12ffbc(esp)
| FF |
| 12 | --ERRl构的第一个成?br />|_00_|
| 53 |12ffc0
| 10 |
| 40 | --ERRl构的第二个成员
| 00 |
内存高地址
好了然后E序CALL一个函?函数里面有一个局部变量ƈ且在往其分配的I间中写入的数据时生溢?q时堆栈如下
____
| |12f000 局部变量分配的I间,q且?2ffc0方向溢出?
| |
....
....
|_EBP|12ffb4 函数中保存老的EBP
| xx |
| xx |
| xx |
|_EIP|12ffb8 call函数时EIPq栈
| xx |
| xx |
|_xx_|
| E0 |12ffbc(esp) {当SEH起作用的时候EBX刚好指向q个地址(也可说L指向当前ERRl构)}
| FF |
| 12 | --ERRl构的第一个成?br />|_00_|
| 53 |12ffc0
| 10 |
| 40 | --ERRl构的第二个成员
|_00_|
| |12ffc4
l?l看,假设溢出代码一直到?2ffc4,然后call的函数该q回?因ؓ保存的EIP被溢Z码代替所以程序出?不会不出错吧?),q样ESH开?起作用了(?在这期间pȝ要执行一些操?所以EBX才会指向当前ERR).q样一来程序就会蟩?2ffc0里的地址L??2ffc0里的东东 早已不是原来?0401053?q样我们不就改变了程序的向了么.12ffc0中该写入什么内容呢?应是内存中JMP EBX的代码的地址.q样??下后最l就会蟩?2ffbcL?q个四字节可是宝늚?img src="http://www.blogcn.com/images/smile.gif" alt="" border="0" hspace="2" vspace="2" />现在假设JMP EBXq个指o在内存中的地址?x77e33f4d
那下具体看一下现在堆栈的情况:
| EB |12ffbc(esp) {当ESH起作用的时候EBX刚好指向q个地址(也可说L指向当前ERRl构)}
| 06 |
| 90 | --ERRl构的第一个成?执行JMP EBX后就到这儿来执行?EB 06是短跌{JMP 12FFC4的机器码)
|_90_| 后面?0是nopI指令的机器?
| 4D |12ffc0
| 3F |
| E3 | --ERRl构的第二个成员,出错处理函数的入口地址(现在成了JMP EBX的地址)
|_77_|
| |12ffc4
....
好现在来看看12ffc4里面有些什么代?(单的说这D代码的作用是计真正的shellcode的v始地址,然后跌L?
低地址
| |12f000(shellcode开始地址)
....
....
| 81 |12ffc4
| C3 | add ebx,FFFFF03Ch(ebx=12ffc4,指o长度6,作用计算计算shellcode地址)
| 3C |
| F0 |
| FF |
| FF |
| FF |12ffca jmp ebx
| D3 |
高地址
试E序
-------------------------SEH.ASM------------------
.386
.model flat,stdcall
option casemap:none
include ../include/user32.inc
includelib ../lib/user32.lib
include ../include/kernel32.inc
includelib ../lib/kernel32.lib
.data
hello db '利用一个读INI文g的API来演CWIN2000本地溢出',0
lpFileName db './seh.ini',0
lpAppName db 'iam',0
lpKeyName db 'czy',0
lpDefault db 'ddd',0
szCap db "SEH TEST",0
szMsgOK db "OK,the exceptoin was handled by final handler!",0
szMsgERR1 db "It would never Get here!",0
.code
testov proc
local lpReturnedString[2224] : byte ;q回的字串搞成本地变量这样就和C语言一样了,它是在栈?nbsp;
invoke GetPrivateProfileString,offset lpAppName,offsetQlpKeyName,offset lpDefault,ADDR lpReturnedString,2249,offset lpFileName
invoke MessageBox,0,addr lpReturnedString,addr lpReturnedString,1
ret
testov endp
start:
ASSUME fs:NOTHING
invoke MessageBox,0,addr szMsgERR1,addr szCap,30h+1000h ;下断?nbsp;
push offset Final_Handler ;压入正常的出错处理程序入口地址
push FS:[0] ;把前一个TIB的地址压入
mov fs:[0],esp
call testov
pop fs:[0] ;q原FS:[0]
Final_Handler: ;׃溢出了下面的代码不会被执?
invoke MessageBox,0,addr szMsgOK,addr szCap,30h
invoke ExitProcess,0
mov eax,1
ret
end start
P?br />-----------------end-------------
1如何更好的在内存中找JMP EBX的代?
在softice中执行S 10:0 L FFFFFFFF FF D3可以了,但实际上q样扑ֈ?br />地址可能不能执行代码.所以用下面的方?
map32 kernel32(在当前进E中查找映射的kernel32 DLL的信?
一般有如下昄:
Owner Obj Name Obj# Address Size TYPE
kernel32 .text 0001 001b:77b61000 0005d1ae code RO
......
然后
S 77b61000 L 5d1ae FF D3
如果昄如下说明扑ֈ?
Pattern Found at 0023:77e61674 ....
2)关于~冲区的大小的问?
利用SEH的办法就L要设?000个字节多,你的shellcode才不会被不知哪来的数据覆?
q道菜czy做的不好吃:Q我感觉理解h有些困难~Q因为关于缓冲区溢出自己接触的太,不过好东西要保留的,以后回过头看Q?br />W四道菜Q用 SEH 技术实?nbsp;API Hook
q一部分不想展开了,l大家一个链接吧?br />http://www.luocong.com/articles/show_article.asp?Article_ID=25
最后作为结束语说说的缺点吧Q)一个h只有正视自己的缺Ҏ(gu)能不断地q步Q呵?br />?SEH异常处理链中最后一个被装蝲的SEH异常处理E序L被第一个调用,x如果自己׃一个星期才写出来一个异常处理程序,能够完美处理所有异?q?希望异常全部׃来处?但很不幸,比如你调用了一个外部模?而这个模块自己安装了一个ugly的seh处理例程,他的动作是只要有异常发生q单地l?止程序,哈哈Q那死(zhn)?zhn)了。又比如你想在你的加壳程序里面加密目标程序代码段,然后发生无效指o异常的时候用你自己安装的处理句柄来解密代码段l箋??听v来这的确是一个好L,但遗憄是大多数C/C++代码都用_try{}_except{}块来保证其正运?而这些异常处理例E是在你x?的例E之后安装的,因而也在铄前面,无效指o一执行,首先是C/C++~译器本w提供的处理例程或者程序其他的异常处理例程来处?可能单结束程?或?...
好篏Q~~~~~~
写了两天Q错了,应该是剪?消化了两天,有很多的E序和文字是从hume,老罗Q还有czy那里“剽窃”的:Q希望高手们不要生气~~天下书籍一大抄。你们的必将是我的,当然我的也会׃nl你们的。呵呵,现在q不行,U别不够啊?/div>转自:
]]>
a. 配置可执行文件目?E:\WinDDK\7600.16385.1\bin\x86;
b. 配置包含目录QE:\WinDDK\7600.16385.1\inc\ddk
E:\WinDDK\7600.16385.1\inc\crt
E:\WinDDK\7600.16385.1\inc\api
c. 配置库目? E:\WinDDK\7600.16385.1\lib\win7\i3865
目标文g扩展名:.sys //必?
6. 讄C/C++选项
常规选项?/span>
1 调试信息格式(C7 兼容(/Z7) //可?
2 警告{ Q? U?/W2) //可?
3 警告视为错?nbsp; (?/wx) //可?
优化选项?/span>
优化(用/Od) //可?
预处理器
预处理器定义QWIN32=100;_X86_=1;WINVER=0x501;DBG=1 //必?
代码生成
启用最重新生成:?nbsp; //可?nbsp;
q行时库Q多U程调试(/MTd) ?nbsp; 多线E?/MT) //?span color="#0000ff" style="color: #0000ff;"> <本h选择的是多线E调?/MTd)>
~冲区安全检查:?nbsp; //可?
(可避免出?nbsp; LINK : error LNK2001: 无法解析外部W号 __security_cookie)
高
调用U定 __stdcall(/Gz) //必?
7. 链接器设|?
常规
启用增量链接Q否(/INCREMENTAL:NO) // 选上
忽略导入库:?nbsp; // 可?nbsp;
( 讄为此值时Q必d附加库目录中加: E:\WinDDK\7600.16385.1\lib\win7\i3865 q样目׃会依?IDE 环境的设 |?
输入
附加依赖?
ntoskrnl.lib;Hal.lib;wdm.lib;wdmsec.lib;wmilib.lib;ndis.lib;MSVCRT.LIB;LIBCMT.LIB //必?nbsp;
( HalXXX 函数在Hal.libQ?WmiXXX 函数?nbsp; wmilib.lib Q?NdisXXX函数?nbsp; ndis.lib )
( 必要旉要增加微软的标准?nbsp; MSVCRT.LIB MSVCRTD.LIB(调试? LIBCMT.LIBIBCMTD.LIB(调试? )
( 如果源码中有 source 文gQ那么该文g?nbsp; TARGETLIBS 字段会列?目需要的?nbsp; )
启用用户账户控制QUACQ?nbsp; ?nbsp; //必?nbsp;
调试Q?
生成调试信息 ?/DEBUG) //可?
生成映像文gQ是(/MAP) //可?
映像文g名:$(TargetDir)$(TargetName).map //可?
pȝ(System)
子系l? 控制?/SUBSYSTEM:CONSOLE) //必?
堆栈保留大小Q?194304 //可?
堆栈提交大小Q?nbsp; 4096 //可?
驱动E序: 驱动E序(/DRIVER) //必?nbsp;
高Q?/span>
入口点:DriverEntry //必?
随机基址:清空 //把框里的数据删掉。(yes也不是no也不是就是要一个干q净净的文本框Q?/span> //必?
不然会出?nbsp; e:\xxx.sys : fatal error LNK1295:
“/DYNAMICBASE”?#8220;/DRIVER”规范不兼容;链接时不使用“/DYNAMICBASE”
数据执行保护(DEP): 清空 //把框里的数据删掉。(yes也不是no也不是就是要一个干q净净的文本框Q?/span> //必?nbsp;
不然会出?nbsp; e:\xxx.sys : fatal error LNK1295:
“/NXCOMPAT:NO”?#8220;/DRIVER”规范不兼容;链接时不使用“/NXCOMPAT:NO”
命o行:/SECTION:INIT,D /IGNORE:4078 Q徏议不要写q去Q会报错Q)
DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
return STATUS_UNSUCCESSFUL;
}
]]>
]]>
Windows NT
3.1引入了一U名为PE文g格式的新可执行文件格式。PE文g格式的规范包含在了MSDN的CD中(Specs and Strategy,
Specifications, Windows NT File Format SpecificationsQ,但是它非怹晦ӆ?
然而这一的文档ƈ未提供够的信息Q所以开发者们无法很好地弄懂PE格式。本文旨在解册一问题Q它会对整个的PE文g格式作一个十分彻底的解释Q另外,本文中还带有Ҏ(gu)有必需l构的描qC及示范如何用这些信息的源码CZ?
Z获得PE文g中所包含的重要信息,我编写了一个名为PEFILE.DLL的动态链接库Q本文中所有出现的源码CZ亦均摘自于此。这个DLL和它的源?
码都作ؓPEFileCZE序的一部分包含在了CD中(译注Q示例程序请在MSDN中寻找,本站恕不提供Q,你可以在你自q应用E序中用这个DLLQ?
同样Q你亦可以依你所愿地使用q构建它的源码。在本文末尾Q你会找到PEFILE.DLL的函数导出列表和一个如何用它们的说明。我觉得你会发现q些?
C让你从容应付PE文g格式的?
介绍
Windows操作pȝ家族最q增加的Windows
NT为开发环境和应用E序本n带来了很大的改变Q这之中一个最为重大的当属PE文g格式了。新的PE文g格式主要来自于UNIX操作pȝ所通用的COFF
规范Q同时ؓ了保证与旧版本MS-DOS及Windows操作pȝ的兼容,PE文g格式也保留了MS-DOS中那熟?zhn)的MZ头部?
在本文之中,PE文g格式是以自顶而下的顺序解释的。在你从头开始研I文件内容的q程之中Q本文会详细讨论PE文g的每一个组成部分?
许多单独的文件成分定义都来自于Microsoft Win32
SDK开发包中的WINNT.H文gQ在q个文g中你会发现用来描q文件头部和数据目录{各U成分的l构cd定义。但是,在WINNT.H中缺对PE?
件结构够的定义Q在q种情况下,我定义了自己的结构来存取文g数据。你会在PEFILE.DLL工程的PEFILE.H中找到这些结构的定义Q整套的
PEFILE.H开发文件包含在PEFileCZE序之中?
本文配套的示例程序除了PEFILE.DLLCZ代码之外Q还有一个单独的Win32CZ应用E序Q名为EXEVIEW.EXE。创一CZ目的有二Q?
首先Q我需要测试PEFILE.DLL的函敎ͼq且某些情况要求我同时查看多个文Ӟ其次Q很多解决PE文g格式的工作和直接观看数据有关。例如,要弄?
导入地址名称表是如何构成的,我就得同时查?idataD头部、导入映像数据目录、可选头部以及当前的.idataD实体,而EXEVIEW.EXE?
是查看这些信息的最佳示例?
闲话叙Q让我们开始吧?
PE文gl构
PE文g格式被组lؓ一个线性的数据,它由一个MS-DOS头部开始,接着是一个是模式的程序残余以及一个PE文g标志Q这之后紧接着PE文g头和可?
头部。这些之后是所有的D头部,D头部之后跟随着所有的D实体。文件的l束处是一些其它的区域Q其中是一些杂的信息Q包括重分配信息、符可信息、行?
信息以及字串表数据。我所有这些成分列于图1?br>
?.PE文g映像l构
从MS-DOS文g头结构开始,我将按照PE文g格式各成分的出现序依次对其q行讨论Qƈ且讨论的大部分是以示例代码ؓ基础来示范如何获得文件的信息
的。所有的源码均摘自PEFILE.DLL模块的PEFILE.C文g。这些示例都利用了Windows
NT最L特色之一——内存映文Ӟq一特色允许用户使用一个简单的指针来存取文件中所包含的数据,因此所有的CZ都用了内存映射文g来存取PE文g
中的数据?
注意Q请查阅本文末尾关于如何使用PEFILE.DLL的那一Dc?
MS-DOS头部/实模式头?/strong>
如上所qͼPE文g格式的第一个组成部分是MS-DOS头部。在PE文g格式中,它ƈ非一个新概念Q因为它与MS-DOS
2.0以来已有的MS-DOS头部是完全一L。保留这个相同结构的最主要原因是,当你试在Windows 3.1以下或MS-DOS
2.0以上的系l下装蝲一个文件的时候,操作pȝ能够dq个文gq明白它是和当前pȝ不相兼容的。换句话_当你在MS-DOS
6.0下运行一个Windows NT可执行文件时Q你会得到这样一条消息:“This program cannot be run in DOS
mode.”如果MS-DOS头部不是作ؓPE文g格式的第一部分的话Q操作系l装载文件的时候就会失败,q提供一些完全没用的信息Q例如:“The
name specified is not recognized as an internal or external command,
operable program or batch file.”
MS-DOS头部占据了PE文g的头64个字节,描述它内容的l构如下Q?
//WINNT.H
typedef struct _IMAGE_DOS_HEADER { // DOS?EXE头部
USHORT e_magic; // 术数字
USHORT e_cblp; // 文g最后页的字节数
USHORT e_cp; // 文g|
USHORT e_crlc; // 重定义元素个?
USHORT e_cparhdr; // 头部寸Q以D落为单?
USHORT e_minalloc; // 所需的最附加段
USHORT e_maxalloc; // 所需的最大附加段
USHORT e_ss; // 初始的SS|相对偏移量)
USHORT e_sp; // 初始的SP?
USHORT e_csum; // 校验?
USHORT e_ip; // 初始的IP?
USHORT e_cs; // 初始的CS|相对偏移量)
USHORT e_lfarlc; // 重分配表文g地址
USHORT e_ovno; // 覆盖?
USHORT e_res[4]; // 保留?
USHORT e_oemid; // OEM标识W(相对e_oeminfoQ?
USHORT e_oeminfo; // OEM信息
USHORT e_res2[10]; // 保留?
LONG e_lfanew; // 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
W一个域e_magicQ被UCؓ术数字Q它被用于表CZ
个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文g都将q个D?x5A4DQ表CASCII字符MZ。MS-DOS头部之所以有的时?
被称为MZ头部Q就是这个缘故。还有许多其它的域对于MS-DOS操作pȝ来说都有用,但是对于Windows
NT来说Q这个结构中只有一个有用的域——最后一个域e_lfnewQ一?字节的文件偏U量QPE文g头部是由它定位的。对于Windows
NT的PE文g来说QPE文g头部是紧跟在MS-DOS头部和实模式E序D余之后的?
实模式残余程?/strong>
实模式残余程序是一个在装蝲时能够被MS-DOSq行的实际程序。对于一个MS-DOS的可执行映像文gQ应用程序就是从q里执行的。对?
Windows、OS/2、Windows
NTq些操作pȝ来说QMS-DOSD余E序׃替了ȝ序的位置被放在这里。这U残余程序通常什么也不做Q而只是输Z行文本,例如Q?#8220;This
program requires Microsoft Windows v3.1 or
greater.”当然Q用户可以在此放入Q何的D余E序Q这意味着你可能经常看到像q样的东西:“You can''t run a Windows
NT application on OS/2, it''s simply not possible.”
当ؓWindows
3.1构徏一个应用程序的时候,链接器将向你的可执行文g中链接一个名为WINSTUB.EXE的默认残余程序。你可以用一个基于MS-DOS的有效程?
取代WINSTUBQƈ且用STUB模块定义语句指示链接器,q样p够取代链接器的默认行为。ؓWindows
NT开发的应用E序可以通过使用-STUB:链接器选项来实现?
PE文g头部与标?/strong>
PE文g头部是由MS-DOS头部的e_lfanew域定位的Q这个域只是l出了文件的偏移量,所以要定PE头部的实际内存映地址Q就需要添加文件的内存映射基地址。例如,以下的宏是包含在PEFILE.H源文件之中的Q?
//PEFILE.H
#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)->e_lfanew))
在处理PE文g信息的时候,我发现文件之中有些位|需要经常查阅。既然这些位|仅仅是Ҏ(gu)件的偏移量,那么用宏来实现这些定位就比较Ҏ(gu)Q因为它们较之函数有更好的表现?
h意这个宏所获得的是PE文g标志Q而ƈ非PE文g头部的偏U量。那是由于自Windows与OS/2的可执行文g开始,.EXE文g都被赋予了目标操
作系l的标志。对于Windows
NT的PE文g格式而言Q这一标志在PE文g头部l构之前。在Windows和OS/2的某些版本中Q这一标志是文件头的第一个字。同P对于PE文g?
式,Windows NT使用了一个DWORD倹{?
以上的宏q回了文件标志的偏移量,而不它是哪U类型的可执行文件。所以,文g头部是在DWORD标志之后Q还是在WORD标志处,是由q个标志是否
Windows NT文g标志所军_的。要解决q个问题Q我~写了ImageFileType函数Q如下)Q它q回了映像文件的cdQ?
//PEFILE.C
DWORD WINAPI ImageFileType (LPVOID lpFile)
{
/* 首先出现的是DOS文g标志 */
if (*(USHORT *)lpFile == IMAGE_DOS_SIGNATURE)
{
/* 由DOS头部军_PE文g头部的位|?*/
if (LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
IMAGE_OS2_SIGNATURE ||
LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
IMAGE_OS2_SIGNATURE_LE)
return (DWORD)LOWORD(*(DWORD *)NTSIGNATURE (lpFile));
else if (*(DWORD *)NTSIGNATURE (lpFile) ==
IMAGE_NT_SIGNATURE)
return IMAGE_NT_SIGNATURE;
else
return IMAGE_DOS_SIGNATURE;
}
else
/* 不明文gU类 */
return 0;
}
以上列出的代码立卛_诉了你NTSIGNATURE宏有多么有用。对于比较不同文件类型ƈ且返回一个适当的文件种cL_q个宏就会ɘq两件事变得非常单。WINNT.H之中定义的四U不同文件类型有Q?
//WINNT.H
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_OS2_SIGNATURE 0x454E // NE
#define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
首先QWindows的可执行文gcd没有出现在这一列表中,q一点看h很奇怪。但是,在稍微研I一下之后,p得到原因了:除了操作
pȝ版本规范的不同之外,Windows的可执行文g和OS/2的可执行文g实在没有什么区别。这两个操作pȝ拥有相同的可执行文gl构?
现在把我们的注意力{向Windows NT PE文g格式Q我们会发现只要我们得到了文件标志的位置QPE文g之后׃?个字节相跟随。下一个宏标识了PE文g的头部:
//PEFILE.C
#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)->e_lfanew + \
SIZE_OF_NT_SIGNATURE))
q个宏与上一个宏的唯一不同是这个宏加入了一个常量SIZE_OF_NT_SIGNATURE。不q的是,q个帔Rq未定义在WINNT.H之中Q于是我它定义在了PEFILE.H中,它是一个DWORD的大?
既然我们知道了PE文g头的位置Q那么就可以查头部的数据了。我们只需要把q个位置赋值给一个结构,如下Q?
PIMAGE_FILE_HEADER pfh;
pfh = (PIMAGE_FILE_HEADER)PEFHDROFFSET(lpFile);
在这个例子中QlpFile表示一个指向可执行文g内存映像基地址的指针,q就昑և了内存映文件的好处Q不需要执行文件的I/OQ只需使用指针pfhp存取文g中的信息。PE文g头结构被定义为:
//WINNT.H
typedef struct _IMAGE_FILE_HEADER {
USHORT Machine;
USHORT NumberOfSections;
ULONG TimeDateStamp;
ULONG PointerToSymbolTable;
ULONG NumberOfSymbols;
USHORT SizeOfOptionalHeader;
USHORT Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
#define IMAGE_SIZEOF_FILE_HEADER 20
h意这个文件头部的大小已经定义在这个包含文件之中了Q这样一来,惌得到q个l构的大就很方便了。但是我觉得对结构本w?
sizeofq算W(译注Q原文ؓ“function”Q更单一些,因ؓq样的话我就不必Cq个帔R的名?
IMAGE_SIZEOF_FILE_HEADERQ而只需要记住结构IMAGE_FILE_HEADER的名字就可以了。另一斚wQ记住所有结构的名字
已经够有挑战性的了,其在是q些l构只有WINNT.H中才有的情况下?
PE文g中的信息基本上是一些高U信息,q些信息是被操作pȝ或者应用程序用来决定如何处理这个文件的。第一个域是用来表C个可执行文g被构建的目标?
器种c,例如DEC(R) Alpha、MIPS R4000、Intel(R)
x86或一些其它处理器。系l用这一信息来在dq个文g的其它数据之前决定如何处理它?
Characteristics域表CZ文g的一些特征。比如对于一个可执行文g而言Q分调试文件是如何操作的。调试器通常使用的方法是调试信息从
PE文g中分,q保存到一个调试文Ӟ.DBGQ中。要q么做的话,调试器需要了解是否要在一个单独的文g中寻找调试信息,以及q个文g是否已经调?
信息分离了。我们可以通过深入可执行文件ƈL调试信息的方法来完成q一工作。要使调试器不在文g中查扄话,需要用?
IMAGE_FILE_DEBUG_STRIPPEDq个特征Q它表示文g的调试信息是否已l被分离了。这样一来,调试器可以通过快速查看PE文g的头?
的方法来军_文g中是否存在着调试信息?
WINNT.H定义了若q其它表C文件头信息的标讎ͼ和以上的例子差不多。我把研I这些标记的事情留给读者作为练习,׃们来看看它们是不是很有趣Q这些标C于WINNT.H中的IMAGE_FILE_HEADERl构之后?
PE文g头结构中另一个有用的入口是NumberOfSections域,它表C如果你要方便地提取文g信息的话Q就需要了解多个D——更明确一Ҏ(gu)
_有多个D头部和多少个段实体。每一个段头部和段实体都在文g中连l地排列着Q所以要军_D头部和D实体在哪里l束的话Q段的数目是必需的。以下的?
CPE文g头中提取了段的数目:
PEFILE.C
int WINAPI NumOfSections(LPVOID lpFile)
{
/* 文g头部中所表示出的D|?*/
return (int)((PIMAGE_FILE_HEADER)
PEFHDROFFSET (lpFile))->NumberOfSections);
}
如你所见,PEFHDROFFSET以及其它宏用h非常方便?br>
PE可选头?/strong>
PE可执行文件中接下来的224个字节组成了PE可选头部。虽然它的名字是“可选头?#8221;Q但是请信Q这个头部ƈ?#8220;可?#8221;Q而是“必需”的。OPTHDROFFSET宏可以获得指向可选头部的指针Q?
//PEFILE.H
#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)->e_lfanew + \
SIZE_OF_NT_SIGNATURE + \
sizeof(IMAGE_FILE_HEADER)))
可选头部包含了很多关于可执行映像的重要信息Q例如初始的堆栈大小、程序入口点的位|、首选基地址、操作系l版本、段寚w的信息等{。IMAGE_OPTIONAL_HEADERl构如下Q?
//WINNT.H
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// 标准?
//
USHORT Magic;
UCHAR MajorLinkerVersion;
UCHAR MinorLinkerVersion;
ULONG SizeOfCode;
ULONG SizeOfInitializedData;
ULONG SizeOfUninitializedData;
ULONG AddressOfEntryPoint;
ULONG BaseOfCode;
ULONG BaseOfData;
//
// NT附加?
//
ULONG ImageBase;
ULONG SectionAlignment;
ULONG FileAlignment;
USHORT MajorOperatingSystemVersion;
USHORT MinorOperatingSystemVersion;
USHORT MajorImageVersion;
USHORT MinorImageVersion;
USHORT MajorSubsystemVersion;
USHORT MinorSubsystemVersion;
ULONG Reserved1;
ULONG SizeOfImage;
ULONG SizeOfHeaders;
ULONG CheckSum;
USHORT Subsystem;
USHORT DllCharacteristics;
ULONG SizeOfStackReserve;
ULONG SizeOfStackCommit;
ULONG SizeOfHeapReserve;
ULONG SizeOfHeapCommit;
ULONG LoaderFlags;
ULONG NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
如你所见,q个l构中所列出的域实在是冗长得q分。ؓ了不让你Ҏ(gu)有这些域感到厌烦Q我会仅仅讨论有用的——就是说Q对于探IPE文g格式而言有用的?
标准?/strong>
首先Q请注意q个l构被划分ؓ“标准?#8221;?#8220;NT附加?#8221;。所谓标准域Q就是和UNIX可执行文件的COFF格式所公共的部分。虽然标准域保留了COFF中定义的名字Q但是Windows NT仍然它们用作了不同的目的——尽换个名字更好一些?
·Magic。我不知道这个域是干什么的Q对于示例程序EXEVIEW.EXECZE序而言Q这个值是0x010B?67Q译注:0x010B?EXEQ?x0107为ROM映像Q这个信息我是从eXeScope上得来的Q?
·MajorLinkerVersion、MinorLinkerVersion。表C链接此映像的链接器版本。随Window NT build 438配套的Windows NT SDK包含的链接器版本?.39Q十六进制ؓ2.27Q?
·SizeOfCode。可执行代码寸?
·SizeOfInitializedData。已初始化的数据寸?
·SizeOfUninitializedData。未初始化的数据寸?
·AddressOfEntryPoint。在标准域中QAddressOfEntryPoint域是对PE文g格式来说最为有的了。这个域表示应用E?
序入口点的位|。ƈ且,对于pȝ黑客来说Q这个位|就是导入地址表(IATQ的末尾。以下的函数C了如何从可选头部获得Windows
NT可执行映像的入口炏V?
//PEFILE.C
LPVOID WINAPI GetModuleEntryPoint(LPVOID lpFile)
{
PIMAGE_OPTIONAL_HEADER poh;
poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
if (poh != NULL)
return (LPVOID)poh->AddressOfEntryPoint;
else
return NULL;
}
·BaseOfCode。已载入映像的代码(“.text”D)的相对偏U量?
·BaseOfData。已载入映像的未初始化数据(“.bss”D)的相对偏U量?
Windows NT附加?/strong>
d到Windows NT PE文g格式中的附加域ؓWindows NT特定的进E行为提供了装蝲器的支持Q以下ؓq些域的概述?
·ImageBase。进E映像地址I间中的首选基地址。Windows NT的Microsoft Win32 SDK链接器将q个值默认设?x00400000Q但是你可以使用-BASE:linker开x变这个倹{?
·SectionAlignment。从ImageBase开始,每个D都被相l的装入q程的地址I间中。SectionAlignment则规定了装蝲时段能够占据的最空间数量——就是说Q段是关于SectionAlignment寚w的?
Windows NT虚拟内存理器规定,D对齐不能少于页寸Q当前的x86q_?096字节Q,q且必须是成倍的尺寸?096字节是x86链接器的默认|但是它可以通过-ALIGN: linker开x讄?
·FileAlignment。映像文仉先装载的最的信息块间隔。例如,链接器将一个段实体Q段的原始数据)加零扩展为文件中最接近?
FileAlignment边界。早先提及的2.39版链接器映像文件以0x200字节的边界对齐,q个值可以被强制改ؓ512?5535q么多?
·MajorOperatingSystemVersion。表CWindows NT操作pȝ的主版本P通常对Windows NT 1.0而言Q这个D设ؓ1?
·MinorOperatingSystemVersion。表CWindows NT操作pȝ的次版本P通常对Windows NT 1.0而言Q这个D设ؓ0?
·MajorImageVersion。用来表C应用程序的ȝ本号Q对于Microsoft Excel 4.0而言Q这个值是4?
·MinorImageVersion。用来表C应用程序的ơ版本号Q对于Microsoft Excel 4.0而言Q这个值是0?
·MajorSubsystemVersion。表CWindows NT Win32子系l的ȝ本号Q通常对于Windows NT 3.10而言Q这个D设ؓ3?
·MinorSubsystemVersion。表CWindows NT Win32子系l的ơ版本号Q通常对于Windows NT 3.10而言Q这个D设ؓ10?
·Reserved1。未知目的,通常不被pȝ使用Qƈ被链接器设ؓ0?
·SizeOfImage。表C入的可执行映像的地址I间中要保留的地址I间大小Q这个数字很大程度上受SectionAlignment的媄响。例
如,考虑一个拥有固定页寸4096字节的系l,如果你有一?1个段的可执行文gQ它的每个段都少?096字节Qƈ且关?5536字节边界寚wQ那
么SizeOfImage域将会被设ؓ11 * 65536 =
720896Q?76)。而如果一个相同的文g关于4096字节寚w的话Q那么SizeOfImage域的l果是11 * 4096 =
45056Q?1)。这只是个简单的例子Q它说明每个D需要少于一个页面的内存。在现实中,链接器通过个别地计每个段的方法来军_
SizeOfImage切的倹{它首先军_每个D需要多字节,q且最后将面L向上取整x接近的SectionAlignment边界Q然后L
是每个D个别需求之和了?
·SizeOfHeaders。这个域表示文g中有多少I间用来保存所有的文g头部Q包括MS-DOS头部、PE文g头部、PE可选头部以及PED头部。文件中所有的D实体就开始于q个位置?
·CheckSum。校验和是用来在装蝲旉证可执行文g的,它是由链接器讄q检验的。由于创些校验和的算法是U有信息Q所以在此不q行讨论?
·Subsystem。用于标识该可执行文件目标子pȝ的域。每个可能的子系l取值列于WINNT.H的IMAGE_OPTIONAL_HEADERl构之后?
·DllCharacteristics。用来表CZ个DLL映像是否E和U程的初始化及终止包含入口点的标记?
·SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve?
SizeOfHeapCommit。这些域控制要保留的地址I间数量Qƈ且负责栈和默认堆的申诗在默认情况下,栈和堆都拥有1个页面的甌g?6?
面的保留倹{这些值可以用链接器开?STACKSIZE:?HEAPSIZE:来设|?
·LoaderFlags。告知装载器是否在装载时中止和调试,或者默认地正常q行?
·NumberOfRvaAndSizes。这个域标识了接下来的DataDirectory数组。请注意它被用来标识q个数组Q而不是数l中的各个入口数字,q一炚w帔R要?
·DataDirectory。数据目录表C文件中其它可执行信息重要组成部分的位置。它事实上就是一个IMAGE_DATA_DIRECTORYl构的数l,位于可选头部结构的末尾。当前的PE文g格式定义?6U可能的数据目录Q这之中?1U现在在使用中?
数据目录
WINNT.H之中所定义的数据目录ؓQ?
//WINNT.H
// 目录入口
// 导出目录
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0
// 导入目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
// 资源目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
// 异常目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
// 安全目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
// 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
// 调试目录
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
// 描述字串
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7
// 机器|MIPS GPQ?
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
// TLS目录
#define IMAGE_DIRECTORY_ENTRY_TLS 9
// 载入配置目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
基本上,每个数据目录都是一个被定义为IMAGE_DATA_DIRECTORY的结构。虽然数据目录入口本w是相同的,但是每个特定的目录种cd是完全唯一的。每个数据目录的定义在本文的以后部分被描qCؓ“预定义段”?
//WINNT.H
typedef struct _IMAGE_DATA_DIRECTORY {
ULONG VirtualAddress;
ULONG Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
每个数据目录入口指定了该目录的尺寸和相对虚拟地址。如果你要定义一个特定的目录的话Q就需要从可选头部中的数据目录数l中军_相对的地址Q?
然后使用虚拟地址来决定该目录位于哪个D中。一旦你军_了哪个段包含了该目录Q该D늚D头部就会被用于查找数据目录的精文件偏U量位置?
所以要获得一个数据目录的话,那么首先你需要了解段的概c我在下面会对其q行描述Q这个讨Z后还有一个有兛_何定位数据目录的CZ?
PE文gD?/strong>
PE文g规范q前ؓ止定义的那些头部以及一个名?#8220;D?#8221;的一般对象组成。段包含了文件的内容Q包括代码、数据、资源以及其它可执行信息Q每个段都有一?
头部和一个实体(原始数据Q。我在下面描述D头部的有关信息Q但是段实体则缺一个严格的文gl构。因此,它们几乎可以被链接器按Q何的Ҏ(gu)l织Q只?
它的头部填充了够能够解释数据的信息?
D头?/strong>
PE文g格式中,所有的D头部位于可选头部之后。每个段头部?0个字节长Qƈ且没有Q何的填充信息。段头部被定义ؓ以下的结构:
//WINNT.H
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
UCHAR Name[IMAGE_SIZEOF_SHORT_NAME];
union {
ULONG PhysicalAddress;
ULONG VirtualSize;
} Misc;
ULONG VirtualAddress;
ULONG SizeOfRawData;
ULONG PointerToRawData;
ULONG PointerToRelocations;
ULONG PointerToLinenumbers;
USHORT NumberOfRelocations;
USHORT NumberOfLinenumbers;
ULONG Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
你如何才能获得一个特定段的段头部信息Q既然段头部是被q箋的组lv来的Q而且没有一个特定的序Q那么段头部必须由名U来定位。以下的函数C了如何从一个给定了D名U的PE映像文g中获得一个段头部Q?
//PEFILE.C
BOOL WINAPI GetSectionHdrByName(LPVOID lpFile, IMAGE_SECTION_HEADER *sh, char *szSection)
{
PIMAGE_SECTION_HEADER psh;
int nSections = NumOfSections (lpFile);
int i;
if ((psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile))
!= NULL)
{
/* 由名U查找段 */
for (i = 0; i < nSections; i++)
{
if (!strcmp(psh->Name, szSection))
{
/* 向头部复制数?*/
CopyMemory((LPVOID)sh, (LPVOID)psh,
sizeof(IMAGE_SECTION_HEADER));
return TRUE;
}
else
psh++;
}
}
return FALSE;
}
q个函数通过SECHDROFFSET宏将W一个段头部定位Q然后它开始在所有段中@环,q将要寻扄D名U和每个D늚名称相比较,直到?
C正确的那一个ؓ止。当扑ֈ了段的时候,函数内存映像文件的数据复制C入函数的l构中,然后IMAGE_SECTION_HEADERl构的各域就
能够被直接存取了?
D头部的?/strong>
·Name。每个段都有一?字符长的名称域,q且W一个字W必L一个句炏V?
·PhysicalAddress或VirtualSize。第二个域是一个union域,现在已不使用了?
·VirtualAddress。这个域标识了进E地址I间中要装蝲q个D늚虚拟地址。实际的地址由将q个域的值加上可选头部结构中的ImageBase
虚拟地址得到。切讎ͼ如果q个映像文g是一个DLLQ那么这个DLL׃一定会装蝲到ImageBase要求的位|。所以一旦这个文件被装蝲q入了一个进
E,实际的ImageBase值应该通过使用GetModuleHandle来检验?
·SizeOfRawData。这个域表示了相对FileAlignment的段实体寸。文件中实际的段实体寸少于或{于
FileAlignment的整倍数。一旦映像被装蝲q入了一个进E的地址I间Q段实体的尺寸将会变得少于或{于FileAlignment的整倍数?
·PointerToRawData。这是一个文件中D实体位|的偏移量?
·PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers。这些域在PE格式中不使用?
·Characteristics。定义了D늚特征。这些值可以在WINNT.H及本光盘Q译注:MSDN的光盘)的PE格式规范中找到?
? 定义
0x00000020 代码D?
0x00000040 已初始化数据D?
0x00000080 未初始化数据D?
0x04000000 该段数据不能被缓?
0x08000000 该段不能被分?
0x10000000 ׃nD?
0x20000000 可执行段
0x40000000 可读D?
0x80000000 可写D?
定位数据目录
数据目录存在于它们相应的数据D中。典型地来说Q数据目录是D实体中的第一个结构,但不是必需的。由于这个缘故,如果你需要定位一个指定的数据目录的话Q就需要从D头部和可选头部中获得信息?
Z让这个过E简单一点,我编写了以下的函数来定位M一个在WINNT.H之中定义的数据目录?
// PEFILE.C
LPVOID WINAPI ImageDirectoryOffset(LPVOID lpFile,
DWORD dwIMAGE_DIRECTORY)
{
PIMAGE_OPTIONAL_HEADER poh;
PIMAGE_SECTION_HEADER psh;
int nSections = NumOfSections(lpFile);
int i = 0;
LPVOID VAImageDir;
/* 必须??NumberOfRvaAndSizes-1)之间 */
if (dwIMAGE_DIRECTORY >= poh->NumberOfRvaAndSizes)
return NULL;
/* 获得可选头部和D头部的偏移?*/
poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile);
/* 定位映像目录的相对虚拟地址 */
VAImageDir = (LPVOID)poh->DataDirectory
[dwIMAGE_DIRECTORY].VirtualAddress;
/* 定位包含映像目录的段 */
while (i++ < nSections)
{
if (psh->VirtualAddress <= (DWORD)VAImageDir &&
psh->VirtualAddress +
psh->SizeOfRawData > (DWORD)VAImageDir)
break;
psh++;
}
if (i > nSections)
return NULL;
/* q回映像导入目录的偏U量 */
return (LPVOID)(((int)lpFile +
(int)VAImageDir. psh->VirtualAddress) +
(int)psh->PointerToRawData);
}
该函数首先确认被h的数据目录入口数字,然后它分别获取指向可选头部和W一个段头部的两个指针。它从可选头部决定数据目录的虚拟地址Q?
然后它用这个值来军_数据目录定位在哪个段实体之中。如果适当的段实体已经被标识了Q那么数据目录特定的位置可以通过它的相对虚拟地址转换为文件中
地址的方法来扑ֈ?
]]>
1、基本概?br>
2、WINDOWS完成端口的特?br>
3、完成端口(Completion Ports Q相x据结构和创徏
4、完成端口线E的工作原理
5、Windows完成端口的实例代?br>
Linux的EPoll模型
1、ؓ什么select落后
2、内怸提高I(y)/O性能的新Ҏ(gu)epoll
3、epoll的优?br>
4、epoll的工作模?
5、epoll的用方?br>
6、Linux下EPOll~程实例
ȝ
摘要Q开发网l程序从来都不是一件容易的事情Q尽只需要遵守很的一些规?创徏socket,发vq接Q接受连接,发送和接受数据。真正的困难在于Q?
让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。利用Windowsq_完成端口q行重叠I/O的技术和Linux?.6版本的内怸
引入的EPOll技术,可以很方便在在在Windows和Linuxq_上开发出支持大量q接的网l服务程序。本文介l在Windows和Linuxq_
上用的完成端口和EPoll模型开发的基本原理Q同时给出实际的例子。本文主要关注C/Sl构的服务器端程序,因ؓ一般来_开发一个大定wQ具可扩?
性的winsockE序一般就是指服务E序?br>
1、基本概?br>
讑֤---windows操作pȝ上允讔R信的Q何东西,比如文g、目录、串行口、ƈ行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑盘、物?
盘{。绝大多C讑֤打交道的函数都是CreateFile/ReadFile/WriteFile{。所以我们不能看?*File函数只惛_文g
讑֤。与讑֤通信有两U方式,同步方式和异步方式。同步方式下Q当调用ReadFile函数Ӟ函数会等待系l执行完所要求的工作,然后才返回;异步方式
下,ReadFileq类函数会直接返回,pȝ自己d成对讑֤的操作,然后以某U方式通知完成操作?br>
重叠I/O----思义Q当你调用了某个函数Q比如ReadFileQ就立刻q回做自q其他动作的时候,同时pȝ也在对I/0讑֤q行你要求的?
作,在这D|间内你的E序和系l的内部动作是重叠的Q因此有更好的性能。所以,重叠I/O是用于异步方式下使用I/O讑֤的?
重叠I/O需要用的一个非帔R要的数据l构OVERLAPPED?br>
2、WINDOWS完成端口的特?br>
Win32重叠I/O(Overlapped
I/O)机制允许发v一个操作,然后在操作完成之后接受到信息。对于那U需要很长时间才能完成的操作来说Q重叠IO机制其有用Q因为发起重叠操作的U程
在重叠请求发出后可以自q做别的事情了。在WinNT和Win2000上,提供的真正的可扩展的I/O模型是使用完成端口QCompletion
PortQ的重叠I/O.完成端口---是一UWINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口?
可,q有讑֤内核对象、事件对象、告警I/0{。但是完成端口内部提供了U程池的理Q可以避免反复创建线E的开销Q同时可以根据CPU的个数灵zȝ军_
U程个数Q而且可以让减线E调度的ơ数从而提高性能其实cM于WSAAsyncSelect和select函数的机制更Ҏ(gu)兼容UnixQ但是难以实?
我们惌?#8220;扩展?#8221;。而且windows的完成端口机制在操作pȝ内部已经作了优化Q提供了更高的效率。所以,我们选择完成端口开始我们的服务器程序的
开发?br>
1、发h作不一定完成,pȝ会在完成的时候通知你,通过用户在完成端口上的等待,处理操作的结果。所以要有检查完成端口,取操作结果的U程。在完成端口
上守候的U程pȝ有优化,除非在执行的U程dQ不会有新的U程被激z,以此来减线E切换造成的性能代h(hun)。所以如果程序中没有太多的阻塞操作,没有必要
启动太多的线E,CPU数量的两倍,一般这h启动U程?br>
2、操作与相关数据的绑定方式:在提交数据的时候用户对数据打相应的标记Q记录操作的cdQ在用户处理操作l果的时候,通过查自己打的标记和pȝ的操作结果进行相应的处理?
3、操作返回的方式:一般操作完成后要通知E序q行后箋处理。但写操作可以不通知用户Q此时如果用户写操作不能马上完成Q写操作的相x据会被暂存到到非
交换~冲ZQ在操作完成的时候,pȝ会自动释攄冲区。此时发起完写操作,使用的内存就可以释放了。此时如果占用非交换~冲太多会ɾpȝ停止响应?br>
3、完成端口(Completion Ports Q相x据结构和创徏
其实可以把完成端口看成系l维护的一个队列,操作pȝ把重叠IO操作完成的事仉知攑ֈ该队列里Q由于是暴露
“操作完成”的事仉知Q所以命名ؓ“完成端口”QCOmpletion
PortsQ。一个socket被创建后Q可以在M时刻和一个完成端口联pv来?br>
完成端口相关最重要的是OVERLAPPED数据l构
typedef struct _OVERLAPPED {
ULONG_PTR Internal;//被系l内部赋|用来表示pȝ状?
ULONG_PTR InternalHigh;// 被系l内部赋|传输的字节数
union {
struct {
DWORD Offset;//和OffsetHigh合成一?4位的整数Q用来表CZ文g头部的多字节开?
DWORD OffsetHigh;//操作Q如果不是对文gI/O来操作,则必设定ؓ0
};
PVOID Pointer;
};
HANDLE hEvent;//如果不用,务必设?,否则误一个有效的Event句柄
} OVERLAPPED, *LPOVERLAPPED;
下面是异步方式用ReadFile的一个例?
OVERLAPPED Overlapped;
Overlapped.Offset=345;
Overlapped.OffsetHigh=0;
Overlapped.hEvent=0;
//假定其他参数都已l被初始?
ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);
q样完成了异步方式L件的操作Q然后ReadFile函数q回Q由操作pȝ做自q事情Q下面介l几个与OVERLAPPEDl构相关的函?
{待重叠I/0操作完成的函?
BOOL GetOverlappedResult (
HANDLE hFile,
LPOVERLAPPED lpOverlapped,//接受q回的重叠I/0l构
LPDWORD lpcbTransfer,//成功传输了多字节数
BOOL fWait //TRUE只有当操作完成才q回QFALSE直接q回Q如果操作没有完成,通过?/用GetLastError ( )函数会返回ERROR_IO_INCOMPLETE
);
宏HasOverlappedIoCompleted可以帮助我们试重叠I/0操作是否完成Q该宏对OVERLAPPEDl构的Internal成员q行了测试,查看是否{于STATUS_PENDING倹{?/p>
IN HANDLE FileHandle,
IN HANDLE ExistingCompletionPort,
IN ULONG_PTR CompletionKey,
IN DWORD NumberOfConcurrentThreads
);
通常创徏工作分两步:
W一步,创徏一个新的完成端口内核对象,可以使用下面的函敎ͼ
HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)
{
return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);
};
W二步,刚创徏的完成端口和一个有效的讑֤句柄兌hQ可以用下面的函数Q?br>
bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)
{
HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
return h==hCompPort;
};
说明
1Q?CreateIoCompletionPort函数也可以一ơ性的既创建完成端口对象,又关联到一个有效的讑֤句柄
2Q?CompletionKey是一个可以自己定义的参数Q我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用Q最好要保证l构里面的内存不是分配在栈上Q除非你有十分的把握内存会保留到你要使用的那一刅R?br>
3Q?
NumberOfConcurrentThreads通常用来指定要允许同时运行的的线E的最大个数。通常我们指定?Q这Ll会Ҏ(gu)CPU的个数来?
动确定。创建和兌的动作完成后Q系l会完成端口关联的讑֤句柄、完成键作ؓ一条纪录加入到q个完成端口的设备列表中。如果你有多个完成端口,׃有多
个对应的讑֤列表。如果设备句柄被关闭Q则表中自动删除该纪录?br>
4、完成端口线E的工作原理
完成端口可以帮助我们理U程池,但是U程池中的线E需要我们用_beginthreadex来创建,凭什么通知完成端口理我们的新U程呢?{案在函数GetQueuedCompletionStatus。该函数原型Q?
BOOL GetQueuedCompletionStatus(
IN HANDLE CompletionPort,
OUT LPDWORD lpNumberOfBytesTransferred,
OUT PULONG_PTR lpCompletionKey,
OUT LPOVERLAPPED *lpOverlapped,
IN DWORD dwMilliseconds
);
q个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候,完成队列中才有纪录。凡是调用这个函数的U程被攑օ到完?
端口的等待线E队列中Q因此完成端口就可以在自qU程池中帮助我们l护q个U程。完成端口的I/0完成队列中存放了当重叠I/0完成的结?---
一条纪录,该纪录拥有四个字D,前三就对应GetQueuedCompletionStatus函数???参数Q最后一个字D|错误信息
dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作?
当I/0完成队列中出CU录Q完成端口将会检查等待线E队列,该队列中的线E都是通过调用GetQueuedCompletionStatus函数使自
己加入队列的。等待线E队列很单,只是保存了这些线E的ID。完成端口会按照后进先出的原则将一个线E队列的ID攑օ到释攄E列表中Q同时该U程从
{待GetQueuedCompletionStatus函数q回的睡眠状态中变ؓ可调度状态等待CPU的调度。所以我们的U程要想成ؓ完成端口理的线
E,必要调用GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还l护了一个暂停线E列表,具体l节可以参?
《Windows高~程指南》,我们现在知道的知识,已经_了?
完成端口U程间数据传递线E间传递数据最常用的办法是在_beginthreadex函数中将参数传递给U程函数Q或者用全局变量。但是完成端口还有自
q传递数据的Ҏ(gu)Q答案就在于CompletionKey和OVERLAPPED参数?br>
CompletionKey被保存在完成端口的设备表中,是和讑֤句柄一一对应的,我们可以与讑֤句柄相关的数据保存到CompletionKey中,
或者将CompletionKey表示为结构指针,q样可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和讑֤句柄的时候做Q因此不能在以后
动态改变?br>
OVERLAPPED参数是在每次调用ReadFileq样的支持重叠I/0的函数时传递给完成端口的。我们可以看刎ͼ如果我们不是Ҏ(gu)件设备做操作Q该
l构的成员变量就Ҏ(gu)们几乎毫无作用。我们需要附加信息,可以创徏自己的结构,然后OVERLAPPEDl构变量作ؓ我们l构变量的第一个成员,然后?
递第一个成员变量的地址lReadFile函数。因为类型匹配,当然可以通过~译。当GetQueuedCompletionStatus函数q回Ӟ?
们可以获取到W一个成员变量的地址Q然后一个简单的强制转换Q我们就可以把它当作完整的自定义l构的指针用,q样可以传递很多附加的数据了。太好了Q?
只有一点要注意Q如果跨U程传递,h意将数据分配到堆上,q且接收端应该将数据用完后释放。我们通常需要将ReadFileq样的异步函数的所需要的~?
冲区攑ֈ我们自定义的l构中,q样当GetQueuedCompletionStatus被返回时Q我们的自定义结构的~冲区变量中存放了I/0操作?
数据。CompletionKey和OVERLAPPED参数Q都可以通过GetQueuedCompletionStatus函数获得?br>
U程的安全退?br>
很多U程Z不止一ơ的执行异步数据处理Q需要用如下语?br>
while (true)
{
......
GetQueuedCompletionStatus(...);
......
}
那么如何退出呢Q答案就在于上面曾提到的PostQueudCompletionStatus函数Q我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址Q里面包含一个状态变量,当状态变量ؓ退出标志时Q线E就执行清除动作然后退出?br>
5、Windows完成端口的实例代码:
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
ULONG_PTR *PerHandleKey;
OVERLAPPED *Overlap;
OVERLAPPEDPLUS *OverlapPlus,
*newolp;
DWORD dwBytesXfered;
while (1)
{
ret = GetQueuedCompletionStatus(
hIocp,
&dwBytesXfered,
(PULONG_PTR)&PerHandleKey,
&Overlap,
INFINITE);
if (ret == 0)
{
// Operation failed
continue;
}
OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);
switch (OverlapPlus->OpCode)
{
case OP_ACCEPT:
// Client socket is contained in OverlapPlus.sclient
// Add client to completion port
CreateIoCompletionPort(
(HANDLE)OverlapPlus->sclient,
hIocp,
(ULONG_PTR)0,
0);
// Need a new OVERLAPPEDPLUS structure
// for the newly accepted socket. Perhaps
// keep a look aside list of free structures.
newolp = AllocateOverlappedPlus();
if (!newolp)
{
// Error
}
newolp->s = OverlapPlus->sclient;
newolp->OpCode = OP_READ;
// This function divpares the data to be sent
PrepareSendBuffer(&newolp->wbuf);
ret = WSASend(
newolp->s,
&newolp->wbuf,
1,
&newolp->dwBytes,
0,
&newolp.ol,
NULL);
if (ret == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error
}
}
// Put structure in look aside list for later use
FreeOverlappedPlus(OverlapPlus);
// Signal accept thread to issue another AcceptEx
SetEvent(hAcceptThread);
break;
case OP_READ:
// Process the data read
// Repost the read if necessary, reusing the same
// receive buffer as before
memset(&OverlapPlus->ol, 0, sizeof(OVERLAPPED));
ret = WSARecv(
OverlapPlus->s,
&OverlapPlus->wbuf,
1,
&OverlapPlus->dwBytes,
&OverlapPlus->dwFlags,
&OverlapPlus->ol,
NULL);
if (ret == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error
}
}
break;
case OP_WRITE:
// Process the data sent, etc.
break;
} // switch
} // while
} // WorkerThread
Linux 2.6内核中提高网lI/O性能的新Ҏ(gu)-epoll I/O多\复用技术在比较多的TCP|络服务器中有用,x较多的用到select函数?br>
1、ؓ什么select落后
首先Q在Linux内核中,select所用到的FD_SET是有限的Q即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个敎ͼ在我用的2.6.15-25-386内核中,该值是1024Q搜索内核源代码得到Q?br>
include/linux/posix_types.h:#define __FD_SETSIZE 1024
也就是说Q如果想要同时检?025个句柄的可读状态是不可能用select实现的。或者同时检?025个句柄的可写状态也是不可能的。其ơ,内核中实
现select是用轮询Ҏ(gu)Q即每次都会遍历所有FD_SET中的句柄Q显Ӟselect函数执行旉与FD_SET中的句柄个数有一个比例关p,
即select要检的句柄数越多就会越Ҏ(gu)。当Ӟ在前文中我ƈ没有提及pollҎ(gu)Q事实上用select的朋友一定也试过pollQ我个h觉得
select和poll大同异Q个人偏好于用select而已?/p>
epoll是什么?按照man手册的说法:是ؓ处理大批量句柄而作了改q的poll。要使用epoll只需要这三个pȝ调用Qepoll_create(2)Q?epoll_ctl(2)Q?epoll_wait(2)?br>
当然Q这不是2.6内核才有的,它是?.5.44内核中被引进?epoll(4) is a new API introduced in Linux kernel 2.5.44)
先介l?本书《The Linux Networking Architecture--Design and Implementation of
Network Protocols in the Linux Kernel》,?.4内核讲解Linux
TCP/IP实现Q相当不?作ؓ一个现实世界中的实玎ͼ很多时候你必须作很多权衡,q时候参考一个久l考验的系l更有实际意义。D个例?linux?
怸sk_buffl构Zq求速度和安全,牺牲了部分内存,所以在发送TCP包的时候,无论应用层数据多?sk_buff最也?72的字?其实
对于socket应用层程序来_另外一本书《UNIX Network Programming Volume
1》意义更大一?2003q的时候,q本书出了最新的W?版本Q不q主要还是修订第2版本。其中第6章《I/O
Multiplexing》是最重要的。Stevensl出了网lIO的基本模型。在q里最重要的莫q于select模型和Asynchronous
I/O模型.从理Z_AIOg是最高效的,你的IO操作可以立即q回Q然后等待os告诉你IO操作完成。但是一直以来,如何实现没有一个完的?
案。最著名的windows完成端口实现的AIO,实际上也是内部用U程池实现的|了Q最后的l果是IO有个U程池,你应用也需要一个线E池......
很多文档其实已经指出了这带来的线Econtext-switch带来的代仗在linux
q_上,关于|络AIO一直是改动最多的地方Q?.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布Q网l?
模块的AIO一直没有进入稳定内核版?大部分都是用用LE模拟方法,在用了NPTL的linux上面其实和windows的完成端口基本上差不?
??.6内核所支持的AIOҎ(gu)盘的AIO---支持io_submit(),io_getevents()以及对Direct
IO的支?是l过VFSpȝbuffer直接写硬盘,对于服务器在内存^Ex上有相当帮??br>
所以,剩下的select模型基本上就是我们在linux上面的唯一选择Q其实,如果加上no-block
socket的配|,可以完成一??AIO的实玎ͼ只不q推动力在于你而不是os而已。不q传l的select/poll函数有着一些无法忍受的~?
点,所以改q一直是2.4-2.5开发版本内核的dQ包?dev/pollQrealtime signal{等。最l,Davide
Libenzi开发的epollq入2.6内核成ؓ正式的解x?br>
3、epoll的优?/strong>
<1>支持一个进E打开大数目的socket描述W?FD)
select
最不能忍受的是一个进E所打开的FD是有一定限制的Q由FD_SETSIZE讄Q默认值是2048。对于那些需要支持的上万q接数目的IM服务器来说显
然太了。这时候你一是可以选择修改q个宏然后重新编译内核,不过资料也同时指样会带来|络效率的下降,二是可以选择多进E的解决Ҏ(gu)(传统?
ApacheҎ(gu))Q不q虽然linux上面创徏q程的代h较小Q但仍旧是不可忽视的Q加上进E间数据同步q比不上U程间同步的高效Q所以也不是一U完
的Ҏ(gu)。不q?
epoll则没有这个限Ӟ它所支持的FD上限是最大可以打开文g的数目,q个数字一般远大于2048,举个例子,?GB内存的机器上大约?0万左
叻I具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和pȝ内存关系很大?br>
<2>IO效率不随FD数目增加而线性下?br>
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合Q不q由于网lgӞM旉只有部分的socket?z跃"的,
但是select/poll每次调用都会U性扫描全部的集合Q导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"z跃"的socketq行
操作---q是因ؓ在内核实Cepoll是根据每个fd上面的callback函数实现的。那么,只有"z跃"的socket才会d的去调用
callback函数Q其他idle状态socket则不会,在这点上Qepoll实现了一??AIOQ因时候推动力在os内核。在一?
benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境Qepollq不比select/poll有什么效率,?
反,如果q多使用epoll_ctl,效率相比q有E微的下降。但是一旦用idle
connections模拟WAN环境,epoll的效率就q在select/poll之上了?br>
<3>使用mmap加速内怸用户I间的消息传递?br>
q点实际上涉及到epoll的具体实C。无论是select,pollq是epoll都需要内核把FD消息通知l用L_如何避免不必要的内存拯?
很重要,在这点上Qepoll是通过内核于用L间mmap同一块内存实现的。而如果你x一样从2.5内核关注epoll的话Q一定不会忘记手?
mmapq一步的?br>
<4>内核微调
q一点其实不epoll的优点了Q而是整个linuxq_的优炏V也怽可以怀疑linuxq_Q但是你无法回避linuxq_赋予你微调内核的能力?
比如Q内核TCP/IP协议栈用内存池理sk_buffl构Q那么可以在q行时期动态调整这个内存pool(skb_head_pool)的大?-
- 通过echo
XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参?TCP完成3ơ握?
的数据包队列长度)Q也可以Ҏ(gu)你^台内存大动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本w大却很小的特D系l上试最新的NAPI|?
卡驱动架构?br>
4、epoll的工作模?br>
令h高兴的是Q?.6内核的epoll比其2.5开发版本的/dev/epollz了许多Q所以,大部分情况下Q强大的东西往往是简单的。唯一有点ȝ是epoll?U工作方?LT和ET?br>
LT(level triggered)是缺省的工作方式Qƈ且同时支持block和no-block
socket.在这U做法中Q内核告诉你一个文件描q符是否qA了,然后你可以对q个qA的fdq行IO操作。如果你不作M操作Q内核还是会l箋通知?
的,所以,q种模式~程出错误可能性要一炏V传l的select/poll都是q种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block
socket。在q种模式下,当描q符从未qA变ؓqAӞ内核通过epoll告诉你。然后它会假设你知道文g描述W已l就l,q且不会再ؓ那个文g描述
W发送更多的qA通知Q直C做了某些操作D那个文g描述W不再ؓqA状态了(比如Q你在发送,接收或者接收请求,或者发送接收的数据于一定量时导?
了一个EWOULDBLOCK 错误Q。但是请注意Q如果一直不对这个fd作IO操作(从而导致它再次变成未就l?Q内怸会发送更多的通知(only
once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark认?br>
epoll只有epoll_create,epoll_ctl,epoll_wait 3个系l调用,具体用法请参?a target="_blank">http://www.xmailserver.org/linux-patches/nio-improve.html Q在http://www.kegel.com/rn/也有一个完整的例子Q大家一看就知道如何使用?br>
Leader/follower模式U程pool实现Q以及和epoll的配合?br>
5?epoll的用方?/strong>
首先通过create_epoll(int
maxfds)来创Z个epoll的句柄,其中maxfdsZepoll所支持的最大句柄数。这个函Cq回一个新的epoll句柄Q之后的所有操?
通过q个句柄来进行操作。在用完之后Q记得用close()来关闭这个创建出来的epoll句柄?
之后在你的网l主循环里面Q每一帧的调用epoll_wait(int epfd, epoll_event events, int max
events, int timeout)来查询所有的|络接口Q看哪一个可以读Q哪一个可以写了。基本的语法为:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd为用epoll_create创徏之后的句柄,events是一个epoll_event*的指针,当epoll_waitq个函数操作?
功之后,epoll_events里面储存所有的d事g。max_events是当前需要监听的所有socket句柄数。最后一个timeout?
epoll_wait的超Ӟ?的时候表C马上返回,?1的时候表CZ直等下去Q直到有事g范围QؓL正整数的时候表C等q么长的旉Q如果一直没
有事Ӟ则范围。一般如果网l主循环是单独的U程的话Q可以用-1来等Q这样可以保证一些效率,如果是和主逻辑在同一个线E的话,则可以用0来保证主循环
的效率?/p>
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) { //如果是主socket的事件的话,则表C有新连接进入了Q进行新q接的处理?
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client); // 新q接|于非阻塞模?
ev.events = EPOLLIN | EPOLLET; // q且新q接也加入EPOLL的监听队列?
注意Q这里的参数EPOLLIN | EPOLLETq没有设|对写socket的监听,如果有写操作的话Q这个时候epoll是不会返回事件的Q如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
//
讄好event之后Q将q个新的event通过epoll_ctl加入到epoll的监听队列里面,q里用EPOLL_CTL_ADD来加一个新?
epoll事gQ通过EPOLL_CTL_DEL来减一个epoll事gQ通过EPOLL_CTL_MOD来改变一个事件的监听方式?
fprintf(stderr, "epoll set insertion error: fd=%d0,
client);
return -1;
}
}
else // 如果不是主socket的事件的话,则代表是一个用户socket的事Ӟ则来处理q个用户socket的事情,比如说read(fd,xxx)之类的,或者一些其他的处理?
do_use_fd(events[n].data.fd);
}
如果(zhn)对epoll的效率还不太了解Q请参考我之前关于|络游戏的网l编E等相关的文章?/p>
以前公司的服务器都是使用HTTPq接Q但是这L话,在手机目前的|络情况下不但显得速度较慢Q而且不稳定。因此大家一致同意用SOCKET来进行连
接。虽然用SOCKET之后Q对于用L费用可能会增?׃是用了CMNET而非CMWAP)Q但是,U着用户体验至上的原则,怿大家q是能够接受
?希望那些玩家月末收到帐单不后能够保持克制...)?br>
q次的服务器设计中,最重要的一个突_是用了EPOLL模型Q虽然对之也是一知半解,但是既然在各大PC|游中已l经q了如此严酷的考验Q相信他不会让我们失望,使用后的l果Q确实也是表现相当不错。在q里Q我q是主要大致介绍一下这个模型的l构?br>
6、Linux下EPOll~程实例
EPOLL模型g只有一U格式,所以大家只要参考我下面的代码,p够对EPOLL有所了解了,代码的解释都已经在注释中Q?/p>
{
int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);//{待EPOLL旉的发生,相当于监听,至于相关的端口,需要在初始化EPOLL的时候绑定?br>
if (nfds <= 0)
continue;
m_bOnTimeChecking = FALSE;
G_CurTime = time(NULL);
for (int i=0; i
{
try
{
if (m_events.data.fd == m_listen_http_fd)//如果新监到一个HTTP用户q接到绑定的HTTP端口Q徏立新的连接。由于我们新采用了SOCKETq接Q所以基本没用?br>
{
OnAcceptHttpEpoll ();
}
else if (m_events.data.fd == m_listen_sock_fd)//如果新监到一个SOCKET用户q接Cl定的SOCKET端口Q徏立新的连接?br>
{
OnAcceptSockEpoll ();
}
else if (m_events.events & EPOLLIN)//如果是已l连接的用户Qƈ且收到数据,那么q行d?br>
{
OnReadEpoll (i);
}
catch (int)
{
PRINTF ("CATCH捕获错误\n");
continue;
}
}
m_bOnTimeChecking = TRUE;
OnTimer ();//q行一些定时的操作Q主要就是删除一些短U用L?br>
}
其实EPOLL的精华,也就是上q的几段短短的代码,看来时代真的不同了,以前如何接受大量用户q接的问题,现在却被如此L的搞定,真是让h不得不感叹,对哪?/p>
ȝ
Windows完成端口与Linux epoll技术方案是q?个^C实现异步IO和设计开发一个大定wQ具可扩展性的winsockE序指服务程序的很好的选择Q本文对q?中技术的实现原理和实际的使用Ҏ(gu)做了一个详l的介绍
//CreateThread(NULL,0,ChildThread,(PVOID)hThreadParent,0,NULL);
DuplicateHandle(GetCurrentProcess(),GetCurrentThread(),GetCurrentProcess(),&hThreadParent,
0,false,DUPLICATE_SAME_ACCESS);
_beginthreadex(NULL,0,ChildThread,(PVOID)hThreadParent,0,NULL);
]]>
]]>
#include <stdio.h>
BOOL IsWin7 ()
{
OSVERSIONINFOEX osvi;
DWORDLONG dwlConditionMask = 0;
int op=VER_GREATER;
// Initialize the OSVERSIONINFOEX structure.
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 6;
osvi.dwMinorVersion = 1;
// Initialize the condition mask.
VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, op );
VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, op );
// Perform the test.
return VerifyVersionInfo(
&osvi,
VER_MAJORVERSION | VER_MINORVERSION,
dwlConditionMask);
}
void main()
{
if(IsWin7())
printf("yes.\n");
else{
printf("no.\n");
}
}
]]>
#include <windows.h>
#include <iostream>
#include <psapi.h>
#include <sstream>
#include <fstream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[],TCHAR* envp[])
{
//PTSTR pEnvBlock=::GetEnvironmentStrings();
//TCHAR szName[MAX_PATH];
//TCHAR szValue[MAX_PATH];
int current=0;
PTSTR* pElement =(PTSTR*) envp;
PTSTR pCurrent=NULL;
while(pElement!=NULL){
pCurrent=(PTSTR)(*pElement);
if(pCurrent==NULL){
pElement=NULL;
}else{
_tprintf(TEXT("[%u] %s\r\n"),current,pCurrent);
current++;
pElement++;
}
}
}
]]>#include <iostream>
2#include <windows.h>
3#include <fstream>
4
5using namespace std;
6int _tmain(int argc, _TCHAR* argv[])
7{
8 HANDLE hWrite,hRead;
9 SECURITY_ATTRIBUTES sa;
10 sa.bInheritHandle=true;
11 sa.nLength=sizeof(sa);
12 sa.lpSecurityDescriptor=NULL;
13
14 HANDLE input=CreateFile(L"in.txt",GENERIC_READ,NULL,&sa,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
15 HANDLE output=CreateFile(L"out.txt",GENERIC_WRITE|GENERIC_READ,NULL,&sa,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
16
17 //::CreatePipe(&hRead,&hWrite,&sa,0);
18 STARTUPINFO si;
19 ZeroMemory(&si,sizeof(si));
20 si.cb=sizeof(si);
21 ::GetStartupInfo(&si);
22 si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
23 si.hStdError=0;
24 si.hStdInput=input;
25 si.hStdOutput=output;
26 si.wShowWindow=SW_HIDE;
27
28 PROCESS_INFORMATION pi;
29
30 CreateProcess(L"..\\Debug\\test.exe",0,0,0,true,0,0,0,&si,&pi);
31 //::CloseHandle(hWrite);
32 WaitForSingleObject(pi.hProcess,INFINITE);
33 CloseHandle(input);
34 CloseHandle(output);
35 //char mm[1000];
36 //memset(mm,0,sizeof(mm));
37 //DWORD d;
38 //ReadFile(hRead,mm,1000,&d,0);
39
40 //cout<<mm<<endl;
41
42 return 0;
43}
]]>