/*
    Binary symbols lookup
    Copyright (C) 2012-2020 Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/

#ifdef Z_HAVE_LIBBFD
#ifndef PACKAGE
#define PACKAGE Z_PACKAGE
#endif
#ifndef PACKAGE_VERSION
#define PACKAGE_VERSION Z_PACKAGE_VERSION
#endif
#endif

#include <zbfd.h>

#include <zdebug.h>
#include <zerror.h>
#include <zmsgbox.h>
#include <zpath.h>
#include <zstr.h>


#ifdef Z_HAVE_EXECINFO_H
#include <execinfo.h>
#endif

#include <stdio.h>
#include <string.h>

//#ifdef Z_MINGW
struct z_stack_frame{
    struct z_stack_frame *next;
    void *ret;
};
//#endif

#ifdef __GNUC__
#define STACKSTEP(n) \
    if (i >= size) break; \
    if (!__builtin_frame_address(n)) break; \
    buffer[i++] = __builtin_return_address(n);
#endif

int z_backtrace(void **buffer, int size, void *vex, void *ctx, int level){
    int i = 0;
/*#if defined (Z_UNIX) && defined(__GNUC__)
    do{
        i = 0;
        STACKSTEP(0);STACKSTEP(1);STACKSTEP(2);STACKSTEP(3);STACKSTEP(4);
        STACKSTEP(5);STACKSTEP(6);STACKSTEP(7);STACKSTEP(8);STACKSTEP(9);
        STACKSTEP(10);STACKSTEP(11);STACKSTEP(12);STACKSTEP(14);STACKSTEP(15);
        STACKSTEP(16);STACKSTEP(17);STACKSTEP(18);STACKSTEP(19);STACKSTEP(20);
        STACKSTEP(20);STACKSTEP(21);STACKSTEP(22);STACKSTEP(24);STACKSTEP(25);
        STACKSTEP(26);STACKSTEP(27);STACKSTEP(28);STACKSTEP(29);STACKSTEP(30);
    }while(0);
#elif*/ 
#if defined(Z_MSC)
/*	HMODULE dll;
	T_StackWalk64 P_StackWalk64;*/
	STACKFRAME64 frame;
	DWORD ret;
    EXCEPTION_POINTERS *ex = (EXCEPTION_POINTERS*)vex;
	
    /*dll = LoadLibrary("dbghelp.dll");
	if (!dll) return 0;
	P_StackWalk64 = (T_StackWalk64)GetProcAddress(dll, "StackWalk64");
	if (!P_StackWalk64) return 0;*/
	
	memset(&frame, 0, sizeof(frame));
    frame.AddrPC.Offset = ex->ContextRecord->Eip;
	frame.AddrPC.Mode = AddrModeFlat;
	frame.AddrFrame.Offset = ex->ContextRecord->Ebp;
	frame.AddrFrame.Mode = AddrModeFlat;
	frame.AddrStack.Offset = ex->ContextRecord->Esp;
	frame.AddrStack.Mode = AddrModeFlat;

	for (i = 0; i < size; i++){
		ret = StackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &frame, NULL, NULL, NULL, NULL, NULL);
		if (!ret) break;
        buffer[i] = (void *)frame.AddrPC.Offset;
	}
	//FreeLibrary(dll);
#elif defined(Z_MINGW)
//    register struct z_stack_frame *ebp asm("ebp");
    EXCEPTION_POINTERS *ex = (EXCEPTION_POINTERS*)vex;
    struct z_stack_frame *fp = (struct z_stack_frame*)ex->ContextRecord->Ebp;
    struct z_stack_frame *x;
    
    buffer[i++] = (void *)ex->ContextRecord->Eip;

    for (x = fp; x != NULL && x->ret != NULL; x = x->next){
        if (i == size) break;
        buffer[i++] = x->ret;
    }
#elif defined(Z_ANDROID)
/*    register struct z_stack_frame *ebp asm("ebp"); TODO FIXME
    struct z_stack_frame *x;

    for (x = ebp; x != NULL && x->ret != NULL; x = x->next){
        if (i == size) break;
        buffer[i++] = x->ret;
    }*/
/*    i = 0;
    do{
        STACKSTEP(0);STACKSTEP(1);STACKSTEP(2);STACKSTEP(3);STACKSTEP(4);
        STACKSTEP(5);STACKSTEP(6);STACKSTEP(7);STACKSTEP(8);STACKSTEP(9);
        STACKSTEP(10);STACKSTEP(11);STACKSTEP(12);STACKSTEP(14);STACKSTEP(15);
        STACKSTEP(16);STACKSTEP(17);STACKSTEP(18);STACKSTEP(19);STACKSTEP(20);
        STACKSTEP(20);STACKSTEP(21);STACKSTEP(22);STACKSTEP(24);STACKSTEP(25);
        STACKSTEP(26);STACKSTEP(27);STACKSTEP(28);STACKSTEP(29);STACKSTEP(30);
    }while(0);*/
#else
    // Z_UNIX
    i = backtrace(buffer, size);

/*    if (ctx != NULL && level > 0 && level < i){
#ifdef __x86_64__
        ucontext_t *uc = (ucontext_t*)ctx;
        buffer[level] = (void *)uc->uc_mcontext.gregs[REG_RIP];
#endif
#ifdef __i386__
        ucontext_t *uc = (ucontext_t*)ctx;
        buffer[level] = (void *)uc->uc_mcontext.gregs[REG_EIP];
#endif
#ifdef __hppa__
        ucontext_t *uc = (ucontext_t*)ctx;
        buffer[level] = (void *)uc->uc_mcontext.sc_iaoq[0] & ~0Ă3UL ;
#endif
#if defined (__ppc__) || defined (__powerpc__)
        ucontext_t *uc = (ucontext_t*)ctx;
        buffer[level] = (void *)uc->uc_mcontext.regs->nip ;
#endif
    }*/

#endif // Z_UNIX

#if 0    
    {
        int j;
        GString *gs = g_string_new("");
        g_string_append_printf(gs, "size=%d stacklen=%d\n", size, i);

        for (j = 0; j < i; j++) g_string_append_printf(gs, "#%-2d %p\n", j, buffer[j]);
        z_msgbox_info("z_backtrace", "%s", gs->str);
        g_string_free(gs, TRUE);
    }
#endif
    return i;
}



struct zbfd *zbfd_init(){
#ifdef Z_MSC_MINGW
	HMODULE dll;
	T_SymInitialize P_SymInitialize;
	int ret;
#endif
    struct zbfd *zbfd;
    zbfd = g_new0(struct zbfd, 1);
    zbfd->errstr = g_string_new("");
#ifdef Z_MSC_MINGW
    zbfd->symbol = (SYMBOL_INFO *)g_malloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char));
    zbfd->symbol->MaxNameLen = 255;
    zbfd->symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
#endif

#ifdef Z_MSC_MINGW

	dll = LoadLibrary("dbghelp.dll");
	if (!dll) { 
		g_string_printf(zbfd->errstr, "Can't LoadLibrary(dbghelp.dll), error %d", GetLastError());	
	}else{
		P_SymInitialize = (T_SymInitialize)GetProcAddress(dll, "SymInitialize");
		if (!P_SymInitialize) {
			g_strdup_printf("Can't load SymInitialize from dbghelp.dll, error %d", GetLastError());
		}else{
			zbfd->SymFromAddr = (T_SymFromAddr)GetProcAddress(dll, "SymFromAddr");
			if (!zbfd->SymFromAddr) {
				g_strdup_printf("Can't load SymFromAddr from dbghelp.dll, error %d", GetLastError());
			}else{
				ret = P_SymInitialize(GetCurrentProcess(), NULL, TRUE);
				if (!ret){
					int err = GetLastError();
					g_string_printf(zbfd->errstr, "SymInitialize failed: error %d ", err);
					z_lasterror(zbfd->errstr);
				}
			}
		}
	}

	//FreeLibrary(dll);

#endif


#if 0
    z_msgbox_info(__FUNCTION__, 
#ifdef Z_HAVE_EXECINFO_H
    "Z_HAVE_EXECINFO_H "
#endif
#ifdef Z_HAVE_LIBBFD
    "Z_HAVE_LIBBFD "
#endif
#ifdef Z_MSC
    "Z_MSC "
#endif
#ifdef Z_MINGW
    "Z_MINGW "
#endif
#if defined (Z_UNIX) && defined(__GNUC__)
    "UNIX+__GNUC__"
#endif
    );
#endif
    return zbfd;
}

void zbfd_free(struct zbfd *zbfd){
    g_string_free(zbfd->errstr, TRUE);
    //zbfd->syms
#ifdef Z_HAVE_BFD
    if (zbfd->bfd) bfd_close(zbfd->bfd);
#endif
#ifdef Z_MSC_MINGW
    g_free(zbfd->symbol);
#endif
#if defined(Z_MINGW) || defined(Z_UNIX)
    if (zbfd->dbin) fclose(zbfd->dbin);
    if (zbfd->dzia) fclose(zbfd->dzia);
#endif
    g_free(zbfd);
}

int zbfd_open(struct zbfd *zbfd, char *filename, char *appddir){

#ifdef Z_HAVE_LIBBFD
    long storage, symcount;
    int dynamic = 0;
    char **matching;

    zbfd->bfd = bfd_openr(filename, Z_TARGET);
    if (!zbfd->bfd) {                                     
        g_string_printf(zbfd->errstr, "bfd_openr() failed: %s", bfd_errmsg(bfd_get_error()));
        goto failed;
    }

    if (bfd_check_format(zbfd->bfd, bfd_archive)){
        g_string_printf(zbfd->errstr, "bfd_check_format() failed: %s", bfd_errmsg(bfd_get_error()));
        goto failed;
    }
    
    if (!bfd_check_format_matches (zbfd->bfd, bfd_object, &matching)){
        g_string_printf(zbfd->errstr, "bfd_check_format_matches() failed: %s", bfd_errmsg(bfd_get_error()));
        goto failed;
    }

    if (!(bfd_get_file_flags(zbfd->bfd) & HAS_SYMS)){
        g_string_printf(zbfd->errstr, "bfd_get_file_flags() failed: %s", bfd_errmsg(bfd_get_error()));
        goto failed;
    }

    storage = bfd_get_symtab_upper_bound(zbfd->bfd);
    if (!storage){
        storage = bfd_get_dynamic_symtab_upper_bound(zbfd->bfd);
        dynamic = 1;
    }
    if (storage < 0){
        g_string_printf(zbfd->errstr, "no bfd symbol storage present\n");
        goto failed;
    }

    zbfd->syms = (struct bfd_symbol **)g_malloc(storage);
    if (dynamic)
        symcount = bfd_canonicalize_dynamic_symtab(zbfd->bfd, zbfd->syms);
    else
        symcount = bfd_canonicalize_symtab(zbfd->bfd, zbfd->syms);

    if (symcount < 0){
        g_string_printf(zbfd->errstr, "bfd_canonicalize_(dynamic_)symtab failed: %s", bfd_errmsg(bfd_get_error()));
        goto failed;
    }
    goto afterbfd;

failed:;
    //return -1;
afterbfd:;
#elif defined(Z_MINGW) || defined(Z_UNIX)
    char *binfilename = z_binary_file_name();
    dbg("\n***\nbinfilename='%s'\n", binfilename);
    if (binfilename){
        GString *gs = g_string_new("");
        if (appddir){
            g_string_append(gs, appddir);
            g_string_append(gs, "/tucnak.d");
        }else{
#ifdef Z_MSC_MINGW
            char *c = z_strcasestr(binfilename, ".exe");
            if (c) *c = '\0';
#endif
            g_string_append(gs, binfilename);
            g_string_append(gs, ".d");
        }

        zbfd->dbin = fopen(gs->str, "rt");
        dbg("zbfd_open('%s')=%p\n", gs->str, zbfd->dbin);

        g_string_free(gs, TRUE);
        g_free(binfilename);
    }
    
    char *ziafilename = z_libzia_file_name(&zbfd->ziacodebase);
    dbg("\n***\nziafilename='%s'\n", ziafilename);
    if (ziafilename){
        char *c;
        GString *gs = g_string_new("");
#ifdef Z_MSC_MINGW
        c = z_strcasestr(ziafilename, ".dll");
        if (c) *c = '\0';
#endif
        c = strrchr(ziafilename, '/');
        if (c) *c = '\0';
        g_string_append(gs, ziafilename);
        g_string_append(gs, "/libzia/");
        g_string_append(gs, c+1);
        g_string_append(gs, ".d");
        dbg("d='%s'\n", gs->str);

        zbfd->dzia = fopen(gs->str, "rt");
        dbg("zbfd_open('%s')=%p\n", gs->str, zbfd->dzia);

        g_string_free(gs, TRUE);
        g_free(ziafilename);
    }
#endif
         

    return 0;
}


#ifdef Z_HAVE_LIBBFD
static void zbfd_find_address_in_section(bfd *bfd, asection *section, void *data){
    bfd_vma vma;
    bfd_size_type size;
    struct zbfd *zbfd = (struct zbfd *)data;

//    printf("find_address_in_section(bfd=%p, section=%p, data=%p)\n", bfd, section, data);
    if (zbfd->found) return;

// bfd_get_section_flags is defined only in "older" version (2020)
// bfd_section_flags is a static inline function in "newer" version

#ifndef bfd_get_section_flags
    if ((bfd_section_flags(section) & SEC_ALLOC) == 0) return;
#else
    if ((bfd_get_section_flags(bfd, section) & SEC_ALLOC) == 0) return;
#endif

#ifndef bfd_get_section_flags    
    vma = bfd_section_vma(section);
#else
    vma = bfd_get_section_vma(bfd, section);
#endif
    if (zbfd->pc < vma) return;


#ifndef bfd_get_section_flags
    size = bfd_section_size(section);
#else
    size = bfd_get_section_size(section);
#endif
    if (zbfd->pc >= vma + size) return;

    zbfd->found = bfd_find_nearest_line(bfd, section, zbfd->syms, zbfd->pc - vma, &zbfd->filename, &zbfd->functionname, &zbfd->line);
}
#endif


int zbfd_lookup(struct zbfd *zbfd, void *addr){
    int ret = -1;

    zbfd->filename = NULL;
    zbfd->functionname = NULL;
    zbfd->line = 0;

#ifdef Z_HAVE_LIBBFD
    char s[32];

    sprintf(s, "%p", addr);
    zbfd->pc = bfd_scan_vma(s, NULL, 0);
    zbfd->found = 0;
    bfd_map_over_sections(zbfd->bfd, zbfd_find_address_in_section, zbfd);
    
    if (zbfd->filename != NULL){
        char *c = strrchr(zbfd->filename, '/');
        if (c != NULL) zbfd->filename = c + 1;
    }
    //z_msgbox_info(__FUNCTION__, "%p %d", addr, zbfd->found);
     if(zbfd->found) ret = 0;
    // // if not found take chance to other methods
#endif

#ifdef Z_MSC_MINGW
    //z_msgbox_info(__FUNCTION__, "addr=%p", addr);
	if (!zbfd->SymFromAddr(GetCurrentProcess(), (DWORD64)(long)addr, 0, zbfd->symbol)) {
		int err = GetLastError();
	}else{
        zbfd->functionname =  zbfd->symbol->Name;
	    zbfd->offset = (long)(((DWORD64)(long)addr) - zbfd->symbol->Address);
        ret = 0;
    }
#endif 
#if defined(Z_MINGW) || defined(Z_UNIX)
    char ss[256], *tok, *sym = NULL;
    char *max = NULL;
        
    strcpy(zbfd->dsym, "");

//    dbg("zbfd->dbin=%p\n", zbfd->dbin);
    if (zbfd->dbin){
        fseek(zbfd->dbin, 0, SEEK_SET);
//        dbg("search for %p\n", addr);
        while (fgets(ss, sizeof(ss) - 1, zbfd->dbin)){
            char *sadr = strtok_r(ss, " \r\n", &tok); 
            char *a = (char *)z_strtop(sadr);
//            dbg("  a=%p ", a);
            if (a > (char *)addr) continue;
            if (a < max) continue;
            max = a;
            sym = strtok_r(NULL, " \r\n", &tok);
            if (!sym) { // last item is .text end
                strcpy(zbfd->dsym, "");
                break;
            }
            g_strlcpy(zbfd->dsym, sym, sizeof(zbfd->dsym));
        }
        if (strlen(zbfd->dsym) > 0){
            zbfd->functionname = zbfd->dsym;
            zbfd->offset = (char *)addr - max;
            ret = 0;
        }
    }
//    dbg("zbfd->dzia=%p\n", zbfd->dzia);
    if (zbfd->dzia){
        fseek(zbfd->dzia, 0, SEEK_SET);
//        dbg("search for %p\n", addr);
        while (fgets(ss, sizeof(ss) - 1, zbfd->dzia)){
            char *sadr = strtok_r(ss, " \r\n", &tok); 
            char *a = (char *)z_strtop(sadr);
//            dbg("  a=%p ", a);
            a += (long)zbfd->ziacodebase;
//            dbg("  aa=%p \n", a);
            if (a > (char *)addr) continue;
            if (a < max) continue;
            max = a;
            sym = strtok_r(NULL, " \r\n", &tok);
            if (!sym) { // last item is .text end
                strcpy(zbfd->dsym, "");
                break;
            }
            g_strlcpy(zbfd->dsym, sym, sizeof(zbfd->dsym));
        }
        if (strlen(zbfd->dsym) > 0){
            zbfd->functionname = zbfd->dsym;
            zbfd->offset = (char *)addr - max;
            ret = 0;
        }
    }
    
#endif    
    return ret;     // no Z_MSC, return error
}
