├── .github ├── Invoke-VisualStudio.ps1 └── workflows │ └── udmp-parser.yml ├── .gitignore ├── LICENSE ├── README.md ├── pics ├── parser-usage.gif └── parser.gif └── src ├── .clang-format ├── CMakeLists.txt ├── build ├── build-release-msvc-wo-python.bat ├── build-release-msvc.bat ├── build-release-osx-wo-python.sh ├── build-release-osx.sh ├── build-release-wo-python.bat ├── build-release-wo-python.sh ├── build-release.bat └── build-release.sh ├── lib ├── CMakeLists.txt └── udmp-parser.h ├── parser ├── CMakeLists.txt └── parser.cc └── python ├── CMakeLists.txt ├── README.md ├── pyproject.toml ├── requirements.txt ├── src ├── udmp_parser.cc └── udmp_parser_utils.cc ├── tests ├── __init__.py ├── pytest.ini ├── requirements.txt ├── test_parser.py └── utils.py └── udmp_parser-stubs ├── __init__.pyi └── utils.pyi /.github/Invoke-VisualStudio.ps1: -------------------------------------------------------------------------------- 1 | Function Invoke-CmdScript { 2 | param( 3 | [String] $scriptName 4 | ) 5 | $cmdLine = """$scriptName"" $args & set" 6 | & $env:SystemRoot\system32\cmd.exe /c $cmdLine | 7 | Select-String '^([^=]*)=(.*)$' | ForEach-Object { 8 | $varName = $_.Matches[0].Groups[1].Value 9 | $varValue = $_.Matches[0].Groups[2].Value 10 | Set-Item Env:$varName $varValue 11 | } 12 | } 13 | 14 | Function Invoke-VisualStudio2022win32 { 15 | Invoke-CmdScript "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars32.bat" 16 | } 17 | 18 | Function Invoke-VisualStudio2022x64 { 19 | Invoke-CmdScript "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" 20 | } 21 | 22 | Function Invoke-VisualStudio2022arm64 { 23 | Invoke-CmdScript "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvarsamd64_arm64.bat" 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/udmp-parser.yml: -------------------------------------------------------------------------------- 1 | name: Builds 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | parser: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | variant: 11 | # Available runners: https://github.com/actions/runner-images 12 | - {os: windows-2025, generator: msvc, arch: x64, config: RelWithDebInfo, } 13 | - {os: windows-2025, generator: msvc, arch: win32, config: RelWithDebInfo, } 14 | - {os: windows-2025, generator: msvc, arch: arm64, config: RelWithDebInfo, } 15 | - {os: ubuntu-22.04, generator: gcc, arch: x64, config: RelWithDebInfo, } 16 | - {os: ubuntu-22.04, generator: clang, arch: x64, config: RelWithDebInfo, } 17 | - {os: ubuntu-24.04, generator: gcc, arch: x64, config: RelWithDebInfo, } 18 | - {os: ubuntu-24.04, generator: clang, arch: x64, config: RelWithDebInfo, } 19 | - {os: macos-15-intel, generator: clang, arch: x64, config: Release, } 20 | - {os: macos-15, generator: clang, arch: arm64, config: Release, } 21 | runs-on: ${{ matrix.variant.os }} 22 | name: parser / ${{ matrix.variant.os }} / ${{ matrix.variant.generator }} / ${{ matrix.variant.arch }} 23 | env: 24 | CMAKE_FLAGS: "-DBUILD_PARSER:BOOL=ON -DBUILD_PYTHON_BINDING:BOOL=OFF" 25 | CMAKE_ARCH: "" 26 | 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v5 30 | 31 | - name: Environment Setup (Windows) 32 | if: matrix.variant.os == 'windows-2025' 33 | run: | 34 | Import-Module .\.github\Invoke-VisualStudio.ps1 35 | Invoke-VisualStudio2022${{ matrix.variant.arch }} 36 | echo "CMAKE_ARCH='-A ${{ matrix.variant.arch }}'" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 37 | 38 | - name: Environment Setup (Linux) 39 | if: matrix.variant.os == 'ubuntu-22.04' 40 | run: | 41 | sudo apt update 42 | 43 | - name: Environment Setup (Linux/GCC) 44 | if: matrix.variant.os == 'ubuntu-22.04' && matrix.variant.generator == 'gcc' 45 | run: | 46 | sudo apt install -y g++ 47 | echo CC=gcc >> $GITHUB_ENV 48 | echo CXX=g++ >> $GITHUB_ENV 49 | 50 | - name: Environment Setup (Linux/CLang) 51 | if: matrix.variant.os == 'ubuntu-22.04' && matrix.variant.generator == 'clang' 52 | run: | 53 | sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" 54 | echo CC=clang >> $GITHUB_ENV 55 | echo CXX=clang++ >> $GITHUB_ENV 56 | 57 | - name: Build 58 | run: | 59 | mkdir build 60 | mkdir artifact 61 | cmake -S ./src -B ./build ${{ env.CMAKE_ARCH }} ${{ env.CMAKE_FLAGS }} 62 | cmake --build ./build --verbose --config ${{ matrix.variant.config }} 63 | cmake --install ./build --config ${{ matrix.variant.config }} --prefix ./artifact 64 | 65 | - name: Upload artifacts 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: parser-${{ matrix.variant.os }}.${{ matrix.variant.generator }}-${{ matrix.variant.arch }}.${{ matrix.variant.config }}-${{ github.sha }} 69 | path: artifact/ 70 | 71 | bindings: 72 | strategy: 73 | fail-fast: false 74 | matrix: 75 | variant: 76 | # Available runners: https://github.com/actions/runner-images 77 | - {os: windows-2025, config: RelWithDebInfo } 78 | - {os: ubuntu-22.04, config: RelWithDebInfo } 79 | - {os: macos-15-intel, config: Release } 80 | runs-on: ${{ matrix.variant.os }} 81 | name: bindings / ${{ matrix.variant.os }} 82 | env: 83 | CMAKE_FLAGS: "-DBUILD_PARSER:BOOL=OFF -DBUILD_PYTHON_BINDING:BOOL=ON" 84 | steps: 85 | - name: Checkout 86 | uses: actions/checkout@v5 87 | 88 | - name: Build / test wheels 89 | uses: pypa/cibuildwheel@v3.2.0 90 | with: 91 | package-dir: ./src/python 92 | 93 | - name: Upload wheel 94 | uses: actions/upload-artifact@v4 95 | with: 96 | name: wheels-${{ matrix.variant.os }} 97 | path: ./wheelhouse/*.whl 98 | 99 | merge: 100 | runs-on: ubuntu-latest 101 | needs: bindings 102 | steps: 103 | - name: Merge Artifacts 104 | uses: actions/upload-artifact/merge@v4 105 | with: 106 | name: wheels 107 | pattern: wheels-* 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .pytest_cache 3 | __pycache__ 4 | *.pyc 5 | src/python/build 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Axel Souchet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # udmp-parser: A Cross-Platform C++ parser library for Windows user minidumps 2 | 3 | ![Build status](https://github.com/0vercl0k/udmp-parser/workflows/Builds/badge.svg) 4 | [![Downloads](https://static.pepy.tech/badge/udmp-parser/month)](https://pepy.tech/project/udmp-parser) 5 | 6 | This is a cross-platform (Windows / Linux / OSX / x86 / x64) C++ library that parses Windows user [minidump](https://docs.microsoft.com/en-us/windows/win32/debug/minidump-files) dumps (`.dump /m` and **not** `.dump /f` in WinDbg usermode). 7 | 8 | ![parser](pics/parser.gif) 9 | 10 | The library supports Intel 32-bit / 64-bit dumps and provides read access to things like: 11 | 12 | - The thread list and their context records, 13 | - The virtual memory, 14 | - The loaded modules. 15 | 16 | Compiled binaries are available in the [releases](https://github.com/0vercl0k/udmp-parser/releases) section. 17 | 18 | ## Parser 19 | 20 | The `parser` application is a small utility to show-case how to use the library and demonstrate its features. You can use it to dump memory, list the loaded modules, dump thread contexts, dump a memory map various, etc. 21 | 22 | ![parser-usage](pics/parser-usage.gif) 23 | 24 | Here are the options supported: 25 | ``` 26 | parser.exe [-a] [-mods] [-mem] [-t [|main] [-h] [-dump ] 27 | 28 | Examples: 29 | Show all: 30 | parser.exe -a user.dmp 31 | Show loaded modules: 32 | parser.exe -mods user.dmp 33 | Show memory map: 34 | parser.exe -mem user.dmp 35 | Show all threads: 36 | parser.exe -t user.dmp 37 | Show thread w/ specific TID: 38 | parser.exe -t 1337 user.dmp 39 | Show foreground thread: 40 | parser.exe -t main user.dmp 41 | Show a memory page at a specific address: 42 | parser.exe -dump 0x7ff00 user.dmp 43 | ``` 44 | 45 | ## Building 46 | 47 | You can build it yourself using the appropriate build script for your platform in the [build](build/) directory. It builds on Linux, Windows, OSX with the [Microsoft](https://visualstudio.microsoft.com/vs/features/cplusplus/), the [LLVM Clang](https://clang.llvm.org/) and [GNU](https://gcc.gnu.org/) compilers. 48 | 49 | Here is an example on Windows: 50 | 51 | ``` 52 | udmp-parser>cd src\build 53 | udmp-parser\src\build>build-release.bat 54 | udmp-parser\src\build>cmake .. -GNinja 55 | -- The C compiler identification is MSVC 19.29.30139.0 56 | -- The CXX compiler identification is MSVC 19.29.30139.0 57 | -- Detecting C compiler ABI info 58 | -- Detecting C compiler ABI info - done 59 | -- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/cl.exe - skipped 60 | -- Detecting C compile features 61 | -- Detecting C compile features - done 62 | -- Detecting CXX compiler ABI info 63 | -- Detecting CXX compiler ABI info - done 64 | -- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/cl.exe - skipped 65 | -- Detecting CXX compile features 66 | -- Detecting CXX compile features - done 67 | -- Configuring done 68 | -- Generating done 69 | -- Build files have been written to: C:/work/codes/udmp-parser/src/build 70 | udmp-parser\src\build>cmake --build . --config RelWithDebInfo 71 | [1/2] Building CXX object parser\CMakeFiles\parser.dir\parser.cc.obj 72 | cl : Command line warning D9025 : overriding '/W3' with '/W4' 73 | [2/2] Linking CXX executable parser\parser.exe 74 | ``` 75 | 76 | And here is another example on Linux: 77 | 78 | ``` 79 | ~/udmp-parser$ cd src/build 80 | ~/udmp-parser/src/build$ chmod u+x build-release.sh 81 | ~/udmp-parser/src/build$ ./build-release.sh 82 | -- The C compiler identification is GNU 9.3.0 83 | -- The CXX compiler identification is GNU 9.3.0 84 | -- Check for working C compiler: /usr/bin/cc 85 | -- Check for working C compiler: /usr/bin/cc -- works 86 | -- Detecting C compiler ABI info 87 | -- Detecting C compiler ABI info - done 88 | -- Detecting C compile features 89 | -- Detecting C compile features - done 90 | -- Check for working CXX compiler: /usr/bin/c++ 91 | -- Check for working CXX compiler: /usr/bin/c++ -- works 92 | -- Detecting CXX compiler ABI info 93 | -- Detecting CXX compiler ABI info - done 94 | -- Detecting CXX compile features 95 | -- Detecting CXX compile features - done 96 | -- Configuring done 97 | -- Generating done 98 | -- Build files have been written to: ~/udmp-parser/src/build 99 | [2/2] Linking CXX executable parser/parser 100 | ``` 101 | 102 | ## Python bindings 103 | 104 | ### From PyPI 105 | 106 | The easiest way is simply to: 107 | 108 | ``` 109 | pip install udmp_parser 110 | ``` 111 | 112 | ### Using PIP 113 | 114 | To install the package 115 | ``` 116 | cd src/python 117 | pip install . 118 | ``` 119 | 120 | To create a wheel pacakge 121 | ``` 122 | cd src/python 123 | pip wheel . 124 | ``` 125 | 126 | 127 | ### Usage 128 | 129 | The Python API was built around the C++ code so the names were preserved. Everything lives within the module `udmp_parser`. 130 | Note: For convenience, a simple [pure Python script](src/python/tests/utils.py) was added to generate minidumps ready to use: 131 | 132 | ```python 133 | $ python -i src/python/tests/utils.py 134 | >>> pid, dmppath = generate_minidump_from_process_name("winver.exe") 135 | Minidump generated successfully: PID=3232 -> minidump-winver.exe-1687024880.dmp 136 | >>> pid 137 | 3232 138 | >>> dmppath 139 | WindowsPath('minidump-winver.exe-1687024880.dmp')) 140 | ``` 141 | 142 | Parsing a minidump object is as simple as: 143 | 144 | ```python 145 | >>> import udmp_parser 146 | >>> udmp_parser.version.major, udmp_parser.version.minor, udmp_parser.version.release 147 | (0, 4, '') 148 | >>> dmp = udmp_parser.UserDumpParser() 149 | >>> dmp.Parse(pathlib.Path("C:/temp/rundll32.dmp")) 150 | True 151 | ``` 152 | 153 | Feature-wise, here are some examples of usage: 154 | 155 | #### Threads 156 | 157 | Get a hashmap of threads (as `{TID: ThreadObject}`), access their information: 158 | 159 | ```python 160 | >>> threads = dmp.Threads() 161 | >>> len(threads) 162 | 14 163 | >>> threads 164 | {5292: Thread(Id=0x14ac, SuspendCount=0x1, Teb=0x2e8000), 165 | 5300: Thread(Id=0x14b4, SuspendCount=0x1, Teb=0x2e5000), 166 | 5316: Thread(Id=0x14c4, SuspendCount=0x1, Teb=0x2df000), 167 | 3136: Thread(Id=0xc40, SuspendCount=0x1, Teb=0x2ee000), 168 | 4204: Thread(Id=0x106c, SuspendCount=0x1, Teb=0x309000), 169 | 5328: Thread(Id=0x14d0, SuspendCount=0x1, Teb=0x2e2000), 170 | 1952: Thread(Id=0x7a0, SuspendCount=0x1, Teb=0x2f7000), 171 | 3888: Thread(Id=0xf30, SuspendCount=0x1, Teb=0x2eb000), 172 | 1760: Thread(Id=0x6e0, SuspendCount=0x1, Teb=0x2f4000), 173 | 792: Thread(Id=0x318, SuspendCount=0x1, Teb=0x300000), 174 | 1972: Thread(Id=0x7b4, SuspendCount=0x1, Teb=0x2fa000), 175 | 1228: Thread(Id=0x4cc, SuspendCount=0x1, Teb=0x2fd000), 176 | 516: Thread(Id=0x204, SuspendCount=0x1, Teb=0x303000), 177 | 2416: Thread(Id=0x970, SuspendCount=0x1, Teb=0x306000)} 178 | ``` 179 | 180 | And access invidual thread, including their register context: 181 | 182 | ```python 183 | >>> thread = threads[5292] 184 | >>> print(f"RIP={thread.Context.Rip:#x} RBP={thread.Context.Rbp:#x} RSP={thread.Context.Rsp:#x}") 185 | RIP=0x7ffc264b0ad4 RBP=0x404fecc RSP=0x7de628 186 | ``` 187 | 188 | 189 | #### Modules 190 | 191 | Get a hashmap of modules (as `{address: ModuleObject}`), access their information: 192 | 193 | ```python 194 | >>> modules = dmp.Modules() 195 | >>> modules 196 | {1572864: Module_t(BaseOfImage=0x180000, SizeOfImage=0x3000, ModuleName=C:\Windows\SysWOW64\sfc.dll), 197 | 10813440: Module_t(BaseOfImage=0xa50000, SizeOfImage=0x14000, ModuleName=C:\Windows\SysWOW64\rundll32.exe), 198 | 1929052160: Module_t(BaseOfImage=0x72fb0000, SizeOfImage=0x11000, ModuleName=C:\Windows\SysWOW64\wkscli.dll), 199 | 1929183232: Module_t(BaseOfImage=0x72fd0000, SizeOfImage=0x52000, ModuleName=C:\Windows\SysWOW64\mswsock.dll), 200 | 1929576448: Module_t(BaseOfImage=0x73030000, SizeOfImage=0xf000, ModuleName=C:\Windows\SysWOW64\browcli.dll), 201 | 1929641984: Module_t(BaseOfImage=0x73040000, SizeOfImage=0xa000, ModuleName=C:\Windows\SysWOW64\davhlpr.dll), 202 | 1929707520: Module_t(BaseOfImage=0x73050000, SizeOfImage=0x19000, ModuleName=C:\Windows\SysWOW64\davclnt.dll), 203 | 1929838592: Module_t(BaseOfImage=0x73070000, SizeOfImage=0x18000, ModuleName=C:\Windows\SysWOW64\ntlanman.dll), 204 | [...] 205 | 140720922427392: Module_t(BaseOfImage=0x7ffc24980000, SizeOfImage=0x83000, ModuleName=C:\Windows\System32\wow64win.dll), 206 | 140720923017216: Module_t(BaseOfImage=0x7ffc24a10000, SizeOfImage=0x59000, ModuleName=C:\Windows\System32\wow64.dll), 207 | 140720950280192: Module_t(BaseOfImage=0x7ffc26410000, SizeOfImage=0x1f8000, ModuleName=C:\Windows\System32\ntdll.dll)} 208 | ``` 209 | 210 | Access directly module info: 211 | 212 | ```python 213 | >>> ntdll_modules = [mod for addr, mod in dmp.Modules().items() if mod.ModuleName.lower().endswith("ntdll.dll")] 214 | >>> len(ntdll_modules) 215 | 2 216 | >>> for ntdll in ntdll_modules: 217 | print(f"{ntdll.ModuleName=} {ntdll.BaseOfImage=:#x} {ntdll.SizeOfImage=:#x}") 218 | 219 | ntdll.ModuleName='C:\\Windows\\SysWOW64\\ntdll.dll' ntdll.BaseOfImage=0x77430000 ntdll.SizeOfImage=0x1a4000 220 | ntdll.ModuleName='C:\\Windows\\System32\\ntdll.dll' ntdll.BaseOfImage=0x7ffc26410000 ntdll.SizeOfImage=0x1f8000 221 | ``` 222 | 223 | A convenience function under `udmp_parser.UserDumpParser.ReadMemory()` can be used to directly read memory from the dump. The signature of the function is as follow: `def ReadMemory(Address: int, Size: int) -> list[int]`. So to dump for instance the `wow64` module, it would go as follow: 224 | 225 | ```python 226 | >>> wow64 = [mod for addr, mod in dmp.Modules().items() if mod.ModuleName.lower() == r"c:\windows\system32\wow64.dll"][0] 227 | >>> print(str(wow64)) 228 | Module_t(BaseOfImage=0x7ffc24a10000, SizeOfImage=0x59000, ModuleName=C:\Windows\System32\wow64.dll) 229 | >>> wow64_module = bytearray(dmp.ReadMemory(wow64.BaseOfImage, wow64.SizeOfImage)) 230 | >>> assert wow64_module[:2] == b'MZ' 231 | >>> import hexdump 232 | >>> hexdump.hexdump(wow64_module[:128]) 233 | 00000000: 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.............. 234 | 00000010: B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@....... 235 | 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 236 | 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 E8 00 00 00 ................ 237 | 00000040: 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ........!..L.!Th 238 | 00000050: 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno 239 | 00000060: 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS 240 | 00000070: 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 mode....$....... 241 | ``` 242 | 243 | 244 | #### Memory 245 | 246 | The memory blocks can also be enumerated in a hashmap `{address: MemoryBlock}`. 247 | 248 | ```python 249 | >>> memory = dmp.Memory() 250 | >>> len(memory) 251 | 0x260 252 | >>> memory 253 | [...] 254 | 0x7ffc26410000: [MemBlock_t(BaseAddress=0x7ffc26410000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x1000)], 255 | 0x7ffc26411000: [MemBlock_t(BaseAddress=0x7ffc26411000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x11c000)], 256 | 0x7ffc2652d000: [MemBlock_t(BaseAddress=0x7ffc2652d000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x49000)], 257 | 0x7ffc26576000: [MemBlock_t(BaseAddress=0x7ffc26576000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x1000)], 258 | 0x7ffc26577000: [MemBlock_t(BaseAddress=0x7ffc26577000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x2000)], 259 | 0x7ffc26579000: [MemBlock_t(BaseAddress=0x7ffc26579000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x9000)], 260 | 0x7ffc26582000: [MemBlock_t(BaseAddress=0x7ffc26582000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x86000)], 261 | 0x7ffc26608000: [MemBlock_t(BaseAddress=0x7ffc26608000, AllocationBase=0x0, AllocationProtect=0x0, RegionSize=0x3d99e8000)]} 262 | ``` 263 | 264 | To facilitate the parsing in a human-friendly manner, some helper functions are provided: 265 | * `udmp_parser.utils.TypeToString`: convert the region type to its meaning (from MSDN) 266 | * `udmp_parser.utils.StateToString`: convert the region state to its meaning (from MSDN) 267 | * `udmp_parser.utils.ProtectionToString`: convert the region protection to its meaning (from MSDN) 268 | 269 | This allows to search and filter in a more comprehensible way: 270 | 271 | 272 | ```python 273 | # Collect only executable memory regions 274 | >>> exec_regions = [region for _, region in dmp.Memory().items() if "PAGE_EXECUTE_READ" in udmp_parser.utils.ProtectionToString(region.Protect)] 275 | 276 | # Pick any, disassemble code using capstone 277 | >>> exec_region = exec_regions[-1] 278 | >>> mem = dmp.ReadMemory(exec_region.BaseAddress, 0x100) 279 | >>> for insn in cs.disasm(bytearray(mem), exec_region.BaseAddress): 280 | print(f"{insn=}") 281 | 282 | insn= 283 | insn= 284 | insn= 285 | insn= 286 | insn= 287 | insn= 288 | insn= 289 | insn= 290 | insn= 291 | insn= 292 | insn= 293 | insn= 294 | insn= 295 | insn= 296 | insn= 297 | insn= 298 | insn= 299 | insn= 300 | insn= 301 | insn= 302 | insn= 303 | [...] 304 | ``` 305 | 306 | # Authors 307 | 308 | * Axel '[@0vercl0k](https://twitter.com/0vercl0k)' Souchet 309 | 310 | # Contributors 311 | 312 | [ ![contributors-img](https://contrib.rocks/image?repo=0vercl0k/udmp-parser) ](https://github.com/0vercl0k/udmp-parser/graphs/contributors) 313 | -------------------------------------------------------------------------------- /pics/parser-usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0vercl0k/udmp-parser/2fff7ac40f22118cd7e68b1df71dbfd22a4906f6/pics/parser-usage.gif -------------------------------------------------------------------------------- /pics/parser.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0vercl0k/udmp-parser/2fff7ac40f22118cd7e68b1df71dbfd22a4906f6/pics/parser.gif -------------------------------------------------------------------------------- /src/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Axel '0vercl0k' Souchet - January 22 2022 2 | # Note: the cmake version must be the same as in python/pyproject.toml::tool.scikit-build:cmake.minimum-version 3 | cmake_minimum_required(VERSION 3.21) 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED True) 6 | set(CMAKE_POSITION_INDEPENDENT_CODE True) 7 | 8 | project( 9 | udmp-parser 10 | DESCRIPTION "A Cross-Platform C++ parser library for Windows user minidumps." 11 | HOMEPAGE_URL https://github.com/0vercl0k/udmp-parser 12 | VERSION 0.6.0 13 | ) 14 | 15 | set(PROJECT_AUTHOR 0vercl0k) 16 | set(PROJECT_LICENSE MIT) 17 | 18 | option(BUILD_PARSER "Build the parser executable for UdmpParser" ${PROJECT_IS_TOP_LEVEL}) 19 | option(UDMP_PARSER_INSTALL "Install targets" ${PROJECT_IS_TOP_LEVEL}) 20 | 21 | add_subdirectory(lib) 22 | 23 | if(BUILD_PARSER) 24 | add_subdirectory(parser) 25 | endif(BUILD_PARSER) 26 | -------------------------------------------------------------------------------- /src/build/build-release-msvc-wo-python.bat: -------------------------------------------------------------------------------- 1 | set ARCH=x64 2 | if "%1"=="win32" set ARCH=Win32 3 | cmake .. -A %ARCH% -DBUILD_PYTHON_BINDING=OFF 4 | cmake --build . --config RelWithDebInfo 5 | -------------------------------------------------------------------------------- /src/build/build-release-msvc.bat: -------------------------------------------------------------------------------- 1 | set ARCH=x64 2 | if "%1"=="win32" set ARCH=Win32 3 | cmake .. -A %ARCH% 4 | cmake --build . --config RelWithDebInfo 5 | -------------------------------------------------------------------------------- /src/build/build-release-osx-wo-python.sh: -------------------------------------------------------------------------------- 1 | cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_BINDING=OFF && cmake --build . 2 | -------------------------------------------------------------------------------- /src/build/build-release-osx.sh: -------------------------------------------------------------------------------- 1 | cmake .. -DCMAKE_BUILD_TYPE=Release && cmake --build . 2 | -------------------------------------------------------------------------------- /src/build/build-release-wo-python.bat: -------------------------------------------------------------------------------- 1 | cmake .. -GNinja -DBUILD_PYTHON_BINDING=OFF 2 | cmake --build . --config RelWithDebInfo 3 | -------------------------------------------------------------------------------- /src/build/build-release-wo-python.sh: -------------------------------------------------------------------------------- 1 | cmake .. -DCMAKE_BUILD_TYPE=Release -GNinja -DBUILD_PYTHON_BINDING=OFF && cmake --build . 2 | -------------------------------------------------------------------------------- /src/build/build-release.bat: -------------------------------------------------------------------------------- 1 | cmake .. -GNinja 2 | cmake --build . --config RelWithDebInfo 3 | -------------------------------------------------------------------------------- /src/build/build-release.sh: -------------------------------------------------------------------------------- 1 | cmake .. -DCMAKE_BUILD_TYPE=Release -GNinja && cmake --build . 2 | -------------------------------------------------------------------------------- /src/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Axel '0vercl0k' Souchet - January 22 2022 2 | add_library(udmp-parser INTERFACE) 3 | target_include_directories(udmp-parser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 4 | 5 | if(WIN32) 6 | target_compile_definitions(udmp-parser INTERFACE NOMINMAX) 7 | endif() 8 | 9 | if(UDMP_PARSER_INSTALL) 10 | install(FILES $/udmp-parser.h DESTINATION inc) 11 | endif(UDMP_PARSER_INSTALL) 12 | -------------------------------------------------------------------------------- /src/lib/udmp-parser.h: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - January 22 2022 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace fs = std::filesystem; 22 | 23 | #if defined(__i386__) || defined(_M_IX86) 24 | #define ARCH_X86 25 | #elif defined(__amd64__) || defined(_M_X64) 26 | #define ARCH_X64 27 | #elif defined(__arm__) || defined(_M_ARM) 28 | #define ARCH_ARM 29 | #elif defined(__aarch64__) || defined(_M_ARM64) 30 | #define ARCH_AARCH64 31 | #else 32 | #error Platform not supported. 33 | #endif 34 | 35 | #if defined(_WIN32) 36 | 37 | #define WINDOWS 38 | #include 39 | 40 | #if defined(ARCH_X86) 41 | #define WINDOWS_X86 42 | #elif defined(ARCH_X64) 43 | #define WINDOWS_X64 44 | #elif defined(ARCH_ARM) 45 | #define WINDOWS_ARM 46 | #elif defined(ARCH_AARCH64) 47 | #define WINDOWS_AARCH64 48 | #endif // ARCH_XXX 49 | 50 | #elif defined(linux) || defined(__linux) || defined(__FreeBSD__) || \ 51 | defined(__FreeBSD_kernel__) || defined(__MACH__) 52 | 53 | #define LINUX 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | #if defined(ARCH_X86) 62 | #define LINUX_X86 63 | #elif defined(ARCH_X64) 64 | #define LINUX_X64 65 | #elif defined(ARCH_ARM) 66 | #define LINUX_ARM 67 | #elif defined(ARCH_AARCH64) 68 | #define LINUX_AARCH64 69 | #endif // ARCH_XXX 70 | 71 | #else 72 | 73 | #error Platform not supported. 74 | 75 | #endif // _WIN32 76 | 77 | namespace udmpparser { 78 | 79 | #ifdef NDEBUG 80 | static void DbgPrintf(const char *Format, ...) { (void)Format; } 81 | #else 82 | static void DbgPrintf(const char *Format, ...) { 83 | va_list ArgList; 84 | va_start(ArgList, Format); 85 | vfprintf(stderr, Format, ArgList); 86 | va_end(ArgList); 87 | } 88 | #endif 89 | 90 | #pragma pack(push) 91 | #pragma pack(1) 92 | 93 | struct Version { 94 | static inline const uint16_t Major = 0; 95 | static inline const uint16_t Minor = 7; 96 | static inline const std::string Release = ""; 97 | }; 98 | 99 | enum class ProcessorArch_t : uint16_t { 100 | X86 = 0, 101 | ARM = 5, 102 | IA64 = 6, 103 | AMD64 = 9, 104 | Unknown = 0xffff 105 | }; 106 | 107 | constexpr uint32_t kWOW64_SIZE_OF_80387_REGISTERS = 80; 108 | 109 | struct FloatingSaveArea32_t { 110 | uint32_t ControlWord; 111 | uint32_t StatusWord; 112 | uint32_t TagWord; 113 | uint32_t ErrorOffset; 114 | uint32_t ErrorSelector; 115 | uint32_t DataOffset; 116 | uint32_t DataSelector; 117 | std::array RegisterArea; 118 | uint32_t Cr0NpxState; 119 | }; 120 | 121 | static_assert(sizeof(FloatingSaveArea32_t) == 0x70); 122 | 123 | constexpr uint32_t kWOW64_MAXIMUM_SUPPORTED_EXTENSION = 512; 124 | 125 | struct Context32_t { 126 | uint32_t ContextFlags; 127 | uint32_t Dr0; 128 | uint32_t Dr1; 129 | uint32_t Dr2; 130 | uint32_t Dr3; 131 | uint32_t Dr6; 132 | uint32_t Dr7; 133 | FloatingSaveArea32_t FloatSave; 134 | uint32_t SegGs; 135 | uint32_t SegFs; 136 | uint32_t SegEs; 137 | uint32_t SegDs; 138 | uint32_t Edi; 139 | uint32_t Esi; 140 | uint32_t Ebx; 141 | uint32_t Edx; 142 | uint32_t Ecx; 143 | uint32_t Eax; 144 | uint32_t Ebp; 145 | uint32_t Eip; 146 | uint32_t SegCs; 147 | uint32_t EFlags; 148 | uint32_t Esp; 149 | uint32_t SegSs; 150 | std::array ExtendedRegisters; 151 | }; 152 | 153 | static_assert(sizeof(Context32_t) == 0x2cc); 154 | 155 | struct uint128_t { 156 | uint64_t Low; 157 | uint64_t High; 158 | }; 159 | 160 | static_assert(sizeof(uint128_t) == 0x10); 161 | 162 | struct Context64_t { 163 | uint64_t P1Home; 164 | uint64_t P2Home; 165 | uint64_t P3Home; 166 | uint64_t P4Home; 167 | uint64_t P5Home; 168 | uint64_t P6Home; 169 | uint32_t ContextFlags; 170 | uint32_t MxCsr; 171 | uint16_t SegCs; 172 | uint16_t SegDs; 173 | uint16_t SegEs; 174 | uint16_t SegFs; 175 | uint16_t SegGs; 176 | uint16_t SegSs; 177 | uint32_t EFlags; 178 | uint64_t Dr0; 179 | uint64_t Dr1; 180 | uint64_t Dr2; 181 | uint64_t Dr3; 182 | uint64_t Dr6; 183 | uint64_t Dr7; 184 | uint64_t Rax; 185 | uint64_t Rcx; 186 | uint64_t Rdx; 187 | uint64_t Rbx; 188 | uint64_t Rsp; 189 | uint64_t Rbp; 190 | uint64_t Rsi; 191 | uint64_t Rdi; 192 | uint64_t R8; 193 | uint64_t R9; 194 | uint64_t R10; 195 | uint64_t R11; 196 | uint64_t R12; 197 | uint64_t R13; 198 | uint64_t R14; 199 | uint64_t R15; 200 | uint64_t Rip; 201 | uint16_t ControlWord; 202 | uint16_t StatusWord; 203 | uint8_t TagWord; 204 | uint8_t Reserved1; 205 | uint16_t ErrorOpcode; 206 | uint32_t ErrorOffset; 207 | uint16_t ErrorSelector; 208 | uint16_t Reserved2; 209 | uint32_t DataOffset; 210 | uint16_t DataSelector; 211 | uint16_t Reserved3; 212 | uint32_t MxCsr2; 213 | uint32_t MxCsr_Mask; 214 | std::array FloatRegisters; 215 | uint128_t Xmm0; 216 | uint128_t Xmm1; 217 | uint128_t Xmm2; 218 | uint128_t Xmm3; 219 | uint128_t Xmm4; 220 | uint128_t Xmm5; 221 | uint128_t Xmm6; 222 | uint128_t Xmm7; 223 | uint128_t Xmm8; 224 | uint128_t Xmm9; 225 | uint128_t Xmm10; 226 | uint128_t Xmm11; 227 | uint128_t Xmm12; 228 | uint128_t Xmm13; 229 | uint128_t Xmm14; 230 | uint128_t Xmm15; 231 | std::array Padding; 232 | std::array VectorRegister; 233 | uint64_t VectorControl; 234 | uint64_t DebugControl; 235 | uint64_t LastBranchToRip; 236 | uint64_t LastBranchFromRip; 237 | uint64_t LastExceptionToRip; 238 | uint64_t LastExceptionFromRip; 239 | }; 240 | 241 | static_assert(offsetof(Context64_t, Xmm0) == 0x1a0); 242 | static_assert(offsetof(Context64_t, VectorRegister) == 0x300); 243 | static_assert(sizeof(Context64_t) == 0x4d0); 244 | 245 | namespace dmp { 246 | 247 | struct Header_t { 248 | static inline const uint32_t ExpectedSignature = 0x50'4d'44'4d; // 'PMDM'; 249 | static inline const uint32_t ValidFlagsMask = 0x00'1f'ff'ff; 250 | uint32_t Signature; 251 | uint16_t Version; 252 | uint16_t ImplementationVersion; 253 | uint32_t NumberOfStreams; 254 | uint32_t StreamDirectoryRva; 255 | uint32_t CheckSum; 256 | uint32_t Reserved; 257 | uint32_t TimeDateStamp; 258 | uint32_t Flags; 259 | 260 | bool LooksGood() const { 261 | if (Signature != ExpectedSignature) { 262 | DbgPrintf("The signature (%" PRIx32 263 | ") does not match the expected signature.\n", 264 | Signature); 265 | return false; 266 | } 267 | 268 | if ((Flags & ValidFlagsMask) != Flags) { 269 | DbgPrintf("The flags have unknown bits set.\n"); 270 | return false; 271 | } 272 | 273 | if (NumberOfStreams == 0) { 274 | DbgPrintf("There is no streams.\n"); 275 | return false; 276 | } 277 | 278 | return true; 279 | } 280 | }; 281 | 282 | static_assert(sizeof(Header_t) == 0x20); 283 | 284 | struct LocationDescriptor32_t { 285 | uint32_t DataSize = 0; 286 | uint32_t Rva = 0; 287 | }; 288 | 289 | static_assert(sizeof(LocationDescriptor32_t) == 0x8); 290 | 291 | struct LocationDescriptor64_t { 292 | uint64_t DataSize = 0; 293 | uint64_t Rva = 0; 294 | }; 295 | 296 | static_assert(sizeof(LocationDescriptor64_t) == 0x10); 297 | 298 | enum class StreamType_t : uint32_t { 299 | Unused = 0, 300 | ThreadList = 3, 301 | ModuleList = 4, 302 | Exception = 6, 303 | SystemInfo = 7, 304 | Memory64List = 9, 305 | MemoryInfoList = 16, 306 | }; 307 | 308 | struct Directory_t { 309 | StreamType_t StreamType = StreamType_t::Unused; 310 | LocationDescriptor32_t Location; 311 | }; 312 | 313 | static_assert(sizeof(Directory_t) == 0x0c); 314 | 315 | struct Memory64ListStreamHdr_t { 316 | uint64_t NumberOfMemoryRanges = 0; 317 | uint64_t BaseRva = 0; 318 | }; 319 | 320 | static_assert(sizeof(Memory64ListStreamHdr_t) == 0x10); 321 | 322 | struct MemoryDescriptor64_t { 323 | uint64_t StartOfMemoryRange = 0; 324 | uint64_t DataSize = 0; 325 | }; 326 | 327 | static_assert(sizeof(MemoryDescriptor64_t) == 0x10); 328 | 329 | struct FixedFileInfo_t { 330 | uint32_t Signature = 0; 331 | uint32_t StrucVersion = 0; 332 | uint32_t FileVersionMS = 0; 333 | uint32_t FileVersionLS = 0; 334 | uint32_t ProductVersionMS = 0; 335 | uint32_t ProductVersionLS = 0; 336 | uint32_t FileFlagsMask = 0; 337 | uint32_t FileFlags = 0; 338 | uint32_t FileOS = 0; 339 | uint32_t FileType = 0; 340 | uint32_t FileSubtype = 0; 341 | uint32_t FileDateMS = 0; 342 | uint32_t FileDateLS = 0; 343 | }; 344 | 345 | static_assert(sizeof(FixedFileInfo_t) == 0x34); 346 | 347 | struct ModuleEntry_t { 348 | uint64_t BaseOfImage = 0; 349 | uint32_t SizeOfImage = 0; 350 | uint32_t CheckSum = 0; 351 | uint32_t TimeDateStamp = 0; 352 | uint32_t ModuleNameRva = 0; 353 | FixedFileInfo_t VersionInfo; 354 | LocationDescriptor32_t CvRecord; 355 | LocationDescriptor32_t MiscRecord; 356 | uint64_t Reserved0 = 0; 357 | uint64_t Reserved1 = 0; 358 | }; 359 | 360 | static_assert(sizeof(ModuleEntry_t) == 0x6c); 361 | 362 | struct MemoryInfoListStream_t { 363 | uint32_t SizeOfHeader = 0; 364 | uint32_t SizeOfEntry = 0; 365 | uint64_t NumberOfEntries = 0; 366 | }; 367 | 368 | static_assert(sizeof(MemoryInfoListStream_t) == 0x10); 369 | 370 | struct MemoryInfo_t { 371 | uint64_t BaseAddress = 0; 372 | uint64_t AllocationBase = 0; 373 | uint32_t AllocationProtect = 0; 374 | uint32_t __alignment1 = 0; 375 | uint64_t RegionSize = 0; 376 | uint32_t State = 0; 377 | uint32_t Protect = 0; 378 | uint32_t Type = 0; 379 | uint32_t __alignment2 = 0; 380 | }; 381 | 382 | static_assert(sizeof(MemoryInfo_t) == 0x30); 383 | 384 | struct MemoryDescriptor_t { 385 | uint64_t StartOfMemoryRange = 0; 386 | LocationDescriptor32_t Memory; 387 | }; 388 | 389 | static_assert(sizeof(MemoryDescriptor_t) == 0x10); 390 | 391 | struct ThreadEntry_t { 392 | uint32_t ThreadId = 0; 393 | uint32_t SuspendCount = 0; 394 | uint32_t PriorityClass = 0; 395 | uint32_t Priority = 0; 396 | uint64_t Teb = 0; 397 | MemoryDescriptor_t Stack; 398 | LocationDescriptor32_t ThreadContext; 399 | }; 400 | 401 | static_assert(sizeof(ThreadEntry_t) == 0x30); 402 | 403 | struct SystemInfoStream_t { 404 | ProcessorArch_t ProcessorArchitecture = ProcessorArch_t::Unknown; 405 | uint16_t ProcessorLevel = 0; 406 | uint16_t ProcessorRevision = 0; 407 | uint8_t NumberOfProcessors = 0; 408 | uint8_t ProductType = 0; 409 | uint32_t MajorVersion = 0; 410 | uint32_t MinorVersion = 0; 411 | uint32_t BuildNumber = 0; 412 | uint32_t PlatformId = 0; 413 | uint32_t CSDVersionRva = 0; 414 | uint16_t SuiteMask = 0; 415 | uint16_t Reserved2 = 0; 416 | }; 417 | 418 | static_assert(sizeof(SystemInfoStream_t) == 0x20); 419 | 420 | constexpr uint32_t kEXCEPTION_MAXIMUM_PARAMETERS = 15; 421 | 422 | struct ExceptionRecord_t { 423 | uint32_t ExceptionCode; 424 | uint32_t ExceptionFlags; 425 | uint64_t ExceptionRecord; 426 | uint64_t ExceptionAddress; 427 | uint32_t NumberParameters; 428 | uint32_t __unusedAlignment; 429 | std::array ExceptionInformation; 430 | }; 431 | 432 | static_assert(sizeof(ExceptionRecord_t) == 0x98); 433 | 434 | struct ExceptionStream_t { 435 | uint32_t ThreadId = 0; 436 | uint32_t __alignment = 0; 437 | ExceptionRecord_t ExceptionRecord; 438 | LocationDescriptor32_t ThreadContext; 439 | }; 440 | 441 | static_assert(sizeof(ExceptionStream_t) == 0xa8); 442 | 443 | } // namespace dmp 444 | #pragma pack(pop) 445 | 446 | class MemoryReader_t { 447 | protected: 448 | std::span View_; 449 | 450 | public: 451 | MemoryReader_t() = default; 452 | virtual ~MemoryReader_t() = default; 453 | MemoryReader_t(const std::span View) : View_(View) {} 454 | MemoryReader_t(MemoryReader_t &&) = default; 455 | MemoryReader_t(const MemoryReader_t &) = delete; 456 | MemoryReader_t &operator=(MemoryReader_t &&) = default; 457 | MemoryReader_t &operator=(const MemoryReader_t &) = delete; 458 | 459 | size_t ViewSize() const { return View_.size_bytes(); } 460 | 461 | bool Read(const size_t Offset, std::span Dest) { 462 | const size_t EndOffset = Offset + Dest.size_bytes(); 463 | if (EndOffset <= Offset) { 464 | DbgPrintf("Overflow detected for EndOffset.\n"); 465 | return false; 466 | } 467 | 468 | if (EndOffset > View_.size_bytes()) { 469 | DbgPrintf("Read request would read OOB.\n"); 470 | return false; 471 | } 472 | 473 | auto Subspan = View_.subspan(Offset, Dest.size()); 474 | std::copy(Subspan.begin(), Subspan.end(), Dest.begin()); 475 | return true; 476 | } 477 | 478 | template bool ReadT(const size_t Offset, Pod_t &Dest) { 479 | std::span Span((uint8_t *)&Dest, sizeof(Dest)); 480 | return Read(Offset, Span); 481 | } 482 | 483 | bool ReadFromLocation32(const dmp::LocationDescriptor32_t &Location, 484 | const size_t Offset, std::span Dest) { 485 | 486 | // 487 | // Exit early if the location is empty (to not trigger the `=` in the below 488 | // check). 489 | // 490 | 491 | if (Dest.size_bytes() == 0) { 492 | return true; 493 | } 494 | 495 | // 496 | // Check if there's any overflows, if what we are reading is indeed 497 | // contained in the block of memory described by `Location`. Worth noting 498 | // that those are useful to detect functional issues but not for memory 499 | // safety. The ones in `Read` are really the one that'll prevent callers 500 | // from reading out of bounds. 501 | // 502 | 503 | const size_t EndOffset = Offset + Dest.size_bytes(); 504 | if (EndOffset <= Offset) { 505 | DbgPrintf("EndOffset overflow.\n"); 506 | return false; 507 | } 508 | 509 | if (EndOffset > size_t(std::numeric_limits::max())) { 510 | DbgPrintf("EndOffset is too large to be truncated to u32.\n"); 511 | return false; 512 | } 513 | 514 | if (uint32_t(EndOffset) > Location.DataSize) { 515 | DbgPrintf("Reading more than what the directory contains.\n"); 516 | return false; 517 | } 518 | 519 | const auto AbsoluteOffset = size_t(Location.Rva) + Offset; 520 | if (AbsoluteOffset <= Offset) { 521 | DbgPrintf("AbsoluteOffset overflow.\n"); 522 | return false; 523 | } 524 | 525 | return Read(AbsoluteOffset, Dest); 526 | } 527 | 528 | template 529 | bool ReadTFromDirectory(const dmp::Directory_t &Directory, 530 | const size_t Offset, Pod_t &Dest) { 531 | std::span Span((uint8_t *)&Dest, sizeof(Dest)); 532 | return ReadFromLocation32(Directory.Location, Offset, Span); 533 | } 534 | }; 535 | 536 | #if defined(WINDOWS) 537 | class FileMapReader_t : public MemoryReader_t { 538 | 539 | // 540 | // Handle to the file mapping. 541 | // 542 | 543 | HANDLE FileMap_ = nullptr; 544 | 545 | public: 546 | ~FileMapReader_t() override { 547 | // 548 | // Unmap the view of the mapping.. 549 | // 550 | 551 | if (!View_.empty()) { 552 | UnmapViewOfFile(View_.data()); 553 | } 554 | 555 | // 556 | // Close the handle to the file mapping. 557 | // 558 | 559 | if (FileMap_ != nullptr) { 560 | CloseHandle(FileMap_); 561 | } 562 | } 563 | 564 | bool MapFile(const char *PathFile) { 565 | 566 | // 567 | // Open the dump file in read-only. 568 | // 569 | 570 | HANDLE File = CreateFileA(PathFile, GENERIC_READ, FILE_SHARE_READ, nullptr, 571 | OPEN_EXISTING, 0, nullptr); 572 | 573 | if (File == nullptr) { 574 | 575 | // 576 | // If we fail to open the file, let the user know. 577 | // 578 | 579 | const DWORD GLE = GetLastError(); 580 | DbgPrintf("CreateFile failed with GLE=%lu.\n", GLE); 581 | 582 | if (GLE == ERROR_FILE_NOT_FOUND) { 583 | DbgPrintf("The file %s was not found.\n", PathFile); 584 | } 585 | 586 | return false; 587 | } 588 | 589 | DWORD High = 0; 590 | const DWORD Low = GetFileSize(File, &High); 591 | const DWORD64 FileSize = (DWORD64(High) << 32) | DWORD64(Low); 592 | if (FileSize > std::numeric_limits::max()) { 593 | DbgPrintf("FileSize is larger than size_t's capacity."); 594 | return false; 595 | } 596 | 597 | // 598 | // Create the ro file mapping. 599 | // 600 | 601 | HANDLE FileMap = 602 | CreateFileMappingA(File, nullptr, PAGE_READONLY, 0, 0, nullptr); 603 | CloseHandle(File); 604 | 605 | if (FileMap == nullptr) { 606 | 607 | // 608 | // If we fail to create a file mapping, let 609 | // the user know. 610 | // 611 | 612 | const DWORD GLE = GetLastError(); 613 | DbgPrintf("CreateFileMapping failed with GLE=%lu.\n", GLE); 614 | return false; 615 | } 616 | 617 | // 618 | // Map a view of the file in memory. 619 | // 620 | 621 | PVOID ViewBase = MapViewOfFile(FileMap, FILE_MAP_READ, 0, 0, 0); 622 | 623 | if (ViewBase == nullptr) { 624 | 625 | CloseHandle(FileMap); 626 | 627 | // 628 | // If we fail to map the view, let the user know. 629 | // 630 | 631 | const DWORD GLE = GetLastError(); 632 | DbgPrintf("MapViewOfFile failed with GLE=%lu.\n", GLE); 633 | return false; 634 | } 635 | 636 | View_ = std::span((uint8_t *)ViewBase, size_t(FileSize)); 637 | return true; 638 | } 639 | }; 640 | 641 | #elif defined(LINUX) 642 | 643 | class FileMapReader_t : public MemoryReader_t { 644 | int Fd_ = -1; 645 | 646 | public: 647 | ~FileMapReader_t() override { 648 | if (!View_.empty()) { 649 | munmap((void *)View_.data(), ViewSize()); 650 | } 651 | 652 | if (Fd_ != -1) { 653 | close(Fd_); 654 | } 655 | } 656 | 657 | bool MapFile(const char *PathFile) { 658 | Fd_ = open(PathFile, O_RDONLY); 659 | if (Fd_ < 0) { 660 | perror("Could not open dump file"); 661 | return false; 662 | } 663 | 664 | struct stat Stat; 665 | if (fstat(Fd_, &Stat) < 0) { 666 | perror("Could not stat dump file"); 667 | return false; 668 | } 669 | 670 | uint8_t *ViewBase = 671 | (uint8_t *)mmap(nullptr, Stat.st_size, PROT_READ, MAP_SHARED, Fd_, 0); 672 | if (ViewBase == MAP_FAILED) { 673 | perror("Could not mmap"); 674 | return false; 675 | } 676 | 677 | View_ = std::span(ViewBase, Stat.st_size); 678 | return true; 679 | } 680 | }; 681 | 682 | #endif 683 | 684 | enum class Arch_t { X86, X64 }; 685 | 686 | struct MemBlock_t { 687 | uint64_t BaseAddress = 0; 688 | uint64_t AllocationBase = 0; 689 | uint32_t AllocationProtect = 0; 690 | uint64_t RegionSize = 0; 691 | uint32_t State = 0; 692 | uint32_t Protect = 0; 693 | uint32_t Type = 0; 694 | uint64_t DataOffset = 0; 695 | uint64_t DataSize = 0; 696 | 697 | MemBlock_t(const dmp::MemoryInfo_t &Info_) 698 | : BaseAddress(Info_.BaseAddress), AllocationBase(Info_.AllocationBase), 699 | AllocationProtect(Info_.AllocationProtect), 700 | RegionSize(Info_.RegionSize), State(Info_.State), 701 | Protect(Info_.Protect), Type(Info_.Type) {}; 702 | 703 | std::string to_string() const { 704 | std::stringstream ss; 705 | ss << "[MemBlock_t("; 706 | ss << "BaseAddress=0x" << std::hex << BaseAddress; 707 | ss << ", AllocationBase=0x" << AllocationBase; 708 | ss << ", AllocationProtect=0x" << AllocationProtect; 709 | ss << ", RegionSize=0x" << RegionSize; 710 | ss << ")]"; 711 | return ss.str(); 712 | } 713 | }; 714 | 715 | struct Module_t { 716 | uint64_t BaseOfImage = 0; 717 | uint32_t SizeOfImage = 0; 718 | uint32_t CheckSum = 0; 719 | uint32_t TimeDateStamp = 0; 720 | std::string ModuleName; 721 | dmp::FixedFileInfo_t VersionInfo; 722 | std::vector CvRecord; 723 | std::vector MiscRecord; 724 | 725 | Module_t(const dmp::ModuleEntry_t &M, const std::string &Name, 726 | std::vector CvRecord_, std::vector MiscRecord_) 727 | : BaseOfImage(M.BaseOfImage), SizeOfImage(M.SizeOfImage), 728 | CheckSum(M.CheckSum), TimeDateStamp(M.TimeDateStamp), ModuleName(Name), 729 | VersionInfo(M.VersionInfo), CvRecord(std::move(CvRecord_)), 730 | MiscRecord(std::move(MiscRecord_)) {} 731 | 732 | std::string to_string() const { 733 | std::stringstream ss; 734 | ss << "Module_t("; 735 | ss << "BaseOfImage=0x" << std::hex << BaseOfImage; 736 | ss << ", SizeOfImage=0x" << SizeOfImage; 737 | ss << ", ModuleName=" << ModuleName; 738 | ss << ")"; 739 | return ss.str(); 740 | } 741 | }; 742 | 743 | class UnknownContext_t {}; 744 | 745 | struct Thread_t { 746 | uint32_t ThreadId = 0; 747 | uint32_t SuspendCount = 0; 748 | uint32_t PriorityClass = 0; 749 | uint32_t Priority = 0; 750 | uint64_t Teb = 0; 751 | std::variant Context; 752 | Thread_t(const dmp::ThreadEntry_t &T, UnknownContext_t &UnknownContext) 753 | : Thread_t(T) { 754 | Context = UnknownContext; 755 | } 756 | 757 | Thread_t(const dmp::ThreadEntry_t &T, Context32_t &Context32) : Thread_t(T) { 758 | Context = Context32; 759 | } 760 | 761 | Thread_t(const dmp::ThreadEntry_t &T, Context64_t &Context64) : Thread_t(T) { 762 | Context = Context64; 763 | } 764 | 765 | std::string to_string() const { 766 | std::stringstream ss; 767 | ss << "Thread("; 768 | ss << "Id=0x" << std::hex << ThreadId << ", "; 769 | ss << "SuspendCount=0x" << std::hex << SuspendCount << ", "; 770 | ss << "Teb=0x" << std::hex << Teb; 771 | ss << ")"; 772 | return ss.str(); 773 | } 774 | 775 | private: 776 | Thread_t(const dmp::ThreadEntry_t &T) 777 | : ThreadId(T.ThreadId), SuspendCount(T.SuspendCount), 778 | PriorityClass(T.PriorityClass), Priority(T.Priority), Teb(T.Teb) {} 779 | }; 780 | 781 | class UserDumpParser { 782 | private: 783 | // 784 | // The memory map; base address -> mem. 785 | // 786 | 787 | std::map Mem_; 788 | 789 | // 790 | // The architecture of the dumped process. 791 | // 792 | 793 | std::optional Arch_; 794 | 795 | // 796 | // The list of loaded modules; base address -> module. 797 | // 798 | 799 | std::map Modules_; 800 | 801 | // 802 | // The thread id of the foreground thread. 803 | // 804 | 805 | std::optional ForegroundThreadId_; 806 | 807 | // 808 | // The list of threads; thread id -> thread. 809 | // 810 | 811 | std::unordered_map Threads_; 812 | 813 | // 814 | // Reader. 815 | // 816 | 817 | std::shared_ptr Reader_; 818 | 819 | public: 820 | // 821 | // Parse the file. 822 | // 823 | 824 | bool Parse(const char *PathFile) { 825 | 826 | // 827 | // Map a view of the file. 828 | // 829 | 830 | if (!fs::exists(PathFile)) { 831 | DbgPrintf("The dump file specified does not exist.\n"); 832 | return false; 833 | } 834 | 835 | auto FileMapReader = std::make_shared(); 836 | if (!FileMapReader->MapFile(PathFile)) { 837 | DbgPrintf("MapFile failed.\n"); 838 | return false; 839 | } 840 | 841 | Reader_ = std::move(FileMapReader); 842 | return Parse(); 843 | } 844 | 845 | bool Parse(const fs::path &PathFile) { 846 | return Parse(PathFile.string().c_str()); 847 | } 848 | 849 | // 850 | // Parse from memory view. 851 | // 852 | 853 | bool Parse(std::shared_ptr Reader) { 854 | if (!Reader) { 855 | DbgPrintf("The memory view passed is null.\n"); 856 | return false; 857 | } 858 | 859 | Reader_ = std::move(Reader); 860 | return Parse(); 861 | } 862 | 863 | const std::map &GetMem() const { return Mem_; } 864 | 865 | const MemBlock_t *GetMemBlock(const void *Address) const { 866 | return GetMemBlock(uint64_t(Address)); 867 | } 868 | 869 | const MemBlock_t *GetMemBlock(const uint64_t Address) const { 870 | auto It = Mem_.upper_bound(Address); 871 | if (It == Mem_.begin()) { 872 | return nullptr; 873 | } 874 | 875 | It--; 876 | const auto &[MemBlockAddress, MemBlock] = *It; 877 | if (Address >= MemBlockAddress && 878 | Address < (MemBlockAddress + MemBlock.RegionSize)) { 879 | return &MemBlock; 880 | } 881 | 882 | return nullptr; 883 | } 884 | 885 | const Module_t *GetModule(const void *Address) const { 886 | return GetModule(uint64_t(Address)); 887 | } 888 | 889 | const Module_t *GetModule(const uint64_t Address) const { 890 | 891 | // 892 | // Look for a module that includes this address. 893 | // 894 | 895 | const auto &Res = 896 | std::find_if(Modules_.begin(), Modules_.end(), [&](const auto &It) { 897 | return Address >= It.first && 898 | Address < (It.first + It.second.SizeOfImage); 899 | }); 900 | 901 | // 902 | // If we have a match, return it! 903 | // 904 | 905 | if (Res != Modules_.end()) { 906 | return &Res->second; 907 | } 908 | 909 | return nullptr; 910 | } 911 | 912 | const std::map &GetModules() const { return Modules_; } 913 | 914 | const std::unordered_map &GetThreads() const { 915 | return Threads_; 916 | } 917 | 918 | std::optional GetForegroundThreadId() const { 919 | return ForegroundThreadId_; 920 | } 921 | 922 | std::string to_string() const { 923 | std::stringstream ss; 924 | ss << "UserDumpParser("; 925 | ss << "ModuleNb=" << Modules_.size(); 926 | ss << ", ThreadNb=" << Threads_.size(); 927 | ss << ")"; 928 | return ss.str(); 929 | } 930 | 931 | std::optional> ReadMemory(const uint64_t Address, 932 | const size_t Size) const { 933 | const auto &Block = GetMemBlock(Address); 934 | if (!Block) { 935 | return std::nullopt; 936 | } 937 | 938 | std::vector Out; 939 | if (Block->DataSize == 0) { 940 | return Out; 941 | } 942 | 943 | const uint64_t OffsetFromStart = Address - Block->BaseAddress; 944 | const uint64_t RemainingSize = Block->DataSize - OffsetFromStart; 945 | if (RemainingSize > uint64_t(std::numeric_limits::max())) { 946 | DbgPrintf("RemainingSize truncation to usize would be lossy.\n"); 947 | return std::nullopt; 948 | } 949 | 950 | const size_t DumpSize = std::min(size_t(RemainingSize), Size); 951 | Out.resize(DumpSize); 952 | if (!Reader_->Read(size_t(Block->DataOffset + OffsetFromStart), Out)) { 953 | DbgPrintf("Failed to ReadMemory.\n"); 954 | return std::nullopt; 955 | } 956 | 957 | return Out; 958 | } 959 | 960 | private: 961 | bool Parse() { 962 | 963 | // 964 | // Read the header.. 965 | // 966 | 967 | dmp::Header_t Hdr; 968 | if (!Reader_->ReadT(0, Hdr)) { 969 | DbgPrintf("The header are not in bounds.\n"); 970 | return false; 971 | } 972 | 973 | // 974 | // ..verify that it looks sane.. 975 | // 976 | 977 | if (!Hdr.LooksGood()) { 978 | DbgPrintf("The header looks wrong.\n"); 979 | return false; 980 | } 981 | 982 | // 983 | // .. walk through its directories. 984 | // 985 | 986 | std::unordered_map Directories; 987 | for (uint32_t StreamIdx = 0; StreamIdx < Hdr.NumberOfStreams; StreamIdx++) { 988 | // 989 | // Read the current directory.. 990 | // 991 | 992 | const auto CurrentStreamDirectoryOffset = 993 | Hdr.StreamDirectoryRva + (StreamIdx * sizeof(dmp::Directory_t)); 994 | dmp::Directory_t CurrentStreamDirectory; 995 | if (!Reader_->ReadT(CurrentStreamDirectoryOffset, 996 | CurrentStreamDirectory)) { 997 | DbgPrintf("The stream directory %" PRIu32 " is out of the bounds.\n", 998 | StreamIdx); 999 | return false; 1000 | } 1001 | 1002 | // 1003 | // ..skip unused ones.. 1004 | // 1005 | 1006 | if (CurrentStreamDirectory.StreamType == dmp::StreamType_t::Unused) { 1007 | continue; 1008 | } 1009 | 1010 | // 1011 | // ..and keep track of the various stream encountered. If we see a stream 1012 | // twice, bail as it isn't expected. 1013 | // 1014 | 1015 | const auto &[_, Inserted] = Directories.try_emplace( 1016 | CurrentStreamDirectory.StreamType, CurrentStreamDirectory); 1017 | 1018 | if (!Inserted) { 1019 | DbgPrintf("There are more than one stream of type %" PRIu32 "\n", 1020 | uint32_t(CurrentStreamDirectory.StreamType)); 1021 | return false; 1022 | } 1023 | } 1024 | 1025 | // 1026 | // Now, let's parse the stream in a specific order. Technically not 1027 | // required, but it makes some logic easier to write. 1028 | // 1029 | 1030 | const dmp::StreamType_t Order[] = { 1031 | dmp::StreamType_t::SystemInfo, dmp::StreamType_t::Exception, 1032 | dmp::StreamType_t::MemoryInfoList, dmp::StreamType_t::Memory64List, 1033 | dmp::StreamType_t::ThreadList, dmp::StreamType_t::ModuleList}; 1034 | 1035 | for (const auto &Type : Order) { 1036 | 1037 | // 1038 | // If we have seen this stream, skip to the next. 1039 | // 1040 | 1041 | const auto &Directory = Directories.find(Type); 1042 | if (Directory == Directories.end()) { 1043 | continue; 1044 | } 1045 | 1046 | // 1047 | // Parse the stream & bail the stream isn't recognized.. 1048 | // 1049 | 1050 | const auto &Result = ParseStream(Directory->second); 1051 | if (!Result.has_value()) { 1052 | DbgPrintf("Seems like there is a missing case for %" PRIu32 1053 | " in ParseStream?\n", 1054 | uint32_t(Type)); 1055 | return false; 1056 | } 1057 | 1058 | // 1059 | // ..or if the parsing of the stream has failed. 1060 | // 1061 | 1062 | if (!Result.value()) { 1063 | DbgPrintf("Failed to parse stream %" PRIu32 ".\n", uint32_t(Type)); 1064 | return false; 1065 | } 1066 | } 1067 | 1068 | // 1069 | // If no foreground thread has been identified, then we're done. 1070 | // 1071 | 1072 | if (!ForegroundThreadId_) { 1073 | return true; 1074 | } 1075 | 1076 | // 1077 | // If we have one, ensure it exists in the list of threads, otherwise bail. 1078 | // 1079 | 1080 | const bool ForegroundThreadExists = 1081 | Threads_.find(*ForegroundThreadId_) != Threads_.end(); 1082 | if (!ForegroundThreadExists) { 1083 | DbgPrintf("The Exception stream referenced a thread id that does not " 1084 | "exist in the thread list.\n"); 1085 | return false; 1086 | } 1087 | 1088 | return true; 1089 | } 1090 | 1091 | std::optional ParseStream(const dmp::Directory_t &StreamDirectory) { 1092 | 1093 | // 1094 | // Parse a stream if we know how to. 1095 | // 1096 | 1097 | switch (StreamDirectory.StreamType) { 1098 | case dmp::StreamType_t::Unused: { 1099 | return true; 1100 | } 1101 | 1102 | case dmp::StreamType_t::SystemInfo: { 1103 | return ParseSystemInfoStream(StreamDirectory); 1104 | } 1105 | 1106 | case dmp::StreamType_t::MemoryInfoList: { 1107 | return ParseMemoryInfoListStream(StreamDirectory); 1108 | } 1109 | 1110 | case dmp::StreamType_t::Memory64List: { 1111 | return ParseMemory64ListStream(StreamDirectory); 1112 | } 1113 | 1114 | case dmp::StreamType_t::ModuleList: { 1115 | return ParseModuleListStream(StreamDirectory); 1116 | } 1117 | 1118 | case dmp::StreamType_t::ThreadList: { 1119 | return ParseThreadListStream(StreamDirectory); 1120 | } 1121 | 1122 | case dmp::StreamType_t::Exception: { 1123 | return ParseExceptionStream(StreamDirectory); 1124 | } 1125 | } 1126 | 1127 | return std::nullopt; 1128 | } 1129 | 1130 | bool ParseExceptionStream(const dmp::Directory_t &StreamDirectory) { 1131 | 1132 | // 1133 | // Read the exception stream.. 1134 | // 1135 | 1136 | dmp::ExceptionStream_t Exception; 1137 | if (!Reader_->ReadTFromDirectory(StreamDirectory, 0, Exception)) { 1138 | DbgPrintf("Failed to read ExceptionStream_t.\n"); 1139 | return false; 1140 | } 1141 | 1142 | // 1143 | // ..and grab the foreground TID (we ignore the rest). 1144 | // 1145 | 1146 | ForegroundThreadId_ = Exception.ThreadId; 1147 | return true; 1148 | } 1149 | 1150 | bool ParseSystemInfoStream(const dmp::Directory_t &StreamDirectory) { 1151 | 1152 | // 1153 | // Read the system infos stream.. 1154 | // 1155 | 1156 | dmp::SystemInfoStream_t SystemInfos; 1157 | if (!Reader_->ReadTFromDirectory(StreamDirectory, 0, SystemInfos)) { 1158 | DbgPrintf("The SystemInfo stream seems malformed.\n"); 1159 | return false; 1160 | } 1161 | 1162 | // 1163 | // ..and grab the processor architecture (we ignore the rest). 1164 | // 1165 | 1166 | Arch_ = SystemInfos.ProcessorArchitecture; 1167 | return true; 1168 | } 1169 | 1170 | template 1171 | bool EmplaceThreadContext(const dmp::LocationDescriptor32_t &ThreadContext, 1172 | const dmp::ThreadEntry_t &Thread) { 1173 | // 1174 | // Make sure the that the thread context location is at least back enough to 1175 | // read a `C_t`; otherwise bail. 1176 | // 1177 | 1178 | C_t Context; 1179 | if (!std::is_same()) { 1180 | if (ThreadContext.DataSize < sizeof(Context)) { 1181 | DbgPrintf("The size of the Context doesn't match up with the thread " 1182 | "context's length.\n"); 1183 | return false; 1184 | } 1185 | 1186 | // 1187 | // Read it.. 1188 | // 1189 | 1190 | if (!Reader_->ReadT(ThreadContext.Rva, Context)) { 1191 | DbgPrintf("Failed to read Context for Thread %" PRIu32 ".\n", 1192 | Thread.ThreadId); 1193 | return false; 1194 | } 1195 | } 1196 | 1197 | // 1198 | // ..and create a `Thread_t`. 1199 | // 1200 | 1201 | Threads_.try_emplace(Thread.ThreadId, Thread, Context); 1202 | return true; 1203 | } 1204 | 1205 | bool ParseThreadListStream(const dmp::Directory_t &StreamDirectory) { 1206 | 1207 | // 1208 | // Read the number of thread.. 1209 | // 1210 | 1211 | uint32_t NumberOfThreads = 0; 1212 | if (!Reader_->ReadTFromDirectory(StreamDirectory, 0, NumberOfThreads)) { 1213 | DbgPrintf("The size of the ThreadList stream is not right.\n"); 1214 | return false; 1215 | } 1216 | 1217 | // 1218 | // ..and walk through every one of them. 1219 | // 1220 | 1221 | for (uint32_t ThreadIdx = 0; ThreadIdx < NumberOfThreads; ThreadIdx++) { 1222 | 1223 | // 1224 | // Read the thread entry which follows the `uint32_t` that contains the 1225 | // number of threads.. 1226 | // 1227 | 1228 | dmp::ThreadEntry_t CurrentThread; 1229 | const auto ThreadEntryOffset = 1230 | sizeof(NumberOfThreads) + (ThreadIdx * sizeof(CurrentThread)); 1231 | if (!Reader_->ReadTFromDirectory(StreamDirectory, ThreadEntryOffset, 1232 | CurrentThread)) { 1233 | DbgPrintf("Failed to read Thread[%" PRIu32 ".\n"); 1234 | return false; 1235 | } 1236 | 1237 | // 1238 | // ..and figure out what kind of context do we expect depending on the 1239 | // architecture if we have found any. 1240 | // 1241 | 1242 | const auto &ThreadContext = CurrentThread.ThreadContext; 1243 | bool Success = false; 1244 | if (Arch_.has_value()) { 1245 | switch (Arch_.value()) { 1246 | case ProcessorArch_t::X86: { 1247 | Success = 1248 | EmplaceThreadContext(ThreadContext, CurrentThread); 1249 | break; 1250 | } 1251 | 1252 | case ProcessorArch_t::AMD64: { 1253 | Success = 1254 | EmplaceThreadContext(ThreadContext, CurrentThread); 1255 | break; 1256 | } 1257 | 1258 | default: { 1259 | Success = EmplaceThreadContext(ThreadContext, 1260 | CurrentThread); 1261 | break; 1262 | } 1263 | } 1264 | } else { 1265 | Success = EmplaceThreadContext(ThreadContext, 1266 | CurrentThread); 1267 | } 1268 | 1269 | if (!Success) { 1270 | return false; 1271 | } 1272 | } 1273 | 1274 | return true; 1275 | } 1276 | 1277 | bool ParseMemoryInfoListStream(const dmp::Directory_t &StreamDirectory) { 1278 | 1279 | // 1280 | // Read the memory info list stream.. 1281 | // 1282 | 1283 | dmp::MemoryInfoListStream_t MemoryInfoList; 1284 | if (!Reader_->ReadTFromDirectory(StreamDirectory, 0, MemoryInfoList)) { 1285 | DbgPrintf("Failed to read MemoryInfoListStream_t.\n"); 1286 | return false; 1287 | } 1288 | 1289 | // 1290 | // ..check that the header looks right.. 1291 | // 1292 | 1293 | if (MemoryInfoList.SizeOfHeader < sizeof(MemoryInfoList)) { 1294 | DbgPrintf("The size of the MemoryInfoList header is not right.\n"); 1295 | return false; 1296 | } 1297 | 1298 | // 1299 | // ..check that the size of the entries looks right.. 1300 | // 1301 | 1302 | if (MemoryInfoList.SizeOfEntry < sizeof(dmp::MemoryInfo_t)) { 1303 | DbgPrintf("The size of the MemoryInfo entries are not right.\n"); 1304 | return false; 1305 | } 1306 | 1307 | // 1308 | // ..and finally check that the size of the stream is what we think it 1309 | // should be. 1310 | // 1311 | 1312 | const uint64_t MaxEntries = std::numeric_limits::max() / 1313 | uint64_t(MemoryInfoList.SizeOfEntry); 1314 | if (MemoryInfoList.NumberOfEntries > MaxEntries) { 1315 | DbgPrintf("Too many entries.\n"); 1316 | return false; 1317 | } 1318 | 1319 | const uint64_t EntryOffset = 1320 | uint64_t(MemoryInfoList.SizeOfEntry) * MemoryInfoList.NumberOfEntries; 1321 | const uint64_t CalculatedStreamSize = 1322 | uint64_t(MemoryInfoList.SizeOfHeader) + EntryOffset; 1323 | if (CalculatedStreamSize <= EntryOffset) { 1324 | DbgPrintf("Overflow with size of header.\n"); 1325 | return false; 1326 | } 1327 | 1328 | if (CalculatedStreamSize != uint64_t(StreamDirectory.Location.DataSize)) { 1329 | DbgPrintf("The MemoryInfoList stream size is not right.\n"); 1330 | return false; 1331 | } 1332 | 1333 | // 1334 | // Walk through the entries.. 1335 | // 1336 | 1337 | for (uint64_t MemoryInfoIdx = 0; 1338 | MemoryInfoIdx < MemoryInfoList.NumberOfEntries; MemoryInfoIdx++) { 1339 | // 1340 | // ..read the entry.. 1341 | // 1342 | 1343 | const uint64_t CurrentMemoryInfoOffset = 1344 | uint64_t(MemoryInfoList.SizeOfHeader) + 1345 | (uint64_t(MemoryInfoList.SizeOfEntry) * MemoryInfoIdx); 1346 | dmp::MemoryInfo_t CurrentMemoryInfo; 1347 | if (!Reader_->ReadTFromDirectory(StreamDirectory, 1348 | size_t(CurrentMemoryInfoOffset), 1349 | CurrentMemoryInfo)) { 1350 | return false; 1351 | } 1352 | 1353 | // 1354 | // ..and insert it in the map. If we've already seen this entry, bail. 1355 | // 1356 | 1357 | const uint64_t BaseAddress = CurrentMemoryInfo.BaseAddress; 1358 | const auto &[_, Inserted] = 1359 | Mem_.try_emplace(BaseAddress, CurrentMemoryInfo); 1360 | 1361 | if (!Inserted) { 1362 | DbgPrintf("The region %" PRIx64 " is already in the memory map.\n", 1363 | BaseAddress); 1364 | return false; 1365 | } 1366 | } 1367 | 1368 | return true; 1369 | } 1370 | 1371 | bool ParseModuleListStream(const dmp::Directory_t &StreamDirectory) { 1372 | 1373 | // 1374 | // Read the number of modules.. 1375 | // 1376 | 1377 | uint32_t NumberOfModules; 1378 | if (!Reader_->ReadTFromDirectory(StreamDirectory, 0, NumberOfModules)) { 1379 | DbgPrintf("Failed to read NumberOfModules.\n"); 1380 | return false; 1381 | } 1382 | 1383 | // 1384 | // ..and walk through the entries. 1385 | // 1386 | 1387 | for (uint32_t ModuleIdx = 0; ModuleIdx < NumberOfModules; ModuleIdx++) { 1388 | // 1389 | // Read the entry.. 1390 | // 1391 | 1392 | dmp::ModuleEntry_t CurrentModule; 1393 | const size_t CurrentModuleOffset = 1394 | sizeof(NumberOfModules) + (sizeof(CurrentModule) * size_t(ModuleIdx)); 1395 | if (!Reader_->ReadTFromDirectory(StreamDirectory, CurrentModuleOffset, 1396 | CurrentModule)) { 1397 | DbgPrintf("Failed to read module entry.\n"); 1398 | return false; 1399 | } 1400 | 1401 | // 1402 | // ..read the length of the module name.. 1403 | // 1404 | 1405 | const uint32_t ModuleNameLengthOffset = CurrentModule.ModuleNameRva; 1406 | uint32_t ModuleNameLength; 1407 | if (!Reader_->ReadT(ModuleNameLengthOffset, ModuleNameLength)) { 1408 | DbgPrintf("Failed to read NameLengthOffset.\n"); 1409 | return false; 1410 | } 1411 | 1412 | // 1413 | // ..verify that it is well formed.. 1414 | // 1415 | 1416 | const bool WellFormed = (ModuleNameLength % 2) == 0; 1417 | if (!WellFormed) { 1418 | DbgPrintf("The MINIDUMP_STRING for the module index %" PRIu32 1419 | " is not well formed.\n", 1420 | ModuleIdx); 1421 | return false; 1422 | } 1423 | 1424 | // 1425 | // ..and finally read the module name. 1426 | // 1427 | 1428 | const size_t ModuleNameOffset = 1429 | size_t(ModuleNameLengthOffset) + sizeof(ModuleNameLength); 1430 | std::string ModuleName(ModuleNameLength, 0); 1431 | std::span ModuleNameSpan((uint8_t *)&ModuleName.front(), 1432 | (uint8_t *)&ModuleName.back()); 1433 | if (!Reader_->Read(ModuleNameOffset, ModuleNameSpan)) { 1434 | DbgPrintf("Failed to read the module name.\n"); 1435 | return false; 1436 | } 1437 | 1438 | // 1439 | // The module name is UTF16, so assume it is ASCII encoded so skip every 1440 | // second bytes. 1441 | // 1442 | 1443 | for (size_t CharIdx = 0; CharIdx < ModuleNameLength; CharIdx += 2) { 1444 | if (!isprint(ModuleName[CharIdx])) { 1445 | DbgPrintf("The MINIDUMP_STRING for the module index %" PRIu32 1446 | " has a non printable ascii character.\n", 1447 | ModuleIdx); 1448 | return false; 1449 | } 1450 | 1451 | ModuleName[CharIdx / 2] = ModuleName[CharIdx]; 1452 | } 1453 | 1454 | // 1455 | // Resize the module name buffer when we read all its ASCII characters. 1456 | // 1457 | 1458 | ModuleName.resize(ModuleNameLength / 2); 1459 | ModuleName.shrink_to_fit(); 1460 | 1461 | // 1462 | // Read the Cv record.. 1463 | // 1464 | 1465 | std::vector CvRecord(CurrentModule.CvRecord.DataSize); 1466 | if (!Reader_->ReadFromLocation32(CurrentModule.CvRecord, 0, CvRecord)) { 1467 | DbgPrintf("Failed to read CvRecord.\n"); 1468 | return false; 1469 | } 1470 | 1471 | // 1472 | // ..read the Misc record.. 1473 | // 1474 | 1475 | std::vector MiscRecord(CurrentModule.MiscRecord.DataSize); 1476 | if (!Reader_->ReadFromLocation32(CurrentModule.MiscRecord, 0, 1477 | MiscRecord)) { 1478 | DbgPrintf("Failed to read MiscRecord.\n"); 1479 | return false; 1480 | } 1481 | 1482 | // 1483 | // ..and finally create a `Module_t`. 1484 | // 1485 | 1486 | Modules_.try_emplace(CurrentModule.BaseOfImage, CurrentModule, ModuleName, 1487 | std::move(CvRecord), std::move(MiscRecord)); 1488 | } 1489 | 1490 | return true; 1491 | } 1492 | 1493 | bool ParseMemory64ListStream(const dmp::Directory_t &StreamDirectory) { 1494 | 1495 | // 1496 | // Read the memory 64 list header.. 1497 | // 1498 | 1499 | dmp::Memory64ListStreamHdr_t Memory64List; 1500 | if (!Reader_->ReadTFromDirectory(StreamDirectory, 0, Memory64List)) { 1501 | DbgPrintf("Failed to read Memory64ListStreamHdr_t.\n"); 1502 | return false; 1503 | } 1504 | 1505 | // 1506 | // Grab the offset of where the actual memory content is stored at. 1507 | // 1508 | 1509 | const uint64_t NumberOfMemoryRanges = Memory64List.NumberOfMemoryRanges; 1510 | uint64_t CurrentDataOffset = Memory64List.BaseRva; 1511 | 1512 | // 1513 | // Walk through the entries.. 1514 | // 1515 | 1516 | for (uint32_t RangeIdx = 0; RangeIdx < NumberOfMemoryRanges; RangeIdx++) { 1517 | 1518 | // 1519 | // ..read a descriptor.. 1520 | // 1521 | 1522 | dmp::MemoryDescriptor64_t CurrentDescriptor; 1523 | const size_t CurrentDescriptorOffset = 1524 | sizeof(Memory64List) + (sizeof(CurrentDescriptor) * size_t(RangeIdx)); 1525 | 1526 | if (!Reader_->ReadTFromDirectory(StreamDirectory, CurrentDescriptorOffset, 1527 | CurrentDescriptor)) { 1528 | DbgPrintf("Failed to read MemoryDescriptor64_t.\n"); 1529 | return false; 1530 | } 1531 | 1532 | // 1533 | // ..and if no existing entry is found, something funky is going on. 1534 | // 1535 | 1536 | const uint64_t StartOfMemoryRange = CurrentDescriptor.StartOfMemoryRange; 1537 | const auto &It = Mem_.find(StartOfMemoryRange); 1538 | if (It == Mem_.end()) { 1539 | DbgPrintf("The memory region starting at %" PRIx64 1540 | " does not exist in the map.\n", 1541 | StartOfMemoryRange); 1542 | return false; 1543 | } 1544 | 1545 | // 1546 | // Update the entry. 1547 | // 1548 | 1549 | const size_t DataSize = size_t(CurrentDescriptor.DataSize); 1550 | It->second.DataOffset = CurrentDataOffset; 1551 | It->second.DataSize = DataSize; 1552 | CurrentDataOffset += DataSize; 1553 | } 1554 | 1555 | return true; 1556 | } 1557 | }; 1558 | } // namespace udmpparser 1559 | -------------------------------------------------------------------------------- /src/parser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Axel '0vercl0k' Souchet - January 22 2022 2 | add_executable(parser parser.cc) 3 | 4 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 5 | message(STATUS "Using GCC") 6 | target_compile_options(parser PRIVATE -Wall -Wextra -Werror -pedantic) 7 | 8 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 9 | message(STATUS "Using Clang") 10 | target_compile_options(parser PRIVATE -Wall -Wextra -Werror -pedantic) 11 | 12 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 13 | message(STATUS "Using MSVC") 14 | target_compile_options(parser PRIVATE /W4 /WX) 15 | target_compile_definitions(parser PRIVATE NOMINMAX) 16 | else() 17 | message(STATUS "Using ${CMAKE_CXX_COMPILER_ID}") 18 | endif() 19 | 20 | target_link_libraries(parser PRIVATE udmp-parser) 21 | 22 | if(UDMP_PARSER_INSTALL) 23 | install(FILES $ DESTINATION bin) 24 | 25 | if(MSVC) 26 | install(FILES $ DESTINATION bin OPTIONAL) 27 | endif(MSVC) 28 | endif() -------------------------------------------------------------------------------- /src/parser/parser.cc: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - January 22 2022 2 | #include "udmp-parser.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // 10 | // Bunch of utils. 11 | // 12 | 13 | namespace utils { 14 | 15 | // 16 | // Get a string representation of the memory state. 17 | // 18 | 19 | const char *StateToString(const uint32_t State) { 20 | switch (State) { 21 | case 0x10'00: { 22 | return "MEM_COMMIT"; 23 | } 24 | 25 | case 0x20'00: { 26 | return "MEM_RESERVE"; 27 | } 28 | 29 | case 0x1'00'00: { 30 | return "MEM_FREE"; 31 | } 32 | 33 | default: { 34 | return "UNKNOWN"; 35 | } 36 | } 37 | } 38 | 39 | const char *TypeToString(const uint32_t Type) { 40 | switch (Type) { 41 | case 0x2'00'00: { 42 | return "MEM_PRIVATE"; 43 | } 44 | case 0x4'00'00: { 45 | return "MEM_MAPPED"; 46 | } 47 | case 0x1'00'00'00: { 48 | return "MEM_IMAGE"; 49 | } 50 | default: { 51 | return "UNKNOWN"; 52 | } 53 | } 54 | } 55 | 56 | // 57 | // Print an Intel X86 context like windbg. 58 | // 59 | 60 | void PrintX86Context(const udmpparser::Context32_t &C, const int Prefix = 0) { 61 | printf("%*ceax=%08" PRIx32 " ebx=%08" PRIx32 " ecx=%08" PRIx32 62 | " edx=%08" PRIx32 " esi=%08" PRIx32 " edi=%08" PRIx32 "\n", 63 | Prefix, ' ', C.Eax, C.Ebx, C.Ecx, C.Edx, C.Esi, C.Edi); 64 | printf("%*ceip=%08" PRIx32 " esp=%08" PRIx32 " ebp=%08" PRIx32 "\n", Prefix, 65 | ' ', C.Eip, C.Esp, C.Ebp); 66 | printf("%*ccs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x " 67 | "efl=%08x\n", 68 | Prefix, ' ', C.SegCs, C.SegSs, C.SegDs, C.SegEs, C.SegFs, C.SegGs, 69 | C.EFlags); 70 | } 71 | 72 | // 73 | // Print an Intel X64 context like windbg. 74 | // 75 | 76 | void PrintX64Context(const udmpparser::Context64_t &C, const int Prefix = 0) { 77 | printf("%*crax=%016" PRIx64 " rbx=%016" PRIx64 " rcx=%016" PRIx64 "\n", 78 | Prefix, ' ', C.Rax, C.Rbx, C.Rcx); 79 | printf("%*crdx=%016" PRIx64 " rsi=%016" PRIx64 " rdi=%016" PRIx64 "\n", 80 | Prefix, ' ', C.Rdx, C.Rsi, C.Rdi); 81 | printf("%*crip=%016" PRIx64 " rsp=%016" PRIx64 " rbp=%016" PRIx64 "\n", 82 | Prefix, ' ', C.Rip, C.Rsp, C.Rbp); 83 | printf("%*c r8=%016" PRIx64 " r9=%016" PRIx64 " r10=%016" PRIx64 "\n", 84 | Prefix, ' ', C.R8, C.R9, C.R10); 85 | printf("%*cr11=%016" PRIx64 " r12=%016" PRIx64 " r13=%016" PRIx64 "\n", 86 | Prefix, ' ', C.R11, C.R12, C.R13); 87 | printf("%*cr14=%016" PRIx64 " r15=%016" PRIx64 "\n", Prefix, ' ', C.R14, 88 | C.R15); 89 | printf("%*ccs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x " 90 | "efl=%08x\n", 91 | Prefix, ' ', C.SegCs, C.SegSs, C.SegDs, C.SegEs, C.SegFs, C.SegGs, 92 | C.EFlags); 93 | printf("%*cfpcw=%04x fpsw=%04x fptw=%04x\n", Prefix, ' ', C.ControlWord, 94 | C.StatusWord, C.TagWord); 95 | printf("%*c st0=%016" PRIx64 "%016" PRIx64 " st1=%016" PRIx64 96 | "%016" PRIx64 "\n", 97 | Prefix, ' ', C.FloatRegisters[0].High, C.FloatRegisters[0].Low, 98 | C.FloatRegisters[1].High, C.FloatRegisters[1].Low); 99 | printf("%*c st2=%016" PRIx64 "%016" PRIx64 " st3=%016" PRIx64 100 | "%016" PRIx64 "\n", 101 | Prefix, ' ', C.FloatRegisters[2].High, C.FloatRegisters[2].Low, 102 | C.FloatRegisters[3].High, C.FloatRegisters[3].Low); 103 | printf("%*c st4=%016" PRIx64 "%016" PRIx64 " st5=%016" PRIx64 104 | "%016" PRIx64 "\n", 105 | Prefix, ' ', C.FloatRegisters[4].High, C.FloatRegisters[4].Low, 106 | C.FloatRegisters[5].High, C.FloatRegisters[5].Low); 107 | printf("%*c st6=%016" PRIx64 "%016" PRIx64 " st7=%016" PRIx64 108 | "%016" PRIx64 "\n", 109 | Prefix, ' ', C.FloatRegisters[6].High, C.FloatRegisters[6].Low, 110 | C.FloatRegisters[7].High, C.FloatRegisters[7].Low); 111 | printf("%*c xmm0=%016" PRIx64 "%016" PRIx64 " xmm1=%016" PRIx64 112 | "%016" PRIx64 "\n", 113 | Prefix, ' ', C.Xmm0.High, C.Xmm0.Low, C.Xmm1.High, C.Xmm1.Low); 114 | printf("%*c xmm2=%016" PRIx64 "%016" PRIx64 " xmm3=%016" PRIx64 115 | "%016" PRIx64 "\n", 116 | Prefix, ' ', C.Xmm2.High, C.Xmm2.Low, C.Xmm3.High, C.Xmm3.Low); 117 | printf("%*c xmm4=%016" PRIx64 "%016" PRIx64 " xmm5=%016" PRIx64 118 | "%016" PRIx64 "\n", 119 | Prefix, ' ', C.Xmm4.High, C.Xmm4.Low, C.Xmm5.High, C.Xmm5.Low); 120 | printf("%*c xmm6=%016" PRIx64 "%016" PRIx64 " xmm7=%016" PRIx64 121 | "%016" PRIx64 "\n", 122 | Prefix, ' ', C.Xmm6.High, C.Xmm6.Low, C.Xmm7.High, C.Xmm7.Low); 123 | printf("%*c xmm8=%016" PRIx64 "%016" PRIx64 " xmm9=%016" PRIx64 124 | "%016" PRIx64 "\n", 125 | Prefix, ' ', C.Xmm8.High, C.Xmm8.Low, C.Xmm9.High, C.Xmm9.Low); 126 | printf("%*cxmm10=%016" PRIx64 "%016" PRIx64 " xmm11=%016" PRIx64 127 | "%016" PRIx64 "\n", 128 | Prefix, ' ', C.Xmm10.High, C.Xmm10.Low, C.Xmm11.High, C.Xmm11.Low); 129 | printf("%*cxmm12=%016" PRIx64 "%016" PRIx64 " xmm13=%016" PRIx64 130 | "%016" PRIx64 "\n", 131 | Prefix, ' ', C.Xmm12.High, C.Xmm12.Low, C.Xmm13.High, C.Xmm13.Low); 132 | printf("%*cxmm14=%016" PRIx64 "%016" PRIx64 " xmm15=%016" PRIx64 133 | "%016" PRIx64 "\n", 134 | Prefix, ' ', C.Xmm14.High, C.Xmm14.Low, C.Xmm15.High, C.Xmm15.Low); 135 | } 136 | 137 | // 138 | // Print a CPU context like windbg does. 139 | // 140 | 141 | void PrintContext(const udmpparser::Thread_t &T, const int Prefix = 0) { 142 | printf("%*cTID %" PRIx32 ", TEB %" PRIx64 "\n", Prefix, ' ', T.ThreadId, 143 | T.Teb); 144 | printf("%*cContext:\n", Prefix, ' '); 145 | const auto &C = T.Context; 146 | if (std::holds_alternative(C)) { 147 | const auto &C32 = std::get(C); 148 | return PrintX86Context(C32, Prefix + 2); 149 | } 150 | 151 | if (std::holds_alternative(C)) { 152 | const auto &C64 = std::get(C); 153 | return PrintX64Context(C64, Prefix + 2); 154 | } 155 | 156 | printf("%*cUnknown type of context!\n", Prefix, ' '); 157 | } 158 | 159 | void Hexdump(const uint64_t Address, const std::span Buffer, 160 | const int Prefix = 0) { 161 | const auto Len = Buffer.size_bytes(); 162 | for (size_t i = 0; i < Len; i += 16) { 163 | printf("%*c%" PRIx64 ": ", Prefix, ' ', Address + i); 164 | for (size_t j = 0; j < 16; j++) { 165 | if (i + j < Len) { 166 | printf("%02" PRIx8, Buffer[i + j]); 167 | } else { 168 | printf(" "); 169 | } 170 | } 171 | printf(" |"); 172 | for (size_t j = 0; j < 16; j++) { 173 | if (i + j < Len) { 174 | printf("%c", isprint(Buffer[i + j]) ? char(Buffer[i + j]) : '.'); 175 | } else { 176 | printf(" "); 177 | } 178 | } 179 | printf("|\n"); 180 | } 181 | } 182 | 183 | } // namespace utils 184 | 185 | // 186 | // Delimiter. 187 | // 188 | 189 | #define DELIMITER \ 190 | "----------------------------------------------------------------------" \ 191 | "----------" 192 | 193 | // 194 | // The options available for the parser. 195 | // 196 | 197 | struct Options_t { 198 | 199 | // 200 | // This is enabled if -h is used. 201 | // 202 | 203 | bool ShowHelp = false; 204 | 205 | // 206 | // This is enabled if -a is used. 207 | // 208 | 209 | bool ShowAll = false; 210 | 211 | // 212 | // This is enabled if -mods is used. 213 | // 214 | 215 | bool ShowLoadedModules = false; 216 | 217 | // 218 | // This is enabled if -mem is used. 219 | // 220 | 221 | bool ShowMemoryMap = false; 222 | 223 | // 224 | // This is enabled if -t is used. 225 | // 226 | 227 | bool ShowThreads = false; 228 | 229 | // 230 | // This holds a TID if -t is followed by an integer. 231 | // 232 | 233 | std::optional Tid; 234 | 235 | // 236 | // This is enabled if -t main is used. 237 | // 238 | 239 | bool ShowForegroundThread = false; 240 | 241 | // 242 | // This is set if -dump is used. 243 | // 244 | 245 | std::optional DumpAddress; 246 | 247 | // 248 | // The path to the dump file. 249 | // 250 | 251 | std::string_view DumpPath; 252 | }; 253 | 254 | // 255 | // Help menu. 256 | // 257 | 258 | void Help() { 259 | printf("parser.exe [-a] [-mods] [-mem] [-t [] [-h] [-dump ] " 260 | "\n"); 261 | printf("\n"); 262 | printf("Examples:\n"); 263 | printf(" Show all:\n"); 264 | printf(" parser.exe -a user.dmp\n"); 265 | printf(" Show loaded modules:\n"); 266 | printf(" parser.exe -mods user.dmp\n"); 267 | printf(" Show memory map:\n"); 268 | printf(" parser.exe -mem user.dmp\n"); 269 | printf(" Show all threads:\n"); 270 | printf(" parser.exe -t user.dmp\n"); 271 | printf(" Show thread w/ specific TID:\n"); 272 | printf(" parser.exe -t 1337 user.dmp\n"); 273 | printf(" Show foreground thread:\n"); 274 | printf(" parser.exe -t main user.dmp\n"); 275 | printf(" Dump a memory page at a specific address:\n"); 276 | printf(" parser.exe -dump 0x7ff00 user.dmp\n"); 277 | printf("\n"); 278 | } 279 | 280 | int main(int argc, char *argv[]) { 281 | 282 | // 283 | // Parse the options. 284 | // 285 | 286 | Options_t Opts; 287 | for (int ArgIdx = 1; ArgIdx < argc; ArgIdx++) { 288 | const std::string_view Arg(argv[ArgIdx]); 289 | const bool IsLastArg = (ArgIdx + 1) >= argc; 290 | if (Arg == "-a") { 291 | Opts.ShowAll = true; 292 | } else if (Arg == "-h") { 293 | Opts.ShowHelp = true; 294 | } else if (Arg == "-mods") { 295 | Opts.ShowLoadedModules = true; 296 | } else if (Arg == "-mem") { 297 | Opts.ShowMemoryMap = true; 298 | } else if (Arg == "-t") { 299 | Opts.ShowThreads = true; 300 | 301 | // 302 | // Verify that there's enough argument for an integer and the last 303 | // argument which is the dump. 304 | // 305 | const std::string_view NextArg(IsLastArg ? "" : argv[ArgIdx + 1]); 306 | if (NextArg == "main") { 307 | Opts.ShowForegroundThread = true; 308 | } else { 309 | char *End = nullptr; 310 | const uint32_t Tid = std::strtoul(NextArg.data(), &End, 0); 311 | const bool Valid = errno == 0 && End != NextArg.data(); 312 | if (Valid) { 313 | Opts.Tid = Tid; 314 | } 315 | } 316 | 317 | // 318 | // If we grabbed a TID or set the foreground thread, skip an argument. 319 | // 320 | 321 | if (Opts.Tid.has_value() || Opts.ShowForegroundThread) { 322 | ArgIdx++; 323 | } 324 | } else if (Arg == "-dump") { 325 | const std::string_view NextArg(IsLastArg ? "" : argv[ArgIdx + 1]); 326 | char *End = nullptr; 327 | const uint64_t Address = std::strtoull(NextArg.data(), &End, 0); 328 | const bool Valid = errno == 0 && End != NextArg.data(); 329 | if (Valid) { 330 | Opts.DumpAddress = Address; 331 | ArgIdx++; 332 | } 333 | } else if (IsLastArg) { 334 | 335 | // 336 | // If this is the last argument, then this is the dump path. 337 | // 338 | 339 | Opts.DumpPath = Arg; 340 | } else { 341 | 342 | // 343 | // Otherwise it seems that the user passed something wrong? 344 | // 345 | 346 | printf("The argument %s is not recognized.\n\n", Arg.data()); 347 | Help(); 348 | return EXIT_FAILURE; 349 | } 350 | } 351 | 352 | // 353 | // Display help if wanted or no argument were specified. 354 | // 355 | 356 | if (argc == 1 || Opts.ShowHelp) { 357 | Help(); 358 | return argc == 1 ? EXIT_FAILURE : EXIT_SUCCESS; 359 | } 360 | 361 | // 362 | // Initialize the parser. 363 | // 364 | 365 | udmpparser::UserDumpParser UserDump; 366 | if (!UserDump.Parse(Opts.DumpPath.data())) { 367 | printf("Loading '%s' failed.\n", Opts.DumpPath.data()); 368 | return EXIT_FAILURE; 369 | } 370 | 371 | // 372 | // Show the loaded modules. 373 | // 374 | 375 | if (Opts.ShowLoadedModules || Opts.ShowAll) { 376 | printf(DELIMITER "\nLoaded modules:\n"); 377 | const auto &Modules = UserDump.GetModules(); 378 | for (const auto &[Base, ModuleInfo] : Modules) { 379 | printf(" %016" PRIx64 ": %s\n", Base, ModuleInfo.ModuleName.c_str()); 380 | } 381 | } 382 | 383 | // 384 | // Show the memory map. 385 | // 386 | 387 | if (Opts.ShowMemoryMap || Opts.ShowAll) { 388 | printf(DELIMITER "\nMemory map:\n"); 389 | for (const auto &[_, Descriptor] : UserDump.GetMem()) { 390 | 391 | // 392 | // Get a string representation for the state of the region. 393 | // 394 | 395 | const char *State = utils::StateToString(Descriptor.State); 396 | 397 | // 398 | // Get a string representation for the memory type of the region. 399 | // 400 | 401 | const char *Type = ""; 402 | if (strcmp(State, "MEM_FREE")) { 403 | Type = utils::TypeToString(Descriptor.Type); 404 | } 405 | 406 | // 407 | // Display start / end / size / state / type of the region. 408 | // 409 | 410 | const auto BaseAddress = Descriptor.BaseAddress; 411 | const auto RegionSize = Descriptor.RegionSize; 412 | const auto EndAddress = BaseAddress + RegionSize; 413 | printf(" %16" PRIx64 " %16" PRIx64 " %16" PRIx64 " %11s %11s", 414 | BaseAddress, EndAddress, RegionSize, State, Type); 415 | 416 | // 417 | // Is the region actually part of a module? 418 | // 419 | 420 | const auto &Module = UserDump.GetModule(BaseAddress); 421 | 422 | // 423 | // If we found a module that overlaps with this region, let's dump it as 424 | // well. 425 | // 426 | 427 | if (Module != nullptr) { 428 | 429 | // 430 | // Find the last '\' to get the module name off its path. 431 | // 432 | 433 | const auto &ModulePathName = Module->ModuleName; 434 | auto ModuleNameOffset = ModulePathName.find_last_of('\\'); 435 | if (ModuleNameOffset == ModulePathName.npos) { 436 | ModuleNameOffset = 0; 437 | } else { 438 | ModuleNameOffset++; 439 | } 440 | 441 | // 442 | // Show the module path & module name. 443 | // 444 | 445 | printf(" [%s; \"%s\"]", &ModulePathName[ModuleNameOffset], 446 | ModulePathName.c_str()); 447 | } 448 | 449 | // 450 | // Dump the first 4 bytes of the region if its available in the dump. 451 | // 452 | 453 | if (Descriptor.DataSize >= 4) { 454 | auto Data = UserDump.ReadMemory(Descriptor.BaseAddress, 4); 455 | printf(" %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 "...", 456 | (*Data)[0], (*Data)[1], (*Data)[2], (*Data)[3]); 457 | } 458 | 459 | // 460 | // Phew! We are done for this region :) 461 | // 462 | 463 | printf("\n"); 464 | } 465 | } 466 | 467 | // 468 | // Show the threads. 469 | // 470 | 471 | if (Opts.ShowThreads || Opts.ShowAll) { 472 | printf(DELIMITER "\nThreads:\n"); 473 | const auto &ForegroundTid = UserDump.GetForegroundThreadId(); 474 | for (const auto &[Tid, Thread] : UserDump.GetThreads()) { 475 | 476 | // 477 | // If we are looking for a specific TID, skip unless we have a match. 478 | // 479 | 480 | if (Opts.Tid && Tid != *Opts.Tid) { 481 | continue; 482 | } 483 | 484 | // 485 | // If we are looking for the foreground thread, skip unless we have a 486 | // match. 487 | // 488 | 489 | if (Opts.ShowForegroundThread && Tid != *ForegroundTid) { 490 | continue; 491 | } 492 | 493 | // 494 | // If we have a match or no filters, dump the context. 495 | // 496 | 497 | utils::PrintContext(Thread, 2); 498 | } 499 | } 500 | 501 | // 502 | // Dump virtual memory. 503 | // 504 | 505 | if (Opts.DumpAddress.has_value()) { 506 | printf(DELIMITER "\nMemory:\n"); 507 | 508 | // 509 | // Find a block of virtual memory that overlaps with the address we want to 510 | // dump. 511 | // 512 | 513 | const auto DumpAddress = *Opts.DumpAddress; 514 | const auto &Block = UserDump.GetMemBlock(DumpAddress); 515 | 516 | // 517 | // If we found a match, let's go. 518 | // 519 | 520 | if (Block != nullptr) { 521 | 522 | // 523 | // Display basic information about the matching memory region. 524 | // 525 | 526 | const auto BlockStart = Block->BaseAddress; 527 | const auto BlockDataSize = Block->DataSize; 528 | const auto BlockRegionSize = Block->RegionSize; 529 | const auto BlockEnd = BlockStart + BlockRegionSize; 530 | printf("%016" PRIx64 " -> %016" PRIx64 "\n", BlockStart, BlockEnd); 531 | if (BlockDataSize > 0) { 532 | 533 | // 534 | // Calculate where from we need to start dumping, and the appropriate 535 | // amount of bytes to dump. 536 | // 537 | 538 | const auto OffsetFromStart = DumpAddress - BlockStart; 539 | const auto Remaining = size_t(BlockDataSize - OffsetFromStart); 540 | const size_t MaxSize = 0x100; 541 | const size_t DumpSize = std::min(MaxSize, Remaining); 542 | auto Data = UserDump.ReadMemory(BlockStart + OffsetFromStart, DumpSize); 543 | utils::Hexdump(BlockStart + OffsetFromStart, *Data, 2); 544 | } else { 545 | printf("The dump does not have the content of the memory at %" PRIx64 546 | "\n", 547 | DumpAddress); 548 | } 549 | } else { 550 | printf("No memory block were found for %" PRIx64 ".\n", DumpAddress); 551 | } 552 | } 553 | 554 | return EXIT_SUCCESS; 555 | } -------------------------------------------------------------------------------- /src/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of udmp-parser project 3 | # 4 | # Released under MIT License, by 0vercl0k - 2023 5 | # 6 | # With contribution from: 7 | # * hugsy - (github.com/hugsy) 8 | # 9 | cmake_minimum_required(VERSION 3.21) 10 | 11 | project( 12 | udmp-parser-python 13 | DESCRIPTION "A Cross-Platform C++ parser library for Windows user minidumps." 14 | HOMEPAGE_URL https://github.com/0vercl0k/udmp-parser 15 | VERSION 0.6.0 16 | ) 17 | 18 | if(NOT PROJECT_IS_TOP_LEVEL) 19 | message(FATAL_ERROR "This project is not intended to be used as a sub-project") 20 | endif() 21 | 22 | find_package(Python 3 23 | REQUIRED COMPONENTS Interpreter Development.Module 24 | OPTIONAL_COMPONENTS Development.SABIModule 25 | ) 26 | 27 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 28 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) 29 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") 30 | endif() 31 | 32 | execute_process( 33 | COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir 34 | OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR) 35 | list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}") 36 | 37 | find_package(nanobind CONFIG REQUIRED) 38 | 39 | set(CMAKE_CXX_STANDARD 20) 40 | 41 | nanobind_add_module(udmp_parser STABLE_ABI src/udmp_parser_utils.cc src/udmp_parser.cc) 42 | 43 | if(MSVC) 44 | target_link_libraries(udmp_parser PRIVATE DbgHelp.lib) 45 | endif(MSVC) 46 | 47 | # 48 | # Those directives are only used when creating a standalone `udmp_parser` python package 49 | # 50 | target_include_directories(udmp_parser PRIVATE ../lib) 51 | install(TARGETS udmp_parser LIBRARY DESTINATION .) 52 | install(DIRECTORY udmp_parser-stubs DESTINATION .) 53 | 54 | if(WIN32) 55 | target_compile_definitions(udmp_parser PRIVATE NOMINMAX) 56 | endif() 57 | -------------------------------------------------------------------------------- /src/python/README.md: -------------------------------------------------------------------------------- 1 | # Python Bindings for `udmp-parser` 2 | 3 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Licence MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?maxAge=2592000?style=plastic)](https://github.com/0vercl0k/udmp-parser/blob/master/LICENSE) 4 | 5 | `udmp-parser` is a cross-platform C++ parser library for Windows [user minidumps](https://docs.microsoft.com/en-us/windows/win32/debug/minidump-files) written by [0vercl0k](https://github.com/0vercl0k). The Python bindings were added by [hugsy](https://github.com/hugsy). Refer to the [project page on Github](https://github.com/0vercl0k/udmp-parser) for documentation, issues and pull requests. 6 | 7 | ![parser](https://github.com/0vercl0k/udmp-parser/raw/main/pics/parser.gif) 8 | 9 | The library supports Intel 32-bit / 64-bit dumps and provides read access to things like: 10 | 11 | - The thread list and their context records, 12 | - The virtual memory, 13 | - The loaded modules. 14 | 15 | ## Installing from PyPI 16 | 17 | The easiest way is simply to: 18 | 19 | ``` 20 | pip install udmp_parser 21 | ``` 22 | 23 | ## Usage 24 | 25 | The Python API was built around the C++ code so the names were preserved. Everything lives within the module `udmp_parser`. 26 | Note: For convenience, a simple [pure Python script](src/python/tests/utils.py) was added to generate minidumps ready to use: 27 | 28 | ```python 29 | $ python -i src/python/tests/utils.py 30 | >>> pid, dmppath = generate_minidump_from_process_name("winver.exe") 31 | Minidump generated successfully: PID=3232 -> minidump-winver.exe-1687024880.dmp 32 | >>> pid 33 | 3232 34 | >>> dmppath 35 | WindowsPath('minidump-winver.exe-1687024880.dmp')) 36 | ``` 37 | 38 | Parsing a minidump object is as simple as: 39 | 40 | ```python 41 | >>> import udmp_parser 42 | >>> udmp_parser.version.major, udmp_parser.version.minor, udmp_parser.version.release 43 | (0, 4, '') 44 | >>> dmp = udmp_parser.UserDumpParser() 45 | >>> dmp.Parse(pathlib.Path("C:/temp/rundll32.dmp")) 46 | True 47 | ``` 48 | 49 | Feature-wise, here are some examples of usage: 50 | 51 | ### Threads 52 | 53 | Get a hashmap of threads (as `{TID: ThreadObject}`), access their information: 54 | 55 | ```python 56 | >>> threads = dmp.Threads() 57 | >>> len(threads) 58 | 14 59 | >>> threads 60 | {5292: Thread(Id=0x14ac, SuspendCount=0x1, Teb=0x2e8000), 61 | 5300: Thread(Id=0x14b4, SuspendCount=0x1, Teb=0x2e5000), 62 | 5316: Thread(Id=0x14c4, SuspendCount=0x1, Teb=0x2df000), 63 | 3136: Thread(Id=0xc40, SuspendCount=0x1, Teb=0x2ee000), 64 | 4204: Thread(Id=0x106c, SuspendCount=0x1, Teb=0x309000), 65 | 5328: Thread(Id=0x14d0, SuspendCount=0x1, Teb=0x2e2000), 66 | 1952: Thread(Id=0x7a0, SuspendCount=0x1, Teb=0x2f7000), 67 | 3888: Thread(Id=0xf30, SuspendCount=0x1, Teb=0x2eb000), 68 | 1760: Thread(Id=0x6e0, SuspendCount=0x1, Teb=0x2f4000), 69 | 792: Thread(Id=0x318, SuspendCount=0x1, Teb=0x300000), 70 | 1972: Thread(Id=0x7b4, SuspendCount=0x1, Teb=0x2fa000), 71 | 1228: Thread(Id=0x4cc, SuspendCount=0x1, Teb=0x2fd000), 72 | 516: Thread(Id=0x204, SuspendCount=0x1, Teb=0x303000), 73 | 2416: Thread(Id=0x970, SuspendCount=0x1, Teb=0x306000)} 74 | ``` 75 | 76 | And access invidual thread, including their register context: 77 | 78 | ```python 79 | >>> thread = threads[5292] 80 | >>> print(f"RIP={thread.Context.Rip:#x} RBP={thread.Context.Rbp:#x} RSP={thread.Context.Rsp:#x}") 81 | RIP=0x7ffc264b0ad4 RBP=0x404fecc RSP=0x7de628 82 | ``` 83 | 84 | 85 | ### Modules 86 | 87 | Get a hashmap of modules (as `{address: ModuleObject}`), access their information: 88 | 89 | ```python 90 | >>> modules = dmp.Modules() 91 | >>> modules 92 | {1572864: Module_t(BaseOfImage=0x180000, SizeOfImage=0x3000, ModuleName=C:\Windows\SysWOW64\sfc.dll), 93 | 10813440: Module_t(BaseOfImage=0xa50000, SizeOfImage=0x14000, ModuleName=C:\Windows\SysWOW64\rundll32.exe), 94 | 1929052160: Module_t(BaseOfImage=0x72fb0000, SizeOfImage=0x11000, ModuleName=C:\Windows\SysWOW64\wkscli.dll), 95 | 1929183232: Module_t(BaseOfImage=0x72fd0000, SizeOfImage=0x52000, ModuleName=C:\Windows\SysWOW64\mswsock.dll), 96 | 1929576448: Module_t(BaseOfImage=0x73030000, SizeOfImage=0xf000, ModuleName=C:\Windows\SysWOW64\browcli.dll), 97 | 1929641984: Module_t(BaseOfImage=0x73040000, SizeOfImage=0xa000, ModuleName=C:\Windows\SysWOW64\davhlpr.dll), 98 | 1929707520: Module_t(BaseOfImage=0x73050000, SizeOfImage=0x19000, ModuleName=C:\Windows\SysWOW64\davclnt.dll), 99 | 1929838592: Module_t(BaseOfImage=0x73070000, SizeOfImage=0x18000, ModuleName=C:\Windows\SysWOW64\ntlanman.dll), 100 | [...] 101 | 140720922427392: Module_t(BaseOfImage=0x7ffc24980000, SizeOfImage=0x83000, ModuleName=C:\Windows\System32\wow64win.dll), 102 | 140720923017216: Module_t(BaseOfImage=0x7ffc24a10000, SizeOfImage=0x59000, ModuleName=C:\Windows\System32\wow64.dll), 103 | 140720950280192: Module_t(BaseOfImage=0x7ffc26410000, SizeOfImage=0x1f8000, ModuleName=C:\Windows\System32\ntdll.dll)} 104 | ``` 105 | 106 | Access directly module info: 107 | 108 | ```python 109 | >>> ntdll_modules = [mod for addr, mod in dmp.Modules().items() if mod.ModuleName.lower().endswith("ntdll.dll")] 110 | >>> len(ntdll_modules) 111 | 2 112 | >>> for ntdll in ntdll_modules: 113 | print(f"{ntdll.ModuleName=} {ntdll.BaseOfImage=:#x} {ntdll.SizeOfImage=:#x}") 114 | 115 | ntdll.ModuleName='C:\\Windows\\SysWOW64\\ntdll.dll' ntdll.BaseOfImage=0x77430000 ntdll.SizeOfImage=0x1a4000 116 | ntdll.ModuleName='C:\\Windows\\System32\\ntdll.dll' ntdll.BaseOfImage=0x7ffc26410000 ntdll.SizeOfImage=0x1f8000 117 | ``` 118 | 119 | A convenience function under `udmp_parser.UserDumpParser.ReadMemory()` can be used to directly read memory from the dump. The signature of the function is as follow: `def ReadMemory(Address: int, Size: int) -> list[int]`. So to dump for instance the `wow64` module, it would go as follow: 120 | 121 | ```python 122 | >>> wow64 = [mod for addr, mod in dmp.Modules().items() if mod.ModuleName.lower() == r"c:\windows\system32\wow64.dll"][0] 123 | >>> print(str(wow64)) 124 | Module_t(BaseOfImage=0x7ffc24a10000, SizeOfImage=0x59000, ModuleName=C:\Windows\System32\wow64.dll) 125 | >>> wow64_module = bytearray(dmp.ReadMemory(wow64.BaseOfImage, wow64.SizeOfImage)) 126 | >>> assert wow64_module[:2] == b'MZ' 127 | >>> import hexdump 128 | >>> hexdump.hexdump(wow64_module[:128]) 129 | 00000000: 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ.............. 130 | 00000010: B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@....... 131 | 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 132 | 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 E8 00 00 00 ................ 133 | 00000040: 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ........!..L.!Th 134 | 00000050: 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno 135 | 00000060: 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS 136 | 00000070: 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 mode....$....... 137 | ``` 138 | 139 | 140 | ### Memory 141 | 142 | The memory blocks can also be enumerated in a hashmap `{address: MemoryBlock}`. 143 | 144 | ```python 145 | >>> memory = dmp.Memory() 146 | >>> len(memory) 147 | 0x260 148 | >>> memory 149 | [...] 150 | 0x7ffc26410000: [MemBlock_t(BaseAddress=0x7ffc26410000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x1000)], 151 | 0x7ffc26411000: [MemBlock_t(BaseAddress=0x7ffc26411000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x11c000)], 152 | 0x7ffc2652d000: [MemBlock_t(BaseAddress=0x7ffc2652d000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x49000)], 153 | 0x7ffc26576000: [MemBlock_t(BaseAddress=0x7ffc26576000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x1000)], 154 | 0x7ffc26577000: [MemBlock_t(BaseAddress=0x7ffc26577000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x2000)], 155 | 0x7ffc26579000: [MemBlock_t(BaseAddress=0x7ffc26579000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x9000)], 156 | 0x7ffc26582000: [MemBlock_t(BaseAddress=0x7ffc26582000, AllocationBase=0x7ffc26410000, AllocationProtect=0x80, RegionSize=0x86000)], 157 | 0x7ffc26608000: [MemBlock_t(BaseAddress=0x7ffc26608000, AllocationBase=0x0, AllocationProtect=0x0, RegionSize=0x3d99e8000)]} 158 | ``` 159 | 160 | To facilitate the parsing in a human-friendly manner, some helper functions are provided: 161 | * `udmp_parser.utils.TypeToString`: convert the region type to its meaning (from MSDN) 162 | * `udmp_parser.utils.StateToString`: convert the region state to its meaning (from MSDN) 163 | * `udmp_parser.utils.ProtectionToString`: convert the region protection to its meaning (from MSDN) 164 | 165 | This allows to search and filter in a more comprehensible way: 166 | 167 | 168 | ```python 169 | # Collect only executable memory regions 170 | >>> exec_regions = [region for _, region in dmp.Memory().items() if "PAGE_EXECUTE_READ" in udmp_parser.utils.ProtectionToString(region.Protect)] 171 | 172 | # Pick any, disassemble code using capstone 173 | >>> exec_region = exec_regions[-1] 174 | >>> mem = dmp.ReadMemory(exec_region.BaseAddress, 0x100) 175 | >>> for insn in cs.disasm(bytearray(mem), exec_region.BaseAddress): 176 | print(f"{insn=}") 177 | 178 | insn= 179 | insn= 180 | insn= 181 | insn= 182 | insn= 183 | insn= 184 | insn= 185 | insn= 186 | insn= 187 | insn= 188 | insn= 189 | insn= 190 | insn= 191 | insn= 192 | insn= 193 | insn= 194 | insn= 195 | insn= 196 | insn= 197 | insn= 198 | insn= 199 | [...] 200 | ``` 201 | 202 | # Authors 203 | 204 | * Axel '[@0vercl0k](https://twitter.com/0vercl0k)' Souchet 205 | 206 | # Contributors 207 | 208 | [ ![contributors-img](https://contrib.rocks/image?repo=0vercl0k/udmp-parser) ](https://github.com/0vercl0k/udmp-parser/graphs/contributors) 209 | -------------------------------------------------------------------------------- /src/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build-core >=0.4.3", "nanobind >=1.3.2"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "udmp-parser" 7 | version = "0.7.0" 8 | description = "A Cross-Platform C++ parser library for Windows user minidumps." 9 | readme = "README.md" 10 | requires-python = ">=3.8" 11 | authors = [{ name = "0vercl0k", email = "0vercl0k@not-your-biz.net" }] 12 | classifiers = [ 13 | "Development Status :: 4 - Beta", 14 | "License :: OSI Approved :: MIT License", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.8", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "Programming Language :: Python :: 3.12", 21 | "Topic :: Software Development :: Assemblers", 22 | "Natural Language :: English", 23 | ] 24 | dependencies = [] 25 | 26 | [project.urls] 27 | Homepage = "https://github.com/0vercl0k/udmp-parser" 28 | 29 | [project.scripts] 30 | generate_minidump = "udmp_parser.utils:generate_minidump_from_command_line" 31 | 32 | [tool.isort] 33 | profile = "black" 34 | 35 | [tool.scikit-build] 36 | wheel.py-api = "cp313" 37 | minimum-version = "0.4" 38 | build-dir = "build/{wheel_tag}" 39 | cmake.minimum-version = "3.21" 40 | 41 | [tool.cibuildwheel] 42 | build-verbosity = 1 43 | # udmp_parser builds fine for this target, but not `lief`. 44 | skip = "cp314t-*" 45 | before-test = "pip install -U -r {project}/src/python/tests/requirements.txt" 46 | test-command = "pytest -vvv {project}/src/python/tests" 47 | 48 | [tool.cibuildwheel.macos.environment] 49 | MACOSX_DEPLOYMENT_TARGET = "10.15" 50 | -------------------------------------------------------------------------------- /src/python/requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools 2 | wheel 3 | nanobind 4 | black 5 | -------------------------------------------------------------------------------- /src/python/src/udmp_parser.cc: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of udmp-parser project 3 | // 4 | // Released under MIT License, by 0vercl0k - 2023 5 | // 6 | // With contribution from: 7 | // * hugsy - (github.com/hugsy) 8 | // 9 | 10 | #include "udmp-parser.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace nb = nanobind; 25 | 26 | void udmp_parser_utils_module(nb::module_ &m); 27 | 28 | NB_MODULE(udmp_parser, m) { 29 | 30 | udmp_parser_utils_module(m); 31 | 32 | nb::enum_(m, "ProcessorArch") 33 | .value("X86", udmpparser::ProcessorArch_t::X86) 34 | .value("ARM", udmpparser::ProcessorArch_t::ARM) 35 | .value("IA64", udmpparser::ProcessorArch_t::IA64) 36 | .value("AMD64", udmpparser::ProcessorArch_t::AMD64) 37 | .value("Unknown", udmpparser::ProcessorArch_t::Unknown) 38 | .export_values(); 39 | 40 | nb::class_(m, "FloatingSaveArea32") 41 | .def_ro("ControlWord", &udmpparser::FloatingSaveArea32_t::ControlWord) 42 | .def_ro("StatusWord", &udmpparser::FloatingSaveArea32_t::StatusWord) 43 | .def_ro("TagWord", &udmpparser::FloatingSaveArea32_t::TagWord) 44 | .def_ro("ErrorOffset", &udmpparser::FloatingSaveArea32_t::ErrorOffset) 45 | .def_ro("ErrorSelector", &udmpparser::FloatingSaveArea32_t::ErrorSelector) 46 | .def_ro("DataOffset", &udmpparser::FloatingSaveArea32_t::DataOffset) 47 | .def_ro("DataSelector", &udmpparser::FloatingSaveArea32_t::DataSelector) 48 | .def_ro("RegisterArea", &udmpparser::FloatingSaveArea32_t::RegisterArea) 49 | .def_ro("Cr0NpxState", &udmpparser::FloatingSaveArea32_t::Cr0NpxState); 50 | 51 | nb::class_(m, "Context32") 52 | .def_ro("ContextFlags", &udmpparser::Context32_t::ContextFlags) 53 | .def_ro("Dr0", &udmpparser::Context32_t::Dr0) 54 | .def_ro("Dr1", &udmpparser::Context32_t::Dr1) 55 | .def_ro("Dr2", &udmpparser::Context32_t::Dr2) 56 | .def_ro("Dr3", &udmpparser::Context32_t::Dr3) 57 | .def_ro("Dr6", &udmpparser::Context32_t::Dr6) 58 | .def_ro("Dr7", &udmpparser::Context32_t::Dr7) 59 | .def_ro("FloatSave", &udmpparser::Context32_t::FloatSave) 60 | .def_ro("SegGs", &udmpparser::Context32_t::SegGs) 61 | .def_ro("SegFs", &udmpparser::Context32_t::SegFs) 62 | .def_ro("SegEs", &udmpparser::Context32_t::SegEs) 63 | .def_ro("SegDs", &udmpparser::Context32_t::SegDs) 64 | .def_ro("Edi", &udmpparser::Context32_t::Edi) 65 | .def_ro("Esi", &udmpparser::Context32_t::Esi) 66 | .def_ro("Ebx", &udmpparser::Context32_t::Ebx) 67 | .def_ro("Edx", &udmpparser::Context32_t::Edx) 68 | .def_ro("Ecx", &udmpparser::Context32_t::Ecx) 69 | .def_ro("Eax", &udmpparser::Context32_t::Eax) 70 | .def_ro("Ebp", &udmpparser::Context32_t::Ebp) 71 | .def_ro("Eip", &udmpparser::Context32_t::Eip) 72 | .def_ro("SegCs", &udmpparser::Context32_t::SegCs) 73 | .def_ro("EFlags", &udmpparser::Context32_t::EFlags) 74 | .def_ro("Esp", &udmpparser::Context32_t::Esp) 75 | .def_ro("SegSs", &udmpparser::Context32_t::SegSs) 76 | .def_ro("ExtendedRegisters", &udmpparser::Context32_t::ExtendedRegisters); 77 | 78 | nb::class_(m, "uint128") 79 | .def_ro("Low", &udmpparser::uint128_t::Low) 80 | .def_ro("High", &udmpparser::uint128_t::High); 81 | 82 | nb::class_(m, "Context64") 83 | .def_ro("P1Home", &udmpparser::Context64_t::P1Home) 84 | .def_ro("P2Home", &udmpparser::Context64_t::P2Home) 85 | .def_ro("P3Home", &udmpparser::Context64_t::P3Home) 86 | .def_ro("P4Home", &udmpparser::Context64_t::P4Home) 87 | .def_ro("P5Home", &udmpparser::Context64_t::P5Home) 88 | .def_ro("P6Home", &udmpparser::Context64_t::P6Home) 89 | .def_ro("ContextFlags", &udmpparser::Context64_t::ContextFlags) 90 | .def_ro("MxCsr", &udmpparser::Context64_t::MxCsr) 91 | .def_ro("SegCs", &udmpparser::Context64_t::SegCs) 92 | .def_ro("SegDs", &udmpparser::Context64_t::SegDs) 93 | .def_ro("SegEs", &udmpparser::Context64_t::SegEs) 94 | .def_ro("SegFs", &udmpparser::Context64_t::SegFs) 95 | .def_ro("SegGs", &udmpparser::Context64_t::SegGs) 96 | .def_ro("SegSs", &udmpparser::Context64_t::SegSs) 97 | .def_ro("EFlags", &udmpparser::Context64_t::EFlags) 98 | .def_ro("Dr0", &udmpparser::Context64_t::Dr0) 99 | .def_ro("Dr1", &udmpparser::Context64_t::Dr1) 100 | .def_ro("Dr2", &udmpparser::Context64_t::Dr2) 101 | .def_ro("Dr3", &udmpparser::Context64_t::Dr3) 102 | .def_ro("Dr6", &udmpparser::Context64_t::Dr6) 103 | .def_ro("Dr7", &udmpparser::Context64_t::Dr7) 104 | .def_ro("Rax", &udmpparser::Context64_t::Rax) 105 | .def_ro("Rcx", &udmpparser::Context64_t::Rcx) 106 | .def_ro("Rdx", &udmpparser::Context64_t::Rdx) 107 | .def_ro("Rbx", &udmpparser::Context64_t::Rbx) 108 | .def_ro("Rsp", &udmpparser::Context64_t::Rsp) 109 | .def_ro("Rbp", &udmpparser::Context64_t::Rbp) 110 | .def_ro("Rsi", &udmpparser::Context64_t::Rsi) 111 | .def_ro("Rdi", &udmpparser::Context64_t::Rdi) 112 | .def_ro("R8", &udmpparser::Context64_t::R8) 113 | .def_ro("R9", &udmpparser::Context64_t::R9) 114 | .def_ro("R10", &udmpparser::Context64_t::R10) 115 | .def_ro("R11", &udmpparser::Context64_t::R11) 116 | .def_ro("R12", &udmpparser::Context64_t::R12) 117 | .def_ro("R13", &udmpparser::Context64_t::R13) 118 | .def_ro("R14", &udmpparser::Context64_t::R14) 119 | .def_ro("R15", &udmpparser::Context64_t::R15) 120 | .def_ro("Rip", &udmpparser::Context64_t::Rip) 121 | .def_ro("ControlWord", &udmpparser::Context64_t::ControlWord) 122 | .def_ro("StatusWord", &udmpparser::Context64_t::StatusWord) 123 | .def_ro("TagWord", &udmpparser::Context64_t::TagWord) 124 | .def_ro("Reserved1", &udmpparser::Context64_t::Reserved1) 125 | .def_ro("ErrorOpcode", &udmpparser::Context64_t::ErrorOpcode) 126 | .def_ro("ErrorOffset", &udmpparser::Context64_t::ErrorOffset) 127 | .def_ro("ErrorSelector", &udmpparser::Context64_t::ErrorSelector) 128 | .def_ro("Reserved2", &udmpparser::Context64_t::Reserved2) 129 | .def_ro("DataOffset", &udmpparser::Context64_t::DataOffset) 130 | .def_ro("DataSelector", &udmpparser::Context64_t::DataSelector) 131 | .def_ro("Reserved3", &udmpparser::Context64_t::Reserved3) 132 | .def_ro("MxCsr2", &udmpparser::Context64_t::MxCsr2) 133 | .def_ro("MxCsr_Mask", &udmpparser::Context64_t::MxCsr_Mask) 134 | .def_ro("FloatRegisters", &udmpparser::Context64_t::FloatRegisters) 135 | .def_ro("Xmm0", &udmpparser::Context64_t::Xmm0) 136 | .def_ro("Xmm1", &udmpparser::Context64_t::Xmm1) 137 | .def_ro("Xmm2", &udmpparser::Context64_t::Xmm2) 138 | .def_ro("Xmm3", &udmpparser::Context64_t::Xmm3) 139 | .def_ro("Xmm4", &udmpparser::Context64_t::Xmm4) 140 | .def_ro("Xmm5", &udmpparser::Context64_t::Xmm5) 141 | .def_ro("Xmm6", &udmpparser::Context64_t::Xmm6) 142 | .def_ro("Xmm7", &udmpparser::Context64_t::Xmm7) 143 | .def_ro("Xmm8", &udmpparser::Context64_t::Xmm8) 144 | .def_ro("Xmm9", &udmpparser::Context64_t::Xmm9) 145 | .def_ro("Xmm10", &udmpparser::Context64_t::Xmm10) 146 | .def_ro("Xmm11", &udmpparser::Context64_t::Xmm11) 147 | .def_ro("Xmm12", &udmpparser::Context64_t::Xmm12) 148 | .def_ro("Xmm13", &udmpparser::Context64_t::Xmm13) 149 | .def_ro("Xmm14", &udmpparser::Context64_t::Xmm14) 150 | .def_ro("Xmm15", &udmpparser::Context64_t::Xmm15) 151 | .def_ro("VectorRegister", &udmpparser::Context64_t::VectorRegister) 152 | .def_ro("VectorControl", &udmpparser::Context64_t::VectorControl) 153 | .def_ro("DebugControl", &udmpparser::Context64_t::DebugControl) 154 | .def_ro("LastBranchToRip", &udmpparser::Context64_t::LastBranchToRip) 155 | .def_ro("LastBranchFromRip", &udmpparser::Context64_t::LastBranchFromRip) 156 | .def_ro("LastExceptionToRip", 157 | &udmpparser::Context64_t::LastExceptionToRip) 158 | .def_ro("LastExceptionFromRip", 159 | &udmpparser::Context64_t::LastExceptionFromRip); 160 | 161 | nb::class_(m, "Header") 162 | .def_ro_static("ExpectedSignature", 163 | &udmpparser::dmp::Header_t::ExpectedSignature) 164 | .def_ro_static("ValidFlagsMask", 165 | &udmpparser::dmp::Header_t::ValidFlagsMask) 166 | .def_ro("Signature", &udmpparser::dmp::Header_t::Signature) 167 | .def_ro("Version", &udmpparser::dmp::Header_t::Version) 168 | .def_ro("ImplementationVersion", 169 | &udmpparser::dmp::Header_t::ImplementationVersion) 170 | .def_ro("NumberOfStreams", &udmpparser::dmp::Header_t::NumberOfStreams) 171 | .def_ro("StreamDirectoryRva", 172 | &udmpparser::dmp::Header_t::StreamDirectoryRva) 173 | .def_ro("CheckSum", &udmpparser::dmp::Header_t::CheckSum) 174 | .def_ro("Reserved", &udmpparser::dmp::Header_t::Reserved) 175 | .def_ro("TimeDateStamp", &udmpparser::dmp::Header_t::TimeDateStamp) 176 | .def_ro("Flags", &udmpparser::dmp::Header_t::Flags) 177 | .def("LooksGood", &udmpparser::dmp::Header_t::LooksGood); 178 | 179 | nb::class_(m, "LocationDescriptor32") 180 | .def_ro("DataSize", &udmpparser::dmp::LocationDescriptor32_t::DataSize) 181 | .def_ro("Rva", &udmpparser::dmp::LocationDescriptor32_t::Rva); 182 | 183 | nb::class_(m, "LocationDescriptor64") 184 | .def_ro("DataSize", &udmpparser::dmp::LocationDescriptor64_t::DataSize) 185 | .def_ro("Rva", &udmpparser::dmp::LocationDescriptor64_t::Rva); 186 | 187 | nb::enum_(m, "StreamType") 188 | .value("Unused", udmpparser::dmp::StreamType_t::Unused) 189 | .value("ThreadList", udmpparser::dmp::StreamType_t::ThreadList) 190 | .value("ModuleList", udmpparser::dmp::StreamType_t::ModuleList) 191 | .value("Exception", udmpparser::dmp::StreamType_t::Exception) 192 | .value("SystemInfo", udmpparser::dmp::StreamType_t::SystemInfo) 193 | .value("Memory64List", udmpparser::dmp::StreamType_t::Memory64List) 194 | .value("MemoryInfoList", udmpparser::dmp::StreamType_t::MemoryInfoList) 195 | .export_values(); 196 | 197 | nb::class_(m, "Directory") 198 | .def_ro("StreamType", &udmpparser::dmp::Directory_t::StreamType) 199 | .def_ro("Location", &udmpparser::dmp::Directory_t::Location); 200 | 201 | nb::class_(m, "MemoryInfoListStream") 202 | .def_ro("SizeOfHeader", 203 | &udmpparser::dmp::MemoryInfoListStream_t::SizeOfHeader) 204 | .def_ro("SizeOfEntry", 205 | &udmpparser::dmp::MemoryInfoListStream_t::SizeOfEntry) 206 | .def_ro("NumberOfEntries", 207 | &udmpparser::dmp::MemoryInfoListStream_t::NumberOfEntries); 208 | 209 | nb::class_(m, 210 | "Memory64ListStreamHdr") 211 | .def_ro("NumberOfMemoryRanges", 212 | &udmpparser::dmp::Memory64ListStreamHdr_t::NumberOfMemoryRanges) 213 | .def_ro("BaseRva", &udmpparser::dmp::Memory64ListStreamHdr_t::BaseRva); 214 | 215 | nb::class_(m, "MemoryDescriptor64") 216 | .def_ro("StartOfMemoryRange", 217 | &udmpparser::dmp::MemoryDescriptor64_t::StartOfMemoryRange) 218 | .def_ro("DataSize", &udmpparser::dmp::MemoryDescriptor64_t::DataSize); 219 | 220 | nb::class_(m, "FixedFileInfo") 221 | .def_ro("Signature", &udmpparser::dmp::FixedFileInfo_t::Signature) 222 | .def_ro("StrucVersion", &udmpparser::dmp::FixedFileInfo_t::StrucVersion) 223 | .def_ro("FileVersionMS", &udmpparser::dmp::FixedFileInfo_t::FileVersionMS) 224 | .def_ro("FileVersionLS", &udmpparser::dmp::FixedFileInfo_t::FileVersionLS) 225 | .def_ro("ProductVersionMS", 226 | &udmpparser::dmp::FixedFileInfo_t::ProductVersionMS) 227 | .def_ro("ProductVersionLS", 228 | &udmpparser::dmp::FixedFileInfo_t::ProductVersionLS) 229 | .def_ro("FileFlagsMask", &udmpparser::dmp::FixedFileInfo_t::FileFlagsMask) 230 | .def_ro("FileFlags", &udmpparser::dmp::FixedFileInfo_t::FileFlags) 231 | .def_ro("FileOS", &udmpparser::dmp::FixedFileInfo_t::FileOS) 232 | .def_ro("FileType", &udmpparser::dmp::FixedFileInfo_t::FileType) 233 | .def_ro("FileSubtype", &udmpparser::dmp::FixedFileInfo_t::FileSubtype) 234 | .def_ro("FileDateMS", &udmpparser::dmp::FixedFileInfo_t::FileDateMS) 235 | .def_ro("FileDateLS", &udmpparser::dmp::FixedFileInfo_t::FileDateLS); 236 | 237 | nb::class_(m, "MemoryInfo") 238 | .def_ro("BaseAddress", &udmpparser::dmp::MemoryInfo_t::BaseAddress) 239 | .def_ro("AllocationBase", &udmpparser::dmp::MemoryInfo_t::AllocationBase) 240 | .def_ro("AllocationProtect", 241 | &udmpparser::dmp::MemoryInfo_t::AllocationProtect) 242 | .def_ro("RegionSize", &udmpparser::dmp::MemoryInfo_t::RegionSize) 243 | .def_ro("State", &udmpparser::dmp::MemoryInfo_t::State) 244 | .def_ro("Protect", &udmpparser::dmp::MemoryInfo_t::Protect) 245 | .def_ro("Type", &udmpparser::dmp::MemoryInfo_t::Type); 246 | 247 | nb::class_(m, "MemoryDescriptor") 248 | .def_ro("StartOfMemoryRange", 249 | &udmpparser::dmp::MemoryDescriptor_t::StartOfMemoryRange) 250 | .def_ro("Memory", &udmpparser::dmp::MemoryDescriptor_t::Memory); 251 | 252 | nb::class_(m, "ThreadEntry") 253 | .def_ro("ThreadId", &udmpparser::dmp::ThreadEntry_t::ThreadId) 254 | .def_ro("SuspendCount", &udmpparser::dmp::ThreadEntry_t::SuspendCount) 255 | .def_ro("PriorityClass", &udmpparser::dmp::ThreadEntry_t::PriorityClass) 256 | .def_ro("Priority", &udmpparser::dmp::ThreadEntry_t::Priority) 257 | .def_ro("Teb", &udmpparser::dmp::ThreadEntry_t::Teb) 258 | .def_ro("Stack", &udmpparser::dmp::ThreadEntry_t::Stack) 259 | .def_ro("ThreadContext", &udmpparser::dmp::ThreadEntry_t::ThreadContext); 260 | 261 | nb::class_(m, "SystemInfoStream") 262 | .def_ro("ProcessorArchitecture", 263 | &udmpparser::dmp::SystemInfoStream_t::ProcessorArchitecture) 264 | .def_ro("ProcessorLevel ", 265 | &udmpparser::dmp::SystemInfoStream_t::ProcessorLevel) 266 | .def_ro("ProcessorRevision ", 267 | &udmpparser::dmp::SystemInfoStream_t::ProcessorRevision) 268 | .def_ro("NumberOfProcessors ", 269 | &udmpparser::dmp::SystemInfoStream_t::NumberOfProcessors) 270 | .def_ro("ProductType ", &udmpparser::dmp::SystemInfoStream_t::ProductType) 271 | .def_ro("MajorVersion ", 272 | &udmpparser::dmp::SystemInfoStream_t::MajorVersion) 273 | .def_ro("MinorVersion ", 274 | &udmpparser::dmp::SystemInfoStream_t::MinorVersion) 275 | .def_ro("BuildNumber ", &udmpparser::dmp::SystemInfoStream_t::BuildNumber) 276 | .def_ro("PlatformId ", &udmpparser::dmp::SystemInfoStream_t::PlatformId) 277 | .def_ro("CSDVersionRva ", 278 | &udmpparser::dmp::SystemInfoStream_t::CSDVersionRva) 279 | .def_ro("SuiteMask ", &udmpparser::dmp::SystemInfoStream_t::SuiteMask) 280 | .def_ro("Reserved2 ", &udmpparser::dmp::SystemInfoStream_t::Reserved2); 281 | 282 | nb::class_(m, "ExceptionRecord") 283 | .def_ro("ExceptionCode", 284 | &udmpparser::dmp::ExceptionRecord_t::ExceptionCode) 285 | .def_ro("ExceptionFlags", 286 | &udmpparser::dmp::ExceptionRecord_t::ExceptionFlags) 287 | .def_ro("ExceptionRecord", 288 | &udmpparser::dmp::ExceptionRecord_t::ExceptionRecord) 289 | .def_ro("ExceptionAddress", 290 | &udmpparser::dmp::ExceptionRecord_t::ExceptionAddress) 291 | .def_ro("NumberParameters", 292 | &udmpparser::dmp::ExceptionRecord_t::NumberParameters) 293 | .def_ro("ExceptionInformation", 294 | &udmpparser::dmp::ExceptionRecord_t::ExceptionInformation); 295 | 296 | nb::class_(m, "ExceptionStream") 297 | .def(nb::init<>()) 298 | .def_ro("ThreadId", &udmpparser::dmp::ExceptionStream_t::ThreadId) 299 | .def_ro("ExceptionRecord", 300 | &udmpparser::dmp::ExceptionStream_t::ExceptionRecord) 301 | .def_ro("ThreadContext", 302 | &udmpparser::dmp::ExceptionStream_t::ThreadContext); 303 | 304 | nb::class_(m, "FileMapReader") 305 | .def(nb::init<>()) 306 | .def("ViewSize", &udmpparser::FileMapReader_t::ViewSize) 307 | .def("MapFile", &udmpparser::FileMapReader_t::MapFile); 308 | 309 | nb::enum_(m, "Arch") 310 | .value("X86", udmpparser::Arch_t::X86) 311 | .value("X64", udmpparser::Arch_t::X64) 312 | .export_values(); 313 | 314 | nb::class_(m, "MemBlock") 315 | .def(nb::init()) 316 | .def_ro("BaseAddress", &udmpparser::MemBlock_t::BaseAddress) 317 | .def_ro("AllocationBase", &udmpparser::MemBlock_t::AllocationBase) 318 | .def_ro("AllocationProtect", &udmpparser::MemBlock_t::AllocationProtect) 319 | .def_ro("RegionSize", &udmpparser::MemBlock_t::RegionSize) 320 | .def_ro("State", &udmpparser::MemBlock_t::State) 321 | .def_ro("Protect", &udmpparser::MemBlock_t::Protect) 322 | .def_ro("Type", &udmpparser::MemBlock_t::Type) 323 | .def_ro("DataOffset", &udmpparser::MemBlock_t::DataOffset) 324 | .def_ro("DataSize", &udmpparser::MemBlock_t::DataSize) 325 | .def("__repr__", &udmpparser::MemBlock_t::to_string); 326 | 327 | nb::class_(m, "Module") 328 | .def(nb::init, std::vector>(), 330 | nb::rv_policy::take_ownership) 331 | .def_ro("BaseOfImage", &udmpparser::Module_t::BaseOfImage) 332 | .def_ro("SizeOfImage", &udmpparser::Module_t::SizeOfImage) 333 | .def_ro("CheckSum", &udmpparser::Module_t::CheckSum) 334 | .def_ro("TimeDateStamp", &udmpparser::Module_t::TimeDateStamp) 335 | .def_ro("ModuleName", &udmpparser::Module_t::ModuleName) 336 | .def_ro("VersionInfo", &udmpparser::Module_t::VersionInfo) 337 | .def_ro("CvRecord", &udmpparser::Module_t::CvRecord) 338 | .def_ro("MiscRecord", &udmpparser::Module_t::MiscRecord) 339 | .def("__repr__", &udmpparser::Module_t::to_string); 340 | 341 | nb::class_(m, "UnknownContext"); 342 | 343 | nb::class_(m, "Thread") 344 | .def(nb::init()) 346 | .def(nb::init()) 348 | .def(nb::init()) 350 | .def_ro("ThreadId", &udmpparser::Thread_t::ThreadId) 351 | .def_ro("SuspendCount", &udmpparser::Thread_t::SuspendCount) 352 | .def_ro("PriorityClass", &udmpparser::Thread_t::PriorityClass) 353 | .def_ro("Priority", &udmpparser::Thread_t::Priority) 354 | .def_ro("Teb", &udmpparser::Thread_t::Teb) 355 | .def_ro("Context", &udmpparser::Thread_t::Context) 356 | .def("__repr__", &udmpparser::Thread_t::to_string); 357 | 358 | nb::class_(m, "UserDumpParser") 359 | .def(nb::init<>()) 360 | .def("Parse", 361 | nb::overload_cast( 362 | &udmpparser::UserDumpParser::Parse), 363 | "Parse the minidump given in argument.") 364 | .def("Modules", &udmpparser::UserDumpParser::GetModules, 365 | nb::rv_policy::reference, "Get the minidump modules") 366 | .def("Memory", &udmpparser::UserDumpParser::GetMem, 367 | nb::rv_policy::reference) 368 | .def("Threads", &udmpparser::UserDumpParser::GetThreads, 369 | "Get the minidump threads") 370 | .def("ForegroundThreadId", 371 | &udmpparser::UserDumpParser::GetForegroundThreadId) 372 | .def("GetMemoryBlock", 373 | nb::overload_cast( 374 | &udmpparser::UserDumpParser::GetMemBlock, nb::const_), 375 | "Access a specific MemoryBlock") 376 | .def("ReadMemory", &udmpparser::UserDumpParser::ReadMemory, 377 | "Read bytes from memory") 378 | .def("__repr__", &udmpparser::UserDumpParser::to_string); 379 | 380 | nb::class_(m, "version") 381 | .def_ro_static("major", &udmpparser::Version::Major) 382 | .def_ro_static("minor", &udmpparser::Version::Minor) 383 | .def_ro_static("release", &udmpparser::Version::Release); 384 | } 385 | -------------------------------------------------------------------------------- /src/python/src/udmp_parser_utils.cc: -------------------------------------------------------------------------------- 1 | // 2 | // This file is part of udmp-parser project 3 | // 4 | // Released under MIT License, by 0vercl0k - 2023 5 | // 6 | // With contribution from: 7 | // * hugsy - (github.com/hugsy) 8 | // 9 | 10 | #include "udmp-parser.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace nb = nanobind; 19 | 20 | #ifdef _WIN32 21 | #include 22 | #include 23 | 24 | bool GenerateMinidumpFromProcessId( 25 | const uint32_t TargetPid, const std::filesystem::path &MiniDumpFilePath) { 26 | const HANDLE File = 27 | CreateFileA(MiniDumpFilePath.string().c_str(), GENERIC_WRITE, 0, nullptr, 28 | CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); 29 | if (File == INVALID_HANDLE_VALUE) { 30 | return false; 31 | } 32 | 33 | const HANDLE Process = OpenProcess(PROCESS_ALL_ACCESS, false, TargetPid); 34 | if (Process == INVALID_HANDLE_VALUE) { 35 | CloseHandle(File); 36 | return false; 37 | } 38 | 39 | MINIDUMP_EXCEPTION_INFORMATION ExceptionInfo = {}; 40 | const auto Flags = MINIDUMP_TYPE::MiniDumpWithFullMemory | 41 | MINIDUMP_TYPE::MiniDumpWithDataSegs | 42 | MINIDUMP_TYPE::MiniDumpScanMemory | 43 | MINIDUMP_TYPE::MiniDumpWithHandleData | 44 | MINIDUMP_TYPE::MiniDumpWithFullMemoryInfo; 45 | 46 | const auto Success = 47 | MiniDumpWriteDump(Process, TargetPid, File, MINIDUMP_TYPE(Flags), 48 | &ExceptionInfo, nullptr, nullptr); 49 | 50 | CloseHandle(Process); 51 | CloseHandle(File); 52 | return Success; 53 | } 54 | #endif 55 | 56 | void udmp_parser_utils_module(nb::module_ &m) { 57 | auto utils = m.def_submodule("utils", "Helper functions"); 58 | 59 | utils.def( 60 | "TypeToString", 61 | [](const uint32_t Type) -> std::string { 62 | switch (Type) { 63 | case 0x2'00'00: { 64 | return "MEM_PRIVATE"; 65 | } 66 | case 0x4'00'00: { 67 | return "MEM_MAPPED"; 68 | } 69 | case 0x1'00'00'00: { 70 | return "MEM_IMAGE"; 71 | } 72 | } 73 | return ""; 74 | }, 75 | "Get a string representation of the memory type"); 76 | 77 | utils.def( 78 | "StateToString", 79 | [](const uint32_t State) { 80 | switch (State) { 81 | case 0x10'00: { 82 | return "MEM_COMMIT"; 83 | } 84 | 85 | case 0x20'00: { 86 | return "MEM_RESERVE"; 87 | } 88 | 89 | case 0x1'00'00: { 90 | return "MEM_FREE"; 91 | } 92 | } 93 | return ""; 94 | }, 95 | "Get a string representation of the memory state"); 96 | 97 | utils.def( 98 | "ProtectionToString", 99 | [](const uint32_t Protection) { 100 | struct { 101 | const char *Name = nullptr; 102 | uint32_t Mask = 0; 103 | } Flags[] = { 104 | {"PAGE_NOACCESS", 0x01}, 105 | {"PAGE_READONLY", 0x02}, 106 | {"PAGE_READWRITE", 0x04}, 107 | {"PAGE_WRITECOPY", 0x08}, 108 | {"PAGE_EXECUTE", 0x10}, 109 | {"PAGE_EXECUTE_READ", 0x20}, 110 | {"PAGE_EXECUTE_READWRITE", 0x40}, 111 | {"PAGE_EXECUTE_WRITECOPY", 0x80}, 112 | {"PAGE_GUARD", 0x100}, 113 | {"PAGE_NOCACHE", 0x200}, 114 | {"PAGE_WRITECOMBINE", 0x400}, 115 | {"PAGE_TARGETS_INVALID", 0x4000'0000}, 116 | }; 117 | std::stringstream ss; 118 | uint32_t KnownFlags = 0; 119 | 120 | for (const auto &Flag : Flags) { 121 | if ((Protection & Flag.Mask) == 0) { 122 | continue; 123 | } 124 | 125 | ss << Flag.Name << ","; 126 | KnownFlags |= Flag.Mask; 127 | } 128 | 129 | const uint32_t MissingFlags = (~KnownFlags) & Protection; 130 | if (MissingFlags) { 131 | ss << std::hex << "0x" << MissingFlags; 132 | } 133 | 134 | std::string ProtectionString = ss.str(); 135 | if (ProtectionString.size() > 1 && 136 | ProtectionString[ProtectionString.size() - 1] == ',') { 137 | ProtectionString = 138 | ProtectionString.substr(0, ProtectionString.size() - 1); 139 | } 140 | 141 | return ProtectionString; 142 | }, 143 | "Get a string representation of the memory protection"); 144 | 145 | #if defined(_WIN32) 146 | utils.def("generate_minidump", GenerateMinidumpFromProcessId, "TargetPid", 147 | "MiniDumpFilePath", 148 | "Generate a minidump for TargetPid and save it to the given path. " 149 | "Returns true on success."); 150 | 151 | utils.def( 152 | "generate_minidump_from_command_line", 153 | []() -> bool { 154 | nb::module_ sys = nb::module_::import_("sys"); 155 | nb::list argv = sys.attr("argv"); 156 | if (!argv.is_valid()) { 157 | return false; 158 | } 159 | 160 | if (argv.size() != 3) { 161 | return false; 162 | } 163 | 164 | auto a1 = nb::str(nb::handle(argv[1])); 165 | const auto TargetPid = uint32_t(std::atol(a1.c_str())); 166 | auto a2 = nb::str(nb::handle(argv[2])); 167 | return GenerateMinidumpFromProcessId(TargetPid, a2.c_str()); 168 | }, 169 | "Generate a minidump for the target TargetPid, write it to the given " 170 | "path. Returns true on success."); 171 | #endif // _WIN32 172 | } 173 | -------------------------------------------------------------------------------- /src/python/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0vercl0k/udmp-parser/2fff7ac40f22118cd7e68b1df71dbfd22a4906f6/src/python/tests/__init__.py -------------------------------------------------------------------------------- /src/python/tests/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_level = INFO 3 | minversion = 6.0 4 | required_plugins = 5 | pytest-xdist 6 | pytest-cov 7 | python_functions = 8 | test_* 9 | time_* 10 | python_files = *.py 11 | testpaths = 12 | . 13 | markers = 14 | slow: flag test as slow (deselect with '-m "not slow"') 15 | online: flag test as requiring internet to work (deselect with '-m "not online"') 16 | -------------------------------------------------------------------------------- /src/python/tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-cov 3 | pytest-xdist 4 | pytest-forked 5 | coverage 6 | lief 7 | -------------------------------------------------------------------------------- /src/python/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of udmp-parser project 3 | # 4 | # Released under MIT License, by 0vercl0k - 2023 5 | # 6 | # With contribution from: 7 | # * hugsy - (github.com/hugsy) 8 | # 9 | 10 | import lief 11 | import pathlib 12 | import platform 13 | import pytest 14 | import subprocess 15 | import tempfile 16 | import time 17 | import unittest 18 | 19 | # format: on 20 | import udmp_parser # type: ignore 21 | from .utils import generate_minidump_from_process_name # type: ignore 22 | 23 | # format: off 24 | 25 | TARGET_PROCESS_NAME: str = "winver.exe" 26 | TARGET_PROCESS_PATH: pathlib.Path = pathlib.Path( 27 | f"C:/Windows/System32/{TARGET_PROCESS_NAME}" 28 | ) 29 | 30 | 31 | @pytest.mark.skipif( 32 | platform.system().lower() != "windows", reason="Tests only for Windows" 33 | ) 34 | class TestParserBasic(unittest.TestCase): 35 | def setUp(self): 36 | # TODO switch to LFS to store minidump test cases (x86, x64, wow64, etc.) 37 | self.process = subprocess.Popen( 38 | [ 39 | TARGET_PROCESS_PATH, 40 | ] 41 | ) 42 | time.sleep(1) 43 | self.tempdir = tempfile.TemporaryDirectory(prefix="minidump_") 44 | self.tempdir_path = pathlib.Path(self.tempdir.name) 45 | res = generate_minidump_from_process_name( 46 | TARGET_PROCESS_NAME, self.tempdir_path 47 | ) 48 | assert res 49 | _, self.minidump_file = res 50 | assert self.minidump_file.exists() 51 | 52 | def tearDown(self) -> None: 53 | self.process.kill() 54 | return super().tearDown() 55 | 56 | def test_version(self): 57 | assert udmp_parser.version.major == 0 58 | assert udmp_parser.version.minor == 7 59 | assert udmp_parser.version.release == "" 60 | 61 | def test_parser_basic(self): 62 | parser = udmp_parser.UserDumpParser() 63 | assert parser.ForegroundThreadId() is None 64 | assert len(parser.Threads()) == 0 65 | assert len(parser.Memory()) == 0 66 | assert parser.Parse(self.minidump_file) 67 | assert len(parser.Threads()) 68 | assert len(parser.Memory()) 69 | assert len(parser.Modules()) 70 | 71 | def test_threads(self): 72 | parser = udmp_parser.UserDumpParser() 73 | assert parser.Parse(self.minidump_file) 74 | threads = parser.Threads() 75 | assert len(threads) 76 | 77 | for _, thread in threads.items(): 78 | assert thread.ThreadId, "invalid ThreadId field" 79 | assert thread.Teb, "invalid Teb field" 80 | assert not isinstance( 81 | thread.Context, udmp_parser.UnknownContext 82 | ), "invalid Context field" 83 | if isinstance(thread.Context, udmp_parser.Context32): 84 | assert thread.Context.Esp 85 | assert thread.Context.Eip 86 | elif isinstance(thread.Context, udmp_parser.Context64): 87 | assert thread.Context.Rsp 88 | assert thread.Context.Rip 89 | else: 90 | assert False, "invalid Context field" 91 | 92 | def test_modules(self): 93 | parser = udmp_parser.UserDumpParser() 94 | assert parser.Parse(self.minidump_file) 95 | modules = parser.Modules() 96 | assert len(modules) 97 | 98 | ntdll_modules = [ 99 | mod 100 | for _, mod in modules.items() 101 | if mod.ModuleName.lower().endswith("ntdll.dll") 102 | ] 103 | kernel32_modules = [ 104 | mod 105 | for _, mod in modules.items() 106 | if mod.ModuleName.lower().endswith("kernel32.dll") 107 | ] 108 | 109 | assert len(ntdll_modules) >= 1 110 | assert len(kernel32_modules) >= 1 111 | 112 | for mod in ntdll_modules + kernel32_modules: 113 | assert mod.BaseOfImage > 0, f"Invalid BaseOfImage for '{mod}'" 114 | assert mod.SizeOfImage > 0, f"Invalid SizeOfImage for '{mod}'" 115 | module_raw = parser.ReadMemory(mod.BaseOfImage, mod.SizeOfImage) 116 | img = lief.PE.parse(module_raw) 117 | assert img 118 | assert img.header.numberof_sections 119 | assert img.optional_header.sizeof_code 120 | assert img.optional_header.imagebase 121 | 122 | def test_memory(self): 123 | parser = udmp_parser.UserDumpParser() 124 | assert parser.Parse(self.minidump_file) 125 | memory_regions = parser.Memory() 126 | assert len(memory_regions) 127 | 128 | def test_memory_inexistent(self): 129 | """This ensures that `ReadMemory` returns `None` when trying to 130 | read a segment of memory that isn't described in the dump file.""" 131 | parser = udmp_parser.UserDumpParser() 132 | assert parser.Parse(self.minidump_file) 133 | assert parser.ReadMemory(0xDEADBEEF_BAADC0DE, 0x10) is None 134 | 135 | def test_memory_empty(self): 136 | """This ensures that `ReadMemory` returns an empty array (and not `None`) 137 | when trying to read into a memory region that has no data associated.""" 138 | parser = udmp_parser.UserDumpParser() 139 | assert parser.Parse(self.minidump_file) 140 | mem = parser.Memory() 141 | empty_regions = list(filter(lambda m: m.DataSize == 0, mem.values())) 142 | assert len(empty_regions) > 0 143 | empty_region = empty_regions[0] 144 | assert len(parser.ReadMemory(empty_region.BaseAddress, 0x10)) == 0 145 | 146 | def test_utils(self): 147 | assert udmp_parser.utils.TypeToString(0x2_0000) == "MEM_PRIVATE" 148 | assert udmp_parser.utils.TypeToString(0x4_0000) == "MEM_MAPPED" 149 | assert udmp_parser.utils.TypeToString(0x100_0000) == "MEM_IMAGE" 150 | assert udmp_parser.utils.TypeToString(0x41414141) == "" 151 | 152 | assert udmp_parser.utils.StateToString(0x1000) == "MEM_COMMIT" 153 | assert udmp_parser.utils.StateToString(0x2000) == "MEM_RESERVE" 154 | assert udmp_parser.utils.StateToString(0x10000) == "MEM_FREE" 155 | assert udmp_parser.utils.StateToString(0x41414141) == "" 156 | 157 | assert udmp_parser.utils.ProtectionToString(0x01) == "PAGE_NOACCESS" 158 | assert udmp_parser.utils.ProtectionToString(0x02) == "PAGE_READONLY" 159 | assert udmp_parser.utils.ProtectionToString(0x04) == "PAGE_READWRITE" 160 | assert udmp_parser.utils.ProtectionToString(0x08) == "PAGE_WRITECOPY" 161 | assert udmp_parser.utils.ProtectionToString(0x10) == "PAGE_EXECUTE" 162 | assert udmp_parser.utils.ProtectionToString( 163 | 0x20) == "PAGE_EXECUTE_READ" 164 | assert udmp_parser.utils.ProtectionToString( 165 | 0x40) == "PAGE_EXECUTE_READWRITE" 166 | assert udmp_parser.utils.ProtectionToString( 167 | 0x80) == "PAGE_EXECUTE_WRITECOPY" 168 | assert ( 169 | udmp_parser.utils.ProtectionToString( 170 | 0x18) == "PAGE_WRITECOPY,PAGE_EXECUTE" 171 | ) 172 | assert ( 173 | udmp_parser.utils.ProtectionToString(0x19) 174 | == "PAGE_NOACCESS,PAGE_WRITECOPY,PAGE_EXECUTE" 175 | ) 176 | assert ( 177 | udmp_parser.utils.ProtectionToString(0x44_0000 | 0x19) 178 | == "PAGE_NOACCESS,PAGE_WRITECOPY,PAGE_EXECUTE,0x440000" 179 | ) 180 | -------------------------------------------------------------------------------- /src/python/tests/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of udmp-parser project 3 | # 4 | # Released under MIT License, by 0vercl0k - 2023 5 | # 6 | # With contribution from: 7 | # * hugsy - (github.com/hugsy) 8 | # 9 | 10 | import pathlib 11 | import ctypes 12 | from ctypes import wintypes 13 | import time 14 | from typing import Optional, Tuple 15 | 16 | import udmp_parser 17 | 18 | 19 | def get_process_id(process_name: str): 20 | kernel32 = ctypes.WinDLL("kernel32") 21 | CreateToolhelp32Snapshot = kernel32.CreateToolhelp32Snapshot 22 | Process32First = kernel32.Process32First 23 | Process32Next = kernel32.Process32Next 24 | CloseHandle = kernel32.CloseHandle 25 | 26 | TH32CS_SNAPPROCESS = 0x00000002 27 | MAX_PATH = 260 28 | 29 | class PROCESSENTRY32(ctypes.Structure): 30 | _fields_ = [ 31 | ("dwSize", wintypes.DWORD), 32 | ("cntUsage", wintypes.DWORD), 33 | ("th32ProcessID", wintypes.DWORD), 34 | ("th32DefaultHeapID", wintypes.LPVOID), 35 | ("th32ModuleID", wintypes.DWORD), 36 | ("cntThreads", wintypes.DWORD), 37 | ("th32ParentProcessID", wintypes.DWORD), 38 | ("pcPriClassBase", wintypes.LONG), 39 | ("dwFlags", wintypes.DWORD), 40 | ("szExeFile", wintypes.CHAR * MAX_PATH), 41 | ] 42 | 43 | snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 44 | if snapshot == -1: 45 | return None 46 | 47 | pe32 = PROCESSENTRY32() 48 | pe32.dwSize = ctypes.sizeof(PROCESSENTRY32) 49 | 50 | if Process32First(snapshot, ctypes.byref(pe32)) == 0: 51 | CloseHandle(snapshot) 52 | return None 53 | 54 | res = None 55 | while True: 56 | process_name_str = pe32.szExeFile.decode("utf-8").lower() 57 | if process_name.lower() == process_name_str: 58 | res = pe32.th32ProcessID 59 | break 60 | 61 | if Process32Next(snapshot, ctypes.byref(pe32)) == 0: 62 | break 63 | 64 | CloseHandle(snapshot) 65 | return res 66 | 67 | 68 | def generate_minidump_from_process_name( 69 | process_name: str = "explorer.exe", output_dir: pathlib.Path = pathlib.Path(".") 70 | ) -> Optional[Tuple[int, pathlib.Path]]: 71 | process_id = get_process_id(process_name) 72 | if not process_id or not isinstance(process_id, int): 73 | return None 74 | 75 | dump_file_path = output_dir / f"minidump-{process_name}-{int(time.time())}.dmp" 76 | 77 | if not udmp_parser.utils.generate_minidump(process_id, dump_file_path): 78 | return None 79 | 80 | print(f"Minidump generated successfully: PID={process_id} -> {dump_file_path}") 81 | return (process_id, dump_file_path) 82 | -------------------------------------------------------------------------------- /src/python/udmp_parser-stubs/__init__.pyi: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Union, Optional, overload 3 | from enum import Enum, IntEnum 4 | import udmp_parser 5 | 6 | 7 | class ProcessorArch(IntEnum): 8 | X86 = 0 9 | ARM = 5 10 | IA64 = 6 11 | AMD64 = 9 12 | Unknown = 0xFFFF 13 | 14 | 15 | class Arch(IntEnum): 16 | X86 = 0 17 | X64 = 1 18 | 19 | 20 | kWOW64_SIZE_OF_80387_REGISTERS: int = 80 21 | 22 | 23 | class FloatingSaveArea32: 24 | ControlWord: int 25 | StatusWord: int 26 | TagWord: int 27 | ErrorOffset: int 28 | ErrorSelector: int 29 | DataOffset: int 30 | DataSelector: int 31 | RegisterArea: bytearray # size =kWOW64_SIZE_OF_80387_REGISTERS 32 | Cr0NpxState: int 33 | 34 | 35 | class Context32: 36 | ContextFlags: int 37 | Dr0: int 38 | Dr1: int 39 | Dr2: int 40 | Dr3: int 41 | Dr6: int 42 | Dr7: int 43 | FloatSave: FloatingSaveArea32 44 | SegGs: int 45 | SegFs: int 46 | SegEs: int 47 | SegDs: int 48 | Edi: int 49 | Esi: int 50 | Ebx: int 51 | Edx: int 52 | Ecx: int 53 | Eax: int 54 | Ebp: int 55 | Eip: int 56 | SegCs: int 57 | EFlags: int 58 | Esp: int 59 | SegSs: int 60 | ExtendedRegisters: bytearray # size =kWOW64_MAXIMUM_SUPPORTED_EXTENSION 61 | 62 | 63 | class uint128_t: 64 | Low: int 65 | High: int 66 | 67 | 68 | class Context64: 69 | P1Home: int 70 | P2Home: int 71 | P3Home: int 72 | P4Home: int 73 | P5Home: int 74 | P6Home: int 75 | ContextFlags: int 76 | MxCsr: int 77 | SegCs: int 78 | SegDs: int 79 | SegEs: int 80 | SegFs: int 81 | SegGs: int 82 | SegSs: int 83 | EFlags: int 84 | Dr0: int 85 | Dr1: int 86 | Dr2: int 87 | Dr3: int 88 | Dr6: int 89 | Dr7: int 90 | Rax: int 91 | Rcx: int 92 | Rdx: int 93 | Rbx: int 94 | Rsp: int 95 | Rbp: int 96 | Rsi: int 97 | Rdi: int 98 | R8: int 99 | R9: int 100 | R10: int 101 | R11: int 102 | R12: int 103 | R13: int 104 | R14: int 105 | R15: int 106 | Rip: int 107 | ControlWord: int 108 | StatusWord: int 109 | TagWord: int 110 | Reserved1: int 111 | ErrorOpcode: int 112 | ErrorOffset: int 113 | ErrorSelector: int 114 | Reserved2: int 115 | DataOffset: int 116 | DataSelector: int 117 | Reserved3: int 118 | MxCsr2: int 119 | MxCsr_Mask: int 120 | FloatRegisters: list[uint128_t] # size =8 121 | Xmm0: uint128_t 122 | Xmm1: uint128_t 123 | Xmm2: uint128_t 124 | Xmm3: uint128_t 125 | Xmm4: uint128_t 126 | Xmm5: uint128_t 127 | Xmm6: uint128_t 128 | Xmm7: uint128_t 129 | Xmm8: uint128_t 130 | Xmm9: uint128_t 131 | Xmm10: uint128_t 132 | Xmm11: uint128_t 133 | Xmm12: uint128_t 134 | Xmm13: uint128_t 135 | Xmm14: uint128_t 136 | Xmm15: uint128_t 137 | Padding: bytearray # size =0x60 138 | VectorRegister: list[uint128_t] # size =26 139 | VectorControl: int 140 | DebugControl: int 141 | LastBranchToRip: int 142 | LastBranchFromRip: int 143 | LastExceptionToRip: int 144 | LastExceptionFromRip: int 145 | 146 | 147 | class Directory: 148 | StreamType: StreamType = StreamType.Unused 149 | LocationDescriptor32_t: int 150 | 151 | 152 | class FileMapReader: 153 | def MapFile(self, arg: str, /) -> bool: ... 154 | def ViewSize(self) -> int: ... 155 | def __init__(self) -> None: ... 156 | 157 | 158 | class FixedFileInfo: 159 | Signature: int = 0 160 | StrucVersion: int = 0 161 | FileVersionMS: int = 0 162 | FileVersionLS: int = 0 163 | ProductVersionMS: int = 0 164 | ProductVersionLS: int = 0 165 | FileFlagsMask: int = 0 166 | FileFlags: int = 0 167 | FileOS: int = 0 168 | FileType: int = 0 169 | FileSubtype: int = 0 170 | FileDateMS: int = 0 171 | FileDateLS: int = 0 172 | 173 | 174 | class Header: 175 | Signature: int 176 | Version: int 177 | ImplementationVersion: int 178 | NumberOfStreams: int 179 | StreamDirectoryRva: int 180 | CheckSum: int 181 | Reserved: int 182 | TimeDateStamp: int 183 | Flags: int 184 | ExpectedSignature: int 185 | ValidFlagsMask: int 186 | 187 | def LooksGood(self) -> bool: ... 188 | 189 | def __init__(*args, **kwargs): 190 | """ 191 | Initialize self. See help(type(self)) for accurate signature. 192 | """ 193 | ... 194 | 195 | 196 | class LocationDescriptor32: 197 | DataSize: int 198 | Rva: int 199 | 200 | def __init__(*args, **kwargs): 201 | """ 202 | Initialize self. See help(type(self)) for accurate signature. 203 | """ 204 | ... 205 | 206 | 207 | class LocationDescriptor64: 208 | DataSize: int 209 | Rva: int 210 | 211 | def __init__(*args, **kwargs): 212 | """ 213 | Initialize self. See help(type(self)) for accurate signature. 214 | """ 215 | ... 216 | 217 | 218 | class MemBlock: 219 | BaseAddress: int 220 | AllocationBase: int 221 | AllocationProtect: int 222 | RegionSize: int 223 | State: int 224 | Protect: int 225 | Type: int 226 | DataOffset: int 227 | DataSize: int 228 | 229 | def __init__(self, arg: udmp_parser.MemoryInfo, /) -> None: ... 230 | def __str__(self) -> str: ... 231 | 232 | 233 | class Memory64ListStreamHdr: 234 | StartOfMemoryRange: int 235 | DataSize: int 236 | 237 | 238 | class MemoryDescriptor: 239 | ThreadId: int 240 | SuspendCount: int 241 | PriorityClass: int 242 | Priority: int 243 | Teb: int 244 | Stack: MemoryDescriptor 245 | ThreadContext: LocationDescriptor32 246 | 247 | 248 | class ThreadEntry: 249 | ThreadId: int 250 | SuspendCount: int 251 | PriorityClass: int 252 | Priority: int 253 | Teb: int 254 | Stack: MemoryDescriptor 255 | ThreadContext: LocationDescriptor32 256 | 257 | 258 | class Thread_t: 259 | ThreadId: int 260 | SuspendCount: int 261 | PriorityClass: int 262 | Priority: int 263 | Teb: int 264 | Context: Union[UnknownContext, Context32, Context64] 265 | 266 | 267 | class MemoryDescriptor64: 268 | StartOfMemoryRange: int 269 | DataSize: int 270 | 271 | 272 | class MemoryInfoListStream: 273 | SizeOfHeader: int 274 | SizeOfEntry: int 275 | NumberOfEntries: int 276 | 277 | 278 | class MemoryInfo: 279 | BaseAddress: int 280 | AllocationBase: int 281 | AllocationProtect: int 282 | __alignment1: int 283 | RegionSize: int 284 | State: int 285 | Protect: int 286 | Type: int 287 | __alignment2: int 288 | 289 | 290 | class Module: 291 | BaseAddress: int 292 | AllocationBase: int 293 | AllocationProtect: int 294 | RegionSize: int 295 | State: int 296 | Protect: int 297 | Type: int 298 | DataSize: int 299 | 300 | @property 301 | def Data(self) -> int: ... 302 | def __str__(self) -> str: ... 303 | 304 | 305 | class StreamType(IntEnum): 306 | Unused = 0 307 | ThreadList = 3 308 | ModuleList = 4 309 | Exception = 6 310 | SystemInfo = 7 311 | Memory64List = 9 312 | MemoryInfoList = 16 313 | 314 | 315 | class SystemInfoStream: 316 | ProcessorArchitecture: ProcessorArch 317 | ProcessorLevel: int 318 | ProcessorRevision: int 319 | NumberOfProcessors: int 320 | ProductType: int 321 | MajorVersion: int 322 | MinorVersion: int 323 | BuildNumber: int 324 | PlatformId: int 325 | CSDVersionRva: int 326 | SuiteMask: int 327 | Reserved2: int 328 | 329 | 330 | class ExceptionRecord: 331 | ExceptionCode: int 332 | ExceptionFlags: int 333 | ExceptionRecord: int 334 | ExceptionAddress: int 335 | NumberParameters: int 336 | __unusedAlignment: int 337 | ExceptionInformation: list[int] # size=kEXCEPTION_MAXIMUM_PARAMETERS 338 | 339 | 340 | class ExceptionStream: 341 | ThreadId: int 342 | __alignment: int 343 | ExceptionRecord: ExceptionRecord 344 | ThreadContext: LocationDescriptor32 345 | 346 | 347 | class UnknownContext: 348 | def __init__(*args, **kwargs): 349 | """ 350 | Initialize self. See help(type(self)) for accurate signature. 351 | """ 352 | ... 353 | 354 | 355 | class UserDumpParser: 356 | def ForegroundThreadId(self) -> Optional[int]: ... 357 | 358 | def GetMemoryBlock(self, arg: int, /) -> udmp_parser.MemBlock: 359 | """ 360 | Access a specific MemoryBlock 361 | """ 362 | ... 363 | 364 | def Memory(self) -> dict[int, udmp_parser.MemBlock]: ... 365 | 366 | def Modules(self) -> dict[int, udmp_parser.Modules]: 367 | """ 368 | Get the minidump modules 369 | """ 370 | ... 371 | 372 | def Parse(self, arg: os.PathLike, /) -> bool: 373 | """ 374 | Parse the minidump given in argument. 375 | """ 376 | ... 377 | 378 | def ReadMemory(self, arg0: int, arg1: int, /) -> Optional[list[int]]: 379 | """ 380 | Read bytes from memory 381 | """ 382 | ... 383 | 384 | def Threads(self) -> dict[int, udmp_parser.Thread]: 385 | """ 386 | Get the minidump threads 387 | """ 388 | ... 389 | 390 | def __init__(self) -> None: ... 391 | 392 | 393 | class version: 394 | def __init__(*args, **kwargs): 395 | """ 396 | Initialize self. See help(type(self)) for accurate signature. 397 | """ 398 | ... 399 | major: int 400 | 401 | minor: int 402 | 403 | release: str 404 | -------------------------------------------------------------------------------- /src/python/udmp_parser-stubs/utils.pyi: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def ProtectionToString(arg: int, /) -> str: 5 | """ 6 | Get a string representation of the memory protection 7 | """ 8 | ... 9 | 10 | 11 | def StateToString(arg: int, /) -> str: 12 | """ 13 | Get a string representation of the memory state 14 | """ 15 | ... 16 | 17 | 18 | def TypeToString(arg: int, /) -> str: 19 | """ 20 | Get a string representation of the memory type 21 | """ 22 | ... 23 | 24 | 25 | def generate_minidump(TargetPid: int, MiniDumpFilePath: os.PathLike) -> bool: 26 | """ 27 | Generate a minidump for TargetPid and save it to the given path. Returns true on success. 28 | """ 29 | ... 30 | 31 | 32 | def generate_minidump_from_command_line() -> bool: 33 | """ 34 | Generate a minidump for the target TargetPid, write it to the given path. Returns true on success. 35 | """ 36 | ... 37 | --------------------------------------------------------------------------------