|
글 수 163
PostQueuedCompletionStatus 함수에 실패 여부도 고려해야 할까?Windows Research 조회 수 8317 추천 수 0 2010.09.04 12:14:27CreateIoCompletionPort 함수는 내부적으로 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)+183 .text:00439378 ; KeAllocateInterrupt(x)+46 .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)+14 PAGE:00669EDA ; IoAllocateMiniCompletionPacket(x,x)+9 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:0044C34C PAGE:00669F55 xor eax, eax PAGE:00669F57 inc eax PAGE:00669F58 retn PAGE:00669F59 ; --------------------------------------------------------------------------- PAGE:00669F59 PAGE:00669F59 loc_669F59: ; DATA XREF: .text:0044C350 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)+79 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)+56 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)+10 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)+98 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)+8E PAGE:00669F84 cmp eax, ebx PAGE:00669F86 jz short loc_669F8E PAGE:00669F88 PAGE:00669F88 loc_669F88: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+34 PAGE:00669F88 ; IopAllocateMiniCompletionPacket(x,x)+4E PAGE:00669F88 mov ecx, [ebp+arg_0] PAGE:00669F8B mov [eax+8], ecx PAGE:00669F8E PAGE:00669F8E loc_669F8E: ; CODE XREF: IopAllocateMiniCompletionPacket(x,x)+AC PAGE:00669F8E call __SEH_epilog4 PAGE:00669F93 retn 8 PAGE:00669F93 _IopAllocateMiniCompletionPacket@8 endp ; sp-analysis failed 이쯤에서 PostQueuedCompletionStatus 함수가 실패할 경우는 다음과 같이 요약됩니다. 첫 번째로 핸들이 잘 못 될 경우 ( 그러나 프로그램 로직상에 문제로 발생할 수 있기 때문에 무시 ) 두 번째로 시스템 메모리가 부족 할 경우.. 시스템 메모리가 부족 할 경우에는 프로그래머의 잘 못으로 일어나는 현상이 아니므로 PostQueuedCompletionStatus 함수는 반드시 실패할 수 있기 때문에 큐를 대기하는 쪽에서는 이부분을 염두해 두어야 합니다. ( 물론 이렇게 코드를 작성하기가 매우 매우 힘들겠지만.. ) |


