Skip to content

Commit daa0870

Browse files
committed
Move instruction bytes from BasicBlock into a transient object
Instruction bytes were previously stored on each basic block during `AnalyzeBasicBlock` and consumed during `LiftFunction` before being discarded. The `DataBuffer` member increased the size of every basic block object, even after the instruction data was discarded. The new approach uses a `LifterInstructionData` class that holds a map from basic block start address to the corresponding instruction data. An instance is created prior to `AnalyzeBasicBlock` and stored on the function object. After the function is lifted, the instance is discarded. `AnalyzeBasicBlock` and `LiftFunction` can access the `LifterInstructionData` via their context objects in order to populate and access its data respectively.
1 parent 5e76336 commit daa0870

10 files changed

Lines changed: 212 additions & 113 deletions

File tree

architecture.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,42 @@ vector<InstructionTextToken> InstructionTextToken::ConvertInstructionTextTokenLi
204204
return result;
205205
}
206206

207+
LifterInstructionData::LifterInstructionData(BNLifterInstructionData* instrData)
208+
{
209+
m_object = instrData;
210+
}
211+
212+
213+
void LifterInstructionData::Append(BasicBlock* block, std::span<const uint8_t> data)
214+
{
215+
BNLifterInstructionDataAppend(m_object, block->GetObject(), data.data(), data.size());
216+
}
217+
218+
219+
std::span<const uint8_t> LifterInstructionData::Get(BasicBlock* block, uint64_t addr)
220+
{
221+
size_t len = 0;
222+
const uint8_t* opcode = BNLifterInstructionDataGet(m_object, block->GetObject(), addr, &len);
223+
if (!opcode)
224+
return {};
225+
return std::span<const uint8_t>(opcode, len);
226+
}
227+
228+
207229
BasicBlockAnalysisContext::BasicBlockAnalysisContext(BNBasicBlockAnalysisContext* context)
208230
{
209231
m_context = context;
232+
if (context->lifterInstructionData)
233+
{
234+
m_lifterInstructionData =
235+
new LifterInstructionData(BNNewLifterInstructionDataReference(context->lifterInstructionData));
236+
}
237+
}
238+
239+
240+
Ref<LifterInstructionData> BasicBlockAnalysisContext::GetLifterInstructionData()
241+
{
242+
return m_lifterInstructionData;
210243
}
211244

212245
const std::map<ArchAndAddr, std::set<ArchAndAddr>> BasicBlockAnalysisContext::GetIndirectBranches()
@@ -526,6 +559,11 @@ FunctionLifterContext::FunctionLifterContext(LowLevelILFunction* func, BNFunctio
526559
}
527560

528561
m_functionArchContext = context->functionArchContext;
562+
if (context->lifterInstructionData)
563+
{
564+
m_lifterInstructionData =
565+
new LifterInstructionData(BNNewLifterInstructionDataReference(context->lifterInstructionData));
566+
}
529567
m_containsInlinedFunctions = context->containsInlinedFunctions;
530568
}
531569

basicblock.cpp

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -454,24 +454,6 @@ void BasicBlock::SetUndeterminedOutgoingEdges(bool value)
454454
}
455455

456456

457-
bool BasicBlock::HasInstructionData() const
458-
{
459-
return BNBasicBlockHasInstructionData(m_object);
460-
}
461-
462-
463-
const uint8_t* BasicBlock::GetInstructionData(uint64_t addr, size_t* len) const
464-
{
465-
return BNBasicBlockGetInstructionData(m_object, addr, len);
466-
}
467-
468-
469-
void BasicBlock::AddInstructionData(const void* data, size_t len)
470-
{
471-
BNBasicBlockAddInstructionData(m_object, data, len);
472-
}
473-
474-
475457
void BasicBlock::SetFallThroughToFunction(bool value)
476458
{
477459
BNBasicBlockSetFallThroughToFunction(m_object, value);

binaryninjaapi.h

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include <type_traits>
5353
#include <optional>
5454
#include <memory>
55+
#include <span>
5556
#include <any>
5657
#include <fmt/format.h>
5758
#include <fmt/ranges.h>
@@ -9647,6 +9648,19 @@ namespace BinaryNinja {
96479648

96489649
typedef size_t ExprId;
96499650

9651+
/*! Per-function store of basic block instruction bytes, populated during basic block analysis
9652+
and read during lifting. Reached through BasicBlockAnalysisContext and FunctionLifterContext.
9653+
*/
9654+
class LifterInstructionData : public CoreRefCountObject<BNLifterInstructionData,
9655+
BNNewLifterInstructionDataReference, BNFreeLifterInstructionData>
9656+
{
9657+
public:
9658+
LifterInstructionData(BNLifterInstructionData* instrData);
9659+
9660+
void Append(BasicBlock* block, std::span<const uint8_t> data);
9661+
std::span<const uint8_t> Get(BasicBlock* block, uint64_t addr);
9662+
};
9663+
96509664
class BasicBlockAnalysisContext
96519665
{
96529666
private:
@@ -9663,6 +9677,8 @@ namespace BinaryNinja {
96639677
std::optional<std::set<ArchAndAddr>> m_haltedDisassemblyAddresses;
96649678
std::optional<std::map<ArchAndAddr, ArchAndAddr>> m_inlinedUnresolvedIndirectBranches;
96659679

9680+
Ref<LifterInstructionData> m_lifterInstructionData;
9681+
96669682
public:
96679683
BNBasicBlockAnalysisContext* m_context;
96689684

@@ -9691,6 +9707,8 @@ namespace BinaryNinja {
96919707
bool SetFunctionArchContextRaw(void* p);
96929708
void* GetFunctionArchContextRaw() const { return m_context->functionArchContext; }
96939709

9710+
Ref<LifterInstructionData> GetLifterInstructionData();
9711+
96949712
template <class ArchT>
96959713
bool SetFunctionArchContext(const ArchT* arch, typename ArchT::FunctionArchContext* context)
96969714
{
@@ -9726,6 +9744,7 @@ namespace BinaryNinja {
97269744
std::set<uint64_t> m_inlinedCalls;
97279745
bool* m_containsInlinedFunctions;
97289746
void* m_functionArchContext;
9747+
Ref<LifterInstructionData> m_lifterInstructionData;
97299748

97309749
public:
97319750
BNFunctionLifterContext* m_context;
@@ -9742,6 +9761,7 @@ namespace BinaryNinja {
97429761
std::set<uint64_t>& GetInlinedCalls();
97439762
void SetContainsInlinedFunctions(bool value);
97449763
void* GetFunctionArchContextRaw() const { return m_functionArchContext; }
9764+
Ref<LifterInstructionData>& GetLifterInstructionData() { return m_lifterInstructionData; }
97459765
template <class ArchT>
97469766
typename ArchT::FunctionArchContext* GetFunctionArchContext(const ArchT* arch)
97479767
{
@@ -12734,21 +12754,6 @@ namespace BinaryNinja {
1273412754
*/
1273512755
void SetUndeterminedOutgoingEdges(bool value);
1273612756

12737-
/*! Get the instruction data for a specific address in this basic block
12738-
12739-
\param addr Address of the instruction
12740-
\param len Pointer to a size_t variable to store the length of the instruction data
12741-
\return Pointer to the instruction data
12742-
*/
12743-
const uint8_t* GetInstructionData(uint64_t addr, size_t* len) const;
12744-
12745-
/*! Add instruction data to the basic block
12746-
12747-
\param data Pointer to the instruction data
12748-
\param len Length of the instruction data
12749-
*/
12750-
void AddInstructionData(const void* data, size_t len);
12751-
1275212757
/*! Set whether the basic blocks falls through to a function
1275312758

1275412759
\param value Whether the basic block falls through to a function
@@ -12773,12 +12778,6 @@ namespace BinaryNinja {
1277312778
*/
1277412779
void SetCanExit(bool value);
1277512780

12776-
/*! Determine whether this basic block has instruction data
12777-
12778-
\return Whether this basic block has instruction data
12779-
*/
12780-
bool HasInstructionData() const;
12781-
1278212781
/*! List of dominators for this basic block
1278312782

1278412783
\param post Whether to get post dominators (default: false)

binaryninjacore.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@
3737
// Current ABI version for linking to the core. This is incremented any time
3838
// there are changes to the API that affect linking, including new functions,
3939
// new types, or modifications to existing functions or types.
40-
#define BN_CURRENT_CORE_ABI_VERSION 178
40+
#define BN_CURRENT_CORE_ABI_VERSION 179
4141

4242
// Minimum ABI version that is supported for loading of plugins. Plugins that
4343
// are linked to an ABI version less than this will not be able to load and
4444
// will require rebuilding. The minimum version is increased when there are
4545
// incompatible changes that break binary compatibility, such as changes to
4646
// existing types or functions.
47-
#define BN_MINIMUM_CORE_ABI_VERSION 178
47+
#define BN_MINIMUM_CORE_ABI_VERSION 179
4848

4949
#ifdef __GNUC__
5050
#ifdef BINARYNINJACORE_LIBRARY
@@ -250,6 +250,7 @@ extern "C"
250250
typedef struct BNArchitecture BNArchitecture;
251251
typedef struct BNFunction BNFunction;
252252
typedef struct BNBasicBlock BNBasicBlock;
253+
typedef struct BNLifterInstructionData BNLifterInstructionData;
253254
typedef struct BNDownloadProvider BNDownloadProvider;
254255
typedef struct BNDownloadInstance BNDownloadInstance;
255256
typedef struct BNWebsocketProvider BNWebsocketProvider;
@@ -2168,6 +2169,7 @@ extern "C"
21682169
BNArchitectureAndAddress* inlinedUnresolvedIndirectBranches;
21692170

21702171
void* functionArchContext;
2172+
BNLifterInstructionData* lifterInstructionData;
21712173
} BNBasicBlockAnalysisContext;
21722174

21732175
typedef struct BNFunctionLifterContext {
@@ -2195,6 +2197,7 @@ extern "C"
21952197
uint64_t* inlinedCalls;
21962198

21972199
void* functionArchContext;
2200+
BNLifterInstructionData* lifterInstructionData;
21982201

21992202
// OUT
22002203
bool* containsInlinedFunctions;
@@ -5414,6 +5417,14 @@ extern "C"
54145417

54155418
BINARYNINJACOREAPI BNBasicBlock* BNNewBasicBlockReference(BNBasicBlock* block);
54165419
BINARYNINJACOREAPI void BNFreeBasicBlock(BNBasicBlock* block);
5420+
5421+
BINARYNINJACOREAPI BNLifterInstructionData* BNNewLifterInstructionDataReference(
5422+
BNLifterInstructionData* instrData);
5423+
BINARYNINJACOREAPI void BNFreeLifterInstructionData(BNLifterInstructionData* instrData);
5424+
BINARYNINJACOREAPI void BNLifterInstructionDataAppend(
5425+
BNLifterInstructionData* instrData, BNBasicBlock* block, const void* data, size_t len);
5426+
BINARYNINJACOREAPI const uint8_t* BNLifterInstructionDataGet(
5427+
BNLifterInstructionData* instrData, BNBasicBlock* block, uint64_t addr, size_t* len);
54175428
BINARYNINJACOREAPI BNBasicBlock** BNGetFunctionBasicBlockList(BNFunction* func, size_t* count);
54185429
BINARYNINJACOREAPI void BNFreeBasicBlockList(BNBasicBlock** blocks, size_t count);
54195430
BINARYNINJACOREAPI BNBasicBlock* BNGetFunctionBasicBlockAtAddress(
@@ -5573,9 +5584,6 @@ extern "C"
55735584
BINARYNINJACOREAPI void BNFreePendingBasicBlockEdgeList(BNPendingBasicBlockEdge* edges);
55745585
BINARYNINJACOREAPI void BNClearBasicBlockPendingOutgoingEdges(BNBasicBlock* block);
55755586
BINARYNINJACOREAPI void BNBasicBlockSetUndeterminedOutgoingEdges(BNBasicBlock* block, bool value);
5576-
BINARYNINJACOREAPI const bool BNBasicBlockHasInstructionData(BNBasicBlock* block);
5577-
BINARYNINJACOREAPI const uint8_t* BNBasicBlockGetInstructionData(BNBasicBlock* block, uint64_t addr, size_t* len);
5578-
BINARYNINJACOREAPI void BNBasicBlockAddInstructionData(BNBasicBlock* block, const void* data, size_t len);
55795587
BINARYNINJACOREAPI void BNBasicBlockSetFallThroughToFunction(BNBasicBlock* block, bool value);
55805588
BINARYNINJACOREAPI bool BNBasicBlockIsFallThroughToFunction(BNBasicBlock* block);
55815589
BINARYNINJACOREAPI bool BNBasicBlockCanExit(BNBasicBlock* block);

defaultarch.cpp

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly
101101
auto& haltedDisassemblyAddresses = context.GetHaltedDisassemblyAddresses();
102102
auto& inlinedUnresolvedIndirectBranches = context.GetInlinedUnresolvedIndirectBranches();
103103

104+
Ref<LifterInstructionData> instrData = context.GetLifterInstructionData();
105+
104106
bool hasInvalidInstructions = false;
105107
set<ArchAndAddr> guidedSourceBlockTargets;
106108
auto guidedSourceBlocks = function->GetGuidedSourceBlocks();
@@ -211,9 +213,13 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly
211213
{
212214
// Instruction is in the middle of a block, need to split the basic block into two
213215
Ref<BasicBlock> splitBlock = context.CreateBasicBlock(location.arch, location.address);
214-
size_t instrDataLen;
215-
const uint8_t* instrData = targetBlock->GetInstructionData(location.address, &instrDataLen);
216-
splitBlock->AddInstructionData(instrData, instrDataLen);
216+
if (instrData)
217+
{
218+
// Copy before appending, as Append can invalidate the span returned by Get
219+
std::span<const uint8_t> tail = instrData->Get(targetBlock, location.address);
220+
std::vector<uint8_t> splitData(tail.begin(), tail.end());
221+
instrData->Append(splitBlock, splitData);
222+
}
217223
splitBlock->SetFallThroughToFunction(targetBlock->IsFallThroughToFunction());
218224
splitBlock->SetUndeterminedOutgoingEdges(targetBlock->HasUndeterminedOutgoingEdges());
219225
splitBlock->SetCanExit(targetBlock->CanExit());
@@ -594,7 +600,8 @@ void Architecture::DefaultAnalyzeBasicBlocks(Function* function, BasicBlockAnaly
594600
}
595601

596602
location.address += info.length;
597-
block->AddInstructionData(opcode, info.length);
603+
if (instrData)
604+
instrData->Append(block, std::span<const uint8_t>(opcode, info.length));
598605

599606
if (endsBlock && !info.delaySlots)
600607
break;
@@ -780,12 +787,13 @@ static void ApplyExternPointerForRelocation(
780787

781788
bool Architecture::DefaultLiftFunction(LowLevelILFunction* function, FunctionLifterContext& context)
782789
{
783-
std::unique_ptr<FastBasicBlockMap<DataBuffer>> instrData;
784790
Ref<BinaryView> data = context.GetView();
785791
Ref<Logger> logger = context.GetLogger();
786792
Ref<Platform> platform = context.GetPlatform();
787793
std::set<ArchAndAddr> noReturnCalls = context.GetNoReturnCalls();
788794
std::vector<Ref<BasicBlock>> blocks = context.GetBasicBlocks();
795+
Ref<LifterInstructionData> lifterInstructionData = context.GetLifterInstructionData();
796+
FastBasicBlockMap<DataBuffer> instrData(blocks);
789797
std::map<ArchAndAddr, bool> contextualReturns = context.GetContextualReturns();
790798
std::map<ArchAndAddr, ArchAndAddr> inlinedRemapping = context.GetInlinedRemapping();
791799
std::optional<pair<ArchAndAddr, ArchAndAddr>> indirectSource;
@@ -835,26 +843,20 @@ bool Architecture::DefaultLiftFunction(LowLevelILFunction* function, FunctionLif
835843
}
836844

837845
size_t len = 0;
838-
const uint8_t* opcode;
839-
840-
if (i->HasInstructionData())
846+
const uint8_t* opcode = nullptr;
847+
if (lifterInstructionData)
841848
{
842-
opcode = i->GetInstructionData(addr, &len);
843-
844-
if (len == 0)
845-
{
846-
// Instruction data not found, emit undefined IL instruction
847-
function->AddInstruction(function->AddExpr(LLIL_UNDEF, 0, 0));
848-
logger->LogDebug("Instruction data not found, inserted LLIL_UNDEF at %#" PRIx64, addr);
849-
break;
850-
}
849+
std::span<const uint8_t> bytes = lifterInstructionData->Get(i, addr);
850+
opcode = bytes.data();
851+
len = bytes.size();
851852
}
852-
else
853-
{
854-
if (!instrData)
855-
instrData = std::make_unique<FastBasicBlockMap<DataBuffer>>(blocks);
856853

857-
DataBuffer& buffer = (*instrData)[i];
854+
if (!opcode)
855+
{
856+
// The instruction data has no bytes for this block (a function loaded from the
857+
// database, a block split after analysis, or an architecture that does not populate
858+
// it). Read the block from the view instead.
859+
DataBuffer& buffer = instrData[i];
858860
if (buffer.GetLength() == 0)
859861
buffer = data->ReadBuffer(i->GetStart(), i->GetEnd() - i->GetStart());
860862

python/architecture.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,34 @@
6868
IntrinsicType = Union[IntrinsicName, 'lowlevelil.ILIntrinsic', IntrinsicIndex]
6969

7070

71+
class LifterInstructionData:
72+
"""Per-function store of basic block instruction bytes, populated during basic block analysis and
73+
read during lifting.
74+
75+
.. note:: This class is meant to be used by Architecture plugins only
76+
"""
77+
78+
def __init__(self, handle: core.BNLifterInstructionDataHandle):
79+
self.handle = handle
80+
81+
def __del__(self):
82+
if core is not None:
83+
core.BNFreeLifterInstructionData(self.handle)
84+
85+
def append(self, block: "basicblock.BasicBlock", data: bytes) -> None:
86+
"""Append decoded bytes for a block. Call during basic block analysis only."""
87+
core.BNLifterInstructionDataAppend(self.handle, block.handle, data, len(data))
88+
89+
def get(self, block: "basicblock.BasicBlock", addr: int) -> bytes:
90+
"""Returns the bytes from ``addr`` to the end of its block, or ``b''`` when the block has no
91+
stored data. Read-only, call during lifting."""
92+
size = ctypes.c_ulonglong(0)
93+
ptr = core.BNLifterInstructionDataGet(self.handle, block.handle, addr, ctypes.byref(size))
94+
if not ptr:
95+
return b''
96+
return ctypes.string_at(ptr, size.value)
97+
98+
7199
@dataclass
72100
class BasicBlockAnalysisContext:
73101
"""Used by ``analyze_basic_blocks`` and contains analysis settings and other contextual information.
@@ -217,6 +245,16 @@ def max_function_size(self) -> int:
217245

218246
return self._max_function_size
219247

248+
@property
249+
def lifter_instruction_data(self) -> Optional["LifterInstructionData"]:
250+
"""The per-function instruction byte store. Populate it during basic block analysis so that
251+
lifting can read instruction bytes without touching the view from the multi-threaded stage."""
252+
253+
handle = self._handle.lifterInstructionData
254+
if not handle:
255+
return None
256+
return LifterInstructionData(core.BNNewLifterInstructionDataReference(handle))
257+
220258
@property
221259
def halt_on_invalid_instruction(self) -> bool:
222260
"""Get the setting from context that determines if analysis should halt on invalid instructions."""
@@ -533,6 +571,15 @@ def function_arch_context(self) -> Any:
533571

534572
return self._function.arch.function_arch_contexts.get(self._function_arch_context_token, None)
535573

574+
@property
575+
def lifter_instruction_data(self) -> Optional["LifterInstructionData"]:
576+
"""The per-function instruction byte store populated during basic block analysis."""
577+
578+
handle = self._handle.lifterInstructionData
579+
if not handle:
580+
return None
581+
return LifterInstructionData(core.BNNewLifterInstructionDataReference(handle))
582+
536583
@dataclass(frozen=True)
537584
class RegisterInfo:
538585
full_width_reg: RegisterName

0 commit comments

Comments
 (0)