0
import sys
import subprocess

command = 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64 -y ' + sys.argv[1] + ' -i ' + sys.argv[2] + ' -z ' + sys.argv[3] + ' -c "!analyze" '
process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
output, error = process.communicate()

I tried this code, I am trying to take input of crash dump name and exe location and then I have to display user understandable crash analysis ouput.How to do that using python scripting? Is it easier with cpp scripting?

Torxed
  • 22,866
  • 14
  • 82
  • 131
Prachi
  • 53
  • 1
  • 7
  • Can u include error stack trace. – sushanth Jun 08 '20 at 13:07
  • I am trying to analyze a crash dump using windbg, but there it takes a lot of overhead like opening dump file in it, entering a particular set of commands for each dump. So to make it easy to use I want to build a python script over that can you tell me how can I do that – Prachi Jun 08 '20 at 14:06

2 Answers2

2

take input of crash dump name and exe location and then I have to display user understandable crash analysis ouput.

It seems you want to parse the text output of the !analyze command. You can do that, but you should be aware that this command can have a lot of different output.

Let me assume you're analyzing a user mode crash dump. In such a case, I would first run a few simpler commands to check whether you got a legit dump. You may try the following commands:

  • || to check the dump type (should be "user")
  • | to get the name of the executable (should match your application)
  • lmvm <app> to check the version number of your executable

If everything is fine, you can go on:

  • .exr -1: distinguish between a crash and a hang. A 80000003 breakpoint is more likely a hang or nothing at all.

This may help you decide if you should run !analyze or !analyze -hang.

How to do that using Python scripting?

[...] \Windows Kits\10\Debuggers\x64 -y ' + [...]

This path contains backslashes, so you want to escape them or use an r-string like r"C:\Program Files (x86)\Windows Kits\10\...".

You should probably start an executable here to make it work. cdb.exe is the command line version of WinDbg.

command.split()

This will not only split the arguments, but also the path to the exectuable. Thus subprocess.popen() will try to an application called C:\Program which does not exist.

This could fail even more often, depending on the arguments with spaces in sys.argv[].

I suggest that you pass the options as they are:

command = r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe'
arguments = [command]
arguments.extend(['-y', sys.argv[1]])  # Symbol path
arguments.extend(['-i', sys.argv[2]])  # Image path
arguments.extend(['-z', sys.argv[3]])  # Dump file
arguments.extend(['-c', '!analyze'])   # Command(s) for analysis
process = subprocess.Popen(arguments, stdout=subprocess.PIPE)

Note that there's no split() involved, which could split in wrong position.

Side note: -i may not work as expected. If you receive the crash dump from clients, they may have a different version than the one you have on disk. Set up a proper symbol server to mitigate this.

Is it easier with CPP scripting?

It will be different, not easier.

Working example

This is a Python code that considers the above. It's still a bit hacky because of the delays etc. but there's no real indicator other than time and output for deciding when a command finished. This succeeds with Python 3.8 on a crash dump of Windows Explorer.

import subprocess
import threading
import time
import re

class ReaderThread(threading.Thread):
    def __init__(self, stream):
        super().__init__()
        self.buffer_lock = threading.Lock()
        self.stream = stream  # underlying stream for reading
        self.output = ""  # holds console output which can be retrieved by getoutput()

    def run(self):
        """
        Reads one from the stream line by lines and caches the result.
        :return: when the underlying stream was closed.
        """
        while True:
            line = self.stream.readline()  # readline() will block and wait for \r\n
            if len(line) == 0:  # this will only apply if the stream was closed. Otherwise there is always \r\n
                break
            with self.buffer_lock:
                self.output += line

    def getoutput(self, timeout=0.1):
        """
        Get the console output that has been cached until now.
        If there's still output incoming, it will continue waiting in 1/10 of a second until no new
        output has been detected.
        :return:
        """
        temp = ""
        while True:
            time.sleep(timeout)
            if self.output == temp:
                break  # no new output for 100 ms, assume it's complete
            else:
                temp = self.output
        with self.buffer_lock:
            temp = self.output
            self.output = ""
        return temp

command = r'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe'
arguments = [command]
arguments.extend(['-y', "srv*D:\debug\symbols*https://msdl.microsoft.com/download/symbols"])  # Symbol path, may use sys.argv[1]
# arguments.extend(['-i', sys.argv[2]])  # Image path
arguments.extend(['-z', sys.argv[3]])  # Dump file
arguments.extend(['-c', ".echo LOADING DONE"])
process = subprocess.Popen(arguments, stdout=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
reader = ReaderThread(process.stdout)
reader.start()

result = ""
while not re.search("LOADING DONE", result):
    result = reader.getoutput()  # ignore initial output

def dbg(command):
    process.stdin.write(command+"\r\n")
    process.stdin.flush()
    return reader.getoutput()


result = dbg("||")
if "User mini" not in result:
    raise Exception("Not a user mode dump")
else:
    print("Yay, it's a user mode dump")

result = dbg("|")
if "explorer" not in result:
    raise Exception("Not an explorer crash")
else:
    print("Yay, it's an Explorer crash")

result = dbg("lm vm explorer")
if re.search(r"^\s*File version:\s*10\.0\..*$", result, re.M):
    print("That's a recent version for which we should analyze crashes")
else:
    raise Exception("That user should update to a newer version before we spend effort on old bugs")

dbg("q")
Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
  • It was really helpful but can u help me writing python script to open crash dump in windbg and after we get the analysis, withou opening the windbg taking the output and only displaying user understandable details to terminal like BUCKET_IT:..... I wrote this command to cmd "windbg -y C:\Users\ASUS\source\repos\makinglib\Release\MyApp.pdb -i C:\Users\ASUS\source\repos\makinglib\Release\MyApp.exe -z C:\Users\ASUS\AppData\Local\CrashDumps\MyApp.exe.3.dmp -c ".logopen /t C:\Users\ASUS\desktop\mylogs\log.txt;!analyze -v" and it worked want this to be done with python script – Prachi Jun 09 '20 at 14:42
  • use cdb.exe instead of windbg it is all console based and you don't need to open the gui – blabb Jun 10 '20 at 04:38
  • i am getting output on treminal but how to do it with python script.It's not working in with it. – Prachi Jun 10 '20 at 13:20
  • @Prachi: I have finished the python script to do step-by-step analysis. See the updated answer – Thomas Weller Jun 10 '20 at 15:07
  • in windbg we had to give symbol path, exe path but here in cdb we are not, in windbg preview it gave me the source code where the error was. But here in cdb it is not. And I am unable to give sym,exe path to the command in cdb. And I ran your code but actually not getting what actually the code is.Can I connect to you to resolve my issue coz I have to do it soon and really need help. – Prachi Jun 10 '20 at 20:08
  • @Prachi: feel free to add more command line parameters to CDB. The `-i` parameter exists there as well. I didn't use it in my example, since I don't have source code of explorer.exe anyway – Thomas Weller Jun 10 '20 at 20:58
  • @Prachi: I'm not doing freelancing for WinDbg any more. Basically you can run any command using `dbg()` and then do with the output whatever is possible with the power of Python. If you need `!analyze`, then do `result = dbg("!analyze", timeout=10)` and you'll get a string for that – Thomas Weller Jun 10 '20 at 21:01
  • yeah I am unable to add path using -i -y when i add them it says everywhere wrong symbols and without the path to symbol and exe how does it give me the error code and details. – Prachi Jun 11 '20 at 06:27
  • @Prachi: It seems you're not familiar with WinDbg. Without proper symbols and without downloading symbols from Microsoft, your results will not be good. I can just say: it is possible to remote control CDB and I've shown how to do it. You can go on and learn how symbols work, e.g. [how to set up symbols](https://stackoverflow.com/a/30019890/480982). Maybe you should [set up your own symbol server](https://stackoverflow.com/a/29323175/480982). As I said, your way will only work if the crash happened with exactly the same version you have on your machine. Anything else will not be correct. – Thomas Weller Jun 11 '20 at 16:51
1

if you don't want to use windbg which is a gui use cdb.exe it is console mode windbg it will output all the results to terminal

here is a demo

F:\>cdb -c "!analyze -v;qq" -z testdmp.dmp | grep -iE "bucket|owner"
DEFAULT_BUCKET_ID:  BREAKPOINT
    Scope:  DEFAULT_BUCKET_ID (Failure Bucket ID prefix)
            BUCKET_ID
FOLLOWUP_NAME:  MachineOwner
BUCKET_ID:  BREAKPOINT_ntdll!LdrpDoDebuggerBreak+30
BUCKET_ID_IMAGE_STR:  ntdll.dll
BUCKET_ID_MODULE_STR:  ntdll
BUCKET_ID_FUNCTION_STR:  LdrpDoDebuggerBreak
BUCKET_ID_OFFSET:  30
BUCKET_ID_MODTIMEDATESTAMP:  c1bb301
BUCKET_ID_MODCHECKSUM:  1f647b
BUCKET_ID_MODVER_STR:  10.0.18362.778
BUCKET_ID_PREFIX_STR:  BREAKPOINT_
FAILURE_BUCKET_ID:  BREAKPOINT_80000003_ntdll.dll!LdrpDoDebuggerBreak
Followup:     MachineOwner

grep is a general purpose string parser
it is built-in in Linux
it is available for windows in several places
if in 32 bit you can use it from gnuwin32 package / Cygwin
if in 64 bit you can find it in git

you can use the native findstr.exe also

:\>dir /b f:\git\usr\bin\gr*
grep.exe
groups.exe

or in msys / mingw / Cygwin / wsl / third party clones /

:\>dir /b /s *grep*.exe
F:\git\mingw64\bin\x86_64-w64-mingw32-agrep.exe
F:\git\mingw64\libexec\git-core\git-grep.exe
F:\git\usr\bin\grep.exe
F:\git\usr\bin\msggrep.exe
F:\msys64\mingw64\bin\msggrep.exe
F:\msys64\mingw64\bin\pcregrep.exe
F:\msys64\mingw64\bin\x86_64-w64-mingw32-agrep.exe
F:\msys64\usr\bin\grep.exe
F:\msys64\usr\bin\grepdiff.exe
F:\msys64\usr\bin\msggrep.exe
F:\msys64\usr\bin\pcregrep.exe

or you can write your own simple string parser in python / JavaScript / typescript / c / c++ / ruby / rust / whatever

here is a sample python word lookup and repeat script

import sys
for line in sys.stdin:
    if "BUCKET" in line:
        print(line)

lets check this out

:\>dir /b *.py
pyfi.py

:\>cat pyfi.py
import sys
for line in sys.stdin:
        if "BUCKET" in line:
                print(line)

:\>cdb -c "!analyze -v ;qq" -z f:\testdmp.dmp | python pyfi.py
DEFAULT_BUCKET_ID:  BREAKPOINT    
    Scope:  DEFAULT_BUCKET_ID (Failure Bucket ID prefix)

            BUCKET_ID

BUCKET_ID:  BREAKPOINT_ntdll!LdrpDoDebuggerBreak+30

BUCKET_ID_IMAGE_STR:  ntdll.dll

BUCKET_ID_MODULE_STR:  ntdll

BUCKET_ID_FUNCTION_STR:  LdrpDoDebuggerBreak

BUCKET_ID_OFFSET:  30

BUCKET_ID_MODTIMEDATESTAMP:  c1bb301

BUCKET_ID_MODCHECKSUM:  1f647b

BUCKET_ID_MODVER_STR:  10.0.18362.778

BUCKET_ID_PREFIX_STR:  BREAKPOINT_

FAILURE_BUCKET_ID:  BREAKPOINT_80000003_ntdll.dll!LdrpDoDebuggerBreak
blabb
  • 8,674
  • 1
  • 18
  • 27