| /******************************************************************************* |
| * Copyright (c) 2007 Wind River Systems, Inc. and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Wind River Systems - initial API and implementation |
| *******************************************************************************/ |
| |
| /* |
| * TCF service line Numbers |
| * The service associates locations in the source files with the corresponding |
| * machine instruction addresses in the executable object. |
| */ |
| |
| #include "config.h" |
| #if SERVICE_LineNumbers |
| |
| #include <errno.h> |
| #include <assert.h> |
| #include <stdio.h> |
| #include "linenumbers.h" |
| #include "context.h" |
| #include "myalloc.h" |
| #include "exceptions.h" |
| #include "json.h" |
| #include "protocol.h" |
| #include "elf.h" |
| #include "dwarfio.h" |
| #include "dwarf.h" |
| |
| static const char * LINENUMBERS = "LineNumbers"; |
| |
| typedef unsigned long ADDR_T; |
| |
| struct FileInfo { |
| char * name; |
| char * dir; |
| U4_T mtime; |
| U4_T size; |
| }; |
| |
| typedef struct FileInfo FileInfo; |
| |
| struct LineNumbersState { |
| unsigned file; |
| unsigned line; |
| unsigned column; |
| ADDR_T address; |
| U1_T isa; |
| U1_T is_stmt; |
| U1_T basic_block; |
| U1_T prologue_end; |
| U1_T epilogue_begin; |
| U1_T end_sequence; |
| }; |
| |
| typedef struct LineNumbersState LineNumbersState; |
| |
| struct CompUnit { |
| ADDR_T low_pc; |
| ADDR_T high_pc; |
| U8_T debug_ranges_offs; |
| U8_T debug_info_offs; |
| U8_T line_info_offs; |
| char * name; |
| char * dir; |
| |
| U4_T files_cnt; |
| U4_T files_max; |
| FileInfo * files; |
| |
| U4_T dirs_cnt; |
| U4_T dirs_max; |
| char ** dirs; |
| |
| U4_T states_cnt; |
| U4_T states_max; |
| LineNumbersState * states; |
| }; |
| |
| typedef struct CompUnit CompUnit; |
| |
| struct LineNumbersCache { |
| ELF_File * file; |
| CompUnit * units; |
| U4_T units_max; |
| U4_T units_cnt; |
| ELF_Section * debug_ranges; |
| ELF_Section * debug_line; |
| }; |
| |
| typedef struct LineNumbersCache LineNumbersCache; |
| |
| static LineNumbersCache * read_cache; |
| |
| static void read_tag_com_unit(U2_T attr, U2_T form) { |
| static CompUnit * unit; |
| switch (attr) { |
| case 0: |
| if (form) { |
| if (read_cache->units_cnt >= read_cache->units_max) { |
| read_cache->units_max = read_cache->units_max == 0 ? 16 : read_cache->units_max * 2; |
| read_cache->units = (CompUnit *)loc_realloc(read_cache->units, sizeof(CompUnit) * read_cache->units_max); |
| } |
| unit = read_cache->units + read_cache->units_cnt++; |
| memset(unit, 0, sizeof(CompUnit)); |
| unit->debug_ranges_offs = ~(U8_T)0; |
| } |
| else { |
| /* Skip to next compilation unit */ |
| assert(dio_gUnitSize > 0); |
| dio_Skip(dio_gUnitPos + dio_gUnitSize - dio_GetPos()); |
| } |
| break; |
| case AT_low_pc: |
| dio_ChkAddr(form); |
| unit->low_pc = (ADDR_T)dio_gFormRef; |
| break; |
| case AT_high_pc: |
| dio_ChkAddr(form); |
| unit->high_pc = (ADDR_T)dio_gFormRef; |
| break; |
| case AT_ranges: |
| dio_ChkData(form); |
| unit->debug_ranges_offs = dio_gFormData; |
| break; |
| case AT_name: |
| dio_ChkString(form); |
| unit->name = (char *)loc_alloc(dio_gFormBlockSize); |
| strcpy(unit->name, (char *)dio_gFormBlockBuf); |
| break; |
| case AT_comp_dir: |
| dio_ChkString(form); |
| unit->dir = (char *)loc_alloc(dio_gFormBlockSize); |
| strcpy(unit->dir, (char *)dio_gFormBlockBuf); |
| break; |
| case AT_stmt_list: |
| dio_ChkData(form); |
| unit->line_info_offs = dio_gFormData; |
| break; |
| } |
| } |
| |
| static void entry_callback(U2_T Tag, U2_T attr, U2_T form) { |
| switch (Tag) { |
| case TAG_compile_unit : |
| read_tag_com_unit(attr, form); |
| break; |
| } |
| } |
| |
| static void free_unit_cache(CompUnit * unit) { |
| U4_T j; |
| |
| for (j = 0; j < unit->files_cnt; j++) { |
| loc_free(unit->files[j].name); |
| } |
| unit->files_cnt = 0; |
| unit->files_max = 0; |
| loc_free(unit->files); |
| |
| for (j = 0; j < unit->dirs_cnt; j++) { |
| loc_free(unit->dirs[j]); |
| } |
| unit->dirs_cnt = 0; |
| unit->dirs_max = 0; |
| loc_free(unit->dirs); |
| |
| unit->states_cnt = 0; |
| unit->states_max = 0; |
| loc_free(unit->states); |
| } |
| |
| static void free_line_numbers_cache(ELF_File * file) { |
| LineNumbersCache * cache = (LineNumbersCache *)file->line_numbers_cache; |
| if (cache != NULL) { |
| U4_T i; |
| for (i = 0; i < cache->units_cnt; i++) { |
| CompUnit * unit = cache->units + i; |
| loc_free(unit->name); |
| loc_free(unit->dir); |
| free_unit_cache(unit); |
| } |
| loc_free(cache->units); |
| loc_free(cache); |
| } |
| } |
| |
| static LineNumbersCache * get_line_numbers_cache(Context * ctx) { |
| ELF_File * file; |
| char fnm[FILE_PATH_SIZE]; |
| |
| #if defined(WIN32) |
| exception(EINVAL); |
| #elif defined(_WRS_KERNEL) |
| exception(EINVAL); |
| #else |
| snprintf(fnm, sizeof(fnm), "/proc/%d/exe", ctx->mem); |
| #endif |
| |
| file = elf_open(fnm); |
| if (file == NULL) exception(errno); |
| if (file->line_numbers_cache == NULL) { |
| Trap trap; |
| if (set_trap(&trap)) { |
| unsigned idx; |
| LineNumbersCache * cache = NULL; |
| dio_LoadAbbrevTable(file); |
| cache = (LineNumbersCache *)(file->line_numbers_cache = loc_alloc_zero(sizeof(LineNumbersCache))); |
| cache->file = file; |
| read_cache = cache; |
| for (idx = 0; idx < file->section_cnt; idx++) { |
| ELF_Section * sec = file->sections[idx]; |
| if (sec == NULL) continue; |
| if (sec->size == 0) continue; |
| if (sec->name == NULL) continue; |
| if (strcmp(sec->name, ".debug") == 0 || strcmp(sec->name, ".debug_info") == 0) { |
| dio_EnterSection(sec, 0); |
| dio_gVersion = strcmp(sec->name, ".debug") == 0 ? 1 : 2; |
| while (dio_GetPos() < sec->size) dio_ReadUnit(entry_callback); |
| dio_ExitSection(); |
| } |
| else if (strcmp(sec->name, ".debug_ranges") == 0) { |
| cache->debug_ranges = sec; |
| } |
| else if (strcmp(sec->name, ".debug_line") == 0) { |
| cache->debug_line = sec; |
| } |
| } |
| read_cache = NULL; |
| clear_trap(&trap); |
| } |
| else { |
| free_line_numbers_cache(file); |
| str_exception(trap.error, trap.msg); |
| } |
| } |
| return (LineNumbersCache *)file->line_numbers_cache; |
| } |
| |
| static void add_dir(CompUnit * unit, char * name) { |
| if (unit->dirs_cnt >= unit->dirs_max) { |
| unit->dirs_max = unit->dirs_max == 0 ? 16 : unit->dirs_max * 2; |
| unit->dirs = (char **)loc_realloc(unit->dirs, sizeof(char *) * unit->dirs_max); |
| } |
| unit->dirs[unit->dirs_cnt++] = name; |
| } |
| |
| static void add_file(CompUnit * unit, FileInfo * file) { |
| if (unit->files_cnt >= unit->files_max) { |
| unit->files_max = unit->files_max == 0 ? 16 : unit->files_max * 2; |
| unit->files = (FileInfo *)loc_realloc(unit->files, sizeof(FileInfo) * unit->files_max); |
| } |
| if (file->dir == NULL) file->dir = unit->dir; |
| unit->files[unit->files_cnt++] = *file; |
| } |
| |
| static void add_state(CompUnit * unit, LineNumbersState * state) { |
| if (unit->states_cnt >= unit->states_max) { |
| unit->states_max = unit->states_max == 0 ? 128 : unit->states_max * 2; |
| unit->states = (LineNumbersState *)loc_realloc(unit->states, sizeof(LineNumbersState) * unit->states_max); |
| } |
| unit->states[unit->states_cnt++] = *state; |
| } |
| |
| static void load_line_numbers(LineNumbersCache * cache, CompUnit * unit) { |
| Trap trap; |
| if (unit->files != NULL && unit->dirs != NULL) return; |
| dio_gUnitPos = unit->line_info_offs; |
| dio_EnterSection(cache->debug_line, dio_gUnitPos); |
| if (set_trap(&trap)) { |
| U8_T header_pos = 0; |
| U1_T opcode_base = 0; |
| U1_T opcode_size[256]; |
| U4_T header_size = 0; |
| U1_T min_instruction_length = 0; |
| U1_T is_stmt_default = 0; |
| I1_T line_base = 0; |
| U1_T line_range = 0; |
| U4_T unit_size = 0; |
| LineNumbersState state; |
| |
| // Read header |
| unit_size = dio_ReadU4(); |
| if (unit_size == 0xffffffffu) { |
| str_exception(ERR_DWARF, "64-bit DWARF is not supported yet"); |
| } |
| else { |
| unit_size += 4; |
| } |
| dio_ReadU2(); /* line info version */ |
| header_size = dio_ReadU4(); |
| header_pos = dio_GetPos(); |
| min_instruction_length = dio_ReadU1(); |
| is_stmt_default = dio_ReadU1() != 0; |
| line_base = (I1_T)dio_ReadU1(); |
| line_range = dio_ReadU1(); |
| opcode_base = dio_ReadU1(); |
| memset(opcode_size, 0, sizeof(opcode_size)); |
| dio_Read(opcode_size + 1, opcode_base - 1); |
| |
| // Read directory names |
| for (;;) { |
| char * name = dio_ReadString(); |
| if (name == NULL) break; |
| add_dir(unit, name); |
| } |
| |
| // Read source files info |
| for (;;) { |
| U4_T dir = 0; |
| FileInfo file; |
| memset(&file, 0, sizeof(file)); |
| file.name = dio_ReadString(); |
| if (file.name == NULL) break; |
| dir = dio_ReadLEB128(); |
| if (dir > 0 && dir <= unit->dirs_cnt) file.dir = unit->dirs[dir - 1]; |
| file.mtime = dio_ReadLEB128(); |
| file.size = dio_ReadLEB128(); |
| add_file(unit, &file); |
| } |
| |
| // Run the program |
| if (header_pos + header_size != dio_GetPos()) |
| str_exception(ERR_DWARF, "Invalid line info header"); |
| memset(&state, 0, sizeof(state)); |
| state.file = 1; |
| state.line = 1; |
| state.is_stmt = is_stmt_default; |
| while (dio_GetPos() < dio_gUnitPos + unit_size) { |
| U1_T opcode = dio_ReadU1(); |
| if (opcode >= opcode_base) { |
| state.line += (unsigned)((int)((opcode - opcode_base) % line_range) + line_base); |
| state.address += (opcode - opcode_base) / line_range * min_instruction_length; |
| add_state(unit, &state); |
| state.basic_block = 0; |
| state.prologue_end = 0; |
| state.epilogue_begin = 0; |
| } |
| else if (opcode == 0) { |
| U4_T op_size = dio_ReadLEB128(); |
| U8_T op_pos = dio_GetPos(); |
| switch (dio_ReadU1()) { |
| case DW_LNE_define_file: { |
| U4_T dir = 0; |
| FileInfo file; |
| memset(&file, 0, sizeof(file)); |
| file.name = dio_ReadString(); |
| dir = dio_ReadLEB128(); |
| if (dir > 0 && dir <= unit->dirs_cnt) file.dir = unit->dirs[dir - 1]; |
| file.mtime = dio_ReadLEB128(); |
| file.size = dio_ReadLEB128(); |
| add_file(unit, &file); |
| break; |
| } |
| case DW_LNE_end_sequence: |
| state.end_sequence = 1; |
| add_state(unit, &state); |
| memset(&state, 0, sizeof(state)); |
| state.file = 1; |
| state.line = 1; |
| state.is_stmt = is_stmt_default; |
| break; |
| case DW_LNE_set_address: |
| state.address = (ADDR_T)dio_ReadAddress(); |
| break; |
| default: |
| dio_Skip(op_size - 1); |
| break; |
| } |
| assert(dio_GetPos() == op_pos + op_size); |
| } |
| else { |
| switch (opcode) { |
| case DW_LNS_copy: |
| add_state(unit, &state); |
| state.basic_block = 0; |
| state.prologue_end = 0; |
| state.epilogue_begin = 0; |
| break; |
| case DW_LNS_advance_pc: |
| state.address += (ADDR_T)(dio_ReadU8LEB128() * min_instruction_length); |
| break; |
| case DW_LNS_advance_line: |
| state.line += dio_ReadLEB128(); |
| break; |
| case DW_LNS_set_file: |
| state.file = dio_ReadLEB128(); |
| break; |
| case DW_LNS_set_column: |
| state.column = dio_ReadLEB128(); |
| break; |
| case DW_LNS_negate_stmt: |
| state.is_stmt = !state.is_stmt; |
| break; |
| case DW_LNS_set_basic_block: |
| state.basic_block = 1; |
| break; |
| case DW_LNS_const_add_pc: |
| state.address += (255 - opcode_base) / line_range * min_instruction_length; |
| break; |
| case DW_LNS_fixed_advance_pc: |
| state.address += dio_ReadU2(); |
| 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: |
| state.isa = (U1_T)dio_ReadLEB128(); |
| break; |
| default: |
| str_exception(ERR_DWARF, "Invalid line info op code"); |
| break; |
| } |
| } |
| } |
| dio_ExitSection(); |
| clear_trap(&trap); |
| } |
| else { |
| dio_ExitSection(); |
| free_unit_cache(unit); |
| str_exception(trap.error, trap.msg); |
| } |
| } |
| |
| static CompUnit * find_unit(LineNumbersCache * cache, ADDR_T addr0, ADDR_T addr1, ADDR_T * addr_next) { |
| U4_T i; |
| CompUnit * unit = NULL; |
| ADDR_T low_pc = 0; |
| // TODO: faster unit search |
| for (i = 0; i < cache->units_cnt; i++) { |
| CompUnit * u = cache->units + i; |
| if (u->debug_ranges_offs != ~(U8_T)0) { |
| if (cache->debug_ranges != NULL) { |
| U8_T base = u->low_pc; |
| U8_T max = 0; |
| dio_gUnitPos = u->debug_ranges_offs; |
| dio_EnterSection(cache->debug_ranges, dio_gUnitPos); |
| while (1) { |
| U8_T x = dio_ReadAddress(); |
| U8_T y = dio_ReadAddress(); |
| if (x == 0 && y == 0) break; |
| if (x == ((U8_T)1 << dio_gAddressSize * 8) - 1) { |
| base = y; |
| } |
| else { |
| x = base + x; |
| y = base + y; |
| if (addr0 < y && addr1 > x) { |
| if (unit == NULL || low_pc > x) { |
| unit = u; |
| low_pc = (ADDR_T)x; |
| *addr_next = (ADDR_T)y; |
| } |
| } |
| } |
| } |
| dio_ExitSection(); |
| } |
| } |
| else if (u->low_pc != 0 && u->high_pc != 0) { |
| if (addr0 < u->high_pc && addr1 > u->low_pc) { |
| if (unit == NULL || low_pc > u->low_pc) { |
| unit = u; |
| low_pc = u->low_pc; |
| *addr_next = u->high_pc; |
| } |
| } |
| } |
| } |
| return unit; |
| } |
| |
| static void load_line_numbers_in_range(LineNumbersCache * cache, ADDR_T addr0, ADDR_T addr1) { |
| while (addr0 < addr1) { |
| ADDR_T next = 0; |
| CompUnit * unit = find_unit(cache, addr0, addr1, &next); |
| if (unit == NULL) break; |
| load_line_numbers(cache, unit); |
| addr0 = next; |
| } |
| } |
| |
| static void command_map_to_source(char * token, InputStream * inp, OutputStream * out) { |
| int err = 0; |
| char * err_msg = NULL; |
| char id[256]; |
| ADDR_T addr0; |
| ADDR_T addr1; |
| Context * ctx = NULL; |
| LineNumbersCache * cache = NULL; |
| Trap trap; |
| |
| json_read_string(inp, id, sizeof(id)); |
| if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX); |
| addr0 = json_read_ulong(inp); |
| if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX); |
| addr1 = json_read_ulong(inp); |
| if (inp->read(inp) != 0) exception(ERR_JSON_SYNTAX); |
| if (inp->read(inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX); |
| |
| ctx = id2ctx(id); |
| if (ctx == NULL) err = ERR_INV_CONTEXT; |
| else if (ctx->exited) err = ERR_ALREADY_EXITED; |
| |
| if (err == 0) { |
| if (set_trap(&trap)) { |
| cache = get_line_numbers_cache(ctx); |
| load_line_numbers_in_range(cache, addr0, addr1); |
| clear_trap(&trap); |
| } |
| else { |
| err = trap.error; |
| err_msg = trap.msg; |
| } |
| } |
| |
| write_stringz(out, "R"); |
| write_stringz(out, token); |
| write_err_msg(out, err, err_msg); |
| if (err != 0) { |
| write_stringz(out, "null"); |
| } |
| else { |
| int cnt = 0; |
| FileInfo * file = NULL; |
| out->write(out, '['); |
| while (addr0 < addr1) { |
| U4_T i; |
| ADDR_T next = 0; |
| FileInfo * state_file = NULL; |
| CompUnit * unit = find_unit(cache, addr0, addr1, &next); |
| if (unit == NULL) break; |
| |
| for (i = 0; i < unit->states_cnt - 1; i++) { |
| LineNumbersState * state = unit->states + i; |
| LineNumbersState * next = unit->states + i + 1; |
| if (state->end_sequence) continue; |
| if (next->address > addr0 && state->address < addr1) { |
| if (cnt > 0) out->write(out, ','); |
| out->write(out, '{'); |
| json_write_string(out, "SLine"); |
| out->write(out, ':'); |
| json_write_ulong(out, state->line - 1); |
| if (state->column > 0) { |
| out->write(out, ','); |
| json_write_string(out, "SCol"); |
| out->write(out, ':'); |
| json_write_ulong(out, state->column - 1); |
| } |
| out->write(out, ','); |
| json_write_string(out, "ELine"); |
| out->write(out, ':'); |
| json_write_ulong(out, next->line - 1); |
| if (next->column > 0) { |
| out->write(out, ','); |
| json_write_string(out, "ECol"); |
| out->write(out, ':'); |
| json_write_ulong(out, next->column - 1); |
| } |
| state_file = NULL; |
| if (state->file >= 1 && state->file <= unit->files_cnt) { |
| state_file = unit->files + (state->file - 1); |
| } |
| if (file != state_file) { |
| file = state_file; |
| out->write(out, ','); |
| json_write_string(out, "File"); |
| out->write(out, ':'); |
| json_write_string(out, file == NULL ? NULL : file->name); |
| out->write(out, ','); |
| json_write_string(out, "Dir"); |
| out->write(out, ':'); |
| json_write_string(out, file == NULL ? NULL : file->dir); |
| } |
| out->write(out, ','); |
| json_write_string(out, "SAddr"); |
| out->write(out, ':'); |
| json_write_ulong(out, state->address); |
| out->write(out, ','); |
| json_write_string(out, "EAddr"); |
| out->write(out, ':'); |
| json_write_ulong(out, next->address); |
| if (state->isa != 0) { |
| out->write(out, ','); |
| json_write_string(out, "ISA"); |
| out->write(out, ':'); |
| json_write_ulong(out, state->isa); |
| } |
| if (state->is_stmt) { |
| out->write(out, ','); |
| json_write_string(out, "IsStmt"); |
| out->write(out, ':'); |
| json_write_boolean(out, state->is_stmt); |
| } |
| if (state->basic_block) { |
| out->write(out, ','); |
| json_write_string(out, "BasicBlock"); |
| out->write(out, ':'); |
| json_write_boolean(out, state->basic_block); |
| } |
| if (state->prologue_end) { |
| out->write(out, ','); |
| json_write_string(out, "PrologueEnd"); |
| out->write(out, ':'); |
| json_write_boolean(out, state->prologue_end); |
| } |
| if (state->epilogue_begin) { |
| out->write(out, ','); |
| json_write_string(out, "EpilogueBegin"); |
| out->write(out, ':'); |
| json_write_boolean(out, state->epilogue_begin); |
| } |
| out->write(out, '}'); |
| cnt++; |
| } |
| } |
| |
| addr0 = next; |
| } |
| out->write(out, ']'); |
| out->write(out, 0); |
| } |
| out->write(out, MARKER_EOM); |
| if (cache != NULL) elf_close(cache->file); |
| } |
| |
| void ini_line_numbers_service(void) { |
| elf_add_close_listener(free_line_numbers_cache); |
| add_command_handler(LINENUMBERS, "mapToSource", command_map_to_source); |
| } |
| |
| #endif |
| |