如何在 windbg 上制作 python 脚本以使其更容易使用?

How to make python script over windbg to make it's use easier?

import sys
import subprocess

command = 'C:\Program Files (x86)\Windows Kits\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()

我试过这段代码,我正在尝试输入崩溃转储名称和 exe 位置,然后我必须显示用户可以理解的崩溃分析 ouput.How 才能使用 python 脚本来做到这一点?使用 cpp 脚本更容易吗?

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

您似乎想要解析 !analyze 命令的文本输出。你可以这样做,但你应该知道这个命令可以有很多不同的输出。

假设您正在分析用户模式故障转储。在这种情况下,我会先 运行 一些简单的命令来检查你是否得到了合法的转储。您可以尝试以下命令:

  • || 检查转储类型(应该是 "user")
  • | 获取可执行文件的名称(应与您的应用程序匹配)
  • lmvm <app> 检查可执行文件的版本号

如果一切正常,您可以继续:

  • .exr -1:区分crash和hang。 80000003 断点更可能是挂起或根本没有。

这可以帮助您决定是否应该 运行 !analyze!analyze -hang.

How to do that using Python scripting?

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

此路径包含反斜杠,因此您想转义它们或使用类似 r"C:\Program Files (x86)\Windows Kits\...".

的 r 字符串

您可能应该在此处启动一个可执行文件以使其运行。 cdb.exe 是 WinDbg 的命令行版本。

command.split()

这不仅会拆分参数,还会拆分可执行文件的路径。因此 subprocess.popen() 将尝试一个名为 C:\Program 的应用程序,它不存在。

这可能会更频繁地失败,具体取决于 sys.argv[].

中带空格的参数

我建议您按原样传递选项:

command = r'C:\Program Files (x86)\Windows Kits\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)

请注意,没有涉及 split(),这可能会在错误的位置拆分。

旁注:-i 可能无法按预期工作。如果您从客户端收到故障转储,它们的版本可能与您在磁盘上的版本不同。设置适当的符号服务器来缓解这种情况。

Is it easier with CPP scripting?

会有所不同,不会更容易。

工作示例

这是考虑到上述情况的 Python 代码。由于延迟等原因,它仍然有点老套,但除了时间和输出之外没有真正的指标来决定命令何时完成。 Python 3.8 在 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\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")

如果你不想使用 windbg,它是一个图形用户界面 使用 cdb.exe 它是控制台模式 windbg 它将所有结果输出到终端

这是一个演示

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 是一个通用的字符串解析器
它内置于 Linux
它在多个地方 windows 可用
如果是 32 位,您可以从 gnuwin32 包/Cygwin
使用它 如果是 64 位,你可以在 git

中找到它

您可以使用本机 findstr.exe 也

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

或在 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

或者您可以在 python / JavaScript / typescript / c / c++ / ruby / rust / whatever

中编写自己的简单字符串解析器

这是一个示例 python 单词查找和重复脚本

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

让我们检查一下

:\>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