diff --git a/Ghidra/Debug/Debugger-importers/certification.manifest b/Ghidra/Debug/Debugger-importers/certification.manifest index 6841c5219a..cb86b212de 100644 --- a/Ghidra/Debug/Debugger-importers/certification.manifest +++ b/Ghidra/Debug/Debugger-importers/certification.manifest @@ -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| diff --git a/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/Makefile b/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/Makefile new file mode 100644 index 0000000000..e59c536805 --- /dev/null +++ b/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/Makefile @@ -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) \ No newline at end of file diff --git a/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/Readme.md b/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/Readme.md new file mode 100644 index 0000000000..8b68deee32 --- /dev/null +++ b/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/Readme.md @@ -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 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 + ``` \ No newline at end of file diff --git a/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/tenetplusplus.cpp b/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/tenetplusplus.cpp new file mode 100644 index 0000000000..de030fa490 --- /dev/null +++ b/Ghidra/Debug/Debugger-importers/data/TenetPlusPlus_PinTool/tenetplusplus.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 KnobOutputFilePrefix( + KNOB_MODE_WRITEONCE, "pintool", "o", "trace", + "Prefix of the output .trace file."); + +static KNOB KnobStartAt( + KNOB_MODE_APPEND, "pintool", "s", "", + "Start tracing when an image:offset is hit.\nExample: -s " + "kernel32.dll:1234 -s app:2FFF"); + +static KNOB KnobStopAt( + KNOB_MODE_APPEND, "pintool", "e", "", + "Stop tracing when an image:offset is hit.\nExample: -e " + "kernel32.dll:1234 -e app:2FFF"); + +static KNOB 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 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 images; + PIN_RWMUTEX images_lock{}; + + // Here we store the names of the images inside our white list. + std::set > 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 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 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(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 image_manager; + + std::vector > pending_start_specs; + std::vector start_addrs; + + std::vector > pending_stop_specs; + std::vector 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(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(v); + const auto *data = new ThreadData(); + context.setThreadLocalData(tid, data); +} + +static VOID on_image_load(const IMG img, VOID *v) { + auto &context = *static_cast(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 tmp(size); + unsigned char *buf = nullptr; + buf = tmp.data(); + PIN_SafeCopy(buf, reinterpret_cast(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(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(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(REG_GR_LAST); ++reg) { + PIN_GetContextRegval(cpu, static_cast(reg), reinterpret_cast(&val)); + + if (val == data->m_cpu[reg]) continue; + + oss << REG_StringShort(static_cast(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 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(logFile); + context->image_manager = std::make_unique(); + + 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(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(inst) << "\n"; + abort(); + } + + PIN_StartProgram(); + + return 0; +} diff --git a/Ghidra/Debug/Debugger-importers/src/main/java/ghidra/app/util/opinion/TenetPlusPlusLoader.java b/Ghidra/Debug/Debugger-importers/src/main/java/ghidra/app/util/opinion/TenetPlusPlusLoader.java index bba45ff796..90872205e4 100644 --- a/Ghidra/Debug/Debugger-importers/src/main/java/ghidra/app/util/opinion/TenetPlusPlusLoader.java +++ b/Ghidra/Debug/Debugger-importers/src/main/java/ghidra/app/util/opinion/TenetPlusPlusLoader.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelineProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelineProvider.java index ebc1c31f14..58dd581996 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelineProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/timeline/BreakpointTimelineProvider.java @@ -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()))); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutor.java index ab8ee84f7f..0f46a853b1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutor.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutor.java @@ -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 { // TODO: Is there a way to know if a userop affects the stack? } + @Override + public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary 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 *