/*******************************************************************************
 * Copyright (c) 2010, 2016 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
 *******************************************************************************/

/* Fake debug context API implementation. It used for testing symbol services. */

#include <tcf/config.h>

#include <sys/stat.h>
#include <assert.h>
#include <stdio.h>
#include <fcntl.h>

#include <tcf/framework/mdep-fs.h>
#include <tcf/framework/context.h>
#include <tcf/framework/events.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/trace.h>

#include <tcf/services/tcf_elf.h>
#include <tcf/services/elf-symbols.h>
#include <tcf/services/symbols.h>
#include <tcf/services/linenumbers.h>
#include <tcf/services/memorymap.h>
#include <tcf/services/dwarfframe.h>
#include <tcf/services/dwarfcache.h>
#include <tcf/services/stacktrace.h>
#include <tcf/services/expressions.h>
#include <tcf/services/dwarf.h>

#include <tcf/backend/backend.h>

#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif

#define MAX_REGS 2000

struct RegisterData {
    uint8_t data[MAX_REGS * 8];
    uint8_t mask[MAX_REGS * 8];
};

static Context * elf_ctx = NULL;
static MemoryMap mem_map;
static RegisterDefinition reg_defs[MAX_REGS];
static char reg_names[MAX_REGS][32];
static uint8_t reg_vals[MAX_REGS * 8];
static unsigned reg_size = 0;

static uint8_t frame_data[0x1000];
static ContextAddress frame_addr = 0x40000000u;

static const char * elf_file_name = NULL;
static ELF_File * elf_file = NULL;
static ContextAddress file_addr_offs = 0;
static int mem_region_pos = 0;
static int file_has_line_info = 0;
static unsigned line_info_cnt = 0;
static ContextAddress pc = 0;
static unsigned pass_cnt = 0;
static int test_posted = 0;
static struct timespec time_start;

static char ** files = NULL;
static unsigned files_max = 0;
static unsigned files_cnt = 0;

static int line_area_ok = 0;

#define AREA_BUF_SIZE 0x100
static CodeArea area_buf[AREA_BUF_SIZE];
static unsigned area_cnt = 0;


extern ObjectInfo * get_symbol_object(Symbol * sym);

static RegisterDefinition * get_reg_by_dwarf_id(unsigned id) {
    static RegisterDefinition ** map = NULL;
    static unsigned map_length = 0;

    if (map == NULL) {
        RegisterDefinition * r;
        RegisterDefinition * regs_index = get_reg_definitions(NULL);
        for (r = regs_index; r->name != NULL; r++) {
            if (r->dwarf_id >= (int)map_length) map_length = r->dwarf_id + 1;
        }
        map = (RegisterDefinition **)loc_alloc_zero(sizeof(RegisterDefinition *) * map_length);
        for (r = regs_index; r->name != NULL; r++) {
            if (r->dwarf_id >= 0) map[r->dwarf_id] = r;
        }
    }
    return id < map_length ? map[id] : NULL;
}

static RegisterDefinition * get_reg_by_eh_frame_id(unsigned id) {
    static RegisterDefinition ** map = NULL;
    static unsigned map_length = 0;

    if (map == NULL) {
        RegisterDefinition * r;
        RegisterDefinition * regs_index = get_reg_definitions(NULL);
        for (r = regs_index; r->name != NULL; r++) {
            if (r->eh_frame_id >= (int)map_length) map_length = r->eh_frame_id + 1;
        }
        map = (RegisterDefinition **)loc_alloc_zero(sizeof(RegisterDefinition *) * map_length);
        for (r = regs_index; r->name != NULL; r++) {
            if (r->eh_frame_id >= 0) map[r->eh_frame_id] = r;
        }
    }
    return id < map_length ? map[id] : NULL;
}

RegisterDefinition * get_reg_by_id(Context * ctx, unsigned id, RegisterIdScope * scope) {
    RegisterDefinition * def = NULL;
    switch (scope->id_type) {
    case REGNUM_DWARF: def = get_reg_by_dwarf_id(id); break;
    case REGNUM_EH_FRAME: def = get_reg_by_eh_frame_id(id); break;
    }
    if (def == NULL) set_errno(ERR_OTHER, "Invalid register ID");
    return def;
}

int read_reg_bytes(StackFrame * frame, RegisterDefinition * reg_def, unsigned offs, unsigned size, uint8_t * buf) {
    if (reg_def != NULL && frame != NULL) {
        if (frame->is_top_frame || frame->regs == NULL) {
            return context_read_reg(frame->ctx, reg_def, offs, size, buf);
        }
        if (frame->regs != NULL) {
            uint8_t * r_addr = (uint8_t *)&frame->regs->data + reg_def->offset;
            uint8_t * m_addr = (uint8_t *)&frame->regs->mask + reg_def->offset;
            size_t i;
            for (i = 0; i < size; i++) {
                if (m_addr[offs + i] != 0xff) {
                    return context_read_reg(frame->ctx, reg_def, offs, size, buf);
                }
            }
            if (offs + size > reg_def->size) {
                errno = ERR_INV_DATA_SIZE;
                return -1;
            }
            memcpy(buf, r_addr + offs, size);
            return 0;
        }
    }
    errno = ERR_INV_CONTEXT;
    return -1;
}

int write_reg_bytes(StackFrame * frame, RegisterDefinition * reg_def, unsigned offs, unsigned size, uint8_t * buf) {
    if (reg_def != NULL && frame != NULL) {
        if (frame->is_top_frame) {
            return context_write_reg(frame->ctx, reg_def, offs, size, buf);
        }
        if (frame->regs == NULL && context_has_state(frame->ctx)) {
            frame->regs = (RegisterData *)loc_alloc_zero(sizeof(RegisterData));
        }
        if (frame->regs != NULL) {
            uint8_t * r_addr = (uint8_t *)&frame->regs->data + reg_def->offset;
            uint8_t * m_addr = (uint8_t *)&frame->regs->mask + reg_def->offset;

            if (offs + size > reg_def->size) {
                errno = ERR_INV_DATA_SIZE;
                return -1;
            }
            memcpy(r_addr + offs, buf, size);
            memset(m_addr + offs, 0xff, size);
            return 0;
        }
    }
    errno = ERR_INV_CONTEXT;
    return -1;
}

RegisterDefinition * get_reg_definitions(Context * ctx) {
    return reg_defs;
}

RegisterDefinition * get_PC_definition(Context * ctx) {
    return reg_defs;
}

Context * id2ctx(const char * id) {
    if (id != NULL && strcmp(id, elf_ctx->id) == 0) return elf_ctx;
    return NULL;
}

unsigned context_word_size(Context * ctx) {
    return get_PC_definition(ctx)->size;
}

int context_has_state(Context * ctx) {
    return 1;
}

Context * context_get_group(Context * ctx, int group) {
    return ctx;
}

int context_read_reg(Context * ctx, RegisterDefinition * def, unsigned offs, unsigned size, void * buf) {
    if (ctx != elf_ctx) {
        errno = ERR_INV_CONTEXT;
        return -1;
    }
    memcpy(buf, reg_vals + def->offset + offs, size);
    return 0;
}

int context_write_reg(Context * ctx, RegisterDefinition * def, unsigned offs, unsigned size, void * buf) {
    if (ctx != elf_ctx) {
        errno = ERR_INV_CONTEXT;
        return -1;
    }
    memcpy(reg_vals + def->offset + offs, buf, size);
    return 0;
}

int context_read_mem(Context * ctx, ContextAddress address, void * buf, size_t size) {
    if (address >= frame_addr && address + size >= address && address + size <= frame_addr + sizeof(frame_data)) {
        memcpy(buf, frame_data + (address - frame_addr), size);
        return 0;
    }
    memset(buf, 0, size);
    return 0;
}

int context_write_mem(Context * ctx, ContextAddress address, void * buf, size_t size) {
    /* TODO: context_write_mem */
    errno = ERR_UNSUPPORTED;
    return -1;
}

int context_get_memory_map(Context * ctx, MemoryMap * map) {
    unsigned i;
    for (i = 0; i < mem_map.region_cnt; i++) {
        MemoryRegion * r = NULL;
        if (map->region_cnt >= map->region_max) {
            map->region_max += 8;
            map->regions = (MemoryRegion *)loc_realloc(map->regions, sizeof(MemoryRegion) * map->region_max);
        }
        r = map->regions + map->region_cnt++;
        *r = mem_map.regions[i];
        if (r->file_name) r->file_name = loc_strdup(r->file_name);
        if (r->sect_name) r->sect_name = loc_strdup(r->sect_name);
    }
    return 0;
}

int crawl_stack_frame(StackFrame * frame, StackFrame * down) {
    frame->fp = frame_addr;
    down->has_reg_data = 1;
    return 0;
}

static void print_symbol(Symbol * sym) {
    ObjectInfo * obj = get_symbol_object(sym);
    if (obj == NULL) {
        printf("Object  : NULL\n");
    }
    else {
        printf("Object  : 0x%" PRIX64 "\n", (uint64_t)obj->mID);
        printf("  Tag   : 0x%02x\n", obj->mTag);
        printf("  Flags : 0x%02x\n", obj->mFlags);
        if (obj->mName != NULL) {
            printf("  Name  : %s\n", obj->mName);
        }
        if (obj->mType != NULL) {
            printf("  Type  : 0x%" PRIX64 "\n", (uint64_t)obj->mType->mID);
        }
        if (obj->mDefinition != NULL) {
            printf("  Def   : 0x%" PRIX64 "\n", (uint64_t)obj->mDefinition->mID);
        }
    }
}

static void error(const char * func) {
    int err = errno;
    printf("File    : %s\n", elf_file_name);
    if (elf_open(elf_file_name)->debug_info_file_name) {
        printf("Symbols : %s\n", elf_open(elf_file_name)->debug_info_file_name);
    }
    printf("Address : 0x%" PRIX64 "\n", (uint64_t)pc);
    printf("Function: %s\n", func);
    printf("Error   : %s\n", errno_to_str(err));
    fflush(stdout);
    exit(1);
}

static void error_sym(const char * func, Symbol * sym) {
    int err = errno;
    print_symbol(sym);
    errno = err;
    error(func);
}

static void addr_to_line_callback(CodeArea * area, void * args) {
    if (area->start_address > pc || area->end_address <= pc) {
        errno = set_errno(ERR_OTHER, "Invalid line area address");
        error("address_to_line");
    }
    if (area->start_line > area->end_line) {
        errno = set_errno(ERR_OTHER, "Invalid line area end line number");
        error("address_to_line");
    }
    if (area->next_address != 0 &&
            area->next_address < area->end_address &&
            area->next_address >= area->start_address) {
        errno = set_errno(ERR_OTHER, "Invalid line area end address");
        error("address_to_line");
    }
    if (area->end_address - area->start_address >= 0x100000) {
        errno = set_errno(ERR_OTHER, "Invalid line area end address");
        error("address_to_line");
    }
    if (area_cnt < AREA_BUF_SIZE) area_buf[area_cnt++] = *area;
}

static void addr_to_line_callback_p1(CodeArea * area, void * args) {
    if (area_cnt < AREA_BUF_SIZE) area_buf[area_cnt++] = *area;
}

static void line_to_addr_callback(CodeArea * area, void * args) {
    CodeArea * org = (CodeArea *)args;
    if (area->start_line > org->start_line || (area->start_line == org->start_line && area->start_column > org->start_column) ||
        area->end_line < org->start_line || (area->end_line == org->start_line && area->end_column <= org->start_column)) {
        errno = set_errno(ERR_OTHER, "Invalid line area line numbers");
        error("line_to_address");
    }
    if (area->start_address > pc || area->end_address <= pc) return;
    if (org->start_address == area->start_address || org->end_address == area->end_address) {
        line_area_ok = 1;
    }
}

static void print_time(struct timespec time_start, int cnt) {
    struct timespec time_now;
    struct timespec time_diff;
    if (cnt == 0) return;
    clock_gettime(CLOCK_REALTIME, &time_now);
    time_diff.tv_sec = time_now.tv_sec - time_start.tv_sec;
    if (time_now.tv_nsec < time_start.tv_nsec) {
        time_diff.tv_sec--;
        time_diff.tv_nsec = time_now.tv_nsec + 1000000000 - time_start.tv_nsec;
    }
    else {
        time_diff.tv_nsec = time_now.tv_nsec - time_start.tv_nsec;
    }
    time_diff.tv_nsec /= cnt;
    time_diff.tv_nsec += (long)(((uint64_t)(time_diff.tv_sec % cnt) * 1000000000) / cnt);
    time_diff.tv_sec /= cnt;
    printf("search time: %ld.%06ld\n", (long)time_diff.tv_sec, time_diff.tv_nsec / 1000);
    fflush(stdout);
}

static int symcmp(Symbol * x, Symbol * y) {
    char id[256];
    strcpy(id, symbol2id(x));
    return strcmp(id, symbol2id(y));
}

static int errcmp(int err, const char * msg) {
    const char * txt = errno_to_str(err);
    size_t msg_len = strlen(msg);
    size_t txt_len = strlen(txt);
    unsigned i;
    for (i = 0; txt_len - i >= msg_len; i++) {
       if (strncmp(txt + i, msg, msg_len) == 0) return 0;
    }
    return 1;
}

static char * esc(char * s) {
    size_t n = 0;
    size_t i = 0;
    char * t = s;
    for (i = 0; s[i]; i++) {
        if (s[i] == '\\') n++;
    }
    if (n > 0) {
        size_t sz = i + n + 1;
        t = (char *)tmp_alloc(sz);
        for (i = 0, n = 0; s[i]; i++) {
            t[n++] = s[i];
            if (s[i] == '\\') t[n++] = '\\';
        }
        t[n++] = 0;
        assert(n == sz);
    }
    return t;
}

static void test(void * args);
static void loc_var_func(void * args, Symbol * sym);

static int is_cpp_reference(Symbol * type) {
    int type_class = 0;
    if (type == NULL) return 0;
    if (get_symbol_type_class(type, &type_class) < 0) {
        error_sym("get_symbol_type_class", type);
    }
    if (type_class == TYPE_CLASS_POINTER) {
        Symbol * ptr = type;
        for (;;) {
            SYM_FLAGS ptr_flags = 0;
            Symbol * next = NULL;
            if (get_symbol_flags(ptr, &ptr_flags) < 0) {
                error_sym("get_symbol_flags", ptr);
            }
            if (ptr_flags & SYM_FLAG_REFERENCE) {
                return 1;
            }
            if (get_symbol_type(ptr, &next) < 0) {
                error_sym("get_symbol_type", ptr);
            }
            if (next == ptr) break;
            ptr = next;
        }
    }
    return 0;
}

static int is_indirect(Symbol * type) {
    if (type == NULL) return 0;
    for (;;) {
        SYM_FLAGS flags = 0;
        Symbol * next = NULL;
        if (get_symbol_flags(type, &flags) < 0) {
            error_sym("get_symbol_flags", type);
        }
        if (flags & SYM_FLAG_INDIRECT) {
            return 1;
        }
        if (get_symbol_type(type, &next) < 0) {
            error_sym("get_symbol_type", type);
        }
        if (next == type) break;
        type = next;
    }
    return 0;
}

static void test_enumeration_type(Symbol * type) {
    int i;
    int count = 0;
    Symbol ** children = NULL;
    Symbol * enum_type = type;
    ContextAddress enum_type_size = 0;
    char * type_name = NULL;
    Symbol * find_sym = NULL;
    int visible = 0;

    for (i = 0;; i++) {
        SYM_FLAGS enum_flags = 0;
        if (get_symbol_flags(enum_type, &enum_flags) < 0) {
            error_sym("get_symbol_flags", enum_type);
        }
        if ((enum_flags & SYM_FLAG_ENUM_TYPE) != 0) break;
        if ((enum_flags & SYM_FLAG_BOOL_TYPE) != 0) break;
        if (get_symbol_type(enum_type, &enum_type) < 0) {
            error_sym("get_symbol_type", enum_type);
        }
        if (i >= 1000) {
            errno = ERR_OTHER;
            error("Invalid original type for enumeration type class");
        }
    }
    if (get_symbol_size(enum_type, &enum_type_size) < 0) {
        error_sym("get_symbol_size", enum_type);
    }
    if (get_symbol_children(type, &children, &count) < 0) {
        error_sym("get_symbol_children", type);
    }
    if (get_symbol_name(type, &type_name) < 0) {
        error_sym("get_symbol_name", type);
    }
    if (type_name != NULL && find_symbol_by_name(elf_ctx, STACK_TOP_FRAME, 0, type_name, &find_sym) == 0) {
        visible = symcmp(type, find_sym) == 0;
    }
    for (i = 0; i < count; i++) {
        Symbol * child_type = NULL;
        void * value = NULL;
        size_t value_size = 0;
        int big_endian = 0;
        ContextAddress child_size = 0;
        char * name = NULL;
        if (get_symbol_value(children[i], &value, &value_size, &big_endian) < 0) {
            error_sym("get_symbol_value", children[i]);
        }
        if (get_symbol_type(children[i], &child_type) < 0) {
            error_sym("get_symbol_type", children[i]);
        }
        if (symcmp(enum_type, child_type) != 0) {
            errno = ERR_OTHER;
            error("Invalid type of enum element");
        }
        if (get_symbol_size(children[i], &child_size) < 0) {
            error_sym("get_symbol_size", children[i]);
        }
        if (enum_type_size != child_size) {
            errno = ERR_OTHER;
            error("Invalid size of enumeration constant");
        }
        if (value_size != child_size) {
            errno = ERR_OTHER;
            error("Invalid size of enumeration constant");
        }
        if (get_symbol_name(children[i], &name) < 0) {
            error_sym("get_symbol_name", children[i]);
        }
        if (visible && name != NULL && get_symbol_object(children[i]) != NULL) {
            if (find_symbol_by_name(elf_ctx, STACK_TOP_FRAME, 0, name, &find_sym) < 0) {
                error_sym("find_symbol_by_name", children[i]);
            }
#if 0
            /* TODO: better enum const search */
            if (symcmp(children[i], find_sym) != 0) {
                errno = ERR_OTHER;
                error_sym("find_symbol_by_name", children[i]);
            }
#endif
        }
    }
}

static void test_variant_part(Symbol * part) {
    int i;
    int count = 0;
    Symbol ** children = NULL;

    if (get_symbol_children(part, &children, &count) < 0) {
        error_sym("get_symbol_children", part);
    }
    for (i = 0; i < count; i++) {
        int member_class = 0;
        Symbol * member_container = NULL;
        LocationInfo * loc_info = NULL;
        if (get_symbol_class(children[i], &member_class) < 0) {
            error_sym("get_symbol_class", children[i]);
        }
        if (get_symbol_container(children[i], &member_container) < 0) {
            error_sym("get_symbol_container", children[i]);
        }
        if (get_location_info(children[i], &loc_info) < 0) {
            error_sym("get_location_info", children[i]);
        }
    }
}

static void test_composite_type(Symbol * type) {
    int i;
    int count = 0;
    char * type_name = NULL;
    Symbol * org_type = type;
    Symbol ** children = NULL;
    for (;;) {
        SYM_FLAGS flags = 0;
        if (get_symbol_flags(org_type, &flags) < 0) {
            error("get_symbol_flags");
        }
        if ((flags & SYM_FLAG_TYPEDEF) == 0) break;
        if (get_symbol_type(org_type, &org_type) < 0) {
            error("get_symbol_base_type");
        }
    }
    if (get_symbol_name(org_type, &type_name) < 0) {
        error("get_symbol_name");
    }
    if (get_symbol_children(type, &children, &count) < 0) {
        error_sym("get_symbol_children", type);
    }
    for (i = 0; i < count; i++) {
        int member_class = 0;
        Symbol * member_container = NULL;
        int container_class = 0;
        ContextAddress offs = 0;
        ContextAddress size = 0;
        ContextAddress length = 0;
        if (get_symbol_class(children[i], &member_class) < 0) {
            error_sym("get_symbol_class", children[i]);
        }
        if (get_symbol_container(children[i], &member_container) < 0) {
            error_sym("get_symbol_container", children[i]);
        }
        if (get_symbol_class(member_container, &container_class) < 0) {
            error_sym("get_symbol_class", member_container);
        }
        if (container_class != SYM_CLASS_TYPE) {
            errno = ERR_OTHER;
            error_sym("Invalid result of get_symbol_container()", children[i]);
        }
        if (type_name != NULL) {
            char * container_name = NULL;
            if (get_symbol_name(member_container, &container_name) < 0) {
                error_sym("get_symbol_name", member_container);
            }
            if (container_name == NULL || strcmp(container_name, type_name) != 0) {
                errno = ERR_OTHER;
                error_sym("Invalid result of get_symbol_container()", children[i]);
            }
        }
        if (member_class == SYM_CLASS_REFERENCE) {
            Symbol * member_type = NULL;
            int member_type_class  = 0;
            if (get_symbol_type(children[i], &member_type) < 0) {
                error_sym("get_symbol_type", children[i]);
            }
            if (get_symbol_type_class(children[i], &member_type_class) < 0) {
                error_sym("get_symbol_type_class", children[i]);
            }
            if (get_symbol_address(children[i], &offs) < 0) {
                if (get_symbol_offset(children[i], &offs) < 0) {
#if 0
                    int ok = 0;
                    int err = errno;
                    unsigned type_flags;
                    if (get_symbol_flags(children[i], &type_flags) < 0) {
                        error("get_symbol_flags");
                    }
                    if (type_flags & SYM_FLAG_EXTERNAL) ok = 1;
                    if (!ok) {
                        errno = err;
                        error("get_symbol_offset");
                    }
#endif
                }
                else if (!is_cpp_reference(member_type)) {
                    Value v;
                    uint64_t n = 0;
                    char * expr = (char *)tmp_alloc(512);
                    unsigned base = (rand() & 0xffff) << 4;
                    sprintf(expr, "&(((${%s} *)%u)->${%s})", tmp_strdup(symbol2id(type)), base, tmp_strdup(symbol2id(children[i])));
                    if (evaluate_expression(elf_ctx, STACK_TOP_FRAME, 0, expr, 0, &v) < 0) {
                        error("evaluate_expression");
                    }
                    if (value_to_unsigned(&v, &n) < 0) {
                        error("value_to_unsigned");
                    }
                    if (n != base + offs) {
                        errno = ERR_OTHER;
                        error("invalid result of evaluate_expression");
                    }
                }
            }
            if (get_symbol_size(children[i], &size) < 0) {
                error_sym("get_symbol_size", children[i]);
            }
            if (member_type_class == TYPE_CLASS_ARRAY && get_symbol_length(children[i], &length) < 0) {
                error_sym("get_symbol_length", children[i]);
            }
        }
        else if (member_class == SYM_CLASS_VALUE) {
            void * value = NULL;
            size_t value_size = 0;
            int big_endian = 0;
            if (get_symbol_value(children[i], &value, &value_size, &big_endian) < 0) {
                error_sym("get_symbol_value", children[i]);
            }
        }
        else if (member_class == SYM_CLASS_VARIANT_PART) {
            test_variant_part(children[i]);
        }
    }
}

static void test_this_pointer(Symbol * base_type) {
    int i;
    int count = 0;
    Symbol ** children = NULL;
    if (get_symbol_children(base_type, &children, &count) < 0) {
        error_sym("get_symbol_children", base_type);
    }
    for (i = 0; i < count; i++) {
        int member_class = 0;
        char * member_name = NULL;
        if (get_symbol_class(children[i], &member_class) < 0) {
            error_sym("get_symbol_class", children[i]);
        }
        if (get_symbol_name(children[i], &member_name) < 0) {
            error_sym("get_symbol_name", children[i]);
        }
        if (member_name != NULL) {
            Symbol * impl_this = NULL;
            if (find_symbol_by_name(elf_ctx, STACK_TOP_FRAME, pc, member_name, &impl_this) < 0) {
                error_sym("find_symbol_by_name", children[i]);
            }
            loc_var_func(NULL, impl_this);
        }
    }
}

static void test_implicit_pointer(Symbol * sym) {
    Trap trap;
    StackFrame * frame_info = NULL;
    LocationInfo * loc_info = NULL;
    const char * id = symbol2id(sym);
    LocationExpressionState * state = NULL;
    int type_class = 0;
    Symbol * type = NULL;
    int cpp_ref = 0; /* '1' if the symbol is C++ reference */
    Value v;

    if (get_symbol_type(sym, &type) < 0) {
        error_sym("get_symbol_type", sym);
    }
    if (type != NULL) {
        if (get_symbol_type_class(sym, &type_class) < 0) {
            error_sym("get_symbol_type_class", sym);
        }
        cpp_ref = is_cpp_reference(type);
    }

    if (get_location_info(sym, &loc_info) < 0) {
        error_sym("get_location_info", sym);
    }
    else if (get_frame_info(elf_ctx, STACK_TOP_FRAME, &frame_info) < 0) {
        error("get_frame_info");
    }
    assert(loc_info->value_cmds.cnt > 0);
    assert(loc_info->code_size == 0 || (loc_info->code_addr <= pc && loc_info->code_addr + loc_info->code_size > pc));
    if (set_trap(&trap)) {
        unsigned i;
        unsigned implicit_pointer = 0;
        state = evaluate_location_expression(elf_ctx, frame_info,
            loc_info->value_cmds.cmds, loc_info->value_cmds.cnt, NULL, 0);
        if (state->pieces_cnt == 0) {
            errno = set_errno(ERR_OTHER, "Expected pieces");
            error("evaluate_location_expression");
        }
        for (i = 0; i < state->pieces_cnt; i++) {
            implicit_pointer += state->pieces[i].implicit_pointer;
        }
        if (implicit_pointer == 0) {
            errno = set_errno(ERR_OTHER, "Expected implicit pointer");
            error("evaluate_location_expression");
        }
        clear_trap(&trap);
    }
    else {
        error("evaluate_location_expression");
    }

    if (state->pieces_cnt == 1 && !cpp_ref) {
        char * expr = (char *)tmp_alloc(strlen(id) + 16);
        sprintf(expr, "${%s}", id);
        if (evaluate_expression(elf_ctx, STACK_TOP_FRAME, pc, expr, 0, &v) < 0) {
            error_sym("evaluate_expression", sym);
        }
        if (v.loc->pieces->implicit_pointer != state->pieces->implicit_pointer) {
            errno = set_errno(ERR_OTHER, "Invalid implicit_pointer");
            error("evaluate_expression");
        }

        sprintf(expr, "*${%s}", id);
        if (evaluate_expression(elf_ctx, STACK_TOP_FRAME, pc, expr, 0, &v) < 0) {
            error_sym("evaluate_expression", sym);
        }
        if (v.loc && v.loc->pieces_cnt > 0) {
            if (v.loc->pieces->implicit_pointer != state->pieces->implicit_pointer - 1) {
                errno = set_errno(ERR_OTHER, "Invalid implicit_pointer");
                error("evaluate_expression");
            }
        }
        else {
            if (state->pieces->implicit_pointer != 1) {
                errno = set_errno(ERR_OTHER, "Invalid implicit_pointer");
                error("evaluate_expression");
            }
        }
    }
}

static unsigned get_bit_stride(Symbol * type) {
    for (;;) {
        Symbol * next = NULL;
        SymbolProperties props;
        memset(&props, 0, sizeof(props));
        if (get_symbol_props(type, &props) < 0) {
            error_sym("get_symbol_props", type);
        }
        if (props.bit_stride) return props.bit_stride;
        if (get_symbol_type(type, &next) < 0) {
            error_sym("get_symbol_type", type);
        }
        if (next == type) break;
        type = next;
    }
    return 0;
}

static void loc_var_func(void * args, Symbol * sym) {
    int frame = 0;
    Context * ctx = NULL;
    RegisterDefinition * reg = NULL;
    ContextAddress addr = 0;
    ContextAddress size = 0;
    ContextAddress addr_proxy = 0;
    ContextAddress size_proxy = 0;
    Symbol * sym_proxy = NULL;
    int addr_ok = 0;
    int size_ok = 0;
    SYM_FLAGS flags = 0;
    int symbol_class = 0;
    int type_class = 0;
    Symbol * type = NULL;
    Symbol * index_type = NULL;
    int cpp_ref = 0; /* '1' if the symbol is C++ reference */
    int indirect = 0;
    int ref_size_ok = 0;
    ContextAddress length = 0;
    int64_t lower_bound = 0;
    void * value = NULL;
    size_t value_size = 0;
    int value_big_endian = 0;
    char * name = NULL;
    char * type_name = NULL;
    StackFrame * frame_info = NULL;
    LocationInfo * loc_info = NULL;
    LocationExpressionState * loc_state = NULL;
    UnitAddressRange * unit_range = (UnitAddressRange *)args;
    Symbol * sym_container = NULL;
    int out_of_body = 0;

    if (id2symbol(symbol2id(sym), &sym_proxy) < 0) {
        error_sym("id2symbol", sym);
    }
    if (symcmp(sym, sym_proxy) != 0) {
        errno = ERR_OTHER;
        error("Invalid result of id2symbol()");
    }
    if (get_symbol_class(sym, &symbol_class) < 0) {
        error_sym("get_symbol_class", sym);
    }
    if (get_symbol_flags(sym, &flags) < 0) {
        error_sym("get_symbol_flags", sym);
    }
    if (get_symbol_object(sym) != NULL && get_symbol_container(sym, &sym_container) < 0) {
        error_sym("get_symbol_container", sym);
    }
    if (get_symbol_name(sym, &name) < 0) {
        error_sym("get_symbol_name", sym);
    }
    /* Check for out-of-body definition */
    out_of_body = sym_container != NULL && get_symbol_object(sym)->mParent != get_symbol_object(sym_container);
    if (!out_of_body && name != NULL) {
        int found_next = 0;
        int search_in_scope = 0;
        Symbol * find_sym = NULL;
        name = tmp_strdup(name);
        if (find_symbol_by_name(elf_ctx, STACK_TOP_FRAME, 0, name, &find_sym) < 0) {
            error("find_symbol_by_name");
        }
        for (;;) {
            Symbol * find_next = NULL;
            if (find_next_symbol(&find_next) < 0) {
                if (get_error_code(errno) != ERR_SYM_NOT_FOUND) {
                    error("find_next_symbol");
                }
                break;
            }
            else if (symcmp(find_sym, find_next) == 0) {
                errno = ERR_OTHER;
                error("Invalid result of find_next_symbol()");
            }
            else if (symcmp(sym, find_next) == 0) {
                found_next = 1;
            }
        }
        if (get_symbol_object(sym) != NULL && symcmp(sym, find_sym) != 0) {
            ObjectInfo * obj = get_symbol_object(sym);
            if (unit_range == NULL) search_in_scope = 0;
            else if (unit_range->mUnit != obj->mCompUnit) search_in_scope = 0;
            else if (obj->mFlags & DOIF_pub_mark) search_in_scope = 0;
            else if (obj == get_symbol_object(find_sym)) search_in_scope = 0;
            else search_in_scope = 1;
        }
        if (search_in_scope) {
            /* 'sym' is eclipsed in the current scope by a nested declaration */
            Symbol * find_container = NULL;
            int find_container_class = 0;
            if (!found_next) {
                find_symbol_by_name(elf_ctx, STACK_TOP_FRAME, 0, name, &find_sym);
                errno = ERR_OTHER;
                error_sym("Invalid result of find_next_symbol()", find_sym);
            }
            found_next = 0;
            if (find_symbol_in_scope(elf_ctx, STACK_TOP_FRAME, 0, sym_container, name, &find_sym) < 0) {
                error("find_symbol_in_scope");
            }
            if (get_symbol_container(find_sym, &find_container) < 0) {
                error_sym("get_symbol_container", find_sym);
            }
            if (get_symbol_class(find_container, &find_container_class) < 0) {
                error_sym("get_symbol_class", find_container);
            }
            if (find_container_class != SYM_CLASS_NAMESPACE) {
                if (get_symbol_object(sym_container) != get_symbol_object(find_container)) {
                    errno = ERR_OTHER;
                    error("Invalid result of find_symbol_in_scope()");
                }
            }
            for (;;) {
                Symbol * find_next = NULL;
                if (find_next_symbol(&find_next) < 0) {
                    if (get_error_code(errno) != ERR_SYM_NOT_FOUND) {
                        error("find_next_symbol");
                    }
                    break;
                }
                else if (symcmp(find_sym, find_next) == 0) {
                    errno = ERR_OTHER;
                    error("Invalid result of find_next_symbol()");
                }
                else if (symcmp(sym, find_next) == 0) {
                    found_next = 1;
                }
                if (get_symbol_container(find_next, &find_container) < 0) {
                    error("get_symbol_container");
                }
                if (get_symbol_class(find_container, &find_container_class) < 0) {
                    error_sym("get_symbol_class", find_container);
                }
                if (find_container_class != SYM_CLASS_NAMESPACE) {
                    if (get_symbol_object(sym_container) != get_symbol_object(find_container)) {
                        errno = ERR_OTHER;
                        error("Invalid result of find_next_symbol()");
                    }
                }
            }
            if (symcmp(sym, find_sym) != 0) {
                if (!found_next) {
                    errno = ERR_OTHER;
                    //error("Invalid result of find_next_symbol()");
                }
            }
        }
    }
    if (get_symbol_address(sym, &addr) < 0 &&
            (get_symbol_register(sym, &ctx, &frame, &reg) < 0 || reg == NULL) &&
            (get_symbol_value(sym, &value, &value_size, &value_big_endian) < 0 || value == NULL)) {
        int err = errno;
        ObjectInfo * obj = get_symbol_object(sym);
        if (errcmp(err, "No object location info found") == 0) return;
        if (obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
            if (errcmp(err, "Object does not have location information") == 0) return;
        }
        if (symbol_class == SYM_CLASS_TYPE && obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
            if (errcmp(err, "Invalid address of containing object") == 0) return;
        }
        if (errcmp(err, "Object is not available at this location") == 0) return;
        if (errcmp(err, "OP_fbreg: cannot read AT_frame_base") == 0) return;
        if (errcmp(err, "OP_implicit_pointer: invalid object reference") == 0) return;
        if (errcmp(err, "Thread local storage access is not supported yet for machine type") == 0) return;
        if (errcmp(err, "Division by zero in location") == 0) return;
        if (errcmp(err, "Cannot find loader debug") == 0) return;
        if (errcmp(err, "Cannot get TLS module ID") == 0) return;
        if (errcmp(err, "Cannot get address of ELF symbol: indirect symbol") == 0) return;
        if (errcmp(err, "Unsupported type in OP_GNU_const_type") == 0) return;
        if (errcmp(err, "Unsupported type in OP_GNU_regval_type") == 0) return;
        if (errcmp(err, "Unsupported type in OP_GNU_convert") == 0) return;
        if (errcmp(err, "Invalid size of implicit value") == 0) return;
        if (errcmp(err, "Invalid implicit pointer") == 0) return;
        if (errcmp(err, "Cannot get symbol value: optimized away") == 0) return;
        if (errcmp(err, "Cannot get symbol value: implicit pointer") == 0) {
            test_implicit_pointer(sym);
            return;
        }
        if (symbol_class == SYM_CLASS_TYPE && errcmp(err, "Wrong object kind") == 0) return;
        if (out_of_body && errcmp(err, "Object location is relative to owner") == 0) return;
        errno = err;
        error_sym("get_symbol_value", sym);
    }
    else if (get_location_info(sym, &loc_info) < 0) {
        error_sym("get_location_info", sym);
    }
    else if (get_frame_info(elf_ctx, STACK_TOP_FRAME, &frame_info) < 0) {
        error("get_frame_info");
    }
    else {
        Trap trap;
        assert(loc_info->value_cmds.cnt > 0);
        assert(loc_info->code_size == 0 || (loc_info->code_addr <= pc && loc_info->code_addr + loc_info->code_size > pc));
        if (set_trap(&trap)) {
            uint64_t loc_addr = 0;
            loc_state = evaluate_location_expression(elf_ctx, frame_info,
                loc_info->value_cmds.cmds, loc_info->value_cmds.cnt, NULL, 0);
            if (loc_state->stk_pos == 1) {
                loc_addr = loc_state->stk[0];
                addr_ok = 1;
            }
            else if (loc_state->pieces_cnt == 1 &&
                    loc_state->pieces->implicit_pointer == 0 && loc_state->pieces->optimized_away == 0 &&
                    loc_state->pieces->reg == NULL && loc_state->pieces->value == NULL && loc_state->pieces->bit_offs == 0) {
                loc_addr = loc_state->pieces->addr;
                addr_ok = 1;
            }
            clear_trap(&trap);
            if (addr_ok && loc_addr != addr) str_fmt_exception(ERR_OTHER,
                "ID 0x%" PRIX64 ": invalid location expression result 0x%" PRIX64 " != 0x%" PRIX64,
                get_symbol_object(sym)->mID, loc_addr, addr);
        }
        else {
            error("evaluate_location_expression");
        }
    }
    if (get_symbol_type(sym, &type) < 0) {
        error_sym("get_symbol_type", sym);
    }
    if (type != NULL) {
        if (get_symbol_name(type, &type_name) < 0) {
            error_sym("get_symbol_name", type);
        }
        if (type_name != NULL) type_name = tmp_strdup(type_name);
        if (get_symbol_type_class(sym, &type_class) < 0) {
            error_sym("get_symbol_type_class", sym);
        }
        cpp_ref = is_cpp_reference(type);
        indirect = is_indirect(type);
    }
    size_ok = 1;
    if (get_symbol_size(sym, &size) < 0) {
        int ok = 0;
        int err = errno;
        ObjectInfo * obj = get_symbol_object(sym);
        if (type != NULL) {
            unsigned type_flags;
            if (get_symbol_flags(type, &type_flags) < 0) {
                error_sym("get_symbol_flags", type);
            }
            if (name == NULL && type_name != NULL && strcmp(type_name, "exception") == 0 && (type_flags & SYM_FLAG_CLASS_TYPE)) {
                /* GCC does not tell size of std::exception class */
                ok = 1;
            }
        }
        if (!ok && errcmp(err, "Size not available: indirect symbol") == 0) ok = 1;
        if (!ok && errcmp(err, "Object is not available at this location") == 0) ok = 1;
        if (!ok && symbol_class == SYM_CLASS_COMP_UNIT) {
            /* Comp unit with code ranges */
            ok = 1;
        }
        if (!ok && symbol_class == SYM_CLASS_REFERENCE && addr_ok && type == NULL && name == NULL) {
            /* GCC C++ 4.1 produces entries like this */
            ok = 1;
        }
        if (!ok && type_class == TYPE_CLASS_ARRAY) {
            if (errcmp(err, "Cannot get array upper bound. No object location info found") == 0) ok = 1;
        }
        if (!ok && obj != NULL) {
            if (!ok && obj->mTag == TAG_dwarf_procedure) ok = 1;
            if (!ok && obj->mTag == TAG_subprogram) ok = 1;
            if (obj->mCompUnit->mLanguage == LANG_ADA95) {
                if (!ok && errcmp(err, "No object location info found in DWARF") == 0) ok = 1;
                if (!ok && errcmp(err, "Object does not have memory address") == 0) ok = 1;
                if (!ok && errcmp(err, "Cannot get object address: the object is located in a register") == 0) ok = 1;
            }
        }
        if (!ok) {
            errno = err;
            error_sym("get_symbol_size", sym);
        }
        size_ok = 0;
    }
    if (get_symbol_address(sym_proxy, &addr_proxy) < 0) {
        if (addr_ok) error_sym("get_symbol_address", sym_proxy);
    }
    else {
        errno = ERR_OTHER;
        if (!addr_ok) error_sym("get_symbol_address", sym_proxy);
        if (addr != addr_proxy) error_sym("get_symbol_address", sym_proxy);
    }
    if (get_symbol_size(sym_proxy, &size_proxy) < 0) {
        if (size_ok) error_sym("get_symbol_size", sym_proxy);
    }
    else {
        errno = ERR_OTHER;
        if (!size_ok) error_sym("get_symbol_size", sym_proxy);
        if (size != size_proxy) error_sym("get_symbol_size", sym_proxy);
    }
    if (cpp_ref) {
        Symbol * base_type = NULL;
        ref_size_ok = 1;
        if (get_symbol_base_type(sym, &base_type) < 0) {
            error_sym("get_symbol_base_type", sym);
        }
        if (get_symbol_size(base_type, &size) < 0) {
            int ok = 0;
            int err = errno;
            ObjectInfo * obj = get_symbol_object(base_type);
            if (obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
                if (!ok && errcmp(err, "Invalid reference in OP_call") == 0) ok = 1;
                if (!ok && errcmp(err, "Object is not available at this location") == 0) ok = 1;
            }
            if (!ok) {
                errno = err;
                error_sym("get_symbol_size", base_type);
            }
            ref_size_ok = 0;
        }
    }
    if (get_symbol_frame(sym, &ctx, &frame) < 0) {
        error_sym("get_symbol_frame", sym);
    }
    if (size_ok &&
            (!cpp_ref || ref_size_ok) &&
            (symbol_class == SYM_CLASS_VALUE ||
            symbol_class == SYM_CLASS_REFERENCE ||
            symbol_class == SYM_CLASS_FUNCTION ||
            symbol_class == SYM_CLASS_VARIANT_PART ||
            symbol_class == SYM_CLASS_VARIANT)) {
        Value v;
        char expr[300];
        RegisterDefinition * reg = NULL;
        if (!cpp_ref && loc_state != NULL && loc_state->pieces_cnt == 1 && loc_state->pieces->implicit_pointer == 0 &&
            loc_state->pieces->reg != NULL && loc_state->pieces->reg->size == loc_state->pieces->size) reg = loc_state->pieces->reg;
        sprintf(expr, "${%s}", symbol2id(sym));
        if (evaluate_expression(elf_ctx, frame, pc, expr, 0, &v) < 0) {
            error_sym("evaluate_expression", sym);
        }
        if (indirect) {
            if (v.sym != NULL) {
                set_errno(ERR_OTHER, "Value.sym != NULL");
                error_sym("evaluate_expression", sym);
            }
        }
        else if (!cpp_ref) {
            if (v.sym == NULL) {
                set_errno(ERR_OTHER, "Value.sym = NULL");
                error_sym("evaluate_expression", sym);
            }
            if (symcmp(sym, v.sym) != 0) {
                set_errno(ERR_OTHER, "Invalid Value.sym");
                error_sym("evaluate_expression", sym);
            }
        }
        else {
            if (v.sym != NULL) {
                set_errno(ERR_OTHER, "Value.sym != NULL");
                error_sym("evaluate_expression", sym);
            }
        }
        if (reg != v.reg) {
            set_errno(ERR_OTHER, "Invalid Value.reg");
            error_sym("evaluate_expression", sym);
        }
        if (!v.remote && !cpp_ref) {
            unsigned n;
            int implicit_pointer = 0;
            if (v.loc != NULL) {
                for (n = 0; n < v.loc->pieces_cnt; n++) {
                    if (v.loc->pieces[n].implicit_pointer) {
                        implicit_pointer = 1;
                        break;
                    }
                }
            }
            if (!implicit_pointer) {
                if (evaluate_expression(elf_ctx, frame, pc, expr, 1, &v) < 0) {
                    error_sym("evaluate_expression", sym);
                }
                if (v.sym == NULL) {
                    set_errno(ERR_OTHER, "Value.sym = NULL");
                    error_sym("evaluate_expression", sym);
                }
                if (symcmp(sym, v.sym) != 0) {
                    set_errno(ERR_OTHER, "Invalid Value.sym");
                    error_sym("evaluate_expression", sym);
                }
                if (reg != v.reg) {
                    set_errno(ERR_OTHER, "Invalid Value.reg");
                    error_sym("evaluate_expression", sym);
                }
            }
        }
    }
    if (name != NULL && !cpp_ref &&
            (symbol_class == SYM_CLASS_VALUE ||
            symbol_class == SYM_CLASS_REFERENCE ||
            symbol_class == SYM_CLASS_FUNCTION ||
            symbol_class == SYM_CLASS_VARIANT_PART ||
            symbol_class == SYM_CLASS_VARIANT)) {
        Symbol * find_sym = NULL;
        if (find_symbol_by_name(elf_ctx, frame, pc, name, &find_sym) == 0 && symcmp(sym, find_sym) == 0) {
            Value v;
            unsigned p = 0;
            ContextAddress a0 = addr;
            char * expr = (char *)tmp_alloc(strlen(name) + 300);
            if (!elf_file->elf64) a0 &= 0xffffffffu;
            sprintf(expr, "$\"%s\"", esc(name));
            if (evaluate_expression(elf_ctx, frame, pc, expr, 0, &v) < 0) {
                if (addr_ok && size_ok) error_sym("evaluate_expression", sym);
            }
            for (p = 0; p < 2; p++) {
                ContextAddress a1 = 0;
                switch (p) {
                case 0:
                    sprintf(expr, "&$\"%s\"", esc(name));
                    break;
                case 1:
                    if (sym_container == NULL) continue;
                    if (find_symbol_in_scope(elf_ctx, frame, pc, sym_container, name, &find_sym) < 0) continue;
                    if (symcmp(sym, find_sym) != 0) continue;
                    sprintf(expr, "&${%s}::$\"%s\"", symbol2id(sym_container), esc(name));
                    break;
                }
                if (evaluate_expression(elf_ctx, frame, pc, expr, 0, &v) < 0) {
                    if (!addr_ok) continue;
                    error_sym("evaluate_expression", sym);
                }
                if (!addr_ok) {
                    set_errno(ERR_OTHER, "Expression expected to return error");
                    error_sym("evaluate_expression", sym);
                }
                if (v.size != (elf_file->elf64 ? 8 : 4)) {
                    errno = ERR_INV_ADDRESS;
                    error_sym("evaluate_expression", sym);
                }
                if (value_to_address(&v, &a1) < 0) {
                    error_sym("value_to_address", sym);
                }
                if (a0 != a1 && !indirect) {
                    errno = ERR_INV_ADDRESS;
                    error_sym("value_to_address", sym);
                }
            }
        }
    }
    if (symbol_class != SYM_CLASS_TYPE && addr_ok && size_ok && !cpp_ref && !indirect) {
        Value v;
        char expr[300];
        ContextAddress a0 = addr;
        ContextAddress a1 = 0;
        if (!elf_file->elf64) a0 &= 0xffffffffu;
        sprintf(expr, "&${%s}", symbol2id(sym));
        if (evaluate_expression(elf_ctx, frame, pc, expr, 0, &v) < 0) {
            error_sym("evaluate_expression", sym);
        }
        if (v.size != (elf_file->elf64 ? 8 : 4)) {
            errno = ERR_INV_ADDRESS;
            error_sym("evaluate_expression", sym);
        }
        if (value_to_address(&v, &a1) < 0) {
            error_sym("value_to_address", sym);
        }
        if (a0 != a1) {
            errno = ERR_INV_ADDRESS;
            error_sym("value_to_address", sym);
        }
    }
    if (type != NULL) {
        int type_sym_class = 0;
        int type_type_class = 0;
        Symbol * base_type = NULL;
        Symbol * org_type = NULL;
        Symbol * type_container = NULL;
        unsigned type_bit_stride = 0;
        int container_class = 0;
        int base_type_class = 0;
        ContextAddress type_length = 0;
        if (get_symbol_class(type, &type_sym_class) < 0) {
            error_sym("get_symbol_class", type);
        }
        if (type_sym_class != SYM_CLASS_TYPE) {
            errno = ERR_OTHER;
            error_sym("Invalid symbol class of a type", type);
        }
        if (get_symbol_type_class(type, &type_type_class) < 0) {
            error_sym("get_symbol_type_class", type);
        }
        if (type_class != type_type_class) {
            errno = ERR_OTHER;
            error("Invalid symbol type class");
        }
        if (get_symbol_flags(type, &flags) < 0) {
            error_sym("get_symbol_flags", type);
        }
        if (flags & SYM_FLAG_TYPEDEF) {
            if (get_symbol_object(type) == NULL) {
                errno = ERR_OTHER;
                error("Invalid DWARF object of typedef");
            }
            if (get_symbol_type(type, &org_type) < 0) {
                error_sym("get_symbol_type", type);
            }
            if (symcmp(type, org_type) == 0) {
                errno = ERR_OTHER;
                error("Invalid original type of typedef");
            }
#if 0
            if (type_name == NULL) {
                errno = ERR_OTHER;
                error("typedef must have a name");
            }
#endif
            if ((flags & SYM_FLAG_CONST_TYPE) || (flags & SYM_FLAG_VOLATILE_TYPE)) {
                errno = ERR_OTHER;
                error("Invalid flags of typedef");
            }
        }
        else if ((flags & SYM_FLAG_CONST_TYPE) || (flags & SYM_FLAG_VOLATILE_TYPE)) {
            if (get_symbol_type(type, &org_type) < 0) {
                error_sym("get_symbol_type", type);
            }
            if (symcmp(type, org_type) == 0) {
                errno = ERR_OTHER;
                error("Invalid original type of modified type");
            }
        }
        if (org_type != NULL) {
            int org_type_class = 0;
            if (get_symbol_type_class(org_type, &org_type_class) < 0) {
                error_sym("get_symbol_type_class", org_type);
            }
            if (type_class != org_type_class) {
                errno = ERR_OTHER;
                error("Invalid symbol type class");
            }
        }
        if (get_symbol_index_type(type, &index_type) < 0) {
            if (type_class == TYPE_CLASS_ARRAY) {
                error_sym("get_symbol_index_type", type);
            }
        }
        else if (org_type != NULL) {
            Symbol * org_index_type = NULL;
            if (get_symbol_index_type(org_type, &org_index_type) < 0) {
                error_sym("get_symbol_index_type", org_type);
            }
            if (symcmp(index_type, org_index_type) != 0) {
                errno = ERR_OTHER;
                error("Invalid index type of typedef");
            }
        }
        if (get_symbol_base_type(type, &base_type) < 0) {
            if (type_class == TYPE_CLASS_ARRAY || type_class == TYPE_CLASS_FUNCTION ||
                type_class == TYPE_CLASS_POINTER || type_class == TYPE_CLASS_MEMBER_PTR) {
                error_sym("get_symbol_base_type", type);
            }
        }
        else {
            char * base_type_name = NULL;
            if (get_symbol_name(base_type, &base_type_name) < 0) {
                error_sym("get_symbol_name", base_type);
            }
            if (base_type_name != NULL) base_type_name = tmp_strdup(base_type_name);
            if (get_symbol_type_class(base_type, &base_type_class) < 0) {
                error_sym("get_symbol_type_class", base_type);
            }
            if (org_type != NULL) {
                Symbol * org_base_type = NULL;
                if (get_symbol_base_type(org_type, &org_base_type) < 0) {
                    error_sym("get_symbol_base_type", org_type);
                }
                if (symcmp(base_type, org_base_type) != 0) {
                    errno = ERR_OTHER;
                    error("Invalid base type of typedef");
                }
            }
        }
        if (get_symbol_container(type, &type_container) < 0) {
            error_sym("get_symbol_container", type);
        }
        if (get_symbol_class(type_container, &container_class) < 0) {
            error_sym("get_symbol_class", type);
        }
        if (type_class == TYPE_CLASS_MEMBER_PTR) {
            if (org_type != NULL) {
                Symbol * org_container = NULL;
                if (get_symbol_container(org_type, &org_container) < 0) {
                    error_sym("get_symbol_container", org_type);
                }
                if (symcmp(type_container, org_container) != 0) {
                    errno = ERR_OTHER;
                    error("Invalid container of typedef");
                }
            }
        }
        else if (container_class == SYM_CLASS_COMP_UNIT && type_name != NULL) {
            /* This test is too slow, don't run it evry time */
            static unsigned cnt = 0;
            if (strcmp(type_name, "int") != 0 && cnt++ % 33 == 0) {
                Symbol * find_sym = NULL;
                unsigned find_cnt = 0;
                unsigned find_max = 16;
                Symbol ** find_syms = (Symbol **)tmp_alloc(sizeof(Symbol *) * find_max);
                unsigned i;
                if (find_symbol_by_name(elf_ctx, STACK_NO_FRAME, 0, type_name, &find_sym) < 0) {
                    error("find_symbol_by_name");
                }
                find_syms[find_cnt++] = find_sym;
                while (find_next_symbol(&find_sym) == 0) {
                    if (find_cnt >= find_max) {
                        find_max *= 2;
                        find_syms = (Symbol **)tmp_realloc(find_syms, sizeof(Symbol *) * find_max);
                    }
                    find_syms[find_cnt++] = find_sym;
                }
                for (i = 0; i < find_cnt; i++) {
                    int find_class = 0;
                    SYM_FLAGS find_flags = 0;
                    ContextAddress find_size = 0;
                    find_sym = find_syms[i];
                    if (get_symbol_class(find_sym, &find_class) < 0) {
                        error("get_symbol_class");
                    }
                    if (get_symbol_flags(find_sym, &find_flags) < 0) {
                        error("get_symbol_flags");
                    }
                    switch (find_class) {
                    case SYM_CLASS_TYPE:
                        if (get_symbol_size(find_sym, &find_size) == 0) {
                            Value v;
                            uint64_t n = 0;
                            char * expr = (char *)tmp_alloc(512);
                            const char * id = symbol2id(find_sym);
                            sprintf(expr, "sizeof(${%s})", id);
                            if (evaluate_expression(elf_ctx, STACK_NO_FRAME, 0, expr, 0, &v) < 0) {
                                error("evaluate_expression");
                            }
                            if (value_to_unsigned(&v, &n) < 0) {
                                error("value_to_unsigned");
                            }
                            if (n != find_size) {
                                errno = ERR_OTHER;
                                error("invalid result of evaluate_expression");
                            }
                        }
                        break;
                    }
                }
            }
        }
        if (get_symbol_length(sym, &length) < 0) {
            if (type_class == TYPE_CLASS_ARRAY) {
                int ok = 0;
                int err = errno;
                ObjectInfo * obj = get_symbol_object(sym);
                if (!ok && errcmp(err, "Object is not available at this location") == 0) ok = 1;
                if (!ok && obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
                    if (errcmp(err, "No object location info found in DWARF") == 0) ok = 1;
                }
                if (!ok && obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
                    if (errcmp(err, "Object does not have memory address") == 0) ok = 1;
                }
                if (!ok && obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
                    if (errcmp(err, "Cannot get object address: the object is located in a register") == 0) ok = 1;
                }
                if (!ok && obj != NULL) {
                    if (errcmp(err, "Cannot get array upper bound. No object location info found") == 0) ok = 1;
                }
                if (!ok) {
                    errno = err;
                    error_sym("get_symbol_length", sym);
                }
            }
        }
        else if (get_symbol_length(type, &type_length) < 0) {
            error_sym("get_symbol_length", type);
        }
        else if (length != type_length) {
            errno = ERR_OTHER;
            error("Invalid length of a type");
        }
        else if (org_type != NULL) {
            ContextAddress org_length = 0;
            if (get_symbol_length(org_type, &org_length) < 0) {
                error_sym("get_symbol_length", org_type);
            }
            if (length != org_length) {
                errno = ERR_OTHER;
                error("Invalid length of typedef");
            }
        }
        type_bit_stride = get_bit_stride(type);
        if (length > 0 && size_ok && type_bit_stride % 8 == 0) {
            ContextAddress base_type_size = 0;
            if (get_symbol_size(base_type, &base_type_size) < 0) {
                error_sym("get_symbol_size", base_type);
            }
            if (base_type_size * length > size) {
                errno = ERR_OTHER;
                error("Invalid size of base type");
            }
        }
        if (get_symbol_lower_bound(sym, &lower_bound) < 0) {
            if (type_class == TYPE_CLASS_ARRAY) {
                int ok = 0;
                int err = errno;
                ObjectInfo * obj = get_symbol_object(sym);
                if (obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
                    if (!ok && errcmp(err, "Object is not available at this location") == 0) ok = 1;
                }
                if (obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
                    if (!ok && errcmp(err, "Object does not have memory address") == 0) ok = 1;
                }
                if (!ok && obj != NULL && obj->mCompUnit->mLanguage == LANG_ADA95) {
                    if (errcmp(err, "Cannot get object address: the object is located in a register") == 0) ok = 1;
                }
                if (!ok) {
                    errno = err;
                    error_sym("get_symbol_lower_bound", sym);
                }
            }
        }
        else if (get_symbol_lower_bound(type, &lower_bound) < 0) {
            error_sym("get_symbol_lower_bound", type);
        }
        else if (org_type != NULL) {
            int64_t org_lower_bound = 0;
            if (get_symbol_lower_bound(org_type, &org_lower_bound) < 0) {
                error_sym("get_symbol_lower_bound", org_type);
            }
            if (lower_bound != org_lower_bound) {
                errno = ERR_OTHER;
                error("Invalid lower bound of typedef");
            }
        }
        if (type_class == TYPE_CLASS_ENUMERATION) {
            test_enumeration_type(type);
        }
        else if (type_class == TYPE_CLASS_COMPOSITE) {
            test_composite_type(type);
        }
        else if (type_class == TYPE_CLASS_POINTER) {
            if (base_type_class == TYPE_CLASS_COMPOSITE &&
                    (flags & SYM_FLAG_PARAMETER) && (flags & SYM_FLAG_ARTIFICIAL) &&
                    name != NULL && strcmp(name, "this") == 0) {
                test_composite_type(base_type);
                test_this_pointer(base_type);
            }
        }
        if (unit_range != NULL && type_name != NULL && strcmp(type_name, "boolean") == 0) {
            Symbol * sym_true = NULL;
            if (find_symbol_by_name(elf_ctx, STACK_NO_FRAME, pc, "true", &sym_true) < 0) {
                if (get_error_code(errno) == ERR_SYM_NOT_FOUND) {
                    // OK
                }
                else {
                    error_sym("find_symbol_by_name", type);
                }
            }
            if (sym_true != NULL) {
                Symbol * sym_true_type = NULL;
                if (get_symbol_type(sym_true, &sym_true_type) < 0) {
                    error_sym("get_symbol_type", sym_true);
                }
                if (sym_true_type == NULL || get_symbol_object(sym_true_type) == NULL) {
                    errno = ERR_OTHER;
                    error("Invalid type of 'true'");
                }
                if (get_symbol_object(sym_true_type)->mCompUnit != unit_range->mUnit) {
                    errno = ERR_OTHER;
                    error("Invalid type of 'true'");
                }
            }
        }
    }
}

static int is_in_segment(U8_T lt_addr) {
    unsigned j;
    for (j = 0; j < elf_file->pheader_cnt; j++) {
        ELF_PHeader * p = elf_file->pheaders + j;
        if (p->type != PT_LOAD) continue;
        if (lt_addr >= p->address && lt_addr < p->address + p->mem_size) return 1;
    }
    return 0;
}

static void test_public_names(void) {
    DWARFCache * cache = get_dwarf_cache(get_dwarf_file(elf_file));
    unsigned n = 0;
    unsigned m = 0;
    time_t time_start = time(0);
    while (n < cache->mPubNames.mCnt) {
        ObjectInfo * obj = cache->mPubNames.mNext[n++].mObject;
        if (obj != NULL && (obj->mParent == NULL || obj->mParent->mTag != TAG_namespace)) {
            Symbol * sym1 = NULL;
            Symbol * sym2 = NULL;
            ContextAddress addr = 0;
            if (find_symbol_by_name(elf_ctx, STACK_NO_FRAME, 0, obj->mName, &sym1) < 0) {
                error("find_symbol_by_name");
            }
            if (obj->mCompUnit->mLanguage == LANG_C) {
                /* Note: this test fails for C++ because of name overloading */
                SYM_FLAGS flags = 0;
                if (get_symbol_flags(sym1, &flags) < 0) error("get_symbol_flags");
                if (obj->mTag == TAG_subprogram && (flags & SYM_FLAG_EXTERNAL) != 0 && get_symbol_address(sym1, &addr) == 0) {
                    /* Check weak symbol is not the first symbol */
                    while (find_next_symbol(&sym2) == 0) {
                        ContextAddress nxt_addr = 0;
                        if (get_symbol_object(sym2) == NULL && get_symbol_address(sym2, &nxt_addr) == 0) {
                            if (get_symbol_flags(sym2, &flags) < 0) error("get_symbol_flags");
                            if ((flags & SYM_FLAG_EXTERNAL) != 0 && addr != nxt_addr) {
                                set_errno(ERR_OTHER, "Invalid address - weak symbol?");
                                error("find_symbol_by_name");
                            }
                        }
                    }
                }
            }
            loc_var_func(NULL, sym1);
        }
        if ((n % 10) == 0) {
            tmp_gc();
            if (time(0) - time_start >= 120) break;
        }
    }
    for (m = 1; m < elf_file->section_cnt; m++) {
        ELF_Section * tbl = elf_file->sections + m;
        if (tbl->sym_names_hash == NULL) continue;
        time_start = time(0);
        for (n = 0; n < tbl->sym_names_hash_size; n++) {
            Trap trap;
            if (set_trap(&trap)) {
                ELF_SymbolInfo sym_info;
                unpack_elf_symbol_info(tbl, n, &sym_info);
                if (sym_info.name && sym_info.section_index != SHN_UNDEF && sym_info.type != STT_FILE) {
                    Symbol * sym = NULL;
                    Symbol * sym1 = NULL;
                    if (elf_tcf_symbol(elf_ctx, &sym_info, &sym) < 0) {
                        error("elf_tcf_symbol");
                    }
                    switch (sym_info.type) {
                    case STT_OBJECT:
                    case STT_FUNC:
                        if (sym_info.section != NULL) {
                            U8_T value = sym_info.value;
                            ContextAddress addr = 0;
                            ContextAddress lt = 0;
                            ELF_File * lt_file = NULL;
                            ELF_Section * lt_sec = NULL;
                            if (elf_file->type == ET_REL) {
                                value += sym_info.section->addr;
                            }
                            if (is_in_segment(value)) {
                                if (get_symbol_address(sym, &addr) < 0) {
                                    error("get_symbol_address");
                                }
                                if (addr != value + file_addr_offs) {
                                    set_errno(ERR_OTHER, "Invalid address - broken relocation logic?");
                                    error("get_symbol_address");
                                }
                                lt = elf_map_to_link_time_address(elf_ctx, addr, 0, &lt_file, &lt_sec);
                                if (lt != value) {
                                    set_errno(ERR_OTHER, "Invalid address - broken relocation logic?");
                                    error("elf_map_to_link_time_address");
                                }
                            }
                        }
                        break;
                    }
                    loc_var_func(NULL, sym);
                    if (find_symbol_by_name(elf_ctx, STACK_NO_FRAME, 0, sym_info.name, &sym1) < 0) {
                        error("find_symbol_by_name");
                    }
                    loc_var_func(NULL, sym1);
                }
                clear_trap(&trap);
            }
            else {
                error("unpack_elf_symbol_info");
            }
            if ((n % 10) == 0) {
                tmp_gc();
                if (time(0) - time_start >= 120) break;
            }
        }
    }
}

static void check_addr_ranges(void) {
    DWARFCache * cache = get_dwarf_cache(get_dwarf_file(elf_file));
    if (cache->mAddrRangesCnt > 1) {
        unsigned i;
        unsigned n = 0;
        for (i = 0; i < cache->mAddrRangesCnt - 1; i++) {
            UnitAddressRange * x = cache->mAddrRanges + i;
            UnitAddressRange * y = cache->mAddrRanges + i + 1;
            if (x->mSection == y->mSection &&
                    x->mAddr < y->mAddr + y->mSize &&
                    y->mAddr < x->mAddr + x->mSize) {
                if (n < 20) {
                    printf("Overlapping address ranges: %08x %08x, %08x %08x\n",
                        (unsigned)x->mAddr, (unsigned)x->mSize,
                        (unsigned)y->mAddr, (unsigned)y->mSize);
                }
                n++;
            }
        }
        if (n >= 20) printf("Overlapping address ranges: total %d ranges ...\n", n);
    }
}

static void check_line_info_cb(CodeArea * area, void * args) {
    area_cnt++;
}

static void check_line_info(void) {
    assert(file_has_line_info);
    area_cnt = 0;
    if (address_to_line(elf_ctx, 0, 0xffffffffffffffff, check_line_info_cb, NULL) < 0) {
        error("address_to_line");
    }
    if (area_cnt == 0) {
        set_errno(ERR_OTHER, "address_to_line(elf_ctx, 0, 0xffffffffffffffff,...) does not work");
        error("address_to_line");
    }
}

static void next_region(void) {
    Symbol * sym = NULL;
    ContextAddress lt_addr;
    ELF_File * lt_file;
    ELF_Section * lt_sec;
    ObjectInfo * func_object = NULL;
    char * func_name = NULL;
    struct timespec time_now;
    Trap trap;
    int test_cnt = 0;
    int loaded = mem_region_pos < 0;
    ContextAddress next_pc = pc + 1;
    UnitAddressRange * unit_range = NULL;
    ContextAddress rage_rt_addr = 0;
    const char * isa = NULL;
    ContextAddress isa_range_addr = 0;
    ContextAddress isa_range_size = 0;

    for (;;) {
        if (mem_region_pos < 0) {
            mem_region_pos = 0;
            pc = mem_map.regions[mem_region_pos].addr;
        }
        else if (next_pc < mem_map.regions[mem_region_pos].addr + mem_map.regions[mem_region_pos].size) {
            pc = next_pc;
        }
        else if (mem_region_pos + 1 < (int)mem_map.region_cnt) {
            mem_region_pos++;
            pc = mem_map.regions[mem_region_pos].addr;
        }
        else {
            mem_region_pos++;
            pc = 0;
            print_time(time_start, test_cnt);
            return;
        }

        while ((mem_map.regions[mem_region_pos].flags & MM_FLAG_X) == 0 || mem_map.regions[mem_region_pos].size == 0) {
            if (mem_region_pos + 1 < (int)mem_map.region_cnt) {
                mem_region_pos++;
                pc = mem_map.regions[mem_region_pos].addr;
            }
            else {
                mem_region_pos++;
                pc = 0;
                print_time(time_start, test_cnt);
                return;
            }
        }

        set_regs_PC(elf_ctx, pc);
        send_context_changed_event(elf_ctx);

        if (get_context_isa(elf_ctx, pc, &isa, &isa_range_addr, &isa_range_size) < 0) {
            error("get_context_isa");
        }
        if (isa != NULL) {
            if (pc < isa_range_addr) {
                set_errno(ERR_OTHER, "pc < isa_range_addr");
                error("get_context_isa");
            }
            if (isa_range_addr + isa_range_size > 0 && pc >= isa_range_addr + isa_range_size) {
                set_errno(ERR_OTHER, "pc >= isa_range_addr + isa_range_size");
                error("get_context_isa");
            }
        }

        func_name = NULL;
        func_object = NULL;
        if (find_symbol_by_addr(elf_ctx, STACK_NO_FRAME, pc, &sym) < 0) {
            if (get_error_code(errno) != ERR_SYM_NOT_FOUND) {
                error("find_symbol_by_addr");
            }
        }
        else {
            int i;
            ContextAddress func_addr = 0;
            ContextAddress func_size = 0;
            Symbol * frame_sym = NULL;
            Symbol * func_type = NULL;
            int func_children_count = 0;
            Symbol ** func_children = NULL;
            SYM_FLAGS flags = 0;
            func_object = get_symbol_object(sym);
            if (get_symbol_name(sym, &func_name) < 0) {
                error_sym("get_symbol_name", sym);
            }
            if (func_name != NULL) func_name = tmp_strdup(func_name);
            if (get_symbol_address(sym, &func_addr) < 0) {
                error_sym("get_symbol_address", sym);
            }
            if (get_symbol_size(sym, &func_size) < 0) {
                error_sym("get_symbol_size", sym);
            }
            if (get_symbol_flags(sym, &flags) < 0) {
                error_sym("get_symbol_flags", sym);
            }
            /* Function type can be frame based */
            if (find_symbol_by_addr(elf_ctx, STACK_TOP_FRAME, pc, &frame_sym) < 0) {
                if (get_error_code(errno) != ERR_SYM_NOT_FOUND) {
                    error("find_symbol_by_addr");
                }
            }
            if (frame_sym == NULL || get_symbol_object(sym) != get_symbol_object(frame_sym)) {
                set_errno(ERR_OTHER, "Find by PC and find by top frame differ");
                error_sym("find_symbol_by_addr", frame_sym);
            }
            if (get_symbol_type(frame_sym, &func_type) < 0) {
                error_sym("get_symbol_type", sym);
            }
            if (func_type != NULL) {
                if (get_symbol_children(func_type, &func_children, &func_children_count) < 0) {
                    error_sym("get_symbol_children", func_type);
                }
                for (i = 0; i < func_children_count; i++) {
                    Symbol * arg_type = NULL;
                    int arg_class = 0;
                    ContextAddress arg_size = 0;
                    if (get_symbol_type(func_children[i], &arg_type) < 0) {
                        error_sym("get_symbol_type", func_children[i]);
                    }
                    if (get_symbol_class(func_children[i], &arg_class) < 0) {
                        error_sym("get_symbol_class", func_children[i]);
                    }
                    if (get_symbol_size(func_children[i], &arg_size) < 0) {
                        int error = errno;
                        if (errcmp(error, "Cannot get object address: the object is located in a register") == 0) {
                            /* OK */
                        }
                        else if (errcmp(error, "Object is not available at this location") == 0) {
                            /* OK */
                        }
                        else {
                            errno = error;
                            error_sym("get_symbol_size", func_children[i]);
                        }
                    }
                }
            }
            if (pc < func_addr || (func_size > 0 && pc >= func_addr + func_size)) {
                errno = ERR_OTHER;
                error("invalid symbol address");
            }
            if (func_name != NULL) {
                Symbol * fnd_sym = NULL;
                if (find_symbol_by_name(elf_ctx, STACK_TOP_FRAME, 0, func_name, &fnd_sym) < 0) {
                    if (get_error_code(errno) != ERR_SYM_NOT_FOUND) {
                        error("find_symbol_by_name");
                    }
                }
                else {
                    Symbol * fnd_scp_sym = NULL;
                    char * fnd_name = NULL;
                    ContextAddress fnd_addr = 0;
                    if (find_symbol_in_scope(elf_ctx, STACK_TOP_FRAME, pc, NULL, func_name, &fnd_scp_sym) < 0) {
                        if (get_error_code(errno) != ERR_SYM_NOT_FOUND) {
                            error("find_symbol_in_scope");
                        }
                    }
                    if (get_symbol_name(fnd_sym, &fnd_name) < 0) {
                        error_sym("get_symbol_name", fnd_sym);
                    }
                    if (fnd_name == NULL) {
                        errno = ERR_OTHER;
                        error_sym("invalid symbol name", fnd_sym);
                    }
                    if (strcmp(fnd_name, func_name) != 0) {
                        errno = ERR_OTHER;
                        error_sym("strcmp(name_buf, name)", fnd_sym);
                    }
                    if (get_symbol_address(fnd_sym, &fnd_addr) == 0) {
                        Value v;
                        SYM_FLAGS flags = 0;
                        char * expr = (char *)tmp_alloc(strlen(func_name) + 16);
                        if (get_symbol_flags(fnd_sym, &flags) < 0) {
                            error_sym("get_symbol_flags", fnd_sym);
                        }
                        sprintf(expr, "$\"%s\"", esc(func_name));
                        if (evaluate_expression(elf_ctx, STACK_TOP_FRAME, 0, expr, 0, &v) < 0) {
                            error_sym("evaluate_expression", fnd_sym);
                        }
                        if (flags & SYM_FLAG_EXTERNAL) {
                            if (find_symbol_by_name(elf_ctx, STACK_NO_FRAME, 0, func_name, &fnd_sym) < 0) {
                                error("find_symbol_by_name");
                            }
                        }
                    }
                }
            }
        }

        if (func_object != NULL) {
            Symbol * func_type = NULL;
            Symbol * ret_type1 = NULL;
            Symbol * ret_type2 = NULL;
            if (get_symbol_type(sym, &func_type) < 0) {
                error_sym("get_symbol_type", sym);
            }
            if (get_symbol_base_type(sym, &ret_type1) < 0) {
                error_sym("get_symbol_base_type", sym);
            }
            if (get_symbol_base_type(func_type, &ret_type2) < 0) {
                error_sym("get_symbol_base_type", func_type);
            }
            if (symcmp(ret_type1, ret_type2) != 0) {
                errno = ERR_OTHER;
                error_sym("symcmp(ret_type, ret_type2)", sym);
            }
        }

        unit_range = elf_find_unit(elf_ctx, pc, pc, &rage_rt_addr);
        if (unit_range == NULL && errno) error("elf_find_unit");
        if (unit_range != NULL) {
            ELF_Section * sec = unit_range->mUnit->mFile->sections + unit_range->mSection;
            ContextAddress addr = elf_map_to_run_time_address(elf_ctx, sec->file, sec, unit_range->mAddr);
            if (addr > pc || addr + unit_range->mSize <= pc) {
                set_errno(ERR_OTHER, "Invalid compile unit address");
                error("elf_find_unit");
            }
            if (func_object != NULL && unit_range->mUnit != func_object->mCompUnit) {
                set_errno(ERR_OTHER, "Invalid compile unit");
                error("elf_find_unit");
            }
        }
        else if (func_object != NULL) {
            set_errno(ERR_OTHER, "Compile unit not found");
            error("elf_find_unit");
        }

        if (find_symbol_by_name(elf_ctx, STACK_TOP_FRAME, 0, "@ non existing name @", &sym) < 0) {
            if (get_error_code(errno) != ERR_SYM_NOT_FOUND) {
                error("find_symbol_by_name");
            }
        }

        area_cnt = 0;
        line_area_ok = 0;
        if (address_to_line(elf_ctx, pc, pc + 1, addr_to_line_callback, NULL) < 0) {
            error("address_to_line");
        }
        next_pc = pc + 8;
        if (area_cnt > 0) {
            unsigned i;
            if (area_buf[0].end_address > pc) next_pc = area_buf[0].end_address + test_cnt % 6;
            if (next_pc > pc + 12) next_pc = pc + 12;
            if (area_cnt > 1) {
                printf("Overlapping line info ranges: %08x %08x, %08x %08x\n",
                    (unsigned)area_buf[0].start_address, (unsigned)area_buf[0].end_address,
                    (unsigned)area_buf[1].start_address, (unsigned)area_buf[1].end_address);
            }
            for (i = 0; i < area_cnt; i++) {
                CodeArea area = area_buf[i];
                char * elf_file_name = tmp_strdup(area.file);
                if (area.start_address > pc || area.end_address <= pc) {
                    errno = set_errno(ERR_OTHER, "Invalid line area address");
                    error("address_to_line");
                }
                if (line_to_address(elf_ctx, elf_file_name, area.start_line, area.start_column, line_to_addr_callback, &area) < 0) {
                    error("line_to_address");
                }
                if (!line_area_ok) {
                    errno = set_errno(ERR_OTHER, "Invalid line area address");
                    error("line_to_address");
                }
                line_info_cnt++;
            }
        }
        else {
            unsigned i;
            if (address_to_line(elf_ctx, pc >= 2 ? pc - 2: pc, pc + 3, addr_to_line_callback_p1, NULL) < 0) {
                error("address_to_line");
            }
            for (i = 0; i < area_cnt; i++) {
                CodeArea area = area_buf[i];
                if (pc >= area.start_address && pc < area.end_address) {
                    if (unit_range == NULL) {
                        printf("Incomplete or conflicting data in .debug_aranges section for %08x\n", (unsigned)pc);
                    }
                    else {
                        errno = set_errno(ERR_OTHER, "Line info not found");
                        error("address_to_line");
                    }
                }
            }
        }

        lt_file = NULL;
        lt_sec = NULL;
        lt_addr = elf_map_to_link_time_address(elf_ctx, pc, 0, &lt_file, &lt_sec);
        if (errno) error("elf_map_to_link_time_address");
        assert(lt_file != NULL);
        assert(lt_file == elf_file);
        assert(lt_sec == NULL || lt_sec->file == lt_file);
        assert(pc == elf_map_to_run_time_address(elf_ctx, lt_file, lt_sec, lt_addr));
        if (set_trap(&trap)) {
            get_dwarf_stack_frame_info(elf_ctx, lt_file, lt_sec, lt_addr);
            clear_trap(&trap);
        }
        else {
            error("get_dwarf_stack_frame_info 0");
        }

        lt_file = NULL;
        lt_sec = NULL;
        lt_addr = elf_map_to_link_time_address(elf_ctx, pc, 1, &lt_file, &lt_sec);
        if (errno) error("elf_map_to_link_time_address");
        assert(lt_file != NULL);
        assert(lt_file == get_dwarf_file(elf_file));
        if (lt_sec != NULL) {
            ContextAddress rt_addr;
            assert(lt_sec->file == lt_file);
            rt_addr = elf_map_to_run_time_address(elf_ctx, lt_file, lt_sec, lt_addr);
            if (errno) error("elf_map_to_run_time_address");
            if (pc != rt_addr) {
                set_errno(ERR_OTHER, "Invalid address - broken relocation logic?");
                error("elf_map_to_run_time_address");
            }
            if (set_trap(&trap)) {
                get_dwarf_stack_frame_info(elf_ctx, lt_file, lt_sec, lt_addr);
                clear_trap(&trap);
            }
            else {
                error("get_dwarf_stack_frame_info 1");
            }
        }

        if (unit_range != NULL) {
            static char unit_id[256];
            const char * unit_name = unit_range->mUnit->mObject->mName;
            Symbol * unit_sym = NULL;
            if (unit_name != NULL) {
                if (find_symbol_by_name(elf_ctx, STACK_TOP_FRAME, pc, unit_name, &unit_sym) < 0) {
                    error("find_symbol_by_name");
                }
                if (unit_sym == NULL) {
                    errno = set_errno(ERR_OTHER, "Cannot find compilation unit by name");
                    error("find_symbol_by_name");
                }
                if (get_symbol_object(unit_sym) != unit_range->mUnit->mObject) {
                    errno = set_errno(ERR_OTHER, "Wrong result searching compilation unit by name");
                    error("find_symbol_by_name");
                }
                if (strcmp(unit_id, symbol2id(unit_sym)) != 0) {
                    int i;
                    int count = 0;
                    Symbol ** children = NULL;
                    strlcpy(unit_id, symbol2id(unit_sym), sizeof(unit_id));
                    if (get_symbol_children(unit_sym, &children, &count) < 0) {
                        error_sym("get_symbol_children", unit_sym);
                    }
                    for (i = 0; i < count; i++) {
                        loc_var_func(unit_range, children[i]);
                    }
                }
            }
        }

        if (enumerate_symbols(elf_ctx, STACK_TOP_FRAME, loc_var_func, unit_range) < 0) {
            error("enumerate_symbols");
        }

        if (func_object != NULL) {
            if (set_trap(&trap)) {
                StackFrame * frame = NULL;
                if (get_frame_info(elf_ctx, STACK_TOP_FRAME, &frame) < 0) exception(errno);
                if (frame->fp != frame_addr) {
                    PropertyValue v;
                    uint64_t addr = 0;
                    memset(&v, 0, sizeof(v));
                    read_and_evaluate_dwarf_object_property(elf_ctx, STACK_TOP_FRAME, func_object, AT_frame_base, &v);
                    if (v.mPieceCnt == 1 && v.mPieces[0].reg != NULL && v.mPieces[0].bit_size == 0) {
                        if (read_reg_value(frame, v.mPieces[0].reg, &addr) < 0) exception(errno);
                    }
                    else {
                        addr = get_numeric_property_value(&v);
                    }
                    if (addr != frame->fp) {
                        /* AT_frame_base is not valid in prologue and epilogue.
                        str_exception(ERR_OTHER, "Invalid FP");
                        */
                    }
                }
                clear_trap(&trap);
            }
            else if (trap.error != ERR_SYM_NOT_FOUND) {
                error("AT_frame_base");
            }
        }

        if (func_object != NULL) {
            set_regs_PC(elf_ctx, 0);
            send_context_changed_event(elf_ctx);
            if (find_symbol_by_addr(elf_ctx, STACK_TOP_FRAME, pc, &sym) < 0) {
                error("find_symbol_by_addr");
            }
        }

        test_cnt++;
        tmp_gc();

        if (loaded) {
            struct timespec time_diff;
            clock_gettime(CLOCK_REALTIME, &time_now);
            time_diff.tv_sec = time_now.tv_sec - time_start.tv_sec;
            if (time_now.tv_nsec < time_start.tv_nsec) {
                time_diff.tv_sec--;
                time_diff.tv_nsec = time_now.tv_nsec + 1000000000 - time_start.tv_nsec;
            }
            else {
                time_diff.tv_nsec = time_now.tv_nsec - time_start.tv_nsec;
            }
            printf("load time: %ld.%06ld\n", (long)time_diff.tv_sec, time_diff.tv_nsec / 1000);
            fflush(stdout);
            time_start = time_now;
            loaded = 0;
            test_public_names();
            clock_gettime(CLOCK_REALTIME, &time_now);
            time_diff.tv_sec = time_now.tv_sec - time_start.tv_sec;
            if (time_now.tv_nsec < time_start.tv_nsec) {
                time_diff.tv_sec--;
                time_diff.tv_nsec = time_now.tv_nsec + 1000000000 - time_start.tv_nsec;
            }
            else {
                time_diff.tv_nsec = time_now.tv_nsec - time_start.tv_nsec;
            }
            printf("pub names time: %ld.%06ld\n", (long)time_diff.tv_sec, time_diff.tv_nsec / 1000);
            fflush(stdout);
            check_addr_ranges();
            time_start = time_now;
        }
        else if (test_cnt >= 10000) {
            print_time(time_start, test_cnt);
            clock_gettime(CLOCK_REALTIME, &time_start);
            return;
        }
    }
}

static void next_file(void) {
    unsigned j, k;
    ELF_File * f = NULL;
    int can_relocate = 0;
    struct stat st;

    if (pass_cnt == files_cnt) exit(0);
    elf_file_name = files[pass_cnt % files_cnt];

    printf("\n");
    printf("File: %s\n", elf_file_name);
    fflush(stdout);
    if (stat(elf_file_name, &st) < 0) {
        printf("Cannot stat ELF: %s\n", errno_to_str(errno));
        exit(1);
    }

    clock_gettime(CLOCK_REALTIME, &time_start);

    f = elf_open(elf_file_name);
    if (f == NULL) {
        printf("Cannot open ELF: %s\n", errno_to_str(errno));
        exit(1);
    }

    if (elf_ctx == NULL) {
        elf_ctx = create_context("test");
        elf_ctx->stopped = 1;
        elf_ctx->pending_intercept = 1;
        elf_ctx->mem = elf_ctx;
        elf_ctx->big_endian = f->big_endian;
        list_add_first(&elf_ctx->ctxl, &context_root);
        elf_ctx->ref_count++;
    }

    file_addr_offs = 0;
    context_clear_memory_map(&mem_map);
    for (j = 0; j < f->pheader_cnt; j++) {
        ELF_PHeader * p = f->pheaders + j;
        if (p->type != PT_LOAD) continue;
        can_relocate = 1;
    }
    for (j = 0; j < f->pheader_cnt; j++) {
        ELF_PHeader * p = f->pheaders + j;
        if (p->type != PT_LOAD) continue;
        for (k = 0; k < j; k++) {
            ELF_PHeader * h = f->pheaders + k;
            if (h->type != PT_LOAD) continue;
            if (p->offset == h->offset) can_relocate = 0;
        }
    }
    if (can_relocate) file_addr_offs = 0x10000;

    for (j = 0; j < f->pheader_cnt; j++) {
        MemoryRegion * r = NULL;
        ELF_PHeader * p = f->pheaders + j;
        if (p->type != PT_LOAD) continue;
        if (p->file_size > 0) {
            if (mem_map.region_cnt >= mem_map.region_max) {
                mem_map.region_max += 8;
                mem_map.regions = (MemoryRegion *)loc_realloc(mem_map.regions, sizeof(MemoryRegion) * mem_map.region_max);
            }
            r = mem_map.regions + mem_map.region_cnt++;
            memset(r, 0, sizeof(MemoryRegion));
            r->addr = (ContextAddress)p->address + file_addr_offs; /* Relocated */
            r->file_name = loc_strdup(elf_file_name);
            r->file_offs = p->offset;
            r->size = (ContextAddress)p->file_size;
            r->flags = MM_FLAG_R | MM_FLAG_W;
            if (p->flags & PF_X) r->flags |= MM_FLAG_X;
            r->valid = MM_VALID_ADDR | MM_VALID_SIZE | MM_VALID_FILE_OFFS;
            r->dev = st.st_dev;
            r->ino = st.st_ino;
        }
        if (p->file_size < p->mem_size) {
            if (mem_map.region_cnt >= mem_map.region_max) {
                mem_map.region_max += 8;
                mem_map.regions = (MemoryRegion *)loc_realloc(mem_map.regions, sizeof(MemoryRegion) * mem_map.region_max);
            }
            r = mem_map.regions + mem_map.region_cnt++;
            memset(r, 0, sizeof(MemoryRegion));
            r->bss = 1;
            r->addr = (ContextAddress)(p->address + p->file_size) + file_addr_offs; /* Relocated */
            r->file_name = loc_strdup(elf_file_name);
            r->file_offs = p->offset + p->file_size;
            r->size = (ContextAddress)(p->mem_size - p->file_size);
            r->flags = MM_FLAG_R | MM_FLAG_W;
            if (p->flags & PF_X) r->flags |= MM_FLAG_X;
            r->valid = MM_VALID_ADDR | MM_VALID_SIZE;
            r->dev = st.st_dev;
            r->ino = st.st_ino;
        }
        if (r != NULL && (p->flags & PF_X) == 0 && (r->addr + r->size) % p->align != 0) {
            r->size = r->size + p->align - (r->addr + r->size) % p->align;
        }
    }
    if (mem_map.region_cnt == 0) {
        ContextAddress addr = 0x10000;
        assert(file_addr_offs == 0);
        for (j = 0; j < f->section_cnt; j++) {
            ELF_Section * sec = f->sections + j;
            if (sec->name == NULL) continue;
            if (sec->flags & SHF_ALLOC) {
                MemoryRegion * r = NULL;
                if (mem_map.region_cnt >= mem_map.region_max) {
                    mem_map.region_max += 8;
                    mem_map.regions = (MemoryRegion *)loc_realloc(mem_map.regions, sizeof(MemoryRegion) * mem_map.region_max);
                }
                r = mem_map.regions + mem_map.region_cnt++;
                memset(r, 0, sizeof(MemoryRegion));
                r->addr = addr;
                r->size = (ContextAddress)sec->size;
                if (sec->type == SHT_NOBITS) r->bss = 1;
                r->dev = st.st_dev;
                r->ino = st.st_ino;
                r->file_name = loc_strdup(elf_file_name);
                r->sect_name = loc_strdup(sec->name);
                r->flags = MM_FLAG_R;
                if (sec->flags & SHF_WRITE) r->flags |= MM_FLAG_W;
                if (sec->flags & SHF_EXECINSTR) r->flags |= MM_FLAG_X;
                if (strcmp(sec->name, ".text") == 0) r->flags |= MM_FLAG_X;
                if (strcmp(sec->name, ".init.text") == 0) r->flags |= MM_FLAG_X;
                if (strcmp(sec->name, ".exit.text") == 0) r->flags |= MM_FLAG_X;
                r->valid = MM_VALID_ADDR | MM_VALID_SIZE;
                addr += r->size;
                addr = (addr + 0x10000) & ~(ContextAddress)0xffff;
            }
        }
    }
    if (mem_map.region_cnt == 0) {
        printf("File has no program headers.\n");
        exit(1);
    }
    memory_map_event_module_loaded(elf_ctx);
    mem_region_pos = -1;

    line_info_cnt = 0;
    file_has_line_info = 0;
    for (j = 0; j < f->section_cnt; j++) {
        ELF_Section * sec = f->sections + j;
        if (sec->name == NULL) continue;
        if (sec->size > 0 && (strcmp(sec->name, ".debug_line") == 0 || strcmp(sec->name, ".line") == 0)) {
            file_has_line_info = 1;
        }
    }

    reg_size = 0;
    memset(reg_defs, 0, sizeof(reg_defs));
    memset(reg_vals, 0, sizeof(reg_vals));
    for (j = 0; j < MAX_REGS - 1; j++) {
        RegisterDefinition * r = reg_defs + j;
        r->big_endian = f->big_endian;
        r->dwarf_id = (int16_t)(j == 0 ? MAX_REGS : j - 1);
        r->eh_frame_id = r->dwarf_id;
        r->name = reg_names[j];
        if (j == 0) {
            snprintf(reg_names[j], sizeof(reg_names[j]), "PC");
        }
        else {
            snprintf(reg_names[j], sizeof(reg_names[j]), "R%d", j - 1);
        }
        r->offset = reg_size;
        r->size = f->elf64 ? 8 : 4;
        if (j == 0) {
            r->role = "PC";
            r->no_write = 1;
        }
        reg_size += r->size;
    }

    pc = 0;
    elf_file = f;
    pass_cnt++;
}

static void test(void * args) {
    assert(test_posted);
    test_posted = 0;
    if (elf_file_name == NULL || mem_region_pos >= (int)mem_map.region_cnt) {
        if (file_has_line_info) {
            if (line_info_cnt == 0) {
                set_errno(ERR_OTHER, "Line info not accessable");
                error("address_to_line");
            }
            check_line_info();
        }
        next_file();
    }
    else {
        next_region();
    }
    assert(test_posted == 0);
    test_posted = 1;
    post_event_with_delay(test, NULL, 1);
}

static int file_name_comparator(const void * x, const void * y) {
    return strcmp(*(const char **)x, *(const char **)y);
}

static void add_dir(const char * dir_name) {
    DIR * dir = opendir(dir_name);
    char ** buf = NULL;
    unsigned buf_len = 0;
    unsigned buf_max = 0;
    unsigned i;

    if (dir == NULL) {
        printf("Cannot open '%s' directory\n", dir_name);
        fflush(stdout);
        exit(1);
    }
    for (;;) {
        struct dirent * e = readdir(dir);
        if (e == NULL) break;
        if (strcmp(e->d_name, ".") == 0) continue;
        if (strcmp(e->d_name, "..") == 0) continue;
        if (strcmp(e->d_name + strlen(e->d_name) - 6, ".debug") == 0) continue;
        if (strcmp(e->d_name + strlen(e->d_name) - 7, ".x86_64") == 0) continue;
        if (strcmp(e->d_name + strlen(e->d_name) - 4, ".txt") == 0) continue;
        if (buf_len >= buf_max) {
            buf_max = buf_max == 0 ? 256 : buf_max * 2;
            buf = (char **)loc_realloc(buf, buf_max * sizeof(char *));
        }
        buf[buf_len++] = loc_strdup(e->d_name);
    }
    closedir(dir);
    qsort(buf, buf_len, sizeof(char *), file_name_comparator);
    for (i = 0; i < buf_len; i++) {
        char * name = buf[i];
        char path[FILE_PATH_SIZE];
        struct stat st;
        if (strcmp(dir_name, ".") == 0) {
            strlcpy(path, name, sizeof(path));
        }
        else {
            snprintf(path, sizeof(path), "%s/%s", dir_name, name);
        }
        if (stat(path, &st) == 0) {
            if (S_ISDIR(st.st_mode)) {
                add_dir(path);
            }
            else {
                int fd = open(path, O_RDONLY | O_BINARY, 0);
                if (fd < 0) {
                    printf("File %s: %s\n", path, errno_to_str(errno));
                }
                else {
                    close(fd);
                    if (files_cnt >= files_max) {
                        files_max = files_max == 0 ? 256 : files_max * 2;
                        files = (char **)loc_realloc(files, files_max * sizeof(char *));
                    }
                    files[files_cnt++] = loc_strdup(path);
                }
            }
        }
        loc_free(name);
    }
    loc_free(buf);
}

void init_contexts_sys_dep(void) {
    const char * dir_name = ".";
    add_dir(dir_name);
    test_posted = 1;
    post_event(test, NULL);
}
