Tuesday, August 3, 2010

HOWTO Debug a Windows Service Using windbg

There are a lot of ways to debug a Windows service.

The simplest is to attach the debugger directly.
1. Use Task Manager to discover the PID for the service or service host. You can also use tlist.exe.
2. Use windbg and attach to that PID

Oftentimes you need to debug the service right from startup. Also it is a good idea to isolate the service in its own process since Windows generally runs several processes that have the same security context in the same host process.
1. Stop the service - 'net stop service_name'
2. Edit the following keys in the registry:
- HKLM\System\CurrentControlSet\Control - create new DWORD value - name = 'ServicesPipeTimeout', value = 86400000. The longer timeout gives you a chance to attach a debugger and set things up.
- HKLM\System\CurrentControlSet\Services\service_name - edit the ImagePath to launch myhost.exe instead of svchost.exe
- HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options - Add a new subkey called 'myhost.exe'. Under it, create a 'String' value called 'Debugger' and set the value to "C:\debuggers\ntsd.exe -server npipe:pipe=service_name_debug"
3. Make a copy of svchost.exe and name it myhost.exe ('copy \windows\system32\svchost.exe myhost.exe')
4. Enable the service to run in its own process - 'sc config service_name type= own'
5. You might need to reboot now if the service dll was in use still (was not unloaded on service stop). Also, it's better to mark the service as 'Manual' start so that it doesn't start automatically at boot - this way you can be ready when you launch the service.
6. Start the service – ‘net start service_name’
7. Attach the debugger ‘windbg –remote npipe:pipe=service_name_debug,server=localhost’. In the debugger, you can setup breakpoints, exceptions, and whatever else you need to do to debug the service, then type ‘g’ to let the service startup.

This article provides more ways to debug a Windows service.
http://support.microsoft.com/kb/824344

Tuesday, March 9, 2010

Link Error LNK2001 __cdecl

You may have ran into a link error like this while trying to link your driver or something.

Linking Executable - s:\depot.obj.amd64\proj\objchk\amd64\proj.dll
errors in directory s:\depot\proj
s:\depot\proj\proj.obj : error LNK2001: unresolved external symbol "long __cdecl _LibFunction(unsigned long,void *, ...)" (?_LibFunction@@YAJKPEAX000PEAPEAUHPNPCTX__@@@Z)

This recently happened to me while I was trying to link a C lib file into my C++ project. If this doesn't already cue you to the problem, this link error is caused by trying to link a C lib object in my C++ code. In the lib's header or around the #include in your C++ project, wrap the prototypes with this:

#if __cplusplus
extern "C" {
#endif

_LibFunction(...);
...

#if __cplusplus
}
#endif

If you just forgot to add the lib to your TARGETLIBS macro in your sources file, the error would look like this instead:

Linking Executable - s:\depot.obj.amd64chk\proj\objchk\amd64\proj.dll
errors in directory s:\depot\proj
s:\depot\proj.obj : error LNK2001: unresolved external symbol _LibFunction

Monday, February 1, 2010

Automatically Launching the Debugger windbg

Having the debugger attach to a process as soon as it is created is something that I always end up needing to do, but don’t do every day and sometimes forget how to do it. Today I needed to do it again, so one of my coworkers reminded me how to do it.

One way is to just debug with the kernel debugger which is the more hardcore way to always debug. Once you have attached remotely to your test machine with the kernel debugger, you can go to any process you want. I think this is the best way if you regularly write kernel code.

I don’t write kernel code all that often, so I normally use windbg on the machine directly. If you need to debug two processes because the talk to each other via DCOM or RPC directly, you will need to attach windbg to each process. If the other process launches on demand, you will need a way to automatically launch windbg to attach to the process. There is a mechanism built into Windows for just the thing. You can read this MSDN blog post about it.

http://blogs.msdn.com/junfeng/archive/2004/04/28/121871.aspx


The short of it is to add the value “Debugger” to a key that is the name of your executable at:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options

So, the common case would be to add the key “dllhost.exe” and then give that key a new string value called “Debugger” with “windbg.exe” as the data.

Tuesday, January 26, 2010

BEGIN_OBJECT_MAP Removal Update

I recently run into a bug in where my dll was supposed to support multiple objects. In my previous post, I was just supporting one. When com was calling DllGetClassObject, it was always just getting one object, which was incorrect. So this is how I went about fixing it.
Imagine you ATL code had something like this:

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Obj1, CObj1)
OBJECT_ENTRY(CLSID_Obj2, CObj2)
OBJECT_ENTRY(CLSID_Obj3, CObj3)
END_OBJECT_MAP()

In your class factory, you need to be able to create the correct object. First I fixed my class factory object.
I added a new member variable:
REFCLSID m_clsid;
And got is set in the constructor:

CClassFactory::CClassFactory(
REFCLSID clsid) : m_cRef(1), m_clsid(clsid)
{
IncModuleCount();
} // CClassFactory::CClassFactory

Next I fixed my CreateInstance method

STDMETHODIMP CClassFactory::CreateInstance(
__in_opt IUnknown *pUnkownOuter,
REFIID riid,
__deref_out_opt void **ppv)
{
HRESULT hr = S_OK;
IUnknown *pUnknown = NULL;

if (ppv)
{
*ppv = NULL;
}
else
{
hr = E_INVALIDARG;
}

if (S_OK == hr)
{
if (pUnkownOuter)
{
hr = CLASS_E_NOAGGREGATION;
}
}

if (S_OK == hr)
{
if (CLSID_Obj1 == m_clsid)
{
pUnknown = new CObj1();
}
else if (CLSID_Obj2 == m_clsid)
{
pUnknown = new CObj2();
}
else if (CLSID_Obj3 == m_clsid)
{
pUnknown = new CObj3();
}
else
{
hr = E_NOINTERFACE;
}

if (S_OK == hr && !pUnknown)
{
hr = E_OUTOFMEMORY;
}
}

if (S_OK == hr)
{
hr = pUnknown->QueryInterface(riid, ppv);
}

if (pUnknown)
{
pUnknown->Release();
}

return hr;
} // CClassFactory::CreateInstance


And then fixed my DllGetClassObject

STDAPI DllGetClassObject(
__in REFCLSID rclsid,
__in REFIID riid,
__deref_out LPVOID FAR *ppv)
{
HRESULT hr = S_OK;
CClassFactory *pClassFactory = NULL;

if (ppv)
{
*ppv = NULL;
}
else
{
hr = E_INVALIDARG;
}

if (S_OK == hr)
{
pClassFactory = new CClassFactory(rclsid);

if (!pClassFactory)
{
hr = E_OUTOFMEMORY;
}
}

if (S_OK == hr)
{
hr = pClassFactory->QueryInterface(riid, ppv);
}

if (pClassFactory)
{
pClassFactory->Release();
}

return hr;
}