Skip to content

Commit 4b4fb36

Browse files
author
Your Name
committed
Add support for macros
1 parent a1c8472 commit 4b4fb36

7 files changed

Lines changed: 207 additions & 6 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "frida-cshell",
3-
"version": "1.5.1",
3+
"version": "1.5.2",
44
"description": "Frida's CShell",
55
"scripts": {
66
"prepare": "npm run build && npm run version && npm run package && npm run copy",

src/cmdlets/development/js.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import {
6565
TraceUniqueBlockCmdLet,
6666
TraceCoverageCmdLet,
6767
} from '../trace/trace.js';
68+
import { MacroCmdLet } from '../misc/macro.js';
6869

6970
export class JsCmdLet extends CmdLet {
7071
name = 'js';
@@ -117,6 +118,7 @@ js path - load commandlet JS script
117118
LdCmdLet: LdCmdLet,
118119
Line: Line,
119120
LogCmdLet: LogCmdLet,
121+
MacroCmdLet: MacroCmdLet,
120122
Mem: Mem,
121123
MemoryBps: MemoryBps,
122124
ModCmdLet: ModCmdLet,

src/cmdlets/misc/macro.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { CmdLet } from '../../commands/cmdlet.js';
2+
import { Command } from '../../commands/command.js';
3+
import { Input, InputInterceptLine } from '../../io/input.js';
4+
import { Output } from '../../io/output.js';
5+
import { Parser } from '../../io/parser.js';
6+
import { Token } from '../../io/token.js';
7+
import { Macro, Macros } from '../../macros/macros.js';
8+
import { Var } from '../../vars/var.js';
9+
import { Vars } from '../../vars/vars.js';
10+
11+
export class MacroCmdLet extends CmdLet implements InputInterceptLine {
12+
name = 'm';
13+
category = 'misc';
14+
help = 'manage macros';
15+
private static readonly USAGE: string = `Usage: m
16+
m - show all macros
17+
18+
m name - create, modify or display a macro
19+
name the name of the macro
20+
21+
m name ${CmdLet.DELETE_CHAR} - delete a macro
22+
name the name of the macro to delete`;
23+
24+
private current: string | null = null;
25+
private commands: string[] = [];
26+
27+
public runSync(tokens: Token[]): Var {
28+
const retWithNameAndHash = this.runDelete(tokens);
29+
if (retWithNameAndHash !== null) return retWithNameAndHash;
30+
31+
const retWithNameAndPointer = this.runSet(tokens);
32+
if (retWithNameAndPointer !== null) return retWithNameAndPointer;
33+
34+
const retWithName = this.runShow(tokens);
35+
if (retWithName !== null) return retWithName;
36+
37+
return this.usage();
38+
}
39+
40+
private runDelete(tokens: Token[]): Var | null {
41+
const vars = this.transform(tokens, [this.parseLiteral, this.parseDelete]);
42+
if (vars === null) return null;
43+
const [name, _] = vars as [string, string];
44+
45+
const macro = Macros.delete(name);
46+
if (macro === null) {
47+
Output.writeln(`macro ${Output.green(name)} not set`);
48+
} else {
49+
Output.writeln(`deleted macro ${Output.green(name)}`);
50+
}
51+
return Var.ZERO;
52+
}
53+
54+
private runSet(tokens: Token[]): Var | null {
55+
const vars = this.transform(tokens, [this.parseLiteral]);
56+
if (vars === null) return null;
57+
const [name] = vars as [string];
58+
this.commands = [];
59+
this.current = name;
60+
const macro = Macros.get(name);
61+
if (macro === null) {
62+
Output.writeln(`Creating macro '${Output.green(name)}'`);
63+
} else {
64+
Output.writeln(`Modifying macro '${Output.green(name)}'`);
65+
Output.writeln(macro.toString());
66+
}
67+
Input.setInterceptLine(this);
68+
return Var.ZERO;
69+
}
70+
71+
addLine(line: string): void {
72+
this.commands.push(line);
73+
}
74+
75+
clear(): void {
76+
if (this.current != null) {
77+
const macro = new Macro(this.current, []);
78+
Macros.set(macro);
79+
}
80+
}
81+
82+
done(): void {
83+
if (this.current != null) {
84+
const macro = new Macro(this.current, this.commands);
85+
Macros.set(macro);
86+
}
87+
}
88+
89+
abort(): void {}
90+
91+
private runShow(tokens: Token[]): Var | null {
92+
if (tokens.length !== 0) return null;
93+
Output.writeln(Output.blue('Macros:'));
94+
for (const macro of Macros.all()) {
95+
Output.writeln(`${Output.green(macro.name)}:`, true);
96+
97+
Output.writeln(macro.toString(), true);
98+
99+
Output.writeln();
100+
}
101+
return Var.ZERO;
102+
}
103+
104+
public usage(): Var {
105+
Output.writeln(MacroCmdLet.USAGE);
106+
return Var.ZERO;
107+
}
108+
109+
public static run(macro: Macro): Var {
110+
let ret = Var.ZERO;
111+
for (const command of macro.commands) {
112+
if (command.length === 0) continue;
113+
if (command.charAt(0) === '#') continue;
114+
115+
Output.writeln(`${Output.bold(Input.PROMPT)}${command}`);
116+
117+
if (command.trim().length === 0) continue;
118+
119+
const parser = new Parser(command.toString());
120+
const tokens = parser.tokenize();
121+
ret = Command.runSync(tokens);
122+
Vars.setRet(ret);
123+
Output.writeRet();
124+
Output.writeln();
125+
}
126+
return ret;
127+
}
128+
}

src/commands/cmdlets.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import {
5656
TraceUniqueBlockCmdLet,
5757
TraceCoverageCmdLet,
5858
} from '../cmdlets/trace/trace.js';
59+
import { MacroCmdLet } from '../cmdlets/misc/macro.js';
5960

6061
export class CmdLets {
6162
private static byName: Map<string, CmdLet> = new Map<string, CmdLet>();
@@ -89,6 +90,7 @@ export class CmdLets {
8990
this.registerCmdletType(LogCmdLet);
9091
this.registerCmdletType(OrCmdLet);
9192
this.registerCmdletType(ReadCmdLet);
93+
this.registerCmdletType(MacroCmdLet);
9294
this.registerCmdletType(ModCmdLet);
9395
this.registerCmdletType(MulCmdLet);
9496
this.registerCmdletType(NotCmdLet);

src/commands/command.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@ import { Format } from '../misc/format.js';
44
import { Var } from '../vars/var.js';
55
import { Token } from '../io/token.js';
66
import { CmdLet } from './cmdlet.js';
7+
import { Macro, Macros } from '../macros/macros.js';
8+
import { MacroCmdLet } from '../cmdlets/misc/macro.js';
79

810
export class Command {
11+
private static readonly MACRO_PREFIX: string = '!';
912
public static async run(tokens: Token[]): Promise<Var> {
1013
const cmdlet = this.getCmdlet(tokens);
11-
if (cmdlet === null) {
12-
return this.runFunction(tokens);
13-
} else {
14+
if (cmdlet !== null) {
1415
return cmdlet.run(tokens.slice(1));
1516
}
17+
18+
const macro = this.getMacro(tokens);
19+
if (macro !== null) {
20+
return MacroCmdLet.run(macro);
21+
}
22+
23+
return this.runFunction(tokens);
1624
}
1725

1826
public static runSync(tokens: Token[]): Var {
@@ -30,6 +38,17 @@ export class Command {
3038
return CmdLets.getByName(t0.getLiteral());
3139
}
3240

41+
private static getMacro(tokens: Token[]): Macro | null {
42+
if (tokens.length === 0) throw new Error('failed to tokenize macro');
43+
const t0 = tokens[0] as Token;
44+
const name = t0.getLiteral();
45+
if (!name.startsWith(Command.MACRO_PREFIX)) return null;
46+
if (name.length === 1) throw new Error('macro name not supplied');
47+
const macro = Macros.get(name.slice(1));
48+
if (macro === null) throw new Error(`failed to recognozie macro ${Output.green(name)}`);
49+
return macro;
50+
}
51+
3352
private static runFunction(tokens: Token[]): Var {
3453
if (tokens.length === 0) throw new Error('failed to tokenize command');
3554
const t0 = tokens[0] as Token;

src/macros/macros.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Output } from '../io/output.js';
2+
3+
export class Macro {
4+
private readonly _name: string;
5+
private readonly _commands: string[] = [];
6+
7+
constructor(name: string, commands: string[]) {
8+
this._name = name;
9+
this._commands = commands;
10+
}
11+
12+
public get name(): string {
13+
return this._name;
14+
}
15+
16+
public get commands(): string[] {
17+
return this._commands;
18+
}
19+
20+
public toString(): string {
21+
return this._commands
22+
.map(l => Output.writeln(` - ${Output.yellow(l)}`))
23+
.join('\n');
24+
}
25+
}
26+
27+
export class Macros {
28+
private static map: Map<string, Macro> = new Map<string, Macro>();
29+
30+
public static get(name: string): Macro | null {
31+
return this.map.get(name) ?? null;
32+
}
33+
34+
public static set(macro: Macro) {
35+
this.map.set(macro.name, macro);
36+
}
37+
38+
public static delete(name: string): Macro | null {
39+
const macro = this.map.get(name);
40+
if (macro === undefined) return null;
41+
this.map.delete(name);
42+
return macro;
43+
}
44+
45+
public static all(): Macro[] {
46+
return Array.from(this.map.entries())
47+
.sort(([k1, _v1], [k2, _v2]) => k1.localeCompare(k2))
48+
.map(([k, v]) => v);
49+
}
50+
}

0 commit comments

Comments
 (0)