This can be laughed at but I have always wondered how the limit of semaphore object is used in windows so I ended up doing a little experiment during my dump analysis routine at work
The signal-state in dispatcher header is set to the initial count in this case and this keeps decrementing and once 0 we can’t acquire it any more. This is just a demonstration or a POC for people who are still learning internals. Initially I thought I will have to set up a live debug scenario to capture this but this was fairly simple using a user mode CDB and livekd.exe. The only glitch in the whole workout is we need to re-launch the livekd every time we need the updated kernel states
This was an exercise I could perform while I was travelling to my office in a cab.
// Standard code from MSDN demonstrating semaphores
#include <windows.h>
#include <stdio.h>
#define MAX_SEM_COUNT 10
#define THREADCOUNT 12
HANDLE ghSemaphore;
DWORD WINAPI ThreadProc( LPVOID );
void main()
{
HANDLE aThread[THREADCOUNT];
DWORD ThreadID;
int i;
// Create a semaphore with initial and max counts of MAX_SEM_COUNT
ghSemaphore = CreateSemaphore(
NULL, // default security attributes
MAX_SEM_COUNT, // initial count
MAX_SEM_COUNT, // maximum count
NULL); // unnamed semaphore
if (ghSemaphore == NULL)
{
printf("CreateSemaphore error: %d\n", GetLastError());
return;
}
// Create worker threads
for( i=0; i < THREADCOUNT; i++ )
{
aThread[i] = CreateThread(
NULL, // default security attributes
0, // default stack size
(LPTHREAD_START_ROUTINE) ThreadProc,
NULL, // no thread function arguments
0, // default creation flags
&ThreadID); // receive thread identifier
if( aThread[i] == NULL )
{
printf("CreateThread error: %d\n", GetLastError());
return;
}
}
// Wait for all threads to terminate
WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);
// Close thread and semaphore handles
for( i=0; i < THREADCOUNT; i++ )
CloseHandle(aThread[i]);
CloseHandle(ghSemaphore);
}
DWORD WINAPI ThreadProc( LPVOID lpParam )
{
DWORD dwWaitResult;
BOOL bContinue=TRUE;
while(bContinue)
{
// Try to enter the semaphore gate.
dwWaitResult = WaitForSingleObject(
ghSemaphore, // handle to semaphore
0L); // zero-second time-out interval
switch (dwWaitResult)
{
// The semaphore object was signaled.
case WAIT_OBJECT_0:
// TODO: Perform task
printf("Thread %d: wait succeeded\n", GetCurrentThreadId());
bContinue=FALSE;
// Simulate thread spending time on task
Sleep(5*1000*60);
// Relase the semaphore when task is finished
if (!ReleaseSemaphore(
ghSemaphore, // handle to semaphore
1, // increase count by one
NULL) ) // not interested in previous count
{
printf("ReleaseSemaphore error: %d\n", GetLastError());
}
break;
// The semaphore was nonsignaled, so a time-out occurred.
case WAIT_TIMEOUT:
printf("Thread %d: wait timed out\n", GetCurrentThreadId());
break;
}
}
return TRUE;
}
/*
DEBUGGER OUTPUTS
User mode CDB
D:\addy\src>c:\debug86\cdb semaphore.exe
Microsoft (R) Windows Debugger Version 6.12.0002.633 X86
Copyright (c) Microsoft Corporation. All rights reserved.
CommandLine: semaphore.exe
Symbol search path is: srv*d:\symbols\public*http://msdl.microsoft.com/download/symbols;srv*d:\symbols\citrix*http://
Executable search path is:
ModLoad: 00310000 0031f000 image00310000
ModLoad: 77090000 771cd000 ntdll.dll
ModLoad: 75930000 75a04000 C:\Windows\system32\kernel32.dll
ModLoad: 753c0000 7540a000 C:\Windows\system32\KERNELBASE.dll
(21e8.4f4): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0041f3a0 edx=770d6344 esi=fffffffe edi=00000000
eip=7712ebbe esp=0041f3bc ebp=0041f3e8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2c:
7712ebbe cc int 3
0:000> bp kernel32!CreateSemaphoreA
0:000> bp kernel32!CreateSemaphoreW
0:000> g
Breakpoint 0 hit
eax=005c2150 ebx=7ffdf000 ecx=00000001 edx=770d6344 esi=00000000 edi=00000000
eip=7596bdd7 esp=0041f788 ebp=0041f7d4 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
kernel32!CreateSemaphoreA:
7596bdd7 8bff mov edi,edi
0:000> kvn
*** WARNING: Unable to verify checksum for image00310000
*** ERROR: Module load completed but symbols could not be loaded for image00310000
# ChildEBP RetAddr Args to Child
00 0041f784 00311014 00000000 0000000a 0000000a kernel32!CreateSemaphoreA (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
01 0041f7d4 00311385 00000001 005c2120 005c2150 image00310000+0x1014
02 0041f81c 75981114 7ffdf000 0041f868 770eb429 image00310000+0x1385
03 0041f828 770eb429 7ffdf000 731c916d 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
04 0041f868 770eb3fc 003113db 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
05 0041f880 00000000 003113db 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0:000> gu
eax=00000020 ebx=7ffdf000 ecx=7ffde000 edx=00000000 esi=00000000 edi=00000000
eip=00311014 esp=0041f79c ebp=0041f7d4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
image00310000+0x1014:
00311014 a3dcca3100 mov dword ptr [image00310000+0xcadc (0031cadc)],eax ds:0023:0031cadc=00000000
0:000> g
0:000>** get the object once we change the process context in the livekd launched debugger in this case kd
Live KD
Creation of Semaphore object
0: kd> .process -r -p 89594030
Implicit process is now 89594030
Loading User Symbols
....
0: kd> !handle 20
PROCESS 89594030 SessionId: 1 Cid: 21e8 Peb: 7ffdf000 ParentCid
DirBase: d98bbfc0 ObjectTable: ba811088 HandleCount: 8.
Image: semaphore.exe
Handle table at ec021000 with 8 entries in use
0020: Object: 89aba490 GrantedAccess: 001f0003 Entry: ec021040
Object: 89aba490 Type: (85ed1518) Semaphore
ObjectHeader: 89aba478 (new version)
HandleCount: 1 PointerCount: 1
0: kd> dt nt!_KSEMAPHORE 89aba490 ç init via KeInitializeSemaphore
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : 0n10 <===
0: kd> dt nt!_KSEMAPHORE 89aba490 Header.
+0x000 Header :
+0x000 Type : 0x5 ''
+0x001 TimerControlFlags : 0 ''
+0x001 Absolute : 0y0
+0x001 Coalescable : 0y0
+0x001 KeepShifting : 0y0
+0x001 EncodedTolerableDelay : 0y00000 (0)
+0x001 Abandoned : 0 ''
+0x001 Signalling : 0 ''
+0x002 ThreadControlFlags : 0x5 ''
+0x002 CpuThrottled : 0y1
+0x002 CycleProfiling : 0y0
+0x002 CounterProfiling : 0y1
+0x002 Reserved : 0y00000 (0)
+0x002 Hand : 0x5 ''
+0x002 Size : 0x5 ''
+0x003 TimerMiscFlags : 0 ''
+0x003 Index : 0y0
+0x003 Processor : 0y00000 (0)
+0x003 Inserted : 0y0
+0x003 Expired : 0y0
+0x003 DebugActive : 0 ''
+0x003 ActiveDR7 : 0y0
+0x003 Instrumented : 0y0
+0x003 Reserved2 : 0y0000
+0x003 UmsScheduled : 0y0
+0x003 UmsPrimary : 0y0
+0x003 DpcActive : 0 ''
+0x000 Lock : 0n327685
+0x004 SignalState : 0n10 <<====
+0x008 WaitListHead : _LIST_ENTRY [ 0x89aba498 - 0x89aba498 ]
After acquisitions
----------------------
0: kd> .process -r -p 89594030
Implicit process is now 89594030
Loading User Symbols
....
0: kd> !handle 20
PROCESS 89594030 SessionId: 1 Cid: 21e8 Peb: 7ffdf000 ParentCid: 0f3c
DirBase: d98bbfc0 ObjectTable: ba811088 HandleCount: 22.
Image: semaphore.exe
Handle table at ec021000 with 22 entries in use
0020: Object: 89aba490 GrantedAccess: 001f0003 Entry: ec021040
Object: 89aba490 Type: (85ed1518) Semaphore
ObjectHeader: 89aba478 (new version)
HandleCount: 1 PointerCount: 2
0: kd> dt nt!_KSEMAPHORE 89aba490
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : 0n10
0: kd> dt nt!_KSEMAPHORE 89aba490 Header.
+0x000 Header :
+0x000 Type : 0x5 ''
+0x001 TimerControlFlags : 0 ''
+0x001 Absolute : 0y0
+0x001 Coalescable : 0y0
+0x001 KeepShifting : 0y0
+0x001 EncodedTolerableDelay : 0y00000 (0)
+0x001 Abandoned : 0 ''
+0x001 Signalling : 0 ''
+0x002 ThreadControlFlags : 0x5 ''
+0x002 CpuThrottled : 0y1
+0x002 CycleProfiling : 0y0
+0x002 CounterProfiling : 0y1
+0x002 Reserved : 0y00000 (0)
+0x002 Hand : 0x5 ''
+0x002 Size : 0x5 ''
+0x003 TimerMiscFlags : 0 ''
+0x003 Index : 0y0
+0x003 Processor : 0y00000 (0)
+0x003 Inserted : 0y0
+0x003 Expired : 0y0
+0x003 DebugActive : 0 ''
+0x003 ActiveDR7 : 0y0
+0x003 Instrumented : 0y0
+0x003 Reserved2 : 0y0000
+0x003 UmsScheduled : 0y0
+0x003 UmsPrimary : 0y0
+0x003 DpcActive : 0 ''
+0x000 Lock : 0n327685
+0x004 SignalState : 0n0 ç Queue Full can’t acquire anymore
+0x008 WaitListHead : _LIST_ENTRY [ 0x89aba498 - 0x89aba498 ]
Tracking each acquisition
eax=00000020 ebx=00000000 ecx=00000000 edx=00151005 esi=00000000 edi=00000000
eip=7597ef83 esp=0076ff04 ebp=0076ff04 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
kernel32!WaitForSingleObjectExImplementation+0x48:
7597ef83 8bc8 mov ecx,eax
0:001> gu
eax=00000000 ebx=00000000 ecx=753c17c4 edx=770d6344 esi=00000000 edi=00000000
eip=7597ef52 esp=0076ff18 ebp=0076ff18 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
kernel32!WaitForSingleObject+0x12:
7597ef52 5d pop ebp
0:001> t
Breakpoint 0 hit
eax=75981102 ebx=00000000 ecx=00000000 edx=00151005 esi=00000000 edi=00000000
eip=00151100 esp=00c0f988 ebp=00c0f990 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
semaphore!ThreadProc:
00151100 55 push ebp
0:002>
eax=75981102 ebx=00000000 ecx=00000000 edx=00151005 esi=00000000 edi=00000000
eip=00151101 esp=00c0f984 ebp=00c0f990 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
semaphore!ThreadProc+0x1:
00151101 8bec mov ebp,esp
0:002>
eax=75981102 ebx=00000000 ecx=00000000 edx=00151005 esi=00000000 edi=00000000
eip=00151103 esp=00c0f984 ebp=00c0f984 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
semaphore!ThreadProc+0x3:
00151103 83ec0c sub esp,0Ch
0:002> kvn
# ChildEBP RetAddr Args to Child
00 00c0f984 75981114 00000000 00c0f9d0 770eb429 semaphore!ThreadProc+0x3 (FPO: [Non-Fpo]) (CONV: std
01 00c0f990 770eb429 00000000 738241d2 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
02 00c0f9d0 770eb3fc 00151005 00000000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
03 00c0f9e8 00000000 00151005 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
0: kd> dt nt!_KSEMAPHORE 86acba70
*** ERROR: Module load completed but symbols could not be loaded for LiveKdD.SYS
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : 0n10
0: kd> +0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : 0n10
0: kd> dt nt!_KSEMAPHORE 86acba70 Header.
+0x000 Header :
+0x000 Type : 0x5 ''
+0x001 TimerControlFlags : 0 ''
+0x001 Absolute : 0y0
+0x001 Coalescable : 0y0
+0x001 KeepShifting : 0y0
+0x001 EncodedTolerableDelay : 0y00000 (0)
+0x001 Abandoned : 0 ''
+0x001 Signalling : 0 ''
+0x002 ThreadControlFlags : 0x5 ''
+0x002 CpuThrottled : 0y1
+0x002 CycleProfiling : 0y0
+0x002 CounterProfiling : 0y1
+0x002 Reserved : 0y00000 (0)
+0x002 Hand : 0x5 ''
+0x002 Size : 0x5 ''
+0x003 TimerMiscFlags : 0 ''
+0x003 Index : 0y0
+0x003 Processor : 0y00000 (0)
+0x003 Inserted : 0y0
+0x003 Expired : 0y0
+0x003 DebugActive : 0 ''
+0x003 ActiveDR7 : 0y0
+0x003 Instrumented : 0y0
+0x003 Reserved2 : 0y0000
+0x003 UmsScheduled : 0y0
+0x003 UmsPrimary : 0y0
+0x003 DpcActive : 0 ''
+0x000 Lock : 0n327685
+0x004 SignalState : 0n9
+0x008 WaitListHead : _LIST_ENTRY [ 0x86acba78 - 0x86acba78 ]
2 more acquires
0:003>
eax=00000020 ebx=00000000 ecx=00000000 edx=00151005 esi=00000000 edi=00000000
eip=7597ef40 esp=0060f8c8 ebp=0060f8e0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!WaitForSingleObject:
7597ef40 8bff mov edi,edi
0:003>
eax=00000020 ebx=00000000 ecx=00000000 edx=00151005 esi=00000000 edi=00000000
eip=7597ef42 esp=0060f8c8 ebp=0060f8e0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!WaitForSingleObject+0x2:
7597ef42 55 push ebp
0:003> gu
eax=00000000 ebx=00000000 ecx=753c17c4 edx=770d6344 esi=00000000 edi=00000000
eip=00151125 esp=0060f8d4 ebp=0060f8e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
semaphore!ThreadProc+0x25:
00151125 8945fc mov dword ptr [ebp-4],eax ss:0023:0060f8dc=00000000
0:003> t
eax=00000000 ebx=00000000 ecx=753c17c4 edx=770d6344 esi=00000000 edi=00000000
eip=00151128 esp=0060f8d4 ebp=0060f8e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
semaphore!ThreadProc+0x28:
00151128 8b4dfc mov ecx,dword ptr [ebp-4] ss:0023:0060f8dc=00000000
0:003>
eax=00000000 ebx=00000000 ecx=00000000 edx=770d6344 esi=00000000 edi=00000000
eip=0015112b esp=0060f8d4 ebp=0060f8e0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
semaphore!ThreadProc+0x2b:
0: kd> dt nt!_KSEMAPHORE 86acba70
*** ERROR: Module load completed but symbols could not be loaded for LiveKdD.SYS
+0x000 Header : _DISPATCHER_HEADER
+0x010 Limit : 0n10
0: kd> dt nt!_KSEMAPHORE 86acba70 Header.
+0x000 Header :
+0x000 Type : 0x5 ''
+0x001 TimerControlFlags : 0 ''
+0x001 Absolute : 0y0
+0x001 Coalescable : 0y0
+0x001 KeepShifting : 0y0
+0x001 EncodedTolerableDelay : 0y00000 (0)
+0x001 Abandoned : 0 ''
+0x001 Signalling : 0 ''
+0x002 ThreadControlFlags : 0x5 ''
+0x002 CpuThrottled : 0y1
+0x002 CycleProfiling : 0y0
+0x002 CounterProfiling : 0y1
+0x002 Reserved : 0y00000 (0)
+0x002 Hand : 0x5 ''
+0x002 Size : 0x5 ''
+0x003 TimerMiscFlags : 0 ''
+0x003 Index : 0y0
+0x003 Processor : 0y00000 (0)
+0x003 Inserted : 0y0
+0x003 Expired : 0y0
+0x003 DebugActive : 0 ''
+0x003 ActiveDR7 : 0y0
+0x003 Instrumented : 0y0
+0x003 Reserved2 : 0y0000
+0x003 UmsScheduled : 0y0
+0x003 UmsPrimary : 0y0
+0x003 DpcActive : 0 ''
+0x000 Lock : 0n327685
+0x004 SignalState : 0n7
+0x008 WaitListHead : _LIST_ENTRY [ 0x86acba78 - 0x86acba78 ]
. . . . .. .
*/