You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
675 lines
20 KiB
675 lines
20 KiB
3 years ago
|
static u64
|
||
|
get_section_offset(u8 *file, char *name)
|
||
|
{
|
||
|
struct elf_header_x64 header = { 0 };
|
||
|
memcpy(&header, file, sizeof(header));
|
||
|
|
||
|
for (int i = 0; i < header.header_table_entry_count; ++i) {
|
||
|
struct elf_header_table_entry_x64 header_entry = { 0 };
|
||
|
u64 offset = header.header_table_offset + header.header_table_entry_size * i;
|
||
|
memcpy(&header_entry, file + offset, sizeof(header_entry));
|
||
|
int a = 1;
|
||
|
}
|
||
|
|
||
|
struct elf_section_table_entry_x64 shstrtab_header = { 0 };
|
||
|
u64 shstrtab_header_offset = header.section_table_offset + header.section_table_entry_size * header.section_names_table_index;
|
||
|
memcpy(&shstrtab_header, file + shstrtab_header_offset, sizeof(shstrtab_header));
|
||
|
|
||
|
u64 shstrtab_offset = shstrtab_header.offset_in_file;
|
||
|
u64 debug_info_offset = 0;
|
||
|
|
||
|
for (int i = 0; i < header.section_table_entry_count; ++i) {
|
||
|
struct elf_section_table_entry_x64 section_entry = { 0 };
|
||
|
u64 offset = header.section_table_offset + header.section_table_entry_size * i;
|
||
|
memcpy(§ion_entry, file + offset, sizeof(section_entry));
|
||
|
u64 section_name_offset = shstrtab_offset + section_entry.name_offset;
|
||
|
|
||
|
if (strncmp((char *) file + section_name_offset, name, strlen(name) + 1) == 0) {
|
||
|
debug_info_offset = section_entry.offset_in_file;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return(debug_info_offset);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
dwarf_section_contribution_is_64(u8 *file, u64 offset)
|
||
|
{
|
||
|
u32 length;
|
||
|
memcpy(&length, file + offset, 4);
|
||
|
return(length >= 0xFFFFFFF0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
decode_leb128(u8 *at, u32 *dest)
|
||
|
{
|
||
|
int offset = 0;
|
||
|
|
||
|
u64 result = 0;
|
||
|
u64 shift = 0;
|
||
|
|
||
|
while (1) {
|
||
|
u8 byte = at[offset++];
|
||
|
|
||
|
result |= ((byte & 127) << shift);
|
||
|
|
||
|
if ((byte & 128) == 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
shift += 7;
|
||
|
}
|
||
|
|
||
|
if (dest) {
|
||
|
*dest = result;
|
||
|
}
|
||
|
|
||
|
return(offset);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
decode_leb128s(u8 *at, s32 *dest)
|
||
|
{
|
||
|
int offset = 0;
|
||
|
|
||
|
s64 result = 0;
|
||
|
u64 shift = 0;
|
||
|
u32 size = 32;
|
||
|
u8 byte;
|
||
|
|
||
|
while (1) {
|
||
|
byte = at[offset++];
|
||
|
|
||
|
result |= ((byte & 127) << shift);
|
||
|
|
||
|
if ((byte & 128) == 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
shift += 7;
|
||
|
}
|
||
|
|
||
|
if ((shift < size) && (byte & 128)) {
|
||
|
result |= -(1 << shift);
|
||
|
}
|
||
|
|
||
|
if (dest) {
|
||
|
*dest = result;
|
||
|
}
|
||
|
|
||
|
return(offset);
|
||
|
}
|
||
|
|
||
|
static u64
|
||
|
abbrev_entry_offset(u8 *file, u64 abbrev_offset, u32 requested_code)
|
||
|
{
|
||
|
u32 code, tag;
|
||
|
u32 offset = 0;
|
||
|
|
||
|
do {
|
||
|
offset += decode_leb128(file + abbrev_offset + offset, &code);
|
||
|
offset += decode_leb128(file + abbrev_offset + offset, &tag);
|
||
|
|
||
|
u32 has_children = file[abbrev_offset + offset++];
|
||
|
|
||
|
if (code == requested_code) {
|
||
|
return(abbrev_offset);
|
||
|
}
|
||
|
|
||
|
if (code == 0) {
|
||
|
/* Abbreviation code not found, this should not happen */
|
||
|
assert(0);
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
u32 attribute, form;
|
||
|
|
||
|
do {
|
||
|
offset += decode_leb128(file + abbrev_offset + offset, &form);
|
||
|
offset += decode_leb128(file + abbrev_offset + offset, &attribute);
|
||
|
} while (attribute != 0 || form != 0);
|
||
|
|
||
|
abbrev_offset += offset;
|
||
|
offset = 0;
|
||
|
} while (code != 0);
|
||
|
|
||
|
assert(0);
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
static u64
|
||
|
find_subroutine_offset(u8 *file, u64 header_size, u8 address_size,
|
||
|
u64 string_offset, u64 abbrev_offset, u64 data_offset,
|
||
|
char *subroutine)
|
||
|
{
|
||
|
u32 code, tag;
|
||
|
u64 schema_offset;
|
||
|
u32 depth = 0;
|
||
|
u64 original_data_offset = data_offset;
|
||
|
|
||
|
int found_sr = 0;
|
||
|
|
||
|
do {
|
||
|
data_offset += decode_leb128(file + data_offset, &code);
|
||
|
|
||
|
if (code == 0) {
|
||
|
if (depth > 1) {
|
||
|
--depth;
|
||
|
continue;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
schema_offset = abbrev_entry_offset(file, abbrev_offset, code);
|
||
|
schema_offset += decode_leb128(file + schema_offset, NULL);
|
||
|
schema_offset += decode_leb128(file + schema_offset, &tag);
|
||
|
|
||
|
//printf("%d %s\n", code, tag_to_str(tag));
|
||
|
|
||
|
u32 has_children = file[schema_offset++];
|
||
|
if (has_children) {
|
||
|
++depth;
|
||
|
}
|
||
|
|
||
|
u32 attribute, form;
|
||
|
|
||
|
do {
|
||
|
schema_offset += decode_leb128(file + schema_offset, &attribute);
|
||
|
schema_offset += decode_leb128(file + schema_offset, &form);
|
||
|
|
||
|
if (attribute) {
|
||
|
//printf("\t%s ", attribute_to_str(attribute));
|
||
|
}
|
||
|
|
||
|
switch (form) {
|
||
|
case DW_FORM_sec_offset:
|
||
|
case DW_FORM_strp: {
|
||
|
u32 data = file[data_offset];
|
||
|
data_offset += 4; // 8 bytes for x64 DWARF!
|
||
|
|
||
|
if (form == DW_FORM_strp) {
|
||
|
char *str = (char *) file + string_offset + data;
|
||
|
//printf("(indirect string, offset: %#x): %s\n", data, str);
|
||
|
if (tag == DW_TAG_subprogram) {
|
||
|
if (strcmp(str, subroutine) == 0) {
|
||
|
found_sr = 1;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
//printf("%#x\n", data);
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_FORM_addr: {
|
||
|
u64 data = 0;
|
||
|
memcpy(&data, file + data_offset, address_size);
|
||
|
data_offset += address_size;
|
||
|
//printf("%#lx\n", data);
|
||
|
|
||
|
if (tag == DW_TAG_subprogram && found_sr == 1 && attribute == DW_AT_low_pc) {
|
||
|
return(data);
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
case DW_FORM_string: {
|
||
|
char *data = (char *) file + data_offset;
|
||
|
data_offset += strlen(data) + 1;
|
||
|
//printf("%s\n", data);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_FORM_flag_present: {
|
||
|
int data = 1;
|
||
|
//printf("Flag = 1\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_FORM_ref4: {
|
||
|
u32 data = file[data_offset];
|
||
|
data_offset += 4;
|
||
|
//printf("%#x\n", data);
|
||
|
u32 referenced_data = file[original_data_offset - header_size + data];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_FORM_exprloc: {
|
||
|
u32 length;
|
||
|
data_offset += decode_leb128(file + data_offset, &length);
|
||
|
//printf("%d byte block:", length);
|
||
|
|
||
|
for (u32 i = 0; i < length; ++i) {
|
||
|
//printf(" %x", file[data_offset + i]);
|
||
|
}
|
||
|
//printf("\n");
|
||
|
|
||
|
data_offset += length;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_FORM_data1: {
|
||
|
u8 data = file[data_offset];
|
||
|
data_offset += 1;
|
||
|
//printf("%#x\n", data);
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
case DW_FORM_data2: {
|
||
|
u16 data = file[data_offset];
|
||
|
data_offset += 2;
|
||
|
//printf("%#x\n", data);
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
case DW_FORM_data4: {
|
||
|
u32 data = file[data_offset];
|
||
|
data_offset += 4;
|
||
|
//printf("%#x\n", data);
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
case DW_FORM_data8: {
|
||
|
u64 data = file[data_offset];
|
||
|
data_offset += 8;
|
||
|
//printf("%#lx\n", data);
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
default: {
|
||
|
if (form) {
|
||
|
//printf("unknown attribute form %d\n", form);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} while (attribute != 0 || form != 0);
|
||
|
} while (1);
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
static u64
|
||
|
read_line_number_info(u8 *file, u64 dl_offset, u64 pc)
|
||
|
{
|
||
|
struct dwarf_debug_line_header_v3_x32 header = { 0 };
|
||
|
memcpy(&header, file + dl_offset, 15); /* all fixed-size info */
|
||
|
|
||
|
dl_offset += 15;
|
||
|
|
||
|
header.standard_opcode_lengths = malloc(header.opcode_base - 1);
|
||
|
memcpy(header.standard_opcode_lengths, file + dl_offset, header.opcode_base - 1);
|
||
|
|
||
|
dl_offset += header.opcode_base - 1;
|
||
|
|
||
|
/* "Each entry is a null-terminated string containing a full path name. The last entry
|
||
|
is followed by a single null byte." */
|
||
|
u8 ndirs = 0;
|
||
|
u8 nfiles = 0;
|
||
|
|
||
|
u8 *p = file + dl_offset;
|
||
|
|
||
|
while (*p != 0) {
|
||
|
++ndirs;
|
||
|
while (*p != 0) {
|
||
|
++p;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
header.ndirs = ndirs;
|
||
|
header.include_directories = 0; // malloc(ndirs * sizeof(char *));
|
||
|
|
||
|
dl_offset += (p - (file + dl_offset)) + 1;
|
||
|
|
||
|
|
||
|
p = file + dl_offset;
|
||
|
while (*p != 0) {
|
||
|
++nfiles;
|
||
|
|
||
|
/* null-terminated string */
|
||
|
while (*p != 0) {
|
||
|
++p;
|
||
|
}
|
||
|
|
||
|
++p;
|
||
|
|
||
|
u64 offset = 0;
|
||
|
u32 dummy = 0;
|
||
|
|
||
|
offset += decode_leb128(p, &dummy);
|
||
|
offset += decode_leb128(p, &dummy);
|
||
|
offset += decode_leb128(p, &dummy);
|
||
|
|
||
|
p += offset;
|
||
|
}
|
||
|
|
||
|
header.files = malloc(nfiles * sizeof(struct dwarf_debug_line_file_info));
|
||
|
header.nfiles = nfiles;
|
||
|
|
||
|
struct dwarf_debug_line_file_info *f = header.files;
|
||
|
|
||
|
p = file + dl_offset;
|
||
|
while (*p != 0) {
|
||
|
|
||
|
/* null-terminated string */
|
||
|
f->name = (char *) p;
|
||
|
|
||
|
while (*p != 0) {
|
||
|
++p;
|
||
|
}
|
||
|
|
||
|
++p;
|
||
|
|
||
|
u64 offset = 0;
|
||
|
u32 dummy = 0;
|
||
|
|
||
|
offset += decode_leb128(p, &f->directory_index);
|
||
|
offset += decode_leb128(p, &f->time_modified);
|
||
|
offset += decode_leb128(p, &f->file_size);
|
||
|
|
||
|
p += offset;
|
||
|
|
||
|
++f;
|
||
|
}
|
||
|
|
||
|
dl_offset = p - file + 1;
|
||
|
|
||
|
u8 opcode;
|
||
|
enum dwarf_lnp_opcode opcode_regular;
|
||
|
enum dwarf_lnp_opcode_extended opcode_extended;
|
||
|
|
||
|
p = file + dl_offset;
|
||
|
|
||
|
struct dwarf_line_number_state state = { 0 };
|
||
|
|
||
|
state.file = 1;
|
||
|
state.line = 1;
|
||
|
state.is_stmt = header.default_is_stmt;
|
||
|
|
||
|
|
||
|
u64 last_line = 0;
|
||
|
|
||
|
do {
|
||
|
opcode = *p;
|
||
|
++p;
|
||
|
|
||
|
u8 nops = 0;
|
||
|
if (opcode) {
|
||
|
if (opcode <= header.opcode_base) {
|
||
|
/* standart opcode */
|
||
|
opcode_regular = opcode;
|
||
|
|
||
|
switch (opcode_regular) {
|
||
|
case DW_LNS_copy: {
|
||
|
state.basic_block = 0;
|
||
|
state.prologue_end = 0;
|
||
|
state.epilogue_begin = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_advance_pc: {
|
||
|
u32 operand;
|
||
|
p += decode_leb128(p, &operand);
|
||
|
operand *= header.minimum_instruction_length;
|
||
|
state.pc += operand;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_advance_line: {
|
||
|
s32 operand;
|
||
|
p += decode_leb128s(p, &operand);
|
||
|
last_line = state.line;
|
||
|
state.line += operand;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_set_file: {
|
||
|
u32 operand;
|
||
|
p += decode_leb128(p, &operand);
|
||
|
operand *= header.minimum_instruction_length;
|
||
|
state.file = operand;
|
||
|
|
||
|
// printf("Switch to file %s in directory %d\n", header.files[state.file - 1].name, header.files[state.file - 1].directory_index);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_set_column: {
|
||
|
u32 operand;
|
||
|
p += decode_leb128(p, &operand);
|
||
|
operand *= header.minimum_instruction_length;
|
||
|
state.column = operand;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_negate_stmt: {
|
||
|
state.is_stmt = 1 - state.is_stmt;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_set_basic_block: {
|
||
|
state.basic_block = 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_const_add_pc: {
|
||
|
u8 adjusted = 255 - header.opcode_base;
|
||
|
s32 address_increment = (adjusted / header.line_range) * header.minimum_instruction_length;
|
||
|
state.pc += address_increment;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_fixed_advance_pc: {
|
||
|
u16 operand;
|
||
|
memcpy(&operand, p, 2);
|
||
|
p += 2;
|
||
|
state.pc += operand;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_set_prologue_end: {
|
||
|
state.prologue_end = 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_set_epilogue_begin: {
|
||
|
state.epilogue_begin = 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNS_set_isa: {
|
||
|
u32 operand;
|
||
|
p += decode_leb128(p, &operand);
|
||
|
operand *= header.minimum_instruction_length;
|
||
|
state.isa = operand;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
/* special opcode */
|
||
|
u8 adjusted = opcode - header.opcode_base;
|
||
|
s32 address_increment = (adjusted / header.line_range) * header.minimum_instruction_length;
|
||
|
s32 line_increment = header.line_base + (adjusted % header.line_range);
|
||
|
|
||
|
last_line = state.line;
|
||
|
|
||
|
state.pc += address_increment;
|
||
|
state.line += line_increment;
|
||
|
state.basic_block = 0;
|
||
|
state.prologue_end = 0;
|
||
|
state.epilogue_begin = 0;
|
||
|
state.discriminator = 0;
|
||
|
}
|
||
|
} else {
|
||
|
/* extended opcode */
|
||
|
u32 instruction_length;
|
||
|
p += decode_leb128(p, &instruction_length);
|
||
|
opcode = *p;
|
||
|
opcode_extended = opcode;
|
||
|
|
||
|
++p;
|
||
|
|
||
|
switch (opcode_extended) {
|
||
|
case DW_LNE_end_sequence: {
|
||
|
state.end_sequence = 1;
|
||
|
//printf("END: %lx -> %d\n", state.pc, state.line);
|
||
|
memset(&state, 0, sizeof(state));
|
||
|
state.file = 1;
|
||
|
state.line = 1;
|
||
|
state.is_stmt = header.default_is_stmt;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNE_set_address: {
|
||
|
u64 address;
|
||
|
memcpy(&address, p, 8);
|
||
|
state.pc = address;
|
||
|
p += 8;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNE_define_file: {
|
||
|
struct dwarf_debug_line_file_info f = { 0 };
|
||
|
f.name = (char *) p;
|
||
|
|
||
|
while (*p != 0) {
|
||
|
++p;
|
||
|
}
|
||
|
++p;
|
||
|
|
||
|
p += decode_leb128(p, &f.directory_index);
|
||
|
p += decode_leb128(p, &f.time_modified);
|
||
|
p += decode_leb128(p, &f.file_size);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case DW_LNE_set_discriminator: {
|
||
|
u32 operand;
|
||
|
p += decode_leb128(p, &operand);
|
||
|
operand *= header.minimum_instruction_length;
|
||
|
state.discriminator = operand;
|
||
|
break;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO: this is off by one?
|
||
|
if (state.pc >= pc) {
|
||
|
return(last_line);
|
||
|
}
|
||
|
|
||
|
} while (opcode_extended != DW_LNE_end_sequence);
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
static u64
|
||
|
get_executable_base_address(u8 *elf_file, int pid)
|
||
|
{
|
||
|
char path[256] = { 0 };
|
||
|
|
||
|
snprintf(path, 256, "/proc/%d/maps", pid);
|
||
|
|
||
|
FILE *maps_file = fopen(path, "rb");
|
||
|
if (!maps_file) {
|
||
|
DIE("proc map not found\n");
|
||
|
}
|
||
|
|
||
|
struct elf_header_x64 header = { 0 };
|
||
|
memcpy(&header, elf_file, sizeof(header));
|
||
|
|
||
|
u64 elf_offset = 0;
|
||
|
|
||
|
for (int i = 0; i < header.header_table_entry_count; ++i) {
|
||
|
struct elf_header_table_entry_x64 header_entry = { 0 };
|
||
|
u64 offset = header.header_table_offset + header.header_table_entry_size * i;
|
||
|
memcpy(&header_entry, elf_file + offset, sizeof(header_entry));
|
||
|
|
||
|
//printf("%#018lx %s\n", header_entry.segment_offset, header_entry.flags & PF_X ? "E" : "");
|
||
|
|
||
|
if (header_entry.flags & PF_X) {
|
||
|
elf_offset = header_entry.segment_offset;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!elf_offset) {
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
size_t len;
|
||
|
char *line = malloc(4096);
|
||
|
|
||
|
while ((len = getline(&line, &len, maps_file)) != -1UL) {
|
||
|
u64 base;
|
||
|
u64 end;
|
||
|
u64 offset;
|
||
|
|
||
|
char *at = line;
|
||
|
|
||
|
base = strtoll(at, &at, 16);
|
||
|
|
||
|
return(base);
|
||
|
|
||
|
end = strtoll(at + 1, &at, 16);
|
||
|
(void) end;
|
||
|
|
||
|
while (*at < '0' || *at > '9') ++at;
|
||
|
|
||
|
offset = strtoll(at, &at, 16);
|
||
|
|
||
|
if (offset == elf_offset) {
|
||
|
return(base);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(line);
|
||
|
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
static u64
|
||
|
get_address_of_subroutine(u8 *file, char *sr)
|
||
|
{
|
||
|
u64 debug_info_offset = get_section_offset(file, ".debug_info");
|
||
|
printf("Found .debug_info at offset %#lx\n", debug_info_offset);
|
||
|
|
||
|
u64 debug_line_offset = get_section_offset(file, ".debug_line");
|
||
|
printf("Found .debug_line at offset %#lx\n", debug_line_offset);
|
||
|
|
||
|
u64 debug_abbrev_offset = get_section_offset(file, ".debug_abbrev");
|
||
|
printf("Found .debug_abbrev at offset %#lx\n", debug_abbrev_offset);
|
||
|
|
||
|
u64 debug_str_offset = get_section_offset(file, ".debug_str");
|
||
|
printf("Found .debug_str at offset %#lx\n", debug_str_offset);
|
||
|
|
||
|
struct dwarf_debug_info_header_x32 di_header = { 0 };
|
||
|
memcpy(&di_header, file + debug_info_offset, sizeof(di_header));
|
||
|
|
||
|
u64 abbrev_offset = debug_abbrev_offset + di_header.debug_abbrev_offset;
|
||
|
u64 data_offset = debug_info_offset + sizeof(di_header);
|
||
|
|
||
|
u64 result = find_subroutine_offset(file, sizeof(di_header), di_header.address_size,
|
||
|
debug_str_offset, abbrev_offset, data_offset, sr);
|
||
|
|
||
|
//read_line_number_info(file, debug_line_offset);
|
||
|
|
||
|
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
static u64
|
||
|
pc_to_line_number(u8 *file, u64 pc)
|
||
|
{
|
||
|
u64 debug_line_offset = get_section_offset(file, ".debug_line");
|
||
|
//printf("Found .debug_line at offset %#lx\n", debug_line_offset);
|
||
|
u64 result = read_line_number_info(file, debug_line_offset, pc);
|
||
|
return(result);
|
||
|
}
|