diff --git a/.github/workflows/linuxdemo.yml b/.github/workflows/linuxdemo.yml
index d76d939..abf9b5a 100644
--- a/.github/workflows/linuxdemo.yml
+++ b/.github/workflows/linuxdemo.yml
@@ -1,6 +1,7 @@
name: Linux Demo
on:
- push
+ push:
+ workflow_dispatch:
jobs:
success-case:
runs-on: ubuntu-latest
diff --git a/.github/workflows/macdemo.yml b/.github/workflows/macdemo.yml
index 9a47170..d1fa905 100644
--- a/.github/workflows/macdemo.yml
+++ b/.github/workflows/macdemo.yml
@@ -1,6 +1,7 @@
name: Mac Demo
on:
- push
+ push:
+ workflow_dispatch:
jobs:
success-case:
runs-on: macos-latest
diff --git a/.github/workflows/windowsdemo.yml b/.github/workflows/windowsdemo.yml
index b7a8e3e..b23323c 100644
--- a/.github/workflows/windowsdemo.yml
+++ b/.github/workflows/windowsdemo.yml
@@ -1,6 +1,7 @@
name: Windows Demo
on:
- push
+ push:
+ workflow_dispatch:
jobs:
success-case:
runs-on: windows-latest
diff --git a/baserunner.py b/baserunner.py
index 3407195..4a908fe 100644
--- a/baserunner.py
+++ b/baserunner.py
@@ -83,6 +83,13 @@ def get_dump_dir(cls) -> str:
"""
pass
+ @classmethod
+ def list_dump_dirs(cls) -> List[str]:
+ """
+ List directories where dump file is potentially stored.
+ """
+ return [cls.get_dump_dir()]
+
@abc.abstractmethod
def get_dump_path(self) -> str:
"""
@@ -170,7 +177,7 @@ def run(self):
if self.popen.returncode != 0:
self.info(f'exit code {self.popen.returncode}, waiting until crash dump is written')
- for _ in range(30):
+ for _ in range(120):
if self.detect_crash():
break
time.sleep(1)
@@ -179,8 +186,20 @@ def run(self):
self.err('ERROR: UNABLE TO FIND CRASH DUMP FILE!')
dump_dir = self.get_dump_dir()
pat = self.get_dump_pattern()
- files = glob.glob(os.path.join(dump_dir, pat))
- self.err(f'ls {dump_dir}/{pat}: ' + ' '.join(files[:20]))
+ dump_pat = os.path.join(dump_dir, pat)
+ files = glob.glob(dump_pat)
+ self.err(f'ls {dump_pat}: ' + ' '.join(files[:20]))
+ if not files:
+ self.info("No dump file found")
+ self.info("Listing contents of common dump directories:")
+ dirs = self.list_dump_dirs()
+ for dir in dirs:
+ dump_pat = os.path.join(dir, pat)
+ try:
+ files = glob.glob(dump_pat)
+ except:
+ files = ['
']
+ self.err(f'ls {dump_pat}: ' + ' '.join(files[:20]))
else:
self.info(f'crash dump found: {self.get_dump_path()}')
time.sleep(5)
diff --git a/installwindows.ps1 b/installwindows.ps1
index bc1b2ca..79704e8 100644
--- a/installwindows.ps1
+++ b/installwindows.ps1
@@ -1,8 +1,8 @@
pushd $PSScriptRoot
# Install crash dump registry
-echo Installing crash dump registry
-Start-Process cmd.exe -Wait -Verb runAs -ArgumentList ("/C", "cd", $(Get-Location), "&&", "reg.exe", "IMPORT", "localdumps.reg")
+#Write-Host Installing crash dump registry
+#Start-Process cmd.exe -Wait -Verb runAs -ArgumentList ("/C", "cd", $(Get-Location), "&&", "reg.exe", "IMPORT", "localdumps.reg")
# Download procdump.exe, check cdb.exe, etc
python winrunner.py -i
diff --git a/winrunner.py b/winrunner.py
index 84b850c..b7bf3e6 100644
--- a/winrunner.py
+++ b/winrunner.py
@@ -1,6 +1,8 @@
import datetime
import glob
import os
+import platform
+import re
import shutil
import subprocess
import sys
@@ -17,6 +19,7 @@ class WinRunner(Runner):
"""
Windows runner
"""
+ dump_path: str = None
def __init__(self, path: str, args: List[str], **kwargs):
super().__init__(path, args, **kwargs)
@@ -30,6 +33,38 @@ def __init__(self, path: str, args: List[str], **kwargs):
raise Exception('Could not find procdump.exe')
self.procdump_exe = os.path.abspath(self.procdump_exe)
+ @classmethod
+ def search_cdb(cls, start_dir: str, filter_machine_type = False) -> List[str]:
+ """
+ Find cdb.exe suitable for current platform
+ """
+ results = []
+ ndirs = 0
+
+ if filter_machine_type:
+ machine_type = platform.machine()
+ if machine_type == 'AMD64' or machine_type == 'x86_64':
+ machine_type = 'x64'
+ else:
+ machine_type = 'x86'
+
+ for root, dirs, files in os.walk(start_dir):
+ cdbs = [os.path.join(root, f) for f in files
+ if 'cdb'==f.lower()[:3] and '.exe'==f.lower()[-4:]
+ ]
+ if filter_machine_type:
+ cdbs = [path for path in cdbs
+ if machine_type in path.lower()
+ ]
+ for path in cdbs:
+ print(path)
+ results += cdbs
+ ndirs += 1
+
+ #print(f'Walked {ndirs} directories and found {len(results)} result(s)')
+ return results
+
+
@classmethod
def find_cdb(cls) -> str:
"""
@@ -46,6 +81,7 @@ def find_cdb(cls) -> str:
for path in CDB_PATHS:
if os.path.exists(path):
return path
+
return None
@classmethod
@@ -67,49 +103,131 @@ def find_procdump(cls) -> str:
# giving up
return None
+ @classmethod
+ def expand_envs(cls, path: str) -> str:
+ pat = r'%([a-zA-Z0-9_]+)%'
+ while True:
+ m = re.search(pat, path, re.I)
+ if m is None:
+ break
+ path = path.replace(f'%{m[1]}%', os.environ[m[1].upper()] )
+ return path
+
@classmethod
def get_dump_dir(cls) -> str:
"""Get the actual path of the dump directory that is installed in the registry"""
- home = os.environ['userprofile']
- return os.path.abspath( os.path.join(home, 'Dumps') )
+ if not cls.dump_path:
+ HKLM = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+ LD = r'SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps'
+ ld = winreg.OpenKey(HKLM, LD)
+ path, _ = winreg.QueryValueEx(ld, 'DumpFolder')
+ winreg.CloseKey(ld)
+ cls.dump_path = cls.expand_envs(path)
+ cls.info(f'Dump dir is "{cls.dump_path}"')
+
+ return cls.dump_path
+ @classmethod
+ def list_dump_dirs(cls) -> List[str]:
+ """
+ List directories where dump file is potentially stored.
+ """
+ dirs = [
+ cls.get_dump_dir(),
+ os.getcwd(),
+ 'C:\\Windows\\Minidump',
+ # https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
+ cls.expand_envs('%LOCALAPPDATA%\\CrashDumps'),
+ cls.expand_envs('%SystemRoot%\\Minidump'),
+ ]
+
+ dirs = [os.path.abspath(d) for d in dirs]
+ return dirs
+
@classmethod
def get_dump_pattern(cls) -> str:
"""
Get file pattern to find dump files
"""
return "*.dmp"
+ #return "*"
+
+ @classmethod
+ def list_registry(cls, path: str):
+ HKLM = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+ cls.info(f'Dumping registry "HKLM\\{path}":')
+ try:
+ key = winreg.OpenKey(HKLM, path)
+ except OSError as e:
+ cls.info(" Not found.")
+ return
+
+ try:
+ val = winreg.QueryValue(key, None)
+ cls.info(f' - "root": "{val}"')
+ for i in range(20):
+ name, val, type = winreg.EnumValue(key, i)
+ cls.info(f' - "{name}": "{val}" [type={type}]')
+ except OSError as e:
+ pass
+ winreg.CloseKey(key)
@classmethod
def install(cls):
"""Requires administrator privilege to write to registry"""
-
+ # For top level overview on user mode exception handling:
+ # https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/enabling-postmortem-debugging
+
+ # Disable automatic debugging (otherwise crash dump will not be created)
+ # https://learn.microsoft.com/en-us/windows/win32/debug/configuring-automatic-debugging
+
+ HKLM = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+ AEDEBUG_PATH = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug'
+ cls.list_registry(AEDEBUG_PATH)
+ try:
+ key = winreg.OpenKey(HKLM, AEDEBUG_PATH, access=winreg.KEY_ALL_ACCESS)
+ try:
+ val, _ = winreg.QueryValueEx(key, 'Debugger')
+ if val and val != '-':
+ cls.info(f'Disabling Debugger')
+ winreg.SetValueEx(key, "Debugger", 0, winreg.REG_SZ, "-")
+ winreg.CloseKey(key)
+ key = None
+ cls.list_registry(AEDEBUG_PATH)
+ except OSError as e:
+ cls.info(f' Exception: {e}')
+ if key is not None:
+ winreg.CloseKey(key)
+ except Exception as e:
+ cls.info(f' Caught exception: {e}')
- #
# Setup registry to tell Windows to create minidump on app crash.
# https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
- #
- HKLM = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
+
LD = r'SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps'
+ cls.list_registry(LD)
+
+ dump_dir = None
try:
- ld = winreg.OpenKey(HKLM, LD)
+ dump_dir = cls.get_dump_dir()
except OSError as e:
- ld = winreg.CreateKey(HKLM, LD)
- cls.info(f'Registry "LocalDumps" key created')
-
- dump_dir = cls.get_dump_dir()
- if not os.path.exists(dump_dir):
- os.makedirs(dump_dir)
- cls.info(f'Directory {dump_dir} created')
+ pass
- DUMP_FOLDER = '%userprofile%\Dumps'
try:
- val, type = winreg.QueryValueEx(ld, 'DumpFolder')
+ ld = winreg.OpenKey(HKLM, LD, access=winreg.KEY_ALL_ACCESS)
except OSError as e:
- val, type = '', None
- if val.lower() != DUMP_FOLDER.lower() or type != winreg.REG_EXPAND_SZ:
+ ld = winreg.CreateKey(HKLM, LD)
+ cls.info(f'Registry "LocalDumps" key created')
+
+ if not dump_dir:
+ DUMP_FOLDER = '%userprofile%\Dumps'
+ cls.info(f'Setting DumpFolder')
+ dump_dir = cls.expand_envs(DUMP_FOLDER)
+ if not os.path.exists(dump_dir):
+ os.makedirs(dump_dir)
+ cls.info(f'Directory {dump_dir} created')
+
winreg.SetValueEx(ld, 'DumpFolder', None, winreg.REG_EXPAND_SZ, DUMP_FOLDER)
- cls.info(f'Registry "DumpFolder" set to {DUMP_FOLDER}')
try:
val, type = winreg.QueryValueEx(ld, 'DumpType')
@@ -117,17 +235,24 @@ def install(cls):
val, type = -1, None
MINIDUMP = 1
if val!=MINIDUMP or type!=winreg.REG_DWORD:
+ cls.info(f'Setting Registry "DumpType" set to {MINIDUMP}')
winreg.SetValueEx(ld, 'DumpType', None, winreg.REG_DWORD, MINIDUMP)
- cls.info(f'Registry "DumpType" set to {MINIDUMP}')
winreg.CloseKey(ld)
+ cls.list_registry(LD)
- # Check cdb.exe and procdump.exe
+ # Check cdb.exe and install if necessary
errors = []
cdb_exe = cls.find_cdb()
if not cdb_exe:
- errors.append('cdb.exe not found')
+ cls.info('Installing cdb..')
+ os.system('choco install windows-sdk-10-version-2004-windbg --yes --no-progress')
+
+ cdb_exe = cls.find_cdb()
+ if not cdb_exe:
+ errors.append('cdb.exe not found')
+ # Check procdump
procdump_exe = cls.find_procdump()
if not procdump_exe:
cls.info('Downloading procdump.zip..')