Getting the PID and TID of a COM Call

There are times when I may be debugging a latency issue that involves a COM component running in a dllhost process. Depending on where we are in that process, I may need to debug the dllhost process in addition to the aspnet_wp or w3wp process. So how do you know which dllhost process to debug, and once you do know the process, how do you know which thread your COM call is executing on? Getting that information isn't hard, but it's not something that you'll be able to figure out on your own.

Finding the Thread Making the COM Call

The first thing you need to do is locate the thread in the aspnet_wp or w3wp process that is executing the COM call. What you want to do is look for a native call to rpcrt4!I_RpcSendReceive. To do that, use the "~*kp" command in the debugger. This will dump out the stack for all threads.

You can see the thread that you should target in the output below.

   26 Id: 560.a34 Suspend: 0 Teb: 7ff6c000 Unfrozen
ChildEBP RetAddr
01bffb68 77d5f2a1 NTDLL!NtRequestWaitReplyPort+0xb
01bffb94 77d5d675 rpcrt4!LRPC_CCALL::SendReceive+0x11e
01bffba0 7cf05eb8 rpcrt4!I_RpcSendReceive+0x2c  
01bffbc0 7cf05d4a OLE32!any_handle <PERF>
01bffbd8 7cf05c57 OLE32!any_handle <PERF>
01bffc18 7cf05eae OLE32!any_handle <PERF>
01bffc88 7ce35b67 OLE32!any_handle <PERF>
01bffce0 77d9a063 OLE32!CPagedVector::GetTableWithSect+0x1f7
01bffcfc 77d9a011 rpcrt4!NdrProxySendReceive+0x4c
01bfff44 77d99db8 rpcrt4!NdrClientCall2+0x4f5
01bfff60 77d4183f rpcrt4!ObjectStublessClient+0x76
01bfff70 787f5818 rpcrt4!ObjectStubless+0xf
01bfffb4 7c57b388 comsvcs!CEventDispatcher::GetEventServerInfoThread(void * pData = 0xffffffff)+0x118
01bfffec 00000000 KERNEL32!BaseThreadStart+0x52

I've bolded the call that you want to look for in the stack above. Once you locate that stack, switch to that thread. In this case, it's thread 26, so I'll issue the "~26s" command to the debugger to switch to that thread.

Digging Into OLE32 Calls

The next step is not so easy to do because you won't have access to private symbols for OLE32.dll. That means that you don't have type information. (It also explains why the above stack shows several calls to OLE32!any_handle.) Before we go any further, let's first dump out the stack above with arguments using the kv command. I'll actually use the kvL command so that source paths are not included so that the output will be a bit cleaner.

0:026> kvL
ChildEBP RetAddr Args to Child
01bffb68 77d5f2a1 00000858 001297a8 001297a8 NTDLL!NtRequestWaitReplyPort+0xb (FPO: [3,0,0])
01bffb94 77d5d675 0012b098 01bffbc0 7cf05eb8 rpcrt4!LRPC_CCALL::SendReceive+0x11e (FPO: [Non-Fpo])
01bffba0 7cf05eb8 0012b098 0012923c 0012923c rpcrt4!I_RpcSendReceive+0x2c (FPO: [Non-Fpo])
01bffbc0 7cf05d4a 00000000 0012923c 00000000 OLE32!any_handle <PERF> (OLE32+0xe5eb8)
01bffbd8 7cf05c57 01bffc14 0012923c 0007eee8 OLE32!any_handle <PERF> (OLE32+0xe5d4a)
01bffc18 7cf05eae 0012923c 01bffd4c 01bffd08 OLE32!any_handle <PERF> (OLE32+0xe5c57)
01bffc88 7ce35b67 0012923c 01bffd4c 01bffd08 OLE32!any_handle <PERF> (OLE32+0xe5eae)
01bffce0 77d9a063 0012923c 01bffd4c 01bffd08 OLE32!CPagedVector::GetTableWithSect+0x1f7 (FPO: [Non-Fpo])
01bffcfc 77d9a011 00120d14 01bffd98 05030117 rpcrt4!NdrProxySendReceive+0x4c (FPO: [Non-Fpo])
01bfff44 77d99db8 7874c538 7874c5aa 01bfff78 rpcrt4!NdrClientCall2+0x4f5 (FPO: [Non-Fpo])
01bfff60 77d4183f 00000018 00000003 7886cf0c rpcrt4!ObjectStublessClient+0x76 (FPO: [Non-Fpo])
01bfff70 787f5818 00120d14 00000000 01bfffa8 rpcrt4!ObjectStubless+0xf
01bfffb4 7c57b388 7886cec8 0197f1fc 00000000 comsvcs!CEventDispatcher::GetEventServerInfoThread+0x118 (FPO: [Uses EBP] [1,7,4]) (CONV: stdcall)
01bfffec 00000000 787f5700 7886cec8 00000000 KERNEL32!BaseThreadStart+0x52 (FPO: [Non-Fpo])

Now let's focus on the calls into OLE32. All of the function names you see into OLE32 are wrong because your symbols don't line up. However, don't worry. You still have what you need. The call that you're interested in is the function at offset 0xe5c57. You want to dump out the memory at the address of that function call's first argument, in this case 0012923c. Both this function and the memory address of the first argument are bolded in the output above. To dump that memory, dd the address of the argument as follows:

0:026> dd 0012923c
0012923c 7ce681b0 7ce55750 00000003 0000000a
0012924c 00000000 00000000 0007eee8 0007f7c0
0012925c 0012dd90 0012bda0 7ce6a938 00060005
0012926c 00000000 ffffffff 00000000 7ce681b0
0012927c 7ce55750 00000001 00000124 00000000
0012928c 00000000 0007ee08 00000000 00000000
0012929c 00133100 7ce6a938 00060005 ffffffff
001292ac ffffffff ffffffff 0013a748 f1eef1ee

Now you want to focus on the 7th dword. (I've bolded it in the output above.) That is the address of an internal class in OLE32 that holds the information that you need. Do a dd on that address as follows.

0:026> dd 0007eee8
0007eee8 7cf117a0 0007ee78 00000a9c 000006e8
0007eef8 3a76f9c2 6ac6c553 7ecc4b7f 6e0ad0df
0007ef08 0000c009 ffff0a9c b72cdaa4 0647c9ba
0007ef18 00000082 00000000 00000000 000e2e00
0007ef28 00000000 001273f0 00000006 ffffffff
0007ef38 0007ecf0 001266b4 00000003 00000000
0007ef48 00000000 00000001 00000000 00060005
0007ef58 00000000 f1eef1ee 00000000 00000000

The values you are interested in here are the 3rd and 4th dwords. (I've bolded them.) The 3rd dword is the PID of the dllhost process that this COM call is executing in. In this case, it's hex a9c. You can easily convert that to decimal in the debugger using the ? command as follows.

0:026> ? a9c
Evaluate expression: 2716 = 00000a9c

So now you know that you're looking for the dllhost process with a PID of 2716. The thread that your COM call is executing on is the 4th dword of the output above, in this case 6e8. To find that thread, open the dump of the dllhost process ID 2716 and issue a "~" command. You'll see the thread ID as follows.

0:000> ~
. 0 Id: a9c.464 Suspend: 0 Teb: 7ffde000 Unfrozen
1 Id: a9c.c94 Suspend: 0 Teb: 7ffdd000 Unfrozen
2 Id: a9c.4bc Suspend: 0 Teb: 7ffdc000 Unfrozen
3 Id: a9c.b10 Suspend: 0 Teb: 7ffd9000 Unfrozen
4 Id: a9c.c30 Suspend: 0 Teb: 7ffd8000 Unfrozen
5 Id: a9c.c24 Suspend: 0 Teb: 7ffd7000 Unfrozen
6 Id: a9c.65c Suspend: 0 Teb: 7ffd6000 Unfrozen
7 Id: a9c.398 Suspend: 0 Teb: 7ffd4000 Unfrozen
8 Id: a9c.3dc Suspend: 0 Teb: 7ffaf000 Unfrozen
9 Id: a9c.c2c Suspend: 0 Teb: 7ffd5000 Unfrozen
10 Id: a9c.b68 Suspend: 0 Teb: 7ffae000 Unfrozen
11 Id: a9c.2c0 Suspend: 0 Teb: 7ffad000 Unfrozen
12 Id: a9c.b74 Suspend: 0 Teb: 7ffac000 Unfrozen
13 Id: a9c.6e8 Suspend: 0 Teb: 7ffab000 Unfrozen

Your COM call is executing on thread 13. You now have the information that you need to dig into that thread and see what's going on.