Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/debuggerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,7 @@ namespace BinaryNinjaDebuggerAPI {
// TTD Code Coverage Analysis Methods
bool IsInstructionExecuted(uint64_t address);
bool RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime);
size_t GetInstructionExecutionCount(uint64_t address);
size_t GetExecutedInstructionCount() const;
bool SaveCodeCoverageToFile(const std::string& filePath) const;
bool LoadCodeCoverageFromFile(const std::string& filePath);
Expand Down
4 changes: 4 additions & 0 deletions api/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,10 @@ bool DebuggerController::IsInstructionExecuted(uint64_t address)
return BNDebuggerIsInstructionExecuted(m_object, address);
}

size_t DebuggerController::GetInstructionExecutionCount(uint64_t address)
{
return BNDebuggerGetInstructionExecutionCount(m_object, address);
}

bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime)
{
Expand Down
1 change: 1 addition & 0 deletions api/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ extern "C"
// TTD Code Coverage Analysis Functions
DEBUGGER_FFI_API bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address);
DEBUGGER_FFI_API bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime);
DEBUGGER_FFI_API size_t BNDebuggerGetInstructionExecutionCount(BNDebuggerController* controller, uint64_t address);
DEBUGGER_FFI_API size_t BNDebuggerGetExecutedInstructionCount(BNDebuggerController* controller);
DEBUGGER_FFI_API bool BNDebuggerSaveCodeCoverageToFile(BNDebuggerController* controller, const char* filePath);
DEBUGGER_FFI_API bool BNDebuggerLoadCodeCoverageFromFile(BNDebuggerController* controller, const char* filePath);
Expand Down
11 changes: 11 additions & 0 deletions api/python/debuggercontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2927,6 +2927,17 @@ def is_instruction_executed(self, address: int) -> bool:
"""
return dbgcore.BNDebuggerIsInstructionExecuted(self.handle, address)

def get_instruction_execution_count(self, address: int) -> int:
"""
Get the execution count for a specific instruction address.

This method requires that run_code_coverage_analysis() has been called first.

:param address: address of the instruction to check
:return: number of times the instruction was executed (0 if not executed or analysis not run)
"""
return dbgcore.BNDebuggerGetInstructionExecutionCount(self.handle, address)

def get_executed_instruction_count(self) -> int:
"""
Get the count of executed instructions from the last code coverage analysis.
Expand Down
70 changes: 53 additions & 17 deletions core/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3198,7 +3198,27 @@ bool DebuggerController::IsInstructionExecuted(uint64_t address)
return false;
}

return m_executedInstructions.find(address) != m_executedInstructions.end();
return m_executedInstructionCounts.find(address) != m_executedInstructionCounts.end();
}

size_t DebuggerController::GetInstructionExecutionCount(uint64_t address)
{
if (!m_state->IsConnected() || !IsTTD())
{
return 0;
}

if (!m_codeCoverageAnalysisRun)
{
return 0;
}

auto iter = m_executedInstructionCounts.find(address);
if (iter != m_executedInstructionCounts.end())
{
return iter->second;
}
return 0;
}


Expand All @@ -3217,7 +3237,7 @@ bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t
}

// Clear previous analysis results
m_executedInstructions.clear();
m_executedInstructionCounts.clear();
m_codeCoverageAnalysisRun = false;

LogInfo("Starting TTD code coverage analysis.");
Expand All @@ -3243,23 +3263,22 @@ bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t
// Add all executed instruction addresses within the range
if (event.instructionAddress >= startAddress && event.instructionAddress <= endAddress)
{
// Check if the event is within the specified time range
m_executedInstructions.insert(event.address);
m_executedInstructionCounts[event.instructionAddress]++;
}
}
}

m_codeCoverageAnalysisRun = true;
LogInfo("TTD code coverage analysis completed for ranges. Found %" PRIu64 " executed instructions.",
(uint64_t)m_executedInstructions.size());
(uint64_t)m_executedInstructionCounts.size());

return true;
}


size_t DebuggerController::GetExecutedInstructionCount() const
{
return m_executedInstructions.size();
return m_executedInstructionCounts.size();
}


Expand All @@ -3282,17 +3301,18 @@ bool DebuggerController::SaveCodeCoverageToFile(const std::string& filePath) con

// Write header
uint32_t magic = 0x54544443; // "TTDC" - TTD Coverage
uint32_t version = 1;
size_t count = m_executedInstructions.size();
uint32_t version = 2;
size_t count = m_executedInstructionCounts.size();

file.write(reinterpret_cast<const char*>(&magic), sizeof(magic));
file.write(reinterpret_cast<const char*>(&version), sizeof(version));
file.write(reinterpret_cast<const char*>(&count), sizeof(count));

// Write addresses
for (uint64_t addr : m_executedInstructions)
// Write addresses and execution counts in pairs
for (const auto& [addr, execCount] : m_executedInstructionCounts)
{
file.write(reinterpret_cast<const char*>(&addr), sizeof(addr));
file.write(reinterpret_cast<const char*>(&execCount), sizeof(execCount));
}

file.close();
Expand Down Expand Up @@ -3331,22 +3351,38 @@ bool DebuggerController::LoadCodeCoverageFromFile(const std::string& filePath)
}

file.read(reinterpret_cast<char*>(&version), sizeof(version));
if (version != 1)
if (version != 1 && version != 2)
{
LogError("%s", fmt::format("Unsupported file version: {}", version).c_str());
return false;
}

file.read(reinterpret_cast<char*>(&count), sizeof(count));

// Clear existing data and read addresses
m_executedInstructions.clear();
// Clear existing data
m_executedInstructionCounts.clear();

for (size_t i = 0; i < count; i++)
// Read executed instruction addresses according to version
if (version == 1)
{
// Version 1 files don't have execution counts, so assume count = 1 for backward compatibility
for (size_t i = 0; i < count; i++)
{
uint64_t addr;
file.read(reinterpret_cast<char*>(&addr), sizeof(addr));
m_executedInstructionCounts[addr] = 1;
}
}
else if (version > 1)
{
uint64_t addr;
file.read(reinterpret_cast<char*>(&addr), sizeof(addr));
m_executedInstructions.insert(addr);
for (size_t i = 0; i < count; i++)
{
uint64_t addr;
uint32_t execCount;
file.read(reinterpret_cast<char*>(&addr), sizeof(addr));
file.read(reinterpret_cast<char*>(&execCount), sizeof(execCount));
m_executedInstructionCounts[addr] = execCount;
}
}

file.close();
Expand Down
3 changes: 2 additions & 1 deletion core/debuggercontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ namespace BinaryNinjaDebugger {
BinaryNinja::Ref<BinaryNinja::AnalysisCompletionEvent> m_rebaseCompletionEvent;

// TTD Code Coverage Analysis
std::unordered_set<uint64_t> m_executedInstructions;
std::unordered_map<uint64_t, uint32_t> m_executedInstructionCounts;
bool m_codeCoverageAnalysisRun = false;

public:
Expand Down Expand Up @@ -406,6 +406,7 @@ namespace BinaryNinjaDebugger {
// TTD Code Coverage Analysis Methods
bool IsInstructionExecuted(uint64_t address);
bool RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime);
size_t GetInstructionExecutionCount(uint64_t address);
size_t GetExecutedInstructionCount() const;
bool SaveCodeCoverageToFile(const std::string& filePath) const;
bool LoadCodeCoverageFromFile(const std::string& filePath);
Expand Down
5 changes: 5 additions & 0 deletions core/ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,11 @@ bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t
return controller->object->IsInstructionExecuted(address);
}

size_t BNDebuggerGetInstructionExecutionCount(BNDebuggerController* controller, uint64_t address)
{
return controller->object->GetInstructionExecutionCount(address);
}

bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime)
{
TTDPosition startPos(startTime.sequence, startTime.step);
Expand Down
23 changes: 17 additions & 6 deletions ui/ttdcoveragerenderlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ void TTDCoverageRenderLayer::ApplyToBlock(Ref<BasicBlock> block, std::vector<Dis
if (line.tokens.empty() || (line.tokens[0].type == CommentToken))
continue;

// Check if this instruction was executed during the TTD trace
bool isExecuted = controller->IsInstructionExecuted(line.addr);
// Check if this instruction was executed during the TTD trace (single lookup optimization)
uint64_t executionCount = controller->GetInstructionExecutionCount(line.addr);

if (isExecuted)
if (executionCount > 0)
{
// Highlight executed instructions with a red color
line.highlight.style = StandardHighlightColor;
Expand All @@ -57,6 +57,9 @@ void TTDCoverageRenderLayer::ApplyToBlock(Ref<BasicBlock> block, std::vector<Dis
line.highlight.g = 0;
line.highlight.b = 0;
line.highlight.alpha = 255;

InstructionTextToken execCountToken = InstructionTextToken(AnnotationToken, " [" + std::to_string(executionCount) + "]", line.addr);
line.tokens.push_back(execCountToken);
}
}
}
Expand All @@ -81,10 +84,10 @@ void TTDCoverageRenderLayer::ApplyToHighLevelILBody(Ref<Function> function, std:
if (line.tokens.empty() || (line.tokens[0].type == CommentToken))
continue;

// Check if this instruction was executed during the TTD trace
bool isExecuted = controller->IsInstructionExecuted(line.addr);
// Check if this instruction was executed during the TTD trace (single lookup optimization)
uint64_t executionCount = controller->GetInstructionExecutionCount(line.addr);

if (isExecuted)
if (executionCount > 0)
{
// Highlight executed instructions with a red color
line.highlight.style = StandardHighlightColor;
Expand All @@ -95,6 +98,14 @@ void TTDCoverageRenderLayer::ApplyToHighLevelILBody(Ref<Function> function, std:
line.highlight.g = 0;
line.highlight.b = 0;
line.highlight.alpha = 255;

//only add execution count if the line has tokens and is not only indentation
if (!line.tokens.empty() && std::prev(line.tokens.end())->type != IndentationToken)
{
InstructionTextToken execCountToken = InstructionTextToken(
AnnotationToken, " [" + std::to_string(executionCount) + "]", line.addr);
line.tokens.push_back(execCountToken);
}
}
}
}