Skip to content

Commit

Permalink
Add frida-itrace tool for instruction tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
oleavr committed Jul 19, 2023
1 parent d56bea2 commit 0461d7a
Show file tree
Hide file tree
Showing 10 changed files with 1,611 additions and 1 deletion.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
all: frida_tools/fs_agent.js frida_tools/tracer_agent.js
all: frida_tools/fs_agent.js frida_tools/tracer_agent.js frida_tools/itracer_agent.js

frida_tools/fs_agent.js: agents/fs/agent.ts
cd agents/fs && npm install && npm run build

frida_tools/tracer_agent.js: agents/tracer/agent.ts
cd agents/tracer && npm install && npm run build

frida_tools/itracer_agent.js: agents/itracer/agent.ts
cd agents/itracer && npm install && npm run build

.PHONY: all
169 changes: 169 additions & 0 deletions agents/itracer/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
TraceBuffer,
TraceBufferReader,
TraceSession,
TraceStrategy,
} from "frida-itrace";

type RawTraceStrategy = RawTraceThreadStrategy | RawTraceRangeStrategy;
type RawTraceThreadStrategy = ["thread", ["id", number] | ["index", number]];
type RawTraceRangeStrategy = ["range", [CodeLocation, CodeLocation | null]]

type CodeLocation =
| ["address", string]
| ["module-export", [string, string]]
| ["module-offset", [string, number]]
| ["symbol", string]
;

const BUFFER_READER_POLL_INTERVAL_MSEC = 10;

class Agent {
session: TraceSession | null = null;
buffer: TraceBuffer | null = null;
reader: TraceBufferReader | null = null;
drainTimer: NodeJS.Timer | null = null;

createBuffer(): string {
this.buffer = TraceBuffer.create();
return this.buffer.location;
}

openBuffer(location: string) {
this.buffer = TraceBuffer.open(location);
}

launchBufferReader() {
this.reader = new TraceBufferReader(this.buffer!);
this.drainTimer = setInterval(this.#drainBuffer, BUFFER_READER_POLL_INTERVAL_MSEC);
}

stopBufferReader() {
clearInterval(this.drainTimer!);
this.drainTimer = null;

this.#drainBuffer();

this.reader = null;
}

#drainBuffer = () => {
const chunk = this.reader!.read();
if (chunk.byteLength === 0) {
return;
}
send({ type: "itrace:chunk" }, chunk);

const lost = this.reader!.lost;
if (lost !== 0) {
send({ type: "itrace:lost", payload: { lost } });
}
};

launchTraceSession(rawStrategy: RawTraceStrategy) {
const strategy = parseTraceStrategy(rawStrategy);
const session = new TraceSession(strategy, this.buffer!);
this.session = session;

session.events.on("start", (regSpecs, regValues) => {
send({ type: "itrace:start", payload: regSpecs }, regValues);
});
session.events.on("end", () => {
send({ type: "itrace:end" });
});
session.events.on("compile", block => {
send({ type: "itrace:compile", payload: block });
});
session.events.on("panic", message => {
console.error(message);
});

session.open();
}

queryProgramName() {
return Process.enumerateModules()[0].name;
}

listThreads() {
return Process.enumerateThreads();
}
}

function parseTraceStrategy(rawStrategy: RawTraceStrategy): TraceStrategy {
const [kind, params] = rawStrategy;
switch (kind) {
case "thread": {
let thread: ThreadDetails;
const threads = Process.enumerateThreads();
switch (params[0]) {
case "id": {
const desiredId = params[1];
const th = threads.find(t => t.id === desiredId);
if (th === undefined) {
throw new Error("invalid thread ID");
}
thread = th;
break;
}
case "index": {
thread = threads[params[1]];
if (thread === undefined) {
throw new Error("invalid thread index");
}
break;
}
}
return {
type: "thread",
threadId: thread.id
};
}
case "range": {
return {
type: "range",
start: parseCodeLocation(params[0]),
end: parseCodeLocation(params[1])
};
}
}
}

function parseCodeLocation(location: CodeLocation | null): NativePointer {
if (location === null) {
return NULL;
}

const [kind, params] = location;
switch (kind) {
case "address": {
const address = ptr(params);
try {
address.readVolatile(1);
} catch (e) {
throw new Error(`invalid address: ${address}`);
}
return address;
}
case "module-export":
return Module.getExportByName(params[0], params[1]);
case "module-offset":
return Module.getBaseAddress(params[0]).add(params[1]);
case "symbol": {
const name = params;
const { address } = DebugSymbol.fromName(name);
if (!address.isNull()) {
return address;
}
return Module.getExportByName(null, name);
}
}
}

const agent = new Agent();

const agentMethodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(agent))
.filter(name => name !== "constructor");
for (const name of agentMethodNames) {
rpc.exports[name] = (agent as any)[name].bind(agent);
}
21 changes: 21 additions & 0 deletions agents/itracer/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
entrypoint = 'agent.ts'

sources = [
entrypoint,
'package.json',
'package-lock.json',
'tsconfig.json',
]

custom_target('itracer-agent.js',
input: sources,
output: ['itracer_agent.js'],
command: [
build_agent,
'@INPUT@',
'@OUTPUT0@',
'@PRIVATE_DIR@',
],
install: true,
install_dir: pkgdir,
)
Loading

0 comments on commit 0461d7a

Please sign in to comment.