9/25/2008

Write Debugger Friendly Applications

One of the challenges for debugging when developing distributed system is that, you may need to debug some processes that are created by some other processes(for example Deamon on Data/Compute Node).

How to get debugger hooked in at the very first place when the process is spawned by those Deamon processes?
- The solution is to write "Debug Friendly" applications.

In order to get the process hooked in by Debugger at the starting up point, you should enable:
1. Stop and Wait the debugger to start and kick in, at the process entry point.
2. Some flag/switch mechanism to contorl whether your process should do step 1

Here is one way to realize these visions:
1. Use environment variable to control whether the debuggee should stop/wait debugger
2. Use IsDebuggerPresent() to determine whether your process is attached by a debugger
3. Use DebugBreak() to get Debugger kicked into the Debuggee.

{
    // if MY_DEBUG_BREAK is defined, break into the debugger
    SomeStrUtil strDebugBreak;
    if (strDebugBreak.GetEnvVar("MY_DEBUG_BREAK ") == S_OK && strDebugBreak.GetLength() != 0) {
        printf("Process-[%d] is waiting for debugger ...\n", GetCurrentProcessId()</a>);
        fflush(stdout);
        while (!IsDebuggerPresent()) {
            Sleep(someTime);
        }
        DebugBreak();
    }
}


If the upper code is put at the entry point (main/wmain) of your code, and you set the proper environment variable(here its MY_DEBUG_BREAK), it will wait the Debugger to attach to it. If it's attached, it will kick the Debugger into its code logic.

You can now break the debuggee, set break point and start your debugging journey.

9/11/2008

Get Process Owner

When investigating with access control and security problems, you may want to get process owner pragmatically.

Since security mechanism on windows is far more complex and trivial than those in *nix world, I will list the steps to accomplish this task here:
1. Get access token associated with the target process
2. Get user info(SID of the user) from access token
3. Get domain\user info from AD using SID

code for querying process owner
1 // open the access token associated with the process
2 HANDLE tokenHandle = NULL;
3 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tokenHandle))
4 {
5     Log(LogLevel_Error, "Failed to get process token, LastError=%u", GetLastError());
6     return;
7 }
8
9 // retrieve user security info(SID) about this access token
10 DWORD dwRequireSize = 0;
11 char userInfo[128];
12 if (!GetTokenInformation(
13     tokenHandle,
14     TokenUser,
15     &userInfo,
16     sizeof(userInfo),
17     &dwRequireSize))
18 {
19     Log(LogLevel_Error, "Failed to get process owner SID, LastError=%u", GetLastError());
20     return;
21 }
22
23 // retrieve the domain\name of the account for this SID
24 TOKEN_USER* userToken = (TOKEN_USER*)userInfo;
25 char name[MAX_PATH];
26 char domain[MAX_PATH];
27 DWORD nameSize = MAX_PATH;
28 DWORD domainSize = MAX_PATH;
29 SID_NAME_USE nu;
30 if (!LookupAccountSidA(NULL,
31     userToken->User.Sid,
32     name,
33     &nameSize,
34     domain,
35     &domainSize,
36     &nu))
37 {
38     Log(LogLevel_Error, "Failed to lookup user info from SID. LastError=%u", GetLastError());
39 }
40 else
41 {
42     Log(LogLevel_Info, "Current Process is using domain=%s, user=%s", domain, name);
43 }


Note:
- I pass a 128 char array, rather than a pointer to struct TOKEN_USER, to the 3-th parameter of GetTokenInformation() function at line 15. It's because this API needs more space than TOKEN_USER. On my dev machine, it requires 44 bytes, so I enlarge it to 128 to make it more flexible. But the beginning memory are of this buffer is always a TOKEN_USER structure, so I convert the start addr of this buffer to TOKEN_USER pointer at line 24.