mirror of
https://github.com/hasherezade/tiny_tracer
synced 2026-06-08 14:36:37 +00:00
214 lines
6.7 KiB
C++
214 lines
6.7 KiB
C++
#include "TrackReturns.h"
|
|
#include "Settings.h"
|
|
#include "TinyTracer.h"
|
|
#include "ModuleInfo.h"
|
|
#include "TraceLog.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <stack>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
#define MEM_SNAPSHOT_SIZE 8
|
|
|
|
struct CallInfo
|
|
{
|
|
ADDRINT returnAddress = UNKNOWN_ADDR; // Return addr of the call / Addr of syscall
|
|
std::string functionName; // Name of the API
|
|
size_t argCount = 0; // Number of arguments
|
|
std::vector<std::wstring> args; // Stored arguments values (result of paramToStr)
|
|
std::vector<VOID*> argPointers; // Stored args pointers (empty if args is not a pointer)
|
|
std::vector<std::vector<uint8_t>> argSnapshots; // Memory snapshots for arguments
|
|
std::wstring returnValue; // Stored return value (ret of paramToStr)
|
|
ADDRINT returnPtr = UNKNOWN_ADDR; // Stored return Ptr if return value is ptr
|
|
};
|
|
|
|
struct CallStack
|
|
{
|
|
public:
|
|
std::stack<CallInfo> callStack;
|
|
};
|
|
|
|
static void deleteCallstack(void* arg) noexcept
|
|
{
|
|
auto* threadStack = static_cast<CallStack*>(arg);
|
|
delete threadStack;
|
|
}
|
|
|
|
//---
|
|
|
|
// Convert to string and log
|
|
void LogBuffer(const std::wstringstream& ss)
|
|
{
|
|
if (!ss.str().empty()) {
|
|
const std::wstring wstr = ss.str();
|
|
const std::string s(wstr.begin(), wstr.end());
|
|
traceLog.logLine(s);
|
|
}
|
|
}
|
|
//---
|
|
|
|
namespace RetTracker {
|
|
|
|
static TLS_KEY tlsKey = static_cast<TLS_KEY>(-1); // INVALID by convention
|
|
|
|
VOID InitTracker()
|
|
{
|
|
// Create the TLS key
|
|
RetTracker::tlsKey = PIN_CreateThreadDataKey(deleteCallstack);
|
|
if (tlsKey == static_cast<TLS_KEY>(-1)) {
|
|
// handle error -> you've exhausted TLS keys
|
|
std::cerr << "Exhausted TLS keys!\n";
|
|
PIN_ExitProcess(1);
|
|
}
|
|
}
|
|
|
|
// Init the thread-local call stack
|
|
VOID InitTrackerForThread(THREADID tid)
|
|
{
|
|
const CallStack* newStack = new CallStack();
|
|
PIN_SetThreadData(RetTracker::tlsKey, newStack, tid);
|
|
}
|
|
|
|
// Retrieve the thread-local call stack
|
|
CallStack* GetCallStackForThread(const THREADID tid)
|
|
{
|
|
return static_cast<CallStack*>(PIN_GetThreadData(RetTracker::tlsKey, tid));
|
|
}
|
|
|
|
// Copy memory content into the snapshot
|
|
bool MakeMemorySnapshot(const ADDRINT addr, std::vector<uint8_t>& vec, const size_t size)
|
|
{
|
|
if (!addr || addr == UNKNOWN_ADDR || !size) return false;
|
|
vec.clear();
|
|
|
|
uint8_t* inPtr = (uint8_t*)addr;
|
|
const size_t maxSize = getReadableMemSize(inPtr);
|
|
if (!maxSize) return false;
|
|
|
|
size_t snapSize = (maxSize > size) ? size : maxSize;
|
|
vec.resize(snapSize);
|
|
uint8_t* outPtr = (uint8_t*)&vec[0];
|
|
size_t res = PIN_SafeCopy(outPtr, inPtr, snapSize);
|
|
if (res != snapSize) {
|
|
vec.resize(res);
|
|
}
|
|
return res ? true : false;
|
|
}
|
|
|
|
// Compare the current memory with the stored snapshot
|
|
bool IsMemorySame(const ADDRINT addr, const std::vector<uint8_t>& snapshot)
|
|
{
|
|
if (!addr || addr == UNKNOWN_ADDR) {
|
|
if (snapshot.empty()) return true;
|
|
return false;
|
|
}
|
|
const size_t snapSize = snapshot.size();
|
|
std::vector<uint8_t> snap2;
|
|
if (!MakeMemorySnapshot(addr, snap2, snapshot.size())) {
|
|
return false;
|
|
}
|
|
if (snap2.size() != snapSize) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < snapSize; i++) {
|
|
if (snap2.at(i) != snapshot.at(i)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CheckAndLogChanges(CallInfo& callInfo)
|
|
{
|
|
std::wstringstream ss;
|
|
ss << callInfo.functionName.c_str() << " changed:\n";
|
|
// Check argument changes
|
|
bool isChanged = false;
|
|
for (size_t i = 0; i < callInfo.argCount; i++) {
|
|
|
|
if (callInfo.argSnapshots[i].empty()) continue;
|
|
|
|
// Should be always true because previous condition checks if argSnapshots is not empty
|
|
// .ie a corresponding valid pointer exists as it is used to do the snapshot
|
|
if (callInfo.argPointers[i] == nullptr) continue;
|
|
|
|
if (!IsMemorySame((ADDRINT)callInfo.argPointers[i], callInfo.argSnapshots[i])) {
|
|
isChanged = true;
|
|
ss << L"\tArg[" << i << L"] = " << paramToStr(callInfo.argPointers[i]) << L"\n";
|
|
}
|
|
}
|
|
if (isChanged) {
|
|
LogBuffer(ss);
|
|
}
|
|
}
|
|
}; // namespace RetTracker
|
|
|
|
VOID RetTracker::LogCallDetails(const ADDRINT Address, const CHAR* name, uint32_t argCount,
|
|
VOID* arg1, VOID* arg2, VOID* arg3, VOID* arg4,
|
|
VOID* arg5, VOID* arg6, VOID* arg7, VOID* arg8,
|
|
VOID* arg9, VOID* arg10, VOID* arg11)
|
|
{
|
|
const THREADID tid = PIN_ThreadId();
|
|
|
|
// Retrieve the thread-local call stack
|
|
auto* c = GetCallStackForThread(tid);
|
|
if (!c) return;
|
|
|
|
// Initialize CallInfo
|
|
CallInfo info;
|
|
info.returnAddress = Address;
|
|
info.argCount = argCount;
|
|
info.functionName = name ? name : "?";
|
|
|
|
// Prepare arguments to log their value (paramToStr result)
|
|
VOID* args[] = { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 };
|
|
|
|
for (size_t i = 0; i < argCount; i++) {
|
|
info.args.push_back(paramToStr(args[i])); // Convert and store arguments
|
|
|
|
// Take memory snapshot for pointers
|
|
if (isValidReadPtr(args[i])) {
|
|
info.argPointers.push_back(args[i]); // Store the raw address
|
|
|
|
std::vector<uint8_t> snapshot;
|
|
MakeMemorySnapshot((ADDRINT)args[i], snapshot, MEM_SNAPSHOT_SIZE);
|
|
info.argSnapshots.push_back(snapshot); // Add the snapshot to the vector
|
|
}
|
|
else {
|
|
info.argSnapshots.push_back(std::vector<uint8_t>()); // Push an empty vector for non-pointers
|
|
info.argPointers.push_back(nullptr); // Push empty vector
|
|
}
|
|
}
|
|
|
|
// Store function call info in call stack
|
|
c->callStack.push(info);
|
|
}
|
|
|
|
VOID RetTracker::HandleFunctionReturn(const THREADID tid, const ADDRINT returnIp, const ADDRINT rawRetVal)
|
|
{
|
|
auto* c = GetCallStackForThread(tid);
|
|
if (!c || c->callStack.empty()) return;
|
|
|
|
CallInfo& topCall = c->callStack.top();
|
|
if (topCall.returnAddress != returnIp) return;
|
|
|
|
// Copy before popping so we can log after
|
|
CallInfo info = topCall;
|
|
c->callStack.pop();
|
|
|
|
std::wstring retStr = paramToStr(reinterpret_cast<VOID*>(rawRetVal));
|
|
info.returnValue = retStr;
|
|
info.returnPtr = rawRetVal;
|
|
|
|
std::wstringstream ss;
|
|
ss << info.functionName.c_str() << L" returned:\n";
|
|
ss << L"\t" << retStr << L"\n";
|
|
|
|
if (m_Settings.followArgReturn) {
|
|
RetTracker::CheckAndLogChanges(info);
|
|
}
|
|
LogBuffer(ss);
|
|
}
|