blob: a3630dbc6048b19c5918500e77ec3e8e313776ee [file] [edit]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dwarf/line_info.h"
#include "dwarf/dwarf_util.h"
#include "dwarf_constants.h"
using namespace dwarf2reader;
using std::string_view;
namespace bloaty {
extern int verbose_level;
namespace dwarf {
const std::string& LineInfoReader::GetExpandedFilename(size_t index) {
if (index >= filenames_.size()) {
THROW("filename index out of range");
}
// Generate these lazily.
if (expanded_filenames_.size() <= index) {
expanded_filenames_.resize(filenames_.size());
}
std::string& ret = expanded_filenames_[index];
if (ret.empty()) {
const FileName& filename = filenames_[index];
std::string_view directory = include_directories_[filename.directory_index];
ret = std::string(directory);
if (!ret.empty()) {
ret += "/";
}
ret += std::string(filename.name);
}
return ret;
}
void LineInfoReader::Advance(uint64_t amount) {
if (params_.maximum_operations_per_instruction == 1) {
// This is by far the common case (only false on VLIW architectuers),
// and this inlining/specialization avoids a costly division.
DoAdvance(amount, 1);
} else {
DoAdvance(amount, params_.maximum_operations_per_instruction);
}
}
void LineInfoReader::DoAdvance(uint64_t advance, uint8_t max_per_instr) {
info_.address += params_.minimum_instruction_length *
((info_.op_index + advance) / max_per_instr);
info_.op_index = (info_.op_index + advance) % max_per_instr;
}
void LineInfoReader::SpecialOpcodeAdvance(uint8_t op) {
Advance(AdjustedOpcode(op) / params_.line_range);
}
uint8_t LineInfoReader::AdjustedOpcode(uint8_t op) {
return op - params_.opcode_base;
}
void LineInfoReader::SeekToOffset(uint64_t offset, uint8_t address_size) {
string_view data = file_.debug_line;
SkipBytes(offset, &data);
sizes_.SetAddressSize(address_size);
data = sizes_.ReadInitialLength(&data);
sizes_.ReadDWARFVersion(&data);
if (sizes_.dwarf_version() >= 5) {
auto encoded_addr_size = ReadFixed<uint8_t>(&data);
auto encoded_selector_size = ReadFixed<uint8_t>(&data);
if (encoded_addr_size != address_size) {
THROW("DWARF line info address size mismatch");
}
(void)encoded_selector_size;
}
uint64_t header_length = sizes_.ReadDWARFOffset(&data);
string_view program = data;
SkipBytes(header_length, &program);
params_.minimum_instruction_length = ReadFixed<uint8_t>(&data);
if (sizes_.dwarf_version() >= 4) {
params_.maximum_operations_per_instruction = ReadFixed<uint8_t>(&data);
if (params_.maximum_operations_per_instruction == 0) {
THROW("DWARF line info had maximum_operations_per_instruction=0");
}
} else {
params_.maximum_operations_per_instruction = 1;
}
params_.default_is_stmt = ReadFixed<uint8_t>(&data);
params_.line_base = ReadFixed<int8_t>(&data);
params_.line_range = ReadFixed<uint8_t>(&data);
params_.opcode_base = ReadFixed<uint8_t>(&data);
if (params_.line_range == 0) {
THROW("line_range of zero will cause divide by zero");
}
standard_opcode_lengths_.resize(params_.opcode_base);
for (size_t i = 1; i < params_.opcode_base; i++) {
standard_opcode_lengths_[i] = ReadFixed<uint8_t>(&data);
}
// Read include_directories.
include_directories_.clear();
filenames_.clear();
expanded_filenames_.clear();
if (sizes_.dwarf_version() <= 4) {
// Implicit current directory entry.
include_directories_.push_back(string_view());
while (true) {
string_view dir = ReadNullTerminated(&data);
if (dir.empty()) {
break;
}
include_directories_.push_back(dir);
}
// Read file_names.
// Filename 0 is unused.
filenames_.push_back(FileName());
while (true) {
FileName file_name;
file_name.name = ReadNullTerminated(&data);
if (file_name.name.empty()) {
break;
}
file_name.directory_index = ReadLEB128<uint32_t>(&data);
file_name.modified_time = ReadLEB128<uint64_t>(&data);
file_name.file_size = ReadLEB128<uint64_t>(&data);
if (file_name.directory_index >= include_directories_.size()) {
THROW("directory index out of range");
}
filenames_.push_back(file_name);
}
} else {
// Dwarf V5 and beyond.
//
auto readPath = [&] (DwarfForm form) {
switch (form) {
case DW_FORM_string:
return ReadNullTerminated(&data);
case DW_FORM_line_strp: {
auto offset = sizes_.ReadDWARFOffset(&data);
return ReadDebugStrEntry(file_.debug_line_str, offset);
}
default:
THROW("directory index out of range");
}
};
auto readEntryFormats = [&]() {
std::vector<std::pair<DwarfLineNumberContentType, DwarfForm>> entryFormats;
auto formatCount = ReadFixed<uint8_t>(&data);
for (uint8_t i = 0; i < formatCount; ++i) {
auto type = static_cast<DwarfLineNumberContentType>(
ReadLEB128<uint32_t>(&data));
auto form = static_cast<DwarfForm>(ReadLEB128<uint32_t>(&data));
entryFormats.emplace_back(type, form);
}
return entryFormats;
};
auto entryFormats = readEntryFormats();
auto directoryCount = ReadLEB128<uint32_t>(&data);
while (directoryCount--) {
std::string_view path = "";
for (auto [ type, form ] : entryFormats) {
switch (type) {
case DW_LNCT_path:
path = readPath(form);
break;
default:
THROW("unhandled directory entry format");
}
}
include_directories_.push_back(path);
}
auto fileFormats = readEntryFormats();
auto fileCount = ReadLEB128<uint32_t>(&data);
while (fileCount--) {
FileName file_name;
auto &idx = file_name.directory_index;
idx = 0;
for (auto &[ type, form ] : fileFormats) {
switch (type) {
case DW_LNCT_path:
file_name.name = readPath(form);
break;
case DW_LNCT_directory_index: {
switch (form) {
case DW_FORM_udata:
idx = ReadLEB128<uint32_t>(&data);
break;
case DW_FORM_data1:
idx = ReadFixed<uint8_t>(&data);
break;
case DW_FORM_data2:
idx = ReadFixed<uint16_t>(&data);
break;
case DW_FORM_data4:
idx = ReadFixed<uint32_t>(&data);
break;
default:
THROW("unhandled form for directory index");
}
break;
}
case DW_LNCT_MD5: {
switch (form) {
case DW_FORM_data16:
SkipBytes(16, &data); // MD5 is 16 bytes
break;
default:
THROW("unhandled form for MD5");
}
break;
}
case DW_LNCT_timestamp:
case DW_LNCT_size: {
// Skip optional timestamp and size fields - bloaty doesn't need them
switch (form) {
case DW_FORM_udata:
ReadLEB128<uint64_t>(&data);
break;
case DW_FORM_data1:
ReadFixed<uint8_t>(&data);
break;
case DW_FORM_data2:
ReadFixed<uint16_t>(&data);
break;
case DW_FORM_data4:
ReadFixed<uint32_t>(&data);
break;
case DW_FORM_data8:
ReadFixed<uint64_t>(&data);
break;
default:
THROW("unhandled form for timestamp/size");
}
break;
}
default: {
THROW("unhandled type for file format");
}
}
}
if (file_name.directory_index >= include_directories_.size()) {
THROW("directory index out of range");
}
filenames_.push_back(file_name);
}
}
info_ = LineInfo(params_.default_is_stmt);
remaining_ = program;
shadow_ = false;
}
bool LineInfoReader::ReadLineInfo() {
// Final step of last DW_LNS_copy / special opcode.
info_.discriminator = 0;
info_.basic_block = false;
info_.prologue_end = false;
info_.epilogue_begin = false;
// Final step of DW_LNE_end_sequence.
info_.end_sequence = false;
string_view data = remaining_;
while (true) {
if (data.empty()) {
remaining_ = data;
return false;
}
uint8_t op = ReadFixed<uint8_t>(&data);
if (op >= params_.opcode_base) {
SpecialOpcodeAdvance(op);
info_.line +=
params_.line_base + (AdjustedOpcode(op) % params_.line_range);
if (!shadow_) {
remaining_ = data;
return true;
}
} else {
switch (op) {
case DW_LNS_extended_op: {
uint16_t len = ReadLEB128<uint16_t>(&data);
uint8_t extended_op = ReadFixed<uint8_t>(&data);
switch (extended_op) {
case DW_LNE_end_sequence: {
// Preserve address and set end_sequence, but reset everything
// else.
uint64_t addr = info_.address;
info_ = LineInfo(params_.default_is_stmt);
info_.address = addr;
info_.end_sequence = true;
if (!shadow_) {
remaining_ = data;
return true;
}
break;
}
case DW_LNE_set_address:
info_.address = sizes_.ReadAddress(&data);
info_.op_index = 0;
shadow_ = (info_.address == 0);
break;
case DW_LNE_define_file: {
FileName file_name;
file_name.name = ReadNullTerminated(&data);
file_name.directory_index = ReadLEB128<uint32_t>(&data);
file_name.modified_time = ReadLEB128<uint64_t>(&data);
file_name.file_size = ReadLEB128<uint64_t>(&data);
if (file_name.directory_index >= include_directories_.size()) {
THROW("directory index out of range");
}
filenames_.push_back(file_name);
break;
}
case DW_LNE_set_discriminator:
info_.discriminator = ReadLEB128<uint32_t>(&data);
break;
default:
// We don't understand this opcode, skip it.
SkipBytes(len, &data);
if (verbose_level > 0) {
fprintf(stderr,
"bloaty: warning: unknown DWARF line table extended "
"opcode: %d\n",
extended_op);
}
break;
}
break;
}
case DW_LNS_copy:
if (!shadow_) {
remaining_ = data;
return true;
}
break;
case DW_LNS_advance_pc:
Advance(ReadLEB128<uint64_t>(&data));
break;
case DW_LNS_advance_line:
info_.line += ReadLEB128<int32_t>(&data);
break;
case DW_LNS_set_file:
info_.file = ReadLEB128<uint32_t>(&data);
if (info_.file >= filenames_.size()) {
THROW("filename index too big");
}
break;
case DW_LNS_set_column:
info_.column = ReadLEB128<uint32_t>(&data);
break;
case DW_LNS_negate_stmt:
info_.is_stmt = !info_.is_stmt;
break;
case DW_LNS_set_basic_block:
info_.basic_block = true;
break;
case DW_LNS_const_add_pc:
SpecialOpcodeAdvance(255);
break;
case DW_LNS_fixed_advance_pc:
info_.address += ReadFixed<uint16_t>(&data);
info_.op_index = 0;
break;
case DW_LNS_set_prologue_end:
info_.prologue_end = true;
break;
case DW_LNS_set_epilogue_begin:
info_.epilogue_begin = true;
break;
case DW_LNS_set_isa:
info_.isa = ReadLEB128<uint8_t>(&data);
break;
default:
// Unknown opcode, but we know its length so can skip it.
SkipBytes(standard_opcode_lengths_[op], &data);
if (verbose_level > 0) {
fprintf(stderr,
"bloaty: warning: unknown DWARF line table opcode: %d\n",
op);
}
break;
}
}
}
}
} // namespace dwarf
} // namespace bloaty