Add support to process INLINE records in symbol files

This adds the support to process INLINE and INLINE_ORIGIN records in
symbol files and to generate inlined frames using those records if
possible.

Bug: 1190878
Change-Id: Ia0b6d56c9de37cf818d9bb6842d58c9b68f235b2
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/3024690
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
Zequan Wu 2021-07-22 11:13:37 -07:00 committed by Joshua Peraza
parent 32096a2dc8
commit f080350795
18 changed files with 948 additions and 363 deletions

View file

@ -40,6 +40,7 @@
#include <limits>
#include <map>
#include <memory>
#include <utility>
#include <vector>
@ -52,6 +53,7 @@
using std::map;
using std::vector;
using std::make_pair;
using std::unique_ptr;
namespace google_breakpad {
@ -202,6 +204,16 @@ bool BasicSourceLineResolver::Module::LoadMapFromMemory(
// Ignore these as well, they're similarly just for housekeeping.
//
// INFO CODE_ID <code id> <filename>
} else if (strncmp(buffer, "INLINE ", 7) == 0) {
linked_ptr<Inline> in = ParseInline(buffer);
if (!in.get())
LogParseError("ParseInline failed", line_number, &num_errors);
else
cur_func->AppendInline(in);
} else if (strncmp(buffer, "INLINE_ORIGIN ", 14) == 0) {
if (!ParseInlineOrigin(buffer)) {
LogParseError("ParseInlineOrigin failed", line_number, &num_errors);
}
} else {
if (!cur_func.get()) {
LogParseError("Found source line data without a function",
@ -225,7 +237,39 @@ bool BasicSourceLineResolver::Module::LoadMapFromMemory(
return true;
}
void BasicSourceLineResolver::Module::LookupAddress(StackFrame* frame) const {
int BasicSourceLineResolver::Module::ConstructInlineFrames(
StackFrame* frame,
MemAddr address,
const RangeMap<uint64_t, linked_ptr<Inline>>& inlines,
vector<unique_ptr<StackFrame>>* inlined_frames) const {
linked_ptr<Inline> in;
MemAddr inline_base;
if (!inlines.RetrieveRange(address, &in, &inline_base, nullptr, nullptr))
return -1;
StackFrame new_frame = StackFrame(*frame);
new_frame.function_name = in->name;
// Use the starting adress of the inlined range as inlined function base.
new_frame.function_base = new_frame.module->base_address() + inline_base;
auto it = files_.find(in->source_file_id);
if (it != files_.end())
new_frame.source_file_name = it->second;
new_frame.trust = StackFrame::FRAME_TRUST_INLINE;
// Must add frames before calling ConstructInlineFrames to get correct order.
int current_idx = inlined_frames->size();
inlined_frames->push_back(std::make_unique<StackFrame>(new_frame));
int source_line = ConstructInlineFrames(&new_frame, address,
in->child_inlines, inlined_frames);
if (source_line != -1) {
(*inlined_frames)[current_idx]->source_line = source_line;
}
return in->call_site_line;
}
void BasicSourceLineResolver::Module::LookupAddress(
StackFrame* frame,
vector<unique_ptr<StackFrame>>* inlined_frames) const {
MemAddr address = frame->instruction - frame->module->base_address();
// First, look for a FUNC record that covers address. Use
@ -256,6 +300,15 @@ void BasicSourceLineResolver::Module::LookupAddress(StackFrame* frame) const {
frame->source_line = line->line;
frame->source_line_base = frame->module->base_address() + line_base;
}
// Check if this is inlined function call.
if (inlined_frames) {
int source_line =
ConstructInlineFrames(frame, address, func->inlines, inlined_frames);
if (source_line != -1) {
frame->source_line = source_line;
}
}
} else if (public_symbols_.Retrieve(address,
&public_symbol, &public_address) &&
(!func.get() || public_address > function_base)) {
@ -357,6 +410,38 @@ bool BasicSourceLineResolver::Module::ParseFile(char* file_line) {
return false;
}
bool BasicSourceLineResolver::Module::ParseInlineOrigin(
char* inline_origin_line) {
long origin_id;
long source_file_id;
char* origin_name;
if (SymbolParseHelper::ParseInlineOrigin(inline_origin_line, &origin_id,
&source_file_id, &origin_name)) {
inline_origins_.insert(make_pair(
origin_id, new InlineOrigin(origin_id, source_file_id, origin_name)));
return true;
}
return false;
}
linked_ptr<BasicSourceLineResolver::Inline>
BasicSourceLineResolver::Module::ParseInline(char* inline_line) {
long inline_nest_level;
long call_site_line;
long origin_id;
vector<std::pair<MemAddr, MemAddr>> ranges;
if (SymbolParseHelper::ParseInline(inline_line, &inline_nest_level,
&call_site_line, &origin_id, &ranges)) {
auto origin = inline_origins_.find(origin_id);
if (origin != inline_origins_.end()) {
return linked_ptr<Inline>(
new Inline(inline_nest_level, call_site_line, origin->second->name,
origin->second->source_file_id, ranges));
}
}
return linked_ptr<Inline>();
}
BasicSourceLineResolver::Function*
BasicSourceLineResolver::Module::ParseFunction(char* function_line) {
bool is_multiple;
@ -502,6 +587,26 @@ bool BasicSourceLineResolver::Module::ParseCFIFrameInfo(
return true;
}
bool BasicSourceLineResolver::Function::AppendInline(linked_ptr<Inline> in) {
// This happends if in's parent wasn't added due to a malformed INLINE record.
if (in->inline_nest_level > last_added_inline_nest_level + 1)
return false;
RangeMap<MemAddr, linked_ptr<Inline>>* current_inlines = &this->inlines;
auto iter = recent_inlines.find(in->inline_nest_level - 1);
if (iter != recent_inlines.end())
current_inlines = &iter->second->child_inlines;
else
assert(in->inline_nest_level == 0);
last_added_inline_nest_level = in->inline_nest_level;
recent_inlines[last_added_inline_nest_level] = in;
// Store all ranges into current level of inlines.
for (auto range : in->inline_ranges)
current_inlines->StoreRange(range.first, range.second, in);
return true;
}
// static
bool SymbolParseHelper::ParseFile(char* file_line, long* index,
char** filename) {
@ -529,6 +634,97 @@ bool SymbolParseHelper::ParseFile(char* file_line, long* index,
return true;
}
// static
bool SymbolParseHelper::ParseInlineOrigin(char* inline_origin_line,
long* origin_id,
long* file_id,
char** name) {
// INLINE_ORIGIN <origin_id> <file_id> <name>
assert(strncmp(inline_origin_line, "INLINE_ORIGIN ", 14) == 0);
inline_origin_line += 14; // skip prefix
vector<char*> tokens;
if (!Tokenize(inline_origin_line, kWhitespace, 3, &tokens)) {
return false;
}
char* after_number;
*origin_id = strtol(tokens[0], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *origin_id < 0 ||
*origin_id == std::numeric_limits<long>::max()) {
return false;
}
*file_id = strtol(tokens[1], &after_number, 10);
// If the file id is -1, it might be an artificial function that doesn't have
// file id. So, we consider -1 as a valid special case.
if (!IsValidAfterNumber(after_number) ||
*file_id < -1 | *origin_id == std::numeric_limits<long>::max()) {
return false;
}
*name = tokens[2];
if (!*name) {
return false;
}
return true;
}
// static
bool SymbolParseHelper::ParseInline(
char* inline_line,
long* inline_nest_level,
long* call_site_line,
long* origin_id,
vector<std::pair<MemAddr, MemAddr>>* ranges) {
// INLINE <inline_nest_level> <call_site_line> <origin_id> <address> <size>
// ...
assert(strncmp(inline_line, "INLINE ", 7) == 0);
inline_line += 7; // skip prefix
vector<char*> tokens;
Tokenize(inline_line, kWhitespace, std::numeric_limits<int>::max(), &tokens);
// The length of the vector should be at least 5 and an odd number.
if (tokens.size() < 5 && tokens.size() % 2 == 0)
return false;
char* after_number;
*inline_nest_level = strtol(tokens[0], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *inline_nest_level < 0 ||
*inline_nest_level == std::numeric_limits<long>::max()) {
return false;
}
*call_site_line = strtol(tokens[1], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *call_site_line < 0 ||
*call_site_line == std::numeric_limits<long>::max()) {
return false;
}
*origin_id = strtol(tokens[2], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *origin_id < 0 ||
*origin_id == std::numeric_limits<long>::max()) {
return false;
}
for (size_t i = 3; i < tokens.size();) {
MemAddr address = strtoull(tokens[i++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
address == std::numeric_limits<unsigned long long>::max()) {
return false;
}
MemAddr size = strtoull(tokens[i++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
size == std::numeric_limits<unsigned long long>::max()) {
return false;
}
ranges->push_back({address, size});
}
return true;
}
// static
bool SymbolParseHelper::ParseFunction(char* function_line, bool* is_multiple,
uint64_t* address, uint64_t* size,