// Copyright (c) 2019-present, iQIYI, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

// Created by caikelun on 2019-03-07.

#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <link.h>
#include <elf.h>
#include <sys/types.h>
#include "xcc_errno.h"
#include "xcd_elf_interface.h"
#include "xcd_dwarf.h"
#include "xcd_arm_exidx.h"
#include "xcd_memory.h"
#include "xcd_log.h"
#include "xcd_util.h"
#include "queue.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct xcd_elf_load
{
    uintptr_t vaddr;
    size_t    offset;
    size_t    size;
    TAILQ_ENTRY(xcd_elf_load,) link;
} xcd_elf_load_t;
#pragma clang diagnostic pop
typedef TAILQ_HEAD(xcd_elf_load_queue, xcd_elf_load,) xcd_elf_load_queue_t;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct xcd_elf_symbols
{
    size_t sym_offset;
    size_t sym_end;
    size_t sym_entry_size;
    size_t str_offset;
    size_t str_end;
    TAILQ_ENTRY(xcd_elf_symbols,) link;
} xcd_elf_symbols_t;
#pragma clang diagnostic pop
typedef TAILQ_HEAD(xcd_elf_symbols_queue, xcd_elf_symbols,) xcd_elf_symbols_queue_t;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct xcd_elf_strtab
{
    size_t addr;
    size_t offset;
    TAILQ_ENTRY(xcd_elf_strtab,) link;
} xcd_elf_strtab_t;
#pragma clang diagnostic pop
typedef TAILQ_HEAD(xcd_elf_strtab_queue, xcd_elf_strtab,) xcd_elf_strtab_queue_t;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
struct xcd_elf_interface
{
    pid_t                    pid;
    xcd_memory_t            *memory;
    char                    *so_name;
    uintptr_t                load_bias;
    int                      is_gnu;

    //symbols (.dynsym with .dynstr, .symtab with .strtab)
    xcd_elf_symbols_queue_t  symbolsq;

    //string tables
    xcd_elf_strtab_queue_t   strtabq;

    //.note.gnu.build-id
    size_t                   build_id_offset;
    size_t                   build_id_size;

    //.eh_frame with option .eh_frame_hdr
    size_t                   eh_frame_offset;
    size_t                   eh_frame_size;
    uintptr_t                eh_frame_load_bias;
    size_t                   eh_frame_hdr_offset;
    size_t                   eh_frame_hdr_size;
    uintptr_t                eh_frame_hdr_load_bias;
    xcd_dwarf_t             *dwarf_eh_frame;
    xcd_dwarf_type_t         dwarf_eh_frame_type;

    //.debug_frame
    size_t                   debug_frame_offset;
    size_t                   debug_frame_size;
    xcd_dwarf_t             *dwarf_debug_frame;

    //.ARM.exidx
    size_t                   arm_exidx_offset;
    size_t                   arm_exidx_size;

    //.gnu_debugdata
    size_t                   gnu_debugdata_offset;
    size_t                   gnu_debugdata_size;

    //.dynamic
    size_t                   dynamic_offset;
    size_t                   dynamic_size;
};
#pragma clang diagnostic pop

static int xcd_elf_interface_check_valid(ElfW(Ehdr) *ehdr)
{
    //check magic
    if(0 != memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) return XCC_ERRNO_FORMAT;

    //check class (64/32)
#if defined(__LP64__)
    if(ELFCLASS64 != ehdr->e_ident[EI_CLASS]) return XCC_ERRNO_FORMAT;
#else
    if(ELFCLASS32 != ehdr->e_ident[EI_CLASS]) return XCC_ERRNO_FORMAT;
#endif

    //check endian (little/big)
    if(ELFDATA2LSB != ehdr->e_ident[EI_DATA]) return XCC_ERRNO_FORMAT;

    //check version
    if(EV_CURRENT != ehdr->e_ident[EI_VERSION]) return XCC_ERRNO_FORMAT;

    //check type
    if(ET_EXEC != ehdr->e_type && ET_DYN != ehdr->e_type) return XCC_ERRNO_FORMAT;

    //check machine
#if defined(__arm__)
    if(EM_ARM != ehdr->e_machine) return XCC_ERRNO_FORMAT;
#elif defined(__aarch64__)
    if(EM_AARCH64 != ehdr->e_machine) return XCC_ERRNO_FORMAT;
#elif defined(__i386__)
    if(EM_386 != ehdr->e_machine) return XCC_ERRNO_FORMAT;
#elif defined(__x86_64__)
    if(EM_X86_64 != ehdr->e_machine) return XCC_ERRNO_FORMAT;
#else
    return XCC_ERRNO_FORMAT;
#endif

    //check version
    if(EV_CURRENT != ehdr->e_version) return XCC_ERRNO_FORMAT;

    return 0;
}

static int xcd_elf_interface_read_program_headers(xcd_elf_interface_t *self, ElfW(Ehdr) *ehdr, uintptr_t *load_bias)
{
    size_t     i;
    ElfW(Phdr) phdr;
    int        try_save_load_bias = 0;
    int        r;
    
    for(i = 0; i < ehdr->e_phnum * ehdr->e_phentsize; i += ehdr->e_phentsize)
    {
        if(0 != xcd_memory_read_fully(self->memory, ehdr->e_phoff + i, &phdr, sizeof(phdr)))
        {
            r = XCC_ERRNO_MEM;
            goto err;
        }

        switch(phdr.p_type)
        {
        case PT_LOAD:
            {
                if(0 == (phdr.p_flags & PF_X)) continue;

                if(!try_save_load_bias && phdr.p_vaddr > phdr.p_offset)
                {
                    self->load_bias = phdr.p_vaddr - phdr.p_offset;
                    if(NULL != load_bias) *load_bias = self->load_bias;
                }
                try_save_load_bias = 1;
                break;
            }
        case PT_GNU_EH_FRAME:
            self->eh_frame_hdr_offset = phdr.p_offset;
            self->eh_frame_hdr_size = phdr.p_memsz;
            self->eh_frame_hdr_load_bias = phdr.p_vaddr - phdr.p_offset;
            break;
        case PT_ARM_EXIDX:
            self->arm_exidx_offset = phdr.p_offset;
            self->arm_exidx_size = phdr.p_memsz;
            break;
        case PT_DYNAMIC:
            self->dynamic_offset = phdr.p_offset;
            self->dynamic_size = phdr.p_memsz;
            break;
        default:
            break;
        }
    }
    return 0;

 err:
    return r;
}

static int xcd_elf_interface_read_section_headers(xcd_elf_interface_t *self, ElfW(Ehdr) *ehdr)
{
    size_t             i;
    ElfW(Shdr)         shdr;
    ElfW(Shdr)         str_shdr;
    size_t             sec_offset = 0;
    size_t             sec_size   = 0;
    xcd_elf_symbols_t *symbols, *symbols_tmp;
    xcd_elf_strtab_t  *strtab, *strtab_tmp;
    char               name[128];
    int                r;

    //get section header entry that contains the section names
    if(ehdr->e_shstrndx < ehdr->e_shnum)
    {
        if(0 != xcd_memory_read_fully(self->memory, ehdr->e_shoff + ehdr->e_shstrndx * ehdr->e_shentsize,
                                      &shdr, sizeof(shdr))) return XCC_ERRNO_MEM;
        sec_offset = shdr.sh_offset;
        sec_size   = shdr.sh_size;
    }

    for(i = ehdr->e_shentsize; i < ehdr->e_shnum * ehdr->e_shentsize; i += ehdr->e_shentsize)
    {
        if(0 != xcd_memory_read_fully(self->memory, ehdr->e_shoff + i, &shdr, sizeof(shdr)))
        {
            r = XCC_ERRNO_MEM;
            goto err;
        }

        switch(shdr.sh_type)
        {
        case SHT_NOTE:
            {
                if(shdr.sh_name >= sec_size) continue;
                if(0 != xcd_memory_read_string(self->memory, sec_offset + shdr.sh_name, name, sizeof(name), UINTPTR_MAX))
                    continue;
                
                if(0 == strcmp(name, ".note.gnu.build-id"))
                {
                    self->build_id_offset = shdr.sh_offset;
                    self->build_id_size = shdr.sh_size;
                }
            }
        case SHT_SYMTAB:
        case SHT_DYNSYM:
            {
                //get the associated strtab section
                if(shdr.sh_link >= ehdr->e_shnum) continue;
                if(0 != xcd_memory_read_fully(self->memory, ehdr->e_shoff + shdr.sh_link * ehdr->e_shentsize, &str_shdr, sizeof(str_shdr)))
                {
                    r = XCC_ERRNO_MEM;
                    goto err;
                }
                if(SHT_STRTAB != str_shdr.sh_type) continue;

                //save symbols and the associated strtab
                if(NULL == (symbols = malloc(sizeof(xcd_elf_symbols_t))))
                {
                    r = XCC_ERRNO_NOMEM;
                    goto err;
                }
                symbols->sym_offset = shdr.sh_offset;
                symbols->sym_end = shdr.sh_offset + shdr.sh_size;
                symbols->sym_entry_size = shdr.sh_entsize;
                symbols->str_offset = str_shdr.sh_offset;
                symbols->str_end = str_shdr.sh_offset + str_shdr.sh_size;
                TAILQ_INSERT_TAIL(&(self->symbolsq), symbols, link);
                break;
            }
        case SHT_STRTAB:
            {
                if(NULL == (strtab = malloc(sizeof(xcd_elf_strtab_t))))
                {
                    r = XCC_ERRNO_NOMEM;
                    goto err;
                }
                strtab->addr = shdr.sh_addr;
                strtab->offset = shdr.sh_offset;
                TAILQ_INSERT_TAIL(&(self->strtabq), strtab, link);
                break;
            }
        case SHT_PROGBITS:
            {
                if(shdr.sh_name >= sec_size) continue;
                if(0 != xcd_memory_read_string(self->memory, sec_offset + shdr.sh_name, name, sizeof(name), UINTPTR_MAX))
                {
                    r = XCC_ERRNO_MEM;
                    goto err;
                }
                
                if(0 == strcmp(name, ".debug_frame"))
                {
                    self->debug_frame_offset = shdr.sh_offset;
                    self->debug_frame_size = shdr.sh_size;
                }
                else if(0 == strcmp(name, ".eh_frame"))
                {
                    self->eh_frame_offset = shdr.sh_offset;
                    self->eh_frame_size = shdr.sh_size;
                    self->eh_frame_load_bias = shdr.sh_addr - shdr.sh_offset;
                }
                else if(0 == strcmp(name, ".eh_frame_hdr") && 0 == self->eh_frame_hdr_offset)
                {
                    self->eh_frame_hdr_offset = shdr.sh_offset;
                    self->eh_frame_hdr_size = shdr.sh_size;
                    self->eh_frame_hdr_load_bias = shdr.sh_addr - shdr.sh_offset;
                }
                else if(0 == strcmp(name, ".gnu_debugdata"))
                {
                    self->gnu_debugdata_offset = shdr.sh_offset;
                    self->gnu_debugdata_size = shdr.sh_size;                    
                }
                break;
            }
        default:
            break;
        }
    }

    return 0;
    
 err:
    TAILQ_FOREACH_SAFE(symbols, &(self->symbolsq), link, symbols_tmp)
    {
        TAILQ_REMOVE(&(self->symbolsq), symbols, link);
        free(symbols);
    }
    TAILQ_FOREACH_SAFE(strtab, &(self->strtabq), link, strtab_tmp)
    {
        TAILQ_REMOVE(&(self->strtabq), strtab, link);
        free(strtab);
    }
    return r;
}

int xcd_elf_interface_create(xcd_elf_interface_t **self, pid_t pid, xcd_memory_t *memory, uintptr_t *load_bias)
{
    int r;
    ElfW(Ehdr) ehdr;

    //get ELF header
    if(0 != xcd_memory_read_fully(memory, 0, &ehdr, sizeof(ehdr))) return XCC_ERRNO_MEM;
    
    //check valid
    if(0 != (r = xcd_elf_interface_check_valid(&ehdr))) return r;

    //init
    if(NULL == (*self = calloc(1, sizeof(xcd_elf_interface_t)))) return XCC_ERRNO_NOMEM;
    (*self)->pid = pid;
    (*self)->memory = memory;
    TAILQ_INIT(&((*self)->symbolsq));
    TAILQ_INIT(&((*self)->strtabq));

    //read program headers, save and return load_bias
    if(0 != (r = xcd_elf_interface_read_program_headers(*self, &ehdr, load_bias)))
    {
        free(*self);
        *self = NULL;
        return r;
    }

    //read section headers
    xcd_elf_interface_read_section_headers(*self, &ehdr);

    //dwarf from .eh_frame with .eh_frame_hdr
    if(0 != (*self)->eh_frame_hdr_offset && 0 != (*self)->eh_frame_hdr_size)
    {
        xcd_dwarf_create(&((*self)->dwarf_eh_frame), memory, pid,
                         (*self)->eh_frame_load_bias, (*self)->eh_frame_hdr_load_bias,
                         (*self)->eh_frame_hdr_offset, (*self)->eh_frame_hdr_size,
                         XCD_DWARF_TYPE_EH_FRAME_HDR);
        (*self)->dwarf_eh_frame_type = XCD_DWARF_TYPE_EH_FRAME_HDR;
    }

    //dwarf from .eh_frame without .eh_frame_hdr
    if(NULL == (*self)->dwarf_eh_frame && 0 != (*self)->eh_frame_offset && 0 != (*self)->eh_frame_size)
    {
        xcd_dwarf_create(&((*self)->dwarf_eh_frame), memory, pid,
                         (*self)->eh_frame_load_bias, 0,
                         (*self)->eh_frame_offset, (*self)->eh_frame_size,
                         XCD_DWARF_TYPE_EH_FRAME);
        (*self)->dwarf_eh_frame_type = XCD_DWARF_TYPE_EH_FRAME;
    }

    //dwarf from .debug_frame
    if(0 != (*self)->debug_frame_offset && 0 != (*self)->debug_frame_size)
        xcd_dwarf_create(&((*self)->dwarf_debug_frame), memory, pid,
                         (*self)->load_bias, 0,
                         (*self)->debug_frame_offset, (*self)->debug_frame_size,
                         XCD_DWARF_TYPE_DEBUG_FRAME);

    return 0;
}

xcd_elf_interface_t *xcd_elf_interface_gnu_create(xcd_elf_interface_t *self)
{
    xcd_elf_interface_t *gnu;
    xcd_memory_t        *memory = NULL;
    uint8_t             *src = NULL;
    size_t               src_size;
    uint8_t             *dst = NULL;
    size_t               dst_size;
    
    if(0 == self->gnu_debugdata_offset || 0 == self->gnu_debugdata_size) return NULL;

    //dump xz data
    src_size = self->gnu_debugdata_size;
    if(NULL == (src = malloc(src_size))) goto err;
    if(0 != xcd_memory_read_fully(self->memory, self->gnu_debugdata_offset, src, src_size)) goto err;

    //xz decompress
    if(0 != xcd_util_xz_decompress(src, src_size, &dst, &dst_size)) goto err;

    //create memory object
    if(0 != xcd_memory_create_from_buf(&memory, dst, dst_size)) goto err;

    //create ELF interface from .gnu_debugdata
    if(0 != xcd_elf_interface_create(&gnu, self->pid, memory, NULL)) goto err;
    gnu->load_bias = self->load_bias;
    gnu->is_gnu = 1;

    return gnu;

 err:
    XCD_LOG_WARN("ELF: create GNU interface FAILED");
    if(NULL != memory) xcd_memory_destroy(&memory);
    if(NULL != dst) free(dst);
    if(NULL != src) free(src);
    return NULL;
}

int xcd_elf_interface_dwarf_step(xcd_elf_interface_t *self, uintptr_t step_pc, xcd_regs_t *regs, int *finished)
{
    int r;

    //try .debug_frame
    if(NULL != self->dwarf_debug_frame)
    {
        r = xcd_dwarf_step(self->dwarf_debug_frame, regs, step_pc, finished);
#if XCD_ELF_INTERFACE_DEBUG
        XCD_LOG_DEBUG("ELF: step by .debug_frame%s %s, step_pc=%"PRIxPTR", load_bias=%"PRIxPTR", finished=%d",
                      (self->is_gnu ? " (in .gnu_debugdata)" : ""),
                      (0 == r ? "OK" : "FAILED"),
                      step_pc, self->load_bias, *finished);
#endif
        if(0 == r) return 0;
    }

    //try .eh_frame (with or without .eh_frame_hdr)
    if(NULL != self->dwarf_eh_frame)
    {
        r = xcd_dwarf_step(self->dwarf_eh_frame, regs, step_pc, finished);
#if XCD_ELF_INTERFACE_DEBUG
        XCD_LOG_DEBUG("ELF: step by %s%s %s, step_pc=%"PRIxPTR", load_bias=%"PRIxPTR", finished=%d",
                      (XCD_DWARF_TYPE_EH_FRAME_HDR == self->dwarf_eh_frame_type ? ".eh_frame_hdr" : ".eh_frame"),
                      (self->is_gnu ? " (in .gnu_debugdata)" : ""),
                      (0 == r ? "OK" : "FAILED"),
                      step_pc, self->load_bias, *finished);
#endif
        if(0 == r) return 0;
    }
    
    return XCC_ERRNO_MISSING;
}

#ifdef __arm__
int xcd_elf_interface_arm_exidx_step(xcd_elf_interface_t *self, uintptr_t step_pc, xcd_regs_t *regs, int *finished)
{
    int r;
    
    if(0 != self->arm_exidx_offset && 0 != self->arm_exidx_size)
    {
        r = xcd_arm_exidx_step(regs, self->memory, self->pid, self->arm_exidx_offset, self->arm_exidx_size,
                                  self->load_bias, step_pc, finished);
#if XCD_ELF_INTERFACE_DEBUG
        XCD_LOG_DEBUG("ELF: step by .ARM.exidx %s, step_pc=%x, load_bias=%x, finished=%d",
                      (0 == r ? "OK" : "FAILED"), step_pc, self->load_bias, *finished);
#endif
        if(0 == r) return 0;
    }

    return XCC_ERRNO_MISSING;
}
#endif

int xcd_elf_interface_get_function_info(xcd_elf_interface_t *self, uintptr_t addr, char **name, size_t *name_offset)
{
    xcd_elf_symbols_t *symbols;
    size_t             offset;
    size_t             start_offset;
    size_t             end_offset;
    size_t             str_offset;
    ElfW(Sym)          sym;
    char               buf[512];

    TAILQ_FOREACH(symbols, &(self->symbolsq), link)
    {
        for(offset = symbols->sym_offset; offset < symbols->sym_end; offset += symbols->sym_entry_size)
        {
            if(0 != xcd_memory_read_fully(self->memory, offset, &sym, sizeof(sym))) break;
            if(sym.st_shndx == SHN_UNDEF || ELF_ST_TYPE(sym.st_info) != STT_FUNC) continue;
            
            start_offset = sym.st_value;
            end_offset = start_offset + sym.st_size;
            if(addr < start_offset || addr >= end_offset) continue;
            
            *name_offset = addr - start_offset;
            
            str_offset = symbols->str_offset + sym.st_name;
            if(str_offset >= symbols->str_end) continue;
            
            if(0 != xcd_memory_read_string(self->memory, str_offset, buf, sizeof(buf), symbols->str_end - str_offset)) continue;
            if(NULL == (*name = strdup(buf))) break;

            return 0;
        }
    }

    *name = NULL;
    *name_offset = 0;
    return XCC_ERRNO_NOTFND;
}

int xcd_elf_interface_get_symbol_addr(xcd_elf_interface_t *self, const char *name, uintptr_t *addr)
{
    xcd_elf_symbols_t *symbols;
    size_t             offset;
    size_t             str_offset;
    ElfW(Sym)          sym;
    char               buf[512];

    TAILQ_FOREACH(symbols, &(self->symbolsq), link)
    {
        for(offset = symbols->sym_offset; offset < symbols->sym_end; offset += symbols->sym_entry_size)
        {
            //read .symtab / .dynsym
            if(0 != xcd_memory_read_fully(self->memory, offset, &sym, sizeof(sym))) break;
            if(sym.st_shndx == SHN_UNDEF) continue;

            //read .strtab / .dynstr
            str_offset = symbols->str_offset + sym.st_name;
            if(str_offset >= symbols->str_end) continue;
            if(0 != xcd_memory_read_string(self->memory, str_offset, buf, sizeof(buf), symbols->str_end - str_offset)) continue;

            //compare symbol name
            if(0 != strcmp(name, buf)) continue;

            //found it
            *addr = sym.st_value;
            return 0;
        }
    }

    *addr = 0;
    return XCC_ERRNO_NOTFND;
}

int xcd_elf_interface_get_build_id(xcd_elf_interface_t *self, uint8_t *build_id, size_t build_id_len, size_t *build_id_len_ret)
{
    ElfW(Nhdr) nhdr;
    uintptr_t  addr;
    int        r;

    if(0 == self->build_id_offset || 0 == self->build_id_size) return XCC_ERRNO_MISSING;
    if(self->build_id_size < sizeof(nhdr)) return XCC_ERRNO_FORMAT;

    //read .note.gnu.build-id header
    if(0 != (r = xcd_memory_read_fully(self->memory, self->build_id_offset, &nhdr, sizeof(nhdr)))) return r;
    if(0 == nhdr.n_descsz) return XCC_ERRNO_MISSING;
    if(nhdr.n_descsz > build_id_len) return XCC_ERRNO_NOSPACE;

    //read .note.gnu.build-id data
    addr = self->build_id_offset + sizeof(nhdr) + ((nhdr.n_namesz + 3) & (~(unsigned int)3)); //skip the name (should be "GNU\0": 0x47 0x4e 0x55 0x00)
    if(0 != (r = xcd_memory_read_fully(self->memory, addr, build_id, nhdr.n_descsz))) return r;

    if(NULL != build_id_len_ret) *build_id_len_ret = nhdr.n_descsz;
    return 0;
}

char *xcd_elf_interface_get_so_name(xcd_elf_interface_t *self)
{
    uintptr_t         offset;
    ElfW(Dyn)         dyn;
    xcd_elf_strtab_t *strtab;
    uintptr_t         strtab_addr = 0;
    uintptr_t         strtab_size = 0;
    uintptr_t         soname_offset = 0;
    char              buf[256] = "\0";

    if(0 == self->dynamic_offset || 0 == self->dynamic_size) goto err;
    if(NULL != self->so_name) return self->so_name;

    for(offset = self->dynamic_offset; offset < self->dynamic_offset + self->dynamic_size; offset += sizeof(dyn))
    {
        if(0 != xcd_memory_read_fully(self->memory, offset, &dyn, sizeof(dyn))) goto err;
        if(DT_NULL == dyn.d_tag) break;
        switch(dyn.d_tag)
        {
        case DT_STRTAB:
            strtab_addr = dyn.d_un.d_ptr;
            break;
        case DT_STRSZ:
            strtab_size = (uintptr_t)(dyn.d_un.d_val);
            break;
        case DT_SONAME:
            soname_offset = (uintptr_t)(dyn.d_un.d_val);
            break;
        default:
            break;
        }
    }

    TAILQ_FOREACH(strtab, &(self->strtabq), link)
    {
        if(strtab->addr == strtab_addr)
        {
            soname_offset += strtab->offset;
            if(soname_offset >= strtab->offset + strtab_size) goto err;
            if(0 != xcd_memory_read_string(self->memory, soname_offset, buf, sizeof(buf), strtab->offset + strtab_size - soname_offset)) goto err;
            if(NULL == (self->so_name = strdup(buf))) goto err;
            return self->so_name;
        }
    }

 err:
    self->so_name = "";
    return self->so_name;
}
