gctf2023/pwn/flipper/dist/utils/add-debug/dwarf/line.cc
2023-11-24 13:11:34 -05:00

435 lines
16 KiB
C++

#include "internal.hh"
#include <cassert>
using namespace std;
DWARFPP_BEGIN_NAMESPACE
// The expected number of arguments for standard opcodes. This is
// used to check the opcode_lengths header field for compatibility.
static const int opcode_lengths[] = {
0,
// DW_LNS::copy
0, 1, 1, 1, 1,
// DW_LNS::negate_stmt
0, 0, 0, 1, 0,
// DW_LNS::set_epilogue_begin
0, 1
};
struct line_table::impl
{
shared_ptr<section> sec;
// Header information
section_offset program_offset;
ubyte minimum_instruction_length;
ubyte maximum_operations_per_instruction;
bool default_is_stmt;
sbyte line_base;
ubyte line_range;
ubyte opcode_base;
vector<ubyte> standard_opcode_lengths;
vector<string> include_directories;
vector<file> file_names;
// The offset in sec following the last read file name entry.
// File name entries can appear both in the line table header
// and in the line number program itself. Since we can
// iterate over the line number program repeatedly, this keeps
// track of how far we've gotten so we don't add the same
// entry twice.
section_offset last_file_name_end;
// If an iterator has traversed the entire program, then we
// know we've gathered all file names.
bool file_names_complete;
impl() : last_file_name_end(0), file_names_complete(false) {};
bool read_file_entry(cursor *cur, bool in_header);
};
line_table::line_table(const shared_ptr<section> &sec, section_offset offset,
unsigned cu_addr_size, const string &cu_comp_dir,
const string &cu_name)
: m(make_shared<impl>())
{
// XXX DWARF2 and 3 give a weird specification for DW_AT_comp_dir
string comp_dir, abs_path;
if (cu_comp_dir.empty() || cu_comp_dir.back() == '/')
comp_dir = cu_comp_dir;
else
comp_dir = cu_comp_dir + '/';
// Read the line table header (DWARF2 section 6.2.4, DWARF3
// section 6.2.4, DWARF4 section 6.2.3)
cursor cur(sec, offset);
m->sec = cur.subsection();
cur = cursor(m->sec);
cur.skip_initial_length();
m->sec->addr_size = cu_addr_size;
// Basic header information
uhalf version = cur.fixed<uhalf>();
if (version < 2 || version > 4)
throw format_error("unknown line number table version " +
std::to_string(version));
section_length header_length = cur.offset();
m->program_offset = cur.get_section_offset() + header_length;
m->minimum_instruction_length = cur.fixed<ubyte>();
m->maximum_operations_per_instruction = 1;
if (version == 4)
m->maximum_operations_per_instruction = cur.fixed<ubyte>();
if (m->maximum_operations_per_instruction == 0)
throw format_error("maximum_operations_per_instruction cannot"
" be 0 in line number table");
m->default_is_stmt = cur.fixed<ubyte>();
m->line_base = cur.fixed<sbyte>();
m->line_range = cur.fixed<ubyte>();
if (m->line_range == 0)
throw format_error("line_range cannot be 0 in line number table");
m->opcode_base = cur.fixed<ubyte>();
static_assert(sizeof(opcode_lengths) / sizeof(opcode_lengths[0]) == 13,
"opcode_lengths table has wrong length");
// Opcode length table
m->standard_opcode_lengths.resize(m->opcode_base);
m->standard_opcode_lengths[0] = 0;
for (unsigned i = 1; i < m->opcode_base; i++) {
ubyte length = cur.fixed<ubyte>();
if (length != opcode_lengths[i])
// The spec never says what to do if the
// opcode length of a standard opcode doesn't
// match the header. Do the safe thing.
throw format_error(
"expected " +
std::to_string(opcode_lengths[i]) +
" arguments for line number opcode " +
std::to_string(i) + ", got " +
std::to_string(length));
m->standard_opcode_lengths[i] = length;
}
// Include directories list
string incdir;
// Include directory 0 is implicitly the compilation unit
// current directory
m->include_directories.push_back(comp_dir);
while (true) {
cur.string(incdir);
if (incdir.empty())
break;
if (incdir.back() != '/')
incdir += '/';
if (incdir[0] == '/')
m->include_directories.push_back(move(incdir));
else
m->include_directories.push_back(comp_dir + incdir);
}
// File name list
string file_name;
// File name 0 is implicitly the compilation unit file name.
// cu_name can be relative to comp_dir or absolute.
if (!cu_name.empty() && cu_name[0] == '/')
m->file_names.emplace_back(cu_name);
else
m->file_names.emplace_back(comp_dir + cu_name);
while (m->read_file_entry(&cur, true));
}
line_table::iterator
line_table::begin() const
{
if (!valid())
return iterator(nullptr, 0);
return iterator(this, m->program_offset);
}
line_table::iterator
line_table::end() const
{
if (!valid())
return iterator(nullptr, 0);
return iterator(this, m->sec->size());
}
line_table::iterator
line_table::find_address(taddr addr) const
{
iterator prev = begin(), e = end();
if (prev == e)
return prev;
iterator it = prev;
for (++it; it != e; prev = it++) {
if (prev->address <= addr && it->address > addr &&
!prev->end_sequence)
return prev;
}
prev = e;
return prev;
}
const line_table::file *
line_table::get_file(unsigned index) const
{
if (index >= m->file_names.size()) {
// It could be declared in the line table program.
// This is unlikely, so we don't have to be
// super-efficient about this. Just force our way
// through the whole line table program.
if (!m->file_names_complete) {
for (auto &ent : *this)
(void)ent;
}
if (index >= m->file_names.size())
throw out_of_range
("file name index " + std::to_string(index) +
" exceeds file table size of " +
std::to_string(m->file_names.size()));
}
return &m->file_names[index];
}
bool
line_table::impl::read_file_entry(cursor *cur, bool in_header)
{
assert(cur->sec == sec);
string file_name;
cur->string(file_name);
if (in_header && file_name.empty())
return false;
uint64_t dir_index = cur->uleb128();
uint64_t mtime = cur->uleb128();
uint64_t length = cur->uleb128();
// Have we already processed this file entry?
if (cur->get_section_offset() <= last_file_name_end)
return true;
last_file_name_end = cur->get_section_offset();
if (file_name[0] == '/')
file_names.emplace_back(move(file_name), mtime, length);
else if (dir_index < include_directories.size())
file_names.emplace_back(
include_directories[dir_index] + file_name,
mtime, length);
else
throw format_error("file name directory index out of range: " +
std::to_string(dir_index));
return true;
}
line_table::file::file(string path, uint64_t mtime, uint64_t length)
: path(path), mtime(mtime), length(length)
{
}
void
line_table::entry::reset(bool is_stmt)
{
address = op_index = 0;
file = nullptr;
file_index = line = 1;
column = 0;
this->is_stmt = is_stmt;
basic_block = end_sequence = prologue_end = epilogue_begin = false;
isa = discriminator = 0;
}
string
line_table::entry::get_description() const
{
string res = file->path;
if (line) {
res.append(":").append(std::to_string(line));
if (column)
res.append(":").append(std::to_string(column));
}
return res;
}
line_table::iterator::iterator(const line_table *table, section_offset pos)
: table(table), pos(pos)
{
if (table) {
regs.reset(table->m->default_is_stmt);
++(*this);
}
}
line_table::iterator &
line_table::iterator::operator++()
{
cursor cur(table->m->sec, pos);
// Execute opcodes until we reach the end of the stream or an
// opcode emits a line table row
bool stepped = false, output = false;
while (!cur.end() && !output) {
output = step(&cur);
stepped = true;
}
if (stepped && !output)
throw format_error("unexpected end of line table");
if (stepped && cur.end()) {
// Record that all file names must be known now
table->m->file_names_complete = true;
}
if (output) {
// Resolve file name of entry
if (entry.file_index < table->m->file_names.size())
entry.file = &table->m->file_names[entry.file_index];
else
throw format_error("bad file index " +
std::to_string(entry.file_index) +
" in line table");
}
pos = cur.get_section_offset();
return *this;
}
bool
line_table::iterator::step(cursor *cur)
{
struct line_table::impl *m = table->m.get();
// Read the opcode (DWARF4 section 6.2.3)
ubyte opcode = cur->fixed<ubyte>();
if (opcode >= m->opcode_base) {
// Special opcode (DWARF4 section 6.2.5.1)
ubyte adjusted_opcode = opcode - m->opcode_base;
unsigned op_advance = adjusted_opcode / m->line_range;
signed line_inc = m->line_base + (signed)adjusted_opcode % m->line_range;
regs.line += line_inc;
regs.address += m->minimum_instruction_length *
((regs.op_index + op_advance)
/ m->maximum_operations_per_instruction);
regs.op_index = (regs.op_index + op_advance)
% m->maximum_operations_per_instruction;
entry = regs;
regs.basic_block = regs.prologue_end =
regs.epilogue_begin = false;
regs.discriminator = 0;
return true;
} else if (opcode != 0) {
// Standard opcode (DWARF4 sections 6.2.3 and 6.2.5.2)
//
// According to the standard, any opcode between the
// highest defined opcode for a given DWARF version
// and opcode_base should be treated as a
// vendor-specific opcode. However, the de facto
// standard seems to be to process these as standard
// opcodes even if they're from a later version of the
// standard than the line table header claims.
uint64_t uarg;
#pragma GCC diagnostic push
#pragma GCC diagnostic warning "-Wswitch-enum"
switch ((DW_LNS)opcode) {
case DW_LNS::copy:
entry = regs;
regs.basic_block = regs.prologue_end =
regs.epilogue_begin = false;
regs.discriminator = 0;
break;
case DW_LNS::advance_pc:
// Opcode advance (as for special opcodes)
uarg = cur->uleb128();
advance_pc:
regs.address += m->minimum_instruction_length *
((regs.op_index + uarg)
/ m->maximum_operations_per_instruction);
regs.op_index = (regs.op_index + uarg)
% m->maximum_operations_per_instruction;
break;
case DW_LNS::advance_line:
regs.line = (signed)regs.line + cur->sleb128();
break;
case DW_LNS::set_file:
regs.file_index = cur->uleb128();
break;
case DW_LNS::set_column:
regs.column = cur->uleb128();
break;
case DW_LNS::negate_stmt:
regs.is_stmt = !regs.is_stmt;
break;
case DW_LNS::set_basic_block:
regs.basic_block = true;
break;
case DW_LNS::const_add_pc:
uarg = (255 - m->opcode_base) / m->line_range;
goto advance_pc;
case DW_LNS::fixed_advance_pc:
regs.address += cur->fixed<uhalf>();
regs.op_index = 0;
break;
case DW_LNS::set_prologue_end:
regs.prologue_end = true;
break;
case DW_LNS::set_epilogue_begin:
regs.epilogue_begin = true;
break;
case DW_LNS::set_isa:
regs.isa = cur->uleb128();
break;
default:
// XXX Vendor extensions
throw format_error("unknown line number opcode " +
to_string((DW_LNS)opcode));
}
return ((DW_LNS)opcode == DW_LNS::copy);
} else { // opcode == 0
// Extended opcode (DWARF4 sections 6.2.3 and 6.2.5.3)
assert(opcode == 0);
uint64_t length = cur->uleb128();
section_offset end = cur->get_section_offset() + length;
opcode = cur->fixed<ubyte>();
switch ((DW_LNE)opcode) {
case DW_LNE::end_sequence:
regs.end_sequence = true;
entry = regs;
regs.reset(m->default_is_stmt);
break;
case DW_LNE::set_address:
regs.address = cur->address();
regs.op_index = 0;
break;
case DW_LNE::define_file:
m->read_file_entry(cur, false);
break;
case DW_LNE::set_discriminator:
// XXX Only DWARF4
regs.discriminator = cur->uleb128();
break;
case DW_LNE::lo_user...DW_LNE::hi_user:
// XXX Vendor extensions
throw runtime_error("vendor line number opcode " +
to_string((DW_LNE)opcode) +
" not implemented");
default:
// XXX Prior to DWARF4, any opcode number
// could be a vendor extension
throw format_error("unknown line number opcode " +
to_string((DW_LNE)opcode));
}
#pragma GCC diagnostic pop
if (cur->get_section_offset() > end)
throw format_error("extended line number opcode exceeded its size");
cur += end - cur->get_section_offset();
return ((DW_LNE)opcode == DW_LNE::end_sequence);
}
}
DWARFPP_END_NAMESPACE