Ensures that addresses in the returned LineTable are monotonically increasing.
fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit: *const CompileUnit) !CompileUnit.SrcLocCache
fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit: *const CompileUnit) !CompileUnit.SrcLocCache {
const compile_unit_cwd = try compile_unit.die.getAttrString(d, endian, AT.comp_dir, d.section(.debug_line_str), compile_unit);
const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
var fr: Reader = .fixed(d.section(.debug_line).?);
fr.seek = @intCast(line_info_offset);
const unit_header = try readUnitHeader(&fr, endian);
if (unit_header.unit_length == 0) return missing();
const next_offset = unit_header.header_length + unit_header.unit_length;
const version = try fr.takeInt(u16, endian);
if (version < 2) return bad();
const addr_size_bytes: u8, const seg_size: u8 = if (version >= 5) .{
try fr.takeByte(),
try fr.takeByte(),
} else .{
compile_unit.addr_size_bytes,
0,
};
if (seg_size != 0) return bad(); // unsupported
const prologue_length = try readFormatSizedInt(&fr, unit_header.format, endian);
const prog_start_offset = fr.seek + prologue_length;
const minimum_instruction_length = try fr.takeByte();
if (minimum_instruction_length == 0) return bad();
if (version >= 4) {
const maximum_operations_per_instruction = try fr.takeByte();
_ = maximum_operations_per_instruction;
}
const default_is_stmt = (try fr.takeByte()) != 0;
const line_base = try fr.takeByteSigned();
const line_range = try fr.takeByte();
if (line_range == 0) return bad();
const opcode_base = try fr.takeByte();
const standard_opcode_lengths = try fr.take(opcode_base - 1);
var directories: ArrayList(FileEntry) = .empty;
defer directories.deinit(gpa);
var file_entries: ArrayList(FileEntry) = .empty;
defer file_entries.deinit(gpa);
if (version < 5) {
try directories.append(gpa, .{ .path = compile_unit_cwd });
while (true) {
const dir = try fr.takeSentinel(0);
if (dir.len == 0) break;
try directories.append(gpa, .{ .path = dir });
}
while (true) {
const file_name = try fr.takeSentinel(0);
if (file_name.len == 0) break;
const dir_index = try fr.takeLeb128(u32);
const mtime = try fr.takeLeb128(u64);
const size = try fr.takeLeb128(u64);
try file_entries.append(gpa, .{
.path = file_name,
.dir_index = dir_index,
.mtime = mtime,
.size = size,
});
}
} else {
const FileEntFmt = struct {
content_type_code: u16,
form_code: u16,
};
{
var dir_ent_fmt_buf: [10]FileEntFmt = undefined;
const directory_entry_format_count = try fr.takeByte();
if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad();
for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| {
ent_fmt.* = .{
.content_type_code = try fr.takeLeb128(u8),
.form_code = try fr.takeLeb128(u16),
};
}
const directories_count = try fr.takeLeb128(usize);
for (try directories.addManyAsSlice(gpa, directories_count)) |*e| {
e.* = .{ .path = &.{} };
for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| {
const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, addr_size_bytes, null);
switch (ent_fmt.content_type_code) {
DW.LNCT.path => e.path = try form_value.getString(d.*),
DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64),
DW.LNCT.size => e.size = try form_value.getUInt(u64),
DW.LNCT.MD5 => e.md5 = switch (form_value) {
.data16 => |data16| data16.*,
else => return bad(),
},
else => continue,
}
}
}
}
var file_ent_fmt_buf: [10]FileEntFmt = undefined;
const file_name_entry_format_count = try fr.takeByte();
if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
ent_fmt.* = .{
.content_type_code = try fr.takeLeb128(u16),
.form_code = try fr.takeLeb128(u16),
};
}
const file_names_count = try fr.takeLeb128(usize);
try file_entries.ensureUnusedCapacity(gpa, file_names_count);
for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| {
e.* = .{ .path = &.{} };
for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| {
const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, addr_size_bytes, null);
switch (ent_fmt.content_type_code) {
DW.LNCT.path => e.path = try form_value.getString(d.*),
DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64),
DW.LNCT.size => e.size = try form_value.getUInt(u64),
DW.LNCT.MD5 => e.md5 = switch (form_value) {
.data16 => |data16| data16.*,
else => return bad(),
},
else => continue,
}
}
}
}
var prog = LineNumberProgram.init(default_is_stmt, version);
var line_table: CompileUnit.SrcLocCache.LineTable = .{};
errdefer line_table.deinit(gpa);
fr.seek = @intCast(prog_start_offset);
const next_unit_pos = line_info_offset + next_offset;
while (fr.seek < next_unit_pos) {
const opcode = try fr.takeByte();
if (opcode == DW.LNS.extended_op) {
const op_size = try fr.takeLeb128(u64);
if (op_size < 1) return bad();
const sub_op = try fr.takeByte();
switch (sub_op) {
DW.LNE.end_sequence => {
// The row being added here is an "end" address, meaning
// that it does not map to the source location here -
// rather it marks the previous address as the last address
// that maps to this source location.
// In this implementation we don't mark end of addresses.
// This is a performance optimization based on the fact
// that we don't need to know if an address is missing
// source location info; we are only interested in being
// able to look up source location info for addresses that
// are known to have debug info.
//if (debug_debug_mode) assert(!line_table.contains(prog.address));
//try line_table.put(gpa, prog.address, CompileUnit.SrcLocCache.LineEntry.invalid);
prog.reset();
},
DW.LNE.set_address => {
prog.address = try readAddress(&fr, endian, addr_size_bytes);
},
DW.LNE.define_file => {
const path = try fr.takeSentinel(0);
const dir_index = try fr.takeLeb128(u32);
const mtime = try fr.takeLeb128(u64);
const size = try fr.takeLeb128(u64);
try file_entries.append(gpa, .{
.path = path,
.dir_index = dir_index,
.mtime = mtime,
.size = size,
});
},
else => try fr.discardAll64(op_size - 1),
}
} else if (opcode >= opcode_base) {
// special opcodes
const adjusted_opcode = opcode - opcode_base;
const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range);
const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range);
prog.line += inc_line;
prog.address += inc_addr;
try prog.addRow(gpa, &line_table);
prog.basic_block = false;
} else {
switch (opcode) {
DW.LNS.copy => {
try prog.addRow(gpa, &line_table);
prog.basic_block = false;
},
DW.LNS.advance_pc => {
const arg = try fr.takeLeb128(u64);
prog.address += arg * minimum_instruction_length;
},
DW.LNS.advance_line => {
const arg = try fr.takeLeb128(i64);
prog.line += arg;
},
DW.LNS.set_file => {
const arg = try fr.takeLeb128(usize);
prog.file = arg;
},
DW.LNS.set_column => {
const arg = try fr.takeLeb128(u64);
prog.column = arg;
},
DW.LNS.negate_stmt => {
prog.is_stmt = !prog.is_stmt;
},
DW.LNS.set_basic_block => {
prog.basic_block = true;
},
DW.LNS.const_add_pc => {
const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range);
prog.address += inc_addr;
},
DW.LNS.fixed_advance_pc => {
const arg = try fr.takeInt(u16, endian);
prog.address += arg;
},
DW.LNS.set_prologue_end => {},
else => {
if (opcode - 1 >= standard_opcode_lengths.len) return bad();
try fr.discardAll(standard_opcode_lengths[opcode - 1]);
},
}
}
}
// Dwarf standard v5, 6.2.5 says
// > Within a sequence, addresses and operation pointers may only increase.
// However, this is empirically not the case in reality, so we sort here.
line_table.sortUnstable(struct {
keys: []const u64,
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
return ctx.keys[a_index] < ctx.keys[b_index];
}
}{ .keys = line_table.keys() });
try directories.shrinkToLen(gpa);
try file_entries.shrinkToLen(gpa);
return .{
.line_table = line_table,
.directories = directories.toOwnedSliceAssert(),
.files = file_entries.toOwnedSliceAssert(),
.version = version,
};
}