Closed WankkoRee closed 1 year ago
Thanks for contributing!
If I got this correctly, you want to log the call trees that have a certain method(s) as root (can we name it foretracer?). This is handy if you want to log the methods that are called by a designated method.
This is an interesting use case for sure :smiley:
However, I think there are a better API and implementation that can address this use case (checking if a given entry is member of an array on every call is a nightmare for performance).
We should deep dive into this, i.e. probably Stalker
is the right tool for this.
yes, it would make the performance degradation if check at every time, so may it need to separate into a class (may named FrontTrace
?🤔) or just try optimize the judgment way?
is it right way to use Stalker
?
axhlzy / Il2CppHookScripts - Il2cppHook/agent/base/extends.ts#L456-L508
I have try to use it, but it will be segmentation fault after log one time.
so may it need to separate into a class (may named FrontTrace?
This is a possibility. However, I don't know if it makes sense to keep the current AbstractTracer
as it is.
is it right way to use Stalker?
Err, I never used Stalker
once! That's why I need to investigate.
frida stalker https://github.com/iGio90/Hooah-Trace
I played with Stalker
a little bit, but I couldn't figure out a way to effectively use it to replace the current tracing system: it looks like it misses few method calls...
I'm leaving this work in progress snippet here for housekeeping purposes.
const map = new Map<string, Il2Cpp.Method>();
Il2Cpp.domain.assemblies.forEach(_ => {
_.image.classes.forEach(_ => {
_.methods.forEach(_ => {
map.set(_.virtualAddress.toString(), _);
});
});
});
function isInModule(address: NativePointer) {
return address.compare(Il2Cpp.module.base) > 0 && address.compare(Il2Cpp.module.base.add(Il2Cpp.module.size)) < 0;
}
function callAddress({ type, value }: X86Operand, context: X64CpuContext): NativePointer {
switch (type) {
case "imm":
return ptr(value.toString());
case "reg":
return context[value as keyof X64CpuContext];
case "mem":
return context[value.base as keyof X64CpuContext].add(value.disp).readPointer();
default:
return NULL;
}
}
const events: string[] = [];
Stalker.follow(Il2Cpp.mainThread.id, {
transform(iterator: StalkerX86Iterator) {
let instruction: X86Instruction | null;
while ((instruction = iterator.next()) != null) {
if (isInModule(instruction.address) && (instruction.mnemonic == "jmp" || instruction.mnemonic == "call")) {
const operand = instruction.operands[0];
iterator.putCallout(_ => {
const method = map.get(callAddress(operand, _ as X64CpuContext).toString());
if (method) {
events.push(`-> \x1B[3m\x1B[2m${method.class.type.name}::\x1B[0m${method.name}`);
}
if (events.length == 128) {
console.log(events.join("\n"));
events.length = 0;
}
});
}
iterator.keep();
}
}
});
Hey, I took some spare time to figure out how this feature should be implemented. Unfortunately it doesn't look like this is a common use case so that it should be part of the code base (especially considering the amount of lines of code!).
However, v0.8.1
lets you pass a custom applier to Il2Cpp::Tracer
, so that you don't need to modify the source code:
function foretrace(...methods: Il2Cpp.Method[]): Il2Cpp.Tracer {
const starters = new Set<number>(methods.map(_ => _.virtualAddress.toUInt32()));
const applier = (): Il2Cpp.Tracer.Apply => (method, state, threadId) => {
const paddedVirtualAddress = method.relativeVirtualAddress.toString(16).padStart(8, "0");
Interceptor.attach(method.virtualAddress, {
onEnter() {
this.flag = this.threadId == threadId && (state.depth > 0 || starters.has(method.virtualAddress.toInt32()));
if (this.flag) {
// prettier-ignore
state.buffer.push(`\x1b[2m0x${paddedVirtualAddress}\x1b[0m ${`│ `.repeat(state.depth++)}┌─\x1b[35m${method.class.type.name}::\x1b[1m${method.name}\x1b[0m\x1b[0m`);
}
},
onLeave() {
if (this.flag) {
// prettier-ignore
state.buffer.push(`\x1b[2m0x${paddedVirtualAddress}\x1b[0m ${`│ `.repeat(--state.depth)}└─\x1b[33m${method.class.type.name}::\x1b[1m${method.name}\x1b[0m\x1b[0m`);
state.flush();
}
}
});
};
return new Il2Cpp.Tracer(applier());
}
Hopefully, I can iterate on the API so that it doesn't take much code, possibly:
const starters = new Set<number>(methods.map(_ => _.virtualAddress.toUInt32()));
Il2Cpp.trace()
.logCondition((state, method) => state.depth > 0 || starters.has(method.virtualAddress.toInt32())
// ...
use it can know that what methods called by special methods.
just like: A call B, B call C, C call D. And all of them are in same assembly or class, so I can attach the assembly or class and specify A as start method, then I can know A->B->C->D.
e.g. :
then I can know what methods called by
ShopModel.Save
.In the past:
Il2Cpp.trace().assemblies(AssemblyCSharp).and().attach()
, it will show too much console log.Il2Cpp.trace().methods(ShopModel_Save).and().attach()
, it will only show log about call and return ofShopModel.Save
.Because not familiar with this project, there may be some bugs in the code. I just verified this way is feasible.