Repository: 0vercl0k/windbg-scripts
Branch: master
Commit: f8e01a9783e5
Files: 26
Total size: 124.7 KB
Directory structure:
gitextract_yxreilaj/
├── LICENSE
├── Manifest/
│ ├── Manifest.1.xml
│ ├── ManifestVersion.txt
│ └── config.xml
├── README.md
├── basics/
│ ├── Int64.js
│ ├── breakpoint.js
│ ├── breakpoint2.js
│ ├── eval.js
│ ├── extendmodel.js
│ ├── extendmodel_1.js
│ ├── extendmodel_2.js
│ ├── extendmodel_2_1.js
│ └── readmemory.js
├── codecov/
│ ├── README.md
│ └── codecov.js
├── gdt/
│ ├── README.md
│ └── gdt.js
├── parse_eh_win64/
│ ├── README.md
│ └── parse_eh_win64.js
├── policybuffer/
│ ├── README.md
│ └── policybuffer.js
├── sm/
│ ├── README.md
│ └── sm.js
└── telescope/
├── README.md
└── telescope.js
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Axel Souchet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Manifest/Manifest.1.xml
================================================
Telescope
1.0.0.1
Telescope data dereference
]]>
Gdt
1.0.0.1
Gdt dump
]]>
sm
1.0.0.1
Pretty printers for JS::Value and JSObject objects in SpiderMonkey
]]>
]]>
================================================
FILE: Manifest/ManifestVersion.txt
================================================
1
1.0.0.0
1
================================================
FILE: Manifest/config.xml
================================================
================================================
FILE: README.md
================================================
# windbg-scripts
`windbg-scripts` is a collection of [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extensions for WinDbg.
* [basics](basics/): various examples of basic usage of various APIs,
* [parse_eh_win64](parse_eh_win64/): example of extending the data-model with exception handling related information (cf [Debugger data model, Javascript & x64 exception handling](https://doar-e.github.io/blog/2017/12/01/debugger-data-model/)),
* [telescope](telescope/): [telescope](https://gef.readthedocs.io/en/latest/commands/dereference/) like command for WinDbg,
* [sm](sm/): pretty-printing of Spidermonkey `js::Value` and `JSObject` objects,
* [codecov](codecov/): extract code-coverage out of a [TTD](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview) trace,
* [policybuffer](policybuffer/): disassemble a Chrome policy buffer program,
* [gdt](gdt/): dump the [Global Descriptor Table](https://wiki.osdev.org/Global_Descriptor_Table).
## Installing the script gallery
If you would like to have `telescope` and `sm` loaded every time your debugger starts instead of loading the extensions manually follow the below steps:
1. Clone this GitHub repository,
2. Edit the `Manifest\config.xml` file and update the `LocalCacheRootFolder` path with a value that makes sense,
3. Open the debugger and import the gallery by running `.settings load c:\path\where\cloned\windbg-scripts\Manifest\config.xml` and `.settings save`.
4. Restart the debugger and you should be able to run `!telescope` as well as inspecting the gallery content from the data-model.
```text
0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories
Debugger.State.ExtensionGallery.ExtensionRepositories
[0x0] : overgallery
[0x1] : LocalInstalled
0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0]
Debugger.State.ExtensionGallery.ExtensionRepositories[0] : overgallery
Name : overgallery
ManifestVersion : 0x1
URL
Enabled : true
Packages
0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages
Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages
[0x0] : Telescope
0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages[0]
Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages[0] : Telescope
Name : Telescope
Version : 1.0.0.1
Description : Telescope data dereference
Size : 0
IsDownloaded : true
Components
```
================================================
FILE: basics/Int64.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
let logln = function (e) {
host.diagnostics.debugLog(e + '\n');
}
function invokeScript() {
let a = host.Int64(1337);
let aplusone = a + 1;
logln(aplusone.toString(16));
let b = host.parseInt64('0xdeadbeefbaadc0de', 16);
let bplusone = b.add(1);
logln(bplusone.toString(16));
let bplusonenothrow = b.convertToNumber() + 1;
logln(bplusonenothrow);
try {
let bplusonethrow = b + 1;
} catch(e) {
logln(e);
}
logln(a.compareTo(1));
logln(a.compareTo(1337));
logln(a.compareTo(1338));
}
================================================
FILE: basics/breakpoint.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
let logln = function (e) {
host.diagnostics.debugLog(e + '\n');
}
function handle_bp() {
let Regs = host.currentThread.Registers.User;
let Args = [Regs.rcx, Regs.rdx, Regs.r8];
let ArgsS = Args.map(c => c.toString(16));
let HeapHandle = ArgsS[0];
let Flags = ArgsS[1];
let Size = ArgsS[2];
logln('RtlAllocateHeap: HeapHandle: ' + HeapHandle + ', Flags: ' + Flags + ', Size: ' + Size);
}
function invokeScript() {
let Control = host.namespace.Debugger.Utility.Control;
let Regs = host.currentThread.Registers.User;
let CurrentProcess = host.currentProcess;
let BreakpointAlreadySet = CurrentProcess.Debug.Breakpoints.Any(
c => c.OffsetExpression == 'ntdll!RtlAllocateHeap+0x0'
);
if(BreakpointAlreadySet == false) {
let Bp = Control.SetBreakpointAtOffset('RtlAllocateHeap', 0, 'ntdll');
Bp.Command = '.echo doare; dx @$scriptContents.handle_bp(); gc';
} else {
logln('Breakpoint already set.');
}
logln('Press "g" to run the target.');
// let Lines = Control.ExecuteCommand('gc');
// for(let Line of Lines) {
// logln('Line: ' + Line);
// }
}
================================================
FILE: basics/breakpoint2.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
let logln = function (e) {
host.diagnostics.debugLog(e + '\n');
}
function handle_bp() {
let Regs = host.currentThread.Registers.User;
let Args = [Regs.rcx, Regs.rdx, Regs.r8];
let ArgsS = Args.map(c => c.toString(16));
let HeapHandle = ArgsS[0];
let Flags = ArgsS[1];
let Size = ArgsS[2];
logln('RtlAllocateHeap: HeapHandle: ' + HeapHandle + ', Flags: ' + Flags + ', Size: ' + Size);
if(Args[2].compareTo(0x100) > 0) {
// stop execution if the allocation size is bigger than 0x100
return true;
}
// keep the execution going if it's a small size
return false;
}
function invokeScript() {
let Control = host.namespace.Debugger.Utility.Control;
let Regs = host.currentThread.Registers.User;
let CurrentProcess = host.currentProcess;
let HeapAlloc = host.getModuleSymbolAddress('ntdll', 'RtlAllocateHeap');
let BreakpointAlreadySet = CurrentProcess.Debug.Breakpoints.Any(
c => c.Address == HeapAlloc
);
if(BreakpointAlreadySet == false) {
logln('RltAllocateHeap @ ' + HeapAlloc.toString(16));
Control.ExecuteCommand('bp /w "@$scriptContents.handle_bp()" ' + HeapAlloc.toString(16));
} else {
logln('Breakpoint already set.');
}
logln('Press "g" to run the target.');
}
================================================
FILE: basics/eval.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
let logln = function (e) {
host.diagnostics.debugLog(e + '\n');
}
function invokeScript() {
let Control = host.namespace.Debugger.Utility.Control;
for(let Line of Control.ExecuteCommand('kp')) {
logln('Line: ' + Line);
}
}
================================================
FILE: basics/extendmodel.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
class Attribute {
constructor(Process, Name, Value) {
this.__process = Process;
this.Name = Name;
this.Value = Value;
}
toString() {
let S = 'Process: ' + this.__process.Name + ', ';
S += 'Name: ' + this.Name + ', ';
S += 'Value: ' + this.Value;
return S;
}
}
class Attributes {
constructor() {
this.__attrs = [];
}
push(Attr) {
this.__attrs.push(Attr);
}
*[Symbol.iterator]() {
for (let Attr of this.__attrs) {
yield Attr;
}
}
toString() {
return 'Attributes';
}
}
class Sub {
constructor(Process) {
this.__process = Process;
}
get SubFoo() {
return 'SubFoo from ' + this.__process.Name;
}
get SubBar() {
return 'SubBar from ' + this.__process.Name;
}
get Attributes() {
let Attrs = new Attributes();
Attrs.push(new Attribute(this.__process, 'attr0', 'value0'));
Attrs.push(new Attribute(this.__process, 'attr1', 'value0'));
return Attrs;
}
toString() {
return 'Sub module';
}
}
class DiaryOfAReverseEngineer {
constructor(Process) {
this.__process = Process;
}
get Foo() {
return 'Foo from ' + this.__process.Name;
}
get Bar() {
return 'Bar from ' + this.__process.Name;
}
Add(a, b) {
return a + b;
}
get Sub() {
return new Sub(this.__process);
}
toString() {
return 'Diary of a reverse-engineer';
}
}
class ProcessModelParent {
get DiaryOfAReverseEngineer() {
return new DiaryOfAReverseEngineer(this);
}
}
function initializeScript() {
return [new host.namedModelParent(
ProcessModelParent,
'Debugger.Models.Process'
)];
}
================================================
FILE: basics/extendmodel_1.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
class ProcessModelParent {
get DiaryOfAReverseEngineer() {
return 'hello from ' + this.Name;
}
}
function initializeScript() {
return [new host.namedModelParent(
ProcessModelParent,
'Debugger.Models.Process'
)];
}
================================================
FILE: basics/extendmodel_2.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
class DiaryOfAReverseEngineer {
constructor(Process) {
this.process = Process;
}
get Foo() {
return 'Foo from ' + this.process.Name;
}
get Bar() {
return 'Bar from ' + this.process.Name;
}
Add(a, b) {
return a + b;
}
}
class ProcessModelParent {
get DiaryOfAReverseEngineer() {
return new DiaryOfAReverseEngineer(this);
}
}
function initializeScript() {
return [new host.namedModelParent(
ProcessModelParent,
'Debugger.Models.Process'
)];
}
================================================
FILE: basics/extendmodel_2_1.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
class DiaryOfAReverseEngineer {
constructor(Process) {
this.__process = process;
}
get Foo() {
return 'Foo from ' + this.__process.Name;
}
get Bar() {
return 'Bar from ' + this.__process.Name;
}
Add(a, b) {
return a + b;
}
toString() {
return 'Diary of a reverse-engineer';
}
}
class ProcessModelParent {
get DiaryOfAReverseEngineer() {
return new DiaryOfAReverseEngineer(this);
}
}
function initializeScript() {
return [new host.namedModelParent(
ProcessModelParent,
'Debugger.Models.Process'
)];
}
================================================
FILE: basics/readmemory.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
let logln = function (e) {
host.diagnostics.debugLog(e + '\n');
}
function read_u64(addr) {
return host.memory.readMemoryValues(addr, 1, 8)[0];
}
function invokeScript() {
let Regs = host.currentThread.Registers.User;
let a = read_u64(Regs.rsp);
logln(a.toString(16));
try {
read_u64(0xdeadbeef);
} catch(e) {
logln(e);
}
let WideStr = host.currentProcess.Environment.EnvironmentBlock.ProcessParameters.ImagePathName.Buffer;
logln(host.memory.readWideString(WideStr));
let WideStrAddress = WideStr.address;
logln(host.memory.readWideString(WideStrAddress));
}
================================================
FILE: codecov/README.md
================================================
# codecov.js
`codecov.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that allows to extract code-coverage out of a [TTD](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview) trace. It generates a text file with every offsets in a module that have been executed during the recording.
The file looks like the below:
```text
; TracePath: C:\work\codes\blazefox\js01.run
; c:\windows\system32\kernelbase.dll, 7fffb4ce0000, 293000
kernelbase.dll+5df40
kernelbase.dll+5df43
kernelbase.dll+5df47
kernelbase.dll+5df4b
kernelbase.dll+5df4f
...
; c:\windows\system32\kernel32.dll, 7fffb6460000, b3000
kernel32.dll+1f3a0
kernel32.dll+21bb0
kernel32.dll+1bb90
kernel32.dll+1a280
kernel32.dll+1a284
kernel32.dll+1e640
kernel32.dll+63a0
```
## Usage
Run `.scriptload codecov.js` to load the script. You can extract code-coverage using `!codecov "foo"`.
## Examples
Extract code-coverage for every module having `kernel` in their name:
```text
0:000> !codecov "kernel"
Looking for *kernel*..
Found 2 hits
Found 7815 unique addresses in C:\WINDOWS\System32\KERNELBASE.dll
Found 1260 unique addresses in C:\WINDOWS\System32\KERNEL32.DLL
Writing C:\work\codes\tmp\js01.run.kernel.text...
Done!
@$codecov("kernel")
0:000> !codecov "kernel"
Looking for *kernel*..
The output file C:\work\codes\tmp\js01.run.kernel.text already exists, exiting.
@$codecov("kernel")
```
================================================
FILE: codecov/codecov.js
================================================
// Axel '0vercl0k' Souchet - 2 Feb 2018
//
// Example:
// 0:000> !codecov "kernel"
// Looking for *kernel*..
// Found 2 hits
// Found 7815 unique addresses in C:\WINDOWS\System32\KERNELBASE.dll
// Found 1260 unique addresses in C:\WINDOWS\System32\KERNEL32.DLL
// Writing C:\work\codes\tmp\js01.run.kernel.text...
// Done!
// @$codecov("kernel")
// 0:000> !codecov "kernel"
// Looking for *kernel*..
// The output file C:\work\codes\tmp\js01.run.kernel.text already exists, exiting.
// @$codecov("kernel")
//
'use strict';
const log = host.diagnostics.debugLog;
const logln = p => host.diagnostics.debugLog(p + '\n');
const hex = p => p.toString(16);
function ExtractModuleName(ModulePath) {
return ModulePath.slice(
ModulePath.lastIndexOf('\\') + 1
);
}
function CodeCoverageModule(Module) {
const CurrentSession = host.currentSession;
const BaseAddress = Module.BaseAddress;
const Size = Module.Size;
const CoverageLines = CurrentSession.TTD.Memory(
BaseAddress,
BaseAddress.add(Size),
'EC'
);
const Offsets = Array.from(CoverageLines).map(
p => hex(
p.Address.subtract(BaseAddress)
)
);
return {
'Path' : Module.Name.toLowerCase(),
'Base' : BaseAddress,
'Size' : Size,
'Offsets' : Offsets
};
}
function CodeCov(ModulePattern) {
const CurrentSession = host.currentSession;
const CurrentProcess = host.currentProcess;
const Utility = host.namespace.Debugger.Utility;
if(!CurrentSession.Attributes.Target.IsTTDTarget) {
logln('!codecov expects a TTD trace');
return;
}
if(ModulePattern == undefined) {
logln('!codecov "pattern"');
return;
}
ModulePattern = ModulePattern.toLowerCase();
logln('Looking for *' + ModulePattern + '*..');
const Modules = CurrentProcess.Modules.Where(
p => p.Name.toLowerCase().indexOf(ModulePattern) != -1
);
if(Modules.Count() == 0) {
logln('Could not find any matching module, exiting');
return;
}
const TracePath = CurrentSession.Attributes.Target.Details.DumpFileName;
const TraceDir = TracePath.slice(
0,
TracePath.lastIndexOf('\\')
);
const TraceName = TracePath.slice(
TracePath.lastIndexOf('\\') + 1
);
const FilePath = TraceDir + '\\' + TraceName + '.' + ModulePattern + '.txt';
if(Utility.FileSystem.FileExists(FilePath)) {
logln('The output file ' + FilePath + ' already exists, exiting.');
return;
}
const Metadata = {
'TracePath' : TracePath
};
const CoverageModules = [];
logln('Found ' + Modules.Count() + ' hits');
for(const Module of Modules) {
const ModuleCoverage = CodeCoverageModule(Module);
logln('Found ' + ModuleCoverage.Offsets.length + ' unique addresses in ' + Module.Name);
CoverageModules.push(ModuleCoverage);
}
logln('Writing ' + FilePath + '...');
const FileHandle = Utility.FileSystem.CreateFile(FilePath, 'CreateAlways');
const Writer = Utility.FileSystem.CreateTextWriter(FileHandle);
for(const [Name, Value] of Object.entries(Metadata)) {
Writer.WriteLine('; ' + Name + ': ' + Value);
}
for(const Module of CoverageModules) {
//
// First write the metadata that is module specific.
//
Writer.WriteLine('; ' + Module.Path + ', ' + hex(Module.Base) + ', ' + hex(Module.Size));
//
// Write down the offsets.
//
const ModuleName = ExtractModuleName(Module.Path);
for(const Offset of Module.Offsets) {
Writer.WriteLine(ModuleName + '+' + hex(Offset));
}
}
FileHandle.Close();
logln('Done!');
}
function initializeScript() {
return [
new host.apiVersionSupport(1, 2),
new host.functionAlias(
CodeCov,
'codecov'
)
];
}
================================================
FILE: gdt/README.md
================================================
# gdt.js
`gdt.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that dumps the [Global Descriptor Table](https://wiki.osdev.org/Global_Descriptor_Table) on 64-bit kernels. I wrote this extension because I always find the output of the `dg` command confusing, if not broken.
## Usage
Run `.scriptload gdt.js` to load the script. You can dump a specific entry by passing the segment selector to the `!gdt` command, or it will dump the entire table if nothing is passed. Run `!wow64exts.sw` if you are running the script while being in the context of a [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details) thread.
## Examples
* Dumping the GDT entry that enables [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details):
```
32.kd> !gdt @cs
dt nt!_KGDTENTRY64 0xfffff8045215dfd0
Base: [0x0 -> 0xffffffff]
Type: Code Execute/Read Accessed (0xb)
DPL: 0x3
Present: 0x1
Mode: 32b Compat
@$gdt(@cs)
32.kd> dg @cs
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0023 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb
```
* Dumping the GDT entry that allows 32-bit code to invoke 64-bit code:
```
32.kd> !gdt 0x33
dt nt!_KGDTENTRY64 0xfffff8045215dfe0
Base: [0x0 -> 0x0]
Type: Code Execute/Read Accessed (0xb)
DPL: 0x3
Present: 0x1
Mode: 64b
@$gdt(0x33)
32.kd> dg 33
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb
```
* Dumping the [Task State Segment](https://wiki.osdev.org/Task_State_Segment):
```
32.kd> !gdt @tr
dt nt!_KGDTENTRY64 0xfffff8045215dff0
Base: [0xfffff8045215c000 -> 0xfffff8045215c067]
Type: TSS64 Busy (0xb)
DPL: 0x0
Present: 0x1
@$gdt(@tr)
32.kd> dg @tr
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- ----------------- ----------------- ---------- - -- -- -- -- --------
0040 00000000`5215c000 00000000`00000067 TSS32 Busy 0 Nb By P Nl 0000008b
```
* Dumping the [Thread Environment Block](https://en.wikipedia.org/wiki/Win32_Thread_Information_Block) of a [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details) thread:
```
32.kd> !gdt @fs
dt nt!_KGDTENTRY64 0xfffff8045215e000
Base: [0x326000 -> 0x329c00]
Type: Data Read/Write Accessed (0x3)
DPL: 0x3
Present: 0x1
@$gdt(@fs)
32.kd> !teb
Wow64 TEB32 at 0000000000326000
ExceptionList: 00000000004ff59c
StackBase: 0000000000500000
StackLimit: 00000000004f2000
SubSystemTib: 0000000000000000
FiberData: 0000000000001e00
ArbitraryUserPointer: 0000000000000000
Self: 0000000000326000
EnvironmentPointer: 0000000000000000
ClientId: 0000000000001ad8 . 0000000000001adc
RpcHandle: 0000000000000000
Tls Storage: 0000000000834188
PEB Address: 0000000000323000
LastErrorValue: 0
LastStatusValue: c000007c
Count Owned Locks: 0
HardErrorMode: 0
```
* Dumping the entire GDT on a Windows 10 64-bit Virtual Machine:
```
32.kd> !gdt
Dumping the GDT from 0xfffff8045215dfb0 to 0xfffff8045215e007..
[0]: dt nt!_KGDTENTRY64 0xfffff8045215dfb0
Base: [0x0 -> 0x0]
Type: Reserved (0x0)
DPL: 0x0
Present: 0x0
[1]: dt nt!_KGDTENTRY64 0xfffff8045215dfb8
Base: [0x0 -> 0x0]
Type: Reserved (0x0)
DPL: 0x0
Present: 0x0
[2]: dt nt!_KGDTENTRY64 0xfffff8045215dfc0
Base: [0x0 -> 0x0]
Type: Code Execute/Read Accessed (0xb)
DPL: 0x0
Present: 0x1
Mode: 64b
[3]: dt nt!_KGDTENTRY64 0xfffff8045215dfc8
Base: [0x0 -> 0x0]
Type: Data Read/Write Accessed (0x3)
DPL: 0x0
Present: 0x1
[4]: dt nt!_KGDTENTRY64 0xfffff8045215dfd0
Base: [0x0 -> 0xffffffff]
Type: Code Execute/Read Accessed (0xb)
DPL: 0x3
Present: 0x1
Mode: 32b Compat
[5]: dt nt!_KGDTENTRY64 0xfffff8045215dfd8
Base: [0x0 -> 0xffffffff]
Type: Data Read/Write Accessed (0x3)
DPL: 0x3
Present: 0x1
[6]: dt nt!_KGDTENTRY64 0xfffff8045215dfe0
Base: [0x0 -> 0x0]
Type: Code Execute/Read Accessed (0xb)
DPL: 0x3
Present: 0x1
Mode: 64b
[7]: dt nt!_KGDTENTRY64 0xfffff8045215dfe8
Base: [0x0 -> 0x0]
Type: Reserved (0x0)
DPL: 0x0
Present: 0x0
[8]: dt nt!_KGDTENTRY64 0xfffff8045215dff0
Base: [0xfffff8045215c000 -> 0xfffff8045215c067]
Type: TSS64 Busy (0xb)
DPL: 0x0
Present: 0x1
[9]: dt nt!_KGDTENTRY64 0xfffff8045215e000
Base: [0x326000 -> 0x329c00]
Type: Data Read/Write Accessed (0x3)
DPL: 0x3
Present: 0x1
@$gdt()
```
================================================
FILE: gdt/gdt.js
================================================
// Axel '0vercl0k' Souchet - October 8 2021
'use strict';
//
// Small reminders from the manual intels on segments in x64:
// - Because ES, DS, and SS segment registers are not used in 64-bit mode, their fields (base, limit, and attribute) in
// segment descriptor registers are ignored.
// - Selector.Index selects one of 8192 descriptors in the GDT or LDT. The processor multiplies
// the index value by 8 (the number of bytes in a segment descriptor) and adds the result to the base
// address of the GDT or LDT (from the GDTR or LDTR register, respectively).
// - The first entry of the GDT is not used by the processor.
// - The hidden descriptor register fields for FS.base and GS.base are physically mapped to MSRs in order to load all
// address bits supported by a 64-bit implementation. Software with CPL = 0 (privileged software) can load all
// supported linear-address bits into FS.base or GS.base using WRMSR.
//
const log = host.diagnostics.debugLog;
const logln = p => log(`${p}\n`);
const hex = p => `0x${p.toString(16)}`;
const GdtSystemEntryTypes = new Map([
[0, 'Reserved'],
[1, 'Reserved'],
[2, 'LDT'],
[3, 'Reserved'],
[4, 'Reserved'],
[5, 'Reserved'],
[6, 'Reserved'],
[7, 'Reserved'],
[8, 'Reserved'],
[9, 'TSS64 Available'],
[10, 'Reserved'],
[11, 'TSS64 Busy'],
[12, 'CallGate64'],
[13, 'Reserved'],
[14, 'InterruptGate64'],
[15, 'TrapGate64'],
]);
const GdtNonSystemEntryTypes = new Map([
[0, 'Data Read-Only'],
[1, 'Data Read-Only Accessed'],
[2, 'Data Read/Write'],
[3, 'Data Read/Write Accessed'],
[4, 'Data Read-Only Expand-Down'],
[5, 'Data Read-Only Expand-Down Accessed'],
[6, 'Data Read/Write Expand-Down'],
[7, 'Data Read/Write Expand-Down Accessed'],
[8, 'Code Execute-Only'],
[9, 'Code Execute-Only Accessed'],
[10, 'Code Execute/Read'],
[11, 'Code Execute/Read Accessed'],
[12, 'Code Execute-Only Conforming'],
[13, 'Code Execute-Only Conforming Accessed'],
[14, 'Code Execute/Read Conforming'],
[15, 'Code Execute/Read Conforming Accessed'],
]);
const GdtEntryTypes = new Map([
[0, GdtSystemEntryTypes],
[1, GdtNonSystemEntryTypes]
]);
class GdtEntry {
constructor(Addr) {
this._Addr = Addr;
const Entry = host.createPointerObject(Addr, 'nt', '_KGDTENTRY64*');
const LimitHigh = Entry.Bits.LimitHigh.bitwiseShiftLeft(16);
const LimitLow = Entry.LimitLow;
this._Limit = LimitHigh.add(LimitLow);
// For whatever reason _KGDTENTRY64 is 5 bits long. The intel manuals describes
// it as 4bits and the 'Descriptor type' bit.
// We grab the lower 4 bits as the type, and the MSB as the 'Descriptor type'.
this._Type = Entry.Bits.Type & 15;
this._NonSystem = (Entry.Bits.Type >> 4) & 1;
this._TypeS = GdtEntryTypes.get(this._NonSystem).get(this._Type);
// Note that system descriptors in IA-32e mode are 16 bytes instead
// of 8 bytes.
this._Size = 8;
if (!this._NonSystem && this._TypeS != 'Reserved') {
this._Size = 16;
}
this._Dpl = Entry.Bits.Dpl;
this._Granularity = Entry.Bits.Granularity;
this._Present = Entry.Bits.Present;
this._LongMode = Entry.Bits.LongMode;
this._DefaultBig = Entry.Bits.DefaultBig;
const BaseUpper = this._Size == 8 ? 0 : Entry.BaseUpper.bitwiseShiftLeft(32);
const BaseHigh = Entry.Bytes.BaseHigh.bitwiseShiftLeft(24);
const BaseMiddle = Entry.Bytes.BaseMiddle.bitwiseShiftLeft(16);
const BaseLow = Entry.BaseLow;
this._Base = BaseUpper.add(BaseHigh).add(BaseMiddle).add(BaseLow);
const Flags1 = Entry.Bytes.Flags1;
const Flags2 = Entry.Bytes.Flags2.bitwiseShiftLeft(8);
this._Attrs = Flags2.add(Flags1);
}
toString() {
const Increments = this._Granularity == 1 ? 1024 * 4 : 1;
// For example, when the granularity flag is set, a limit of 0 results in
// valid offsets from 0 to 4095.
const Size = this._Limit * Increments + (this._Granularity ? 0xfff : 0);
let S = `dt nt!_KGDTENTRY64 ${hex(this._Addr)}
Base: [${hex(this._Base)} -> ${hex(this._Base.add(Size))}]
Type: ${this._TypeS} (${hex(this._Type)})
DPL: ${hex(this._Dpl)}
Present: ${hex(this._Present)}
Atributes: ${hex(this._Attrs)}`;
if (this._TypeS.startsWith('Code')) {
S += `
Mode: ${this._LongMode ? '64b' : (this._DefaultBig ? '32b Compat' : '16b Compat')}`
}
return S;
}
}
function GetGdt() {
const Control = host.namespace.Debugger.Utility.Control;
const [_, GdtrValue] = Control.ExecuteCommand('r @gdtr').First().split('=');
const [__, GdtlValue] = Control.ExecuteCommand('r @gdtl').First().split('=');
return [host.parseInt64(GdtrValue, 16), host.parseInt64(GdtlValue, 16)];
}
function DumpGdtEntry(Addr) {
return new GdtEntry(Addr);
}
function DumpAllGdt() {
const [GdtBase, Gdtl] = GetGdt();
const GdtEnd = GdtBase.add(Gdtl);
logln(`Dumping the GDT from ${hex(GdtBase)} to ${hex(GdtEnd)}..`);
for (let CurrentEntry = GdtBase, Idx = 0;
CurrentEntry.compareTo(GdtEnd) < 0;
Idx++) {
const Entry = DumpGdtEntry(CurrentEntry);
logln(`[${Idx}]: ${Entry}`);
CurrentEntry = CurrentEntry.add(Entry._Size);
}
}
function DumpGdt(Selector) {
const [GdtBase, _] = GetGdt();
const Index = Selector.bitwiseShiftRight(3);
const Offset = Index.multiply(8);
const EntryAddress = GdtBase.add(Offset);
const Entry = DumpGdtEntry(EntryAddress);
logln(Entry);
}
function Gdt(Selector) {
const Attributes = host.currentSession.Attributes;
const IsKernel = Attributes.Target.IsKernelTarget;
//
// XXX: Not sure how to do this better?
// Attributes.Machine.PointerSize is 4 when running in a Wow64 thread :-/.
//
let Is64Bit = true;
try { host.createPointerObject(0, 'nt', '_KGDTENTRY64*'); } catch(e) { Is64Bit = false; }
if (!IsKernel || !Is64Bit) {
logln('The running session is not a kernel session or it is not running a 64-bit OS, so exiting');
return;
}
if (Selector == undefined) {
DumpAllGdt();
} else {
DumpGdt(Selector);
}
}
function initializeScript() {
return [
new host.apiVersionSupport(1, 3),
new host.functionAlias(
Gdt,
'gdt'
),
];
}
================================================
FILE: parse_eh_win64/README.md
================================================
# parse_eh_win64.js
`parse_eh_win64.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that shows examples of how to extending the data-model with exception-handling related information for 64 bits executables.
More background is available in this article: [Debugger data model, Javascript & x64 exception handling](https://doar-e.github.io/blog/2017/12/01/debugger-data-model/).
## Usage
Run `.scriptload parse_eh_win64.js` to load the script. The script extends the `Debugger.Models.Process`, `Debugger.Models.Module` models and also exposes the `!ehhandlers` command.
## Examples
* At the process level, dumping `Function` objects and ordering them by the number of exception-handlers they define:
```text
0:002> dx @$curprocess.Functions.OrderByDescending(p => p.ExceptionHandlers.Count())
@$curprocess.Functions.OrderByDescending(p => p.ExceptionHandlers.Count())
[0x0] : RVA:7fffb64bebf0 -> RVA:7fffb64bf022, 12 exception handlers
[0x1] : RVA:7fffb8bdff80 -> RVA:7fffb8be0b67, 11 exception handlers
[0x2] : RVA:7fffb1df8114 -> RVA:7fffb1df8360, 9 exception handlers
[0x3] : RVA:7fffa0111354 -> RVA:7fffa01115a0, 9 exception handlers
[0x4] : RVA:7fffb2183044 -> RVA:7fffb2183290, 9 exception handlers
[0x5] : RVA:7fffa0d41344 -> RVA:7fffa0d41590, 9 exception handlers
[0x6] : RVA:7fffb6573020 -> RVA:7fffb6573356, 6 exception handlers
[0x7] : RVA:7fffb4c71f94 -> RVA:7fffb4c720b4, 6 exception handlers
[0x8] : RVA:7fffb65e5774 -> RVA:7fffb65e5894, 6 exception handlers
[0x9] : RVA:7fffb660c62c -> RVA:7fffb660cf2e, 6 exception handlers
[0xa] : RVA:7fffb6c6f014 -> RVA:7fffb6c6f134, 6 exception handlers
[0xb] : RVA:7fffb8b9a350 -> RVA:7fffb8b9b39b, 6 exception handlers
[0xc] : RVA:7fffb35168a0 -> RVA:7fffb3516efb, 5 exception handlers
```
* Dumping a `Function` object:
```text
0:002> dx -r1 @$curprocess.Functions[0]
@$curprocess.Functions[0] : RVA:7ff67025a6d0 -> RVA:7ff67025a738, 1 exception handlers
EHHandlerRVA : 0x9b9700
EHHandler : 0x7ff6708f9700
BeginRVA : 0x31a6d0
EndRVA : 0x31a738
Begin : 0x7ff67025a6d0
End : 0x7ff67025a738
ExceptionHandlers : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736}
```
* At the module level, dumping `ExceptionHandler` objects:
```text
0:002> dx @$curprocess.Modules[0].ExceptionHandlers
@$curprocess.Modules[0].ExceptionHandlers : Exception handlers
[0x0] : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736}
[0x1] : __try {7ff6708f80b3 -> 7ff6708f813e} __except(7ff6708f93f2()) {7ff6708f813e}
[0x2] : __try {7ff6708f90fd -> 7ff6708f9202} __except(7ff6708f9425()) {7ff6708f9202}
[0x3] : __try {7ff6708f9236 -> 7ff
```
* Dumping an `ExceptionHandler` object:
```text
0:002> dx @$curprocess.Modules[0].ExceptionHandlers[0]
@$curprocess.Modules[0].ExceptionHandlers[0] : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736}
Begin : 0x7ff67025a6fb
End : 0x7ff67025a712
HandlerAddress : 0x1
JumpTarget : 0x7ff67025a736
IsTryFinally : false
HasFilter : false
```
* Dumping the current call-stack with EH information:
```text
0:002> !ehhandlers
5 stack frames, scanning for handlers...
Frame 1: EHHandler: 7fffb8c1fc90: ntdll!_C_specific_handler:
Except: 7fffb8c5ef1d: ntdll!DbgUiRemoteBreakin+0x4d:
Frame 3: EHHandler: 7fffb8c1fc90: ntdll!_C_specific_handler:
Except: 7fffb8bfa267: ntdll!RtlUserThreadStart+0x37:
Filter: 7fffb8c38021: ntdll!RtlUserThreadStart$filt$0:
@$ehhandlers()
```
================================================
FILE: parse_eh_win64/parse_eh_win64.js
================================================
// Axel '0vercl0k' Souchet - Dec 2017
"use strict";
let log = host.diagnostics.debugLog;
let logln = function (e) {
host.diagnostics.debugLog(e + '\n');
};
function read_u32(addr) {
return host.memory.readMemoryValues(addr, 1, 4)[0];
}
function read_u8(addr) {
return host.memory.readMemoryValues(addr, 1, 1)[0];
}
class ScopeRecord {
// 0:000> dt SCOPE_RECORD
// +0x000 BeginAddress : Uint4B
// +0x004 EndAddress : Uint4B
// +0x008 HandlerAddress : Uint4B
// +0x00c JumpTarget : Uint4B
constructor(ScopeRecordAddress) {
this.Begin = read_u32(ScopeRecordAddress);
this.End = read_u32(ScopeRecordAddress.add(0x4));
this.HandlerAddress = read_u32(ScopeRecordAddress.add(0x8));
this.JumpTarget = read_u32(ScopeRecordAddress.add(0xc));
}
get IsTryFinally() {
return this.JumpTarget.compareTo(0) == 0;
}
get HasFilter() {
return this.IsTryFinally == false &&
this.HandlerAddress.compareTo(1) != 0;
}
InBound(Begin, End) {
return this.Begin.compareTo(Begin) >= 0 &&
this.End.compareTo(End) < 0;
}
toString() {
let S = ' __try {'
S += this.Begin.toString(16) + ' -> ' + this.End.toString(16);
S += '}';
if (this.IsTryFinally == true) {
S += ' __finally {';
S += this.HandlerAddress.toString(16);
} else {
S += ' __except(';
if (this.HasFilter == false) {
S += 'EXCEPTION_EXECUTE_HANDLER';
} else {
S += this.HandlerAddress.toString(16) + '()';
}
S += ') {';
S += this.JumpTarget.toString(16);
}
S += '}';
return S;
}
}
class Function {
constructor(BaseAddress, EHHandler, Begin, End) {
this.__BaseAddress = BaseAddress;
this.EHHandlerRVA = EHHandler;
this.EHHandler = BaseAddress.add(EHHandler);
this.BeginRVA = Begin;
this.EndRVA = End;
this.Begin = BaseAddress.add(Begin);
this.End = BaseAddress.add(End);
this.ExceptionHandlers = [];
}
SetRecords(Records, KeepRVA = false) {
this.ExceptionHandlers = Records;
if (KeepRVA) {
return;
}
for (let ExceptionHandler of this.ExceptionHandlers) {
ExceptionHandler.Begin = ExceptionHandler.Begin.add(this.__BaseAddress);
ExceptionHandler.End = ExceptionHandler.End.add(this.__BaseAddress);
if (ExceptionHandler.JumpTarget.compareTo(0) != 0) {
ExceptionHandler.JumpTarget = ExceptionHandler.JumpTarget.add(this.__BaseAddress);
}
if (ExceptionHandler.HandlerAddress.compareTo(1) != 0) {
ExceptionHandler.HandlerAddress = ExceptionHandler.HandlerAddress.add(this.__BaseAddress);
}
}
}
toString() {
let S = 'RVA:' + this.Begin.toString(16) + ' -> RVA:' + this.End.toString(16);
S += ', ' + this.ExceptionHandlers.length + ' exception handlers';
return S;
}
}
function ParseCSpecificHandlerDatas(
ScopeCount,
ScopeRecords,
Function
) {
// 0:000> ?? sizeof(SCOPE_RECORD)
// unsigned int64 0x10
let Records = [];
let ScopeSize = ScopeCount.multiply(0x10);
for (let i = 0; i < ScopeSize; i += 0x10) {
let CurrentScope = ScopeRecords.add(i);
let Record = new ScopeRecord(CurrentScope);
if (Record.InBound(Function.BeginRVA, Function.EndRVA) == false) {
return [];
}
Records.push(Record);
}
return Records;
}
function ExtractExceptionHandlersForModule(
BaseAddress,
KeepRVA = false
) {
let EHANDLER = 1;
let IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3;
// 0:000> dt _IMAGE_DOS_HEADER e_lfanew
// +0x03c e_lfanew : Int4B
let NtHeaders = BaseAddress.add(read_u32(BaseAddress.add(0x3c)));
// 0:000> dt _IMAGE_NT_HEADERS64 OptionalHeader
// +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64
// 0:000> dt _IMAGE_OPTIONAL_HEADER64 DataDirectory
// +0x070 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
// 0:000> dt _IMAGE_DATA_DIRECTORY
// +0x000 VirtualAddress : Uint4B
// +0x004 Size : Uint4B
let EntryExceptionDirectory = NtHeaders.add(0x18 + 0x70 + (IMAGE_DIRECTORY_ENTRY_EXCEPTION * 8));
let RuntimeFunctionEntry = BaseAddress.add(read_u32(EntryExceptionDirectory));
let SizeOfDirectory = read_u32(EntryExceptionDirectory.add(4));
let Functions = [];
for (let i = 0; i < SizeOfDirectory; i += 0xC) {
// 0:000> dt _IMAGE_RUNTIME_FUNCTION_ENTRY
// +0x000 BeginAddress : Uint4B
// +0x004 EndAddress : Uint4B
// +0x008 UnwindInfoAddress : Uint4B
// +0x008 UnwindData : Uint4B
// 0:000> ?? sizeof(_IMAGE_RUNTIME_FUNCTION_ENTRY)
// unsigned int64 0xc
let CurrentEntry = RuntimeFunctionEntry.add(i);
let BeginAddress = read_u32(CurrentEntry);
let EndAddress = read_u32(CurrentEntry.add(4));
if (BeginAddress.compareTo(0) == 0 || EndAddress.compareTo(0) == 0) {
continue;
}
// 0:000> dt UNWIND_INFO
// +0x000 Version : Pos 0, 3 Bits
// +0x000 Flags : Pos 3, 5 Bits
// +0x001 SizeOfProlog : UChar
// +0x002 CountOfCodes : UChar
// +0x003 FrameRegister : Pos 0, 4 Bits
// +0x003 FrameOffset : Pos 4, 4 Bits
// +0x004 UnwindCode : [1] UNWIND_CODE
let UnwindInfo = BaseAddress.add(read_u32(CurrentEntry.add(8)));
let UnwindInfoFlags = read_u8(UnwindInfo).bitwiseShiftRight(3);
if (UnwindInfoFlags.bitwiseAnd(EHANDLER).compareTo(0) == 0) {
continue;
}
// 0:000> ?? sizeof(UNWIND_CODE)
// unsigned int64 2
let CountOfCodes = read_u8(UnwindInfo.add(2));
// For alignment purposes, this array will always have an even number of entries,
// with the final entry potentially unused (in which case the array will be one
// longer than indicated by the count of unwind codes field).
let AlignedCountOfCodes = (CountOfCodes + 1) & ~1;
// 0:000> dt UNWIND_INFO_END
// +0x000 ExceptionHandler : Uint4B
// +0x004 ExceptionData : Uint4B
let UnwindInfoEnd = UnwindInfo.add(4 + (AlignedCountOfCodes * 2));
let ExceptionHandler = read_u32(UnwindInfoEnd);
// 0:000> dt SEH_SCOPE_TABLE
// +0x000 Count : Uint4B
// +0x004 ScopeRecord : [1] SCOPE_RECORD
let ScopeTable = UnwindInfoEnd.add(4);
let ScopeCount = read_u32(ScopeTable);
if (ScopeCount == 0) {
continue;
}
let Records = ScopeTable.add(4);
let CurrentFunction = new Function(
BaseAddress, ExceptionHandler,
BeginAddress, EndAddress
);
Records = ParseCSpecificHandlerDatas(ScopeCount, Records, CurrentFunction);
if (Records.length == 0) {
continue;
}
CurrentFunction.SetRecords(Records, KeepRVA);
Functions.push(CurrentFunction);
}
return Functions;
}
class ModelExceptionHandlers {
constructor(Type, Inst) {
this.__module = null;
this.__process = null;
if (Type == 'Module') {
this.__module = Inst;
} else {
this.__process = Inst;
}
this.__handlers = null;
// We do not parse the exception handlers information here
// as this constructor is called everytime you do:
// dx @$curprocess
// so we will only parse the information when the user asked for it.
}
__ExceptionHandlers(Modules) {
let Handlers = [];
for (let Module of Modules) {
let Functions = ExtractExceptionHandlersForModule(Module.BaseAddress);
for (let Function of Functions) {
Handlers = Handlers.concat(Function.ExceptionHandlers);
}
}
return Handlers;
}
*[Symbol.iterator]() {
if (this.__handlers == null) {
// Only parse the infornmation once everytine we query it.
let Modules = [this.__module];
if (this.__process != null) {
Modules = this.__process.Modules;
}
this.__handlers = this.__ExceptionHandlers(Modules);
}
for (let Handler of this.__handlers) {
yield Handler;
}
}
toString() {
return 'Exception handlers';
}
}
class ModelFunctions {
constructor(Type, Inst) {
this.__module = null;
this.__process = null;
if (Type == 'Module') {
this.__module = Inst;
} else {
this.__process = Inst;
}
this.__functions = null;
}
__Functions(Modules) {
let Functions = [];
for (let Module of Modules) {
let CurrentFunctions = ExtractExceptionHandlersForModule(Module.BaseAddress);
Functions = Functions.concat(CurrentFunctions);
}
return Functions;
}
*[Symbol.iterator]() {
if (this.__functions == null) {
this.__functions = [];
let Modules = [this.__module];
if (this.__process != null) {
Modules = this.__process.Modules;
}
this.__functions = this.__Functions(Modules);
}
for (let Function of this.__functions) {
yield Function;
}
}
toString() {
return 'Functions';
}
}
class __ProcessModelExtension {
get ExceptionHandlers() {
return new ModelExceptionHandlers('Process', this);
}
get Functions() {
return new ModelFunctions('Process', this);
}
}
class __ModuleModelExtension {
get ExceptionHandlers() {
return new ModelExceptionHandlers('Module', this);
}
get Functions() {
return new ModelFunctions('Module', this);
}
}
function BangEHHandlers() {
let Control = host.namespace.Debugger.Utility.Control;
let CurrentThread = host.currentThread;
let CurrentProcess = host.currentProcess;
let Registers = CurrentThread.Registers.User;
let ReturnAddresses = [Registers.rip];
let Frames = CurrentThread.Stack.Frames;
for (let Frame of Frames) {
ReturnAddresses.push(Frame.Attributes.ReturnOffset);
}
logln(ReturnAddresses.length + ' stack frames, scanning for handlers...');
let Functions = Array.from(CurrentProcess.Functions);
for (let Entry of ReturnAddresses.entries()) {
let FrameNumber = host.Int64(Entry[0]);
let ReturnAddress = Entry[1];
let Func = Functions.find(
c => ReturnAddress.compareTo(c.Begin) >= 0 &&
ReturnAddress.compareTo(c.End) < 0
);
if (Func == undefined) {
continue;
}
let ExceptionHandlers = Array.from(Func.ExceptionHandlers);
let ExceptionHandler = ExceptionHandlers.find(
c => ReturnAddress.compareTo(c.Begin) >= 0 &&
ReturnAddress.compareTo(c.End) < 0
)
if (ExceptionHandler == undefined) {
continue;
}
let Filter = undefined;
let EHHandler = Func.EHHandler;
let Handler = ExceptionHandler.HandlerAddress;
let Name = 'Finally';
if (ExceptionHandler.IsTryFinally == false) {
if (ExceptionHandler.HasFilter) {
Filter = ExceptionHandler.HandlerAddress;
}
Handler = ExceptionHandler.JumpTarget;
Name = ' Except';
}
let FormatAddress = function (Handler) {
let S = Handler.toString(16) + ': ';
S += Control.ExecuteCommand(
'u ' + Handler.toString(16) + ' l1'
).First();
return S;
}
logln('Frame ' + FrameNumber.toString(16) + ': EHHandler: ' + FormatAddress(EHHandler));
logln(' ' + Name + ': ' + FormatAddress(Handler));
if (Filter != undefined) {
logln(' Filter: ' + FormatAddress(Filter));
}
}
}
function initializeScript() {
return [
new host.namedModelParent(
__ProcessModelExtension,
'Debugger.Models.Process'
),
new host.namedModelParent(
__ModuleModelExtension,
'Debugger.Models.Module'
),
new host.functionAlias(
BangEHHandlers,
'ehhandlers'
)
];
}
================================================
FILE: policybuffer/README.md
================================================
# policybuffer.js
`policybuffer.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that disassembles Policy buffer programs used by the Chromium sandbox. Those programs are used to evaluate policy decision.
## Usage
Run `.scriptload policybuffer.js` to load the script. You can invoke the disassembler feature with `!disasspolicy `.
## Examples
* Dumping the File System policy of Firefox:
```text
0:017> !disasspolicy 0x00000131`d2142208
!OP_NUMBER_AND_MATCH
!OP_WSTRING_MATCH
OP_ACTION
!OP_NUMBER_AND_MATCH
OP_WSTRING_MATCH
OP_ACTION
OP_WSTRING_MATCH
OP_ACTION
!OP_NUMBER_AND_MATCH
OP_NUMBER_MATCH
OP_WSTRING_MATCH
OP_ACTION
!OP_NUMBER_AND_MATCH
OP_NUMBER_MATCH
OP_WSTRING_MATCH
OP_ACTION
!OP_NUMBER_AND_MATCH
OP_NUMBER_MATCH
OP_WSTRING_MATCH
OP_ACTION
!OP_NUMBER_AND_MATCH
OP_NUMBER_MATCH
OP_WSTRING_MATCH
OP_ACTION
!OP_NUMBER_AND_MATCH
OP_NUMBER_MATCH
OP_WSTRING_MATCH
OP_ACTION
!OP_NUMBER_AND_MATCH
OP_NUMBER_MATCH
OP_WSTRING_MATCH
OP_ACTION
OP_WSTRING_MATCH
OP_ACTION
OP_WSTRING_MATCH
OP_ACTION
@$disasspolicy(0x00000131`d2142208)
```
================================================
FILE: policybuffer/policybuffer.js
================================================
// Axel '0vercl0k' Souchet - 5 March 2019
// sandbox::PolicyBase::EvalPolicy
'use strict';
//
// Utility functions.
//
const Log = host.diagnostics.debugLog;
const Logln = p => host.diagnostics.debugLog(p + '\n');
const Hex = p => '0x' + p.toString(16);
const ReadWstring = p => host.memory.readWideString(p);
function ReadShort(Address) {
let Value = null;
try {
Value = host.memory.readMemoryValues(
Address, 1, 2
)[0];
} catch(e) {
}
return Value;
}
function ReadDword(Address) {
let Value = null;
try {
Value = host.memory.readMemoryValues(
Address, 1, 4
)[0];
} catch(e) {
}
return Value;
}
function ReadQword(Address) {
let Value = null;
try {
Value = host.memory.readMemoryValues(
Address, 1, 8
)[0];
} catch(e) {
}
return Value;
}
function initializeScript() {
return [
new host.apiVersionSupport(1, 3)
];
}
//
// Constants.
//
// The low-level policy is implemented using the concept of policy 'opcodes'.
// An opcode is a structure that contains enough information to perform one
// comparison against one single input parameter. For example, an opcode can
// encode just one of the following comparison:
//
// - Is input parameter 3 not equal to NULL?
// - Does input parameter 2 start with L"c:\\"?
// - Is input parameter 5, bit 3 is equal 1?
//
// Each opcode is in fact equivalent to a function invocation where all
// the parameters are known by the opcode except one. So say you have a
// function of this form:
// bool fn(a, b, c, d) with 4 arguments
//
// Then an opcode is:
// op(fn, b, c, d)
// Which stores the function to call and its 3 last arguments
//
// Then and opcode evaluation is:
// op.eval(a) ------------------------> fn(a,b,c,d)
// internally calls
//
// The idea is that complex policy rules can be split into streams of
// opcodes which are evaluated in sequence. The evaluation is done in
// groups of opcodes that have N comparison opcodes plus 1 action opcode:
//
// [comparison 1][comparison 2]...[comparison N][action][comparison 1]...
// ----- evaluation order----------->
//
// Each opcode group encodes one high-level policy rule. The rule applies
// only if all the conditions on the group evaluate to true. The action
// opcode contains the policy outcome for that particular rule.
// https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h#77
// The following are the implemented opcodes.
// enum OpcodeID {
// OP_ALWAYS_FALSE, // Evaluates to false (EVAL_FALSE).
// OP_ALWAYS_TRUE, // Evaluates to true (EVAL_TRUE).
// OP_NUMBER_MATCH, // Match a 32-bit integer as n == a.
// OP_NUMBER_MATCH_RANGE, // Match a 32-bit integer as a <= n <= b.
// OP_NUMBER_AND_MATCH, // Match using bitwise AND; as in: n & a != 0.
// OP_WSTRING_MATCH, // Match a string for equality.
// OP_ACTION // Evaluates to an action opcode.
// };
const OP_ALWAYS_FALSE = 0;
const OP_ALWAYS_TRUE = 1;
const OP_NUMBER_MATCH = 2;
const OP_NUMBER_MATCH_RANGE = 3;
const OP_NUMBER_AND_MATCH = 4;
const OP_WSTRING_MATCH = 5;
const OP_ACTION = 6;
const Opcodes = {
[OP_ALWAYS_FALSE] : 'OP_ALWAYS_FALSE',
[OP_ALWAYS_TRUE] : 'OP_ALWAYS_TRUE',
[OP_NUMBER_MATCH] : 'OP_NUMBER_MATCH',
[OP_NUMBER_MATCH_RANGE] : 'OP_NUMBER_MATCH_RANGE',
[OP_NUMBER_AND_MATCH] : 'OP_NUMBER_AND_MATCH',
[OP_WSTRING_MATCH] : 'OP_WSTRING_MATCH',
[OP_ACTION] : 'OP_ACTION'
};
// https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h
// enum StringMatchOptions {
// CASE_SENSITIVE = 0, // Pay or Not attention to the case as defined by
// CASE_INSENSITIVE = 1, // RtlCompareUnicodeString windows API.
// EXACT_LENGHT = 2 // Don't do substring match. Do full string match.
// };
const MatchingOptions = {
0 : 'CASE_SENSITIVE',
1 : 'CASE_INSENSITIVE',
2 : 'EXACT_LENGTH',
3 : 'EXACT_LENGTH | CASE_INSENSITIVE'
};
// These are the possible policy outcomes. Note that some of them might
// not apply and can be removed. Also note that The following values only
// specify what to do, not how to do it and it is acceptable given specific
// cases to ignore the policy outcome.
// enum EvalResult {
// // Comparison opcode values:
// EVAL_TRUE, // Opcode condition evaluated true.
// EVAL_FALSE, // Opcode condition evaluated false.
// EVAL_ERROR, // Opcode condition generated an error while evaluating.
// // Action opcode values:
// ASK_BROKER, // The target must generate an IPC to the broker. On the broker
// // side, this means grant access to the resource.
// DENY_ACCESS, // No access granted to the resource.
// GIVE_READONLY, // Give readonly access to the resource.
// GIVE_ALLACCESS, // Give full access to the resource.
// GIVE_CACHED, // IPC is not required. Target can return a cached handle.
// GIVE_FIRST, // TODO(cpu)
// SIGNAL_ALARM, // Unusual activity. Generate an alarm.
// FAKE_SUCCESS, // Do not call original function. Just return 'success'.
// FAKE_ACCESS_DENIED, // Do not call original function. Just return 'denied'
// // and do not do IPC.
// TERMINATE_PROCESS, // Destroy target process. Do IPC as well.
// };
const Actions = {
3 : 'ASK_BROKER',
4 : 'DENY_ACCESS',
5 : 'GIVE_READONLY',
6 : 'GIVE_ALLACCESS',
7 : 'GIVE_CACHED',
8 : 'GIVE_FIRST',
9 : 'SIGNAL_ALARM',
10 : 'FAKE_SUCCESS',
11 : 'FACE_ACCESS_DENIED',
12 : 'TERMINATE_PROCESS'
};
// https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/internal_types.h#19
// enum ArgType {
// INVALID_TYPE = 0,
// WCHAR_TYPE,
// UINT32_TYPE,
// UNISTR_TYPE,
// VOIDPTR_TYPE,
// INPTR_TYPE,
// INOUTPTR_TYPE,
// LAST_TYPE
// };
const ArgTypes = {
0 : 'INVALID_TYPE',
1 : 'WCHAR_TYPE',
2 : 'UINT32_TYPE',
3 : 'UNISTR_TYPE',
4 : 'VOIDPTR_TYPE',
5 : 'INPTR_TYPE',
6 : 'INOUTPTR_TYPE',
7 : 'LAST_TYPE'
};
// Options that apply to every opcode. They are specified when creating
// each opcode using OpcodeFactory::MakeOpXXXXX() family of functions
// Do nothing special.
const kPolNone = 0;
// Convert EVAL_TRUE into EVAL_FALSE and vice-versa. This allows to express
// negated conditions such as if ( a && !b).
const kPolNegateEval = 1;
// Zero the MatchContext context structure. This happens after the opcode
// is evaluated.
const kPolClearContext = 2;
// Use OR when evaluating this set of opcodes. The policy evaluator by default
// uses AND when evaluating. Very helpful when
// used with kPolNegateEval. For example if you have a condition best expressed
// as if(! (a && b && c)), the use of this flags allows it to be expressed as
// if ((!a) || (!b) || (!c)).
const kPolUseOREval = 4;
// https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_params.h#36
const ParameterNames = {
'OpenFile' : [
'NAME', 'BROKER', 'ACCESS', 'DISPOSITION', 'OPTIONS'
],
};
//
// Code.
//
function DisassPolicyBuffer(PolicyBufferAddress, PolicyType) {
let Ptr = PolicyBufferAddress;
const PolicyBufferOpcodeCount = ReadQword(Ptr);
Ptr += 8;
for(let Idx = 0; Idx < PolicyBufferOpcodeCount; ++Idx) {
//
// Save off the current pointer as it is useful to compute
// where the stored string is in memory for the OP_WSTRING_MATCH
// opcode.
//
const OpcodePtr = Ptr;
//
// Unpack the opcode structure.
//
const OpcodeId = ReadDword(Ptr);
Ptr += 4;
const SelectedParameter = ReadShort(Ptr);
Ptr += 2;
const Options = ReadShort(Ptr);
Ptr += 2;
const Parameters = [];
for(let InnerIdx = 0; InnerIdx < 4; ++InnerIdx) {
Parameters.push(ReadQword(Ptr));
Ptr += 8;
}
//
// Once we dumped the opcode, let's prettify its parameters.
//
const Operands = [];
let FirstOperand = 'Param' + SelectedParameter;
if(ParameterNames[PolicyType] != undefined) {
FirstOperand = PolicyType + '::' + ParameterNames[PolicyType][SelectedParameter];
}
Operands.push(FirstOperand);
if(OpcodeId == OP_ALWAYS_TRUE || OpcodeId == OP_ALWAYS_FALSE) {
} else if(OpcodeId == OP_NUMBER_MATCH) {
const ArgType = ArgTypes[Parameters[1].asNumber()];
Operands.push(ArgType + '(' + Hex(Parameters[0]) + ')');
} else if(OpcodeId == OP_NUMBER_MATCH_RANGE) {
Operands.push('LowerBound(' + Hex(Parameters[0]) + ')');
Operands.push('UpperBound(' + Hex(Parameters[1]) + ')');
} else if(OpcodeId == OP_NUMBER_AND_MATCH) {
Operands.push(Hex(Parameters[0]));
}else if(OpcodeId == OP_WSTRING_MATCH) {
const Displacement = Parameters[0];
const StringAddress = OpcodePtr.add(Displacement);
Operands.push('"' + ReadWstring(StringAddress) + '"');
Operands.push('Length(' + Hex(Parameters[1]) + ')');
Operands.push('Offset(' + Hex(Parameters[2]) + ')');
const MatchingOption = Parameters[3].asNumber();
Operands.push(MatchingOptions[MatchingOption]);
} else if(OpcodeId == OP_ACTION) {
//
// The OP_ACTION is the only opcode that does not need a selected
// parameter.
//
const Action = Actions[Parameters[0].asNumber()];
Operands[0] = Action;
}
//
// Display the opcode and its operands.
//
const OpcodeIdStr = Opcodes[OpcodeId];
if(Options.bitwiseAnd(kPolNegateEval).compareTo(0) != 0) {
Logln('!' + OpcodeIdStr + '<' + Operands.join(', ') + '>');
} else {
Logln(OpcodeIdStr + '<' + Operands.join(', ') + '>');
}
if(OpcodeId == OP_ACTION) {
Logln('');
}
}
}
function initializeScript() {
return [
new host.apiVersionSupport(1, 3),
new host.functionAlias(
DisassPolicyBuffer,
'disasspolicy'
)
];
}
================================================
FILE: sm/README.md
================================================
# sm.js
`sm.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that allows to dump both `js::Value` and `JSObject` [Spidermonkey](https://github.com/mozilla/gecko-dev/tree/master/js) objects. It works on crash-dumps, live debugging, and TTD traces.
The extension detects automatically if it is running from the [Javascript shell](https://github.com/mozilla/gecko-dev/tree/master/js/src/shell) (in which case `js.exe` is the module hosting the JavaScript engine code) or from Firefox directly (in which case `xul.dll` is the module hosting the JavaScript engine code). Private symbol information for the module hosting the JavaScript engine code is required. It only supports x64 version of spidermonkey.
It has been used and tested against spidermonkey during end of 2018 - but should work fine on newer versions assuming core data-structures haven't changed a whole lot (and if they do, it should be fairly easy to adapt anyway).
## Usage
Run `.scriptload sm.js` to load the script. You can dump `js::Value` with `!smdump_jsvalue` and `JSObject` with `!smdump_jsobject`. You can insert a software breakpoint in a JIT buffer with `!ion_insertbp` and `!in_nursery` to figure out if an object lives inside the Nursery heap.
## Examples
* Dumping the `js::Value` associated to the following JavaScript object `['short', 13.37, new Map([[ 1, 'one' ],[ 2, 'two' ]]), ['loooooooooooooooooooooooooooooong', [0x1337, {doare:'in d4 place'}]], false, null, undefined, true, Math.atan2, Math]`:
```text
0:000> !smdump_jsvalue vp[2].asBits_
1e5f10024c0: js!js::ArrayObject: Length: 10
1e5f10024c0: js!js::ArrayObject: Capacity: 10
1e5f10024c0: js!js::ArrayObject: Content: ['short', 13.37, new Map(...), ['loooooooooooooooooooooooooooooong', [0x1337, {'doare' : 'in d4 place'}]], false, null, undefined, true, atan2(), Math]
@$smdump_jsvalue(vp[2].asBits_)
```
* Setting a breakpoint in code being JIT'd by IonMonkey:
```text
0:008> g
Breakpoint 0 hit
js!js::jit::CodeGenerator::visitBoundsCheck:
00007ff7`87d9e1a0 4156 push r14
0:000> !ion_insertbp
unsigned char 0xcc ''
unsigned int64 0x5b
@$ion_insertbp()
0:000> g
(1a60.f58): Break instruction exception - code 80000003 (first chance)
000003d9`ca67991b cc int 3
0:000> u . l2
000003d9`ca67991b cc int 3
000003d9`ca67991c 3bd8 cmp ebx,eax
```
* Figure out if an object lives in the Nursery heap:
```text
0:008> !in_nursery 0x19767e00df8
Using previously cached JSContext @0x000001fe17318000
0x000001fe1731cde8: js::Nursery
ChunkCountLimit: 0x0000000000000010 (16 MB)
Capacity: 0x0000000000fffe80 bytes
CurrentChunk: 0x0000019767e00000
Position: 0x0000019767e00eb0
Chunks:
00: [0x0000019767e00000 - 0x0000019767efffff]
01: [0x00001fa2aee00000 - 0x00001fa2aeefffff]
02: [0x0000115905000000 - 0x00001159050fffff]
03: [0x00002fc505200000 - 0x00002fc5052fffff]
04: [0x000020d078700000 - 0x000020d0787fffff]
05: [0x0000238217200000 - 0x00002382172fffff]
06: [0x00003ff041f00000 - 0x00003ff041ffffff]
07: [0x00001a5458700000 - 0x00001a54587fffff]
-------
0x19767e00df8 has been found in the js::NurseryChunk @0x19767e00000!
0:008> !in_nursery 0x00001fe174be810
Using previously cached JSContext @0x000001fe17318000
0x000001fe1731cde8: js::Nursery
ChunkCountLimit: 0x0000000000000010 (16 MB)
Capacity: 0x0000000000fffe80 bytes
CurrentChunk: 0x0000019767e00000
Position: 0x0000019767e00eb0
Chunks:
00: [0x0000019767e00000 - 0x0000019767efffff]
01: [0x00001fa2aee00000 - 0x00001fa2aeefffff]
02: [0x0000115905000000 - 0x00001159050fffff]
03: [0x00002fc505200000 - 0x00002fc5052fffff]
04: [0x000020d078700000 - 0x000020d0787fffff]
05: [0x0000238217200000 - 0x00002382172fffff]
06: [0x00003ff041f00000 - 0x00003ff041ffffff]
07: [0x00001a5458700000 - 0x00001a54587fffff]
-------
0x1fe174be810 hasn't been found be in any Nursery js::NurseryChunk.
```
================================================
FILE: sm/sm.js
================================================
// Axel '0vercl0k' Souchet - 24-June-2018
//
// Example:
// * from the interpreter:
// Math.atan2(['short', 13.37, new Map([[ 1, 'one' ],[ 2, 'two' ]]), ['loooooooooooooooooooooooooooooong', [0x1337, {doare:'in d4 place'}]], false, null, undefined, true, Math.atan2, Math])
//
// * from the debugger:
// js!js::math_atan2:
// 00007ff6`0227e140 56 push rsi
// 0:000> !smdump_jsvalue vp[2].asBits_
// 1e5f10024c0: js!js::ArrayObject: Length: 10
// 1e5f10024c0: js!js::ArrayObject: Capacity: 10
// 1e5f10024c0: js!js::ArrayObject: Content: ['short', 13.37, new Map(...), ['loooooooooooooooooooooooooooooong', [0x1337, {'doare' : 'in d4 place'}]], false, null, undefined, true, atan2(), Math]
// @$smdump_jsvalue(vp[2].asBits_)
//
'use strict';
let Module = null;
const logln = p => host.diagnostics.debugLog(p + '\n');
const hex = p => '0x' + p.toString(16).padStart(16, '0');
const JSVAL_TAG_SHIFT = host.Int64(47);
const JSVAL_PAYLOAD_MASK = host.Int64(1).bitwiseShiftLeft(JSVAL_TAG_SHIFT).subtract(1);
const CLASS_NON_NATIVE = host.Int64(0x40000);
const FLAG_DELEGATE = host.Int64(8);
const JSVAL_TYPE_DOUBLE = host.Int64(0x1fff0);
const JSVAL_TYPE_INT32 = host.Int64(0x1fff1);
const JSVAL_TYPE_BOOLEAN = host.Int64(0x1fff2);
const JSVAL_TYPE_UNDEFINED = host.Int64(0x1fff3);
const JSVAL_TYPE_NULL = host.Int64(0x1fff4);
const JSVAL_TYPE_MAGIC = host.Int64(0x1fff5);
const JSVAL_TYPE_STRING = host.Int64(0x1fff6);
const JSVAL_TYPE_SYMBOL = host.Int64(0x1fff7);
const JSVAL_TYPE_OBJECT = host.Int64(0x1fffc);
const INLINE_CHARS_BIT = host.Int64(1 << 6);
const LATIN1_CHARS_BIT = host.Int64(1 << 9);
const JSID_TYPE_MASK = host.Int64(0x7);
const JSID_TYPE_STRING = host.Int64(0x0);
const JSID_TYPE_INT = host.Int64(0x1);
const JSID_TYPE_VOID = host.Int64(0x2);
const JSID_TYPE_SYMBOL = host.Int64(0x4);
const SLOT_MASK = host.Int64(0xffffff);
const FIXED_SLOTS_SHIFT = host.Int64(24);
const FunctionConstants = {
0x0001 : 'INTERPRETED',
0x0004 : 'EXTENDED',
0x0008 : 'BOUND_FUN',
0x0010 : 'WASM_OPTIMIZED',
0x0020 : 'HAS_GUESSED_ATOM/HAS_BOUND_FUNCTION_NAME_PREFIX',
0x0040 : 'LAMBDA',
0x0080 : 'SELF_HOSTED',
0x0100 : 'HAS_INFERRED_NAME',
0x0200 : 'INTERPRETED_LAZY',
0x0400 : 'RESOLVED_LENGTH',
0x0800 : 'RESOLVED_NAME',
};
const FunctionKindConstants = {
0 : 'NORMAL_KIND',
1 : 'ARROW_KIND',
2 : 'METHOD_KIND',
3 : 'CLASSCONSTRUCTOR_KIND',
4 : 'GETTER_KIND',
5 : 'SETTER_KIND',
6 : 'ASMJS_KIND'
};
const Tag2Names = {
[JSVAL_TYPE_DOUBLE] : 'Double',
[JSVAL_TYPE_INT32] : 'Int32',
[JSVAL_TYPE_STRING] : 'String',
[JSVAL_TYPE_UNDEFINED] : 'Undefined',
[JSVAL_TYPE_BOOLEAN] : 'Boolean',
[JSVAL_TYPE_NULL] : 'Null',
[JSVAL_TYPE_OBJECT] : 'Object',
[JSVAL_TYPE_SYMBOL] : 'Symbol',
[JSVAL_TYPE_MAGIC] : 'Magic',
};
//
// Read a uint64_t integer from Addr.
//
function read_u64(Addr) {
return host.memory.readMemoryValues(Addr, 1, 8)[0];
}
//
// Mirror the functionality of ::fromElements.
//
function heapslot_to_objectelements(Addr) {
// static ObjectElements* fromElements(HeapSlot* elems) {
// return reinterpret_cast(uintptr_t(elems) - sizeof(ObjectElements));
// }
const ObjectElementsSize = host.getModuleType(Module, 'js::ObjectElements').size;
const ObjectElements = host.createPointerObject(
Addr.subtract(ObjectElementsSize),
Module,
'js::ObjectElements*'
);
return ObjectElements;
}
//
// Is Byte printable?
//
function printable(Byte) {
return Byte >= 0x20 && Byte <= 0x7e;
}
//
// Return a string describing Byte; either a \x41 or its ascii representation
//
function byte_to_str(Byte) {
if(printable(Byte)) {
return String.fromCharCode(Byte);
}
return '\\x' + Byte.toString(16).padStart(2, '0');
}
//
// Is this jsid an integer?
//
function jsid_is_int(Propid) {
const Bits = Propid.value.asBits;
return Bits.bitwiseAnd(JSID_TYPE_MASK).compareTo(JSID_TYPE_INT) == 0;
}
//
// Is this jsid a string?
//
function jsid_is_string(Propid) {
const Bits = Propid.value.asBits;
return Bits.bitwiseAnd(JSID_TYPE_MASK).compareTo(JSID_TYPE_STRING) == 0;
}
//
// Retrieve a property from a Shape; returns an actual integer/string based
// on the propid_.
//
function get_property_from_shape(Shape) {
// XXX: expose a smdump_jsid
const Propid = Shape.propid_;
if(jsid_is_int(Propid)) {
return Propid.value.asBits.bitwiseShiftRight(1);
}
if(jsid_is_string(Propid)) {
return new __JSString(Propid.value.asBits);
}
// XXX: todo
}
function jsvalue_to_instance(Addr) {
const JSValue = new __JSValue(Addr);
if(!Tag2Names.hasOwnProperty(JSValue.Tag)) {
return 'Dunno';
}
const Name = Tag2Names[JSValue.Tag];
const Type = Names2Types[Name];
return new Type(JSValue.Payload);
}
class __JSMagic {
constructor(Addr) {
this._Addr = Addr;
}
toString() {
return 'magic';
}
Logger(Content) {
logln(this._Addr.toString(16) + ': JSVAL_TYPE_MAGIC: ' + Content);
}
Display() {
this.Logger(this);
}
}
class __JSArgument {
// * ArgumentsObject instances use the following reserved slots:
// *
// * INITIAL_LENGTH_SLOT
// * Stores the initial value of arguments.length, plus a bit indicating
// * whether arguments.length and/or arguments[@@iterator] have been
// * modified. Use initialLength(), hasOverriddenLength(), and
// * hasOverriddenIterator() to access these values. If arguments.length has
// * been modified, then the current value of arguments.length is stored in
// * another slot associated with a new property.
// * DATA_SLOT
// * Stores an ArgumentsData*, described above.
// * MAYBE_CALL_SLOT
// * Stores the CallObject, if the callee has aliased bindings. See
// * the ArgumentsData::args comment.
// * CALLEE_SLOT
// * Stores the initial arguments.callee. This value can be overridden on
// * mapped arguments objects, see hasOverriddenCallee.
// */
// class ArgumentsObject : public NativeObject {
// protected:
// static const uint32_t INITIAL_LENGTH_SLOT = 0;
// static const uint32_t DATA_SLOT = 1;
// static const uint32_t MAYBE_CALL_SLOT = 2;
// static const uint32_t CALLEE_SLOT = 3;
constructor(Addr) {
this._Addr = Addr;
this._Obj = host.createPointerObject(
Addr,
Module,
'js::ArgumentsObject*'
);
const INITIAL_LENGTH_SLOT = host.Int64(0);
const DATA_SLOT = host.Int64(1);
const MAYBE_CALL_SLOT = host.Int64(2);
const CALLEE_SLOT = host.Int64(3);
const ArgumentsObjectSize = this._Obj.dereference().targetType.size;
this._SlotAddress = this._Obj.address.add(ArgumentsObjectSize);
const InitialLengthSlot = read_u64(this._SlotAddress.add(
INITIAL_LENGTH_SLOT.multiply(8)
));
this._InitialLength = new __JSInt32(InitialLengthSlot)._Value;
this._Data = read_u64(this._SlotAddress.add(
DATA_SLOT.multiply(8)
)).bitwiseShiftLeft(1);
}
toString() {
return 'Arguments(..)';
}
Logger(Content) {
logln(this._Addr.toString(16) + ': js!js::ArgumentsObject: ' + Content);
}
Display() {
this.Logger('InitialLength: ' + this._InitialLength);
this.Logger(' Data: ' + hex(this._Data) + ' (js!js::ArgumentsData)');
}
}
class __JSNull {
constructor(Addr) {
this._Addr = Addr;
}
toString() {
return 'null';
}
Logger(Content) {
logln(this._Addr.toString(16) + ': JSVAL_TYPE_NULL: ' + Content);
}
Display() {
this.Logger(this);
}
}
class __JSUndefined {
constructor(Addr) {
this._Addr = Addr;
}
toString() {
return 'undefined';
}
Logger(Content) {
logln(this.Addr.toString(16) + ': JSVAL_TYPE_UNDEFINED: ' + Content);
}
Display() {
this.Logger(this);
}
}
class __JSBoolean {
constructor(Addr) {
this._Addr = Addr;
this._Value = Addr.compareTo(1) == 0 ? true : false;
}
toString() {
return this._Value.toString();
}
Logger(Content) {
logln(this._Addr.toString(16) + ': JSVAL_TYPE_BOOLEAN: ' + Content);
}
Display() {
this.Logger(this);
}
}
class __JSInt32 {
constructor(Addr) {
this._Addr = Addr;
this._Value = Addr.bitwiseAnd(0xffffffff);
}
toString() {
return '0x' + this._Value.toString(16);
}
Logger(Content) {
logln(this._Addr.toString(16) + ': JSVAL_TYPE_INT32: ' + Content);
}
Display() {
this.Logger(this);
}
}
class __JSString {
constructor(Addr) {
this._Obj = host.createPointerObject(
Addr,
Module,
'JSString*'
);
/*
* The Flags Word
*
* The flags word stores both the string's type and its character encoding.
*
* If LATIN1_CHARS_BIT is set, the string's characters are stored as Latin1
* instead of TwoByte. This flag can also be set for ropes, if both the
* left and right nodes are Latin1. Flattening will result in a Latin1
* string in this case.
*/
const Flags = this._Obj.d.flags_;
const IsLatin1 = Flags.bitwiseAnd(LATIN1_CHARS_BIT).compareTo(0) != 0;
const IsInline = Flags.bitwiseAnd(INLINE_CHARS_BIT).compareTo(0) != 0;
let Address = null;
if(IsInline) {
//
// inlineStorageLatin1 and inlineStorageTwoByte are in a union and
// as a result are at the same address
//
Address = this._Obj.d.inlineStorageLatin1.address;
} else {
//
// Same as above with nonInlineStorageLatin1 and nonInlineStorageTwoByte.
//
Address = this._Obj.d.s.u2.nonInlineCharsLatin1.address;
}
let Length = Flags.bitwiseShiftRight(32);
if(!IsLatin1) {
Length *= 2;
}
this._String = Array.from(host.memory.readMemoryValues(
Address,
Length,
1
)).map(
p => byte_to_str(p)
).join('');
}
toString() {
return "'" + this._String + "'";
}
Logger(Content) {
logln(this._Obj.address.toString(16) + ': js!JSString: ' + Content);
}
Display() {
this.Logger(this);
}
}
class __JSValue {
constructor(Addr) {
this._Addr = Addr;
this._Tag = this._Addr.bitwiseShiftRight(JSVAL_TAG_SHIFT);
this._IsDouble = this._Tag.compareTo(JSVAL_TYPE_DOUBLE) < 0;
this._Payload = this._Addr.bitwiseAnd(JSVAL_PAYLOAD_MASK);
}
get Payload() {
if(this._IsDouble) {
return this._Addr;
}
return this._Payload;
}
get Tag() {
if(this._IsDouble) {
return JSVAL_TYPE_DOUBLE;
}
return this._Tag;
}
}
class __JSArray {
constructor(Addr) {
this._Obj = host.createPointerObject(
Addr,
Module,
'js::ArrayObject*'
);
// XXX: why doesn't it work?
// this.Obj.elements_.value.address
this._Content = this._Obj.elements_.address;
this._Header = heapslot_to_objectelements(this._Content);
// The flags word stores both the flags and the number of shifted elements.
// Allow shifting 2047 elements before actually moving the elements.
const NumShiftedElementsBits = host.Int64(11);
const NumShiftedElementsShift = host.Int64(32).subtract(NumShiftedElementsBits);
this._Flags = this._Header.flags;
this._NumShifted = this._Flags.bitwiseShiftRight(NumShiftedElementsShift)
this._Length = this._Header.length;
this._Capacity = this._Header.capacity;
this._InitializedLength = this._Header.initializedLength;
}
toString() {
const Max = 10;
const Content = [];
for(let Idx = 0; Idx < Math.min(Max, this._InitializedLength); ++Idx) {
const Addr = this._Content.add(Idx * 8);
const JSValue = read_u64(Addr);
const Inst = jsvalue_to_instance(JSValue);
Content.push(Inst.toString());
}
return '[' + Content.join(', ') + (this._Length > Max ? ', ...' : '') + ']';
}
Logger(Content) {
logln(this._Obj.address.toString(16) + ': js!js::ArrayObject: ' + Content);
}
Display() {
this.Logger(' Length: ' + this._Length);
this.Logger(' Capacity: ' + this._Capacity);
this.Logger('InitializedLength: ' + this._InitializedLength);
this.Logger(' NumShifted: ' + this._NumShifted + ' (flags: ' + hex(this._Flags) + ')');
this.Logger(' Content: ' + this);
}
}
class __JSFunction {
constructor(Addr) {
this._Obj = host.createPointerObject(
Addr,
Module,
'JSFunction*'
);
this._Atom = this._Obj.atom_.value.address;
this._Name = '';
if(this._Atom.compareTo(0) != 0) {
this._Name = new __JSString(this._Atom).toString().slice(1, -1);
}
this._Name += '()';
this._Flags = this._Obj.flags_;
}
toString() {
return this._Name;
}
get Flags() {
const S = [];
for(const Key in FunctionConstants) {
if(this._Flags.bitwiseAnd(host.parseInt64(Key)).compareTo(0) != 0) {
S.push(FunctionConstants[Key]);
}
}
const Kind = (this._Flags >> 13) & 7;
S.push(FunctionKindConstants[Kind]);
return S.join(' | ');
}
Logger(Content) {
logln(this._Obj.address.toString(16) + ': js!JSFunction: ' + Content);
}
Display() {
this.Logger(this);
this.Logger('Flags: ' + this.Flags);
}
}
class __JSSymbol {
constructor(Addr) {
this._Obj = host.createPointerObject(
Addr,
Module,
'js::Symbol*'
);
}
toString() {
const Desc = new __JSString(this._Obj.description_.address);
return 'Symbol(' + Desc + ')';
}
Logger(Content) {
logln(this.Obj_.address.toString(16) + ': js!js::Symbol: ' + Content);
}
Display() {
this.Logger(this);
}
}
class __JSArrayBuffer {
constructor(Addr) {
this._Obj = host.createPointerObject(
Addr,
Module,
'js::ArrayBufferObject*'
);
const ArrayBufferObjectSize = host.getModuleType(Module, 'js::ArrayBufferObject').size;
// static const uint8_t DATA_SLOT = 0;
// static const uint8_t BYTE_LENGTH_SLOT = 1;
const ByteLengthSlotAddr = Addr.add(ArrayBufferObjectSize).add(1 * 8);
const ByteLengthSlot = read_u64(ByteLengthSlotAddr);
this._ByteLength = new __JSInt32(ByteLengthSlot)._Value;
// static const uint8_t FIRST_VIEW_SLOT = 2;
// static const uint8_t FLAGS_SLOT = 3;
const FlagsAddr = Addr.add(ArrayBufferObjectSize).add(3 * 8);
const FlagsSlot = read_u64(FlagsAddr);
this._Flags = new __JSInt32(FlagsSlot)._Value;
}
get Flags() {
// enum BufferKind {
// PLAIN = 0, // malloced or inline data
// WASM = 1,
// MAPPED = 2,
// EXTERNAL = 3,
// KIND_MASK = 0x3
// };
// enum ArrayBufferFlags {
// // The flags also store the BufferKind
// BUFFER_KIND_MASK = BufferKind::KIND_MASK,
// DETACHED = 0x4,
// // The dataPointer() is owned by this buffer and should be released
// // when no longer in use. Releasing the pointer may be done by freeing,
// // invoking a dereference callback function, or unmapping, as
// // determined by the buffer's other flags.
// //
// // Array buffers which do not own their data include buffers that
// // allocate their data inline, and buffers that are created lazily for
// // typed objects with inline storage, in which case the buffer points
// // directly to the typed object's storage.
// OWNS_DATA = 0x8,
// // This array buffer was created lazily for a typed object with inline
// // data. This implies both that the typed object owns the buffer's data
// // and that the list of views sharing this buffer's data might be
// // incomplete. Any missing views will be typed objects.
// FOR_INLINE_TYPED_OBJECT = 0x10,
// // Views of this buffer might include typed objects.
// TYPED_OBJECT_VIEWS = 0x20,
// // This PLAIN or WASM buffer has been prepared for asm.js and cannot
// // henceforth be transferred/detached.
// FOR_ASMJS = 0x40
// };
const BufferKinds = {
0 : 'PLAIN',
1 : 'WASM',
2 : 'MAPPED',
3 : 'EXTERNAL'
};
const BufferKind = BufferKinds[this._Flags.bitwiseAnd(3).asNumber()];
const ArrayBufferFlags = [
'BufferKind(' + BufferKind + ')'
];
const ArrayBufferFlagsConstants = {
[0x04] : 'DETACHED',
[0x08] : 'OWNS_DATA',
[0x10] : 'FOR_INLINE_TYPED_OBJECT',
[0x20] : 'TYPED_OBJECT_VIEWS',
[0x40] : 'FOR_ASMJS'
};
for(const Key in ArrayBufferFlagsConstants) {
if(this._Flags.bitwiseAnd(host.parseInt64(Key)).compareTo(0) != 0) {
ArrayBufferFlags.push(ArrayBufferFlagsConstants[Key]);
}
}
return ArrayBufferFlags.join(' | ');
}
get ByteLength() {
return this._ByteLength;
}
toString() {
return 'ArrayBuffer({ByteLength:' + this._ByteLength + ', ...})';
}
Logger(Content) {
logln(this._Obj.address.toString(16) + ': js!js::ArrayBufferObject: ' + Content);
}
Display() {
this.Logger('ByteLength: ' + this.ByteLength);
this.Logger(' Flags: ' + this.Flags);
this.Logger(' Content: ' + this);
}
}
class __JSTypedArray {
constructor(Addr) {
this._Obj = host.createPointerObject(
Addr,
Module,
'js::TypedArrayObject*'
);
const Group = this._Obj.group_.value;
this._TypeName = host.memory.readString(Group.clasp_.name)
const Sizes = {
'Float64Array' : 8,
'Float32Array' : 4,
'Uint32Array' : 4,
'Int32Aray' : 4,
'Uint16Array' : 2,
'Int16Array' : 2,
'Uint8Array' : 1,
'Uint8ClampedArray' : 1,
'Int8Array' : 1
};
this._ElementSize = Sizes[this._TypeName];
const TypedArrayObjectSize = host.getModuleType(Module, 'js::TypedArrayObject').size;
// static const size_t BUFFER_SLOT = 0;
// static const size_t LENGTH_SLOT = 1;
const LengthSlotAddr = Addr.add(TypedArrayObjectSize).add(1 * 8);
const LengthSlot = read_u64(LengthSlotAddr);
this._Length = new __JSInt32(LengthSlot)._Value;
this._ByteLength = this._Length * this._ElementSize;
// static const size_t BYTEOFFSET_SLOT = 2;
const ByteOffsetSlotAddr = Addr.add(TypedArrayObjectSize).add(2 * 8);
const ByteOffsetSlot = read_u64(ByteOffsetSlotAddr);
this._ByteOffset = new __JSInt32(ByteOffsetSlot)._Value;
// static const size_t RESERVED_SLOTS = 3;
}
get Type() {
return this._TypeName;
}
get ByteOffset() {
return this._ByteOffset;
}
get ByteLength() {
return this._ByteLength;
}
get Length() {
return this._Length;
}
toString() {
return this._TypeName + '({Length:' + this._Length + ', ...})';
}
Logger(Content) {
logln(this._Obj.address.toString(16) + ': js!js::TypedArrayObject: ' + Content);
}
Display() {
this.Logger(' Type: ' + this.Type);
this.Logger(' Length: ' + this.Length);
this.Logger('ByteLength: ' + this.ByteLength);
this.Logger('ByteOffset: ' + this.ByteOffset);
this.Logger(' Content: ' + this);
}
}
class __JSMap {
constructor(Addr) {
this._Addr = Addr;
}
// XXX: TODO
toString() {
return 'new Map(...)';
}
Logger(Content) {
logln(this._Addr.toString(16) + ': js!js::MapObject: ' + Content);
}
Display() {
this.Logger('Content: ' + this);
}
}
class __JSDouble {
constructor(Addr) {
this._Addr = Addr;
}
toString() {
const U32 = new Uint32Array([
this._Addr.getLowPart(),
this._Addr.getHighPart()
]);
const F64 = new Float64Array(U32.buffer);
return F64[0];
}
Logger(Content) {
logln(this._Addr.toString(16) + ': JSVAL_TYPE_DOUBLE: ' + Content);
}
Display() {
this.Logger(this);
}
}
const Names2Types = {
'Function' : __JSFunction,
'Array' : __JSArray,
'ArrayBuffer' : __JSArrayBuffer,
'Map' : __JSMap,
'Int32' : __JSInt32,
'String' : __JSString,
'Boolean' : __JSBoolean,
'Null' : __JSNull,
'Undefined' : __JSUndefined,
'Symbol' : __JSSymbol,
'Double' : __JSDouble,
'Magic' : __JSMagic,
'Arguments' : __JSArgument,
'Float64Array' : __JSTypedArray,
'Float32Array' : __JSTypedArray,
'Uint32Array' : __JSTypedArray,
'Int32Array' : __JSTypedArray,
'Uint16Array' : __JSTypedArray,
'Int16Array' : __JSTypedArray,
'Uint8Array' : __JSTypedArray,
'Uint8ClampedArray' : __JSTypedArray,
'Int8Array' : __JSTypedArray
};
class __JSObject {
/* JSObject.h
* A JavaScript object.
*
* This is the base class for all objects exposed to JS script (as well as some
* objects that are only accessed indirectly). Subclasses add additional fields
* and execution semantics. The runtime class of an arbitrary JSObject is
* identified by JSObject::getClass().
*
* The members common to all objects are as follows:
*
* - The |group_| member stores the group of the object, which contains its
* prototype object, its class and the possible types of its properties.
*
* - The |shapeOrExpando_| member points to (an optional) guard object that JIT
* may use to optimize. The pointed-to object dictates the constraints
* imposed on the JSObject:
* nullptr
* - Safe value if this field is not needed.
* js::Shape
* - All objects that might point |shapeOrExpando_| to a js::Shape
* must follow the rules specified on js::ShapedObject.
* JSObject
* - Implies nothing about the current object or target object. Either
* of which may mutate in place. Store a JSObject* only to save
* space, not to guard on.
*/
constructor(Addr) {
this._Addr = Addr;
this._Obj = host.createPointerObject(
this._Addr,
Module,
'JSObject*'
);
this._Properties = [];
const Group = this._Obj.group_.value;
this._ClassName = host.memory.readString(Group.clasp_.name);
const NonNative = Group.clasp_.flags.bitwiseAnd(CLASS_NON_NATIVE).compareTo(0) != 0;
if(NonNative) {
return;
}
const Shape = host.createPointerObject(
this._Obj.shapeOrExpando_.address,
Module,
'js::Shape*'
);
const NativeObject = host.createPointerObject(Addr, Module, 'js::NativeObject*');
if(this._ClassName == 'Array') {
//
// Optimization for 'length' property if 'Array' cf
// js::ArrayObject::length / js::GetLengthProperty
//
const ObjectElements = heapslot_to_objectelements(NativeObject.elements_.address);
this._Properties.push('length : ' + ObjectElements.length);
return;
}
//
// Walk the list of Shapes and get the property names
//
const Properties = {};
let CurrentShape = Shape;
while(CurrentShape.parent.value.address.compareTo(0) != 0) {
const SlotIdx = CurrentShape.immutableFlags.bitwiseAnd(SLOT_MASK).asNumber();
Properties[SlotIdx] = get_property_from_shape(CurrentShape);
CurrentShape = CurrentShape.parent.value;
}
//
// Walk the slots to get the values now (check NativeGetPropertyInline/GetExistingProperty)
//
const NativeObjectTypeSize = host.getModuleType(Module, 'js::NativeObject').size;
const NativeObjectElements = NativeObject.address.add(NativeObjectTypeSize);
const NativeObjectSlots = NativeObject.slots_.address;
const Max = Shape.immutableFlags.bitwiseShiftRight(FIXED_SLOTS_SHIFT).asNumber();
for(let Idx = 0; Idx < Object.keys(Properties).length; Idx++) {
//
// Check out NativeObject::getSlot()
//
const PropertyName = Properties[Idx];
let PropertyValue = undefined;
let ElementAddr = undefined;
if(Idx < Max) {
ElementAddr = NativeObjectElements.add(Idx * 8);
} else {
ElementAddr = NativeObjectSlots.add((Idx - Max) * 8);
}
const JSValue = read_u64(ElementAddr);
PropertyValue = jsvalue_to_instance(JSValue);
this._Properties.push(PropertyName + ' : ' + PropertyValue);
}
}
get Properties() {
return this._Properties;
}
get ClassName() {
return this._ClassName;
}
toString() {
if(this._ClassName != 'Object' && Names2Types.hasOwnProperty(this._ClassName)) {
const Type = Names2Types[this._ClassName];
return new Type(this._Addr).toString();
}
if(this._ClassName != 'Object') {
return this._ClassName;
}
if(this._Properties != undefined && this._Properties.length > 0) {
return '{' + this._Properties.join(', ') + '}';
}
if(this._ClassName == 'Object') {
return '[Object]';
}
return 'Dunno';
}
Logger(Content) {
logln(this._Addr.toString(16) + ': js!JSObject: ' + Content);
}
Display() {
this.Logger('Content: ' + this);
//
// If the class name is not Object then it means the toString() method
// might already have displayed the properties.
// {foo:'bar'} VS Math.
//
if(this._ClassName != 'Object') {
this.Logger('Properties: {' + this._Properties.join(', ') + '}');
}
}
}
Names2Types['Object'] = __JSObject;
function smdump_jsobject(Addr, Type = null) {
init();
if(Addr.hasOwnProperty('address')) {
Addr = Addr.address;
}
let ClassName;
if(Type == 'Object' || Type == null) {
const JSObject = new __JSObject(Addr);
ClassName = JSObject.ClassName;
if(!Names2Types.hasOwnProperty(ClassName)) {
JSObject.Display();
}
} else {
ClassName = Type;
}
if(Names2Types.hasOwnProperty(ClassName)) {
const Inst = new Names2Types[ClassName](Addr);
Inst.Display();
}
}
function smdump_jsvalue(Addr) {
init();
if(Addr == undefined) {
logln('!smdump_jsvalue ');
return;
}
//
// Ensure Addr is an unsigned value. If we don't do this
// the shift operations don't behave the way we want them to.
//
Addr = Addr.bitwiseAnd(host.parseInt64('0xffffffffffffffff'));
const JSValue = new __JSValue(Addr);
if(!Tag2Names.hasOwnProperty(JSValue.Tag)) {
logln('Tag ' + JSValue.Tag.toString(16) + ' not recognized');
return;
}
const Name = Tag2Names[JSValue.Tag];
return smdump_jsobject(JSValue.Payload, Name);
}
function init() {
if(Module != null) {
return;
}
const Xul = host.currentProcess.Modules.Any(
p => p.Name.toLowerCase().endsWith('xul.dll')
);
if(Xul) {
Module = 'xul.dll';
logln('Detected xul.dll, using it as js module.');
return;
}
Module = 'js.exe';
}
function ion_insertbp() {
// XXX: Having the current frame would be better.. but not sure if
// this is something possible?
const CurrentThread = host.currentThread;
const LowestFrame = CurrentThread.Stack.Frames[0];
const LocalVariables = LowestFrame.LocalVariables;
const CodeGenerator = LocalVariables.this;
if(CodeGenerator === undefined ||
CodeGenerator.targetType.toString() != 'js::jit::CodeGenerator *') {
logln('The script expects `this` to be a js::jit::CodeGenerator in the lowest frame.');
return;
}
const JITBuffer = CodeGenerator.masm.masm.m_formatter.m_buffer.m_buffer;
// XXX: So sounds like I can't do JITBuffer.mBegin[JITBuffer.mLength] = 0xcc,
// so here I am writing ugly things :x
const BreakpointAddress = JITBuffer.mBegin.address.add(JITBuffer.mLength);
logln(`JIT buffer is at ${hex(JITBuffer.mBegin.address)}`);
logln(`Writing breakpoint at ${hex(BreakpointAddress)}`);
host.evaluateExpression(`*(char*)${hex(BreakpointAddress)} = 0xcc`);
JITBuffer.mLength += 1;
}
let Context = undefined;
function dump_nursery_stats(Nursery) {
logln(`${hex(Nursery.address)}: js::Nursery`);
const ChunkCountLimit = Nursery.chunkCountLimit_;
const NurseryChunk = host.createPointerObject(
0,
Module,
'js::NurseryChunk*'
);
const ChunkSize = NurseryChunk.dereference().targetType.size;
const MaxSize = ChunkCountLimit.multiply(ChunkSize);
logln(` ChunkCountLimit: ${hex(ChunkCountLimit)} (${MaxSize / 1024 / 1024} MB)`);
const Capacity = Nursery.capacity_;
logln(` Capacity: ${hex(Capacity)} bytes`)
const CurrentChunk = Nursery.currentStartPosition_;
logln(` CurrentChunk: ${hex(CurrentChunk)}`);
const Position = Nursery.position_;
logln(` Position: ${hex(Position)}`);
logln(' Chunks:');
const Chunks = Nursery.chunks_;
for(let Idx = 0; Idx < Chunks.mLength; Idx++) {
const Chunk = Chunks.mBegin[Idx];
const StartAddress = Chunk.data.address;
const EndAddress = StartAddress.add(ChunkSize).subtract(1);
const PaddedIdx = Idx.toString().padStart(2, '0');
logln(` ${PaddedIdx}: [${hex(StartAddress)} - ${hex(EndAddress)}]`);
}
}
function in_nursery(Addr) {
init();
if(Addr == undefined) {
logln('!in_nursery