mirror of
https://github.com/yrp604/bdump
synced 2026-06-08 18:32:32 +00:00
Initial
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# bdump
|
||||
|
||||
A windbg-js plugin to dump the cpu state.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
0: kd> .scriptload c:\path\to\bdump.js
|
||||
0: kd> !bdump "c:\\path\\to\\dump"
|
||||
```
|
||||
|
||||
This will create two files:
|
||||
- `dump\mem.dmp` is a standard windows dumpfile. Note that this is _not_ a
|
||||
minidump!
|
||||
- `dump\regs.json` is a JSON object containing all the register values encoded
|
||||
as hex strings. E.g
|
||||
|
||||
```json
|
||||
{"rax": "0x41414141", "rbx": "0x42424242", ... }
|
||||
```
|
||||
|
||||
There is also a `cpu-jtoy.py` script which converts the registers from JSON to
|
||||
YAML. This file is vanilla YAML and is used to allow hex encoding of integers
|
||||
and avoid the 53-bit limitation in some JSON implementations. Hopefully with
|
||||
BigInt support in more JS engines soon I can drop this...
|
||||
|
||||
This script will also add in the AVX register state with all values zeroed.
|
||||
|
||||
## Caveats
|
||||
- doesn't currently capture `xmm`/`ymm`/`zmm` register states
|
||||
- doesn't currently capture `mxcsr_mask`
|
||||
- capturing segments (especially `tr` and `es`) is kinda sketchy
|
||||
@@ -0,0 +1,333 @@
|
||||
"use strict";
|
||||
|
||||
const log = e => host.diagnostics.debugLog(e);
|
||||
const logln = e => log(e + '\n');
|
||||
const hex = e => '0x' + e .toString(16);
|
||||
|
||||
const is_usermode = e => e.bitwiseShiftRight(63) == 1;
|
||||
|
||||
function usage() {
|
||||
logln('[bdump] Usage: !bdump "C:\\\\path\\\\to\\\\dump"')
|
||||
logln('[bdump] This will create a dump directory and fill it with a memory and register files');
|
||||
logln('[bdump] NOTE: you must include the quotes and escape the backslashes!');
|
||||
}
|
||||
|
||||
function __hex_obj(o) {
|
||||
for (let k in o) {
|
||||
// if limit is in our obj, its almost definitely a seg reg and we
|
||||
// should recurse instead of hexing
|
||||
// if 0 is in our obj, its probably an array and we should also
|
||||
// recurse
|
||||
if (typeof o[k] === 'object' && ('limit' in o[k] || 0 in o[k])) {
|
||||
__hex_obj(o[k]);
|
||||
} else if (typeof o[k] == 'boolean') {
|
||||
continue;
|
||||
} else {
|
||||
o[k] = hex(o[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __create_dir(path) {
|
||||
const Control = host.namespace.Debugger.Utility.Control;
|
||||
|
||||
Control.ExecuteCommand('.shell -i- mkdir ' + path);
|
||||
}
|
||||
|
||||
function __rdmsr(msr_num) {
|
||||
const Control = host.namespace.Debugger.Utility.Control;
|
||||
|
||||
let line = Control.ExecuteCommand('rdmsr ' + hex(msr_num)).Last();
|
||||
// 1: kd> rdmsr 3a
|
||||
// msr[3a] = 00000000`00000001
|
||||
let chunks = line.split(' ');
|
||||
return host.parseInt64(chunks[chunks.length - 1].replace('`', ''), 16);
|
||||
}
|
||||
|
||||
// must run after __collect_segs
|
||||
function __collect_msrs(regs) {
|
||||
const msrs = {
|
||||
"tsc": 0x10,
|
||||
"apic_base": 0x1b,
|
||||
"sysenter_cs": 0x174,
|
||||
"sysenter_esp": 0x175,
|
||||
"sysenter_eip": 0x176,
|
||||
"pat": 0x277,
|
||||
"efer": 0xc0000080,
|
||||
"star": 0xc0000081,
|
||||
"lstar": 0xc0000082,
|
||||
"cstar": 0xc0000083,
|
||||
"sfmask": 0xc0000084,
|
||||
"kernel_gs_base": 0xc0000102,
|
||||
"tsc_aux": 0xc0000103,
|
||||
};
|
||||
|
||||
for (let msr in msrs) {
|
||||
regs[msr] = __rdmsr(msrs[msr]);
|
||||
}
|
||||
|
||||
// windbg shows these are zero because it stores the values in the msrs
|
||||
const msr_fs_base = 0xc0000100;
|
||||
regs.fs.base = __rdmsr(msr_fs_base);
|
||||
const msr_gs_base = 0xc0000101;
|
||||
regs.gs.base = __rdmsr(msr_gs_base);
|
||||
}
|
||||
|
||||
function __collect_seg(n) {
|
||||
const Control = host.namespace.Debugger.Utility.Control;
|
||||
|
||||
let r = {};
|
||||
let line = Control.ExecuteCommand('dg ' + n).Last();
|
||||
// Sel Base Limit Type l ze an es ng Flags
|
||||
// ---- ----------------- ----------------- ---------- - -- -- -- -- --------
|
||||
// 0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb
|
||||
if (line.indexOf('Unable to get descriptor') != -1) {
|
||||
logln('[bdump] could not recover ' + n + '!');
|
||||
return null;
|
||||
}
|
||||
|
||||
let chunks = line.split(' ');
|
||||
|
||||
r.present = chunks[9] == 'P';
|
||||
r.selector = host.parseInt64(chunks[0], 16);
|
||||
r.base = host.parseInt64(chunks[1].replace('`', ''), 16);
|
||||
r.limit = host.parseInt64(chunks[2].replace('`', ''), 16);
|
||||
r.attr = host.parseInt64(chunks[chunks.length - 1].replace('`', ''), 16);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
// must run before __collect_msrs
|
||||
function __collect_segs(regs) {
|
||||
regs.es = __collect_seg('es');
|
||||
regs.cs = __collect_seg('cs');
|
||||
regs.ss = __collect_seg('ss');
|
||||
regs.ds = __collect_seg('ds');
|
||||
// these values below will be wrong -- reading msrs will give us the
|
||||
// correct ones
|
||||
regs.fs = __collect_seg('fs');
|
||||
regs.gs = __collect_seg('gs');
|
||||
|
||||
regs.tr = __collect_seg('tr');
|
||||
// our ghetto string parsing is wrong for tr, force it to true
|
||||
regs.tr.present = true;
|
||||
|
||||
regs.ldtr = __collect_seg('ldtr');
|
||||
}
|
||||
|
||||
function __collect_user(regs) {
|
||||
const User = host.currentThread.Registers.User;
|
||||
|
||||
regs.rax = User.rax;
|
||||
regs.rbx = User.rbx;
|
||||
regs.rcx = User.rcx;
|
||||
regs.rdx = User.rdx;
|
||||
regs.rsi = User.rsi;
|
||||
regs.rdi = User.rdi;
|
||||
regs.rip = User.rip;
|
||||
regs.rsp = User.rsp;
|
||||
regs.rbp = User.rbp;
|
||||
regs.r8 = User.r8;
|
||||
regs.r9 = User.r9;
|
||||
regs.r10 = User.r10;
|
||||
regs.r11 = User.r11;
|
||||
regs.r12 = User.r12;
|
||||
regs.r13 = User.r13;
|
||||
regs.r14 = User.r14;
|
||||
regs.r15 = User.r15;
|
||||
|
||||
regs.rflags = User.efl;
|
||||
|
||||
if (is_usermode(regs.rip)) {
|
||||
regs.dr0 = User.dr0;
|
||||
regs.dr1 = User.dr1;
|
||||
regs.dr2 = User.dr2;
|
||||
regs.dr3 = User.dr3;
|
||||
regs.dr6 = User.dr6;
|
||||
regs.dr7 = User.dr7;
|
||||
}
|
||||
}
|
||||
|
||||
function __collect_fp(regs) {
|
||||
const Fprs = host.currentThread.Registers.FloatingPoint;
|
||||
|
||||
regs.fpcw = Fprs.fpcw;
|
||||
regs.fpsw = Fprs.fpsw;
|
||||
regs.fptw = Fprs.fptw;
|
||||
|
||||
regs.fpst = Array();
|
||||
regs.fpst[0] = Fprs.st0;
|
||||
regs.fpst[1] = Fprs.st1;
|
||||
regs.fpst[2] = Fprs.st2;
|
||||
regs.fpst[3] = Fprs.st3;
|
||||
regs.fpst[4] = Fprs.st4;
|
||||
regs.fpst[5] = Fprs.st5;
|
||||
regs.fpst[6] = Fprs.st6;
|
||||
regs.fpst[7] = Fprs.st7;
|
||||
}
|
||||
|
||||
function __collect_simd(regs) {
|
||||
const Simd = host.currentThread.Registers.SIMD;
|
||||
|
||||
// XXX TODO XXX
|
||||
|
||||
if (is_usermode(regs.rip)) {
|
||||
regs.mxcsr = Simd.mxcsr;
|
||||
}
|
||||
}
|
||||
|
||||
function __collect_kern(regs) {
|
||||
const Kern = host.currentThread.Registers.Kernel;
|
||||
|
||||
regs.cr0 = Kern.cr0;
|
||||
regs.cr2 = Kern.cr2;
|
||||
regs.cr3 = Kern.cr3;
|
||||
regs.cr4 = Kern.cr4;
|
||||
regs.cr8 = Kern.cr8;
|
||||
|
||||
regs.xcr0 = Kern.xcr0;
|
||||
|
||||
regs.gdtr = {};
|
||||
regs.gdtr.base = Kern.gdtr;
|
||||
regs.gdtr.limit = Kern.gdtl;
|
||||
|
||||
regs.idtr = {};
|
||||
regs.idtr.base = Kern.idtr;
|
||||
regs.idtr.limit = Kern.idtl;
|
||||
|
||||
if (!is_usermode(regs.rip)) {
|
||||
regs.dr0 = Kern.kdr0;
|
||||
regs.dr1 = Kern.kdr1;
|
||||
regs.dr2 = Kern.kdr2;
|
||||
regs.dr3 = Kern.kdr3;
|
||||
regs.dr6 = Kern.kdr6;
|
||||
regs.dr7 = Kern.kdr7;
|
||||
|
||||
regs.mxcsr = Kern.kmxcsr;
|
||||
}
|
||||
}
|
||||
|
||||
function __fixup_regs(regs) {
|
||||
// regs im not sure how to get out of windbg...
|
||||
logln("[bdump] don't know how to get mxcsr_mask or fpop, setting to zero...");
|
||||
regs.mxcsr_mask = 0;
|
||||
regs.fpop = 0;
|
||||
logln('[bdump]');
|
||||
|
||||
logln("[bdump] don't know how to get avx registers, skipping...");
|
||||
logln('[bdump]');
|
||||
|
||||
// top 32 bits of tr are incorrectly either 0 or -1
|
||||
// take the top bits from the gdtr and OR them in to get a valid tr
|
||||
if (regs.tr.base.bitwiseShiftRight(32) == host.Int64(0xffffffff)
|
||||
|| regs.tr.base.bitwiseShiftRight(32) == host.Int64(0)) {
|
||||
logln('[bdump] tr.base is not cannonical...');
|
||||
logln('[bdump] old tr.base: ' + hex(regs.tr.base));
|
||||
|
||||
const low32 = host.Int64(0xffffffff);
|
||||
let new_tr = regs.tr.base.bitwiseAnd(low32);
|
||||
|
||||
const hi32 = host.parseInt64('0xffffffff00000000', 16);
|
||||
const top_bits = regs.gdtr.base.bitwiseAnd(hi32);
|
||||
|
||||
new_tr = new_tr.bitwiseOr(top_bits);
|
||||
regs.tr.base = new_tr;
|
||||
|
||||
logln('[bdump] new tr.base: ' + hex(regs.tr.base));
|
||||
logln('[bdump]');
|
||||
}
|
||||
|
||||
// need to make sure cs has that magic value set?
|
||||
const flag = host.Int64(0x2000);
|
||||
if (regs.cs.attr.bitwiseAnd(flag) != flag) {
|
||||
logln('[bdump] setting flag 0x2000 on cs.attr...');
|
||||
logln('[bdump] old cs.attr: ' + hex(regs.cs.attr));
|
||||
regs.cs.attr = regs.cs.attr.bitwiseOr(flag);
|
||||
logln('[bdump] new cs.attr: ' + hex(regs.cs.attr));
|
||||
logln('[bdump]');
|
||||
}
|
||||
|
||||
// kernel/user gs can be swapped when reading from msrs
|
||||
if (is_usermode(regs.rip) != is_usermode(regs.gs.base)) {
|
||||
logln("[bdump] rip and gs don't match kernel/user, swapping...");
|
||||
const tmp = regs.kernel_gs_base;
|
||||
regs.kernel_gs_base = regs.gs.base;
|
||||
regs.gs.base = tmp;
|
||||
logln('[bdump] rip: ' + hex(regs.rip));
|
||||
logln('[bdump] new gs.base: ' + hex(regs.gs.base));
|
||||
logln('[bdump] new kernel_gs_base: ' + hex(regs.kernel_gs_base));
|
||||
logln('[bdump]');
|
||||
}
|
||||
|
||||
// if es was lost to the void, copy ds
|
||||
if (regs.es === null) {
|
||||
logln("[bdump] could not recover es, copying ds...");
|
||||
Object.assign(regs.es, regs.ds);
|
||||
logln('[bdump]');
|
||||
}
|
||||
|
||||
// turn everything into strings because javascript
|
||||
__hex_obj(regs);
|
||||
}
|
||||
|
||||
function __collect_regs() {
|
||||
let regs = {};
|
||||
|
||||
__collect_user(regs);
|
||||
__collect_segs(regs);
|
||||
__collect_msrs(regs);
|
||||
__collect_fp(regs);
|
||||
__collect_simd(regs);
|
||||
__collect_kern(regs);
|
||||
|
||||
return regs;
|
||||
}
|
||||
|
||||
function __save_regs(path, regs) {
|
||||
const Fs = host.namespace.Debugger.Utility.FileSystem;
|
||||
|
||||
const data = JSON.stringify(regs);
|
||||
|
||||
let file = Fs.CreateFile(path + '\\regs.json');
|
||||
let writer = Fs.CreateTextWriter(file);
|
||||
|
||||
writer.WriteLine(data);
|
||||
file.Close();
|
||||
}
|
||||
|
||||
function __save_mem(path) {
|
||||
const Control = host.namespace.Debugger.Utility.Control;
|
||||
|
||||
for (let line of Control.ExecuteCommand('.dump /ka ' + path + '\\mem.dmp')) {
|
||||
logln('[bdump] ' + line);
|
||||
}
|
||||
}
|
||||
|
||||
function __bdump(path) {
|
||||
if (path == undefined) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
logln('[bdump] creating dir...');
|
||||
__create_dir(path);
|
||||
|
||||
logln('[bdump] saving regs...');
|
||||
const regs = __collect_regs();
|
||||
logln('[bdump] register fixups...');
|
||||
__fixup_regs(regs);
|
||||
__save_regs(path, regs);
|
||||
logln('[bdump] saving mem...');
|
||||
__save_mem(path);
|
||||
|
||||
logln('[bdump] done!');
|
||||
}
|
||||
|
||||
function initializeScript() {
|
||||
usage();
|
||||
|
||||
return [
|
||||
new host.apiVersionSupport(1, 2),
|
||||
new host.functionAlias(__bdump, "bdump"),
|
||||
];
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
"""
|
||||
This is an annoying hack -- Right now the windbg js dumps integer values as
|
||||
hex strings, e.g.:
|
||||
{ "rax": "0x4141" }
|
||||
|
||||
This is because js ints are 53 bit, so we can't actually store full 64b
|
||||
registers. Writing a decoder for this in rust kind sucks for two reasons:
|
||||
- We'd need to create a separate decode function for each bitsize we want
|
||||
to decode, e.g. one for u16, u32, and u64
|
||||
- We'd need to create a separate decode function for each array size we
|
||||
want to decide, e.g. [u64; 4 vs [u64; 6]
|
||||
|
||||
While this is definitely doable, I'd rather just bang out a json to yaml
|
||||
converter and have the mofo load yaml instead.
|
||||
"""
|
||||
|
||||
def usage():
|
||||
print('Usage: %s <cpu-state.json>' % sys.argv[0])
|
||||
sys.exit(-1)
|
||||
|
||||
# json hex string decoder class
|
||||
# https://stackoverflow.com/questions/45068797/how-to-convert-string-int-json-into-real-int-with-json-loads
|
||||
class HexStr(json.JSONDecoder):
|
||||
def decode(self, s):
|
||||
result = super(HexStr, self).decode(s)
|
||||
return self._decode(result)
|
||||
|
||||
def _decode(self, o):
|
||||
if isinstance(o, str) or isinstance(o, unicode):
|
||||
try:
|
||||
return int(o, 0)
|
||||
except ValueError:
|
||||
try:
|
||||
return float(o)
|
||||
except ValueError:
|
||||
return o
|
||||
elif isinstance(o, dict):
|
||||
return {k: self._decode(v) for k, v in o.items()}
|
||||
elif isinstance(o, list):
|
||||
return [self._decode(v) for v in o]
|
||||
else:
|
||||
return o
|
||||
|
||||
# yaml hex encoder functions
|
||||
# https://stackoverflow.com/questions/9100662/how-to-print-integers-as-hex-strings-using-json-dumps-in-python/9101562#9101562
|
||||
def hexint_presenter(dumper, data):
|
||||
return dumper.represent_int('%#x' % data)
|
||||
|
||||
def unicode_representer(dumper, uni):
|
||||
node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni)
|
||||
return node
|
||||
|
||||
yaml.add_representer(int, hexint_presenter)
|
||||
yaml.add_representer(long, hexint_presenter)
|
||||
yaml.add_representer(unicode, unicode_representer)
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2: usage()
|
||||
|
||||
json_cpu_file = sys.argv[1]
|
||||
|
||||
with open(json_cpu_file, 'rb') as h:
|
||||
j = json.load(h, cls=HexStr)
|
||||
|
||||
# HACK HACK HACK
|
||||
# remove when I acutally lift zmm state from windbg
|
||||
j['zmm'] = []
|
||||
for ii in range(32):
|
||||
j['zmm'].append({ 'q': [0 for _ in range(8)] })
|
||||
|
||||
y = yaml.dump(j)
|
||||
|
||||
print(y)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user