/*******************************************************************************
 * Copyright (c) 2007, 2014 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * The Eclipse Public License is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 * You may elect to redistribute this code under either of these licenses.
 *
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/

/*
 * TCF service line Numbers
 * The service associates locations in the source files with the corresponding
 * machine instruction addresses in the executable object.
 */

#include <tcf/config.h>

#if SERVICE_LineNumbers && (!ENABLE_LineNumbersProxy || ENABLE_LineNumbersMux) && ENABLE_PE

#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <tcf/framework/json.h>
#include <tcf/framework/protocol.h>
#include <tcf/framework/context.h>
#include <tcf/framework/exceptions.h>
#include <tcf/framework/cache.h>
#include <tcf/services/pathmap.h>
#include <tcf/services/linenumbers.h>
#include <system/Windows/tcf/windbgcache.h>
#include <system/Windows/tcf/context-win32.h>
#if ENABLE_LineNumbersMux
#define LINENUMBERS_READER_PREFIX win32_reader_
#include <tcf/services/linenumbers_mux.h>
#endif

static int compare_path(Channel * chnl, Context * ctx, const char * file, const char * name) {
    int i, j;
    char * full_name = NULL;

    if (file == NULL) return 0;
    if (name == NULL) return 0;

    while (file[0] == '.') {
        if (file[1] == '.' && file[2] == '/') file += 3;
        else if (file[1] == '/') file += 2;
        else break;
    }
    i = strlen(file);

    full_name = canonic_path_map_file_name(name);
    j = strlen(full_name);
    if (i <= j && strcmp(file, full_name + j - i) == 0) return 1;
#if SERVICE_PathMap
    {
        char * s = apply_path_map(chnl, ctx, full_name, PATH_MAP_TO_CLIENT);
        if (s != full_name) {
            full_name = canonic_path_map_file_name(s);
            j = strlen(full_name);
            if (i <= j && strcmp(file, full_name + j - i) == 0) return 1;
        }
    }
#endif
    return 0;
}

int line_to_address(Context * ctx, const char * file, int line, int column,
                    LineNumbersCallBack * callback, void * user_args) {
    int err = 0;

    if (ctx == NULL) err = ERR_INV_CONTEXT;
    else if (ctx->exited) err = ERR_ALREADY_EXITED;

    if (err == 0 && ctx->parent != NULL) ctx = ctx->parent;

    if (err == 0) {
        CodeArea area;
        LONG offset = 0;
        IMAGEHLP_LINE img_line;
        Channel * chnl = cache_channel();

        memset(&area, 0, sizeof(area));
        memset(&img_line, 0, sizeof(img_line));
        img_line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
        file = canonic_path_map_file_name(file);

        if (!SymGetLineFromName(get_context_handle(ctx), NULL, file, line, &offset, &img_line)) {
            DWORD win_err = GetLastError();
            if (win_err != ERROR_NOT_FOUND) {
                err = set_win32_errno(win_err);
            }
        }
        else {
            IMAGEHLP_LINE img_next;
            memcpy(&img_next, &img_line, sizeof(img_next));
            img_next.SizeOfStruct = sizeof(IMAGEHLP_LINE);
            if (!SymGetLineNext(get_context_handle(ctx), &img_next)) {
                err = set_win32_errno(GetLastError());
            }
            else if (compare_path(chnl, ctx, file, img_line.FileName)) {
                area.file = img_line.FileName;
                area.start_line = img_line.LineNumber;
                area.start_address = img_line.Address;
                area.end_line = img_next.LineNumber;
                area.end_address = img_next.Address;
                callback(&area, user_args);
            }
        }
    }

    if (err != 0) {
        errno = err;
        return -1;
    }
    return 0;
}

#define JMPD08      0xeb
#define JMPD32      0xe9
#define GRP5        0xff
#define JMPN        0x25

int address_to_line(Context * ctx, ContextAddress addr0, ContextAddress addr1, LineNumbersCallBack * callback, void * user_args) {
    int err = 0;
    int not_found = 0;
    DWORD offset = 0;
    IMAGEHLP_LINE line;
    IMAGEHLP_LINE next;
    ContextAddress org_addr0 = addr0;
    ContextAddress org_addr1 = addr1;

    if (ctx == NULL) err = ERR_INV_CONTEXT;
    else if (ctx->exited) err = ERR_ALREADY_EXITED;

    if (err == 0 && ctx->parent != NULL) ctx = ctx->parent;

    memset(&line, 0, sizeof(line));
    line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
    if (addr0 >= addr1) not_found = 1;

    while (err == 0 && not_found == 0 && !SymGetLineFromAddr(get_context_handle(ctx), addr0, &offset, &line)) {
        DWORD w = GetLastError();
        if (w == ERROR_MOD_NOT_FOUND) {
            not_found = 1;
        }
        else if (w == ERROR_INVALID_ADDRESS) {
            /* Check if the address points to a jump instruction (e.g. inside a jump table)
             * and try to get line info for jump destination address.
             */
            unsigned char instr;    /* instruction opcode at <addr0> */
            ContextAddress dest = 0; /* Jump destination address */
            if (context_read_mem(ctx, addr0, &instr, 1) == 0) {
                /* If instruction is a JMP, get destination adrs */
                if (instr == JMPD08) {
                    signed char disp08;
                    if (context_read_mem(ctx, addr0 + 1, &disp08, 1) == 0) {
                        dest = addr0 + 2 + disp08;
                        org_addr1 = addr0 + 2;
                    }
                }
                else if (instr == JMPD32) {
                    int disp32;
                    assert(sizeof(disp32) == 4);
                    if (context_read_mem(ctx, addr0 + 1, &disp32, 4) == 0) {
                        dest = addr0 + 5 + disp32;
                        org_addr1 = addr0 + 5;
                    }
                }
                else if (instr == GRP5) {
                    if (context_read_mem(ctx, addr0 + 1, &instr, 1) == 0 && instr == JMPN) {
                        ContextAddress ptr = 0;
                        if (context_read_mem(ctx, addr0 + 2, &ptr, 4) == 0) {
                            context_read_mem(ctx, ptr, &dest, 4);
                            org_addr1 = addr0 + 6;
                        }
                    }
                }
            }
            if (dest != 0) {
                addr0 = dest;
                addr1 = dest + 1;
            }
            else {
                not_found = 1;
            }
        }
        else {
            err = set_win32_errno(w);
        }
    }
    memcpy(&next, &line, sizeof(next));
    if (err == 0 && !not_found && !SymGetLineNext(get_context_handle(ctx), &next)) {
        DWORD w = GetLastError();
        if (w == ERROR_NOT_FOUND) {
            /* Last line in the source file */
            ULONG64 buffer[(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64)];
            SYMBOL_INFO * info = (SYMBOL_INFO *)buffer;
            info->SizeOfStruct = sizeof(SYMBOL_INFO);
            info->MaxNameLen = MAX_SYM_NAME;
            if (SymFromAddr(get_context_handle(ctx), next.Address, NULL, info)) {
                next.Address = (ULONG_PTR)info->Address + info->Size;
                next.LineNumber++;
            }
        }
        else {
            err = set_win32_errno(GetLastError());
        }
    }

    if (err == 0 && !not_found) {
        while (line.Address < next.Address && line.Address < addr1 && next.Address > addr0) {
            CodeArea area;

            memset(&area, 0, sizeof(area));
            area.file = line.FileName;
            area.start_address = line.Address;
            area.start_line = line.LineNumber;
            area.end_address = next.Address;
            area.end_line = next.LineNumber;
            if (org_addr0 != addr0) {
                area.start_address = org_addr0;
                area.end_address = org_addr1;
            }
            callback(&area, user_args);
            memcpy(&line, &next, sizeof(line));
            if (!SymGetLineNext(get_context_handle(ctx), &next)) break;
        }
    }

    if (err != 0) {
        errno = err;
        return -1;
    }
    return 0;
}

void ini_line_numbers_lib(void) {
    SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS);
#if ENABLE_LineNumbersMux
    add_line_numbers_reader(&line_numbers_reader);
#endif
}

#endif /* SERVICE_LineNumbers && (!ENABLE_LineNumbersProxy || ENABLE_LineNumbersMux) && ENABLE_PE */
