Skip to content

Commit 234feda

Browse files
author
Your Name
committed
Added support for function tracing
1 parent 152c14a commit 234feda

File tree

8 files changed

+446
-108
lines changed

8 files changed

+446
-108
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"[c]": {
66
"editor.defaultFormatter": "ms-vscode.cpptools"
77
},
8-
"cmake.configureOnOpen": false
8+
"cmake.configureOnOpen": false,
9+
"makefile.configureOnOpen": false
910
}

assets/target/target.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,104 @@
55
#include <string.h>
66
#include <unistd.h>
77

8+
typedef unsigned int uint;
9+
810
__attribute__((noinline)) void my_memcpy(void *dest, const void *src, size_t n)
911
{
1012
memcpy(dest, src, n);
1113
}
1214

1315
const char test[] = "TEST_STRING";
1416

17+
void my_h(uint i)
18+
{
19+
printf("chain: %u\n", i);
20+
}
21+
22+
void my_g(uint i)
23+
{
24+
if ((i % 2) == 0)
25+
{
26+
my_h(1);
27+
}
28+
else
29+
{
30+
my_h(2);
31+
}
32+
}
33+
34+
void my_f(uint i)
35+
{
36+
if ((i % 2) == 0)
37+
{
38+
my_g(1);
39+
}
40+
else
41+
{
42+
my_g(2);
43+
}
44+
}
45+
46+
void my_e(uint i)
47+
{
48+
if ((i % 2) == 0)
49+
{
50+
my_f(1);
51+
}
52+
else
53+
{
54+
my_f(2);
55+
}
56+
}
57+
58+
void my_d(uint i)
59+
{
60+
if ((i % 2) == 0)
61+
{
62+
my_e(1);
63+
}
64+
else
65+
{
66+
my_e(2);
67+
}
68+
}
69+
70+
void my_c(uint i)
71+
{
72+
if ((i % 2) == 0)
73+
{
74+
my_d(1);
75+
}
76+
else
77+
{
78+
my_d(2);
79+
}
80+
}
81+
82+
void my_b(uint i)
83+
{
84+
if ((i % 2) == 0)
85+
{
86+
my_c(1);
87+
}
88+
else
89+
{
90+
my_c(2);
91+
}
92+
}
93+
94+
void my_a(uint i)
95+
{
96+
if ((i % 2) == 0)
97+
{
98+
my_b(1);
99+
}
100+
else
101+
{
102+
my_b(2);
103+
}
104+
}
105+
15106
int main(int argc, char **argv, char **envp)
16107
{
17108
int fd = open("/dev/null", O_RDWR);
@@ -22,6 +113,9 @@ int main(int argc, char **argv, char **envp)
22113

23114
while (true)
24115
{
116+
117+
my_a(rand());
118+
25119
char *buf = malloc(sizeof(test));
26120

27121
if (buf == NULL)

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.1.9",
3+
"version": "1.2.0",
44
"description": "Frida's CShell",
55
"scripts": {
66
"prepare": "npm run build && npm run version && npm run package && npm run copy",

src/breakpoints/bp.ts

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export enum BpType {
2020
Instruction = 'instruction',
2121
FunctionEntry = 'function entry',
2222
FunctionExit = 'function exit',
23+
FunctionTrace = 'function trace',
2324
MemoryRead = 'memory read',
2425
MemoryWrite = 'memory write',
2526
}
@@ -32,10 +33,12 @@ export class Bp {
3233
private _addr: Var | null;
3334
private _literal: string | null;
3435
private _length: number;
36+
private _depth: number;
3537

3638
private _lines: string[] = [];
3739
private _listener: InvocationListener | null;
3840
private _overlay: string | null = null;
41+
private _trace: ArrayBuffer = new ArrayBuffer(0);
3942

4043
public constructor(
4144
type: BpType,
@@ -44,13 +47,15 @@ export class Bp {
4447
addr: Var | null,
4548
literal: string | null,
4649
length: number = 0,
50+
depth: number = 0,
4751
) {
4852
this._type = type;
4953
this._idx = idx;
5054
this._hits = hits;
5155
this._addr = addr;
5256
this._literal = literal;
5357
this._length = length;
58+
this._depth = depth;
5459
this._listener = null;
5560
}
5661

@@ -74,8 +79,8 @@ export class Bp {
7479
case BpType.Instruction:
7580
case BpType.FunctionEntry:
7681
case BpType.FunctionExit:
82+
case BpType.FunctionTrace:
7783
return BpKind.Code;
78-
break;
7984
case BpType.MemoryRead:
8085
case BpType.MemoryWrite:
8186
return BpKind.Memory;
@@ -116,11 +121,155 @@ export class Bp {
116121
},
117122
});
118123
break;
124+
case BpType.FunctionTrace:
125+
this._listener = Interceptor.attach(addr.toPointer(), {
126+
onEnter() {
127+
bp.startCoverage(this.threadId, this.context);
128+
},
129+
onLeave(_retVal) {
130+
bp.stopCoverage(this.threadId, this.context);
131+
},
132+
});
119133
}
120134

121135
Interceptor.flush();
122136
}
123137

138+
private displayEvents() {
139+
const events = Stalker.parse(this._trace, {
140+
annotate: true,
141+
stringify: false,
142+
}) as StalkerEventFull[];
143+
144+
let currentDepth = 0;
145+
let first = true;
146+
for (const e of events) {
147+
switch (e.length) {
148+
case 3: {
149+
const [kind, start, _end] = e;
150+
if (currentDepth >= this._depth) break;
151+
if (kind !== 'block') break;
152+
const name = this.getAddressString(start as NativePointer);
153+
if (name === null) break;
154+
if (first) {
155+
currentDepth = 0;
156+
first = false;
157+
}
158+
if (currentDepth > 0) {
159+
Output.write('\t'.repeat(currentDepth));
160+
}
161+
Output.writeln(name);
162+
break;
163+
}
164+
case 4: {
165+
const [kind, _from, _to, _depth] = e;
166+
if (kind === 'call') {
167+
currentDepth += 1;
168+
} else if (kind === 'ret') {
169+
if (currentDepth > 0) {
170+
currentDepth -= 1;
171+
}
172+
}
173+
break;
174+
}
175+
default:
176+
break;
177+
}
178+
}
179+
}
180+
181+
private getAddressString(address: NativePointer): string | null {
182+
const debug = DebugSymbol.fromAddress(address);
183+
if (debug === null || debug.name === null) {
184+
const module = Process.findModuleByAddress(address);
185+
if (module === null) {
186+
return null;
187+
}
188+
189+
const offset = address.sub(module.base);
190+
const prefix = `${module.name}+0x${offset.toString(16)}`;
191+
return `${Output.green(prefix.padEnd(40, '.'))} ${Output.yellow(Format.toHexString(address))}`;
192+
}
193+
194+
const lookup = DebugSymbol.fromName(debug.name);
195+
let offset = ptr(0);
196+
if (lookup !== null && lookup.address.compare(debug.address) < 0) {
197+
offset = debug.address.sub(lookup.address);
198+
}
199+
const OFFSET_MAX = 1024;
200+
const prefix = debug.moduleName === null ? '' : `${debug.moduleName}!`;
201+
202+
let name = `${prefix}${debug.name}`;
203+
if (offset !== ptr(0) || offset.compare(OFFSET_MAX) < 0) {
204+
name = `${prefix}${debug.name}+0x${offset.toString(16)}`;
205+
}
206+
207+
const symbol = `${Output.green(name.padEnd(40, '.'))} ${Output.yellow(Format.toHexString(debug.address))}`;
208+
if (debug.fileName !== null && debug.lineNumber !== null) {
209+
if (debug.fileName.length !== 0 && debug.lineNumber !== 0) {
210+
return `${symbol} ${Output.blue(debug.fileName)}:${Output.blue(debug.lineNumber.toString())}`;
211+
}
212+
}
213+
return symbol;
214+
}
215+
216+
private startCoverage(threadId: ThreadId, ctx: CpuContext) {
217+
if (this._hits === 0) return;
218+
Output.clearLine();
219+
Output.writeln(Output.yellow('-'.repeat(80)));
220+
Output.write(`${Output.yellow('|')} Start Trace `);
221+
Output.write(`${Output.green(`#${this._idx}`)} `);
222+
Output.write(`[${this._type}] `);
223+
Output.write(`${Output.yellow(this._literal ?? '')} `);
224+
Output.write(`@ $pc=${Output.blue(Format.toHexString(ctx.pc))} `);
225+
Output.write(`$tid=${threadId}, depth=${this._depth}`);
226+
Output.writeln();
227+
Output.writeln(Output.yellow('-'.repeat(80)));
228+
229+
this._trace = new ArrayBuffer(0);
230+
231+
Stalker.follow(threadId, {
232+
events: {
233+
call: true,
234+
ret: true,
235+
block: true,
236+
},
237+
onReceive: (events: ArrayBuffer) => {
238+
const newBuffer = new Uint8Array(
239+
this._trace.byteLength + events.byteLength,
240+
);
241+
newBuffer.set(new Uint8Array(this._trace), 0);
242+
newBuffer.set(new Uint8Array(events), this._trace.byteLength);
243+
this._trace = newBuffer.buffer as ArrayBuffer;
244+
},
245+
});
246+
}
247+
248+
private stopCoverage(threadId: ThreadId, ctx: CpuContext) {
249+
if (this._hits === 0) return;
250+
else if (this._hits > 0) this._hits--;
251+
try {
252+
Stalker.unfollow(threadId);
253+
Stalker.flush();
254+
Output.writeln(Output.blue('-'.repeat(80)));
255+
this.displayEvents();
256+
Output.writeln(Output.blue('-'.repeat(80)));
257+
Output.clearLine();
258+
Output.writeln(Output.yellow('-'.repeat(80)));
259+
Output.write(`${Output.yellow('|')} Stop Trace `);
260+
Output.write(`${Output.green(`#${this._idx}`)} `);
261+
Output.write(`[${this._type}] `);
262+
Output.write(`${Output.yellow(this._literal ?? '')} `);
263+
Output.write(`@ $pc=${Output.blue(Format.toHexString(ctx.pc))} `);
264+
Output.write(`$tid=${threadId}`);
265+
Output.writeln();
266+
Output.writeln(Output.yellow('-'.repeat(80)));
267+
} finally {
268+
Input.prompt();
269+
Regs.clear();
270+
}
271+
}
272+
124273
private breakCode(
125274
threadId: ThreadId,
126275
ctx: CpuContext,
@@ -310,6 +459,10 @@ export class Bp {
310459
return this._length;
311460
}
312461

462+
public get depth(): number | null {
463+
return this._depth;
464+
}
465+
313466
public get hits(): number {
314467
return this._hits;
315468
}
@@ -329,6 +482,11 @@ export class Bp {
329482
this._length = length;
330483
}
331484

485+
public set depth(depth: number | null) {
486+
if (depth === null) return;
487+
this._depth = depth;
488+
}
489+
332490
public set hits(hits: number) {
333491
this._hits = hits;
334492
}

0 commit comments

Comments
 (0)