GP-7021: Add tenet++ pin tracer and docs

This commit is contained in:
ghidra_red
2026-08-27 17:37:25 +00:00
parent 52bb03d247
commit a19c2f4c2c
7 changed files with 909 additions and 10 deletions

View File

@@ -1,3 +1,5 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/TenetPlusPlus_PinTool/Makefile||GHIDRA||||END|
data/TenetPlusPlus_PinTool/Readme.md||GHIDRA||||END|

View File

@@ -0,0 +1,11 @@
CONFIG_ROOT := $(PIN_ROOT)/source/tools/Config
include $(CONFIG_ROOT)/makefile.config
TOOL_ROOTS := tenetplusplus
$(OBJDIR)tenetplusplus$(PINTOOL_SUFFIX): $(OBJDIR)tenetplusplus$(OBJ_SUFFIX)
$(LINKER) $(TOOL_LDFLAGS) $(LINK_EXE)$@ $^ $(TOOL_LPATHS) $(TOOL_LIBS)
include $(TOOLS_ROOT)/Config/makefile.default.rules
all: $(OBJDIR)tenetplusplus$(PINTOOL_SUFFIX)

View File

@@ -0,0 +1,157 @@
# Tenet++
The `tenetplusplus` pintool is an enhancement of the `pintenet` tracer found in
the [Tenet](https://github.com/gaasedelen/tenet) repo under the MIT license. With the Tenet++ tracer comes a new format
as well to enable some additional features when imported into Ghidra.
## New Format
Tenet++ expands the original tenet format to be better suited for additional features added to Ghidra.
### Example
```
Loaded image: 0x55b9ab65b000:0x55b9ab660d57 -> /usr/bin/whoami Bytes: 7f454c4602010100000000000000000003003e000100000020270000000000004000000000000000587600000000000000000000400038000d0040001e001d000600000004000000400000000000000040000000000000004000000000000000d802000000000000d80>
Loaded image: 0x7f78bd58d000:0x7f78bd5c38a7 -> /lib64/ld-linux-x86-64.so.2 Bytes: 7f454c4602010103000000000000000003003e000100000030ef0100000000004000000000000000684a0e000000000000000000400038000b004000230022000100000004000000000000000000000000000000000000000000000000000000301b000>
tid=0,rdi=0x7ffd3cbe5eb0,rsp=0x7ffd3cbe5eb0,rip=0x7f78bd5abf33
tid=0,rsp=0x7ffd3cbe5ea8,rip=0x7f78bd5acca0,mw=0x7ffd3cbe5ea8:38bf5abd787f0000
tid=0,rdi=0x6fffffff,rbp=0x7ffd3cbe5ea0,rsp=0x7ffd3cbe5e40,rbx=0x6ffffeff,rdx=0x7f78bd5c5e90,rcx=0x7f78bd5c6a70,rax=0xe,r8=0x70000022,r9=0x32,r10=0x6ffffdff,r11=0x6ffffe35,r12=0x7f78bd58d000,r13=0x6fffff41,r15=0x7ffd3cbe5eb0,rip=0x7f78bd5acd2d,ma=0x7f78bd5c5ac0:5ebb5be2a9520000,ma>
tid=0,rip=0x7f78bd5acd4a
tid=0,rsi=0x4c,rdx=0x7f78bd5c5eb0,rax=0x4c,r14=0xeffffef5,rip=0x7f78bd5ad089,ma=0x7f78bd5c5ea0:0400000000000000,ma=0x7f78bd5c5eb0:f5feff6f00000000,ma=0x7f78bd5c6a90:a05e5cbd787f0000,ma=0x7f78bd5c6ae0:905e5cbd787f0000
tid=0,rip=0x7f78bd5acd39
tid=0,rsi=0x29,rdx=0x7f78bd5c5f90,rax=0x7f78bd5c5eb0,rip=0x7f78bd5ad114,ma=0x7f78bd5c5ea8:f0d258bd787f0000,ma=0x7f78bd5c5eb8:38d458bd787f0000050000000000000090d958bd787f00000600000000000000a0d558bd787f00000a00000000000000,ma=0x7f78bd5c5ef0:0b00000000000000,ma=0x7f78bd5c5f00:070000>
tid=0,rip=0x7f78bd5acdaa
```
### Breakdown
A single line is a new snapshot/event and can be one of three types:
#### Instruction execution
* __tid=[thread id],[register name]=[value]...,m[r/w/a]=[address]:[bytes]...__
* __tid=[thread id]__
* Records which thread this event happens in
* Required for every instruction event
* eg. `tid=0`
* __[register name]=[value]__
* Records register assignments
* 0 or more per line
* eg. `rdi=0x7ffd3cbe5eb0`
* __m[r/w/a]=[address]:[bytes]__
* Records memory reads/writes as well as the coalesced memory from the `-rec_all_memory` option.
* 0 or more per line
* eg. `mw=0x7ffd3cbe5ea8:38bf5abd787f0000`
#### Image loading
* __Loaded image: [start address]:[end address] -> [module name] Bytes: [bytes starting from start address]__
* Records when images are loaded into memory as well as the bytes that were loaded
* eg. `Loaded image: 0x55b9ab65b000:0x55b9ab660d57 -> /usr/bin/whoami Bytes: 7f454c46020101000...`
#### Image unloading
* __Unloaded image: [start address]:[end address] -> [module name]__
* Records when images are unloaded from memory
* eg. `Unloaded image: 0x55b9ab65b000:0x55b9ab660d57 -> /usr/bin/whoami`
There can be a variable amount of actual time between events based on the recording granularity.
## Improvements made by Tenet++ pin tracer:
1. Trace start and stop addresses
- These allow the user to precisely trace only certain points of a program
2. Trace granularity
- These give the user an option to choose how granular the tracer is: Instruction, Block or Routine level
3. Record all memory
- Even if using routine level tracing, the user can record all memory events that get coalesced and read prior to
the
next routine event. This ensures the event has an updated snapshot of modified memory
4. Record image bytes at image load time
5. Record all threads in process in a single trace
# Usage
The pintool can be used to trace simple usermode applications on Windows or Linux. To use it, provide the path for a
compiled version of `tenetplusplus` to `pin` via the `-t` argument.
Example usage:
```
C:\pin\pin -t obj-ia32\tenetplusplus.dll -- "C:\Users\user\Desktop\calc.exe"
```
This pintool will generate a single trace containing trace data from all threads.
## Additional parameters
* `-s` Start tracing when an [image name]:[image base offset] is hit
* Example: `-s kernel32.dll:1234 -s app:2FFF`
* `-e ` Stop tracing when an [image name]:[image base offset] is hit
* Example: `-e kernel32.dll:1234 -e app:2FFF`
* `-g` Trace granularity level, default is 2
* `2` Instruction Level Tracing
* `4` Basic Block Level Tracing
* `8` Routine Level Tracing
* `-o` Prefix of the output .trace file, default is `trace`
* `-rec_all_memory` Record all memory r/w events regardless of granularity level until next log write
* `-w` Add a module to the whitelist. If none is specified, every module is white-listed
* Example: `-w calc.exe`, or `-w calc.exe,kernel32.dll`
# Compilation
To compile the pintool, you first will need
to [download](https://software.intel.com/en-us/articles/pin-a-binary-instrumentation-tool-downloads) and extract Pin.
Follow the build instructions below for your respective platform.
## Building for Linux
On Linux, one can compile the pintool using the following commands.
```
cd <location where tenetplusplus.cpp is>
# Location where you extracted Pin
export PIN_ROOT=~/pin
export PATH=$PATH:$PIN_ROOT
make TARGET=intel64
make TARGET=ia32
```
## Building for Windows
Install deps for building Pintools:
- Install Visual Studio Community 2019 Edition
from [https://visualstudio.microsoft.com/downloads/](https://visualstudio.microsoft.com/downloads/)
- Make sure to install the Desktop development for C++ workload
- Install GNU's make, version 4.2.1, using Cygwin's 64-bit installer. Cygwin installer link
here: [https://cygwin.com/install.html](https://cygwin.com/install.html)
### Building 32bit
1. Launch a new CMD window and paste the EXACT following:
```
set PIN_ROOT=C:\\pin
set PATH=%PATH%;C:\cygwin64\bin
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat"
```
2. Change to the directory containing the `tenetplusplus` source, build the 32bit pin tool:
```
make TARGET=ia32
```
### Building 64bit
1. Launch a new CMD window and paste the EXACT following:
```
set PIN_ROOT=C:\\pin
set PATH=%PATH%;C:\cygwin64\bin
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
```
2. Change to the directory containing the `tenetplusplus` source, and build the 64bit pin tool:
```
make TARGET=intel64
```

View File

@@ -0,0 +1,711 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Code is derived from work done by https://github.com/gaasedelen/tenet under the MIT license
#include <algorithm>
#include <array>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <regex>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "pin.H"
using std::ofstream;
#ifdef __i386__
constexpr auto PC = "eip";
#else
constexpr auto PC = "rip";
#endif
#if defined(TARGET_WINDOWS)
constexpr char PATH_SEPARATOR = '\\';
#else
constexpr char PATH_SEPARATOR = '/';
#endif
static KNOB<std::string> KnobModuleWhitelist(
KNOB_MODE_APPEND, "pintool", "w", "",
"Add a module to the whitelist. If none is specified, every module is "
"white-listed. Example: calc.exe");
static KNOB<std::string> KnobOutputFilePrefix(
KNOB_MODE_WRITEONCE, "pintool", "o", "trace",
"Prefix of the output .trace file.");
static KNOB<std::string> KnobStartAt(
KNOB_MODE_APPEND, "pintool", "s", "",
"Start tracing when an image:offset is hit.\nExample: -s "
"kernel32.dll:1234 -s app:2FFF");
static KNOB<std::string> KnobStopAt(
KNOB_MODE_APPEND, "pintool", "e", "",
"Stop tracing when an image:offset is hit.\nExample: -e "
"kernel32.dll:1234 -e app:2FFF");
static KNOB<int> KnobTraceGranularity(
KNOB_MODE_WRITEONCE, "pintool", "g", "2",
"Trace granularity level:\n2=Instruction Level Tracing\n4=Basic Block "
"Level Tracing\n8=Routine Level Tracing");
static KNOB<BOOL> KnobCollectMem(KNOB_MODE_WRITEONCE, "pintool",
"rec_all_memory", "0",
"Record all memory r/w events regardless of "
"granularity level until next log write");
static std::string base_name(const std::string &path) {
const std::string::size_type idx = path.rfind(PATH_SEPARATOR);
std::string name = (idx == std::string::npos) ? path : path.substr(idx + 1);
return name;
}
namespace {
struct Image {
std::string name_;
ADDRINT low_;
ADDRINT high_;
explicit Image(std::string n = "", const ADDRINT low = 0, const ADDRINT high = 0)
: name_(std::move(n)), low_(low), high_(high) {
}
// Overloaded method to implement searches over the loaded images list
// and also allow this class to be used on a set like STL container.
bool operator<(const Image &rhs) const { return low_ < rhs.low_; }
};
class ImageManager {
// Set of module names that are allowed to be traced.
std::set<Image> images;
PIN_RWMUTEX images_lock{};
// Here we store the names of the images inside our white list.
std::set<std::string, std::less<> > whitelist;
// Store the last recently matched image so we can use it as a cache.
ADDRINT cached_low{};
ADDRINT cached_high{};
public:
ImageManager() { PIN_RWMutexInit(&images_lock); }
virtual ~ImageManager() { PIN_RWMutexFini(&images_lock); }
VOID addWhiteListedImage(const std::string &image_name) {
whitelist.insert(image_name);
}
BOOL isWhiteListed(const std::string &image_name) {
return whitelist.find(image_name) != whitelist.end();
}
BOOL isInterestingAddress(ADDRINT addr) {
PIN_RWMutexReadLock(&images_lock);
{
// If there is no white-listed image, everything is white-listed.
if ((images.empty() && whitelist.empty()) ||
(addr >= cached_low && addr < cached_high)) {
PIN_RWMutexUnlock(&images_lock);
return true;
}
auto i = images.upper_bound(Image("", addr));
if (i == images.begin()) {
PIN_RWMutexUnlock(&images_lock);
return false;
}
--i;
// If the instruction address does not fall inside a valid white listed
// image, bail out.
if (!(i != images.end() && i->low_ <= addr && addr < i->high_)) {
PIN_RWMutexUnlock(&images_lock);
return false;
}
// Save the matched image.
cached_low = i->low_;
cached_high = i->high_;
}
PIN_RWMutexUnlock(&images_lock);
return true;
}
VOID addImage(std::string image_name, ADDRINT lo_addr, ADDRINT hi_addr) {
PIN_RWMutexWriteLock(&images_lock);
{
images.emplace(std::move(image_name), lo_addr, hi_addr);
}
PIN_RWMutexUnlock(&images_lock);
}
VOID removeImage(const ADDRINT low) {
PIN_RWMutexWriteLock(&images_lock);
{
if (const auto i = images.find(Image("", low)); i != images.end()) {
images.erase(i);
}
}
PIN_RWMutexUnlock(&images_lock);
}
};
struct ThreadData {
std::array<ADDRINT, REG_GR_LAST + 1> m_cpu{};
ADDRINT mem_w_addr{};
ADDRINT mem_w_size{};
ADDRINT mem_r_addr{};
ADDRINT mem_r_size{};
ADDRINT mem_r2_addr{};
ADDRINT mem_r2_size{};
ADDRINT largestSize = 0;
struct MemEvent {
ADDRINT addr = 0;
UINT32 size = 0;
uint8_t access_flags = 0;
ADDRINT end() const { return addr + size; }
};
std::map<ADDRINT, MemEvent> mem_events;
};
enum class TraceGranularity : int {
TRACE_INSTRUCTION = 2,
TRACE_BASIC_BLOCK = 4,
TRACE_ROUTINE = 8,
};
class ToolContext {
public:
explicit ToolContext(const std::string &log_name) {
PIN_InitLock(&write_lock);
tls_key = PIN_CreateThreadDataKey(nullptr);
trace_file = ofstream(log_name.c_str());
trace_file << std::hex;
}
~ToolContext() {
trace_file.close();
}
ThreadData *GetThreadLocalData(const THREADID tid) const {
return static_cast<ThreadData *>(PIN_GetThreadData(tls_key, tid));
}
void setThreadLocalData(const THREADID tid, const ThreadData *data) const {
PIN_SetThreadData(tls_key, data, tid);
}
std::ofstream trace_file;
std::unique_ptr<ImageManager> image_manager;
std::vector<std::pair<std::string, ADDRINT> > pending_start_specs;
std::vector<ADDRINT> start_addrs;
std::vector<std::pair<std::string, ADDRINT> > pending_stop_specs;
std::vector<ADDRINT> stop_addrs;
bool wait_for_start = false;
bool start_and_end_defined = false;
PIN_LOCK write_lock{};
bool tracing_enabled = true;
TLS_KEY tls_key;
};
}
static BOOL should_trace(ToolContext *context, const ADDRINT pc) {
const BOOL interestingAddress = context->image_manager->isInterestingAddress(pc);
if (context->start_and_end_defined) {
BOOL pending_disable_tracing = false;
if (context->wait_for_start) {
for (const ADDRINT tgt: context->start_addrs) {
if (pc == tgt) {
context->tracing_enabled = true;
context->wait_for_start = false;
LOG("Start address hit: 0x" + hexstr(tgt) + ", enabling tracing\n");
break;
}
}
} else {
context->tracing_enabled = interestingAddress;
for (const ADDRINT tgt: context->stop_addrs) {
if (pc == tgt) {
context->tracing_enabled = false;
pending_disable_tracing = true;
context->wait_for_start = true;
LOG("Stop address hit: 0x" + hexstr(tgt) + ", disabling tracing\n");
break;
}
}
}
if (pending_disable_tracing) {
context->tracing_enabled = false;
} else if (!context->tracing_enabled) {
return false;
}
return interestingAddress;
}
return interestingAddress;
}
static void add_mem_event(ThreadData *data, const ADDRINT addr, const UINT32 size,
const uint8_t flags) {
if (size == 0) return;
ADDRINT newEnd = addr + size;
ADDRINT newStart = addr;
UINT8 newFlags = flags;
auto it = data->mem_events.lower_bound(newStart);
if (it != data->mem_events.begin()) {
if (const auto prev = std::prev(it); prev->second.end() >= newStart) {
newStart = prev->first;
newEnd = std::max(newEnd, prev->second.end());
newFlags |= prev->second.access_flags;
data->mem_events.erase(prev);
}
}
while (it != data->mem_events.end() && it->first <= newEnd) {
newFlags |= it->second.access_flags;
newEnd = std::max(newEnd, it->second.end());
it = data->mem_events.erase(it);
}
const auto newSize = static_cast<UINT32>(newEnd - newStart);
if (newSize > data->largestSize) data->largestSize = newSize;
data->mem_events[newStart] = {.addr = newStart, .size = newSize, .access_flags = newFlags};
}
static VOID on_thread_start(const THREADID tid, CONTEXT *ctxt, INT32 flags, VOID *v) {
const auto &context = *static_cast<ToolContext *>(v);
const auto *data = new ThreadData();
context.setThreadLocalData(tid, data);
}
static VOID on_image_load(const IMG img, VOID *v) {
auto &context = *static_cast<ToolContext *>(v);
const std::string img_name = base_name(IMG_Name(img));
if (IMG_IsVDSO(img)) {
return;
}
const ADDRINT low = IMG_LowAddress(img);
const ADDRINT high = IMG_HighAddress(img);
const size_t size = high - low;
std::vector<unsigned char> tmp(size);
unsigned char *buf = nullptr;
buf = tmp.data();
PIN_SafeCopy(buf, reinterpret_cast<const VOID *>(low), size);
PIN_GetLock(&context.write_lock, 1);
{
context.trace_file << "Loaded image: 0x" << low << ":0x" << high << " -> "
<< IMG_Name(img) << " Bytes: ";
LOG("Loaded image: 0x" + hexstr(low) + ":0x" + hexstr(high) + " -> " +
IMG_Name(img) + "\n");
for (UINT32 i = 0; i < size; i++) {
context.trace_file << std::hex << std::setw(2) << std::setfill('0') << (buf[i] & 0xff);
}
context.trace_file << std::endl;
}
PIN_ReleaseLock(&context.write_lock);
for (const auto &[spec_img, offset]: context.pending_start_specs) {
if (spec_img == img_name) {
ADDRINT target = low + offset;
context.start_addrs.push_back(target);
LOG("Resolved start location for " + spec_img + " -> 0x" +
hexstr(target) + "\n");
}
}
for (const auto &[spec_img, offset]: context.pending_stop_specs) {
if (spec_img == img_name) {
ADDRINT target = low + offset;
context.stop_addrs.push_back(target);
LOG("Resolved stop location for " + spec_img + " -> 0x" + hexstr(target) +
"\n");
}
}
if (context.image_manager->isWhiteListed(img_name)) {
context.image_manager->addImage(img_name, low, high);
}
}
static VOID on_image_unload(const IMG img, VOID *v) {
auto &context = *static_cast<ToolContext *>(v);
if (IMG_IsVDSO(img)) {
return;
}
context.image_manager->removeImage(IMG_LowAddress(img));
const ADDRINT low = IMG_LowAddress(img);
const ADDRINT high = IMG_HighAddress(img);
context.trace_file << "Unloaded image: 0x" << low << ":0x" << high << " -> "
<< IMG_Name(img) << std::endl;
LOG("Unloaded image: 0x" + hexstr(low) + ":0x" + hexstr(high) + " -> " +
IMG_Name(img) + "\n");
}
static VOID PIN_FAST_ANALYSIS_CALL record_diff(const CONTEXT *cpu, const ADDRINT pc,
VOID *v) {
auto &context = *static_cast<ToolContext *>(v);
const auto tid = PIN_ThreadId();
ThreadData *data = context.GetThreadLocalData(tid);
ADDRINT val;
std::ostringstream oss;
oss << std::hex;
oss << "tid=" << tid << ",";
for (int reg = REG_GR_BASE; reg <= static_cast<int>(REG_GR_LAST); ++reg) {
PIN_GetContextRegval(cpu, static_cast<REG>(reg), reinterpret_cast<UINT8 *>(&val));
if (val == data->m_cpu[reg]) continue;
oss << REG_StringShort(static_cast<REG>(reg)) << "=0x" << val << ",";
data->m_cpu[reg] = val;
}
oss << PC << "=0x" << pc;
const ADDRINT largest =
std::max(data->largestSize,
std::max(data->mem_r_size,
std::max(data->mem_r2_size, data->mem_w_size)));
std::vector<unsigned char> tmp(largest);
unsigned char *buf = nullptr;
buf = tmp.data();
for (auto const &[_, ev]: data->mem_events) {
if (ev.size == 0) continue;
PIN_SafeCopy(buf, reinterpret_cast<const VOID *>(ev.addr), ev.size);
// It's easier to just call all these events as access instead of an
// explicit read or write. Especially since the trace is not keeping track
// of where these occurred
constexpr auto atype = "ma";
oss << "," << atype << "=0x" << ev.addr << ":";
for (UINT32 i = 0; i < ev.size; i++) {
oss << std::hex << std::setw(2) << std::setfill('0') << (buf[i] & 0xff);
}
}
data->mem_events.clear();
data->largestSize = 0;
if (data->mem_r_size) {
PIN_SafeCopy(buf, reinterpret_cast<const VOID *>(data->mem_r_addr), data->mem_r_size);
oss << ",mr=0x" << data->mem_r_addr << ":";
for (UINT32 i = 0; i < data->mem_r_size; i++) {
oss << std::hex << std::setw(2) << std::setfill('0')
<< (buf[i] & 0xff);
}
data->mem_r_size = 0;
}
if (data->mem_r2_size) {
PIN_SafeCopy(buf, reinterpret_cast<const VOID *>(data->mem_r2_addr), data->mem_r2_size);
oss << ",mr=0x" << data->mem_r2_addr << ":";
for (UINT32 i = 0; i < data->mem_r2_size; i++) {
oss << std::hex << std::setw(2) << std::setfill('0')
<< (buf[i] & 0xff);
}
data->mem_r2_size = 0;
}
if (data->mem_w_size) {
PIN_SafeCopy(buf, reinterpret_cast<const VOID *>(data->mem_w_addr), data->mem_w_size);
oss << ",mw=0x" << data->mem_w_addr << ":";
for (UINT32 i = 0; i < data->mem_w_size; i++) {
oss << std::hex << std::setw(2) << std::setfill('0')
<< (buf[i] & 0xff);
}
data->mem_w_size = 0;
}
oss << std::endl;
PIN_GetLock(&context.write_lock, 1);
{
context.trace_file << oss.str();
}
PIN_ReleaseLock(&context.write_lock);
}
static VOID PIN_FAST_ANALYSIS_CALL record_read(const THREADID tid, const ADDRINT access_addr,
const UINT32 access_size, VOID *v,
const BOOL recordMemOnlyOp) {
auto const &context = *static_cast<ToolContext *>(v);
ThreadData *data = context.GetThreadLocalData(tid);
if (!recordMemOnlyOp) {
data->mem_r_addr = access_addr;
data->mem_r_size = access_size;
} else {
add_mem_event(data, access_addr, access_size, 1);
}
}
static VOID PIN_FAST_ANALYSIS_CALL record_read2(const THREADID tid, const ADDRINT access_addr,
const UINT32 access_size, VOID *v,
const BOOL recordMemOnlyOp) {
auto const &context = *static_cast<ToolContext *>(v);
ThreadData *data = context.GetThreadLocalData(tid);
if (!recordMemOnlyOp) {
data->mem_r2_addr = access_addr;
data->mem_r2_size = access_size;
} else {
add_mem_event(data, access_addr, access_size, 1);
}
}
static VOID PIN_FAST_ANALYSIS_CALL record_write(const THREADID tid, const ADDRINT access_addr,
const UINT32 access_size, VOID *v,
const BOOL recordMemOnlyOp) {
auto const &context = *static_cast<ToolContext *>(v);
ThreadData *data = context.GetThreadLocalData(tid);
if (!recordMemOnlyOp) {
data->mem_w_addr = access_addr;
data->mem_w_size = access_size;
} else {
add_mem_event(data, access_addr, access_size, 2);
}
}
static VOID PIN_FAST_ANALYSIS_CALL record_after_instruction(const CONTEXT *cpu,
const ADDRINT target, VOID *v) {
auto &context = *static_cast<ToolContext *>(v);
if (should_trace(&context, target)) {
record_diff(cpu, target, v);
}
}
static VOID instrument_inst(const INS ins, VOID *v, const BOOL recordNext, const BOOL recordMemOnly) {
auto &context = *static_cast<ToolContext *>(v);
const BOOL shouldTrace = should_trace(&context, INS_Address(ins));
if (KnobCollectMem.Value() == 0 && !shouldTrace) {
return;
}
if (!recordMemOnly && shouldTrace) {
INS_InsertCall(ins, IPOINT_BEFORE, reinterpret_cast<AFUNPTR>(record_diff),
IARG_FAST_ANALYSIS_CALL, IARG_CONST_CONTEXT, IARG_INST_PTR,
IARG_PTR, v, IARG_END);
}
if (!recordMemOnly && recordNext && INS_IsControlFlow(ins)) {
INS_InsertCall(ins, IPOINT_TAKEN_BRANCH, reinterpret_cast<AFUNPTR>(record_after_instruction),
IARG_FAST_ANALYSIS_CALL, IARG_CONTEXT,
IARG_BRANCH_TARGET_ADDR, IARG_PTR, v, IARG_END);
}
const BOOL memoryRecord = recordMemOnly || !shouldTrace;
if (INS_IsMemoryRead(ins) || INS_IsMemoryWrite(ins)) {
if (INS_IsMemoryRead(ins)) {
INS_InsertPredicatedCall(ins, IPOINT_BEFORE, reinterpret_cast<AFUNPTR>(record_read),
IARG_FAST_ANALYSIS_CALL, IARG_THREAD_ID,
IARG_MEMORYREAD_EA, IARG_MEMORYREAD_SIZE,
IARG_PTR, v, IARG_BOOL, memoryRecord, IARG_END);
}
if (INS_HasMemoryRead2(ins)) {
INS_InsertPredicatedCall(ins, IPOINT_BEFORE, reinterpret_cast<AFUNPTR>(record_read2),
IARG_FAST_ANALYSIS_CALL, IARG_THREAD_ID,
IARG_MEMORYREAD2_EA, IARG_MEMORYREAD_SIZE,
IARG_PTR, v, IARG_BOOL, memoryRecord, IARG_END);
}
if (INS_IsMemoryWrite(ins)) {
INS_InsertPredicatedCall(ins, IPOINT_BEFORE, reinterpret_cast<AFUNPTR>(record_write),
IARG_FAST_ANALYSIS_CALL, IARG_THREAD_ID,
IARG_MEMORYWRITE_EA, IARG_MEMORYWRITE_SIZE,
IARG_PTR, v, IARG_BOOL, memoryRecord, IARG_END);
}
}
}
static VOID instrument_inst_cb(const INS ins, VOID *v) { instrument_inst(ins, v, false, false); }
static VOID instrument_trace_cb(const TRACE trace, VOID *v) {
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) {
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins)) {
if (ins == BBL_InsHead(bbl) || (ins == BBL_InsTail(bbl) &&
BBL_InsHead(bbl) != BBL_InsTail(bbl))) {
instrument_inst(ins, v, false, false);
} else if (KnobCollectMem.Value() == 1) {
instrument_inst(ins, v, false, true);
}
}
}
}
static VOID instrument_routine_cb(const TRACE trace, VOID *v) {
static bool recordNext = false;
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) {
for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins)) {
if (INS_IsCall(ins) || INS_IsRet(ins) || (INS_IsControlFlow(ins) && !INS_IsCall(ins) &&
INS_Category(ins) != XED_CATEGORY_COND_BR && !INS_IsRet(ins) &&
!BBL_Valid(BBL_Next(bbl)))) {
instrument_inst(ins, v, true, false);
} else if (INS_IsSyscall(ins)) {
recordNext = true;
instrument_inst(ins, v, false, false);
} else if (recordNext) {
recordNext = false;
instrument_inst(ins, v, true, false);
} else if (KnobCollectMem.Value() == 1) {
instrument_inst(ins, v, false, true);
}
}
}
}
static INT32 usage() {
std::cerr << "TenetPlusPlus pintool tracer" << std::endl;
std::cerr << KNOB_BASE::StringKnobSummary() << std::endl;
return -1;
}
static EXCEPT_HANDLING_RESULT exception_handler_cb(THREADID tid, EXCEPTION_INFO *pExceptInfo,
PHYSICAL_CONTEXT *pPhysCtxt, VOID *v) {
std::cout << pExceptInfo->ToString() << std::endl;
return EHR_UNHANDLED;
}
int main(int argc, char *argv[]) {
PIN_InitSymbols();
if (PIN_Init(argc, argv)) {
return usage();
}
PIN_AddInternalExceptionHandler(exception_handler_cb, nullptr);
auto logFile = KnobOutputFilePrefix.Value() + ".trace";
LOG("Trace will be saved in " + logFile + "\n");
auto context = std::make_unique<ToolContext>(logFile);
context->image_manager = std::make_unique<ImageManager>();
for (unsigned i = 0; i < KnobModuleWhitelist.NumberOfValues(); ++i) {
LOG("White-listing image: " + KnobModuleWhitelist.Value(i) + "\n");
context->tracing_enabled = false;
context->image_manager->addWhiteListedImage(KnobModuleWhitelist.Value(i));
}
if (KnobStartAt.NumberOfValues() > 0) {
context->wait_for_start = true;
context->tracing_enabled = false;
std::regex pattern(R"(^(.+):([0-9a-fA-F]+)$)");
std::smatch matches;
for (unsigned i = 0; i < KnobStartAt.NumberOfValues(); ++i) {
if (!std::regex_match(KnobStartAt.Value(i), matches, pattern)) {
continue;
}
ADDRINT offset = std::stoi(matches[2], nullptr, 16);
if (KnobModuleWhitelist.NumberOfValues() > 0) {
context->image_manager->addWhiteListedImage(KnobStartAt.Value(i));
}
context->pending_start_specs.emplace_back(matches[1], offset);
}
context->start_and_end_defined = true;
}
if (KnobStopAt.NumberOfValues() > 0) {
std::regex pattern(R"(^(.+):([0-9a-fA-F]+)$)");
std::smatch matches;
for (unsigned i = 0; i < KnobStopAt.NumberOfValues(); ++i) {
if (!std::regex_match(KnobStopAt.Value(i), matches, pattern)) {
continue;
}
ADDRINT offset = std::stoi(matches[2], nullptr, 16);
if (KnobModuleWhitelist.NumberOfValues() > 0) {
context->image_manager->addWhiteListedImage(KnobStopAt.Value(i));
}
context->pending_stop_specs.emplace_back(matches[1], offset);
}
context->start_and_end_defined = true;
}
PIN_AddThreadStartFunction(on_thread_start, std::move(context).get());
IMG_AddInstrumentFunction(on_image_load, std::move(context).get());
IMG_AddUnloadFunction(on_image_unload, std::move(context).get());
switch (auto inst = static_cast<TraceGranularity>(KnobTraceGranularity.Value())) {
case TraceGranularity::TRACE_INSTRUCTION:
LOG("Running with instruction level tracing\n");
INS_AddInstrumentFunction(instrument_inst_cb, std::move(context).get());
break;
case TraceGranularity::TRACE_BASIC_BLOCK:
LOG("Running with block level tracing\n");
TRACE_AddInstrumentFunction(instrument_trace_cb, std::move(context).get());
break;
case TraceGranularity::TRACE_ROUTINE:
LOG("Running with routine level tracing\n");
TRACE_AddInstrumentFunction(instrument_routine_cb, std::move(context).get());
break;
default:
std::cerr << "Unknown trace type: " << static_cast<int>(inst) << "\n";
abort();
}
PIN_StartProgram();
return 0;
}

View File

@@ -61,7 +61,7 @@ import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
public class TenetPlusPlusLoader implements Loader {
private record ModuleEvent(String path, String module, AddressRange range) {}
private record ModuleEvent(String path, String module, AddressRange range, byte[] bytes) {}
private static final String TRACE_SUFFIX = ".trace";
private static final String TENET_SUFFIX = ".tenet";
@@ -73,7 +73,7 @@ public class TenetPlusPlusLoader implements Loader {
private static final Pattern MEM_PATTERN =
Pattern.compile("(?:^|,)(mr|mw|ma)=0x([^:]+):([0-9a-fA-F]+)");
private static final Pattern MODULE_LOAD_PATTERN =
Pattern.compile("Loaded image: 0x([0-9a-fA-F]+):0x([0-9a-fA-F]+) -> (.*)$");
Pattern.compile("Loaded image: 0x([0-9a-fA-F]+):0x([0-9a-fA-F]+) -> (.*) Bytes: (.*)$");
private static final Pattern MODULE_UNLOAD_PATTERN =
Pattern.compile("Unloaded image: 0x([0-9a-fA-F]+):0x([0-9a-fA-F]+) -> (.*)$");
@@ -214,6 +214,8 @@ public class TenetPlusPlusLoader implements Loader {
if (!load.isEmpty()) {
for (final ModuleEvent event : load) {
modMan.addLoadedModule(event.path, event.module, event.range, snap);
trace.getMemoryManager()
.putBytes(snap, event.range.getMinAddress(), ByteBuffer.wrap(event.bytes));
}
load.clear();
@@ -512,7 +514,8 @@ public class TenetPlusPlusLoader implements Loader {
load.add(new ModuleEvent("Modules[%s]".formatted(mod), mod,
rng(Long.parseLong(modLoadMatcher.group(1), 16),
Long.parseLong(modLoadMatcher.group(2), 16))));
Long.parseLong(modLoadMatcher.group(2), 16)),
HexFormat.of().parseHex(modLoadMatcher.group(4))));
return true;
}
@@ -525,7 +528,8 @@ public class TenetPlusPlusLoader implements Loader {
unload.add(new ModuleEvent("Modules[%s]".formatted(mod), mod,
rng(Long.parseLong(modUnloadMatcher.group(1), 16),
Long.parseLong(modUnloadMatcher.group(2), 16))));
Long.parseLong(modUnloadMatcher.group(2), 16)),
null));
return true;
}
return false;

View File

@@ -287,15 +287,17 @@ public class BreakpointTimelineProvider extends ComponentProvider {
for (final TraceReference reference : currentTrace.getReferenceManager()
.getReferencesToRange(Lifespan.ALL, range)) {
if ((reference.getReferenceType() == RefType.READ) &&
(kind == TraceBreakpointKind.READ)) {
if ((reference.getReferenceType() == RefType.READ &&
kind == TraceBreakpointKind.READ) ||
(reference.getReferenceType() == RefType.READ_WRITE)) {
breakpointHits.add(
new BreakpointHitEvent(reference.getStartSnap(), TraceBreakpointKind.READ,
breakpointLocation.getName(reference.getStartSnap())));
}
if ((reference.getReferenceType() == RefType.WRITE) &&
(kind == TraceBreakpointKind.WRITE)) {
if ((reference.getReferenceType() == RefType.WRITE &&
kind == TraceBreakpointKind.WRITE) ||
(reference.getReferenceType() == RefType.READ_WRITE)) {
breakpointHits.add(
new BreakpointHitEvent(reference.getStartSnap(), TraceBreakpointKind.WRITE,
breakpointLocation.getName(reference.getStartSnap())));

View File

@@ -31,8 +31,8 @@ import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.*;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.exception.NotFoundException;
import ghidra.util.Msg;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
@@ -99,6 +99,18 @@ public class SymPcodeExecutor extends PcodeExecutor<Sym> {
// TODO: Is there a way to know if a userop affects the stack?
}
@Override
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<Sym> library) {
// TODO: This function can probably be removed after GP-6707 is complete
try {
monitor.checkCancelled();
}
catch (CancelledException e) {
throw new PcodeExecutionException("Monitor was cancelled", frame, e);
}
super.stepOp(op, frame, library);
}
/**
* Attempt to figure the stack depth change for a given function
*