File: line.cc

package info (click to toggle)
libelfin 0.3-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 668 kB
  • sloc: cpp: 4,883; makefile: 189; python: 139; sh: 129; ansic: 10
file content (438 lines) | stat: -rw-r--r-- 16,857 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
// Copyright (c) 2013 Austin T. Clements. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

#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