Revert "Fix incorrect source file name for inlined frames"
This reverts commit54d878abcb
.54d878abcb
changed the dump_syms format incompatibly. This must be redone in a multi-step process: the processor must be made to understand the old and new formats simultaneously and the processor service must be rebuilt and run with that update before dump_syms output can change to use the new format. Bug: chromium:1263390 Change-Id: I5b6f8aff8ea2916b2c07ac6a74b569fa27db51b9 Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/3244775 Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
parent
076073c96b
commit
dfcb7b6799
18 changed files with 214 additions and 223 deletions
|
@ -254,6 +254,9 @@ struct DwarfCUToModule::CUContext {
|
||||||
|
|
||||||
// A map of function pointers to the its forward specification DIE's offset.
|
// A map of function pointers to the its forward specification DIE's offset.
|
||||||
map<Module::Function*, uint64_t> spec_function_offsets;
|
map<Module::Function*, uint64_t> spec_function_offsets;
|
||||||
|
|
||||||
|
// From file index to vector of subprogram's offset in this CU.
|
||||||
|
map<uint64_t, vector<uint64_t>> inline_origins;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Information about the context of a particular DIE. This is for
|
// Information about the context of a particular DIE. This is for
|
||||||
|
@ -558,8 +561,7 @@ class DwarfCUToModule::InlineHandler : public GenericDIEHandler {
|
||||||
DwarfForm high_pc_form_; // DW_AT_high_pc can be length or address.
|
DwarfForm high_pc_form_; // DW_AT_high_pc can be length or address.
|
||||||
DwarfForm ranges_form_; // DW_FORM_sec_offset or DW_FORM_rnglistx
|
DwarfForm ranges_form_; // DW_FORM_sec_offset or DW_FORM_rnglistx
|
||||||
uint64_t ranges_data_; // DW_AT_ranges
|
uint64_t ranges_data_; // DW_AT_ranges
|
||||||
int call_site_line_; // DW_AT_call_line
|
int call_site_line_;
|
||||||
int call_site_file_id_; // DW_AT_call_file
|
|
||||||
int inline_nest_level_;
|
int inline_nest_level_;
|
||||||
// A vector of inlines in the same nest level. It's owned by its parent
|
// A vector of inlines in the same nest level. It's owned by its parent
|
||||||
// function/inline. At Finish(), add this inline into the vector.
|
// function/inline. At Finish(), add this inline into the vector.
|
||||||
|
@ -587,9 +589,6 @@ void DwarfCUToModule::InlineHandler::ProcessAttributeUnsigned(
|
||||||
case DW_AT_call_line:
|
case DW_AT_call_line:
|
||||||
call_site_line_ = data;
|
call_site_line_ = data;
|
||||||
break;
|
break;
|
||||||
case DW_AT_call_file:
|
|
||||||
call_site_file_id_ = data;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
GenericDIEHandler::ProcessAttributeUnsigned(attr, form, data);
|
GenericDIEHandler::ProcessAttributeUnsigned(attr, form, data);
|
||||||
break;
|
break;
|
||||||
|
@ -662,8 +661,8 @@ void DwarfCUToModule::InlineHandler::Finish() {
|
||||||
cu_context_->file_context->module_->inline_origin_map
|
cu_context_->file_context->module_->inline_origin_map
|
||||||
.GetOrCreateInlineOrigin(specification_offset_, name_);
|
.GetOrCreateInlineOrigin(specification_offset_, name_);
|
||||||
unique_ptr<Module::Inline> in(
|
unique_ptr<Module::Inline> in(
|
||||||
new Module::Inline(origin, ranges, call_site_line_, call_site_file_id_,
|
new Module::Inline(origin, ranges, call_site_line_, inline_nest_level_,
|
||||||
inline_nest_level_, std::move(child_inlines_)));
|
std::move(child_inlines_)));
|
||||||
inlines_.push_back(std::move(in));
|
inlines_.push_back(std::move(in));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,6 +679,7 @@ class DwarfCUToModule::FuncHandler: public GenericDIEHandler {
|
||||||
high_pc_form_(DW_FORM_addr),
|
high_pc_form_(DW_FORM_addr),
|
||||||
ranges_form_(DW_FORM_sec_offset),
|
ranges_form_(DW_FORM_sec_offset),
|
||||||
ranges_data_(0),
|
ranges_data_(0),
|
||||||
|
decl_file_data_(UINT64_MAX),
|
||||||
inline_(false),
|
inline_(false),
|
||||||
handle_inline_(handle_inline) {}
|
handle_inline_(handle_inline) {}
|
||||||
|
|
||||||
|
@ -701,6 +701,8 @@ class DwarfCUToModule::FuncHandler: public GenericDIEHandler {
|
||||||
DwarfForm high_pc_form_; // DW_AT_high_pc can be length or address.
|
DwarfForm high_pc_form_; // DW_AT_high_pc can be length or address.
|
||||||
DwarfForm ranges_form_; // DW_FORM_sec_offset or DW_FORM_rnglistx
|
DwarfForm ranges_form_; // DW_FORM_sec_offset or DW_FORM_rnglistx
|
||||||
uint64_t ranges_data_; // DW_AT_ranges
|
uint64_t ranges_data_; // DW_AT_ranges
|
||||||
|
// DW_AT_decl_file, value of UINT64_MAX means undefined.
|
||||||
|
uint64_t decl_file_data_;
|
||||||
bool inline_;
|
bool inline_;
|
||||||
vector<unique_ptr<Module::Inline>> child_inlines_;
|
vector<unique_ptr<Module::Inline>> child_inlines_;
|
||||||
bool handle_inline_;
|
bool handle_inline_;
|
||||||
|
@ -725,6 +727,9 @@ void DwarfCUToModule::FuncHandler::ProcessAttributeUnsigned(
|
||||||
ranges_data_ = data;
|
ranges_data_ = data;
|
||||||
ranges_form_ = form;
|
ranges_form_ = form;
|
||||||
break;
|
break;
|
||||||
|
case DW_AT_decl_file:
|
||||||
|
decl_file_data_ = data;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
GenericDIEHandler::ProcessAttributeUnsigned(attr, form, data);
|
GenericDIEHandler::ProcessAttributeUnsigned(attr, form, data);
|
||||||
break;
|
break;
|
||||||
|
@ -852,7 +857,8 @@ void DwarfCUToModule::FuncHandler::Finish() {
|
||||||
|
|
||||||
// Only keep track of DW_TAG_subprogram which have the attributes we are
|
// Only keep track of DW_TAG_subprogram which have the attributes we are
|
||||||
// interested.
|
// interested.
|
||||||
if (handle_inline_ && (!empty_range || inline_)) {
|
if (handle_inline_ &&
|
||||||
|
(!empty_range || inline_ || decl_file_data_ != UINT64_MAX)) {
|
||||||
StringView name = name_.empty() ? name_omitted : name_;
|
StringView name = name_.empty() ? name_omitted : name_;
|
||||||
uint64_t offset =
|
uint64_t offset =
|
||||||
specification_offset_ != 0 ? specification_offset_ : offset_;
|
specification_offset_ != 0 ? specification_offset_ : offset_;
|
||||||
|
@ -860,6 +866,8 @@ void DwarfCUToModule::FuncHandler::Finish() {
|
||||||
offset);
|
offset);
|
||||||
cu_context_->file_context->module_->inline_origin_map
|
cu_context_->file_context->module_->inline_origin_map
|
||||||
.GetOrCreateInlineOrigin(offset_, name);
|
.GetOrCreateInlineOrigin(offset_, name);
|
||||||
|
if (decl_file_data_ != UINT64_MAX)
|
||||||
|
cu_context_->inline_origins[decl_file_data_].push_back(offset_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1442,12 +1450,10 @@ void DwarfCUToModule::AssignLinesToFunctions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DwarfCUToModule::AssignFilesToInlines() {
|
void DwarfCUToModule::AssignFilesToInlines() {
|
||||||
// Assign File* to Inlines inside this CU.
|
for (auto iter : files_) {
|
||||||
auto assignFile = [this](unique_ptr<Module::Inline>& in) {
|
cu_context_->file_context->module_->inline_origin_map
|
||||||
in->call_site_file = files_[in->call_site_file_id];
|
.AssignFilesToInlineOrigins(cu_context_->inline_origins[iter.first],
|
||||||
};
|
iter.second);
|
||||||
for (auto func : cu_context_->functions) {
|
|
||||||
Module::Inline::InlineDFS(func->inlines, assignFile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,17 @@ void Module::InlineOriginMap::SetReference(uint64_t offset,
|
||||||
references_[offset] = specification_offset;
|
references_[offset] = specification_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Module::InlineOriginMap::AssignFilesToInlineOrigins(
|
||||||
|
const vector<uint64_t>& inline_origin_offsets,
|
||||||
|
Module::File* file) {
|
||||||
|
for (uint64_t offset : inline_origin_offsets)
|
||||||
|
if (references_.find(offset) != references_.end()) {
|
||||||
|
auto origin = inline_origins_.find(references_[offset]);
|
||||||
|
if (origin != inline_origins_.end())
|
||||||
|
origin->second->file = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Module::Module(const string& name, const string& os,
|
Module::Module(const string& name, const string& os,
|
||||||
const string& architecture, const string& id,
|
const string& architecture, const string& id,
|
||||||
const string& code_id /* = "" */) :
|
const string& code_id /* = "" */) :
|
||||||
|
@ -265,18 +276,13 @@ void Module::AssignSourceIds(
|
||||||
line_it != func->lines.end(); ++line_it)
|
line_it != func->lines.end(); ++line_it)
|
||||||
line_it->file->source_id = 0;
|
line_it->file->source_id = 0;
|
||||||
}
|
}
|
||||||
|
// Also mark all files cited by inline functions by setting each one's source
|
||||||
// Also mark all files cited by inline callsite by setting each one's source
|
|
||||||
// id to zero.
|
// id to zero.
|
||||||
auto markInlineFiles = [](unique_ptr<Inline>& in) {
|
for (InlineOrigin* origin : inline_origins)
|
||||||
// There are some artificial inline functions which don't belong to
|
// There are some artificial inline functions which don't belong to
|
||||||
// any file. Those will have file id -1.
|
// any file. Those will have file id -1.
|
||||||
if (in->call_site_file)
|
if (origin->file)
|
||||||
in->call_site_file->source_id = 0;
|
origin->file->source_id = 0;
|
||||||
};
|
|
||||||
for (auto func : functions_) {
|
|
||||||
Inline::InlineDFS(func->inlines, markInlineFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, assign source ids to those files that have been marked.
|
// Finally, assign source ids to those files that have been marked.
|
||||||
// We could have just assigned source id numbers while traversing
|
// We could have just assigned source id numbers while traversing
|
||||||
|
@ -290,6 +296,15 @@ void Module::AssignSourceIds(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void InlineDFS(
|
||||||
|
vector<unique_ptr<Module::Inline>>& inlines,
|
||||||
|
std::function<void(unique_ptr<Module::Inline>&)> const& forEach) {
|
||||||
|
for (unique_ptr<Module::Inline>& in : inlines) {
|
||||||
|
forEach(in);
|
||||||
|
InlineDFS(in->child_inlines, forEach);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Module::CreateInlineOrigins(
|
void Module::CreateInlineOrigins(
|
||||||
set<InlineOrigin*, InlineOriginCompare>& inline_origins) {
|
set<InlineOrigin*, InlineOriginCompare>& inline_origins) {
|
||||||
// Only add origins that have file and deduplicate origins with same name and
|
// Only add origins that have file and deduplicate origins with same name and
|
||||||
|
@ -302,7 +317,7 @@ void Module::CreateInlineOrigins(
|
||||||
in->origin = *it;
|
in->origin = *it;
|
||||||
};
|
};
|
||||||
for (Function* func : functions_)
|
for (Function* func : functions_)
|
||||||
Module::Inline::InlineDFS(func->inlines, addInlineOrigins);
|
InlineDFS(func->inlines, addInlineOrigins);
|
||||||
int next_id = 0;
|
int next_id = 0;
|
||||||
for (InlineOrigin* origin : inline_origins) {
|
for (InlineOrigin* origin : inline_origins) {
|
||||||
origin->id = next_id++;
|
origin->id = next_id++;
|
||||||
|
@ -366,7 +381,8 @@ bool Module::Write(std::ostream& stream, SymbolData symbol_data) {
|
||||||
}
|
}
|
||||||
// Write out inline origins.
|
// Write out inline origins.
|
||||||
for (InlineOrigin* origin : inline_origins) {
|
for (InlineOrigin* origin : inline_origins) {
|
||||||
stream << "INLINE_ORIGIN " << origin->id << " " << origin->name << "\n";
|
stream << "INLINE_ORIGIN " << origin->id << " " << origin->getFileID()
|
||||||
|
<< " " << origin->name << "\n";
|
||||||
if (!stream.good())
|
if (!stream.good())
|
||||||
return ReportError();
|
return ReportError();
|
||||||
}
|
}
|
||||||
|
@ -391,12 +407,12 @@ bool Module::Write(std::ostream& stream, SymbolData symbol_data) {
|
||||||
auto write_inline = [&](unique_ptr<Inline>& in) {
|
auto write_inline = [&](unique_ptr<Inline>& in) {
|
||||||
stream << "INLINE ";
|
stream << "INLINE ";
|
||||||
stream << in->inline_nest_level << " " << in->call_site_line << " "
|
stream << in->inline_nest_level << " " << in->call_site_line << " "
|
||||||
<< in->getCallSiteFileID() << " " << in->origin->id << hex;
|
<< in->origin->id << hex;
|
||||||
for (const Range& r : in->ranges)
|
for (const Range& r : in->ranges)
|
||||||
stream << " " << (r.address - load_address_) << " " << r.size;
|
stream << " " << (r.address - load_address_) << " " << r.size;
|
||||||
stream << dec << "\n";
|
stream << dec << "\n";
|
||||||
};
|
};
|
||||||
Module::Inline::InlineDFS(func->inlines, write_inline);
|
InlineDFS(func->inlines, write_inline);
|
||||||
if (!stream.good())
|
if (!stream.good())
|
||||||
return ReportError();
|
return ReportError();
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,6 @@
|
||||||
#ifndef COMMON_LINUX_MODULE_H__
|
#ifndef COMMON_LINUX_MODULE_H__
|
||||||
#define COMMON_LINUX_MODULE_H__
|
#define COMMON_LINUX_MODULE_H__
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -132,7 +131,7 @@ class Module {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InlineOrigin {
|
struct InlineOrigin {
|
||||||
explicit InlineOrigin(StringView name) : id(-1), name(name) {}
|
explicit InlineOrigin(StringView name): id(-1), name(name), file(nullptr) {}
|
||||||
|
|
||||||
// A unique id for each InlineOrigin object. INLINE records use the id to
|
// A unique id for each InlineOrigin object. INLINE records use the id to
|
||||||
// refer to its INLINE_ORIGIN record.
|
// refer to its INLINE_ORIGIN record.
|
||||||
|
@ -140,6 +139,10 @@ class Module {
|
||||||
|
|
||||||
// The inlined function's name.
|
// The inlined function's name.
|
||||||
StringView name;
|
StringView name;
|
||||||
|
|
||||||
|
File* file;
|
||||||
|
|
||||||
|
int getFileID() const { return file ? file->source_id : -1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// A inlined call site.
|
// A inlined call site.
|
||||||
|
@ -147,14 +150,11 @@ class Module {
|
||||||
Inline(InlineOrigin* origin,
|
Inline(InlineOrigin* origin,
|
||||||
const vector<Range>& ranges,
|
const vector<Range>& ranges,
|
||||||
int call_site_line,
|
int call_site_line,
|
||||||
int call_site_file_id,
|
|
||||||
int inline_nest_level,
|
int inline_nest_level,
|
||||||
vector<std::unique_ptr<Inline>> child_inlines)
|
vector<std::unique_ptr<Inline>> child_inlines)
|
||||||
: origin(origin),
|
: origin(origin),
|
||||||
ranges(ranges),
|
ranges(ranges),
|
||||||
call_site_line(call_site_line),
|
call_site_line(call_site_line),
|
||||||
call_site_file_id(call_site_file_id),
|
|
||||||
call_site_file(nullptr),
|
|
||||||
inline_nest_level(inline_nest_level),
|
inline_nest_level(inline_nest_level),
|
||||||
child_inlines(std::move(child_inlines)) {}
|
child_inlines(std::move(child_inlines)) {}
|
||||||
|
|
||||||
|
@ -165,29 +165,10 @@ class Module {
|
||||||
|
|
||||||
int call_site_line;
|
int call_site_line;
|
||||||
|
|
||||||
// The id is only meanful inside a CU. It's only used for looking up real
|
|
||||||
// File* after scanning a CU.
|
|
||||||
int call_site_file_id;
|
|
||||||
|
|
||||||
File* call_site_file;
|
|
||||||
|
|
||||||
int inline_nest_level;
|
int inline_nest_level;
|
||||||
|
|
||||||
// A list of inlines which are children of this inline.
|
// A list of inlines which are children of this inline.
|
||||||
vector<std::unique_ptr<Inline>> child_inlines;
|
vector<std::unique_ptr<Inline>> child_inlines;
|
||||||
|
|
||||||
int getCallSiteFileID() const {
|
|
||||||
return call_site_file ? call_site_file->source_id : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void InlineDFS(
|
|
||||||
vector<std::unique_ptr<Module::Inline>>& inlines,
|
|
||||||
std::function<void(std::unique_ptr<Module::Inline>&)> const& forEach) {
|
|
||||||
for (std::unique_ptr<Module::Inline>& in : inlines) {
|
|
||||||
forEach(in);
|
|
||||||
InlineDFS(in->child_inlines, forEach);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef map<uint64_t, InlineOrigin*> InlineOriginByOffset;
|
typedef map<uint64_t, InlineOrigin*> InlineOriginByOffset;
|
||||||
|
@ -201,7 +182,9 @@ class Module {
|
||||||
// value of its DW_AT_specification or equals to offset if
|
// value of its DW_AT_specification or equals to offset if
|
||||||
// DW_AT_specification doesn't exist in that DIE.
|
// DW_AT_specification doesn't exist in that DIE.
|
||||||
void SetReference(uint64_t offset, uint64_t specification_offset);
|
void SetReference(uint64_t offset, uint64_t specification_offset);
|
||||||
|
void AssignFilesToInlineOrigins(
|
||||||
|
const vector<uint64_t>& inline_origin_offsets,
|
||||||
|
File* file);
|
||||||
~InlineOriginMap() {
|
~InlineOriginMap() {
|
||||||
for (const auto& iter : inline_origins_) {
|
for (const auto& iter : inline_origins_) {
|
||||||
delete iter.second;
|
delete iter.second;
|
||||||
|
@ -279,7 +262,9 @@ class Module {
|
||||||
|
|
||||||
struct InlineOriginCompare {
|
struct InlineOriginCompare {
|
||||||
bool operator() (const InlineOrigin* lhs, const InlineOrigin* rhs) const {
|
bool operator() (const InlineOrigin* lhs, const InlineOrigin* rhs) const {
|
||||||
|
if (lhs->getFileID() == rhs->getFileID())
|
||||||
return lhs->name < rhs->name;
|
return lhs->name < rhs->name;
|
||||||
|
return lhs->getFileID() < rhs->getFileID();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -98,28 +98,28 @@ class SymbolParseHelper {
|
||||||
char** filename); // out
|
char** filename); // out
|
||||||
|
|
||||||
// Parses a |inline_origin_line| declaration. Returns true on success.
|
// Parses a |inline_origin_line| declaration. Returns true on success.
|
||||||
// Format: INLINE_ORIGIN <origin_id> <name>.
|
// Format: INLINE_ORIGIN <origin_id> <file_id> <name>.
|
||||||
// Notice, that this method modifies the input |inline_origin_line| which is
|
// Notice, that this method modifies the input |inline_origin_line| which is
|
||||||
// why it can't be const. On success, <origin_id> and <name> are
|
// why it can't be const. On success, <origin_id>, <file_id> and <name> are
|
||||||
// stored in |*origin_id| and |*name|. No allocation is
|
// stored in |*origin_id|, |*file_id|, and |*name|. No allocation is
|
||||||
// done, |*name| simply points inside |inline_origin_line|.
|
// done, |*name| simply points inside |inline_origin_line|.
|
||||||
static bool ParseInlineOrigin(char* inline_origin_line, // in
|
static bool ParseInlineOrigin(char* inline_origin_line, // in
|
||||||
long* origin_id, // out
|
long* origin_id, // out
|
||||||
|
long* file_id, // out
|
||||||
char** name); // out
|
char** name); // out
|
||||||
|
|
||||||
// Parses a |inline| declaration. Returns true on success.
|
// Parses a |inline| declaration. Returns true on success.
|
||||||
// Format: INLINE <inline_nest_level> <call_site_line> <call_site_file_id>
|
// Format: INLINE <inline_nest_level> <call_site_line> <origin_id> <address>
|
||||||
// <origin_id> [<address> <size>]+
|
// <size> ....
|
||||||
// Notice, that this method modifies the input
|
// Notice, that this method modifies the input |inline|
|
||||||
// |inline| which is why it can't be const. On success, <inline_nest_level>,
|
// which is why it can't be const. On success, <inline_nest_level>,
|
||||||
// <call_site_line>, <call_site_file_id> and <origin_id> are stored in
|
// <call_site_line> and <origin_id> are stored in |*inline_nest_level|,
|
||||||
// |*inline_nest_level|, |*call_site_line|, |*call_site_file_id| and
|
// |*call_site_line|, and |*origin_id|, and all pairs of (<address>, <size>)
|
||||||
// |*origin_id|, and all pairs of (<address>, <size>) are added into ranges.
|
// are added into ranges .
|
||||||
static bool ParseInline(
|
static bool ParseInline(
|
||||||
char* inline_line, // in
|
char* inline_line, // in
|
||||||
long* inline_nest_level, // out
|
long* inline_nest_level, // out
|
||||||
long* call_site_line, // out
|
long* call_site_line, // out
|
||||||
long* call_site_file_id, // out
|
|
||||||
long* origin_id, // out
|
long* origin_id, // out
|
||||||
std::vector<std::pair<MemAddr, MemAddr>>* ranges); // out
|
std::vector<std::pair<MemAddr, MemAddr>>* ranges); // out
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
#ifndef GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_BASE_H__
|
#ifndef GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_BASE_H__
|
||||||
#define GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_BASE_H__
|
#define GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_BASE_H__
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -87,7 +86,7 @@ class SourceLineResolverBase : public SourceLineResolverInterface {
|
||||||
virtual bool IsModuleCorrupt(const CodeModule* module);
|
virtual bool IsModuleCorrupt(const CodeModule* module);
|
||||||
virtual void FillSourceLineInfo(
|
virtual void FillSourceLineInfo(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frames);
|
std::vector<std::unique_ptr<StackFrame>>* inlined_frames);
|
||||||
virtual WindowsFrameInfo* FindWindowsFrameInfo(const StackFrame* frame);
|
virtual WindowsFrameInfo* FindWindowsFrameInfo(const StackFrame* frame);
|
||||||
virtual CFIFrameInfo* FindCFIFrameInfo(const StackFrame* frame);
|
virtual CFIFrameInfo* FindCFIFrameInfo(const StackFrame* frame);
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@
|
||||||
#ifndef GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_INTERFACE_H__
|
#ifndef GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_INTERFACE_H__
|
||||||
#define GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_INTERFACE_H__
|
#define GOOGLE_BREAKPAD_PROCESSOR_SOURCE_LINE_RESOLVER_INTERFACE_H__
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -99,7 +98,7 @@ class SourceLineResolverInterface {
|
||||||
// inlined_frames in an order from outermost frame to inner most frame.
|
// inlined_frames in an order from outermost frame to inner most frame.
|
||||||
virtual void FillSourceLineInfo(
|
virtual void FillSourceLineInfo(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) = 0;
|
std::vector<std::unique_ptr<StackFrame>>* inlined_frames) = 0;
|
||||||
|
|
||||||
// If Windows stack walking information is available covering
|
// If Windows stack walking information is available covering
|
||||||
// FRAME's instruction address, return a WindowsFrameInfo structure
|
// FRAME's instruction address, return a WindowsFrameInfo structure
|
||||||
|
|
|
@ -35,7 +35,6 @@
|
||||||
#ifndef GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_SYMBOLIZER_H__
|
#ifndef GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_SYMBOLIZER_H__
|
||||||
#define GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_SYMBOLIZER_H__
|
#define GOOGLE_BREAKPAD_PROCESSOR_STACK_FRAME_SYMBOLIZER_H__
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -83,7 +82,7 @@ class StackFrameSymbolizer {
|
||||||
const CodeModules* unloaded_modules,
|
const CodeModules* unloaded_modules,
|
||||||
const SystemInfo* system_info,
|
const SystemInfo* system_info,
|
||||||
StackFrame* stack_frame,
|
StackFrame* stack_frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frames);
|
std::vector<std::unique_ptr<StackFrame>>* inlined_frames);
|
||||||
|
|
||||||
virtual WindowsFrameInfo* FindWindowsFrameInfo(const StackFrame* frame);
|
virtual WindowsFrameInfo* FindWindowsFrameInfo(const StackFrame* frame);
|
||||||
|
|
||||||
|
|
|
@ -50,11 +50,10 @@
|
||||||
|
|
||||||
#include "processor/tokenize.h"
|
#include "processor/tokenize.h"
|
||||||
|
|
||||||
using std::deque;
|
|
||||||
using std::make_pair;
|
|
||||||
using std::map;
|
using std::map;
|
||||||
using std::unique_ptr;
|
|
||||||
using std::vector;
|
using std::vector;
|
||||||
|
using std::make_pair;
|
||||||
|
using std::unique_ptr;
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
@ -238,43 +237,42 @@ bool BasicSourceLineResolver::Module::LoadMapFromMemory(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicSourceLineResolver::Module::ConstructInlineFrames(
|
int BasicSourceLineResolver::Module::ConstructInlineFrames(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
MemAddr address,
|
MemAddr address,
|
||||||
const RangeMap<uint64_t, linked_ptr<Inline>>& inlines,
|
const RangeMap<uint64_t, linked_ptr<Inline>>& inlines,
|
||||||
deque<unique_ptr<StackFrame>>* inlined_frames) const {
|
vector<unique_ptr<StackFrame>>* inlined_frames) const {
|
||||||
linked_ptr<Inline> in;
|
linked_ptr<Inline> in;
|
||||||
MemAddr inline_base;
|
MemAddr inline_base;
|
||||||
if (!inlines.RetrieveRange(address, &in, &inline_base, nullptr, nullptr))
|
if (!inlines.RetrieveRange(address, &in, &inline_base, nullptr, nullptr))
|
||||||
return;
|
return -1;
|
||||||
auto origin = inline_origins_.find(in->origin_id);
|
auto origin = inline_origins_.find(in->origin_id);
|
||||||
if (origin == inline_origins_.end())
|
if (origin == inline_origins_.end())
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
// Update parent frame's source line and source file.
|
StackFrame new_frame = StackFrame(*frame);
|
||||||
frame->source_line = in->call_site_line;
|
new_frame.function_name = origin->second->name;
|
||||||
auto file = files_.find(in->call_site_file_id);
|
|
||||||
if (file != files_.end()) {
|
|
||||||
frame->source_file_name = file->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a child frame of `frame`.
|
|
||||||
StackFrame child_frame = StackFrame(*frame);
|
|
||||||
child_frame.function_name = origin->second->name;
|
|
||||||
// Use the starting adress of the inlined range as inlined function base.
|
// Use the starting adress of the inlined range as inlined function base.
|
||||||
child_frame.function_base = child_frame.module->base_address() + inline_base;
|
new_frame.function_base = new_frame.module->base_address() + inline_base;
|
||||||
child_frame.trust = StackFrame::FRAME_TRUST_INLINE;
|
auto it = files_.find(origin->second->source_file_id);
|
||||||
ConstructInlineFrames(&child_frame, address, in->child_inlines,
|
if (it != files_.end())
|
||||||
inlined_frames);
|
new_frame.source_file_name = it->second;
|
||||||
// Add child_frame after ConstructInlineFrames so that the innermost frame is
|
|
||||||
// the first frame inside inlined_frames.
|
new_frame.trust = StackFrame::FRAME_TRUST_INLINE;
|
||||||
inlined_frames->push_back(
|
// Must add frames before calling ConstructInlineFrames to get correct order.
|
||||||
unique_ptr<StackFrame>(new StackFrame(child_frame)));
|
int current_idx = inlined_frames->size();
|
||||||
|
inlined_frames->push_back(unique_ptr<StackFrame>(new 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(
|
void BasicSourceLineResolver::Module::LookupAddress(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
deque<unique_ptr<StackFrame>>* inlined_frames) const {
|
vector<unique_ptr<StackFrame>>* inlined_frames) const {
|
||||||
MemAddr address = frame->instruction - frame->module->base_address();
|
MemAddr address = frame->instruction - frame->module->base_address();
|
||||||
|
|
||||||
// First, look for a FUNC record that covers address. Use
|
// First, look for a FUNC record that covers address. Use
|
||||||
|
@ -308,13 +306,10 @@ void BasicSourceLineResolver::Module::LookupAddress(
|
||||||
|
|
||||||
// Check if this is inlined function call.
|
// Check if this is inlined function call.
|
||||||
if (inlined_frames) {
|
if (inlined_frames) {
|
||||||
int source_line = frame->source_line;
|
int source_line =
|
||||||
string source_file_name = frame->source_file_name;
|
|
||||||
ConstructInlineFrames(frame, address, func->inlines, inlined_frames);
|
ConstructInlineFrames(frame, address, func->inlines, inlined_frames);
|
||||||
if (!inlined_frames->empty()) {
|
if (source_line != -1) {
|
||||||
// Update the inner most frame's source line and source file name.
|
frame->source_line = source_line;
|
||||||
inlined_frames->front()->source_line = source_line;
|
|
||||||
inlined_frames->front()->source_file_name = source_file_name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (public_symbols_.Retrieve(address,
|
} else if (public_symbols_.Retrieve(address,
|
||||||
|
@ -421,10 +416,12 @@ bool BasicSourceLineResolver::Module::ParseFile(char* file_line) {
|
||||||
bool BasicSourceLineResolver::Module::ParseInlineOrigin(
|
bool BasicSourceLineResolver::Module::ParseInlineOrigin(
|
||||||
char* inline_origin_line) {
|
char* inline_origin_line) {
|
||||||
long origin_id;
|
long origin_id;
|
||||||
|
long source_file_id;
|
||||||
char* origin_name;
|
char* origin_name;
|
||||||
if (SymbolParseHelper::ParseInlineOrigin(inline_origin_line, &origin_id,
|
if (SymbolParseHelper::ParseInlineOrigin(inline_origin_line, &origin_id,
|
||||||
&origin_name)) {
|
&source_file_id, &origin_name)) {
|
||||||
inline_origins_.insert(make_pair(origin_id, new InlineOrigin(origin_name)));
|
inline_origins_.insert(
|
||||||
|
make_pair(origin_id, new InlineOrigin(source_file_id, origin_name)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -434,14 +431,12 @@ linked_ptr<BasicSourceLineResolver::Inline>
|
||||||
BasicSourceLineResolver::Module::ParseInline(char* inline_line) {
|
BasicSourceLineResolver::Module::ParseInline(char* inline_line) {
|
||||||
long inline_nest_level;
|
long inline_nest_level;
|
||||||
long call_site_line;
|
long call_site_line;
|
||||||
long call_site_file_id;
|
|
||||||
long origin_id;
|
long origin_id;
|
||||||
vector<std::pair<MemAddr, MemAddr>> ranges;
|
vector<std::pair<MemAddr, MemAddr>> ranges;
|
||||||
if (SymbolParseHelper::ParseInline(inline_line, &inline_nest_level,
|
if (SymbolParseHelper::ParseInline(inline_line, &inline_nest_level,
|
||||||
&call_site_line, &call_site_file_id,
|
&call_site_line, &origin_id, &ranges)) {
|
||||||
&origin_id, &ranges)) {
|
return linked_ptr<Inline>(
|
||||||
return linked_ptr<Inline>(new Inline(inline_nest_level, call_site_line,
|
new Inline(inline_nest_level, call_site_line, origin_id, ranges));
|
||||||
call_site_file_id, origin_id, ranges));
|
|
||||||
}
|
}
|
||||||
return linked_ptr<Inline>();
|
return linked_ptr<Inline>();
|
||||||
}
|
}
|
||||||
|
@ -641,12 +636,13 @@ bool SymbolParseHelper::ParseFile(char* file_line, long* index,
|
||||||
// static
|
// static
|
||||||
bool SymbolParseHelper::ParseInlineOrigin(char* inline_origin_line,
|
bool SymbolParseHelper::ParseInlineOrigin(char* inline_origin_line,
|
||||||
long* origin_id,
|
long* origin_id,
|
||||||
|
long* file_id,
|
||||||
char** name) {
|
char** name) {
|
||||||
// INLINE_ORIGIN <origin_id> <name>
|
// INLINE_ORIGIN <origin_id> <file_id> <name>
|
||||||
assert(strncmp(inline_origin_line, "INLINE_ORIGIN ", 14) == 0);
|
assert(strncmp(inline_origin_line, "INLINE_ORIGIN ", 14) == 0);
|
||||||
inline_origin_line += 14; // skip prefix
|
inline_origin_line += 14; // skip prefix
|
||||||
vector<char*> tokens;
|
vector<char*> tokens;
|
||||||
if (!Tokenize(inline_origin_line, kWhitespace, 2, &tokens)) {
|
if (!Tokenize(inline_origin_line, kWhitespace, 3, &tokens)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,7 +653,15 @@ bool SymbolParseHelper::ParseInlineOrigin(char* inline_origin_line,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*name = tokens[1];
|
*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) {
|
if (!*name) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -670,19 +674,18 @@ bool SymbolParseHelper::ParseInline(
|
||||||
char* inline_line,
|
char* inline_line,
|
||||||
long* inline_nest_level,
|
long* inline_nest_level,
|
||||||
long* call_site_line,
|
long* call_site_line,
|
||||||
long* call_site_file_id,
|
|
||||||
long* origin_id,
|
long* origin_id,
|
||||||
vector<std::pair<MemAddr, MemAddr>>* ranges) {
|
vector<std::pair<MemAddr, MemAddr>>* ranges) {
|
||||||
// INLINE <inline_nest_level> <call_site_line> <call_site_file_id> <origin_id>
|
// INLINE <inline_nest_level> <call_site_line> <origin_id> <address> <size>
|
||||||
// [<address> <size>]+
|
// ...
|
||||||
assert(strncmp(inline_line, "INLINE ", 7) == 0);
|
assert(strncmp(inline_line, "INLINE ", 7) == 0);
|
||||||
inline_line += 7; // skip prefix
|
inline_line += 7; // skip prefix
|
||||||
|
|
||||||
vector<char*> tokens;
|
vector<char*> tokens;
|
||||||
Tokenize(inline_line, kWhitespace, std::numeric_limits<int>::max(), &tokens);
|
Tokenize(inline_line, kWhitespace, std::numeric_limits<int>::max(), &tokens);
|
||||||
|
|
||||||
// The length of the vector should be at least 6 and an even number.
|
// The length of the vector should be at least 5 and an odd number.
|
||||||
if (tokens.size() < 6 || tokens.size() % 2 != 0)
|
if (tokens.size() < 5 && tokens.size() % 2 == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
char* after_number;
|
char* after_number;
|
||||||
|
@ -698,21 +701,13 @@ bool SymbolParseHelper::ParseInline(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*call_site_file_id = strtol(tokens[2], &after_number, 10);
|
*origin_id = strtol(tokens[2], &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) || *call_site_file_id < -1 ||
|
|
||||||
*call_site_file_id == std::numeric_limits<long>::max()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*origin_id = strtol(tokens[3], &after_number, 10);
|
|
||||||
if (!IsValidAfterNumber(after_number) || *origin_id < 0 ||
|
if (!IsValidAfterNumber(after_number) || *origin_id < 0 ||
|
||||||
*origin_id == std::numeric_limits<long>::max()) {
|
*origin_id == std::numeric_limits<long>::max()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 4; i < tokens.size();) {
|
for (size_t i = 3; i < tokens.size();) {
|
||||||
MemAddr address = strtoull(tokens[i++], &after_number, 16);
|
MemAddr address = strtoull(tokens[i++], &after_number, 16);
|
||||||
if (!IsValidAfterNumber(after_number) ||
|
if (!IsValidAfterNumber(after_number) ||
|
||||||
address == std::numeric_limits<unsigned long long>::max()) {
|
address == std::numeric_limits<unsigned long long>::max()) {
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
#ifndef PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_TYPES_H__
|
#ifndef PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_TYPES_H__
|
||||||
#define PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_TYPES_H__
|
#define PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_TYPES_H__
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -109,18 +108,15 @@ class BasicSourceLineResolver::Module : public SourceLineResolverBase::Module {
|
||||||
// with the result.
|
// with the result.
|
||||||
virtual void LookupAddress(
|
virtual void LookupAddress(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frame) const;
|
std::vector<std::unique_ptr<StackFrame>>* inlined_frame) const;
|
||||||
|
|
||||||
// Construct inlined frames for frame. Return true on success.
|
// Construct inlined frame for frame and return inlined function call site
|
||||||
// If successfully construct inlined frames for `frame`, `inline_frames` will
|
// source line. If failed to construct inlined frame, return -1.
|
||||||
// be filled with lined frames and frame->source_file_name and
|
virtual int ConstructInlineFrames(
|
||||||
// frame->source_line will be update to represents the outermost frame.
|
|
||||||
// If failed, `inline_frames` will be empty and frame remains unchanged.
|
|
||||||
virtual void ConstructInlineFrames(
|
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
MemAddr address,
|
MemAddr address,
|
||||||
const RangeMap<uint64_t, linked_ptr<Inline>>& inlines,
|
const RangeMap<uint64_t, linked_ptr<Inline>>& inlines,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inline_frames) const;
|
std::vector<std::unique_ptr<StackFrame>>* inline_frames) const;
|
||||||
|
|
||||||
// If Windows stack walking information is available covering ADDRESS,
|
// If Windows stack walking information is available covering ADDRESS,
|
||||||
// return a WindowsFrameInfo structure describing it. If the information
|
// return a WindowsFrameInfo structure describing it. If the information
|
||||||
|
|
|
@ -421,40 +421,40 @@ TEST_F(TestBasicSourceLineResolver, TestLoadAndResolveInlines) {
|
||||||
"linux_inline.sym"));
|
"linux_inline.sym"));
|
||||||
ASSERT_TRUE(resolver.HasModule(&module));
|
ASSERT_TRUE(resolver.HasModule(&module));
|
||||||
StackFrame frame;
|
StackFrame frame;
|
||||||
std::deque<std::unique_ptr<StackFrame>> inlined_frames;
|
std::vector<std::unique_ptr<StackFrame>> inlined_frames;
|
||||||
frame.instruction = 0x161b6;
|
frame.instruction = 0x161b6;
|
||||||
frame.module = &module;
|
frame.module = &module;
|
||||||
// main frame.
|
// main frame.
|
||||||
resolver.FillSourceLineInfo(&frame, &inlined_frames);
|
resolver.FillSourceLineInfo(&frame, &inlined_frames);
|
||||||
ASSERT_EQ(frame.function_name, "main");
|
ASSERT_EQ(frame.function_name, "main");
|
||||||
ASSERT_EQ(frame.function_base, 0x15b30U);
|
ASSERT_EQ(frame.function_base, 0x15b30U);
|
||||||
ASSERT_EQ(frame.source_file_name, "a.cpp");
|
ASSERT_EQ(frame.source_file_name, "linux_inline.cpp");
|
||||||
ASSERT_EQ(frame.source_line, 42);
|
ASSERT_EQ(frame.source_line, 42);
|
||||||
ASSERT_EQ(frame.source_line_base, 0x161b6U);
|
ASSERT_EQ(frame.source_line_base, 0x161b6U);
|
||||||
|
|
||||||
ASSERT_EQ(inlined_frames.size(), 3UL);
|
ASSERT_EQ(inlined_frames.size(), 3UL);
|
||||||
|
|
||||||
// Inlined frames inside main frame.
|
// Inlined frames inside main frame.
|
||||||
ASSERT_EQ(inlined_frames[2]->function_name, "foo()");
|
ASSERT_EQ(inlined_frames[0]->function_name, "foo()");
|
||||||
ASSERT_EQ(inlined_frames[2]->function_base, 0x15b45U);
|
ASSERT_EQ(inlined_frames[0]->function_base, 0x15b45U);
|
||||||
ASSERT_EQ(inlined_frames[2]->source_file_name, "b.cpp");
|
ASSERT_EQ(inlined_frames[0]->source_file_name, "linux_inline.cpp");
|
||||||
ASSERT_EQ(inlined_frames[2]->source_line, 39);
|
ASSERT_EQ(inlined_frames[0]->source_line, 39);
|
||||||
ASSERT_EQ(inlined_frames[2]->source_line_base, 0x161b6U);
|
ASSERT_EQ(inlined_frames[0]->source_line_base, 0x161b6U);
|
||||||
ASSERT_EQ(inlined_frames[2]->trust, StackFrame::FRAME_TRUST_INLINE);
|
ASSERT_EQ(inlined_frames[0]->trust, StackFrame::FRAME_TRUST_INLINE);
|
||||||
|
|
||||||
ASSERT_EQ(inlined_frames[1]->function_name, "bar()");
|
ASSERT_EQ(inlined_frames[1]->function_name, "bar()");
|
||||||
ASSERT_EQ(inlined_frames[1]->function_base, 0x15b72U);
|
ASSERT_EQ(inlined_frames[1]->function_base, 0x15b72U);
|
||||||
ASSERT_EQ(inlined_frames[1]->source_file_name, "c.cpp");
|
ASSERT_EQ(inlined_frames[1]->source_file_name, "linux_inline.cpp");
|
||||||
ASSERT_EQ(inlined_frames[1]->source_line, 32);
|
ASSERT_EQ(inlined_frames[1]->source_line, 32);
|
||||||
ASSERT_EQ(inlined_frames[1]->source_line_base, 0x161b6U);
|
ASSERT_EQ(inlined_frames[1]->source_line_base, 0x161b6U);
|
||||||
ASSERT_EQ(inlined_frames[1]->trust, StackFrame::FRAME_TRUST_INLINE);
|
ASSERT_EQ(inlined_frames[1]->trust, StackFrame::FRAME_TRUST_INLINE);
|
||||||
|
|
||||||
ASSERT_EQ(inlined_frames[0]->function_name, "func()");
|
ASSERT_EQ(inlined_frames[2]->function_name, "func()");
|
||||||
ASSERT_EQ(inlined_frames[0]->function_base, 0x15b83U);
|
ASSERT_EQ(inlined_frames[2]->function_base, 0x15b83U);
|
||||||
ASSERT_EQ(inlined_frames[0]->source_file_name, "linux_inline.cpp");
|
ASSERT_EQ(inlined_frames[2]->source_file_name, "linux_inline.cpp");
|
||||||
ASSERT_EQ(inlined_frames[0]->source_line, 27);
|
ASSERT_EQ(inlined_frames[2]->source_line, 27);
|
||||||
ASSERT_EQ(inlined_frames[0]->source_line_base, 0x161b6U);
|
ASSERT_EQ(inlined_frames[2]->source_line_base, 0x161b6U);
|
||||||
ASSERT_EQ(inlined_frames[0]->trust, StackFrame::FRAME_TRUST_INLINE);
|
ASSERT_EQ(inlined_frames[2]->trust, StackFrame::FRAME_TRUST_INLINE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test parsing of valid FILE lines. The format is:
|
// Test parsing of valid FILE lines. The format is:
|
||||||
|
@ -781,15 +781,25 @@ TEST(SymbolParseHelper, ParsePublicSymbolInvalid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test parsing of valid INLINE_ORIGIN lines. The format is:
|
// Test parsing of valid INLINE_ORIGIN lines. The format is:
|
||||||
// INLINE_ORIGIN <origin_id> <name>
|
// INLINE_ORIGIN <origin_id> <file_id> <name>
|
||||||
TEST(SymbolParseHelper, ParseInlineOriginValid) {
|
TEST(SymbolParseHelper, ParseInlineOriginValid) {
|
||||||
long origin_id;
|
long origin_id;
|
||||||
|
long file_id;
|
||||||
char* name;
|
char* name;
|
||||||
|
|
||||||
char kTestLine[] = "INLINE_ORIGIN 1 function name";
|
char kTestLine[] = "INLINE_ORIGIN 1 1 function name";
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(SymbolParseHelper::ParseInlineOrigin(kTestLine, &origin_id,
|
||||||
SymbolParseHelper::ParseInlineOrigin(kTestLine, &origin_id, &name));
|
&file_id, &name));
|
||||||
EXPECT_EQ(1, origin_id);
|
EXPECT_EQ(1, origin_id);
|
||||||
|
EXPECT_EQ(1, file_id);
|
||||||
|
EXPECT_EQ("function name", string(name));
|
||||||
|
|
||||||
|
// -1 is a file id, which is used when the function is artifical.
|
||||||
|
char kTestLine1[] = "INLINE_ORIGIN 0 -1 function name";
|
||||||
|
ASSERT_TRUE(SymbolParseHelper::ParseInlineOrigin(kTestLine1, &origin_id,
|
||||||
|
&file_id, &name));
|
||||||
|
EXPECT_EQ(0, origin_id);
|
||||||
|
EXPECT_EQ(-1, file_id);
|
||||||
EXPECT_EQ("function name", string(name));
|
EXPECT_EQ("function name", string(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -797,27 +807,28 @@ TEST(SymbolParseHelper, ParseInlineOriginValid) {
|
||||||
// INLINE_ORIGIN <origin_id> <file_id> <name>
|
// INLINE_ORIGIN <origin_id> <file_id> <name>
|
||||||
TEST(SymbolParseHelper, ParseInlineOriginInvalid) {
|
TEST(SymbolParseHelper, ParseInlineOriginInvalid) {
|
||||||
long origin_id;
|
long origin_id;
|
||||||
|
long file_id;
|
||||||
char* name;
|
char* name;
|
||||||
|
|
||||||
// Test missing function name.
|
// Test missing function name.
|
||||||
char kTestLine[] = "INLINE_ORIGIN 1";
|
char kTestLine[] = "INLINE_ORIGIN 1 1";
|
||||||
ASSERT_FALSE(
|
ASSERT_FALSE(SymbolParseHelper::ParseInlineOrigin(kTestLine, &origin_id,
|
||||||
SymbolParseHelper::ParseInlineOrigin(kTestLine, &origin_id, &name));
|
&file_id, &name));
|
||||||
|
|
||||||
// Test bad origin id.
|
// Test bad origin id.
|
||||||
char kTestLine1[] = "INLINE_ORIGIN x1 function name";
|
char kTestLine1[] = "INLINE_ORIGIN x1 1 function name";
|
||||||
ASSERT_FALSE(
|
ASSERT_FALSE(SymbolParseHelper::ParseInlineOrigin(kTestLine1, &origin_id,
|
||||||
SymbolParseHelper::ParseInlineOrigin(kTestLine1, &origin_id, &name));
|
&file_id, &name));
|
||||||
|
|
||||||
// Test large origin id.
|
// Test large origin id.
|
||||||
char kTestLine2[] = "INLINE_ORIGIN 123123123123123123123123 function name";
|
char kTestLine2[] = "INLINE_ORIGIN 123123123123123123123123 1 function name";
|
||||||
ASSERT_FALSE(
|
ASSERT_FALSE(SymbolParseHelper::ParseInlineOrigin(kTestLine2, &origin_id,
|
||||||
SymbolParseHelper::ParseInlineOrigin(kTestLine2, &origin_id, &name));
|
&file_id, &name));
|
||||||
|
|
||||||
// Test negative origin id.
|
// Test negative origin id.
|
||||||
char kTestLine3[] = "INLINE_ORIGIN -1 function name";
|
char kTestLine3[] = "INLINE_ORIGIN -1 1 function name";
|
||||||
ASSERT_FALSE(
|
ASSERT_FALSE(SymbolParseHelper::ParseInlineOrigin(kTestLine3, &origin_id,
|
||||||
SymbolParseHelper::ParseInlineOrigin(kTestLine3, &origin_id, &name));
|
&file_id, &name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test parsing of valid INLINE lines. The format is:
|
// Test parsing of valid INLINE lines. The format is:
|
||||||
|
@ -825,31 +836,26 @@ TEST(SymbolParseHelper, ParseInlineOriginInvalid) {
|
||||||
TEST(SymbolParseHelper, ParseInlineValid) {
|
TEST(SymbolParseHelper, ParseInlineValid) {
|
||||||
long inline_nest_level;
|
long inline_nest_level;
|
||||||
long call_site_line;
|
long call_site_line;
|
||||||
long call_site_file_id;
|
|
||||||
long origin_id;
|
long origin_id;
|
||||||
std::vector<std::pair<uint64_t, uint64_t>> ranges;
|
std::vector<std::pair<uint64_t, uint64_t>> ranges;
|
||||||
|
|
||||||
char kTestLine[] = "INLINE 0 1 2 3 4 5";
|
char kTestLine[] = "INLINE 0 1 2 3 4";
|
||||||
ASSERT_TRUE(SymbolParseHelper::ParseInline(
|
ASSERT_TRUE(SymbolParseHelper::ParseInline(
|
||||||
kTestLine, &inline_nest_level, &call_site_line, &call_site_file_id,
|
kTestLine, &inline_nest_level, &call_site_line, &origin_id, &ranges));
|
||||||
&origin_id, &ranges));
|
|
||||||
EXPECT_EQ(0, inline_nest_level);
|
EXPECT_EQ(0, inline_nest_level);
|
||||||
EXPECT_EQ(1, call_site_line);
|
EXPECT_EQ(1, call_site_line);
|
||||||
EXPECT_EQ(2, call_site_file_id);
|
EXPECT_EQ(2, origin_id);
|
||||||
EXPECT_EQ(3, origin_id);
|
EXPECT_EQ(0x3ULL, ranges[0].first);
|
||||||
EXPECT_EQ(0x4ULL, ranges[0].first);
|
EXPECT_EQ(0x4ULL, ranges[0].second);
|
||||||
EXPECT_EQ(0x5ULL, ranges[0].second);
|
|
||||||
ranges.clear();
|
ranges.clear();
|
||||||
|
|
||||||
// Test hex and discontinuous ranges.
|
// Test hex and discontinuous ranges.
|
||||||
char kTestLine1[] = "INLINE 0 1 2 3 a b 1a 1b";
|
char kTestLine1[] = "INLINE 0 1 2 a b 1a 1b";
|
||||||
ASSERT_TRUE(SymbolParseHelper::ParseInline(
|
ASSERT_TRUE(SymbolParseHelper::ParseInline(
|
||||||
kTestLine1, &inline_nest_level, &call_site_line, &call_site_file_id,
|
kTestLine1, &inline_nest_level, &call_site_line, &origin_id, &ranges));
|
||||||
&origin_id, &ranges));
|
|
||||||
EXPECT_EQ(0, inline_nest_level);
|
EXPECT_EQ(0, inline_nest_level);
|
||||||
EXPECT_EQ(1, call_site_line);
|
EXPECT_EQ(1, call_site_line);
|
||||||
EXPECT_EQ(2, call_site_file_id);
|
EXPECT_EQ(2, origin_id);
|
||||||
EXPECT_EQ(3, origin_id);
|
|
||||||
EXPECT_EQ(0xaULL, ranges[0].first);
|
EXPECT_EQ(0xaULL, ranges[0].first);
|
||||||
EXPECT_EQ(0xbULL, ranges[0].second);
|
EXPECT_EQ(0xbULL, ranges[0].second);
|
||||||
EXPECT_EQ(0x1aULL, ranges[1].first);
|
EXPECT_EQ(0x1aULL, ranges[1].first);
|
||||||
|
@ -861,39 +867,33 @@ TEST(SymbolParseHelper, ParseInlineValid) {
|
||||||
TEST(SymbolParseHelper, ParseInlineInvalid) {
|
TEST(SymbolParseHelper, ParseInlineInvalid) {
|
||||||
long inline_nest_level;
|
long inline_nest_level;
|
||||||
long call_site_line;
|
long call_site_line;
|
||||||
long call_site_file_id;
|
|
||||||
long origin_id;
|
long origin_id;
|
||||||
std::vector<std::pair<uint64_t, uint64_t>> ranges;
|
std::vector<std::pair<uint64_t, uint64_t>> ranges;
|
||||||
|
|
||||||
// Test negative inline_nest_level.
|
// Test negative inline_nest_level.
|
||||||
char kTestLine[] = "INLINE -1 1 2 3 4 5";
|
char kTestLine[] = "INLINE -1 1 2 3 4";
|
||||||
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
||||||
kTestLine, &inline_nest_level, &call_site_line, &call_site_file_id,
|
kTestLine, &inline_nest_level, &call_site_line, &origin_id, &ranges));
|
||||||
&origin_id, &ranges));
|
|
||||||
|
|
||||||
// Test negative call_site_line.
|
// Test negative call_site_line.
|
||||||
char kTestLine1[] = "INLINE 0 -1 2 3 4 5";
|
char kTestLine1[] = "INLINE 0 -1 2 3 4";
|
||||||
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
||||||
kTestLine1, &inline_nest_level, &call_site_line, &call_site_file_id,
|
kTestLine1, &inline_nest_level, &call_site_line, &origin_id, &ranges));
|
||||||
&origin_id, &ranges));
|
|
||||||
|
|
||||||
// Test negative origin_id.
|
// Test negative origin_id.
|
||||||
char kTestLine2[] = "INLINE 0 1 2 -3 4 5";
|
char kTestLine2[] = "INLINE 0 1 -2 3 4";
|
||||||
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
||||||
kTestLine2, &inline_nest_level, &call_site_line, &call_site_file_id,
|
kTestLine2, &inline_nest_level, &call_site_line, &origin_id, &ranges));
|
||||||
&origin_id, &ranges));
|
|
||||||
|
|
||||||
// Test missing ranges.
|
// Test missing ranges.
|
||||||
char kTestLine3[] = "INLINE 0 1 2 -2";
|
char kTestLine3[] = "INLINE 0 1 -2";
|
||||||
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
||||||
kTestLine3, &inline_nest_level, &call_site_line, &call_site_file_id,
|
kTestLine3, &inline_nest_level, &call_site_line, &origin_id, &ranges));
|
||||||
&origin_id, &ranges));
|
|
||||||
|
|
||||||
// Test missing size for range.
|
// Test missing size for range.
|
||||||
char kTestLine4[] = "INLINE 0 1 -2 3 4";
|
char kTestLine4[] = "INLINE 0 1 -2 3";
|
||||||
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
ASSERT_FALSE(SymbolParseHelper::ParseInline(
|
||||||
kTestLine4, &inline_nest_level, &call_site_line, &call_site_file_id,
|
kTestLine4, &inline_nest_level, &call_site_line, &origin_id, &ranges));
|
||||||
&origin_id, &ranges));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -64,7 +64,7 @@ bool FastSourceLineResolver::ShouldDeleteMemoryBufferAfterLoadModule() {
|
||||||
|
|
||||||
void FastSourceLineResolver::Module::LookupAddress(
|
void FastSourceLineResolver::Module::LookupAddress(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) const {
|
vector<std::unique_ptr<StackFrame>>* inlined_frames) const {
|
||||||
MemAddr address = frame->instruction - frame->module->base_address();
|
MemAddr address = frame->instruction - frame->module->base_address();
|
||||||
|
|
||||||
// First, look for a FUNC record that covers address. Use
|
// First, look for a FUNC record that covers address. Use
|
||||||
|
|
|
@ -119,7 +119,7 @@ class FastSourceLineResolver::Module: public SourceLineResolverBase::Module {
|
||||||
// with the result.
|
// with the result.
|
||||||
virtual void LookupAddress(
|
virtual void LookupAddress(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) const;
|
std::vector<std::unique_ptr<StackFrame>>* inlined_frames) const;
|
||||||
|
|
||||||
// Loads a map from the given buffer in char* type.
|
// Loads a map from the given buffer in char* type.
|
||||||
virtual bool LoadMapFromMemory(char* memory_buffer,
|
virtual bool LoadMapFromMemory(char* memory_buffer,
|
||||||
|
|
|
@ -297,7 +297,7 @@ bool SourceLineResolverBase::IsModuleCorrupt(const CodeModule* module) {
|
||||||
|
|
||||||
void SourceLineResolverBase::FillSourceLineInfo(
|
void SourceLineResolverBase::FillSourceLineInfo(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) {
|
std::vector<std::unique_ptr<StackFrame>>* inlined_frames) {
|
||||||
if (frame->module) {
|
if (frame->module) {
|
||||||
ModuleMap::const_iterator it = modules_->find(frame->module->code_file());
|
ModuleMap::const_iterator it = modules_->find(frame->module->code_file());
|
||||||
if (it != modules_->end()) {
|
if (it != modules_->end()) {
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -71,7 +70,10 @@ class SourceLineResolverBase::AutoFileCloser {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SourceLineResolverBase::InlineOrigin {
|
struct SourceLineResolverBase::InlineOrigin {
|
||||||
explicit InlineOrigin(const string& name) : name(name) {}
|
InlineOrigin(int32_t source_file_id, const string& name)
|
||||||
|
: source_file_id(source_file_id), name(name) {}
|
||||||
|
|
||||||
|
int32_t source_file_id;
|
||||||
string name;
|
string name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,18 +82,15 @@ struct SourceLineResolverBase::Inline {
|
||||||
using InlineRanges = std::vector<std::pair<MemAddr, MemAddr>>;
|
using InlineRanges = std::vector<std::pair<MemAddr, MemAddr>>;
|
||||||
Inline(int32_t inline_nest_level,
|
Inline(int32_t inline_nest_level,
|
||||||
int32_t call_site_line,
|
int32_t call_site_line,
|
||||||
int32_t call_site_file_id,
|
|
||||||
int32_t origin_id,
|
int32_t origin_id,
|
||||||
InlineRanges inline_ranges)
|
InlineRanges inline_ranges)
|
||||||
: inline_nest_level(inline_nest_level),
|
: inline_nest_level(inline_nest_level),
|
||||||
call_site_line(call_site_line),
|
call_site_line(call_site_line),
|
||||||
call_site_file_id(call_site_file_id),
|
|
||||||
origin_id(origin_id),
|
origin_id(origin_id),
|
||||||
inline_ranges(inline_ranges) {}
|
inline_ranges(inline_ranges) {}
|
||||||
|
|
||||||
int32_t inline_nest_level;
|
int32_t inline_nest_level;
|
||||||
int32_t call_site_line;
|
int32_t call_site_line;
|
||||||
int32_t call_site_file_id;
|
|
||||||
int32_t origin_id;
|
int32_t origin_id;
|
||||||
InlineRanges inline_ranges;
|
InlineRanges inline_ranges;
|
||||||
RangeMap<MemAddr, linked_ptr<Inline>> child_inlines;
|
RangeMap<MemAddr, linked_ptr<Inline>> child_inlines;
|
||||||
|
@ -175,7 +174,7 @@ class SourceLineResolverBase::Module {
|
||||||
// with the result.
|
// with the result.
|
||||||
virtual void LookupAddress(
|
virtual void LookupAddress(
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) const = 0;
|
std::vector<std::unique_ptr<StackFrame>>* inlined_frames) const = 0;
|
||||||
|
|
||||||
// If Windows stack walking information is available covering ADDRESS,
|
// If Windows stack walking information is available covering ADDRESS,
|
||||||
// return a WindowsFrameInfo structure describing it. If the information
|
// return a WindowsFrameInfo structure describing it. If the information
|
||||||
|
|
|
@ -58,7 +58,7 @@ StackFrameSymbolizer::SymbolizerResult StackFrameSymbolizer::FillSourceLineInfo(
|
||||||
const CodeModules* unloaded_modules,
|
const CodeModules* unloaded_modules,
|
||||||
const SystemInfo* system_info,
|
const SystemInfo* system_info,
|
||||||
StackFrame* frame,
|
StackFrame* frame,
|
||||||
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) {
|
std::vector<std::unique_ptr<StackFrame>>* inlined_frames) {
|
||||||
assert(frame);
|
assert(frame);
|
||||||
|
|
||||||
const CodeModule* module = NULL;
|
const CodeModule* module = NULL;
|
||||||
|
|
|
@ -218,7 +218,7 @@ static void PrintStackContents(const string& indent,
|
||||||
modules->GetModuleForAddress(pointee_frame.instruction);
|
modules->GetModuleForAddress(pointee_frame.instruction);
|
||||||
|
|
||||||
// Try to look up the function name.
|
// Try to look up the function name.
|
||||||
std::deque<unique_ptr<StackFrame>> inlined_frames;
|
vector<unique_ptr<StackFrame>> inlined_frames;
|
||||||
if (pointee_frame.module)
|
if (pointee_frame.module)
|
||||||
resolver->FillSourceLineInfo(&pointee_frame, &inlined_frames);
|
resolver->FillSourceLineInfo(&pointee_frame, &inlined_frames);
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ bool Stackwalker::Walk(
|
||||||
// frame_pointer fields. The frame structure comes from either the
|
// frame_pointer fields. The frame structure comes from either the
|
||||||
// context frame (above) or a caller frame (below).
|
// context frame (above) or a caller frame (below).
|
||||||
|
|
||||||
std::deque<std::unique_ptr<StackFrame>> inlined_frames;
|
vector<std::unique_ptr<StackFrame>> inlined_frames;
|
||||||
// Resolve the module information, if a module map was provided.
|
// Resolve the module information, if a module map was provided.
|
||||||
StackFrameSymbolizer::SymbolizerResult symbolizer_result =
|
StackFrameSymbolizer::SymbolizerResult symbolizer_result =
|
||||||
frame_symbolizer_->FillSourceLineInfo(modules_, unloaded_modules_,
|
frame_symbolizer_->FillSourceLineInfo(modules_, unloaded_modules_,
|
||||||
|
@ -174,10 +174,10 @@ bool Stackwalker::Walk(
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Add all nested inlined frames belonging to this frame from left to right.
|
// Add all nested inlined frames belonging to this frame in reverse order.
|
||||||
while (!inlined_frames.empty()) {
|
while (!inlined_frames.empty()) {
|
||||||
stack->frames_.push_back(inlined_frames.front().release());
|
stack->frames_.push_back(inlined_frames.back().release());
|
||||||
inlined_frames.pop_front();
|
inlined_frames.pop_back();
|
||||||
}
|
}
|
||||||
// Add the frame to the call stack. Relinquish the ownership claim
|
// Add the frame to the call stack. Relinquish the ownership claim
|
||||||
// over the frame, because the stack now owns it.
|
// over the frame, because the stack now owns it.
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
MODULE Linux x86_64 BBA6FA10B8AAB33D00000000000000000 linux_inline
|
MODULE Linux x86_64 BBA6FA10B8AAB33D00000000000000000 linux_inline
|
||||||
INFO CODE_ID 10FAA6BBAAB83DB3
|
INFO CODE_ID 10FAA6BBAAB83DB3
|
||||||
FILE 0 linux_inline.cpp
|
FILE 0 linux_inline.cpp
|
||||||
FILE 1 a.cpp
|
INLINE_ORIGIN 0 0 bar()
|
||||||
FILE 2 b.cpp
|
INLINE_ORIGIN 1 0 foo()
|
||||||
FILE 3 c.cpp
|
INLINE_ORIGIN 2 0 func()
|
||||||
INLINE_ORIGIN 0 bar()
|
|
||||||
INLINE_ORIGIN 1 foo()
|
|
||||||
INLINE_ORIGIN 2 func()
|
|
||||||
FUNC 15b30 6cf 0 main
|
FUNC 15b30 6cf 0 main
|
||||||
INLINE 0 42 1 1 15b45 6b1
|
INLINE 0 42 1 15b45 6b1
|
||||||
INLINE 1 39 2 0 15b72 684
|
INLINE 1 39 0 15b72 684
|
||||||
INLINE 2 32 3 2 15b83 673
|
INLINE 2 32 2 15b83 673
|
||||||
15b30 15 41 0
|
15b30 15 41 0
|
||||||
15b45 11 36 0
|
15b45 11 36 0
|
||||||
15b56 a 37 0
|
15b56 a 37 0
|
||||||
|
|
Loading…
Reference in a new issue