/*******************************************************************************
 * Copyright (c) 2007, 2015 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
 *******************************************************************************/

/*
 * This module implements caching of DWARF debug information.
 *
 * Functions in this module use exceptions to report errors, see exceptions.h
 */

#include <tcf/config.h>

#if ENABLE_ELF && ENABLE_DebugContext

#include <assert.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/myalloc.h>
#include <tcf/framework/trace.h>
#include <tcf/services/dwarf.h>
#include <tcf/services/dwarfio.h>
#include <tcf/services/dwarfcache.h>
#include <tcf/services/dwarfexpr.h>
#include <tcf/services/stacktrace.h>

#define OBJ_HASH(HashTable,ID) (((U4_T)(ID) + ((U4_T)(ID) >> 8)) % HashTable->mObjectHashSize)

#define OBJECT_ARRAY_SIZE 128

/* Pseudo object IDs for fundamental types */
#define OBJECT_ID_VOID(CompUnit) (~(CompUnit)->mFundTypeID - 0)
#define OBJECT_ID_CHAR(CompUnit) (~(CompUnit)->mFundTypeID - 1)
#define OBJECT_ID_LAST(Cache) (~(Cache)->mFundTypeID)

typedef struct ObjectArray {
    struct ObjectArray * mNext;
    ObjectInfo mArray[OBJECT_ARRAY_SIZE];
} ObjectArray;

typedef struct ObjectReference {
    ObjectInfo * obj;
    ObjectInfo * org;
} ObjectReference;

static DWARFCache * sCache;
static ELF_Section * sDebugSection;
static DIO_UnitDescriptor sUnitDesc;
static CompUnit * sCompUnit;
static ObjectInfo * sParentObject;
static ObjectInfo * sPrevSibling;
static ObjectReference * sObjRefs;
static U4_T sObjRefsCnt = 0;
static U4_T sObjRefsMax = 0;

static int sCloseListenerOK = 0;

unsigned calc_file_name_hash(const char * s) {
    unsigned h = 0;
    if (s != NULL) {
        unsigned l = strlen(s);
        while (l > 0) {
            unsigned g;
            unsigned char ch = s[--l];
            if (ch == '/') break;
            if (ch == '\\') break;
            h = (h << 4) + ch;
            g = h & 0xf0000000;
            if (g) h = (h ^ (g >> 24)) & ~g;
        }
    }
    return h;
}

static ObjectInfo * add_object_info(ContextAddress ID) {
    ObjectHashTable * HashTable = sCache->mObjectHashTable + sDebugSection->index;
    U4_T Hash = OBJ_HASH(HashTable, ID);
    ObjectInfo * Info = HashTable->mObjectHash[Hash];
    while (Info != NULL) {
        if (Info->mID == ID) return Info;
        Info = Info->mHashNext;
    }
    if (ID < OBJECT_ID_LAST(sCache)) {
        if (ID < sDebugSection->addr) str_exception(ERR_INV_DWARF, "Invalid entry reference");
        if (ID > sDebugSection->addr + sDebugSection->size) str_exception(ERR_INV_DWARF, "Invalid entry reference");
    }
    if (sCache->mObjectArrayPos >= OBJECT_ARRAY_SIZE) {
        ObjectArray * Buf = (ObjectArray *)loc_alloc_zero(sizeof(ObjectArray));
        Buf->mNext = sCache->mObjectList;
        sCache->mObjectList = Buf;
        sCache->mObjectArrayPos = 0;
    }
    Info = sCache->mObjectList->mArray + sCache->mObjectArrayPos++;
    Info->mHashNext = HashTable->mObjectHash[Hash];
    HashTable->mObjectHash[Hash] = Info;
    Info->mID = ID;
    return Info;
}

static CompUnit * add_comp_unit(ContextAddress ID) {
    ObjectInfo * Info = add_object_info(ID);
    if (Info->mCompUnit == NULL) {
        CompUnit * Unit = (CompUnit *)loc_alloc_zero(sizeof(CompUnit));
        Unit->mFile = sCache->mFile;
        Unit->mFundTypeID = sCache->mFundTypeID;
        Unit->mRegIdScope.big_endian = sCache->mFile->big_endian;
        Unit->mRegIdScope.machine = sCache->mFile->machine;
        Unit->mRegIdScope.os_abi = sCache->mFile->os_abi;
        Unit->mRegIdScope.elf64 = sCache->mFile->elf64;
        Unit->mRegIdScope.id_type = REGNUM_DWARF;
        Unit->mObject = Info;
        Info->mCompUnit = Unit;
        sCache->mFundTypeID += 2;
    }
    return Info->mCompUnit;
}

static CompUnit * find_comp_unit(ELF_Section * Section, ContextAddress ObjID) {
    ObjectHashTable * HashTable = sCache->mObjectHashTable + Section->index;
    if (sCompUnit != NULL && sCompUnit->mDesc.mSection == Section) {
        ContextAddress ID = sCompUnit->mDesc.mSection->addr + sCompUnit->mDesc.mUnitOffs;
        if (ID <= ObjID && ID + sCompUnit->mDesc.mUnitSize > ObjID) return sCompUnit;
    }
    if (HashTable->mCompUnitsIndex != NULL) {
        unsigned l = 0;
        unsigned h = HashTable->mCompUnitsIndexSize;
        while (l < h) {
            unsigned i = (l + h) / 2;
            CompUnit * unit = HashTable->mCompUnitsIndex[i];
            ContextAddress ID = unit->mDesc.mSection->addr + unit->mDesc.mUnitOffs;
            if (ID > ObjID) {
                h = i;
            }
            else if (ID + unit->mDesc.mUnitSize <= ObjID) {
                l = i + 1;
            }
            else {
                return unit;
            }
        }
    }
    return NULL;
}

static void add_type_unit(CompUnit * Unit) {
    unsigned Hash = (unsigned)(Unit->mDesc.mTypeSignature % sCache->mTypeUnitHashSize);
    assert(Unit->mDesc.mTypeOffset != 0);
    Unit->mNextTypeUnit = sCache->mTypeUnitHash[Hash];
    sCache->mTypeUnitHash[Hash] = Unit;
}

static CompUnit * find_type_unit(U8_T Signature) {
    unsigned Hash = (unsigned)(Signature % sCache->mTypeUnitHashSize);
    CompUnit * Unit = sCache->mTypeUnitHash[Hash];
    while (Unit != NULL) {
        if (Unit->mDesc.mTypeSignature == Signature) return Unit;
        Unit = Unit->mNextTypeUnit;
    }
    return NULL;
}

static void read_type_unit_header(U2_T Tag, U2_T Attr, U2_T Form) {
    if (Attr == 0) {
        if (Form) {
            assert(sParentObject == NULL);
            if (Tag != TAG_type_unit) str_exception(ERR_INV_DWARF, "Invalid .debug_types section");
            sCompUnit = add_comp_unit((ContextAddress)(sDebugSection->addr + dio_gEntryPos));
        }
        else {
            if (find_type_unit(sUnitDesc.mTypeSignature) != NULL) {
                str_exception(ERR_INV_DWARF, "Invalid .debug_types section");
            }
            sCompUnit->mDesc = sUnitDesc;
            add_type_unit(sCompUnit);
            add_object_info((ContextAddress)(sDebugSection->addr + sUnitDesc.mUnitOffs + sUnitDesc.mTypeOffset));
            sCompUnit = NULL;
            assert(sUnitDesc.mUnitSize > 0);
            dio_SetPos(sUnitDesc.mUnitOffs + sUnitDesc.mUnitSize);
        }
    }
}

static void read_object_info(U2_T Tag, U2_T Attr, U2_T Form);
static void read_object_refs(ELF_Section * Section);

ObjectInfo * find_object(ELF_Section * Section, ContextAddress ID) {
    DWARFCache * Cache = get_dwarf_cache(Section->file);
    ObjectHashTable * HashTable = Cache->mObjectHashTable + Section->index;
    ObjectInfo * Info = HashTable->mObjectHash[OBJ_HASH(HashTable, ID)];
    while (Info != NULL) {
        if (Info->mID == ID) return Info;
        Info = Info->mHashNext;
    }
#if ENABLE_DWARF_LAZY_LOAD
    if (Cache->lazy_loaded) {
        sCache = Cache;
        sCompUnit = NULL;
        sCompUnit = find_comp_unit(Section, ID);
        if (sCompUnit != NULL) {
            Trap trap;
            sUnitDesc = sCompUnit->mDesc;
            sDebugSection = sUnitDesc.mSection;
            sParentObject = NULL;
            sPrevSibling = NULL;
            dio_EnterSection(&sCompUnit->mDesc, sDebugSection, ID - sDebugSection->addr);
            if (set_trap(&trap)) {
                dio_ReadEntry(read_object_info, 0);
                Info = HashTable->mObjectHash[OBJ_HASH(HashTable, ID)];
                while (Info != NULL) {
                    if (Info->mID == ID) break;
                    Info = Info->mHashNext;
                }
                clear_trap(&trap);
            }
            dio_ExitSection();
            sDebugSection = NULL;
            read_object_refs(Section);
        }
        sCompUnit = NULL;
        sCache = NULL;
    }
#endif
    return Info;
}

static ObjectInfo * find_loaded_object(ELF_Section * Section, ContextAddress ID) {
    DWARFCache * Cache = (DWARFCache *)Section->file->dwarf_dt_cache;
    if (Cache != NULL) {
        ObjectHashTable * HashTable = Cache->mObjectHashTable + Section->index;
        ObjectInfo * Info = HashTable->mObjectHash[OBJ_HASH(HashTable, ID)];
        while (Info != NULL) {
            if (Info->mID == ID) return Info;
            Info = Info->mHashNext;
        }
    }
    return NULL;
}

static ObjectInfo * find_alt_object_info(ContextAddress ID) {
    unsigned i;
    ObjectInfo * Info = NULL;
    ELF_File * File = sCache->mFile;
    if (File->dwz_file == NULL) str_exception(errno, "Cannot open DWZ file");
    if (get_dwarf_cache(File->dwz_file) == NULL) str_exception(errno, "Cannot read DWZ file");
    for (i = 0; i < File->dwz_file->section_cnt; i++) {
        ELF_Section * section = File->dwz_file->sections + i;
        if (section->name != NULL && strcmp(section->name, ".debug_info") == 0) {
            if (Info != NULL) str_exception(ERR_INV_DWARF, "More then one .debug_info section in DWZ file");
            Info = find_loaded_object(section, ID);
        }
    }
    if (Info == NULL) str_exception(errno, "Invalid DWZ file reference");
    return Info;
}

static void add_object_reference(ObjectInfo * org, ObjectInfo * obj) {
#if ENABLE_DWARF_LAZY_LOAD
    if (org->mTag != 0 && obj == NULL) return;
#else
    if (obj == NULL) return;
#endif
    if (org->mCompUnit == NULL) org->mCompUnit = find_comp_unit(sDebugSection, org->mID);
    if (sObjRefsCnt >= sObjRefsMax) {
        sObjRefsMax += sObjRefsMax ? sObjRefsMax * 2 : 256;
        sObjRefs = (ObjectReference *)loc_realloc(sObjRefs, sizeof(ObjectReference) * sObjRefsMax);
    }
    if (obj != NULL) obj->mFlags |= DOIF_load_mark;
    sObjRefs[sObjRefsCnt].org = org;
    sObjRefs[sObjRefsCnt].obj = obj;
    sObjRefsCnt++;
}

static U4_T get_fund_type_size(CompUnit * Unit, U2_T ft) {
    switch (ft) {
    case FT_char          :
    case FT_signed_char   :
    case FT_unsigned_char :
        return 1;
    case FT_short         :
    case FT_signed_short  :
    case FT_unsigned_short:
        return 2;
    case FT_integer       :
    case FT_signed_integer:
    case FT_unsigned_integer:
        return 4;
    case FT_long          :
    case FT_signed_long   :
    case FT_unsigned_long :
        return Unit->mFile->elf64 ? 8 : 4;
    case FT_pointer       :
        return Unit->mDesc.mAddressSize;
    case FT_float         :
        return 4;
    case FT_dbl_prec_float:
        return 8;
    case FT_complex       :
        return 8;
    case FT_dbl_prec_complex:
        return 16;
    case FT_boolean       :
        return 4;
    case FT_void          :
        return 0;
    }
    str_exception(ERR_INV_DWARF, "Invalid fundamental type code");
    return 0;
}

static void read_mod_fund_type(U2_T Form, ObjectInfo ** Type) {
    U1_T * Buf;
    size_t BufSize;
    size_t BufPos;
    int i;
    U2_T FT = 0;
    dio_ChkBlock(Form, &Buf, &BufSize);
    for (i = 0; i < 2; i++) {
        FT |= (U2_T)Buf[BufSize - 2 +
            (sDebugSection->file->big_endian ? 1 - i : i)] << (i * 8);
    }
    *Type = add_object_info((ContextAddress)(sDebugSection->addr + dio_GetPos() - 2));
    (*Type)->mTag = TAG_fund_type;
    (*Type)->mCompUnit = sCompUnit;
    (*Type)->u.mFundType = FT;
    BufPos = BufSize - 2;
    while (BufPos > 0) {
        U2_T Tag = 0;
        ObjectInfo * Mod = NULL;
        switch (Buf[--BufPos]) {
        case MOD_volatile:
        case MOD_const:
            continue;
        case MOD_pointer_to:
            Tag = TAG_mod_pointer;
            break;
        case MOD_reference_to:
            Tag = TAG_mod_reference;
            break;
        default:
            str_exception(ERR_INV_DWARF, "Invalid type modifier code");
        }
        Mod = add_object_info((ContextAddress)(sDebugSection->addr + dio_GetPos() - BufSize + BufPos));
        Mod->mTag = Tag;
        Mod->mCompUnit = sCompUnit;
        Mod->mType = *Type;
        *Type = Mod;
    }
}

static void read_mod_user_def_type(U2_T Form, ObjectInfo ** Type) {
    U1_T * Buf;
    size_t BufSize;
    size_t BufPos;
    int i;
    U4_T Ref = 0;
    dio_ChkBlock(Form, &Buf, &BufSize);
    for (i = 0; i < 4; i++) {
        Ref |= (U4_T)Buf[BufSize - 4 +
            (sDebugSection->file->big_endian ? 3 - i : i)] << (i * 8);
    }
    *Type = add_object_info((ContextAddress)(sDebugSection->addr + Ref));
    add_object_reference(*Type, NULL);
    BufPos = BufSize - 4;
    while (BufPos > 0) {
        U2_T Tag = 0;
        ObjectInfo * Mod = NULL;
        switch (Buf[--BufPos]) {
        case MOD_volatile:
        case MOD_const:
            continue;
        case MOD_pointer_to:
            Tag = TAG_mod_pointer;
            break;
        case MOD_reference_to:
            Tag = TAG_mod_reference;
            break;
        default:
            str_exception(ERR_INV_DWARF, "Invalid type modifier code");
        }
        Mod = add_object_info((ContextAddress)(sDebugSection->addr + dio_GetPos() - BufSize + BufPos));
        Mod->mTag = Tag;
        Mod->mCompUnit = sCompUnit;
        Mod->mType = *Type;
        *Type = Mod;
    }
}

static I8_T read_long_value(void) {
    switch (get_fund_type_size(sCompUnit, FT_long)) {
    case 4: return (I4_T)dio_ReadU4();
    case 8: return (I8_T)dio_ReadU8();
    }
    str_exception(ERR_OTHER, "Invalid size of long int");
    return 0;
}

static void read_subscr_data(U2_T Form, ObjectInfo * Array) {
    U1_T * Buf;
    size_t BufSize;
    U8_T BufEnd = 0;
    U8_T OrgPos = dio_GetPos();
    ObjectInfo ** Children = &Array->mChildren;

    assert(Array->mChildren == NULL);
    assert(Array->mType == NULL);

    dio_ChkBlock(Form, &Buf, &BufSize);
    dio_SetPos(Buf - (U1_T *)sDebugSection->data);
    BufEnd = dio_GetPos() + BufSize;
    while (dio_GetPos() < BufEnd) {
        ObjectInfo * Type = NULL;
        U1_T Fmt = dio_ReadU1();
        switch (Fmt) {
        case FMT_FT_C_C:
        case FMT_FT_C_X:
        case FMT_FT_X_C:
        case FMT_FT_X_X:
            Type = add_object_info((ContextAddress)(sDebugSection->addr + dio_GetPos()));
            Type->mTag = TAG_fund_type;
            Type->mCompUnit = sCompUnit;
            Type->u.mFundType = dio_ReadU2();
            break;
        case FMT_UT_C_C:
        case FMT_UT_C_X:
        case FMT_UT_X_C:
        case FMT_UT_X_X:
            dio_ReadAttribute(AT_subscr_data, FORM_REF);
            Type = add_object_info((ContextAddress)dio_gFormData);
            add_object_reference(Type, NULL);
            break;
        }
        if (Type != NULL) {
            ObjectInfo * Range = add_object_info((ContextAddress)(sDebugSection->addr + dio_GetPos()));
            Range->mTag = TAG_index_range;
            Range->mCompUnit = sCompUnit;
            Range->mType = Type;
            Range->u.mRange.mFmt = Fmt;
            switch (Fmt) {
            case FMT_FT_C_C:
            case FMT_FT_C_X:
            case FMT_UT_C_C:
            case FMT_UT_C_X:
                Range->u.mRange.mLow.mValue = read_long_value();
                break;
            case FMT_FT_X_C:
            case FMT_FT_X_X:
            case FMT_UT_X_C:
            case FMT_UT_X_X:
                dio_ReadAttribute(0, FORM_BLOCK2);
                Range->u.mRange.mLow.mExpr.mAddr = (U1_T *)dio_gFormDataAddr;
                Range->u.mRange.mLow.mExpr.mSize = dio_gFormDataSize;
                break;
            }
            switch (Fmt) {
            case FMT_FT_C_C:
            case FMT_FT_X_C:
            case FMT_UT_C_C:
            case FMT_UT_X_C:
                Range->u.mRange.mHigh.mValue = read_long_value();
                break;
            case FMT_FT_C_X:
            case FMT_FT_X_X:
            case FMT_UT_C_X:
            case FMT_UT_X_X:
                dio_ReadAttribute(0, FORM_BLOCK2);
                Range->u.mRange.mHigh.mExpr.mAddr = (U1_T *)dio_gFormDataAddr;
                Range->u.mRange.mHigh.mExpr.mSize = dio_gFormDataSize;
                break;
            }
            *Children = Range;
            Children = &Range->mSibling;
        }
        else if (Fmt == FMT_ET) {
            U2_T x = dio_ReadU2();
            U2_T Attr = (x & 0xfff0u) >> 4;
            U2_T Form = x & 0xfu;
            dio_ReadAttribute(Attr, Form);
            switch (Attr) {
            case AT_fund_type:
                dio_ChkData(Form);
                Type = add_object_info((ContextAddress)(sDebugSection->addr + dio_GetPos() - dio_gFormDataSize));
                Type->mTag = TAG_fund_type;
                Type->mCompUnit = sCompUnit;
                Type->u.mFundType = (U2_T)dio_gFormData;
                break;
            case AT_user_def_type:
                dio_ChkRef(Form);
                Type = add_object_info((ContextAddress)dio_gFormData);
                add_object_reference(Type, NULL);
                break;
            case AT_mod_fund_type:
                read_mod_fund_type(Form, &Type);
                break;
            case AT_mod_u_d_type:
                read_mod_user_def_type(Form, &Type);
                break;
            default:
                str_exception(ERR_INV_DWARF, "Invalid array element type format");
            }
            Array->mType = Type;
        }
        else {
            str_exception(ERR_INV_DWARF, "Invalid array subscription format");
        }
    }
    dio_SetPos(OrgPos);
}

static void read_object_info(U2_T Tag, U2_T Attr, U2_T Form) {
    static ObjectInfo * Info;
    static U8_T Sibling;
    static int HasChildren;
    static int Skip;
    static int high_pc_offs;

    if (Skip && Attr && Attr != AT_sibling) return;

    switch (Attr) {
    case 0:
        if (Form) {
            /* Initialization: executed before debug entry processing */
            high_pc_offs = 0;
            if (Tag == TAG_compile_unit || Tag == TAG_partial_unit || Tag == TAG_type_unit) {
                CompUnit * Unit = add_comp_unit((ContextAddress)(sDebugSection->addr + dio_gEntryPos));
                assert(sParentObject == NULL);
                Info = Unit->mObject;
                assert(Info->mTag == 0);
                sCompUnit = Unit;
            }
            else {
                Info = add_object_info((ContextAddress)(sDebugSection->addr + dio_gEntryPos));
            }
            if (sParentObject) {
                Info->mParent = sParentObject;
                if (sParentObject->mFlags & DOIF_need_frame) {
                    /* Allow frame in get_symbol_container() */
                    Info->mFlags |= DOIF_need_frame;
                }
            }
            HasChildren = Form == DWARF_ENTRY_HAS_CHILDREN;
            Sibling = 0;
            Skip = Info->mTag != 0;
            if (Skip) {
                /* Object is already loaded */
                assert(Info->mTag == Tag);
                assert(Tag != TAG_compile_unit);
                assert(Tag != TAG_partial_unit);
                assert(Tag != TAG_type_unit);
                assert(Info->mCompUnit == sCompUnit);
                return;
            }
            Info->mTag = Tag;
            Info->mCompUnit = sCompUnit;
        }
        else {
            /* Finalization: executed after debug entry processing */
            if (high_pc_offs && !(Info->mFlags & DOIF_ranges) && (Info->mFlags & DOIF_low_pc)) {
                Info->u.mCode.mHighPC.mAddr += Info->u.mCode.mLowPC;
            }
            switch (Tag) {
            case TAG_compile_unit:
            case TAG_partial_unit:
            case TAG_type_unit:
                {
                    ObjectHashTable * HashTable = sCache->mObjectHashTable + sDebugSection->index;
                    assert(HashTable->mCompUnitsIndex == NULL);
                    if (Sibling == 0) Sibling = sUnitDesc.mUnitOffs + sUnitDesc.mUnitSize;
                    sCompUnit->mDesc = sUnitDesc;
                    HashTable->mCompUnitsIndexSize++;
                    if (Info->mFlags & DOIF_low_pc) sCompUnit->mTextSection = Info->u.mCode.mSection;
                }
                break;
            case TAG_subprogram:
            case TAG_global_subroutine:
            case TAG_inlined_subroutine:
            case TAG_subroutine_type:
            case TAG_subroutine:
            case TAG_entry_point:
            case TAG_pointer_type:
            case TAG_mod_pointer:
            case TAG_const_type:
            case TAG_volatile_type:
                if (Info->mType == NULL) {
                    /* NULL here means "void" */
                    Info->mType = add_object_info(OBJECT_ID_VOID(sCompUnit));
                    if (Info->mType->mTag == 0) {
                        Info->mType->mTag = TAG_fund_type;
                        Info->mType->mCompUnit = sCompUnit;
                        Info->mType->u.mFundType = FT_void;
                        switch (Info->mCompUnit->mLanguage) {
                        case LANG_C:
                        case LANG_C89:
                        case LANG_C99:
                        case LANG_C_PLUS_PLUS:
                            Info->mType->mName = "void";
                            break;
                        }
                    }
                }
                break;
            case TAG_string_type:
                if (Info->mType == NULL) {
                    /* NULL here means "char" */
                    Info->mType = add_object_info(OBJECT_ID_CHAR(sCompUnit));
                    if (Info->mType->mTag == 0) {
                        Info->mType->mTag = TAG_fund_type;
                        Info->mType->mCompUnit = sCompUnit;
                        Info->mType->u.mFundType = FT_char;
                        switch (Info->mCompUnit->mLanguage) {
                        case LANG_C:
                        case LANG_C89:
                        case LANG_C99:
                        case LANG_C_PLUS_PLUS:
                            Info->mType->mName = "char";
                            break;
                        }
                    }
                }
                break;
            }
            if (sPrevSibling != NULL) sPrevSibling->mSibling = Info;
            else if (sParentObject != NULL) sParentObject->mChildren = Info;
            else if (Tag == TAG_compile_unit) sCache->mObjectHashTable[sDebugSection->index].mCompUnits = Info;
            else if (Tag == TAG_partial_unit) sCache->mObjectHashTable[sDebugSection->index].mCompUnits = Info;
            else if (Tag == TAG_type_unit) sCache->mObjectHashTable[sDebugSection->index].mCompUnits = Info;
            sPrevSibling = Info;
            if (Skip && Sibling != 0) {
                dio_SetPos(Sibling);
                return;
            }
            if (Tag == TAG_enumerator && Info->mType == NULL) Info->mType = sParentObject;
#if ENABLE_DWARF_LAZY_LOAD
            if (sCache->mFile->lock_cnt == 0 && Sibling != 0 && sDebugSection->size >= 0x40000) {
                switch (Tag) {
                case TAG_union_type:
                case TAG_array_type:
                case TAG_class_type:
                case TAG_structure_type:
                case TAG_subroutine_type:
                case TAG_global_subroutine:
                case TAG_subroutine:
                case TAG_subprogram:
                    sCache->lazy_loaded = 1;
                    dio_SetPos(Sibling);
                    return;
                }
            }
#endif
            Info->mFlags |= DOIF_children_loaded;
            if (Sibling != 0 || HasChildren) {
                U8_T SiblingPos = Sibling;
                ObjectInfo * Parent = sParentObject;
                ObjectInfo * PrevSibling = sPrevSibling;
                sParentObject = Info;
                sPrevSibling = NULL;
                for (;;) {
                    if (SiblingPos > 0 && dio_GetPos() >= SiblingPos) break;
                    if (!dio_ReadEntry(read_object_info, 0)) break;
                }
                if (SiblingPos > dio_GetPos()) dio_SetPos(SiblingPos);
                sParentObject = Parent;
                sPrevSibling = PrevSibling;
            }
        }
        break;
    case AT_sibling:
        dio_ChkRef(Form);
        Sibling = dio_gFormData - sDebugSection->addr;
        break;
    case AT_type:
        if (Form == FORM_GNU_REF_ALT) {
            Info->mType = find_alt_object_info((ContextAddress)dio_gFormData);
            add_object_reference(Info->mType, NULL);
            break;
        }
        if (Form == FORM_REF_SIG8) {
            CompUnit * Unit = find_type_unit(dio_gFormData);
            if (Unit != NULL) {
                ContextAddress ID = (ContextAddress)(Unit->mDesc.mSection->addr +
                    Unit->mDesc.mUnitOffs + Unit->mDesc.mTypeOffset);
                Info->mType = find_loaded_object(Unit->mDesc.mSection, ID);
            }
            if (Info->mType == NULL) str_exception(ERR_INV_DWARF, "Invalid type unit reference");
            add_object_reference(Info->mType, NULL);
            break;
        }
        dio_ChkRef(Form);
        Info->mType = add_object_info((ContextAddress)dio_gFormData);
        add_object_reference(Info->mType, NULL);
        break;
    case AT_fund_type:
        dio_ChkData(Form);
        Info->mType = add_object_info((ContextAddress)(sDebugSection->addr + dio_GetPos() - dio_gFormDataSize));
        Info->mType->mTag = TAG_fund_type;
        Info->mType->mCompUnit = sCompUnit;
        Info->mType->u.mFundType = (U2_T)dio_gFormData;
        break;
    case AT_user_def_type:
        dio_ChkRef(Form);
        Info->mType = add_object_info((ContextAddress)dio_gFormData);
        add_object_reference(Info->mType, NULL);
        break;
    case AT_mod_fund_type:
        read_mod_fund_type(Form, &Info->mType);
        break;
    case AT_mod_u_d_type:
        read_mod_user_def_type(Form, &Info->mType);
        break;
    case AT_encoding:
        if (Tag == TAG_base_type) {
            Info->u.mFundType = (U2_T)dio_gFormData;
        }
        break;
    case AT_subscr_data:
        read_subscr_data(Form, Info);
        break;
    case AT_name:
        dio_ChkString(Form);
        if (*(char *)dio_gFormDataAddr) Info->mName = (char *)dio_gFormDataAddr;
        break;
    case AT_specification_v2:
        if (Form == FORM_GNU_REF_ALT) {
            add_object_reference(find_alt_object_info((ContextAddress)dio_gFormData), Info);
        }
        else {
            dio_ChkRef(Form);
            add_object_reference(add_object_info((ContextAddress)dio_gFormData), Info);
        }
        Info->mFlags |= DOIF_specification;
        break;
    case AT_abstract_origin:
        if (Form == FORM_GNU_REF_ALT) {
            add_object_reference(find_alt_object_info((ContextAddress)dio_gFormData), Info);
        }
        else {
            dio_ChkRef(Form);
            add_object_reference(add_object_info((ContextAddress)dio_gFormData), Info);
        }
        Info->mFlags |= DOIF_abstract_origin;
        break;
    case AT_extension:
        dio_ChkRef(Form);
        add_object_reference(add_object_info((ContextAddress)dio_gFormData), Info);
        Info->mFlags |= DOIF_extension;
        break;
    case AT_MIPS_linkage_name:
        Info->mFlags |= DOIF_mips_linkage_name;
        break;
    case AT_linkage_name:
        Info->mFlags |= DOIF_linkage_name;
        break;
    case AT_is_optional:
        dio_ChkFlag(Form);
        if (dio_gFormData) Info->mFlags |= DOIF_optional;
        break;
    case AT_low_pc:
        dio_ChkAddr(Form);
        Info->u.mCode.mLowPC = (ContextAddress)dio_gFormData;
        if (dio_gFormSection) Info->u.mCode.mSection = dio_gFormSection;
        Info->mFlags |= DOIF_low_pc;
        break;
    case AT_high_pc:
        if (Info->mFlags & DOIF_ranges) break;
        if (Form != FORM_ADDR) {
            dio_ChkData(Form);
            high_pc_offs = 1;
        }
        else if (dio_gFormSection) {
            Info->u.mCode.mSection = dio_gFormSection;
        }
        Info->u.mCode.mHighPC.mAddr = (ContextAddress)dio_gFormData;
        break;
    case AT_ranges:
        dio_ChkData(Form);
        Info->u.mCode.mHighPC.mRanges = dio_gFormData;
        Info->mFlags |= DOIF_ranges;
        break;
    case AT_external:
        dio_ChkFlag(Form);
        if (dio_gFormData) Info->mFlags |= DOIF_external;
        break;
    case AT_artificial:
        dio_ChkFlag(Form);
        if (dio_gFormData) Info->mFlags |= DOIF_artificial;
        break;
    case AT_declaration:
        dio_ChkFlag(Form);
        if (dio_gFormData) Info->mFlags |= DOIF_declaration;
        break;
    case AT_private:
        if (Form == FORM_STRING) {
            /* Diab 4.1 */
            Info->mFlags |= DOIF_private;
            break;
        }
        dio_ChkFlag(Form);
        if (dio_gFormData) Info->mFlags |= DOIF_private;
        break;
    case AT_protected:
        if (Form == FORM_STRING) {
            /* Diab 4.1 */
            Info->mFlags |= DOIF_protected;
            break;
        }
        dio_ChkFlag(Form);
        if (dio_gFormData) Info->mFlags |= DOIF_protected;
        break;
    case AT_public:
        if (Form == FORM_STRING) {
            /* Diab 4.1 */
            Info->mFlags |= DOIF_public;
            break;
        }
        dio_ChkFlag(Form);
        if (dio_gFormData) Info->mFlags |= DOIF_public;
        break;
    case AT_accessibility:
        dio_ChkData(Form);
        switch (dio_gFormData) {
        case DW_ACCESS_private  : Info->mFlags |= DOIF_private; break;
        case DW_ACCESS_protected: Info->mFlags |= DOIF_protected; break;
        case DW_ACCESS_public   : Info->mFlags |= DOIF_public; break;
        }
        break;
    case AT_location:
        Info->mFlags |= DOIF_location;
        if (Form == FORM_DATA4 || Form == FORM_DATA8 || Form == FORM_SEC_OFFSET || Tag == TAG_formal_parameter) {
            Info->mFlags |= DOIF_need_frame;
        }
        break;
    case AT_data_location:
        if (sCompUnit->mDesc.mVersion <= 1) {
            /* AT_mangled */
            Info->mFlags |= DOIF_mangled_name;
            break;
        }
        Info->mFlags |= DOIF_data_location;
        if (Form == FORM_DATA4 || Form == FORM_DATA8 || Form == FORM_SEC_OFFSET) {
            Info->mFlags |= DOIF_need_frame;
        }
        break;
    case AT_string_length:
    case AT_return_addr:
    case AT_data_member_location:
    case AT_frame_base:
    case AT_segment:
    case AT_static_link:
    case AT_use_location:
    case AT_vtable_elem_location:
    case AT_upper_bound:
    case AT_lower_bound:
        /* Note: FORM_DATA4, FORM_DATA8 and FORM_SEC_OFFSET are location lists.
         * Location list needs PC, so we set DOIF_need_frame because of that */
        if (Form == FORM_DATA4 || Form == FORM_DATA8 || Form == FORM_SEC_OFFSET) {
            Info->mFlags |= DOIF_need_frame;
        }
        break;
    case AT_const_value:
        Info->mFlags |= DOIF_const_value;
        break;
    }

    if (Tag == TAG_compile_unit || Tag == TAG_partial_unit || Tag == TAG_type_unit) {
        CompUnit * Unit = Info->mCompUnit;
        switch (Attr) {
        case AT_comp_dir:
            dio_ChkString(Form);
            Unit->mDir = (char *)dio_gFormDataAddr;
            break;
        case AT_stmt_list:
            if (Form == FORM_ADDR) {
                /* Workaround: some compilers incorrectly use FORM_ADDR for AT_stmt_list */
                Unit->mLineInfoOffs = dio_gFormData;
                if (dio_gFormSection != NULL) Unit->mLineInfoOffs -= dio_gFormSection->addr;
                break;
            }
            dio_ChkData(Form);
            Unit->mLineInfoOffs = dio_gFormData;
            break;
        case AT_base_types:
            Unit->mBaseTypes = add_comp_unit((ContextAddress)dio_gFormData);
            break;
        case AT_language:
            dio_ChkData(Form);
            Unit->mLanguage = (U2_T)dio_gFormData;
            break;
        }
    }
}

static void read_object_refs(ELF_Section * Section) {
    U4_T pass_cnt = 0;
#if ENABLE_DWARF_LAZY_LOAD
    if (sCache->lazy_loaded) {
        /*
         * Build transitive closure of DWARF objects graph:
         * load objects that are referenced by already loaded objects.
         */
        U4_T pos = 0;
        sCompUnit = NULL;
        while (pos < sObjRefsCnt) {
            ObjectReference ref = sObjRefs[pos++];
            if (ref.org->mCompUnit == NULL) ref.org->mCompUnit = find_comp_unit(Section, ref.org->mID);
            if (ref.org->mTag == 0) {
                Trap trap;
                ObjectInfo * obj = ref.org;
                sCompUnit = obj->mCompUnit;
                sUnitDesc = sCompUnit->mDesc;
                sDebugSection = sUnitDesc.mSection;
                sParentObject = NULL;
                sPrevSibling = NULL;
                dio_EnterSection(&sCompUnit->mDesc, sDebugSection, obj->mID - sDebugSection->addr);
                if (set_trap(&trap)) {
                    dio_ReadEntry(read_object_info, 0);
                    clear_trap(&trap);
                }
                dio_ExitSection();
                sDebugSection = NULL;
                sCompUnit = NULL;
                if (trap.error) exception(trap.error);
            }
        }
    }
#endif
    /*
     * Propagate attributes like mName and mType along chain of references.
     */
    for (;;) {
        U4_T pos = 0;
        int fwd = 0;
        while (pos < sObjRefsCnt) {
            ObjectReference ref = sObjRefs[pos++];
            if (ref.obj != NULL) {
                assert(ref.org->mTag != 0);
                if (ref.org->mFlags & DOIF_load_mark) {
                    fwd = 1;
                }
                else {
                    if (ref.obj->mName == NULL) ref.obj->mName = ref.org->mName;
                    if (ref.obj->mType == NULL || ref.obj->mType->mID == OBJECT_ID_VOID(ref.obj->mCompUnit)) ref.obj->mType = ref.org->mType;
                    ref.obj->mFlags |= ref.org->mFlags & ~(DOIF_children_loaded | DOIF_declaration | DOIF_specification);
                    if (ref.obj->mFlags & DOIF_specification) {
                        ref.org->mDefinition = ref.obj;
                        if ((ref.obj->mFlags & (DOIF_low_pc | DOIF_ranges)) == 0) {
                            ref.obj->mFlags |= ref.org->mFlags & DOIF_declaration;
                        }
                    }
                    if (ref.obj->mFlags & DOIF_abstract_origin) {
                        if ((ref.obj->mTag == TAG_variable && (ref.obj->mFlags & DOIF_external)) ||
                                ref.obj->mTag == TAG_subprogram ||
                                (ref.obj->mTag == TAG_formal_parameter && ref.obj->mParent != NULL && ref.obj->mParent->mTag == TAG_subprogram))
                            ref.org->mDefinition = ref.obj;
                    }
                    if (ref.obj->mFlags & DOIF_external) {
                        ObjectInfo * cls = ref.org;
                        while (cls->mParent != NULL &&
                            (cls->mParent->mTag == TAG_class_type || cls->mParent->mTag == TAG_structure_type)) {
                            cls = cls->mParent;
                        }
                        cls->mFlags |= DOIF_external;
                    }
                    ref.obj->mFlags &= ~DOIF_load_mark;
                    ref.obj = NULL;
                }
            }
        }
        if (!fwd) break;
        if (pass_cnt >= 8) break;
        pass_cnt++;
    }
    sObjRefsCnt = 0;
}

static int addr_ranges_comparator(const void * x, const void * y) {
    UnitAddressRange * rx = (UnitAddressRange *)x;
    UnitAddressRange * ry = (UnitAddressRange *)y;
    if (rx->mAddr < ry->mAddr) return -1;
    if (rx->mAddr > ry->mAddr) return +1;
    if (rx->mSection < ry->mSection) return -1;
    if (rx->mSection > ry->mSection) return +1;
    if (rx->mUnit->mObject->mID < ry->mUnit->mObject->mID) return -1;
    if (rx->mUnit->mObject->mID > ry->mUnit->mObject->mID) return +1;
    return 0;
}

static void add_addr_range(ELF_Section * sec, CompUnit * unit, ContextAddress addr, ContextAddress size) {
    UnitAddressRange * range = NULL;
    if (addr + size <= addr) {
        if (size == 0) return;
        size = 0 - addr;
    }
    if (size > sCache->mAddrRangesMaxSize) sCache->mAddrRangesMaxSize = size;
    if (sCache->mAddrRangesCnt >= sCache->mAddrRangesMax) {
        sCache->mAddrRangesMax = sCache->mAddrRangesMax == 0 ? 64 : sCache->mAddrRangesMax * 2;
        sCache->mAddrRanges = (UnitAddressRange *)loc_realloc(sCache->mAddrRanges, sizeof(UnitAddressRange) * sCache->mAddrRangesMax);
    }
    range = sCache->mAddrRanges + sCache->mAddrRangesCnt++;
    memset(range, 0, sizeof(UnitAddressRange));
    if (sec != NULL) {
        assert(sec->file == sCache->mFile);
        range->mSection = sec->index;
        sCache->mAddrRangesRelocatable = 1;
    }
    range->mAddr = addr;
    range->mSize = size;
    range->mUnit = unit;
}

static void add_object_addr_ranges(ObjectInfo * info) {
    CompUnit * unit = info->mCompUnit;
    ContextAddress base = info->u.mCode.mLowPC;
    assert(info->mFlags & DOIF_low_pc);
    if (info->mFlags & DOIF_ranges) {
        if (sCache->mDebugRanges != NULL) {
            dio_EnterSection(&unit->mDesc, sCache->mDebugRanges, info->u.mCode.mHighPC.mRanges);
            for (;;) {
                U8_T AddrMax = ~(U8_T)0;
                ELF_Section * sec_x = NULL;
                ELF_Section * sec_y = NULL;
                U8_T x = dio_ReadAddress(&sec_x);
                U8_T y = dio_ReadAddress(&sec_y);
                if (x == 0 && y == 0) break;
                if (unit->mDesc.mAddressSize < 8) AddrMax = ((U8_T)1 << unit->mDesc.mAddressSize * 8) - 1;
                if (x == AddrMax) {
                    base = (ContextAddress)y;
                }
                else if (y > x) {
                    ELF_Section * sec = info->u.mCode.mSection;
                    if (sec == NULL) sec = unit->mTextSection;
                    x = base + x;
                    y = base + y;
                    if (sec_x != NULL) sec = sec_x;
                    else if (sec_y != NULL) sec = sec_y;
                    add_addr_range(sec, unit, (ContextAddress)x, (ContextAddress)(y - x));
                }
            }
            dio_ExitSection();
        }
    }
    else if (info->u.mCode.mHighPC.mAddr > base) {
        ELF_Section * sec = info->u.mCode.mSection;
        if (sec == NULL) sec = unit->mTextSection;
        add_addr_range(sec, unit, base, info->u.mCode.mHighPC.mAddr - base);
    }
}

static void load_addr_ranges(ELF_Section * debug_info) {
    Trap trap;
    unsigned idx;
    ELF_File * file = sCache->mFile;

    memset(&trap, 0, sizeof(trap));
    for (idx = 1; idx < file->section_cnt; idx++) {
        ELF_Section * sec = file->sections + idx;
        if (sec->size == 0) continue;
        if (sec->name == NULL) continue;
        if (strcmp(sec->name, ".debug_aranges") == 0) {
            sCompUnit = NULL;
            dio_EnterSection(NULL, sec, 0);
            if (set_trap(&trap)) {
                while (dio_GetPos() < sec->size) {
                    int dwarf64 = 0;
                    U8_T size = dio_ReadU4();
                    U8_T next = 0;
                    if (size == 0xffffffffu) {
                        dwarf64 = 1;
                        size = dio_ReadU8();
                    }
                    next = dio_GetPos() + size;
                    if (dio_ReadU2() == 2) {
                        ELF_Section * unit_sec = NULL;
                        U8_T unit_addr = dio_ReadAddressX(&unit_sec, dwarf64 ? 8 : 4);
                        U1_T addr_size = dio_ReadU1();
                        U1_T segm_size = dio_ReadU1();
                        if (segm_size != 0) str_exception(ERR_INV_DWARF, "segment descriptors are not supported");
                        if (unit_sec == NULL) unit_sec = debug_info;
                        sCompUnit = find_comp_unit(unit_sec, (ContextAddress)unit_addr);
                        if (sCompUnit == NULL) str_exception(ERR_INV_DWARF, "invalid .debug_aranges section");
                        sCompUnit->mObject->mFlags |= DOIF_aranges;
                        while (dio_GetPos() % (addr_size * 2) != 0) dio_Skip(1);
                        for (;;) {
                            ELF_Section * addr_sec = NULL;
                            ELF_Section * size_sec = NULL;
                            ContextAddress addr = (ContextAddress)dio_ReadAddressX(&addr_sec, addr_size);
                            ContextAddress size = (ContextAddress)dio_ReadAddressX(&size_sec, addr_size);
                            /* GCC 4.1.2 can generate 'empty entries' at the beginning of the list */
                            if (addr == 0 && size == 0 && dio_GetPos() + addr_size * 2 > next) break;
                            if (size != 0) add_addr_range(addr_sec, sCompUnit, addr, size);
                        }
                    }
                    dio_SetPos(next);
                }
                clear_trap(&trap);
            }
            dio_ExitSection();
            if (trap.error) break;
        }
    }
    if (trap.error) exception(trap.error);
    for (idx = 1; idx < file->section_cnt; idx++) {
        ObjectInfo * info = sCache->mObjectHashTable[idx].mCompUnits;
        while (info != NULL) {
            if (info->mFlags & DOIF_low_pc) add_object_addr_ranges(info);

            if ((info->mFlags & DOIF_aranges) == 0 && (info->mFlags & DOIF_ranges) == 0 && info->u.mCode.mHighPC.mAddr == 0) {
                /* Unit does not have PC ranges data. As a workaround, add line info ranges */
                unsigned i;
                CompUnit * unit = info->mCompUnit;
                if (set_trap(&trap)) {
                    load_line_numbers(unit);
                    if (unit->mStatesCnt >= 2) {
                        for (i = 0; i < unit->mStatesCnt - 1; i++) {
                            LineNumbersState * x = unit->mStates + i;
                            LineNumbersState * y = unit->mStates + i + 1;
                            if (x->mSection != y->mSection) continue;
                            if (x->mAddress == y->mAddress) continue;
                            if (x->mFlags & LINE_EndSequence) continue;
                            add_addr_range(unit->mTextSection, unit, x->mAddress, y->mAddress - x->mAddress);
                        }
                    }
                    clear_trap(&trap);
                }
            }

            {
                /* Workaround for GCC bug - certain ranges are missing in both ".debug_aranges" and the unit info.
                 * Add address ranges of the underlying scopes. */
                ObjectInfo * obj = info->mChildren;
                assert(info->mFlags & DOIF_children_loaded);
                while (obj != NULL) {
                    if (obj->mFlags & DOIF_low_pc) add_object_addr_ranges(obj);
                    obj = obj->mSibling;
                }
            }

            info = info->mSibling;
        }
    }
    if (sCache->mAddrRangesCnt > 1) {
        unsigned i, j = 0;
        qsort(sCache->mAddrRanges, sCache->mAddrRangesCnt, sizeof(UnitAddressRange), addr_ranges_comparator);
        for (i = 0; i < sCache->mAddrRangesCnt - 1; i++) {
            UnitAddressRange * x = sCache->mAddrRanges + i;
            UnitAddressRange * y = x + 1;
            ContextAddress x_end = x->mAddr + x->mSize;
            if (x->mSection == y->mSection && x->mUnit == y->mUnit && (x_end == 0 || x_end >= y->mAddr)) {
                /* Skip duplicate entry */
                ContextAddress y_end = y->mAddr + y->mSize;
                y->mAddr = x->mAddr;
                if (x_end == 0 || (y_end != 0 && x_end > y_end)) {
                    y->mSize = x->mSize;
                }
                else {
                    y->mSize = y_end - x->mAddr;
                }
                if (y->mSize > sCache->mAddrRangesMaxSize) sCache->mAddrRangesMaxSize = y->mSize;
                continue;
            }
            if (j < i) memcpy(sCache->mAddrRanges + j, x, sizeof(UnitAddressRange));
            j++;
        }
        /* Duplicate seen - adjust size and complete copy */
        if (j < sCache->mAddrRangesCnt - 1) {
            UnitAddressRange * x = sCache->mAddrRanges + sCache->mAddrRangesCnt - 1;
            memcpy(sCache->mAddrRanges + j++, x, sizeof(UnitAddressRange));
            sCache->mAddrRangesCnt = j;
        }
    }
}

static int cmp_pub_objects(ObjectInfo * x, ObjectInfo * y) {
    static const U4_T flags =
        DOIF_declaration |
        DOIF_external |
        DOIF_artificial |
        DOIF_specification |
        DOIF_abstract_origin |
        DOIF_extension |
        DOIF_private |
        DOIF_protected |
        DOIF_public |
        DOIF_ranges |
        DOIF_low_pc |
        DOIF_mips_linkage_name |
        DOIF_linkage_name |
        DOIF_mangled_name |
        DOIF_optional |
        DOIF_location |
        DOIF_data_location |
        DOIF_const_value;

    if ((x->mFlags & flags) != (y->mFlags & flags)) return 0;
    switch (x->mTag) {
    case TAG_base_type:
    case TAG_fund_type:
        if (x->u.mFundType != y->u.mFundType) return 0;
        break;
    }
    if (strcmp(x->mName, y->mName) != 0) return 0;
    return 1;
}

static void add_pub_name(PubNamesTable * tbl, ObjectInfo * obj) {
    PubNamesInfo * info = NULL;
    unsigned h = calc_symbol_name_hash(obj->mName) % tbl->mHashSize;
    obj->mFlags |= DOIF_pub_mark;
    switch (obj->mTag) {
    case TAG_base_type:
    case TAG_typedef:
    case TAG_class_type:
    case TAG_structure_type:
    case TAG_union_type:
    case TAG_interface_type:
    case TAG_enumeration_type:
    case TAG_enumerator:
    case TAG_variable:
        {
            /* Check for duplicates */
            unsigned n = tbl->mHash[h];
            while (n != 0) {
                ObjectInfo * pub = tbl->mNext[n].mObject;
                if (pub->mTag == obj->mTag && cmp_pub_objects(pub, obj)) return;
                n = tbl->mNext[n].mNext;
            }
        }
    }
    if (tbl->mCnt >= tbl->mMax) {
        tbl->mMax = tbl->mMax * 3 / 2;
        tbl->mNext = (PubNamesInfo *)loc_realloc(tbl->mNext, sizeof(PubNamesInfo) * tbl->mMax);
    }
    info = tbl->mNext + tbl->mCnt;
    info->mObject = obj;
    info->mNext = tbl->mHash[h];
    tbl->mHash[h] = tbl->mCnt++;
}

static void load_pub_names(ELF_Section * debug_info, ELF_Section * pub_names) {
    PubNamesTable * tbl = &sCache->mPubNames;
    dio_EnterSection(NULL, pub_names, 0);
    while (dio_GetPos() < pub_names->size) {
        int dwarf64 = 0;
        U8_T size = dio_ReadU4();
        U8_T next = 0;
        if (size == 0xffffffffu) {
            dwarf64 = 1;
            size = dio_ReadU8();
        }
        next = dio_GetPos() + size;
        if (dio_ReadU2() == 2) {
            ELF_Section * unit_sect = NULL;
            U8_T unit_addr = dio_ReadAddressX(&unit_sect, dwarf64 ? 8 : 4);
            U8_T unit_offs = unit_sect == NULL ? unit_addr : unit_addr - unit_sect->addr;
            U8_T unit_size = dwarf64 ? dio_ReadU8() : (U8_T)dio_ReadU4();
            if (unit_sect == NULL) unit_sect = debug_info;
            if (unit_offs + unit_size > unit_sect->size) str_fmt_exception(ERR_INV_DWARF,
                "Invalid unit size in %s section", pub_names->name);
            for (;;) {
                char * name = NULL;
                ObjectInfo * info = NULL;
                U8_T obj_offs = dwarf64 ? dio_ReadU8() : (U8_T)dio_ReadU4();
                if (obj_offs == 0) break;
                if (obj_offs >= unit_size) str_fmt_exception(ERR_INV_DWARF,
                    "Invalid object offset in %s section", pub_names->name);
                name = dio_ReadString();
                info = find_loaded_object(unit_sect, (ContextAddress)(unit_addr + obj_offs));
                if (info == NULL) continue;
                if (info->mName == NULL) continue;
                if (info->mFlags & DOIF_pub_mark) continue;
                if (strcmp(info->mName, name) != 0) continue;
                add_pub_name(tbl, info);
            }
        }
        assert(next >= dio_GetPos());
        dio_SetPos(next);
    }
    dio_ExitSection();
}

static void add_namespace(PubNamesTable * tbl, ObjectInfo * ns) {
    ObjectInfo * obj = get_dwarf_children(ns);
    while (obj != NULL) {
        if ((obj->mFlags & DOIF_pub_mark) == 0 && obj->mDefinition == NULL && obj->mName != NULL) {
            add_pub_name(tbl, obj);
        }
        if (obj->mTag == TAG_enumeration_type) {
            ObjectInfo * n = get_dwarf_children(obj);
            while (n != NULL) {
                if ((n->mFlags & DOIF_pub_mark) == 0 && n->mName != NULL) {
                    add_pub_name(tbl, n);
                }
                n = n->mSibling;
            }
        }
        if (obj->mTag == TAG_namespace) {
            add_namespace(tbl, obj);
        }
        obj = obj->mSibling;
    }
}

static void create_pub_names(unsigned idx) {
    ObjectInfo * unit = sCache->mObjectHashTable[idx].mCompUnits;
    PubNamesTable * tbl = &sCache->mPubNames;
    while (unit != NULL) {
        add_namespace(tbl, unit);
        if ((unit->mFlags & DOIF_pub_mark) == 0 && unit->mName != NULL) {
            add_pub_name(tbl, unit);
        }
        unit = unit->mSibling;
    }
}

static void allocate_obj_hash(ELF_Section * sec) {
    ObjectHashTable * HashTable = sCache->mObjectHashTable + sec->index;
    assert(HashTable->mObjectHash == NULL);
    HashTable->mObjectHashSize = (unsigned)(sec->size / 53);
    if (HashTable->mObjectHashSize < 251) HashTable->mObjectHashSize = 251;
    HashTable->mObjectHash = (ObjectInfo **)loc_alloc_zero(sizeof(ObjectInfo *) * HashTable->mObjectHashSize);
}

static int unit_id_comparator(const void * x1, const void * x2) {
    ObjectInfo * u1 = (*(CompUnit **)x1)->mObject;
    ObjectInfo * u2 = (*(CompUnit **)x2)->mObject;
    if (u1->mID < u2->mID) return -1;
    if (u1->mID > u2->mID) return +1;
    return 0;
}

static void load_debug_info_section(ELF_Section * sec) {
    ObjectHashTable * HashTable = sCache->mObjectHashTable + sec->index;
    Trap trap;

    sObjRefsCnt = 0;
    sDebugSection = sec;
    sParentObject = NULL;
    sPrevSibling = NULL;
    allocate_obj_hash(sec);
    dio_EnterSection(NULL, sec, 0);
    if (set_trap(&trap)) {
        assert(HashTable->mCompUnitsIndexSize == 0);
        assert(HashTable->mCompUnitsIndex == NULL);
        if (strcmp(sec->name, ".debug_types") == 0) {
            while (dio_GetPos() < sec->size) {
                dio_ReadUnit(&sUnitDesc, read_type_unit_header);
            }
            dio_SetPos(0);
        }
        while (dio_GetPos() < sec->size) {
            dio_ReadUnit(&sUnitDesc, read_object_info);
        }
        clear_trap(&trap);
    }
    dio_ExitSection();
    assert(sDebugSection == sec);
    sDebugSection = NULL;
    sParentObject = NULL;
    sPrevSibling = NULL;
    sCompUnit = NULL;
    if (HashTable->mCompUnitsIndexSize > 0) {
        unsigned i = 0;
        ObjectInfo * unit = sCache->mObjectHashTable[sec->index].mCompUnits;
        HashTable->mCompUnitsIndex = (CompUnit **)loc_alloc(sizeof(CompUnit *) * HashTable->mCompUnitsIndexSize);
        while (unit != NULL) {
            assert(unit->mTag == TAG_compile_unit || unit->mTag == TAG_partial_unit || unit->mTag == TAG_type_unit);
            HashTable->mCompUnitsIndex[i++] = unit->mCompUnit;
            unit = unit->mSibling;
        }
        assert(HashTable->mCompUnitsIndexSize == i);
        qsort(HashTable->mCompUnitsIndex, HashTable->mCompUnitsIndexSize, sizeof(CompUnit *), unit_id_comparator);
    }
    if (trap.error) exception(trap.error);
    read_object_refs(sec);
}

static void load_debug_sections(void) {
    unsigned idx;
    ELF_Section * debug_info = NULL;
    FrameInfoIndex * frame_info_d = NULL;
    FrameInfoIndex * frame_info_e = NULL;
    ELF_File * file = sCache->mFile;
    U8_T debug_types_size = 0;

    for (idx = 1; idx < file->section_cnt; idx++) {
        ELF_Section * sec = file->sections + idx;
        if (sec->size == 0) continue;
        if (sec->name == NULL) continue;
        if (sec->type == SHT_NOBITS) continue;
        if (strcmp(sec->name, ".debug_types") == 0) {
            debug_types_size += sec->size;
        }
    }

    if (debug_types_size > 0) {
        sCache->mTypeUnitHashSize = (unsigned)(debug_types_size / 101);
        if (sCache->mTypeUnitHashSize < 239) sCache->mTypeUnitHashSize = 239;
        sCache->mTypeUnitHash = (CompUnit **)loc_alloc_zero(sizeof(CompUnit *) * sCache->mTypeUnitHashSize);
    }

    for (idx = 1; idx < file->section_cnt; idx++) {
        ELF_Section * sec = file->sections + idx;
        if (sec->size == 0) continue;
        if (sec->name == NULL) continue;
        if (sec->type == SHT_NOBITS) continue;
        if (strcmp(sec->name, ".debug_types") == 0) {
            load_debug_info_section(sec);
        }
    }

    for (idx = 1; idx < file->section_cnt; idx++) {
        ELF_Section * sec = file->sections + idx;
        if (sec->size == 0) continue;
        if (sec->name == NULL) continue;
        if (sec->type == SHT_NOBITS) continue;
        if (strcmp(sec->name, ".debug_info") == 0) {
            load_debug_info_section(sec);
            if (debug_info == NULL) debug_info = sec;
        }
    }

    for (idx = 1; idx < file->section_cnt; idx++) {
        ELF_Section * sec = file->sections + idx;
        if (sec->size == 0) continue;
        if (sec->name == NULL) continue;
        if (sec->type == SHT_NOBITS) continue;
        if (strcmp(sec->name, ".debug") == 0) {
            load_debug_info_section(sec);
            if (debug_info == NULL) debug_info = sec;
        }
    }

    for (idx = 1; idx < file->section_cnt; idx++) {
        ELF_Section * sec = file->sections + idx;
        if (sec->size == 0) continue;
        if (sec->name == NULL) continue;
        if (sec->type == SHT_NOBITS) continue;
        if (strcmp(sec->name, ".line") == 0) {
            sCache->mDebugLineV1 = sec;
        }
        else if (strcmp(sec->name, ".debug_line") == 0) {
            sCache->mDebugLineV2 = sec;
        }
        else if (strcmp(sec->name, ".debug_loc") == 0) {
            sCache->mDebugLoc = sec;
        }
        else if (strcmp(sec->name, ".debug_ranges") == 0) {
            sCache->mDebugRanges = sec;
        }
        else if (strcmp(sec->name, ".debug_frame") == 0) {
            FrameInfoIndex * idx = (FrameInfoIndex *)loc_alloc_zero(sizeof(FrameInfoIndex));
            idx->mSection = sec;
            idx->mNext = frame_info_d;
            frame_info_d = idx;
        }
        else if (strcmp(sec->name, ".eh_frame") == 0) {
            FrameInfoIndex * idx = (FrameInfoIndex *)loc_alloc_zero(sizeof(FrameInfoIndex));
            idx->mSection = sec;
            idx->mNext = frame_info_e;
            frame_info_e = idx;
        }
    }

    while (frame_info_e != NULL) {
        FrameInfoIndex * idx = frame_info_e;
        frame_info_e = idx->mNext;
        idx->mNext = sCache->mFrameInfo;
        sCache->mFrameInfo = idx;
    }
    while (frame_info_d != NULL) {
        FrameInfoIndex * idx = frame_info_d;
        frame_info_d = idx->mNext;
        idx->mNext = sCache->mFrameInfo;
        sCache->mFrameInfo = idx;
    }

    if (debug_info != NULL) {
        PubNamesTable * tbl = &sCache->mPubNames;
        tbl->mHashSize = tbl->mMax = (unsigned)(debug_info->size / 151) + 16;
        tbl->mHash = (unsigned *)loc_alloc_zero(sizeof(unsigned) * tbl->mHashSize);
        tbl->mNext = (PubNamesInfo *)loc_alloc(sizeof(PubNamesInfo) * tbl->mMax);
        memset(tbl->mNext + tbl->mCnt++, 0, sizeof(PubNamesInfo));
        for (idx = 1; idx < file->section_cnt; idx++) {
            ELF_Section * sec = file->sections + idx;
            if (sec->size == 0) continue;
            if (sec->name == NULL) continue;
            if (sec->type == SHT_NOBITS) continue;
            if (strcmp(sec->name, ".debug_pubnames") == 0 || strcmp(sec->name, ".debug_pubtypes") == 0) {
                load_pub_names(debug_info, sec);
            }
        }
        for (idx = 1; idx < file->section_cnt; idx++) {
            create_pub_names(idx);
        }
        load_addr_ranges(debug_info);
    }
}

#if ENABLE_DWARF_LAZY_LOAD
ObjectInfo * get_dwarf_children(ObjectInfo * obj) {
    Trap trap;
    if (obj->mFlags & DOIF_children_loaded) return obj->mChildren;
    sObjRefsCnt = 0;
    sCompUnit = obj->mCompUnit;
    sUnitDesc = sCompUnit->mDesc;
    sDebugSection = sCompUnit->mDesc.mSection;
    sCache = (DWARFCache *)sCompUnit->mFile->dwarf_dt_cache;
    dio_EnterSection(&sCompUnit->mDesc, sDebugSection, obj->mID - sDebugSection->addr);
    if (set_trap(&trap)) {
        U8_T end_pos = sCompUnit->mDesc.mUnitOffs + sCompUnit->mDesc.mUnitSize;
        if (obj->mSibling != NULL) end_pos = obj->mSibling->mID - sDebugSection->addr;
        dio_ReadEntry(NULL, (U2_T)0xffffu);
        sParentObject = obj;
        sPrevSibling = NULL;
        while (dio_GetPos() < end_pos) {
            if (!dio_ReadEntry(read_object_info, 0)) break;
        }
        obj->mFlags |= DOIF_children_loaded;
        clear_trap(&trap);
    }
    else {
        /* TODO: dispose obj->mChildren */
        obj->mChildren = NULL;
    }
    dio_ExitSection();
    sDebugSection = NULL;
    sParentObject = NULL;
    sPrevSibling = NULL;
    sCompUnit = NULL;
    if (trap.error) exception(trap.error);
    read_object_refs(obj->mCompUnit->mDesc.mSection);
    assert(obj->mFlags & DOIF_children_loaded);
    return obj->mChildren;
}

ObjectInfo * get_dwarf_parent(ObjectInfo * obj) {
    ObjectInfo * x;
    if (obj->mParent != NULL) return obj->mParent;
    if (obj->mTag == TAG_compile_unit) return NULL;
    if (obj->mTag == TAG_partial_unit) return NULL;
    if (obj->mTag == TAG_type_unit) return NULL;
    x = get_dwarf_children(obj->mCompUnit->mObject);
    while (x != NULL && x->mID < obj->mID) {
        if (x->mSibling == NULL || x->mSibling->mID > obj->mID) {
            x = get_dwarf_children(x);
        }
        else {
            x = x->mSibling;
        }
    }
    return obj->mParent;
}
#endif

static U2_T gop_gAttr = 0;
static U2_T gop_gForm = 0;
static U8_T gop_gFormData = 0;
static size_t gop_gFormDataSize = 0;
static void * gop_gFormDataAddr = NULL;
static ELF_Section * gop_gFormSection = NULL;
static U8_T gop_gSpecification = 0;
static U8_T gop_gAbstractOrigin = 0;
static U8_T gop_gExtension = 0;
static U2_T gop_gSpecificationForm = 0;
static U2_T gop_gAbstractOriginForm = 0;
static U2_T gop_gExtensionForm = 0;

static void get_object_property_callback(U2_T Tag, U2_T Attr, U2_T Form) {
    if (Attr == AT_specification_v2) {
        gop_gSpecification = dio_gFormData;
        gop_gSpecificationForm = Form;
    }
    if (Attr == AT_abstract_origin) {
        gop_gAbstractOrigin = dio_gFormData;
        gop_gAbstractOriginForm = Form;
    }
    if (Attr == AT_extension) {
        gop_gExtension = dio_gFormData;
        gop_gExtensionForm = Form;
    }
    if (Attr != gop_gAttr) return;
    gop_gForm = Form;
    gop_gFormData = dio_gFormData;
    gop_gFormDataSize = dio_gFormDataSize;
    gop_gFormDataAddr = dio_gFormDataAddr;
    gop_gFormSection = dio_gFormSection;
}

U8_T get_numeric_property_value(PropertyValue * Value) {
    U8_T Res = 0;

    if (Value->mPieces != NULL) {
        str_exception(ERR_INV_DWARF, "Constant DWARF attribute value expected");
    }
    else if (Value->mAddr != NULL) {
        size_t i;
        if (Value->mSize > 8) str_exception(ERR_INV_DWARF, "Invalid size of DWARF attribute value");
        for (i = 0; i < Value->mSize; i++) {
            Res = (Res << 8) | Value->mAddr[Value->mBigEndian ? i : Value->mSize - i - 1];
        }
    }
    else {
        Res = Value->mValue;
    }
    return Res;
}

void read_dwarf_object_property(Context * Ctx, int Frame, ObjectInfo * Obj, U2_T Attr, PropertyValue * Value) {

    memset(Value, 0, sizeof(PropertyValue));
    Value->mContext = Ctx;
    Value->mFrame = Frame;
    Value->mObject = Obj;
    Value->mAttr = Attr;
    Value->mBigEndian = Obj->mCompUnit->mFile->big_endian;

    if (Obj->mTag >= TAG_fund_type && Obj->mTag < TAG_fund_type + 0x100) {
        /* Virtual DWARF object that is created by the DWARF reader. It has no properties. */
        if (Obj->mTag == TAG_fund_type) {
            if (Attr == AT_byte_size) {
                Value->mValue = get_fund_type_size(Obj->mCompUnit, Obj->u.mFundType);
                return;
            }
        }
        else if (Obj->mTag == TAG_index_range) {
            if (Attr == AT_lower_bound) {
                switch (Obj->u.mRange.mFmt) {
                case FMT_FT_C_C:
                case FMT_FT_C_X:
                case FMT_UT_C_C:
                case FMT_UT_C_X:
                    Value->mValue = Obj->u.mRange.mLow.mValue;
                    return;
                case FMT_FT_X_C:
                case FMT_FT_X_X:
                case FMT_UT_X_C:
                case FMT_UT_X_X:
                    Value->mForm = FORM_BLOCK2;
                    Value->mAddr = Obj->u.mRange.mLow.mExpr.mAddr;
                    Value->mSize = Obj->u.mRange.mLow.mExpr.mSize;
                    return;
                }
            }
            if (Attr == AT_upper_bound) {
                switch (Obj->u.mRange.mFmt) {
                case FMT_FT_C_C:
                case FMT_FT_X_C:
                case FMT_UT_C_C:
                case FMT_UT_X_C:
                    Value->mValue = Obj->u.mRange.mHigh.mValue;
                    return;
                case FMT_FT_C_X:
                case FMT_FT_X_X:
                case FMT_UT_C_X:
                case FMT_UT_X_X:
                    Value->mForm = FORM_BLOCK2;
                    Value->mAddr = Obj->u.mRange.mHigh.mExpr.mAddr;
                    Value->mSize = Obj->u.mRange.mHigh.mExpr.mSize;
                    return;
                }
            }
        }
        else if (Obj->mTag == TAG_mod_pointer || Obj->mTag == TAG_mod_reference) {
            if (Attr == AT_byte_size) {
                Value->mValue = Obj->mCompUnit->mDesc.mAddressSize;
                return;
            }
        }
        exception(ERR_SYM_NOT_FOUND);
    }

    sCompUnit = Obj->mCompUnit;
    sUnitDesc = sCompUnit->mDesc;
    sDebugSection = sCompUnit->mDesc.mSection;
    sCache = (DWARFCache *)sCompUnit->mFile->dwarf_dt_cache;
    dio_EnterSection(&sUnitDesc, sDebugSection, Obj->mID - sDebugSection->addr);
    for (;;) {
        if (sUnitDesc.mVersion == 1 && Attr == AT_data_member_location) {
            gop_gAttr = AT_location;
        }
        else {
            gop_gAttr = Attr;
        }
        gop_gForm = 0;
        gop_gSpecification = 0;
        gop_gAbstractOrigin = 0;
        gop_gExtension = 0;
        gop_gSpecificationForm = 0;
        gop_gAbstractOriginForm = 0;
        gop_gExtensionForm = 0;
        dio_ReadEntry(get_object_property_callback, gop_gAttr);
        dio_ExitSection();
        if (gop_gForm != 0) break;
        if (gop_gSpecificationForm == FORM_GNU_REF_ALT) {
            ObjectInfo * Ref = find_alt_object_info((ContextAddress)gop_gSpecification);
            sCompUnit = Ref->mCompUnit;
            sUnitDesc = sCompUnit->mDesc;
            sDebugSection = sCompUnit->mDesc.mSection;
            dio_EnterSection(&sUnitDesc, sDebugSection, Ref->mID - sDebugSection->addr);
        }
        else if (gop_gSpecification != 0) {
            ObjectInfo * Ref = NULL;
            dio_ChkRef(gop_gSpecificationForm);
            Ref = find_object(sDebugSection, (ContextAddress)gop_gSpecification);
            sCompUnit = Ref->mCompUnit;
            sUnitDesc = sCompUnit->mDesc;
            if (sDebugSection != sCompUnit->mDesc.mSection) str_exception(ERR_INV_DWARF, "Invalid AT_specification attribute");
            dio_EnterSection(&sUnitDesc, sDebugSection, Ref->mID - sDebugSection->addr);
        }
        else if (gop_gAbstractOriginForm == FORM_GNU_REF_ALT) {
            ObjectInfo * Ref = find_alt_object_info((ContextAddress)gop_gAbstractOrigin);
            sCompUnit = Ref->mCompUnit;
            sUnitDesc = sCompUnit->mDesc;
            sDebugSection = sCompUnit->mDesc.mSection;
            dio_EnterSection(&sUnitDesc, sDebugSection, Ref->mID - sDebugSection->addr);
        }
        else if (gop_gAbstractOrigin != 0) {
            ObjectInfo * Ref = NULL;
            dio_ChkRef(gop_gAbstractOriginForm);
            Ref = find_object(sDebugSection, (ContextAddress)gop_gAbstractOrigin);
            sCompUnit = Ref->mCompUnit;
            sUnitDesc = sCompUnit->mDesc;
            if (sDebugSection != sCompUnit->mDesc.mSection) str_exception(ERR_INV_DWARF, "Invalid AT_abstract_origin attribute");
            dio_EnterSection(&sUnitDesc, sDebugSection, Ref->mID - sDebugSection->addr);
        }
        else if (gop_gExtension != 0) {
            dio_ChkRef(gop_gExtensionForm);
            dio_EnterSection(&sUnitDesc, sDebugSection, gop_gExtension - sDebugSection->addr);
        }
        else break;
    }

    switch (Value->mForm = gop_gForm) {
    case FORM_REF       :
    case FORM_REF_ADDR  :
    case FORM_REF1      :
    case FORM_REF2      :
    case FORM_REF4      :
    case FORM_REF8      :
    case FORM_REF_UDATA :
        Value->mSection = dio_gFormSection;
        Value->mValue = gop_gFormData;
        break;
    case FORM_DATA1     :
    case FORM_DATA2     :
    case FORM_DATA4     :
    case FORM_DATA8     :
    case FORM_FLAG      :
    case FORM_BLOCK1    :
    case FORM_BLOCK2    :
    case FORM_BLOCK4    :
    case FORM_BLOCK     :
    case FORM_STRP      :
    case FORM_SEC_OFFSET:
    case FORM_EXPRLOC   :
    case FORM_REF_SIG8  :
    case FORM_STRING    :
    case FORM_GNU_STRP_ALT:
        Value->mAddr = (U1_T *)gop_gFormDataAddr;
        Value->mSize = gop_gFormDataSize;
        break;
    case FORM_SDATA     :
    case FORM_UDATA     :
        Value->mValue = gop_gFormData;
        break;
    case FORM_ADDR      :
        Value->mSection = dio_gFormSection;
        Value->mValue = elf_map_to_run_time_address(Ctx, Obj->mCompUnit->mFile, gop_gFormSection, (ContextAddress)gop_gFormData);
        if (errno) str_exception(errno, "Cannot get object run-time address");
        break;
    default:
        if (Attr == AT_data_member_location && Obj->mTag == TAG_member && Obj->mParent->mTag == TAG_union_type) {
            Value->mForm = FORM_UDATA;
            Value->mValue = 0;
            break;
        }
        if (Attr == AT_byte_size) {
            if (Obj->mTag == TAG_pointer_type || Obj->mTag == TAG_reference_type || Obj->mTag == TAG_rvalue_reference_type ||
                    Obj->mTag == TAG_mod_pointer || Obj->mTag == TAG_mod_reference) {
                Value->mForm = FORM_UDATA;
                Value->mValue = sCompUnit->mDesc.mAddressSize;
                break;
            }
            if (Obj->mTag == TAG_ptr_to_member_type) {
                Value->mForm = FORM_UDATA;
                Value->mValue = sCompUnit->mDesc.mAddressSize;
                break;
            }
            if (Obj->mTag == TAG_structure_type || Obj->mTag == TAG_class_type || Obj->mTag == TAG_union_type) {
                /* It is OK to return size 0 if the structure has no data members */
                int ok = 1;
                ObjectInfo * c = get_dwarf_children(Obj);
                while (ok && c != NULL) {
                    ObjectInfo * d = c;
                    while (d->mTag == TAG_imported_declaration) {
                        PropertyValue v;
                        read_and_evaluate_dwarf_object_property(Ctx, Frame, d, AT_import, &v);
                        d = find_object(Obj->mCompUnit->mDesc.mSection, (ContextAddress)get_numeric_property_value(&v));
                        if (d == NULL) break;
                    }
                    if (d == NULL) {
                        ok = 0;
                    }
                    else {
                        switch (d->mTag) {
                        case TAG_typedef:
                        case TAG_subprogram:
                        case TAG_template_type_param:
                        case TAG_class_type:
                        case TAG_structure_type:
                        case TAG_union_type:
                        case TAG_enumeration_type:
                            break;
                        case TAG_member:
                            if (d->mFlags & DOIF_external) break;
                            ok = 0;
                            break;
                        default:
                            ok = 0;
                        }
                    }
                    c = c->mSibling;
                }
                if (ok) {
                    Value->mForm = FORM_UDATA;
                    Value->mAddr = 0;
                    Value->mValue = 0;
                    break;
                }
            }
            if (Obj->mTag == TAG_label) {
                /* By definition, label size is 0 */
                Value->mForm = FORM_UDATA;
                Value->mValue = 0;
                break;
            }
        }
        exception(ERR_SYM_NOT_FOUND);
    }

    sCache = NULL;
    sCompUnit = NULL;
    sDebugSection = NULL;
}

void read_and_evaluate_dwarf_object_property_with_args(
        Context * Ctx, int Frame, ObjectInfo * Obj, U2_T Attr,
        uint64_t * Args, unsigned ArgsCnt, PropertyValue * Value) {
    read_dwarf_object_property(Ctx, Frame, Obj, Attr, Value);
    assert(Value->mContext == Ctx);
    assert(Value->mFrame == Frame);
    assert(Value->mObject == Obj);
    assert(Value->mAttr == Attr);
    if (Value->mForm == FORM_EXPRLOC) {
        dwarf_evaluate_expression(Value, Args, ArgsCnt);
    }
    else if (Attr == AT_data_member_location) {
        switch (Value->mForm) {
        case FORM_DATA1     :
        case FORM_DATA2     :
        case FORM_DATA4     :
        case FORM_DATA8     :
        case FORM_SDATA     :
        case FORM_UDATA     :
            if (ArgsCnt == 0) exception(ERR_INV_CONT_OBJ);
            Value->mValue = Args[0] + get_numeric_property_value(Value);
            Value->mForm = FORM_UDATA;
            Value->mAddr = NULL;
            Value->mSize = 0;
            break;
        case FORM_BLOCK1    :
        case FORM_BLOCK2    :
        case FORM_BLOCK4    :
        case FORM_BLOCK     :
            dwarf_evaluate_expression(Value, Args, ArgsCnt);
            break;
        }
    }
    else if (Attr == AT_location || Attr == AT_string_length || Attr == AT_frame_base || Attr == AT_use_location) {
        switch (Value->mForm) {
        case FORM_DATA4     :
        case FORM_DATA8     :
        case FORM_BLOCK1    :
        case FORM_BLOCK2    :
        case FORM_BLOCK4    :
        case FORM_BLOCK     :
        case FORM_SEC_OFFSET:
            dwarf_evaluate_expression(Value, Args, ArgsCnt);
            break;
        }
    }
    else if (Attr == AT_count || Attr == AT_byte_size || Attr == AT_lower_bound || Attr == AT_upper_bound) {
        switch (Value->mForm) {
        case FORM_BLOCK1    :
        case FORM_BLOCK2    :
        case FORM_BLOCK4    :
        case FORM_BLOCK     :
            dwarf_evaluate_expression(Value, Args, ArgsCnt);
            break;
        }
    }
}

static void free_unit_cache(CompUnit * Unit) {
    Unit->mFilesCnt = 0;
    Unit->mFilesMax = 0;
    loc_free(Unit->mFiles);
    Unit->mFiles = NULL;

    Unit->mDirsCnt = 0;
    Unit->mDirsMax = 0;
    loc_free(Unit->mDirs);
    Unit->mDirs = NULL;

    while (Unit->mStatesCnt > 0) {
        loc_free(Unit->mStates[--Unit->mStatesCnt].mFileName);
    }
    loc_free(Unit->mStates);
    loc_free(Unit->mStatesIndex);
    Unit->mStates = NULL;
    Unit->mStatesMax = 0;
    Unit->mStatesIndex = NULL;
}

static void free_dwarf_cache(ELF_File * file) {
    DWARFCache * Cache = (DWARFCache *)file->dwarf_dt_cache;
    if (Cache != NULL) {
        unsigned i;
        assert(Cache->magic == DWARF_CACHE_MAGIC);
        Cache->magic = 0;
        for (i = 0; i < file->section_cnt; i++) {
            ObjectHashTable * Table = Cache->mObjectHashTable + i;
            while (Table->mCompUnits != NULL) {
                CompUnit * Unit = Table->mCompUnits->mCompUnit;
                Table->mCompUnits = Table->mCompUnits->mSibling;
                free_unit_cache(Unit);
                loc_free(Unit);
            }
            loc_free(Table->mObjectHash);
            loc_free(Table->mCompUnitsIndex);
        }
        while (Cache->mObjectList != NULL) {
            ObjectArray * Buf = Cache->mObjectList;
            Cache->mObjectList = Buf->mNext;
            loc_free(Buf);
        }
        while (Cache->mFrameInfo != NULL) {
            FrameInfoIndex * idx = Cache->mFrameInfo;
            Cache->mFrameInfo = idx->mNext;
            loc_free(idx->mFrameInfoRanges);
            loc_free(idx);
        }
        loc_free(Cache->mObjectHashTable);
        loc_free(Cache->mAddrRanges);
        loc_free(Cache->mPubNames.mHash);
        loc_free(Cache->mPubNames.mNext);
        loc_free(Cache->mFileInfoHash);
        loc_free(Cache->mTypeUnitHash);
        loc_free(Cache);
        file->dwarf_dt_cache = NULL;
    }
}

DWARFCache * get_dwarf_cache(ELF_File * file) {
    DWARFCache * Cache = (DWARFCache *)file->dwarf_dt_cache;
    if (Cache == NULL) {
        Trap trap;
        if (!sCloseListenerOK) {
            elf_add_close_listener(free_dwarf_cache);
            sCloseListenerOK = 1;
        }
        if (file->dwz_file_name != NULL) {
            file->dwz_file = elf_open(file->dwz_file_name);
            if (file->dwz_file == NULL) {
                str_exception(errno, "Cannot open DWZ file");
            }
            file->dwz_file->lock_cnt++;
            get_dwarf_cache(file->dwz_file);
        }
        sCache = Cache = (DWARFCache *)(file->dwarf_dt_cache = loc_alloc_zero(sizeof(DWARFCache)));
        sCache->magic = DWARF_CACHE_MAGIC;
        sCache->mFile = file;
        sCache->mObjectArrayPos = OBJECT_ARRAY_SIZE;
        sCache->mObjectHashTable = (ObjectHashTable *)loc_alloc_zero(sizeof(ObjectHashTable) * file->section_cnt);
        if (set_trap(&trap)) {
            dio_LoadAbbrevTable(file);
            load_debug_sections();
            clear_trap(&trap);
        }
        else {
            sCache->mErrorReport = get_error_report(trap.error);
        }
        sCache = NULL;
    }
    if (Cache->mErrorReport) exception(set_error_report_errno(Cache->mErrorReport));
    return Cache;
}

static void add_dir(CompUnit * Unit, char * Name) {
    if (Unit->mDirsCnt >= Unit->mDirsMax) {
        Unit->mDirsMax = Unit->mDirsMax == 0 ? 16 : Unit->mDirsMax * 2;
        Unit->mDirs = (char **)loc_realloc(Unit->mDirs, sizeof(char *) * Unit->mDirsMax);
    }
    Unit->mDirs[Unit->mDirsCnt++] = Name;
}

static void add_file(CompUnit * Unit, FileInfo * file) {
    file->mNameHash = calc_file_name_hash(file->mName);
    if (Unit->mFilesCnt >= Unit->mFilesMax) {
        Unit->mFilesMax = Unit->mFilesMax == 0 ? 16 : Unit->mFilesMax * 2;
        Unit->mFiles = (FileInfo *)loc_realloc(Unit->mFiles, sizeof(FileInfo) * Unit->mFilesMax);
    }
    if (file->mDir == NULL) file->mDir = Unit->mDir;
    Unit->mFiles[Unit->mFilesCnt++] = *file;
}

static void add_state(CompUnit * Unit, LineNumbersState * state) {
    if (state->mFile >= Unit->mFilesCnt) {
        /* Workaround: Diab compiler generates invalid file indices for an empty compilation unit */
        return;
    }
    if (Unit->mFiles[state->mFile].mAreaCnt++ == 0) {
        /* Workaround: compilers don't produce mapping for first lines in a source file.
         * Such mapping is needed, for example, for re-positioning of source line breakpoints.
         * We add artificial entry for that.
         */
        LineNumbersState s;
        memset(&s, 0, sizeof(s));
        s.mLine = 1;
        s.mFile = state->mFile;
        s.mSection = state->mSection;
        s.mAddress = state->mAddress;
        add_state(Unit, &s);
    }
    if (Unit->mStatesCnt >= Unit->mStatesMax) {
        Unit->mStatesMax = Unit->mStatesMax == 0 ? 128 : Unit->mStatesMax * 2;
        Unit->mStates = (LineNumbersState *)loc_realloc(Unit->mStates, sizeof(LineNumbersState) * Unit->mStatesMax);
    }
    Unit->mStates[Unit->mStatesCnt++] = *state;
}

static int state_address_comparator(const void * x1, const void * x2) {
    LineNumbersState * s1 = (LineNumbersState *)x1;
    LineNumbersState * s2 = (LineNumbersState *)x2;
    if (s1->mSection < s2->mSection) return -1;
    if (s1->mSection > s2->mSection) return +1;
    if (s1->mAddress < s2->mAddress) return -1;
    if (s1->mAddress > s2->mAddress) return +1;
    if ((s1->mFlags ^ s2->mFlags) & LINE_EndSequence) {
        return s1->mFlags & LINE_EndSequence ? -1 : +1;
    }
    if (s1->mFile < s2->mFile) return -1;
    if (s1->mFile > s2->mFile) return +1;
    if (s1->mLine < s2->mLine) return -1;
    if (s1->mLine > s2->mLine) return +1;
    if (s1->mColumn < s2->mColumn) return -1;
    if (s1->mColumn > s2->mColumn) return +1;
    return 0;
}

static int state_text_pos_comparator(const void * x1, const void * x2) {
    LineNumbersState * s1 = *(LineNumbersState **)x1;
    LineNumbersState * s2 = *(LineNumbersState **)x2;
    if (s1->mFile < s2->mFile) return -1;
    if (s1->mFile > s2->mFile) return +1;
    if (s1->mLine < s2->mLine) return -1;
    if (s1->mLine > s2->mLine) return +1;
    if (s1->mColumn < s2->mColumn) return -1;
    if (s1->mColumn > s2->mColumn) return +1;
    if (s1->mSection < s2->mSection) return -1;
    if (s1->mSection > s2->mSection) return +1;
    if (s1->mAddress < s2->mAddress) return -1;
    if (s1->mAddress > s2->mAddress) return +1;
    return 0;
}

static void compute_reverse_lookup_indices(DWARFCache * Cache, CompUnit * Unit) {
    U4_T i;
    qsort(Unit->mStates, Unit->mStatesCnt, sizeof(LineNumbersState), state_address_comparator);
    Unit->mStatesIndex = (LineNumbersState **)loc_alloc(sizeof(LineNumbersState *) * Unit->mStatesCnt);
    for (i = 0; i < Unit->mStatesCnt; i++) {
        LineNumbersState * s1 = Unit->mStates + i;
        while (i + 1 < Unit->mStatesCnt) {
            LineNumbersState * s2 = s1 + 1;
            if (s1->mFile != s2->mFile ||
                s1->mLine != s2->mLine || s1->mColumn != s2->mColumn ||
                s1->mFlags != s2->mFlags || s1->mISA != s2->mISA ||
                s1->mOpIndex != s2->mOpIndex || s1->mDiscriminator != s2->mDiscriminator) break;
            memmove(s2, s2 + 1, sizeof(LineNumbersState) * (Unit->mStatesCnt - i - 2));
            Unit->mStatesCnt--;
        }
        Unit->mStatesIndex[i] = s1;
    }
    qsort(Unit->mStatesIndex, Unit->mStatesCnt, sizeof(LineNumbersState *), state_text_pos_comparator);
    for (i = 0; i < Unit->mStatesCnt; i++) Unit->mStatesIndex[i]->mStatesIndexPos = i;
    if (Cache->mFileInfoHash == NULL) {
        Cache->mFileInfoHashSize = 251;
        Cache->mFileInfoHash = (FileInfo **)loc_alloc_zero(sizeof(FileInfo *) * Cache->mFileInfoHashSize);
    }
    for (i = 0; i < Unit->mFilesCnt; i++) {
        FileInfo * File = Unit->mFiles + i;
        unsigned h = File->mNameHash % Cache->mFileInfoHashSize;
        File->mCompUnit = Unit;
        File->mNextInHash = Cache->mFileInfoHash[h];
        Cache->mFileInfoHash[h] = File;
    }
}

static void load_line_numbers_v1(CompUnit * Unit, U4_T unit_size) {
    LineNumbersState state;
    ELF_Section * sec = NULL;
    ContextAddress addr = 0;
    U4_T line = 0;

    memset(&state, 0, sizeof(state));
    addr = (ContextAddress)dio_ReadAddress(&sec);
    if (sec != NULL) state.mSection = sec->index;
    while (dio_GetPos() < Unit->mLineInfoOffs + unit_size) {
        state.mLine = dio_ReadU4();
        state.mColumn = dio_ReadU2();
        if (state.mColumn == 0xffffu) state.mColumn = 0;
        state.mAddress = addr + dio_ReadU4();
        if (state.mLine == 0) {
            state.mLine = line + 1;
            state.mColumn = 0;
        }
        add_state(Unit, &state);
        line = state.mLine;
    }
}

static void load_line_numbers_v2(CompUnit * Unit, U8_T unit_size, int dwarf64) {
    U2_T version = 0;
    U8_T header_pos = 0;
    U1_T opcode_base = 0;
    U1_T opcode_size[256];
    U8_T header_size = 0;
    U1_T min_instruction_length = 0;
    U1_T max_ops_per_instruction = 1;
    U1_T is_stmt_default = 0;
    I1_T line_base = 0;
    U1_T line_range = 0;
    LineNumbersState state;

    version = dio_ReadU2();
    if (version < 2 || version > 4) str_exception(ERR_INV_DWARF, "Invalid line number info version");
    header_size = dwarf64 ? dio_ReadU8() : (U8_T)dio_ReadU4();
    header_pos = dio_GetPos();
    min_instruction_length = dio_ReadU1();
    if (version >= 4) max_ops_per_instruction = dio_ReadU1();
    is_stmt_default = dio_ReadU1() != 0;
    line_base = (I1_T)dio_ReadU1();
    line_range = dio_ReadU1();
    opcode_base = dio_ReadU1();
    memset(opcode_size, 0, sizeof(opcode_size));
    dio_Read(opcode_size + 1, opcode_base - 1);

    /* Read directory names */
    for (;;) {
        char * Name = dio_ReadString();
        if (Name == NULL) break;
        add_dir(Unit, Name);
    }

    /* Read source files info */
    for (;;) {
        U4_T dir = 0;
        FileInfo file;
        memset(&file, 0, sizeof(file));
        file.mName = dio_ReadString();
        if (file.mName == NULL) break;
        dir = dio_ReadULEB128();
        if (dir > 0 && dir <= Unit->mDirsCnt) file.mDir = Unit->mDirs[dir - 1];
        file.mModTime = dio_ReadULEB128();
        file.mSize = dio_ReadULEB128();
        add_file(Unit, &file);
    }

    if (header_pos + header_size != dio_GetPos()) {
        if (dwarf64 && header_pos + header_size == dio_GetPos() + 12) {
            /* OK - bug in GCC for MIPS64. */
            /* GCC generates prologue header_length field with a value which is exactly 12 too large. */
        }
        else {
            str_exception(ERR_INV_DWARF, "Invalid line info header");
        }
    }

    /* Run the program */
    memset(&state, 0, sizeof(state));
    state.mFile = 1;
    state.mLine = 1;
    if (is_stmt_default) state.mFlags |= LINE_IsStmt;
    while (dio_GetPos() < Unit->mLineInfoOffs + unit_size) {
        U1_T opcode = dio_ReadU1();
        if (opcode >= opcode_base) {
            unsigned op_advance = (opcode - opcode_base) / line_range;
            state.mLine += (U4_T)((int)((opcode - opcode_base) % line_range) + line_base);
            state.mAddress += (state.mOpIndex + op_advance) / max_ops_per_instruction * min_instruction_length;
            state.mOpIndex = (state.mOpIndex + op_advance) % max_ops_per_instruction;
            add_state(Unit, &state);
            state.mFlags &= ~(LINE_BasicBlock | LINE_PrologueEnd | LINE_EpilogueBegin);
            state.mDiscriminator = 0;
        }
        else if (opcode == 0) {
            ELF_Section * sec = NULL;
            U4_T op_size = dio_ReadULEB128();
            U8_T op_pos = dio_GetPos();
            switch (dio_ReadU1()) {
            case DW_LNE_define_file: {
                U4_T dir = 0;
                FileInfo file;
                memset(&file, 0, sizeof(file));
                file.mName = dio_ReadString();
                dir = dio_ReadULEB128();
                if (dir > 0 && dir <= Unit->mDirsCnt) file.mDir = Unit->mDirs[dir - 1];
                file.mModTime = dio_ReadULEB128();
                file.mSize = dio_ReadULEB128();
                add_file(Unit, &file);
                break;
            }
            case DW_LNE_end_sequence:
                state.mFlags |= LINE_EndSequence;
                add_state(Unit, &state);
                memset(&state, 0, sizeof(state));
                state.mFile = 1;
                state.mLine = 1;
                if (is_stmt_default) state.mFlags |= LINE_IsStmt;
                break;
            case DW_LNE_set_address:
                state.mAddress = (ContextAddress)dio_ReadAddress(&sec);
                state.mSection = sec != NULL ? sec->index : 0;
                break;
            case DW_LNE_set_discriminator:
                state.mDiscriminator = (U1_T)dio_ReadULEB128();
                break;
            default:
                dio_Skip(op_size - 1);
                break;
            }
            if (dio_GetPos() != op_pos + op_size)
                str_exception(ERR_INV_DWARF, "Invalid line info op size");
        }
        else {
            switch (opcode) {
            case DW_LNS_copy:
                add_state(Unit, &state);
                state.mFlags &= ~(LINE_BasicBlock | LINE_PrologueEnd | LINE_EpilogueBegin);
                break;
            case DW_LNS_advance_pc:
                state.mAddress += (ContextAddress)(dio_ReadU8LEB128() * min_instruction_length);
                break;
            case DW_LNS_advance_line:
                state.mLine += dio_ReadSLEB128();
                break;
            case DW_LNS_set_file:
                state.mFile = dio_ReadULEB128();
                break;
            case DW_LNS_set_column:
                state.mColumn = (U2_T)dio_ReadULEB128();
                break;
            case DW_LNS_negate_stmt:
                state.mFlags ^= LINE_IsStmt;
                break;
            case DW_LNS_set_basic_block:
                state.mFlags |= LINE_BasicBlock;
                break;
            case DW_LNS_const_add_pc:
                state.mAddress += (255 - opcode_base) / line_range * min_instruction_length;
                break;
            case DW_LNS_fixed_advance_pc:
                state.mAddress += dio_ReadU2();
                break;
            case DW_LNS_set_prologue_end:
                state.mFlags |= LINE_PrologueEnd;
                break;
            case DW_LNS_set_epilogue_begin:
                state.mFlags |= LINE_EpilogueBegin;
                break;
            case DW_LNS_set_isa:
                state.mISA = (U1_T)dio_ReadULEB128();
                break;
            default:
                str_exception(ERR_INV_DWARF, "Invalid line info op code");
                break;
            }
        }
    }
}

void load_line_numbers(CompUnit * Unit) {
    Trap trap;
    DWARFCache * Cache = (DWARFCache *)Unit->mFile->dwarf_dt_cache;
    ELF_Section * LineInfoSection = Unit->mDesc.mVersion <= 1 ? Cache->mDebugLineV1 : Cache->mDebugLineV2;
    if (LineInfoSection == NULL) return;
    if (Unit->mLineInfoLoaded) return;
    if (elf_load(LineInfoSection)) exception(errno);
    dio_EnterSection(&Unit->mDesc, LineInfoSection, Unit->mLineInfoOffs);
    if (set_trap(&trap)) {
        U8_T unit_size = 0;
        FileInfo file;
        memset(&file, 0, sizeof(file));
        file.mDir = Unit->mDir;
        file.mName = Unit->mObject->mName;
        add_file(Unit, &file);
        /* Read header */
        unit_size = dio_ReadU4();
        if (Unit->mDesc.mVersion <= 1) {
            /* DWARF 1.1 */
            load_line_numbers_v1(Unit, (U4_T)unit_size);
        }
        else {
            /* DWARF 2+ */
            int dwarf64 = 0;
            if (unit_size == 0xffffffffu) {
                unit_size = dio_ReadU8();
                unit_size += 12;
                dwarf64 = 1;
            }
            else {
                unit_size += 4;
            }
            load_line_numbers_v2(Unit, unit_size, dwarf64);
        }
        dio_ExitSection();
        compute_reverse_lookup_indices(Cache, Unit);
        Unit->mLineInfoLoaded = 1;
        clear_trap(&trap);
    }
    else {
        dio_ExitSection();
        free_unit_cache(Unit);
        exception(trap.error);
    }
}

UnitAddressRange * find_comp_unit_addr_range(DWARFCache * cache, ELF_Section * section,
                                             ContextAddress addr_min, ContextAddress addr_max) {
    unsigned l = 0;
    unsigned h = cache->mAddrRangesCnt;
    U4_T s = 0;

    if (cache->mAddrRangesRelocatable && section != NULL) {
        if (section->file == cache->mFile) {
            s = section->index;
        }
        else {
            unsigned i;
            assert(get_dwarf_file(section->file) == cache->mFile);
            for (i = 1; i < cache->mFile->section_cnt; i++) {
                ELF_Section * sec = cache->mFile->sections + i;
                if (sec->name == NULL) continue;
                if (strcmp(sec->name, section->name) == 0) {
                    s = i;
                    break;
                }
            }
        }
    }

    while (l < h) {
        unsigned k = (h + l) / 2;
        UnitAddressRange * rk = cache->mAddrRanges + k;
        if (rk->mAddr > addr_max) h = k;
        else if (rk->mAddr + cache->mAddrRangesMaxSize <= addr_min) l = k + 1;
        else {
            for (; k > 0; k--) {
                rk = cache->mAddrRanges + k - 1;
                if (rk->mAddr + cache->mAddrRangesMaxSize <= addr_min) break;
            }
            for (; k < h; k++) {
                rk = cache->mAddrRanges + k;
                if (rk->mAddr > addr_max) break;
                if (rk->mAddr + rk->mSize <= addr_min) continue;
                if (rk->mSection && s && rk->mSection != s) continue;
                return rk;
            }
            break;
        }
    }
    return NULL;
}

#endif /* ENABLE_ELF && ENABLE_DebugContext */
