blob: 89e9468e4baaa8a94408a4778fbc9fd27974f519 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007-2018 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
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
/*
* TCF service line Numbers - ELF version.
*
* The service associates locations in the source files with the corresponding
* machine instruction addresses in the executable object.
*/
#include <tcf/config.h>
#if SERVICE_LineNumbers && (!ENABLE_LineNumbersProxy || ENABLE_LineNumbersMux) && ENABLE_ELF
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <tcf/framework/context.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/cache.h>
#include <tcf/framework/trace.h>
#include <tcf/framework/json.h>
#include <tcf/framework/protocol.h>
#include <tcf/services/linenumbers.h>
#include <tcf/services/tcf_elf.h>
#include <tcf/services/dwarfio.h>
#include <tcf/services/dwarf.h>
#include <tcf/services/dwarfcache.h>
#include <tcf/services/stacktrace.h>
#include <tcf/services/pathmap.h>
#if ENABLE_LineNumbersMux
#define LINENUMBERS_READER_PREFIX elf_reader_
#include <tcf/services/linenumbers_mux.h>
#endif
#include <tcf/services/linenumbers_elf-ext.h>
static int compare_path(Channel * chnl, Context * ctx, const char * file, const char * pwd, const char * dir, const char * name) {
int i, j;
char buf[FILE_PATH_SIZE];
char * full_name = NULL;
if (file == NULL) return 0;
if (name == NULL) return 0;
while (file[0] == '.') {
if (file[1] == '.' && file[2] == '/') file += 3;
else if (file[1] == '/') file += 2;
else break;
}
i = strlen(file);
if (is_absolute_path(name)) {
full_name = (char *)name;
}
else if (dir != NULL && is_absolute_path(dir)) {
snprintf(full_name = buf, sizeof(buf), "%s/%s", dir, name);
}
else if (dir != NULL && pwd != NULL) {
snprintf(full_name = buf, sizeof(buf), "%s/%s/%s", pwd, dir, name);
}
else if (pwd != NULL) {
snprintf(full_name = buf, sizeof(buf), "%s/%s", pwd, name);
}
else {
full_name = (char *)name;
}
full_name = canonic_path_map_file_name(full_name);
j = strlen(full_name);
if (i <= j && strcmp(file, full_name + j - i) == 0) return 1;
#if SERVICE_PathMap
{
char * s = apply_path_map(chnl, ctx, full_name, PATH_MAP_TO_CLIENT);
if (s != full_name) {
full_name = canonic_path_map_file_name(s);
j = strlen(full_name);
if (i <= j && strcmp(file, full_name + j - i) == 0) return 1;
}
}
#endif
return 0;
}
static LineNumbersState * get_next_in_text(CompUnit * unit, LineNumbersState * state) {
LineNumbersState * next = NULL;
U4_T index = state->mStatesIndexPos + 1;
if (index >= unit->mStatesCnt) return NULL;
next = unit->mStatesIndex[index++];
while (next->mLine == state->mLine && next->mColumn == state->mColumn) {
if (index >= unit->mStatesCnt) return NULL;
next = unit->mStatesIndex[index++];
}
if (state->mFile != next->mFile) return NULL;
return next;
}
static LineNumbersState * get_next_in_code(CompUnit * unit, LineNumbersState * state) {
LineNumbersState * next = state;
if (state->mFlags & LINE_EndSequence) return NULL;
if (state + 1 >= unit->mStates + unit->mStatesCnt) return NULL;
for (;;) {
next++;
if (next->mFile != state->mFile) break;
if (next->mLine != state->mLine) break;
if (next->mColumn != state->mColumn) break;
if (next->mSection != state->mSection) return NULL;
if (next->mFlags != state->mFlags) break;
if (next->mISA != state->mISA) break;
if (next->mOpIndex != state->mOpIndex) break;
if (next->mDiscriminator != state->mDiscriminator) break;
if (next + 1 >= unit->mStates + unit->mStatesCnt) break;
}
return next;
}
static LineNumbersState * get_next_statement(CompUnit * unit, LineNumbersState * state) {
/* Select addreess most suitable for breakpoint planting.
* DWARF 3 standard says:
* "is_stmt: A boolean indicating that the current instruction is a recommended
* breakpoint location. A recommended breakpoint location is intended to
* "represent" a line, a statement and/or a semantically distinct subpart of a
* statement."
*/
LineNumbersState * next = state;
if (next == NULL) return NULL;
if (next->mFlags & LINE_IsStmt) return next;
for (;;) {
U4_T index = next->mStatesIndexPos + 1;
if (index >= unit->mStatesCnt) break;
next = unit->mStatesIndex[index];
if (next->mFile != state->mFile) break;
if (next->mLine != state->mLine) break;
if (next->mFlags & LINE_IsStmt) return next;
}
return NULL;
}
static void call_client(Context * ctx, CompUnit * unit, LineNumbersState * state,
LineNumbersState * code_next, LineNumbersState * text_next,
ContextAddress state_addr, LineNumbersCallBack * client, void * args) {
CodeArea area;
FileInfo * file_info = unit->mFiles + state->mFile;
LineNumbersState * text_next_stmt = get_next_statement(unit, text_next);
if (code_next == NULL) return;
assert(state->mSection == code_next->mSection);
memset(&area, 0, sizeof(area));
area.start_line = state->mLine;
area.start_column = state->mColumn;
area.end_line = text_next ? text_next->mLine : state->mLine + 1;
area.end_column = text_next ? text_next->mColumn : 0;
area.directory = unit->mDir;
if (state->mFileName != NULL) {
area.file = state->mFileName;
}
else if (is_absolute_path(file_info->mName) || file_info->mDir == NULL) {
area.file = file_info->mName;
}
else if (is_absolute_path(file_info->mDir)) {
area.directory = file_info->mDir;
area.file = file_info->mName;
}
else {
char buf[FILE_PATH_SIZE];
snprintf(buf, sizeof(buf), "%s/%s", file_info->mDir, file_info->mName);
area.file = state->mFileName = loc_strdup(buf);
}
area.file_mtime = file_info->mModTime;
area.file_size = file_info->mSize;
area.start_address = state_addr;
area.end_address = code_next->mAddress - state->mAddress + state_addr;
if (text_next != NULL) {
if (text_next->mSection == state->mSection) {
area.next_address = text_next->mAddress - state->mAddress + state_addr;
}
else {
ELF_Section * s = NULL;
if (text_next->mSection) s = unit->mFile->sections + text_next->mSection;
area.next_address = elf_map_to_run_time_address(ctx, unit->mFile, s, text_next->mAddress);
}
}
if (text_next_stmt != NULL) {
if (text_next_stmt->mSection == state->mSection) {
area.next_stmt_address = text_next_stmt->mAddress - state->mAddress + state_addr;
}
else {
ELF_Section * s = NULL;
if (text_next_stmt->mSection) s = unit->mFile->sections + text_next_stmt->mSection;
area.next_stmt_address = elf_map_to_run_time_address(ctx, unit->mFile, s, text_next_stmt->mAddress);
}
}
area.isa = state->mISA;
area.is_statement = (state->mFlags & LINE_IsStmt) != 0;
area.basic_block = (state->mFlags & LINE_BasicBlock) != 0;
area.prologue_end = (state->mFlags & LINE_PrologueEnd) != 0;
area.epilogue_begin = (state->mFlags & LINE_EpilogueBegin) != 0;
area.op_index = state->mOpIndex;
area.discriminator = state->mDiscriminator;
client(&area, args);
}
static void unit_line_to_address(Context * ctx, MemoryRegion * mem, CompUnit * unit,
unsigned file, unsigned line, unsigned column,
LineNumbersCallBack * client, void * args) {
if (unit->mStatesCnt >= 2) {
unsigned l = 0;
unsigned h = unit->mStatesCnt;
while (l < h) {
unsigned k = (h + l) / 2;
LineNumbersState * state = unit->mStatesIndex[k];
if (state->mFile < file) {
l = k + 1;
}
else if (state->mFile > file || state->mLine > line || (column && state->mLine == line && state->mColumn > column)) {
h = k;
}
else {
LineNumbersState * next = get_next_in_text(unit, state);
U4_T next_line = next ? next->mLine : state->mLine + 1;
U4_T next_column = next ? next->mColumn : 0;
if (next_line < line || (column && next_line == line && next_column <= column)) {
l = k + 1;
}
else {
assert(state->mFile == file);
while (k > 0) {
LineNumbersState * prev = unit->mStatesIndex[k - 1];
if (prev->mFile != state->mFile) break;
if (prev->mLine != state->mLine) break;
if (column && prev->mColumn != state->mColumn) break;
state = prev;
k--;
}
for (;;) {
ELF_Section * sec = state->mSection ? unit->mFile->sections + state->mSection : NULL;
ContextAddress addr = elf_run_time_address_in_region(ctx, mem, unit->mFile, sec, state->mAddress);
if (errno == 0) {
LineNumbersState * code_next = get_next_in_code(unit, state);
if (code_next != NULL && state->mAddress < code_next->mAddress) {
LineNumbersState * text_next = get_next_in_text(unit, state);
U4_T next_line = text_next ? text_next->mLine : state->mLine + 1;
U4_T next_column = text_next ? text_next->mColumn : 0;
if (next_line > line || (next_line == line && next_column > column)) {
UNIT_TO_LINE_ADDR_LOCALS_HOOK
assert(state->mLine <= line);
UNIT_TO_LINE_ADDR_HOOK
{
call_client(ctx, unit, state, code_next, text_next, addr, client, args);
}
}
}
}
if (++k >= unit->mStatesCnt) break;
state = unit->mStatesIndex[k];
if (state->mFile > file) break;
if (state->mLine > line) break;
if (column && state->mColumn > column) break;
}
break;
}
}
}
}
}
int line_to_address(Context * ctx, const char * file_name, int line, int column,
LineNumbersCallBack * client, void * args) {
int err = 0;
Channel * chnl = cache_channel();
static MemoryMap map;
LINE_TO_ADDR_HOOK_0
if (ctx == NULL) err = ERR_INV_CONTEXT;
else if (ctx->exited) err = ERR_ALREADY_EXITED;
if (err == 0 && elf_get_map(ctx, 0, ~(ContextAddress)0, &map) < 0) err = errno;
if (err == 0) {
unsigned i, j;
unsigned h = 0;
char * fnm = NULL;
for (i = 0; i < map.region_cnt; i++) {
Trap trap;
MemoryRegion * r = map.regions + i;
ELF_File * file = elf_open_memory_region_file(r, NULL);
if (file == NULL) continue;
if (set_trap(&trap)) {
DWARFCache * cache = get_dwarf_cache(get_dwarf_file(file));
if (!cache->mLineInfoLoaded) {
for (j = 0; j < file->section_cnt; j++) {
ObjectInfo * info = cache->mObjectHashTable[j].mCompUnits;
while (info != NULL) {
CompUnit * unit = info->mCompUnit;
if (!unit->mLineInfoLoaded) load_line_numbers(unit);
info = info->mSibling;
}
}
cache->mLineInfoLoaded = 1;
}
if (cache->mFileInfoHash) {
FileInfo * f = NULL;
if (fnm == NULL) {
fnm = canonic_path_map_file_name(file_name);
LINE_TO_ADDR_HOOK_1
h = calc_file_name_hash(fnm);
}
LINE_TO_ADDR_HOOK_BP
f = cache->mFileInfoHash[h % cache->mFileInfoHashSize];
while (f != NULL) {
if (f->mNameHash == h && compare_path(chnl, ctx, fnm, f->mCompUnit->mDir, f->mDir, f->mName)) {
CompUnit * unit = f->mCompUnit;
unsigned j = f - unit->mFiles;
LINE_TO_ADDR_HOOK_2
unit_line_to_address(ctx, r, unit, j, line, column, client, args);
}
f = f->mNextInHash;
}
}
clear_trap(&trap);
}
else {
err = trap.error;
trace(LOG_ALWAYS, "Cannot load DWARF line numbers section: %s", errno_to_str(err));
break;
}
}
}
if (err != 0) {
errno = err;
return -1;
}
return 0;
}
int address_to_line(Context * ctx, ContextAddress addr0, ContextAddress addr1, LineNumbersCallBack * client, void * args) {
Trap trap;
/* TODO: make addr0..addr1 range inclusive */
if (addr1 == 0) return 0;
addr1--;
if (!set_trap(&trap)) return -1;
if (ctx == NULL) exception(ERR_INV_CONTEXT);
if (ctx->exited) exception(ERR_ALREADY_EXITED);
while (addr0 <= addr1) {
ContextAddress range_rt_addr = 0;
UnitAddressRange * range = elf_find_unit(ctx, addr0, addr1, &range_rt_addr);
if (range == NULL) break;
assert(range->mSize > 0);
assert(range->mAddr + range->mSize > range->mAddr || range->mAddr + range->mSize == 0);
assert(range_rt_addr + range->mSize > range_rt_addr || range_rt_addr + range->mSize == 0);
assert(addr1 >= range_rt_addr);
assert(addr0 <= range_rt_addr + range->mSize - 1);
if (!range->mUnit->mLineInfoLoaded) load_line_numbers(range->mUnit);
if (range->mUnit->mStatesCnt >= 2) {
CompUnit * unit = range->mUnit;
unsigned l = 0;
unsigned h = unit->mStatesCnt;
ContextAddress addr_min = range->mAddr;
ContextAddress addr_max = range->mAddr + range->mSize - 1;
if (addr0 > range_rt_addr) addr_min = addr0 - range_rt_addr + range->mAddr;
if (addr1 < range_rt_addr + range->mSize - 1) addr_max = addr1 - range_rt_addr + range->mAddr;
assert(addr_min >= range->mAddr);
assert(addr_max <= range->mAddr + range->mSize - 1);
while (l < h) {
unsigned k = (h + l) / 2;
LineNumbersState * state = unit->mStates + k;
if (state->mSection > range->mSection) {
h = k;
}
else if (state->mSection < range->mSection) {
l = k + 1;
}
else if (state->mAddress > addr_max) {
h = k;
}
else {
LineNumbersState * next = get_next_in_code(unit, state);
if (next == NULL || next->mAddress <= addr_min) {
l = k + 1;
}
else {
while (k > 0) {
LineNumbersState * prev = unit->mStates + k - 1;
if (state->mAddress <= addr_min) break;
if (prev->mAddress > addr_max) break;
state = prev;
k--;
}
for (;;) {
LineNumbersState * code_next = get_next_in_code(unit, state);
if (code_next != NULL) {
if (state->mAddress < code_next->mAddress) {
LineNumbersState * text_next = get_next_in_text(unit, state);
ADDR_TO_LINE_HOOK
{
call_client(ctx, unit, state, code_next, text_next, state->mAddress - range->mAddr + range_rt_addr, client, args);
}
}
assert(code_next > state);
k = code_next - unit->mStates;
}
else {
k++;
}
if (k >= unit->mStatesCnt) break;
state = unit->mStates + k;
if (state->mAddress > addr_max) break;
}
break;
}
}
}
}
addr0 = range_rt_addr + range->mSize;
}
clear_trap(&trap);
return 0;
}
void ini_line_numbers_lib(void) {
#if ENABLE_LineNumbersMux
add_line_numbers_reader(&line_numbers_reader);
#endif
}
#endif /* SERVICE_LineNumbers && !ENABLE_LineNumbersProxy && ENABLE_ELF */