실패여부도 반드시 고려해야 합니다. ( 매우 불행하게도... )
이전에 포스팅한 SetEvent와는 다르게 IOCP에서는 매우 비중이 큰 문제입니다.
일단 MSDN에 해당 이 함수는 언제 실패하는지에 대한 어떠한 내용도 나와 있지 않습니다.
그냥 GetLastError로 보라는것 뿐입니다.
IOCP라는 함수가 커널에 밀접하게 관련있다라던지 여러가지말들을 하는 사람들이 많지만
말은 그렇게 할뿐 실제로 어떻게 관련이 있는지는 대부분 정확히 당사자들도 알지 못 합니다.
그렇다면 IOCP 함수중에 PostQueuedCompletionStatus 함수에 실패여부를 알기에 앞서
IOCP를 생성하는 함수인 CreateIoCompletionPort 함수부터 먼저 보겠습니다.
현재 상황으로써는 IOCP가 윈도우즈에 구현되는 매우 특수한 모델로 보입니다.
( 즉, 소켓 프로그래밍 개발자에 입장에서 본다면 IOCP 함수들은 소켓 프로그래밍을 위해서
태어난 함수들처럼 보일지도 모르겠습니다. 그만큼 편리하고 좋기 때문에 특수한 모델로 보일것입니다.
물론 전체적으로 볼 때 비동기 I/O를 하기 위해서 만든것이지만.. )
CreateIoCompletionPort 함수는 내부적으로 NtCreateIoCompletion 함수를 호출하게 되는데
다음과 같이 생겼습니다. ( 디컴파일 결과만 보여줍니다. )
NTSTATUS __stdcall NtCreateIoCompletion(PHANDLE IoCompletionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, ULONG NumberOfConcurrentThreads)
{
void *v4; // ecx@2
PHANDLE v5; // edi@2
char v7; // al@1
int v8; // eax@6
int v9; // esi@7
HANDLE *v10; // eax@7
int PreviousMode; // [sp+1Ch] [bp-1Ch]@1
CPPEH_RECORD ms_exc; // [sp+20h] [bp-18h]@4
int v13; // [sp+10h] [bp-28h]@6
void *v14; // [sp+18h] [bp-20h]@7
v7 = *(_BYTE *)(*MK_FP(__FS__, 292) + 314);
LOBYTE(PreviousMode) = *(_BYTE *)(*MK_FP(__FS__, 292) + 314);
if ( v7 )
{
v5 = IoCompletionHandle;
v4 = IoCompletionHandle;
if ( (unsigned int)IoCompletionHandle >= (unsigned int)MmUserProbeAddress )
v4 = MmUserProbeAddress;
*(_DWORD *)v4 = *(_DWORD *)v4;
ms_exc.disabled = -2;
}
else
{
v5 = IoCompletionHandle;
}
v8 = ObCreateObject(
PreviousMode,
(int)IoCompletionObjectType,
(int)ObjectAttributes,
PreviousMode,
0,
48,
0,
0,
(int)&v13);
IoCompletionHandle = (PHANDLE)v8;
if ( v8 >= 0 )
{
v9 = v13;
KeInitializeQueue(v13, NumberOfConcurrentThreads);
*(_DWORD *)(v9 + 40) = 0;
*(_BYTE *)(v9 + 44) = 0;
v10 = (HANDLE *)ObInsertObject(v9, 0, DesiredAccess, 0, 0, &v14);
IoCompletionHandle = v10;
if ( (signed int)v10 >= 0 )
{
if ( (_BYTE)PreviousMode )
*v5 = v14;
else
*v5 = v14;
}
}
return (NTSTATUS)IoCompletionHandle;
}
함수를 열자마자 바로 실체가 나옵니다.
KeInitializeQueue 를 호출하는 것을 볼 수 있습니다.
즉, IOCP를 초기화하는 과정은 어떠한 특수한 함수를 호출하는것도 아니며 그냥 커널에 있는 함수인
KeInitializeQueue 함수를 호출하며 결국엔 커널에 큐잉모델을 단지 이용하는 것일뿐입니다.
그렇다면 PostQueuedCompletionStatus 함수는 구지 안봐도 어떠한 함수를 호출할지 뻔하지만
그래도 최종적으로 호출되는 코드는 다음과 같습니다.
NTSTATUS __stdcall NtSetIoCompletion(HANDLE IoCompletionHandle, ULONG CompletionKey, ULONG CompletionValue, NTSTATUS Status, ULONG Information)
{
signed int v5; // esi@1
NTSTATUS v7; // eax@1
int AccessMode; // [sp+4h] [bp-8h]@1
PVOID Object; // [sp+8h] [bp-4h]@1
LOBYTE(AccessMode) = *(_BYTE *)(*MK_FP(__FS__, 292) + 314);
v7 = ObReferenceObjectByHandle(IoCompletionHandle, 2u, IoCompletionObjectType, AccessMode, &Object, 0);
v5 = v7;
if ( v7 >= 0 )
{
v5 = IoSetIoCompletion((int)Object, CompletionKey, CompletionValue, Status, Information, 1);
ObfDereferenceObject();
}
return v5;
}
signed int __stdcall IoSetIoCompletion(int a1, int a2, int a3, int a4, int a5, int a6)
{
return IoSetIoCompletionEx(a1, a2, a3, a4, a5, a6, 0);
}
igned int __stdcall IoSetIoCompletionEx(int a1, int a2, int a3, int a4, int a5, int a6, int a7)
{
void *v7; // eax@1
signed int v8; // esi@1
v7 = (void *)a7;
v8 = 0;
if ( a7 || (v7 = IopAllocateMiniCompletionPacket(1, a6), v7) )
{
*((_DWORD *)v7 + 3) = a2;
*((_DWORD *)v7 + 4) = a3;
*((_DWORD *)v7 + 5) = a4;
*((_DWORD *)v7 + 6) = a5;
KiInsertQueue(v7, 0, 0);
}
else
{
v8 = -1073741670;
}
return v8;
}
보시다시피 KiInsertQueue를 호출합니다. ( 참고로 KeInsertQueue 함수는 KiInsertQueue를 내부적으로 호출하므로
거의 동일하다고 보시면 됩니다. )
그렇다면 이제 PostQueuedCompletionStatus 함수에 실패여부도 고려를 해야 할 상황입니다.
가장 첫 번째로 IOCP핸들을 잘 못 넘겼을 경우인데 그 부분은 제외하겠습니다.
또한 KeInsertQueue 함수자체도 에러코드를 리턴하지 않습니다.
하지만 여기서 중요한 것은 위에서 IopAllocateMiniCompletionPacket 함수를 호출하는 부분입니다.
다음과 같이 생겼습니다.
PVOID __stdcall IopAllocateMiniCompletionPacket(int a1, char a2)
{
SIZE_T ST04_4_0; // ST04_4@0
ULONG ST08_4_0; // ST08_4@0
EX_POOL_PRIORITY ST0C_4_0; // ST0C_4@0
int ST10_4_0; // ST10_4@0
int ST14_4_0; // ST14_4@0
int ST18_4_0; // ST18_4@0
int ST1C_4_0; // ST1C_4@0
PVOID result; // eax@2
POOL_TYPE v10; // ebx@2
int v11; // edi@2
int v12; // esi@2
int v13; // esi@3
PVOID v14; // [sp+10h] [bp-1Ch]@2
if ( a1 == 3 )
{
v10 = 0;
ST0C_4_0 = 0;
ST08_4_0 = 544236361;
ST04_4_0 = 40;
goto LABEL_8;
}
v11 = *MK_FP(__FS__, 32);
v12 = *(_DWORD *)(*MK_FP(__FS__, 32) + 1496);
++*(_DWORD *)(v12 + 12);
result = (PVOID)RtlpInterlockedPopEntrySList(ST10_4_0, ST14_4_0);
v14 = result;
v10 = 0;
if ( !result )
{
++*(_DWORD *)(v12 + 16);
v13 = *(_DWORD *)(v11 + 1500);
++*(_DWORD *)(v13 + 12);
result = (PVOID)RtlpInterlockedPopEntrySList(ST18_4_0, ST1C_4_0);
v14 = result;
if ( !result )
{
++*(_DWORD *)(v13 + 16);
if ( a2 )
{
a1 = 2;
v14 = ExAllocatePoolWithQuotaTag(0, 0x1Cu, 0x20706349u);
result = v14;
goto LABEL_9;
}
LABEL_8:
result = ExAllocatePoolWithTagPriority(v10, ST04_4_0, ST08_4_0, ST0C_4_0);
LABEL_9:
if ( result == (PVOID)v10 )
return result;
goto LABEL_10;
}
}
LABEL_10:
*((_DWORD *)result + 2) = a1;
return result;
}
첫 번째로 고려해야 할 함수는 RtlpInterlockedPopEntrySList 입니다.
이 함수는 다음과 같이 생겼습니다.
.text:00439378 ; __fastcall RtlpInterlockedPopEntrySList(x)
.text:00439378 public @RtlpInterlockedPopEntrySList@4
.text:00439378 @RtlpInterlockedPopEntrySList@4 proc near
.text:00439378 ; CODE XREF: MiAllocateMdlPagesByLists(x,x,x,x,x,x,x)+183p
.text:00439378 ; KeAllocateInterrupt(x)+46p ...
.text:00439378
.text:00439378 var_C = dword ptr -0Ch
.text:00439378
.text:00439378 push ebx ; ExInterlockedPopEntrySList
.text:00439379 push ebp
.text:0043937A mov ebp, ecx
.text:0043937C sub esp, 4
.text:0043937F xor eax, eax
.text:00439381 mov [esp+0Ch+var_C], eax
.text:00439381 @RtlpInterlockedPopEntrySList@4 endp ; sp-analysis failed
전혀 고려대상이 아닙니다.
단순한 인라인 함수정도로 생각하면 될것입니다.
( 함수가 좀 길어서 다 올리지 않았습니다. 보시면 맨 마지막에 스택분석 실패라고 나온것을 볼 수 잇죠..
밑에 코드가 사실은 더 있습니다. )
그 다음에 마지막으로 고려해야 할 대상은 ExAllocatePoolWithQuotaTag 함수입니다.
( ExAllocatePoolWithTagPriority 함수도 호출하는데 이 함수는 메모리가 부족하 경우 NULL을 리턴합니다. )
이 함수는 특이하게도 메모리가 부족해서 함수가 실패할 경우 NULL을 리턴하는게 아니라
예외 핸들러를 실행시킵니다.
해당 함수가 디컴파일이 되어서 안 보이겠지만 사실은 예외 핸들러가 작성되어 있습니다.
PAGE:00669EDA ; __stdcall IopAllocateMiniCompletionPacket(x, x)
PAGE:00669EDA _IopAllocateMiniCompletionPacket@8 proc near
PAGE:00669EDA ; CODE XREF: IoSetIoCompletionEx(x,x,x,x,x,x,x)+14p
PAGE:00669EDA ; IoAllocateMiniCompletionPacket(x,x)+9p
PAGE:00669EDA
PAGE:00669EDA var_1C = dword ptr -1Ch
PAGE:00669EDA ms_exc = CPPEH_RECORD ptr -18h
PAGE:00669EDA arg_0 = dword ptr 8
PAGE:00669EDA arg_4 = byte ptr 0Ch
PAGE:00669EDA
PAGE:00669EDA push 0Ch
PAGE:00669EDC push offset dword_44C338
PAGE:00669EE1 call __SEH_prolog4
PAGE:00669EE6 cmp [ebp+arg_0], 3
PAGE:00669EEA jz loc_669F74
PAGE:00669EF0 mov edi, large fs:20h
PAGE:00669EF7 mov esi, [edi+5D8h]
PAGE:00669EFD inc dword ptr [esi+0Ch]
PAGE:00669F00 mov ecx, esi
PAGE:00669F02 call @RtlpInterlockedPopEntrySList@4 ; RtlpInterlockedPopEntrySList(x)
PAGE:00669F07 mov [ebp+var_1C], eax
PAGE:00669F0A xor ebx, ebx
PAGE:00669F0C cmp eax, ebx
PAGE:00669F0E jnz short loc_669F88
PAGE:00669F10 inc dword ptr [esi+10h]
PAGE:00669F13 mov esi, [edi+5DCh]
PAGE:00669F19 inc dword ptr [esi+0Ch]
PAGE:00669F1C mov ecx, esi
PAGE:00669F1E call @RtlpInterlockedPopEntrySList@4 ; RtlpInterlockedPopEntrySList(x)
PAGE:00669F23 mov [ebp+var_1C], eax
PAGE:00669F26 cmp eax, ebx
PAGE:00669F28 jnz short loc_669F88
PAGE:00669F2A inc dword ptr [esi+10h]
PAGE:00669F2D cmp [ebp+arg_4], bl
PAGE:00669F30 jz short loc_669F6A
PAGE:00669F32 mov [ebp+arg_0], 2
PAGE:00669F39 mov [ebp+ms_exc.disabled], ebx
PAGE:00669F3C push 20706349h ; Tag
PAGE:00669F41 push 1Ch ; NumberOfBytes
PAGE:00669F43 push ebx ; PoolType
PAGE:00669F44 call _ExAllocatePoolWithQuotaTag@12 ; ExAllocatePoolWithQuotaTag(x,x,x)
PAGE:00669F49 mov [ebp+var_1C], eax
PAGE:00669F4C mov [ebp+ms_exc.disabled], 0FFFFFFFEh
PAGE:00669F53 jmp short loc_669F65
PAGE:00669F55 ; ---------------------------------------------------------------------------
PAGE:00669F55
PAGE:00669F55 loc_669F55: ; DATA XREF: .text:0044C34Co
PAGE:00669F55 xor eax, eax
PAGE:00669F57 inc eax
PAGE:00669F58 retn
PAGE:00669F59 ; ---------------------------------------------------------------------------
PAGE:00669F59
PAGE:00669F59 loc_669F59: ; DATA XREF: .text:0044C350o
PAGE:00669F59 mov esp, [ebp+ms_exc.old_esp]
PAGE:00669F5C mov [ebp+ms_exc.disabled], 0FFFFFFFEh
PAGE:00669F63 xor ebx, ebx
PAGE:00669F65
PAGE:00669F65 loc_669F65: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+79j
PAGE:00669F65 mov eax, [ebp+var_1C]
PAGE:00669F68 jmp short loc_669F84
PAGE:00669F6A ; ---------------------------------------------------------------------------
PAGE:00669F6A
PAGE:00669F6A loc_669F6A: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+56j
PAGE:00669F6A push ebx
PAGE:00669F6B push 20706349h
PAGE:00669F70 push 1Ch
PAGE:00669F72 jmp short loc_669F7E
PAGE:00669F74 ; ---------------------------------------------------------------------------
PAGE:00669F74
PAGE:00669F74 loc_669F74: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+10j
PAGE:00669F74 xor ebx, ebx
PAGE:00669F76 push ebx ; Priority
PAGE:00669F77 push 20706349h ; Tag
PAGE:00669F7C push 28h ; NumberOfBytes
PAGE:00669F7E
PAGE:00669F7E loc_669F7E: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+98j
PAGE:00669F7E push ebx ; PoolType
PAGE:00669F7F call _ExAllocatePoolWithTagPriority@16 ; ExAllocatePoolWithTagPriority(x,x,x,x)
PAGE:00669F84
PAGE:00669F84 loc_669F84: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+8Ej
PAGE:00669F84 cmp eax, ebx
PAGE:00669F86 jz short loc_669F8E
PAGE:00669F88
PAGE:00669F88 loc_669F88: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+34j
PAGE:00669F88 ; IopAllocateMiniCompletionPacket(x,x)+4Ej
PAGE:00669F88 mov ecx, [ebp+arg_0]
PAGE:00669F8B mov [eax+8], ecx
PAGE:00669F8E
PAGE:00669F8E loc_669F8E: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+ACj
PAGE:00669F8E call __SEH_epilog4
PAGE:00669F93 retn 8
PAGE:00669F93 _IopAllocateMiniCompletionPacket@8 endp ; sp-analysis failed
이쯤에서 PostQueuedCompletionStatus 함수가 실패할 경우는 다음과 같이 요약됩니다.
첫 번째로 핸들이 잘 못 될 경우 ( 그러나 프로그램 로직상에 문제로 발생할 수 있기 때문에 무시 )
두 번째로 시스템 메모리가 부족 할 경우..
시스템 메모리가 부족 할 경우에는 프로그래머의 잘 못으로 일어나는 현상이 아니므로
PostQueuedCompletionStatus 함수는 반드시 실패할 수 있기 때문에 큐를 대기하는 쪽에서는
이부분을 염두해 두어야 합니다. ( 물론 이렇게 코드를 작성하기가 매우 매우 힘들겠지만.. )







최근 덧글