diff --git a/package-lock.json b/package-lock.json index db729c5..2de28cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "frida-cshell", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frida-cshell", - "version": "1.5.0", + "version": "1.5.1", "devDependencies": { "@eslint/js": "^9.10.0", "@types/frida-gum": "^18.7", diff --git a/package.json b/package.json index 6cc5210..9534456 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "frida-cshell", - "version": "1.5.0", + "version": "1.5.1", "description": "Frida's CShell", "scripts": { "prepare": "npm run build && npm run version && npm run package && npm run copy", diff --git a/src/breakpoints/bp.ts b/src/breakpoints/bp.ts index 163c2f4..6957d31 100644 --- a/src/breakpoints/bp.ts +++ b/src/breakpoints/bp.ts @@ -13,8 +13,6 @@ import { Vars } from '../vars/vars.js'; import { CallTrace } from '../traces/call.js'; import { CoverageTrace } from '../traces/coverage/trace.js'; -export const BP_LENGTH: number = 16; - export enum BpKind { Code = 'code', Memory = 'memory', @@ -33,6 +31,7 @@ export enum BpType { } export class Bp { + public static readonly BP_LENGTH: number = 16; private readonly _type: BpType; private readonly _idx: number; @@ -97,7 +96,7 @@ export class Bp { private enableCode() { if (this._addr === null) return; if (this._listener !== null) return; - this._overlay = Overlay.add(this._addr.toPointer(), BP_LENGTH); + this._overlay = Overlay.add(this._addr.toPointer(), Bp.BP_LENGTH); const addr = this._addr; // eslint-disable-next-line @typescript-eslint/no-this-alias const bp = this; @@ -243,9 +242,12 @@ export class Bp { Output.setIndent(true); Output.writeln(); try { - trace.lines().forEach(l => { - Output.writeln(l); - }); + trace + .lines() + .slice(0, TraceData.MAX_LINES) + .forEach(l => { + Output.writeln(l); + }); Output.writeln(); } finally { Output.setIndent(false); diff --git a/src/breakpoints/regs.ts b/src/breakpoints/regs.ts index 1de77af..76c22a0 100644 --- a/src/breakpoints/regs.ts +++ b/src/breakpoints/regs.ts @@ -1,12 +1,12 @@ import { Var } from '../vars/var.js'; -const THREAD_ID_NAME: string = 'tid'; -const RETURN_ADDRESS_NAME: string = 'ra'; -const ADDR_NAME: string = 'addr'; -const PC_NAME: string = 'pc'; -const RETVAL_NAME: string = 'ret'; - export class Regs { + private static readonly THREAD_ID_NAME: string = 'tid'; + private static readonly RETURN_ADDRESS_NAME: string = 'ra'; + private static readonly ADDR_NAME: string = 'addr'; + private static readonly PC_NAME: string = 'pc'; + private static readonly RETVAL_NAME: string = 'ret'; + private static threadId: ThreadId | null = null; private static ctx: CpuContext | null = null; private static returnAddress: NativePointer | null = null; @@ -17,33 +17,33 @@ export class Regs { private constructor() {} public static get(name: string): Var { - if (name === THREAD_ID_NAME) { + if (name === Regs.THREAD_ID_NAME) { if (this.threadId === null) throw new Error('thread ID not available outside of a breakpoint'); - return new Var(uint64(this.threadId), THREAD_ID_NAME); - } else if (name === RETURN_ADDRESS_NAME) { + return new Var(uint64(this.threadId), Regs.THREAD_ID_NAME); + } else if (name === Regs.RETURN_ADDRESS_NAME) { if (this.returnAddress === null) throw new Error('return address not available outside of a breakpoint'); return new Var( uint64(this.returnAddress.toString()), - RETURN_ADDRESS_NAME, + Regs.RETURN_ADDRESS_NAME, ); - } else if (name === ADDR_NAME) { + } else if (name === Regs.ADDR_NAME) { if (this.addr === null) throw new Error('addr not available outside of a breakpoint'); - return new Var(uint64(this.addr.toString()), ADDR_NAME); - } else if (name === RETVAL_NAME) { + return new Var(uint64(this.addr.toString()), Regs.ADDR_NAME); + } else if (name === Regs.RETVAL_NAME) { if (this.retVal === null) throw new Error( 'return Value not available outside of a function exit breakpoint', ); - return new Var(uint64(this.retVal.toString()), RETVAL_NAME); + return new Var(uint64(this.retVal.toString()), Regs.RETVAL_NAME); } else if (this.ctx === null) { - if (name === PC_NAME) { + if (name === Regs.PC_NAME) { if (this.pc === null) { throw new Error('pc not available outside of a breakpoint'); } - return new Var(uint64(this.pc.toString()), PC_NAME); + return new Var(uint64(this.pc.toString()), Regs.PC_NAME); } else { throw new Error('registers not available outside of a breakpoint'); } @@ -163,13 +163,13 @@ export class Regs { } public static set(name: string, value: Var) { - if (name === THREAD_ID_NAME) { + if (name === Regs.THREAD_ID_NAME) { throw new Error('thread ID cannot be set'); - } else if (name === RETURN_ADDRESS_NAME) { + } else if (name === Regs.RETURN_ADDRESS_NAME) { throw new Error('return address cannot be set'); - } else if (name === ADDR_NAME) { + } else if (name === Regs.ADDR_NAME) { throw new Error('addr cannot be set'); - } else if (name === RETVAL_NAME) { + } else if (name === Regs.RETVAL_NAME) { if (this.retVal === null) throw new Error( 'return Value not available outside of a function exit breakpoint', @@ -177,7 +177,7 @@ export class Regs { const ptr = value.toPointer(); this.retVal.replace(ptr); } else if (this.ctx === null) { - if (name === PC_NAME) { + if (name === Regs.PC_NAME) { throw new Error('pc cannot be set'); } else { throw new Error('registers not available outside of a breakpoint'); @@ -300,7 +300,10 @@ export class Regs { if (this.ctx === null) { if (this.pc !== null) { - result.push([PC_NAME, new Var(uint64(this.pc.toString()), PC_NAME)]); + result.push([ + Regs.PC_NAME, + new Var(uint64(this.pc.toString()), Regs.PC_NAME), + ]); } } else { const regs = Array.from(this.getRegs(this.ctx).entries()); @@ -309,29 +312,32 @@ export class Regs { if (this.threadId !== null) { result.push([ - THREAD_ID_NAME, - new Var(uint64(this.threadId), THREAD_ID_NAME), + Regs.THREAD_ID_NAME, + new Var(uint64(this.threadId), Regs.THREAD_ID_NAME), ]); } if (this.returnAddress !== null) { result.push([ - RETURN_ADDRESS_NAME, - new Var(uint64(this.returnAddress.toString()), RETURN_ADDRESS_NAME), + Regs.RETURN_ADDRESS_NAME, + new Var( + uint64(this.returnAddress.toString()), + Regs.RETURN_ADDRESS_NAME, + ), ]); } if (this.addr !== null) { result.push([ - ADDR_NAME, - new Var(uint64(this.addr.toString()), ADDR_NAME), + Regs.ADDR_NAME, + new Var(uint64(this.addr.toString()), Regs.ADDR_NAME), ]); } if (this.retVal !== null) { result.push([ - RETVAL_NAME, - new Var(uint64(this.retVal.toString()), RETVAL_NAME), + Regs.RETVAL_NAME, + new Var(uint64(this.retVal.toString()), Regs.RETVAL_NAME), ]); } diff --git a/src/cmdlets/breakpoints/bp.ts b/src/cmdlets/breakpoints/bp.ts index 1bebd63..d2fa49d 100644 --- a/src/cmdlets/breakpoints/bp.ts +++ b/src/cmdlets/breakpoints/bp.ts @@ -1,4 +1,4 @@ -import { BP_LENGTH, BpType } from '../../breakpoints/bp.js'; +import { Bp, BpType } from '../../breakpoints/bp.js'; import { Bps } from '../../breakpoints/bps.js'; import { CmdLet } from '../../commands/cmdlet.js'; import { Input, InputInterceptLine } from '../../io/input.js'; @@ -66,7 +66,7 @@ abstract class TypedBpCmdLet extends CmdLet implements InputInterceptLine { public usage(): Var { const create = this.usageCreate(); const modify = this.usageModify(); - const USAGE: string = `Usage: ${this.name} + const usage: string = `Usage: ${this.name} ${Output.bold('show:')} ${this.name} - show all ${this.bpType} breakpoints @@ -87,7 +87,7 @@ ${this.name} ${CmdLet.NUM_CHAR}n # - delete a ${this.bpType} breakpoint ${Output.bold('NOTE:')} Set hits to '*' for unlimited breakpoint.`; - Output.writeln(USAGE); + Output.writeln(usage); return Var.ZERO; } @@ -125,10 +125,10 @@ abstract class CodeBpCmdLet const bp = Bps.create(this.bpType, 0, null, 0); Output.writeln(`Created ${bp.toString()}`); } else if (hits === null) { - const bp = Bps.create(this.bpType, -1, addr, BP_LENGTH); + const bp = Bps.create(this.bpType, -1, addr, Bp.BP_LENGTH); Output.writeln(`Created ${bp.toString()}`); } else { - const bp = Bps.create(this.bpType, hits, addr, BP_LENGTH); + const bp = Bps.create(this.bpType, hits, addr, Bp.BP_LENGTH); Output.writeln(`Created ${bp.toString()}`); } @@ -149,10 +149,10 @@ abstract class CodeBpCmdLet const bp = Bps.modify(this.bpType, index, 0, null, 0); Output.writeln(`Modified ${bp.toString()}`); } else if (hits === null) { - const bp = Bps.modify(this.bpType, index, -1, addr, BP_LENGTH); + const bp = Bps.modify(this.bpType, index, -1, addr, Bp.BP_LENGTH); Output.writeln(`Modified ${bp.toString()}`); } else { - const bp = Bps.modify(this.bpType, index, hits, addr, BP_LENGTH); + const bp = Bps.modify(this.bpType, index, hits, addr, Bp.BP_LENGTH); Output.writeln(`Modified ${bp.toString()}`); } @@ -165,7 +165,7 @@ abstract class CodeBpCmdLet } protected override usageCreate(): string { - const USAGE: string = ` + const usage: string = ` ${this.name} 0 - create ${this.bpType} breakpoint without assigning an address ${this.name} addr - create ${this.bpType} breakpoint without a hit limit @@ -175,11 +175,11 @@ ${this.name} addr hits - create ${this.bpType} breakpoint addr the address to create the breakpoint hits the number of times the breakpoint should fire`; - return USAGE; + return usage; } protected override usageModify(): string { - const USAGE: string = ` + const usage: string = ` ${this.name} ${CmdLet.NUM_CHAR}n addr - modify a ${this.bpType} breakpoint without a hit limit ${CmdLet.NUM_CHAR}n the number of the breakpoint to modify addr the address to move the breakpoint @@ -188,7 +188,7 @@ ${this.name} ${CmdLet.NUM_CHAR}n addr hits - modify a ${this.bpType} breakpoint ${CmdLet.NUM_CHAR}n the number of the breakpoint to modify addr the address to move the breakpoint hits the number of times the breakpoint should fire`; - return USAGE; + return usage; } } @@ -263,7 +263,7 @@ abstract class MemoryBpCmdLet } protected override usageCreate(): string { - const USAGE: string = ` + const usage: string = ` ${this.name} 0 0 - create ${this.bpType} breakpoint without assigning an address ${this.name} addr len - create ${this.bpType} breakpoint without a hit limit @@ -274,11 +274,11 @@ ${this.name} addr len hits - create ${this.bpType} breakpoint without a hit limi addr the address to create the breakpoint len the length of the memory region to watch hits the number of times the breakpoint should fire`; - return USAGE; + return usage; } protected override usageModify(): string { - const USAGE: string = ` + const usage: string = ` ${this.name} ${CmdLet.NUM_CHAR}n addr len - modify a ${this.bpType} breakpoint without a hit limit ${CmdLet.NUM_CHAR}n the number of the breakpoint to modify addr the address to move the breakpoint @@ -289,7 +289,7 @@ ${this.name} ${CmdLet.NUM_CHAR}n addr len hits - modify a ${this.bpType} breakpo addr the address to move the breakpoint len the length of the memory region to watch hits the number of times the breakpoint should fire`; - return USAGE; + return usage; } } @@ -313,7 +313,7 @@ abstract class TraceBpCmdLet this.bpType, -1, addr, - BP_LENGTH, + Bp.BP_LENGTH, depth.toU64().toNumber(), ); Output.writeln(`Created ${bp.toString()}`); @@ -322,7 +322,7 @@ abstract class TraceBpCmdLet this.bpType, hits, addr, - BP_LENGTH, + Bp.BP_LENGTH, depth.toU64().toNumber(), ); Output.writeln(`Created ${bp.toString()}`); @@ -352,7 +352,7 @@ abstract class TraceBpCmdLet index, -1, addr, - BP_LENGTH, + Bp.BP_LENGTH, depth.toU64().toNumber(), ); @@ -363,7 +363,7 @@ abstract class TraceBpCmdLet index, hits, addr, - BP_LENGTH, + Bp.BP_LENGTH, depth.toU64().toNumber(), ); @@ -375,7 +375,7 @@ abstract class TraceBpCmdLet } protected override usageCreate(): string { - const USAGE: string = ` + const usage: string = ` ${this.name} addr depth - create ${this.bpType} breakpoint without a hit limit addr the address to create the breakpoint depth the maximum depth of callstack to follow @@ -384,11 +384,11 @@ ${this.name} addr depth hits - create ${this.bpType} breakpoint hits the number of times the breakpoint should fire addr the address to create the breakpoint depth the maximum depth of callstack to follow`; - return USAGE; + return usage; } protected override usageModify(): string { - const USAGE: string = ` + const usage: string = ` ${this.name} ${CmdLet.NUM_CHAR}n addr depth - modify a ${this.bpType} breakpoint without a hit limit ${CmdLet.NUM_CHAR}n the number of the breakpoint to modify addr the address to move the breakpoint @@ -399,7 +399,7 @@ ${this.name} ${CmdLet.NUM_CHAR}n addr depth hits - modify a ${this.bpType} break addr the address to move the breakpoint depth the maximum depth of callstack to follow hits the number of times the breakpoint should fire`; - return USAGE; + return usage; } } diff --git a/src/cmdlets/breakpoints/reg.ts b/src/cmdlets/breakpoints/reg.ts index bcec824..a2db727 100644 --- a/src/cmdlets/breakpoints/reg.ts +++ b/src/cmdlets/breakpoints/reg.ts @@ -5,7 +5,12 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Regs } from '../../breakpoints/regs.js'; -const USAGE: string = `Usage: R +export class RegCmdLet extends CmdLet { + name = 'R'; + category = 'breakpoints'; + help = 'register management'; + + private static readonly USAGE: string = `Usage: R R - show the values of all registers R name - display the value of a named register @@ -15,11 +20,6 @@ R name value - assign a value to a register name the name of the register to assign value the value to assign`; -export class RegCmdLet extends CmdLet { - name = 'R'; - category = 'breakpoints'; - help = 'register management'; - public runSync(tokens: Token[]): Var { const vars = this.transformOptional( tokens, @@ -63,7 +63,7 @@ export class RegCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(RegCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/data/assembly.ts b/src/cmdlets/data/assembly.ts index e57a644..938cfce 100644 --- a/src/cmdlets/data/assembly.ts +++ b/src/cmdlets/data/assembly.ts @@ -5,18 +5,18 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Mem } from '../../memory/mem.js'; -const DEFAULT_LENGTH: number = 10; -const USAGE: string = `Usage: l - -l address - show disassembly listing - address the address/symbol to disassemble - bytes the number of instructions to disassemble (default ${DEFAULT_LENGTH})`; - export class AssemblyCmdLet extends CmdLet { name = 'l'; category = 'data'; help = 'disassembly listing'; + private static readonly DEFAULT_LENGTH: number = 10; + private static readonly USAGE: string = `Usage: l + +l address - show disassembly listing + address the address/symbol to disassemble + bytes the number of instructions to disassemble (default ${AssemblyCmdLet.DEFAULT_LENGTH})`; + public runSync(tokens: Token[]): Var { const vars = this.transformOptional( tokens, @@ -27,7 +27,8 @@ export class AssemblyCmdLet extends CmdLet { const [[v0], [v1]] = vars as [[Var], [Var | null]]; const address = v0.toPointer(); - const length = v1 === null ? DEFAULT_LENGTH : v1.toU64().toNumber(); + const length = + v1 === null ? AssemblyCmdLet.DEFAULT_LENGTH : v1.toU64().toNumber(); if (length > 100) throw new Error(`too many instructions: ${length}`); @@ -125,7 +126,7 @@ export class AssemblyCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(AssemblyCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/data/copy.ts b/src/cmdlets/data/copy.ts index 38dbe26..eb71607 100644 --- a/src/cmdlets/data/copy.ts +++ b/src/cmdlets/data/copy.ts @@ -5,18 +5,18 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Mem } from '../../memory/mem.js'; -const USAGE: string = `Usage: cp +export class CopyCmdLet extends CmdLet { + name = 'cp'; + category = 'data'; + help = 'copy data in memory'; + + private static readonly USAGE: string = `Usage: cp cp dest src bytes - copy data dest the address/symbol to write to src the address/symbol to read from bytes the numer of bytes to read`; -export class CopyCmdLet extends CmdLet { - name = 'cp'; - category = 'data'; - help = 'copy data in memory'; - public runSync(tokens: Token[]): Var { const vars = this.transform(tokens, [ this.parseVar, @@ -42,7 +42,7 @@ export class CopyCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(CopyCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/data/dump.ts b/src/cmdlets/data/dump.ts index ea0cd95..d0875f4 100644 --- a/src/cmdlets/data/dump.ts +++ b/src/cmdlets/data/dump.ts @@ -5,25 +5,25 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Mem } from '../../memory/mem.js'; -const ROW_WIDTH: number = 16; -const DEFAULT_COUNT: number = 32; +export class DumpCmdLet extends CmdLet { + name = 'd'; + category = 'data'; + help = 'dump data from memory'; + + private static readonly ROW_WIDTH: number = 16; + private static readonly DEFAULT_COUNT: number = 32; -const USAGE: string = `Usage: d + private static readonly USAGE: string = `Usage: d d address - show data adress the address/symbol to read from - count the count of fields to read (default ${DEFAULT_COUNT}) + count the count of fields to read (default ${DumpCmdLet.DEFAULT_COUNT}) d address bytes - show data adress the address/symbol to read from - count the count of fields to read (default ${DEFAULT_COUNT}) + count the count of fields to read (default ${DumpCmdLet.DEFAULT_COUNT}) width the width of each field in the output (1, 2, 4 or 8)`; -export class DumpCmdLet extends CmdLet { - name = 'd'; - category = 'data'; - help = 'dump data from memory'; - public runSync(tokens: Token[]): Var { const vars = this.transformOptional( tokens, @@ -34,7 +34,8 @@ export class DumpCmdLet extends CmdLet { const [[v0], [v1, v2]] = vars as [[Var], [Var | null, number | null]]; const address = v0.toPointer(); - const count = v1 === null ? DEFAULT_COUNT : v1.toU64().toNumber(); + const count = + v1 === null ? DumpCmdLet.DEFAULT_COUNT : v1.toU64().toNumber(); const width = v2 === null ? 1 : v2; this.dump(address, count, width); return v0; @@ -48,7 +49,7 @@ export class DumpCmdLet extends CmdLet { const output = Memory.alloc(length); output.writeByteArray(bytes.buffer as ArrayBuffer); const headerPrefix = ' '.repeat(1 + Process.pointerSize * 2); - const headers = [...Array(ROW_WIDTH).keys()] + const headers = [...Array(DumpCmdLet.ROW_WIDTH).keys()] .map(i => { if (i % width !== 0) return ''; const hdr = i.toString(16).toUpperCase(); @@ -59,53 +60,56 @@ export class DumpCmdLet extends CmdLet { const byteHeaders = width === 1 ? '0123456789ABCDEF' : ''; Output.writeln([headerPrefix, headers, byteHeaders].join(' '), true); - const startAddress = address.and(~(ROW_WIDTH - 1)); + const startAddress = address.and(~(DumpCmdLet.ROW_WIDTH - 1)); const endAddress = address .add(length) - .add(ROW_WIDTH - 1) - .and(~(ROW_WIDTH - 1)); - const numChunks = endAddress.sub(startAddress).toUInt32() / ROW_WIDTH; + .add(DumpCmdLet.ROW_WIDTH - 1) + .and(~(DumpCmdLet.ROW_WIDTH - 1)); + const numChunks = + endAddress.sub(startAddress).toUInt32() / DumpCmdLet.ROW_WIDTH; const rows = [...Array(numChunks).keys()] .map(i => { - return startAddress.add(i * ROW_WIDTH); + return startAddress.add(i * DumpCmdLet.ROW_WIDTH); }) .map(rowAddress => { const headerPrefix = `${Output.green(Format.toHexString(rowAddress))}`; - const values = [...Array(ROW_WIDTH / width).keys()].map(i => { - const offset = i * width; - const rowCursor = rowAddress.add(offset); - const limit = address.add(length); - if (rowCursor < address) return ''.padStart(width * 2, ' '); - if (rowCursor >= limit) return ''.padStart(width * 2, ' '); - switch (width) { - case 1: { - const val = rowCursor.readU8(); - const str = val.toString(16).padStart(2, '0'); - return `${Output.yellow(str)}`; - } - case 2: { - const val = rowCursor.readU16(); - const str = val.toString(16).padStart(4, '0'); - return `${Output.yellow(str)}`; - } - case 4: { - const val = rowCursor.readU32(); - const str = val.toString(16).padStart(8, '0'); - return `${Output.yellow(str)}`; - } - case 8: { - const val = rowCursor.readU64(); - const str = val.toString(16).padStart(16, '0'); - return `${Output.yellow(str)}`; - } - default: { - throw new Error(`invalid width: ${width}`); + const values = [...Array(DumpCmdLet.ROW_WIDTH / width).keys()].map( + i => { + const offset = i * width; + const rowCursor = rowAddress.add(offset); + const limit = address.add(length); + if (rowCursor < address) return ''.padStart(width * 2, ' '); + if (rowCursor >= limit) return ''.padStart(width * 2, ' '); + switch (width) { + case 1: { + const val = rowCursor.readU8(); + const str = val.toString(16).padStart(2, '0'); + return `${Output.yellow(str)}`; + } + case 2: { + const val = rowCursor.readU16(); + const str = val.toString(16).padStart(4, '0'); + return `${Output.yellow(str)}`; + } + case 4: { + const val = rowCursor.readU32(); + const str = val.toString(16).padStart(8, '0'); + return `${Output.yellow(str)}`; + } + case 8: { + const val = rowCursor.readU64(); + const str = val.toString(16).padStart(16, '0'); + return `${Output.yellow(str)}`; + } + default: { + throw new Error(`invalid width: ${width}`); + } } - } - }); + }, + ); if (width === 1) { - const hexDigits = [...Array(ROW_WIDTH).keys()] + const hexDigits = [...Array(DumpCmdLet.ROW_WIDTH).keys()] .map(i => { const rowCursor = rowAddress.add(i); const limit = address.add(length); @@ -135,7 +139,7 @@ export class DumpCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(DumpCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/data/string.ts b/src/cmdlets/data/string.ts index d87f478..321f4df 100644 --- a/src/cmdlets/data/string.ts +++ b/src/cmdlets/data/string.ts @@ -5,17 +5,17 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Mem } from '../../memory/mem.js'; -const MAX_STRING_LENGTH: number = 1024; -const USAGE: string = `Usage: ds - -ds address - show string - adress the address/symbol to show`; - export class DumpStringCmdLet extends CmdLet { name = 'ds'; category = 'data'; help = 'dump string'; + private static readonly MAX_STRING_LENGTH: number = 1024; + private static readonly USAGE: string = `Usage: ds + +ds address - show string + adress the address/symbol to show`; + public runSync(tokens: Token[]): Var { const vars = this.transform(tokens, [this.parseVar]); if (vars === null) return this.usage(); @@ -27,7 +27,7 @@ export class DumpStringCmdLet extends CmdLet { private dump(arg: Var) { const name = arg.getLiteral(); const address = arg.toPointer(); - const length = MAX_STRING_LENGTH; + const length = DumpStringCmdLet.MAX_STRING_LENGTH; let bytes: Uint8Array = new Uint8Array(0); let lastError: Error | null = null; while (length > 0) { @@ -70,7 +70,7 @@ export class DumpStringCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(DumpStringCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/development/debug.ts b/src/cmdlets/development/debug.ts index 2964a96..ca766b8 100644 --- a/src/cmdlets/development/debug.ts +++ b/src/cmdlets/development/debug.ts @@ -3,14 +3,14 @@ import { Output } from '../../io/output.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: debug -debug - toggle debug mode`; - export class DebugCmdLet extends CmdLet { name = 'debug'; category = 'development'; help = 'toggle debug mode'; + private static readonly USAGE: string = `Usage: debug +debug - toggle debug mode`; + public runSync(_tokens: Token[]): Var { const debug = !Output.getDebugging(); if (debug) { @@ -24,7 +24,7 @@ export class DebugCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(DebugCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/development/js.ts b/src/cmdlets/development/js.ts index 0ad2acc..72e4dd4 100644 --- a/src/cmdlets/development/js.ts +++ b/src/cmdlets/development/js.ts @@ -66,16 +66,16 @@ import { TraceCoverageCmdLet, } from '../trace/trace.js'; -const USAGE: string = `Usage: js - -js path - load commandlet JS script - path the absolute path of the commandlet script to load (note that paths with spaces must be quoted)`; - export class JsCmdLet extends CmdLet { name = 'js'; category = 'development'; help = 'load script'; + private static readonly USAGE: string = `Usage: js + +js path - load commandlet JS script + path the absolute path of the commandlet script to load (note that paths with spaces must be quoted)`; + public runSync(tokens: Token[]): Var { const vars = this.transform(tokens, [this.parseLiteral]); if (vars === null) return this.usage(); @@ -164,7 +164,7 @@ export class JsCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(JsCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/files/cat.ts b/src/cmdlets/files/cat.ts index 721d05f..d5d2aa8 100644 --- a/src/cmdlets/files/cat.ts +++ b/src/cmdlets/files/cat.ts @@ -3,16 +3,16 @@ import { Output } from '../../io/output.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: cat - -cat file - dump file - file the file to dump`; - export class CatCmdLet extends CmdLet { name = 'cat'; category = 'files'; help = 'dump a file'; + private static readonly USAGE: string = `Usage: cat + +cat file - dump file + file the file to dump`; + public runSync(tokens: Token[]): Var { const vars = this.transform(tokens, [this.parseLiteral]); if (vars === null) return this.usage(); @@ -32,7 +32,7 @@ export class CatCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(CatCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/files/fd.ts b/src/cmdlets/files/fd.ts index 87504fa..2938362 100644 --- a/src/cmdlets/files/fd.ts +++ b/src/cmdlets/files/fd.ts @@ -15,12 +15,6 @@ enum DtType { DT_WHT = 14 /* The file is a BSD whiteout. */, } -const USAGE: string = `Usage: fd -fd - show all the open file descriptors for the process - -fd idx - show the given file descriptor - idx the number of file descriptor to show`; - type Fds = { [key: number]: string; }; @@ -30,6 +24,12 @@ export class FdCmdLet extends CmdLet { category = 'files'; help = 'display file descriptors'; + private static readonly USAGE: string = `Usage: fd +fd - show all the open file descriptors for the process + +fd idx - show the given file descriptor + idx the number of file descriptor to show`; + private static readonly PATH_MAX: number = 4096; private static readonly F_GETFD: number = 1; private static readonly F_GETPATH: number = 50; @@ -230,7 +230,7 @@ export class FdCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(FdCmdLet.USAGE); return Var.ZERO; } diff --git a/src/cmdlets/files/src.ts b/src/cmdlets/files/src.ts index a7cdd23..f4346d7 100644 --- a/src/cmdlets/files/src.ts +++ b/src/cmdlets/files/src.ts @@ -7,16 +7,16 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Vars } from '../../vars/vars.js'; -const USAGE: string = `Usage: src - -src path - run commands from file - path the absolute path of the file to load (note that paths with spaces must be quoted)`; - export class SrcCmdLet extends CmdLet { name = 'src'; category = 'files'; help = 'run commands from file'; + private static readonly USAGE: string = `Usage: src + +src path - run commands from file + path the absolute path of the file to load (note that paths with spaces must be quoted)`; + private static lastPath: string | null = null; public static loadInitScript(path: string) { @@ -74,7 +74,7 @@ export class SrcCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(SrcCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/math/math.ts b/src/cmdlets/math/math.ts index b51a074..91ea2dd 100644 --- a/src/cmdlets/math/math.ts +++ b/src/cmdlets/math/math.ts @@ -4,12 +4,12 @@ import { Format } from '../../misc/format.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const HEX_LABEL: string = 'HEXADECIMAL'; -const DEC_LABEL: string = 'DECIMAL'; - abstract class BinaryOpCmdLet extends CmdLet { category = 'math'; + private static readonly HEX_LABEL: string = 'HEXADECIMAL'; + private static readonly DEC_LABEL: string = 'DECIMAL'; + protected abstract OPERATION: string; protected abstract op(op0: UInt64, op1: UInt64): UInt64; @@ -43,14 +43,26 @@ ${this.name} op1 op2 - ${this.OPERATION} two values together const h0 = Format.toHexString(op0); const h1 = Format.toHexString(op1); const hv = Format.toHexString(val); - const hMax = [HEX_LABEL, DEC_LABEL, h0, h1, hv] + const hMax = [ + BinaryOpCmdLet.HEX_LABEL, + BinaryOpCmdLet.DEC_LABEL, + h0, + h1, + hv, + ] .map(x => x.length) .reduce((max, curr) => Math.max(max, curr)); const d0 = Format.toDecString(op0); const d1 = Format.toDecString(op1); const dv = Format.toDecString(val); - const dMax = [HEX_LABEL, DEC_LABEL, d0, d1, dv] + const dMax = [ + BinaryOpCmdLet.HEX_LABEL, + BinaryOpCmdLet.DEC_LABEL, + d0, + d1, + dv, + ] .map(x => x.length) .reduce((max, curr) => Math.max(max, curr)); @@ -61,7 +73,7 @@ ${this.name} op1 op2 - ${this.OPERATION} two values together Output.writeln(); Output.writeln( - `${pad} ${HEX_LABEL.padStart(hMax, ' ')}${gap}${pad} ${DEC_LABEL.padStart(dMax, ' ')}`, + `${pad} ${BinaryOpCmdLet.HEX_LABEL.padStart(hMax, ' ')}${gap}${pad} ${BinaryOpCmdLet.DEC_LABEL.padStart(dMax, ' ')}`, ); Output.writeln( `${pad} ${Output.blue(h0.padStart(hMax, ' '))}${gap}${pad} ${Output.blue(d0.padStart(dMax, ' '))}`, @@ -81,6 +93,9 @@ ${this.name} op1 op2 - ${this.OPERATION} two values together abstract class UnaryOpCmdLet extends CmdLet { category = 'math'; + private static readonly HEX_LABEL: string = 'HEXADECIMAL'; + private static readonly DEC_LABEL: string = 'DECIMAL'; + protected abstract OPERATION: string; protected abstract op(op0: UInt64): UInt64; @@ -112,13 +127,13 @@ ${this.name} op - perform a ${this.OPERATION} operation on an operand protected output(op0: UInt64, val: UInt64) { const h0 = Format.toHexString(op0); const hv = Format.toHexString(val); - const hMax = [HEX_LABEL, DEC_LABEL, h0, hv] + const hMax = [UnaryOpCmdLet.HEX_LABEL, UnaryOpCmdLet.DEC_LABEL, h0, hv] .map(x => x.length) .reduce((max, curr) => Math.max(max, curr)); const d0 = Format.toDecString(op0); const dv = Format.toDecString(val); - const dMax = [HEX_LABEL, DEC_LABEL, d0, dv] + const dMax = [UnaryOpCmdLet.HEX_LABEL, UnaryOpCmdLet.DEC_LABEL, d0, dv] .map(x => x.length) .reduce((max, curr) => Math.max(max, curr)); @@ -129,7 +144,7 @@ ${this.name} op - perform a ${this.OPERATION} operation on an operand Output.writeln(); Output.writeln( - `${pad} ${HEX_LABEL.padStart(hMax, ' ')}${gap}${pad} ${DEC_LABEL.padStart(dMax, ' ')}`, + `${pad} ${UnaryOpCmdLet.HEX_LABEL.padStart(hMax, ' ')}${gap}${pad} ${UnaryOpCmdLet.DEC_LABEL.padStart(dMax, ' ')}`, ); Output.writeln( `${pad} ${Output.blue(h0.padStart(hMax, ' '))}${gap}${pad} ${Output.blue(d0.padStart(dMax, ' '))}`, diff --git a/src/cmdlets/memory/sym.ts b/src/cmdlets/memory/sym.ts index 7ed4ee0..55e7f3e 100644 --- a/src/cmdlets/memory/sym.ts +++ b/src/cmdlets/memory/sym.ts @@ -5,7 +5,12 @@ import { Format } from '../../misc/format.js'; import { Var } from '../../vars/var.js'; import { Regex } from '../../misc/regex.js'; -const USAGE: string = `Usage: sym +export class SymCmdLet extends CmdLet { + name = 'sym'; + category = 'memory'; + help = 'look up a symbol information'; + + private static readonly USAGE: string = `Usage: sym sym [mod!]name - looks up a symbol based upon a glob mod a glob for the module name @@ -17,11 +22,6 @@ sym name - display address information for a named symbol sym addr - display symbol information associated with an address addr the address of the symbol to lookup`; -export class SymCmdLet extends CmdLet { - name = 'sym'; - category = 'memory'; - help = 'look up a symbol information'; - public runSync(tokens: Token[]): Var { const retWithAddress = this.runShowAddress(tokens); if (retWithAddress !== null) return retWithAddress; @@ -184,7 +184,7 @@ export class SymCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(SymCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/memory/vm.ts b/src/cmdlets/memory/vm.ts index c70b16f..3dccaa8 100644 --- a/src/cmdlets/memory/vm.ts +++ b/src/cmdlets/memory/vm.ts @@ -5,7 +5,12 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Regex } from '../../misc/regex.js'; -const USAGE: string = `Usage: vm +export class VmCmdLet extends CmdLet { + name = 'vm'; + category = 'memory'; + help = 'display virtual memory ranges'; + + private static readonly USAGE: string = `Usage: vm vm - show all mappings @@ -15,11 +20,6 @@ vm address - show mapping for address vm module - show mappings for a module module the name of the module to show mapping information for`; -export class VmCmdLet extends CmdLet { - name = 'vm'; - category = 'memory'; - help = 'display virtual memory ranges'; - public runSync(tokens: Token[]): Var { const retWithAddress = this.runShowAddress(tokens); if (retWithAddress !== null) return retWithAddress; @@ -142,7 +142,7 @@ export class VmCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(VmCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/misc/grep.ts b/src/cmdlets/misc/grep.ts index f21c513..121ded5 100644 --- a/src/cmdlets/misc/grep.ts +++ b/src/cmdlets/misc/grep.ts @@ -3,18 +3,18 @@ import { Output } from '../../io/output.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: grep +export class GrepCmdLet extends CmdLet { + name = 'grep'; + category = 'misc'; + help = 'filter output'; + + private static readonly USAGE: string = `Usage: grep grep - clear output filter grep regex - filter output regex the regex to use to filter the output`; -export class GrepCmdLet extends CmdLet { - name = 'grep'; - category = 'misc'; - help = 'filter output'; - public runSync(tokens: Token[]): Var { const vars = this.transformOptional(tokens, [], [this.parseLiteral]); if (vars === null) return this.usage(); @@ -49,7 +49,7 @@ export class GrepCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(GrepCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/misc/history.ts b/src/cmdlets/misc/history.ts index 3354315..5852cf0 100644 --- a/src/cmdlets/misc/history.ts +++ b/src/cmdlets/misc/history.ts @@ -4,18 +4,18 @@ import { History } from '../../terminal/history.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: h +export class HistoryCmdLet extends CmdLet { + name = 'h'; + category = 'misc'; + help = 'command history'; + + private static readonly USAGE: string = `Usage: h h - show history h index - rerun history item index the index of the item to rerun`; -export class HistoryCmdLet extends CmdLet { - name = 'h'; - category = 'misc'; - help = 'command history'; - public runSync(_tokens: Token[]): Var { throw new Error('not supported'); } @@ -42,7 +42,7 @@ export class HistoryCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(HistoryCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/misc/log.ts b/src/cmdlets/misc/log.ts index 9464395..e13062b 100644 --- a/src/cmdlets/misc/log.ts +++ b/src/cmdlets/misc/log.ts @@ -3,18 +3,18 @@ import { Output } from '../../io/output.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: log +export class LogCmdLet extends CmdLet { + name = 'log'; + category = 'misc'; + help = 'set log file'; + + private static readonly USAGE: string = `Usage: log log - clear log file log file - set log file file the file to log to`; -export class LogCmdLet extends CmdLet { - name = 'log'; - category = 'misc'; - help = 'set log file'; - public runSync(tokens: Token[]): Var { const vars = this.transformOptional(tokens, [], [this.parseLiteral]); if (vars === null) return this.usage(); @@ -41,7 +41,7 @@ export class LogCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(LogCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/misc/print.ts b/src/cmdlets/misc/print.ts index 2231da4..049fd94 100644 --- a/src/cmdlets/misc/print.ts +++ b/src/cmdlets/misc/print.ts @@ -3,17 +3,17 @@ import { Output } from '../../io/output.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: p -p - print an expression - -p exp - print an expression - exp the expression to print`; - export class PrintCmdLet extends CmdLet { name = 'p'; category = 'misc'; help = 'print an expression'; + private static readonly USAGE: string = `Usage: p +p - print an expression + +p exp - print an expression + exp the expression to print`; + public runSync(tokens: Token[]): Var { if (tokens.length !== 1) return this.usage(); const t = tokens[0] as Token; @@ -29,7 +29,7 @@ export class PrintCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(PrintCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/misc/sh.ts b/src/cmdlets/misc/sh.ts index 24095a5..85e28d9 100644 --- a/src/cmdlets/misc/sh.ts +++ b/src/cmdlets/misc/sh.ts @@ -5,25 +5,25 @@ import { Token } from '../../io/token.js'; import { Format } from '../../misc/format.js'; import { Var } from '../../vars/var.js'; -const INT_SIZE: number = 4; -const PIPE_READ_OFFSET: number = 0; -const PIPE_WRITE_OFFSET: number = 4; -const STDIN_FILENO: number = 0; -const STDOUT_FILENO: number = 1; -const STDERR_FILENO: number = 2; -const READ_SIZE: number = 4096; -const WNOHANG: number = 1; - type Pipe = { readFd: number; writeFd: number }; -const USAGE: string = `Usage: fd -sh - run a shell`; - export class ShCmdLet extends CmdLet { name = 'sh'; category = 'misc'; help = 'run a shell'; + private static readonly USAGE: string = `Usage: fd +sh - run a shell`; + + private static readonly INT_SIZE: number = 4; + private static readonly PIPE_READ_OFFSET: number = 0; + private static readonly PIPE_WRITE_OFFSET: number = 4; + private static readonly STDIN_FILENO: number = 0; + private static readonly STDOUT_FILENO: number = 1; + private static readonly STDERR_FILENO: number = 2; + private static readonly READ_SIZE: number = 4096; + private static readonly WNOHANG: number = 1; + private pGetEnv: NativePointer | null = null; private pPipe: NativePointer | null = null; private pFork: NativePointer | null = null; @@ -114,13 +114,13 @@ export class ShCmdLet extends CmdLet { private createPipe(): Pipe { if (this.fnPipe === null) throw new Error('failed to find necessary native functions'); - const pipes = Memory.alloc(INT_SIZE * 2); + const pipes = Memory.alloc(ShCmdLet.INT_SIZE * 2); const { value: pipeRet, errno: pipeErrno } = this.fnPipe( pipes, ) as UnixSystemFunctionResult; if (pipeRet !== 0) throw new Error(`failed to pipe, errno: ${pipeErrno}`); - const readFd = pipes.add(PIPE_READ_OFFSET).readInt(); - const writeFd = pipes.add(PIPE_WRITE_OFFSET).readInt(); + const readFd = pipes.add(ShCmdLet.PIPE_READ_OFFSET).readInt(); + const writeFd = pipes.add(ShCmdLet.PIPE_WRITE_OFFSET).readInt(); return { readFd, writeFd }; } @@ -151,23 +151,23 @@ export class ShCmdLet extends CmdLet { const { value: dup2InRet, errno: dup2InErrno } = this.fnDup2( toChildReadFd, - STDIN_FILENO, + ShCmdLet.STDIN_FILENO, ) as UnixSystemFunctionResult; - if (dup2InRet !== STDIN_FILENO) + if (dup2InRet !== ShCmdLet.STDIN_FILENO) throw new Error(`failed to dup2(stdin), errno: ${dup2InErrno}`); const { value: dup2OutRet, errno: dup2OutErrno } = this.fnDup2( toParentWriteFd, - STDOUT_FILENO, + ShCmdLet.STDOUT_FILENO, ) as UnixSystemFunctionResult; - if (dup2OutRet !== STDOUT_FILENO) + if (dup2OutRet !== ShCmdLet.STDOUT_FILENO) throw new Error(`failed to dup2(stdout), errno: ${dup2OutErrno}`); const { value: dup2ErrRet, errno: dup2ErrErrno } = this.fnDup2( toParentWriteFd, - STDERR_FILENO, + ShCmdLet.STDERR_FILENO, ) as UnixSystemFunctionResult; - if (dup2ErrRet !== STDERR_FILENO) + if (dup2ErrRet !== ShCmdLet.STDERR_FILENO) throw new Error(`failed to dup2(stderr), errno: ${dup2ErrErrno}`); if (command.length === 0) throw new Error('empty command'); @@ -236,9 +236,9 @@ export class ShCmdLet extends CmdLet { Output.debug(`reading pid: ${childPid}`); for ( - let buf = await input.read(READ_SIZE); + let buf = await input.read(ShCmdLet.READ_SIZE); buf.byteLength !== 0; - buf = await input.read(READ_SIZE) + buf = await input.read(ShCmdLet.READ_SIZE) ) { const str: string = Format.toTextString(buf); Output.write(str); @@ -246,12 +246,12 @@ export class ShCmdLet extends CmdLet { Output.debug(`waiting pid: ${childPid}`); - const pStatus = Memory.alloc(INT_SIZE); + const pStatus = Memory.alloc(ShCmdLet.INT_SIZE); const { value: waitRet, errno: waitErrno } = this.fnWaitPid( childPid, pStatus, - WNOHANG, + ShCmdLet.WNOHANG, ) as UnixSystemFunctionResult; if (waitRet < 0) throw new Error(`failed to waitpid ${waitRet}, errno: ${waitErrno}`); @@ -288,7 +288,7 @@ export class ShCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(ShCmdLet.USAGE); return Var.ZERO; } diff --git a/src/cmdlets/misc/var.ts b/src/cmdlets/misc/var.ts index 33b46ca..2e9d47b 100644 --- a/src/cmdlets/misc/var.ts +++ b/src/cmdlets/misc/var.ts @@ -4,7 +4,12 @@ import { Vars } from '../../vars/vars.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: v +export class VarCmdLet extends CmdLet { + name = 'v'; + category = 'misc'; + help = 'variable management'; + + private static readonly USAGE: string = `Usage: v v - show the values of all variables v name - display the value of a named variable @@ -17,11 +22,6 @@ v name value - assign a value to a variable v name ${CmdLet.DELETE_CHAR} - delete a variable name the name of the variable to delete`; -export class VarCmdLet extends CmdLet { - name = 'v'; - category = 'misc'; - help = 'variable management'; - public runSync(tokens: Token[]): Var { const retWithNameAndHash = this.runDelete(tokens); if (retWithNameAndHash !== null) return retWithNameAndHash; @@ -92,7 +92,7 @@ export class VarCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(VarCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/modules/ld.ts b/src/cmdlets/modules/ld.ts index c92c049..a02a5fe 100644 --- a/src/cmdlets/modules/ld.ts +++ b/src/cmdlets/modules/ld.ts @@ -3,18 +3,18 @@ import { Output } from '../../io/output.js'; import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: ld +export class LdCmdLet extends CmdLet { + name = 'ld'; + category = 'modules'; + help = 'load modules'; + + private static readonly USAGE: string = `Usage: ld ld - load a module ld path - load a module path the absolute path of the module to load (note that paths with spaces must be quoted)`; -export class LdCmdLet extends CmdLet { - name = 'ld'; - category = 'modules'; - help = 'load modules'; - public runSync(tokens: Token[]): Var { const vars = this.transform(tokens, [this.parseLiteral]); if (vars === null) return this.usage(); @@ -32,7 +32,7 @@ export class LdCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(LdCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/modules/mod.ts b/src/cmdlets/modules/mod.ts index b765fb7..1bb16f3 100644 --- a/src/cmdlets/modules/mod.ts +++ b/src/cmdlets/modules/mod.ts @@ -5,7 +5,12 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Regex } from '../../misc/regex.js'; -const USAGE: string = `Usage: mod +export class ModCmdLet extends CmdLet { + name = 'mod'; + category = 'modules'; + help = 'display module information'; + + private static readonly USAGE: string = `Usage: mod mod - show all modules @@ -15,11 +20,6 @@ mod address - show module for address mod name - show named module name the name of the module to show information for`; -export class ModCmdLet extends CmdLet { - name = 'mod'; - category = 'modules'; - help = 'display module information'; - public runSync(tokens: Token[]): Var { const retWithAddress = this.runShowAddress(tokens); if (retWithAddress !== null) return retWithAddress; @@ -111,7 +111,7 @@ export class ModCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(ModCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/thread/bt.ts b/src/cmdlets/thread/bt.ts index 0f94111..4b4ef5f 100644 --- a/src/cmdlets/thread/bt.ts +++ b/src/cmdlets/thread/bt.ts @@ -5,17 +5,17 @@ import { Token } from '../../io/token.js'; import { Var } from '../../vars/var.js'; import { Format } from '../../misc/format.js'; -const USAGE: string = `Usage: bt -bt - show the backtrace for the current thread in a breakpoint - -bt name - show backtrace for thread - thread the name of the thread to show backtrace for`; - export class BtCmdLet extends CmdLet { name = 'bt'; category = 'thread'; help = 'display backtrace information'; + private static readonly USAGE: string = `Usage: bt +bt - show the backtrace for the current thread in a breakpoint + +bt name - show backtrace for thread + thread the name of the thread to show backtrace for`; + public runSync(tokens: Token[]): Var { const retWithId = this.runShowId(tokens); if (retWithId !== null) return retWithId; @@ -102,7 +102,7 @@ export class BtCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(BtCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/thread/hot.ts b/src/cmdlets/thread/hot.ts index 338d40e..1823cf3 100644 --- a/src/cmdlets/thread/hot.ts +++ b/src/cmdlets/thread/hot.ts @@ -6,87 +6,87 @@ import { Var } from '../../vars/var.js'; import { Format } from '../../misc/format.js'; import { Input } from '../../io/input.js'; -const MAX_DURATION: number = 10; -const USAGE: string = `Usage: t +export class HotCmdLet extends CmdLet { + name = 'hot'; + category = 'thread'; + help = 'display thread execution time information'; + + private static readonly MAX_DURATION: number = 10; + private static readonly USAGE: string = `Usage: t hot * - show execution time for all threads hot * duration - show execution time for all threads during a given time period - duration the duration in seconds (maximum of ${MAX_DURATION}) over which to time the threads + duration the duration in seconds (maximum of ${HotCmdLet.MAX_DURATION}) over which to time the threads hot id - show execution time for given thread id the id of the thread to show information for hot id duration - show execution time for given thread during a given time period id the id of the thread to show information for - duration the duration in seconds (maximum of ${MAX_DURATION}) over which to time the thread + duration the duration in seconds (maximum of ${HotCmdLet.MAX_DURATION}) over which to time the thread hot name - show execution time for given thread name the name of the thread to show information for hot name duration - show execution time for given thread during a given time period name the name of the thread to show information for - duration the duration in seconds (maximum of ${MAX_DURATION}) over which to time the thread`; - -const FIELD_NAMES = [ - 'pid', - 'comm', - 'state', - 'ppid', - 'pgrp', - 'session', - 'tty_nr', - 'tpgid', - 'flags', - 'minflt', - 'cminflt', - 'majflt', - 'cmajflt', - 'utime', - 'stime', - 'cutime', - 'cstime', - 'priority', - 'nice', - 'num_threads', - 'itrealvalue', - 'starttime', - 'vsize', - 'rss', - 'rsslim', - 'startcode', - 'endcode', - 'startstack', - 'kstkesp', - 'kstkeip', - 'signal', - 'blocked', - 'sigignore', - 'sigcatch', - 'wchan', - 'nswap', - 'cnswap', - 'exit_signal', - 'processor', - 'rt_priority', - 'policy', - 'delayacct_blkio_ticks', - 'guest_time', - 'cguest_time', - 'start_data', - 'end_data', - 'start_brk', - 'arg_start', - 'arg_end', - 'env_start', - 'env_end', - 'exit_code', -]; - -export class HotCmdLet extends CmdLet { - name = 'hot'; - category = 'thread'; - help = 'display thread execution time information'; + duration the duration in seconds (maximum of ${HotCmdLet.MAX_DURATION}) over which to time the thread`; + + private static readonly FIELD_NAMES = [ + 'pid', + 'comm', + 'state', + 'ppid', + 'pgrp', + 'session', + 'tty_nr', + 'tpgid', + 'flags', + 'minflt', + 'cminflt', + 'majflt', + 'cmajflt', + 'utime', + 'stime', + 'cutime', + 'cstime', + 'priority', + 'nice', + 'num_threads', + 'itrealvalue', + 'starttime', + 'vsize', + 'rss', + 'rsslim', + 'startcode', + 'endcode', + 'startstack', + 'kstkesp', + 'kstkeip', + 'signal', + 'blocked', + 'sigignore', + 'sigcatch', + 'wchan', + 'nswap', + 'cnswap', + 'exit_signal', + 'processor', + 'rt_priority', + 'policy', + 'delayacct_blkio_ticks', + 'guest_time', + 'cguest_time', + 'start_data', + 'end_data', + 'start_brk', + 'arg_start', + 'arg_end', + 'env_start', + 'env_end', + 'exit_code', + ]; private static readonly _SC_CLK_TCK: number = 2; private pSysConf: NativePointer | null = null; @@ -154,7 +154,7 @@ export class HotCmdLet extends CmdLet { const v = token.toVar(); if (v === null) return null; const num = v.toU64().toNumber(); - if (num > MAX_DURATION) return null; + if (num > HotCmdLet.MAX_DURATION) return null; return num; } @@ -229,12 +229,14 @@ export class HotCmdLet extends CmdLet { const data = File.readAllText(path); Output.debug(`data: ${data}`); const fields = data.split(' '); - const stats: Record = FIELD_NAMES.reduce< - Record - >((acc, key, index) => { - acc[key] = fields[index]; - return acc; - }, {}); + const stats: Record = + HotCmdLet.FIELD_NAMES.reduce>( + (acc, key, index) => { + acc[key] = fields[index]; + return acc; + }, + {}, + ); Object.keys(stats).forEach((key, index) => { const val = stats[key]; @@ -324,7 +326,7 @@ export class HotCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(HotCmdLet.USAGE); return Var.ZERO; } diff --git a/src/cmdlets/thread/thread.ts b/src/cmdlets/thread/thread.ts index a18d981..dc09307 100644 --- a/src/cmdlets/thread/thread.ts +++ b/src/cmdlets/thread/thread.ts @@ -4,18 +4,18 @@ import { Token } from '../../io/token.js'; import { Format } from '../../misc/format.js'; import { Var } from '../../vars/var.js'; -const USAGE: string = `Usage: t +export class ThreadCmdLet extends CmdLet { + name = 't'; + category = 'thread'; + help = 'display thread information'; + + private static readonly USAGE: string = `Usage: t t - show all threads t name - show named thread name the name of the thread to show information for`; -export class ThreadCmdLet extends CmdLet { - name = 't'; - category = 'thread'; - help = 'display thread information'; - public runSync(tokens: Token[]): Var { const retWithId = this.runShowId(tokens); if (retWithId !== null) return retWithId; @@ -106,7 +106,7 @@ export class ThreadCmdLet extends CmdLet { } public usage(): Var { - Output.writeln(USAGE); + Output.writeln(ThreadCmdLet.USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/trace/trace.ts b/src/cmdlets/trace/trace.ts index 28c2287..cc040ac 100644 --- a/src/cmdlets/trace/trace.ts +++ b/src/cmdlets/trace/trace.ts @@ -5,7 +5,7 @@ import { Format } from '../../misc/format.js'; import { BlockTrace } from '../../traces/block.js'; import { CallTrace } from '../../traces/call.js'; import { CoverageTrace } from '../../traces/coverage/trace.js'; -import { Trace, Traces } from '../../traces/trace.js'; +import { Trace, TraceData, Traces } from '../../traces/trace.js'; import { Var } from '../../vars/var.js'; abstract class TraceBaseCmdLet extends CmdLet { @@ -235,6 +235,7 @@ abstract class TraceCmdLet extends TraceBaseCmdLet< trace .data() .lines() + .slice(0, TraceData.MAX_LINES) .forEach(l => { Output.writeln(l); }); @@ -245,14 +246,28 @@ abstract class TraceCmdLet extends TraceBaseCmdLet< protected override onStop(trace: Trace, meta: TraceCmdLetMeta): void { if (meta.file === null) return; - + Output.writeln(Output.yellow('processing...')); const lines = trace.data().lines(); - for (const line of lines) { + let last = 0; + Output.write( + `${Output.yellow('progress')} ${Output.blue(last.toString())}${Output.blue('%')}`, + ); + for (let i = 0; i < lines.length; i++) { + const line = lines[i] as string; + const current = Math.floor((i * 100) / lines.length); + if (current > last) { + Output.clearLine(); + Output.write( + `${Output.yellow('progress')} ${Output.blue(current.toString())}${Output.blue('%')}`, + ); + last = current; + } meta.file.write(`${Format.removeColours(line)}\n`); } meta.file.flush(); const size = meta.file.tell(); meta.file.close(); + Output.writeln(); Output.writeln( `\tWriting trace to: ${meta.fileName}, ${Format.toSize(size)} bytes`, ); diff --git a/src/commands/cmdlet.ts b/src/commands/cmdlet.ts index 0ffea98..1550f60 100644 --- a/src/commands/cmdlet.ts +++ b/src/commands/cmdlet.ts @@ -1,9 +1,8 @@ import { Token } from '../io/token.js'; import { Var } from '../vars/var.js'; -const UNLIMITED_CHAR: string = '*'; - export abstract class CmdLet { + private static readonly UNLIMITED_CHAR: string = '*'; public static readonly NUM_CHAR: string = '#'; public static readonly DELETE_CHAR: string = '#'; public abstract readonly category: string; @@ -99,7 +98,7 @@ export abstract class CmdLet { } protected parseNumberOrAll(token: Token): number | null { - if (token.getLiteral() === UNLIMITED_CHAR) return -1; + if (token.getLiteral() === CmdLet.UNLIMITED_CHAR) return -1; const v = token.toVar(); if (v === null) return null; diff --git a/src/io/input.ts b/src/io/input.ts index a3ad5a3..1024f76 100644 --- a/src/io/input.ts +++ b/src/io/input.ts @@ -9,15 +9,15 @@ enum InputState { Csi, } -const QUIT_CHAR: string = 'q'; -const ABORT_CHAR: string = 'x'; -const CLEAR_CHAR: string = 'c'; - export class Input { public static readonly PROMPT: string = '-> '; public static readonly FILTERED_PROMPT: string = '~> '; private static readonly EDIT_PROMPT: string = '. '; + private static readonly QUIT_CHAR: string = 'q'; + private static readonly ABORT_CHAR: string = 'x'; + private static readonly CLEAR_CHAR: string = 'c'; + private static buffer: string = ''; private static state: InputState = InputState.Default; private static interceptLine: InputInterceptLine | null = null; @@ -160,7 +160,7 @@ export class Input { Output.clearLine(); Output.writeln(`- ${line}`); - if (line === QUIT_CHAR) { + if (line === Input.QUIT_CHAR) { /* Notify the commandlet we are done and exit edit mode */ try { edit.done(); @@ -168,7 +168,7 @@ export class Input { this.interceptLine = null; } Output.writeRet(); - } else if (line === CLEAR_CHAR) { + } else if (line === Input.CLEAR_CHAR) { /* Notify the commandlet we cleared and exit edit mode */ try { edit.clear(); @@ -176,7 +176,7 @@ export class Input { this.interceptLine = null; } Output.writeRet(); - } else if (line === ABORT_CHAR) { + } else if (line === Input.ABORT_CHAR) { /* Notify the commandlet we aborted and exit edit mode */ try { edit.abort(); @@ -264,7 +264,7 @@ export class Input { this.interceptRaw = null; } Output.writeln( - `Type '${QUIT_CHAR}' to finish, '${CLEAR_CHAR}' to clear, or '${ABORT_CHAR}' to abort`, + `Type '${Input.QUIT_CHAR}' to finish, '${Input.CLEAR_CHAR}' to clear, or '${Input.ABORT_CHAR}' to abort`, ); this.interceptLine = interceptLine; } diff --git a/src/traces/block.ts b/src/traces/block.ts index 1ed3c36..3c7932a 100644 --- a/src/traces/block.ts +++ b/src/traces/block.ts @@ -3,7 +3,6 @@ import { Format } from '../misc/format.js'; import { TraceBase, TraceData, TraceElement, Traces } from './trace.js'; class BlockTraceData extends TraceData { - private static readonly MAX_BLOCKS = 1024; private trace: ArrayBuffer = new ArrayBuffer(0); private depth: number; @@ -20,11 +19,13 @@ class BlockTraceData extends TraceData { } public lines(): string[] { + Output.debug(Output.yellow('parsing...')); const events = Stalker.parse(this.trace, { annotate: true, stringify: false, }) as StalkerEventFull[]; + Output.debug(Output.yellow('filtering events...')); const filtered = this.filterEvents(events, (e: StalkerEventFull) => { if (e.length !== 3) return null; const [kind, start, _end] = e; @@ -32,6 +33,7 @@ class BlockTraceData extends TraceData { return start as NativePointer; }); + Output.debug(Output.yellow('calculating depths...')); /* Assign a depth to each event */ const depths: { depth: number; @@ -74,11 +76,11 @@ class BlockTraceData extends TraceData { { depth: 0, events: [] }, ); + Output.debug(Output.yellow('filtering depths...')); const elements = this.filterElements(depths.events, this.depth); - const named = this.nameElements(elements).slice( - 0, - BlockTraceData.MAX_BLOCKS, - ); + Output.debug(Output.yellow('finding symbols...')); + const named = this.nameElements(elements); + Output.debug(Output.yellow('formatting...')); const strings = this.elementsToStrings(named); return strings; } diff --git a/src/traces/call.ts b/src/traces/call.ts index e4665fd..2db808b 100644 --- a/src/traces/call.ts +++ b/src/traces/call.ts @@ -3,7 +3,6 @@ import { Format } from '../misc/format.js'; import { TraceBase, TraceData, TraceElement, Traces } from './trace.js'; class CallTraceData extends TraceData { - private static readonly MAX_CALLS = 1024; private trace: ArrayBuffer = new ArrayBuffer(0); private depth: number; @@ -20,11 +19,13 @@ class CallTraceData extends TraceData { } public lines(): string[] { + Output.debug(Output.yellow('parsing...')); const events = Stalker.parse(this.trace, { annotate: true, stringify: false, }) as StalkerEventFull[]; + Output.debug(Output.yellow('filtering events...')); /* Filter events for which we have no module information */ const filtered = this.filterEvents(events, (e: StalkerEventFull) => { if (e.length !== 4) return null; @@ -33,6 +34,7 @@ class CallTraceData extends TraceData { return to as NativePointer; }); + Output.debug(Output.yellow('calculating depths...')); /* Assign a depth to each event */ const depths: { first: boolean; @@ -67,8 +69,11 @@ class CallTraceData extends TraceData { { first: true, depth: 0, events: [] }, ); + Output.debug(Output.yellow('filtering depths...')); const elements = this.filterElements(depths.events, this.depth); - const named = this.nameElements(elements).slice(0, CallTraceData.MAX_CALLS); + Output.debug(Output.yellow('finding symbols...')); + const named = this.nameElements(elements); + Output.debug(Output.yellow('formatting...')); const strings = this.elementsToStrings(named); return strings; } diff --git a/src/traces/trace.ts b/src/traces/trace.ts index f5d540e..70e8cb2 100644 --- a/src/traces/trace.ts +++ b/src/traces/trace.ts @@ -5,6 +5,7 @@ export type TraceElement = { addr: NativePointer; depth: number }; export type NamedElement = { name: string; depth: number }; export abstract class TraceData { + public static readonly MAX_LINES: number = 1024; private static readonly MAX_OFFSET: number = 1024; public abstract append(events: ArrayBuffer): void;