Skip to content

Commit 9c9f508

Browse files
bdash0cyn
authored andcommitted
[SharedCache] Optimize ReadExportNode
`ReadExportNode` is called a lot during the initial load of the shared cache and thus impacts how long it takes for the UI to become responsive. This is a collection of optimizations that cut the time spent within `ReadExportNode` by 50%: 1. Pass iterators to `ReadExportNode` rather than a `DataBuffer` + offset. The lack of inlining in `DataBuffer`'s `operator[]` kills performance. Ideally this would have used `std::span`, but that would require bumping the minimum C++ version to C++20. 2. Add `MMappedFileAccessor::ReadSpan` so that `ReadExportNode` can operate directly on the mapped data without first copying it. 3. Removes a call to `GetAnalysisFunctionsForAddress` whose result was unused. 4. Use `std::find` to find the nul at the end of strings rather than assembling the string a character at a time. This avoids repeatedly growing the string. 5. Avoid the usual `Symbol` constructor in favor of `BNCreateSymbol`. The `Symbol` constructor has over head in two forms: 1. It has a `NameSpace` as an argument. Creating / destroying this allocates and deallocates memory We could create a single instance and reuse it for all calls, but... 2. The constructor copies all fields of the `NameSpace` to the heap before calling `BNCreateSymbol` and then deallocates them afterwards. This seems unnecessary, and adds a non-trivial amount of overhead. This can go back to directly constructing the `Symbol` once the constructors are improved.
1 parent af7715d commit 9c9f508

File tree

4 files changed

+88
-71
lines changed

4 files changed

+88
-71
lines changed

view/sharedcache/core/SharedCache.cpp

Lines changed: 65 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,14 @@ BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt) {
156156

157157
#pragma clang diagnostic push
158158
#pragma clang diagnostic ignored "-Wunused-function"
159-
static int64_t readSLEB128(DataBuffer& buffer, size_t length, size_t& offset)
159+
static int64_t readSLEB128(const uint8_t*& current, const uint8_t* end)
160160
{
161161
uint8_t cur;
162162
int64_t value = 0;
163163
size_t shift = 0;
164-
while (offset < length)
164+
while (current != end)
165165
{
166-
cur = buffer[offset++];
166+
cur = *current++;
167167
value |= (cur & 0x7f) << shift;
168168
shift += 7;
169169
if ((cur & 0x80) == 0)
@@ -175,16 +175,16 @@ static int64_t readSLEB128(DataBuffer& buffer, size_t length, size_t& offset)
175175
#pragma clang diagnostic pop
176176

177177

178-
static uint64_t readLEB128(DataBuffer& p, size_t end, size_t& offset)
178+
static uint64_t readLEB128(const uint8_t*& current, const uint8_t* end)
179179
{
180180
uint64_t result = 0;
181181
int bit = 0;
182182
do
183183
{
184-
if (offset >= end)
184+
if (current >= end)
185185
return -1;
186186

187-
uint64_t slice = p[offset] & 0x7f;
187+
uint64_t slice = *current & 0x7f;
188188

189189
if (bit > 63)
190190
return -1;
@@ -193,14 +193,14 @@ static uint64_t readLEB128(DataBuffer& p, size_t end, size_t& offset)
193193
result |= (slice << bit);
194194
bit += 7;
195195
}
196-
} while (p[offset++] & 0x80);
196+
} while (*current++ & 0x80);
197197
return result;
198198
}
199199

200200

201-
uint64_t readValidULEB128(DataBuffer& buffer, size_t& cursor)
201+
uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end)
202202
{
203-
uint64_t value = readLEB128(buffer, buffer.GetLength(), cursor);
203+
uint64_t value = readLEB128(current, end);
204204
if ((int64_t)value == -1)
205205
throw ReadException();
206206
return value;
@@ -2281,7 +2281,7 @@ std::optional<SharedCacheMachOHeader> SharedCache::LoadHeaderForAddress(std::sha
22812281
}
22822282

22832283
void SharedCache::InitializeHeader(
2284-
Ref<BinaryView> view, VM* vm, SharedCacheMachOHeader header, std::vector<MemoryRegion*> regionsToLoad)
2284+
Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, std::vector<MemoryRegion*> regionsToLoad)
22852285
{
22862286
WillMutateState();
22872287

@@ -2552,9 +2552,11 @@ void SharedCache::InitializeHeader(
25522552
uint64_t curfunc = header.textBase;
25532553
uint64_t curOffset;
25542554

2555-
while (i < header.functionStarts.funcsize)
2555+
auto current = static_cast<const uint8_t*>(funcStarts.GetData());
2556+
auto end = current + funcStarts.GetLength();
2557+
while (current != end)
25562558
{
2557-
curOffset = readLEB128(funcStarts, header.functionStarts.funcsize, i);
2559+
curOffset = readLEB128(current, end);
25582560
bool addFunction = false;
25592561
for (const auto& region : regionsToLoad)
25602562
{
@@ -2709,98 +2711,94 @@ void SharedCache::InitializeHeader(
27092711
}
27102712
}
27112713

2712-
struct ExportNode
2713-
{
2714-
std::string text;
2715-
uint64_t offset;
2716-
uint64_t flags;
2717-
};
2718-
27192714

2720-
void SharedCache::ReadExportNode(std::vector<Ref<Symbol>>& symbolList, SharedCacheMachOHeader& header, DataBuffer& buffer, uint64_t textBase,
2721-
const std::string& currentText, size_t cursor, uint32_t endGuard)
2715+
void SharedCache::ReadExportNode(std::vector<Ref<Symbol>>& symbolList, const SharedCacheMachOHeader& header,
2716+
const uint8_t* begin, const uint8_t* end, const uint8_t* current, uint64_t textBase, const std::string& currentText)
27222717
{
2723-
2724-
if (cursor > endGuard)
2718+
if (current >= end)
27252719
throw ReadException();
27262720

2727-
uint64_t terminalSize = readValidULEB128(buffer, cursor);
2728-
uint64_t childOffset = cursor + terminalSize;
2721+
uint64_t terminalSize = readValidULEB128(current, end);
2722+
const uint8_t* child = current + terminalSize;
27292723
if (terminalSize != 0) {
27302724
uint64_t imageOffset = 0;
2731-
uint64_t flags = readValidULEB128(buffer, cursor);
2725+
uint64_t flags = readValidULEB128(current, end);
27322726
if (!(flags & EXPORT_SYMBOL_FLAGS_REEXPORT))
27332727
{
2734-
imageOffset = readValidULEB128(buffer, cursor);
2735-
auto symbolType = m_dscView->GetAnalysisFunctionsForAddress(textBase + imageOffset).size() ? FunctionSymbol : DataSymbol;
2728+
imageOffset = readValidULEB128(current, end);
2729+
if (!currentText.empty() && textBase + imageOffset)
27362730
{
2737-
if (!currentText.empty() && textBase + imageOffset)
2731+
uint32_t flags;
2732+
BNSymbolType type;
2733+
for (auto s : header.sections)
27382734
{
2739-
uint32_t flags;
2740-
BNSymbolType type;
2741-
for (auto s : header.sections)
2735+
if (s.addr < textBase + imageOffset)
27422736
{
2743-
if (s.addr < textBase + imageOffset)
2737+
if (s.addr + s.size > textBase + imageOffset)
27442738
{
2745-
if (s.addr + s.size > textBase + imageOffset)
2746-
{
2747-
flags = s.flags;
2748-
}
2739+
flags = s.flags;
2740+
break;
27492741
}
27502742
}
2751-
if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS
2752-
|| (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS)
2753-
type = FunctionSymbol;
2754-
else
2755-
type = DataSymbol;
2743+
}
2744+
if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS
2745+
|| (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS)
2746+
type = FunctionSymbol;
2747+
else
2748+
type = DataSymbol;
27562749

27572750
#if EXPORT_TRIE_DEBUG
2758-
// BNLogInfo("export: %s -> 0x%llx", n.text.c_str(), image.baseAddress + n.offset);
2751+
// BNLogInfo("export: %s -> 0x%llx", n.text.c_str(), image.baseAddress + n.offset);
27592752
#endif
2760-
auto sym = new Symbol(type, currentText, textBase + imageOffset);
2761-
symbolList.push_back(sym);
2762-
}
2753+
// TODO: The usual `Symbol` constructors take a `NameSpace` and do unnecessary memory allocations
2754+
// to pass its fields down to the core API. Here we pass nullptr for the namespace which is treated
2755+
// the same, but avoids the memory allocations. Switch back to directly constructing a `Symbol`
2756+
// once it gains constructors without that overhead.
2757+
auto symbol = new Symbol(BNCreateSymbol(type, currentText.c_str(), currentText.c_str(),
2758+
currentText.c_str(), textBase + imageOffset, NoBinding, nullptr, 0));
2759+
symbolList.push_back(symbol);
27632760
}
27642761
}
27652762
}
2766-
cursor = childOffset;
2767-
uint8_t childCount = buffer[cursor];
2768-
cursor++;
2769-
if (cursor > endGuard)
2770-
throw ReadException();
2763+
current = child;
2764+
uint8_t childCount = *current++;
2765+
std::string childText = currentText;
27712766
for (uint8_t i = 0; i < childCount; ++i)
27722767
{
2773-
std::string childText;
2774-
while (buffer[cursor] != 0 & cursor <= endGuard)
2775-
childText.push_back(buffer[cursor++]);
2776-
cursor++;
2777-
if (cursor > endGuard)
2768+
if (current >= end)
2769+
throw ReadException();
2770+
auto it = std::find(current, end, 0);
2771+
childText.append(current, it);
2772+
current = it + 1;
2773+
if (current >= end)
27782774
throw ReadException();
2779-
auto next = readValidULEB128(buffer, cursor);
2775+
auto next = readValidULEB128(current, end);
27802776
if (next == 0)
27812777
throw ReadException();
2782-
ReadExportNode(symbolList, header, buffer, textBase, currentText + childText, next, endGuard);
2778+
ReadExportNode(symbolList, header, begin, end, begin + next, textBase, childText);
2779+
childText.resize(currentText.size());
27832780
}
27842781
}
27852782

27862783

2787-
std::vector<Ref<Symbol>> SharedCache::ParseExportTrie(std::shared_ptr<MMappedFileAccessor> linkeditFile, SharedCacheMachOHeader header)
2784+
std::vector<Ref<Symbol>> SharedCache::ParseExportTrie(std::shared_ptr<MMappedFileAccessor> linkeditFile, const SharedCacheMachOHeader& header)
27882785
{
2789-
std::vector<Ref<Symbol>> symbols;
2786+
if (!header.exportTrie.datasize) {
2787+
return {};
2788+
}
2789+
27902790
try
27912791
{
2792-
auto reader = linkeditFile;
2793-
2794-
std::vector<ExportNode> nodes;
2795-
2796-
DataBuffer buffer = reader->ReadBuffer(header.exportTrie.dataoff, header.exportTrie.datasize);
2797-
ReadExportNode(symbols, header, buffer, header.textBase, "", 0, header.exportTrie.datasize);
2792+
std::vector<Ref<Symbol>> symbols;
2793+
auto [begin, end] = linkeditFile->ReadSpan(header.exportTrie.dataoff, header.exportTrie.datasize);
2794+
ReadExportNode(symbols, header, begin, end, begin, header.textBase, "");
2795+
return symbols;
27982796
}
27992797
catch (std::exception& e)
28002798
{
28012799
BNLogError("Failed to load Export Trie");
2800+
return {};
28022801
}
2803-
return symbols;
28042802
}
28052803

28062804
std::vector<std::string> SharedCache::GetAvailableImages()

view/sharedcache/core/SharedCache.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -620,11 +620,11 @@ namespace SharedCacheCore {
620620
std::optional<SharedCacheMachOHeader> LoadHeaderForAddress(
621621
std::shared_ptr<VM> vm, uint64_t address, std::string installName);
622622
void InitializeHeader(
623-
Ref<BinaryView> view, VM* vm, SharedCacheMachOHeader header, std::vector<MemoryRegion*> regionsToLoad);
624-
void ReadExportNode(std::vector<Ref<Symbol>>& symbolList, SharedCacheMachOHeader& header, DataBuffer& buffer,
625-
uint64_t textBase, const std::string& currentText, size_t cursor, uint32_t endGuard);
623+
Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, std::vector<MemoryRegion*> regionsToLoad);
624+
void ReadExportNode(std::vector<Ref<Symbol>>& symbolList, const SharedCacheMachOHeader& header, const uint8_t* begin,
625+
const uint8_t *end, const uint8_t* current, uint64_t textBase, const std::string& currentText);
626626
std::vector<Ref<Symbol>> ParseExportTrie(
627-
std::shared_ptr<MMappedFileAccessor> linkeditFile, SharedCacheMachOHeader header);
627+
std::shared_ptr<MMappedFileAccessor> linkeditFile, const SharedCacheMachOHeader& header);
628628

629629
Ref<TypeLibrary> TypeLibraryForImage(const std::string& installName);
630630

view/sharedcache/core/VM.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,17 @@ BinaryNinja::DataBuffer MMappedFileAccessor::ReadBuffer(size_t address, size_t l
465465
return BinaryNinja::DataBuffer(data, length);
466466
}
467467

468+
std::pair<const uint8_t*, const uint8_t*> MMappedFileAccessor::ReadSpan(size_t address, size_t length)
469+
{
470+
if (address > m_mmap.len)
471+
throw MappingReadException();
472+
if (address + length > m_mmap.len)
473+
throw MappingReadException();
474+
const uint8_t* data = (&(((uint8_t*)m_mmap._mmap)[address]));
475+
return {data, data + length};
476+
}
477+
478+
468479
void MMappedFileAccessor::Read(void* dest, size_t address, size_t length)
469480
{
470481
if (address > m_mmap.len)

view/sharedcache/core/VM.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,14 @@ class MMappedFileAccessor {
187187

188188
BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length);
189189

190+
// Returns a range of pointers within the mapped memory region corresponding to
191+
// {addr, length}.
192+
// WARNING: The pointers returned by this method is only valid for the lifetime
193+
// of this file accessor.
194+
// TODO: This should use std::span<const uint8_t> once the minimum supported
195+
// C++ version supports it.
196+
std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t addr, size_t length);
197+
190198
void Read(void *dest, size_t addr, size_t length);
191199
};
192200

0 commit comments

Comments
 (0)