Debugging services startup in Svchost from a kernel mode debug session
Windows shared services allow us to run system services together in a single service by having multiple DLLs run in a single process called Svchost. This allows Windows to have many services to run with the overhead of a single process. You can find more information about shared services here and here.
I like to do a lot of my user mode debugging from a kernel mode debugger. I can just attach to a Hyper-V VM though a virtual serial port and all my tools are available to me without having to set up a user mode debugger on the target, plus if my application calls into kernel mode I’m all set.
The Svchost process won’t load the DLL until it’s time to start the service, so it can be tricky to set the correct breakpoint to catch the service starting up. This is complicated by the fact that the dll entry point will usually be <module>!ServiceMain, however this is not necessarily the case.
We can leverage the fact that svchost.exe exists in the same shared memory across all the Svchost processes, and a change to this shared memory will affect all of the svchost process. When you set a user mode breakpoint from a kernel mode debugger the debugger changes the user mode code by inserting an int 3 (the debugger changes the code back when it is executed). We can take advantage of this functionality to set one breakpoint that will fire from any svchost process.
If we can get past the Svchost process loading the service dll, we can find the host process main function or any other function we want to set a breakpoint on.
First, find the process address of any Svchost. I’m just showing the first of a long list of processes:
kd> !process 0 0 svchost.exe
PROCESS fffffa80098c5970
SessionId: 0 Cid: 0270 Peb: 7fffffd8000 ParentCid: 0204
DirBase: 35986000 ObjectTable: fffff8a001115b20 HandleCount: 343.
Image: svchost.exe
Now let’s get into the context of this Svchost (it doesn’t matter which one) and set a breakpoint. .process –i sets an invasive breakpoint so we can get into the active context of the process.
kd> .process -i fffffa80098c5970
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
Then go the debugger to enter the active process.
kd> g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
GetServiceMainFunctions() is where Svchost both loads the service DLL and gets the service main function so let’s set a breakpoint for it:
kd> .reload /user
Loading User Symbols
.......................................................
kd> bp svchost!GetServiceMainFunctions
Now I just go the debugger and start my service. I’ll use the workstation service as an example (I had previously stopped the workstation service for this test).
kd> g
At a command prompt on the target I enter:
C:\> net start workstation
In the debugger I get:
Breakpoint 0 hit
svchost!GetServiceMainFunctions:
0033:00000000`ffc813cc fff3 push rbx
kd> k
Child-SP RetAddr Call Site
00000000`0339f7f8 00000000`ffc8128d svchost!GetServiceMainFunctions
00000000`0339f800 000007fe`fe38a82d svchost!ServiceStarter+0xba
00000000`0339f890 00000000`77a5f56d sechost!ScSvcctrlThreadW+0x25
00000000`0339f8c0 00000000`77b93281 kernel32!BaseThreadInitThunk+0xd
00000000`0339f8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
svchost!GetServiceMainFunctions is called from Svchost!ServiceStarter. I just leave GetServiceMainFunctions with gu (Go Up). Now the service dll will have been loaded and I can load symbols for wkssvc.dll and get the service main() function.
kd> gu
svchost!ServiceStarter+0xba:
0033:00000000`ffc8128d 488b7c2430 mov rdi,qword ptr [rsp+30h] ss:002b:00000000`0339f830=000007fefb443a7c
kd> k
Child-SP RetAddr Call Site
00000000`0339f800 000007fe`fe38a82d svchost!ServiceStarter+0xba
00000000`0339f890 00000000`77a5f56d sechost!ScSvcctrlThreadW+0x25
00000000`0339f8c0 00000000`77b93281 kernel32!BaseThreadInitThunk+0xd
00000000`0339f8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
Now loading my user symbols I can get all the symbols for wkssvc.dll
kd> .reload /user
Loading User Symbols
................................................................
kd> dps 00000000`0339f800 l7
00000000`0339f800 00000000`00000000
00000000`0339f808 00000000`00000000
00000000`0339f810 00000000`00000000
00000000`0339f818 00000000`00000000
00000000`0339f820 00000000`00000000
00000000`0339f828 00000000`00000000
00000000`0339f830 000007fe`fb443a7c wkssvc!ServiceMain <- Here is the Service Main function.
I can now set a breakpoint on wkssvc!ServiceMain, and as a bonus I now have access to all available breakpoints in wkssvc.dll
kd> bp wkssvc!ServiceMain
kd> g
Breakpoint 1 hit
wkssvc!ServiceMain:
0033:000007fe`fb443a7c 48895c2408 mov qword ptr [rsp+8],rbx ss:002b:00000000`0339f800={wkssvc!SvchostPushServiceGlobals (000007fe`fb442ff4)}
kd> k
Child-SP RetAddr Call Site
00000000`0339f7f8 00000000`ffc81344 wkssvc!ServiceMain
00000000`0339f800 000007fe`fe38a82d svchost!ServiceStarter+0x1e8
00000000`0339f890 00000000`77a5f56d sechost!ScSvcctrlThreadW+0x25
00000000`0339f8c0 00000000`77b93281 kernel32!BaseThreadInitThunk+0xd
00000000`0339f8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
Now I can debug the service on startup, not caring what instance of svchost it’s in with full user and kernel mode access.
Comments
Anonymous
July 23, 2010
How is this better than using the command "sxe ld:wkssvc.dll" to break when wkssvc.dll is loaded? [From a kernel debugger "sxe ld" only catches kernel module loads. The kernel debugger will not break when a user mode module loads.]Anonymous
July 27, 2010
Sobp svchost!GetServiceMainFunctions
sets a bp in all svchost instances? How is this affected by copy-on-write? thanks, Marc [When you change the shared memory using the kernel debugger, copy-on-write doesn't happen. This means the change is made in all processes that are sharing the memory.]Anonymous
July 29, 2010
use "!gflag +ksl" can make "sxe ld " works on user mode load event. blogs.msdn.com/.../gone-with-the-exception.aspx [Thank you for the pointer, I had forgotten about that functionality. Even with sxe ld, the easiest way to find the service main function is to set the breakpoint shown in this article. The service main function is not necessarily called "ServiceMain".]