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); }