blob: 039076f277673247bb9432aa81d1b7efec5e736f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2010 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 <config.h>
#if SERVICE_LineNumbers && !ENABLE_LineNumbersProxy && ENABLE_ELF
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <framework/context.h>
#include <framework/myalloc.h>
#include <framework/exceptions.h>
#include <framework/cache.h>
#include <framework/trace.h>
#include <framework/json.h>
#include <framework/protocol.h>
#include <services/linenumbers.h>
#include <services/tcf_elf.h>
#include <services/dwarfio.h>
#include <services/dwarf.h>
#include <services/dwarfcache.h>
#include <services/stacktrace.h>
static int is_absolute_path(char * fnm) {
if (fnm[0] == '/') return 1;
if (fnm[0] == '\\') return 1;
if (fnm[0] != 0 && fnm[1] == ':') {
if (fnm[2] == '/') return 1;
if (fnm[2] == '\\') return 1;
}
return 0;
}
static void canonic_path(char * fnm, char * buf, size_t buf_size) {
unsigned i = 0;
while (i < buf_size - 1) {
char ch = *fnm++;
if (ch == 0) break;
if (ch == '\\') ch = '/';
if (ch == '/' && i >= 2 && buf[i - 1] == '/') continue;
if (ch == '.' && (i == 0 || buf[i - 1] == '/')) {
if (*fnm == '/' || *fnm == '\\') {
fnm++;
continue;
}
if (i > 0 && *fnm == '.' && (fnm[1] == '/' || fnm[1] == '\\')) {
unsigned j = i - 1;
if (j > 0 && buf[j - 1] != '/') {
while (j > 0 && buf[j - 1] != '/') j--;
i = j;
fnm += 2;
continue;
}
}
}
buf[i++] = ch;
}
buf[i++] = 0;
}
static int compare_path(char * file, char * pwd, char * dir, char * name) {
int i, j;
char buf[FILE_PATH_SIZE];
if (file == NULL) return 0;
if (name == NULL) return 0;
if (is_absolute_path(name)) {
canonic_path(name, buf, sizeof(buf));
}
else if (dir != NULL && is_absolute_path(dir)) {
snprintf(buf, sizeof(buf), "%s/%s", dir, name);
canonic_path(buf, buf, sizeof(buf));
}
else if (dir != NULL && pwd != NULL) {
snprintf(buf, sizeof(buf), "%s/%s/%s", pwd, dir, name);
canonic_path(buf, buf, sizeof(buf));
}
else if (pwd != NULL) {
snprintf(buf, sizeof(buf), "%s/%s", pwd, name);
canonic_path(buf, buf, sizeof(buf));
}
else {
canonic_path(name, buf, sizeof(buf));
}
i = strlen(file);
j = strlen(buf);
return i <= j && strcmp(file, buf + j - i) == 0;
}
static LineNumbersState * get_next_in_text(CompUnit * unit, LineNumbersState * state) {
LineNumbersState * next = unit->mStates + state->mNext;
if (state->mNext == 0) return NULL;
while (next->mLine == state->mLine && next->mColumn == state->mColumn) {
if (next->mNext == 0) return NULL;
next = unit->mStates + next->mNext;
}
if (state->mFile != next->mFile) return NULL;
return next;
}
static void call_client(CompUnit * unit, LineNumbersState * state,
ContextAddress state_addr, LineNumbersCallBack * client, void * args) {
CodeArea area;
LineNumbersState * next = get_next_in_text(unit, state);
FileInfo * file_info = unit->mFiles + state->mFile;
if (state->mAddress >= (state + 1)->mAddress) return;
memset(&area, 0, sizeof(area));
area.start_line = state->mLine;
area.start_column = state->mColumn;
area.end_line = next ? next->mLine : state->mLine;
area.end_column = next ? 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 = (state + 1)->mAddress - state->mAddress + state_addr;
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;
client(&area, args);
}
static void unit_line_to_address(Context * ctx, CompUnit * unit, unsigned file, unsigned line, unsigned column,
LineNumbersCallBack * client, void * args) {
if (unit->mStatesCnt >= 2) {
unsigned l = 0;
unsigned h = unit->mStatesCnt - 1;
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) {
h = k;
}
else {
LineNumbersState * next = get_next_in_text(unit, state);
if (next != NULL && next->mLine <= line) {
l = k + 1;
}
else {
unsigned i = k;
while (i > 0) {
LineNumbersState * prev = unit->mStatesIndex[i - 1];
if (prev->mFile != state->mFile) break;
if (prev->mLine != state->mLine) break;
if (prev->mColumn != state->mColumn) break;
state = prev;
i--;
}
for (;;) {
ContextAddress addr = elf_map_to_run_time_address(ctx, unit->mFile, unit->mTextSection, state->mAddress);
if (addr != 0) call_client(unit, state, addr, client, args);
if (i == k) break;
state = unit->mStatesIndex[++i];
}
break;
}
}
}
}
}
int line_to_address(Context * ctx, char * file_name, int line, int column, LineNumbersCallBack * client, void * args) {
int err = 0;
if (ctx == NULL) err = ERR_INV_CONTEXT;
else if (ctx->exited) err = ERR_ALREADY_EXITED;
if (err == 0) {
ELF_File * file = elf_list_first(ctx, 0, ~(ContextAddress)0);
if (file == NULL) err = errno;
if (err == 0) {
unsigned h;
char fnm[FILE_PATH_SIZE];
canonic_path(file_name, fnm, sizeof(fnm));
h = calc_file_name_hash(fnm);
while (file != NULL) {
Trap trap;
/* TODO: support for separate debug info files */
if (set_trap(&trap)) {
DWARFCache * cache = get_dwarf_cache(file);
ObjectInfo * info = cache->mCompUnits;
while (info != NULL) {
CompUnit * unit = info->mCompUnit;
if (!unit->mLineInfoLoaded) load_line_numbers(unit);
info = info->mSibling;
}
if (cache->mFileInfoHash) {
FileInfo * f = cache->mFileInfoHash[h % cache->mFileInfoHashSize];
while (f != NULL) {
if (f->mNameHash == h && compare_path(fnm, f->mCompUnit->mDir, f->mDir, f->mName)) {
CompUnit * unit = f->mCompUnit;
unsigned j = f - unit->mFiles;
unit_line_to_address(ctx, unit, j, line, column, client, args);
}
f = f->mNextInHash;
}
}
clear_trap(&trap);
}
else {
err = trap.error;
break;
}
file = elf_list_next(ctx);
if (file == NULL) err = errno;
}
}
elf_list_done(ctx);
}
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;
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_rt_addr != 0);
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 - 1;
ContextAddress addr_min = addr0 - range_rt_addr + range->mAddr;
ContextAddress addr_max = addr1 - range_rt_addr + range->mAddr;
if (addr_min < range->mAddr) addr_min = range->mAddr;
if (addr_max > range->mAddr + range->mSize) addr_max = range->mAddr + range->mSize;
while (l < h) {
unsigned k = (h + l) / 2;
LineNumbersState * state = unit->mStates + k;
if (state->mAddress >= addr_max) {
h = k;
}
else {
LineNumbersState * next = state + 1;
assert(next->mAddress >= state->mAddress);
if (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;
next = state;
state = prev;
k--;
}
for (;;) {
call_client(unit, state, state->mAddress - range->mAddr + range_rt_addr, client, args);
k++;
if (k >= unit->mStatesCnt - 1) break;
state = unit->mStates + k;
if (state->mAddress >= addr_max) break;
next = state + 1;
}
break;
}
}
}
}
addr0 = range_rt_addr + range->mSize;
}
clear_trap(&trap);
return 0;
}
void ini_line_numbers_lib(void) {
}
#endif /* SERVICE_LineNumbers && !ENABLE_LineNumbersProxy && ENABLE_ELF */