Automate WinDBG
Automate WinDBG
WinDBG is a very convenient debugger, but typing the same command again and again is tiresome, especially during startup. GDB provided a mechanism to run some commands on startup through gdbinit. WinDBG has the equivalent, although it is not as clear. In this blog post, I am going to share how I automated WinDBG to run some commands on startup. For the impatient, just rush to the summary to get the commands.
Startup command
WinDBG supports using the -c
command line argument to specify a startup command. The option is documented here, search for -c
in the page.
Here is a very simple example:
windbg.exe -c q cmd.exe
This will launch a debugger trying to debug cmd.exe
. When the debugger starts up, it will quit itself immediately. Not super interesting, but it works.
Run script file
We do not want to run just one command, we wanted to run a series of them. To do so, WinDBG has a command that run a script file. The command is documented here. The remark is probably the most interesting part to read, which explain how string escaping works.
I do not want to screw myself, so I will use a script file that does not contain a semicolon ;
in the file name. That allow us to use $$>a<
.
Let’s first create an autodbg.script is a file with a single command q
. Then type this into the command prompt, what do we see?
windbg.exe -c $$>a<autodbg.script cmd.exe
We see the debugger do not quit, and a new file named a
is created. The problem is that the >
and <
operators are interpreted by the command prompt as redirection operators, we need to escape them using the caret ^
sign as follow:
windbg.exe -c $$^>a^<autodbg.script cmd.exe
Now it works to quit the prompt.
Handling crash
Suppose a process is launched without a debugger, if it crashes, the process is just gone. To debug that crash, it is possible to tell the system to launch WinDBG and debug when a crash happens. To do so, you run the following command in an elevated command prompt:
windbg.exe -I
This will install WinDBG as a post-mortem debugger. We can experiment with it by having a crashing process as follow:
#include <iostream>
using namespace std;
int main()
{
int* a = NULL;
cout << "Pre crash" << endl;
*a = 0;
cout << "Post crash" << endl;
}
This is obviously an access violation. If we launch this process outside of Visual Studio, we should see WinDBG popping up an pointing right at the line that write to *a
.
Automate Crash Handling
When we invoke windbg.exe -I
, all it does is that it writes to the registry. The key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
contains a value:
Debugger
pointing to the debugger command as follow:
"c:\toolssw\debuggers\amd64\windbg.exe" -p %ld -e %ld -g
That makes me ponder, is it possible to add the -c
option above to automate some of the processing? The answer is yes!
"c:\toolssw\debuggers\amd64\windbg.exe" -c $$>a<c:\temp\autodbg.script -p %ld -e %ld -g
Note that this is not the command prompt, so we no longer need to escape the >
and <
signs. Also, I specified a full path for the script.
For Visual Studio user, the original value is this, you can restore it after the WinDBG session if you wish.
"C:\WINDOWS\system32\vsjitdebugger.exe" -p %ld -e %ld
Launching executables
Launching an executable in a script has some interesting twist. The WinDBG way for launching an executable is using the .shell command. The command is documented here. Notice the -i
option and the fact that -i-
can avoid WinDBG prompting for inputs for the process, it is important if you don’t
To start with, I wrote a very simple executable to be launched to observe the executable launching behavior.
#include <string>
#include <fstream>
using namespace std;
int main(int argc, char** argv)
{
ofstream outputFile("c:\\temp\\output.txt");
for (int i = 0; i < argc; i++)
{
outputFile << argv[i] << endl;
}
outputFile.close();
return 0;
}
This is compiled into c:\temp\work.exe
. Obviously, the program simply write each argument into a separate line in the file.
The next natural step is to automate the launching of this executable. We write this script and launch it as usual.
.shell -i- -o- c:\temp\work.exe
q
You would expect the debugger would quit after running the command, but it actually doesn’t. Inspecting c:\temp\output.txt
would find
c:\temp\work.exe
;q
Now the mystery is clear. WinDBG is interpreting everything after the .shell as the executable name and then their arguments, making it impossible to chain any command after it.
How can we solve this problem? We can make a separate script that contains only the .shell
command and chain it using the master script as follow:
autodbg.script
$$>a<run.script
q
run.script
.shell -i- -o- c:\temp\work.exe "what a test"
This will produce exactly what we want. The script runs, launch the executable with right arguments, and quits.
Summary
The post documented the process of automating WinDBG in various scenarios. Here are the key takeaways:
Command Line
Here is the command to run autodbg.script on startup:
windbg.exe -c $$^>a^<autodbg.script cmd.exe
Registry for crash processing
Put this string into
"c:\toolssw\debuggers\amd64\windbg.exe" -c $$>a<c:\temp\autodbg.script -p %ld -e %ld -g
this registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug:Debugger
to have WinDBG to process crashes with autodbg.script.
Launching executable
Put the .shell -i- -o-
command in a separate script file and chain it with the master script to avoid interpreting the rest of the commands as arguments.