// 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 <sys/mman.h>
#include "queue.h"
#include "xcc_errno.h"
#include "xcc_util.h"
#include "xcd_maps.h"
#include "xcd_map.h"
#include "xcd_util.h"
#include "xcd_log.h"

#define XCD_MAPS_ABORT_MSG_NAME    "[anon:abort message]"
#define XCD_MAPS_ABORT_MSG_FLAGS   (PROT_READ | PROT_WRITE)
#define XCD_MAPS_ABORT_MSG_MAGIC_1 0xb18e40886ac388f0ULL
#define XCD_MAPS_ABORT_MSG_MAGIC_2 0xc6dfba755a1de0b5ULL

typedef struct xcd_maps_item
{
    xcd_map_t map;
    TAILQ_ENTRY(xcd_maps_item,) link;
} xcd_maps_item_t;
typedef TAILQ_HEAD(xcd_maps_item_queue, xcd_maps_item,) xcd_maps_item_queue_t;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
struct xcd_maps
{
    xcd_maps_item_queue_t maps;
    pid_t                 pid;
};
#pragma clang diagnostic pop

static int xcd_maps_parse_line(char *line, xcd_maps_item_t **mi)
{
    uintptr_t  start;
    uintptr_t  end;
    char       flags[5];
    size_t     offset;
    int        pos;
    char      *name;

    *mi = NULL;
    
    //scan
    if(sscanf(line, "%"SCNxPTR"-%"SCNxPTR" %4s %"SCNxPTR" %*x:%*x %*d%n", &start, &end, flags, &offset, &pos) != 4) return 0;
    name = xcc_util_trim(line + pos);
    
    //create map
    if(NULL == (*mi = malloc(sizeof(xcd_maps_item_t)))) return XCC_ERRNO_NOMEM;
    return xcd_map_init(&((*mi)->map), start, end, offset, flags, name);
}

int xcd_maps_create(xcd_maps_t **self, pid_t pid)
{
    char             buf[512];
    FILE            *fp;
    xcd_maps_item_t *mi;
    int              r;

    if(NULL == (*self = malloc(sizeof(xcd_maps_t)))) return XCC_ERRNO_NOMEM;
    TAILQ_INIT(&((*self)->maps));
    (*self)->pid = pid;

    snprintf(buf, sizeof(buf), "/proc/%d/maps", pid);
    if(NULL == (fp = fopen(buf, "r"))) return XCC_ERRNO_SYS;

    while(fgets(buf, sizeof(buf), fp))
    {
        if(0 != (r = xcd_maps_parse_line(buf, &mi)))
        {
            fclose(fp);
            return r;
        }
        
        if(NULL != mi)
            TAILQ_INSERT_TAIL(&((*self)->maps), mi, link);
    }
    
    fclose(fp);
    return 0;
}

void xcd_maps_destroy(xcd_maps_t **self)
{
    xcd_maps_item_t *mi, *mi_tmp;
    TAILQ_FOREACH_SAFE(mi, &((*self)->maps), link, mi_tmp)
    {
        TAILQ_REMOVE(&((*self)->maps), mi, link);
        xcd_map_uninit(&(mi->map));
        free(mi);
    }

    *self = NULL;
}

xcd_map_t *xcd_maps_find_map(xcd_maps_t *self, uintptr_t pc)
{
    xcd_maps_item_t *mi;

    TAILQ_FOREACH(mi, &(self->maps), link)
        if(pc >= mi->map.start && pc < mi->map.end)
            return &(mi->map);

    return NULL;
}

xcd_map_t *xcd_maps_get_prev_map(xcd_maps_t *self, xcd_map_t *cur_map)
{
    (void)self;

    xcd_maps_item_t *cur_mi = (xcd_maps_item_t *)cur_map;
    xcd_maps_item_t *prev_mi = TAILQ_PREV(cur_mi, xcd_maps_item_queue, link);

    return (NULL == prev_mi ? NULL : &(prev_mi->map));
}

uintptr_t xcd_maps_find_abort_msg(xcd_maps_t *self)
{
    xcd_maps_item_t *mi;
    uintptr_t        p;
    uint64_t         magic;

    TAILQ_FOREACH(mi, &(self->maps), link)
    {
        if(NULL != mi->map.name && 0 == strcmp(mi->map.name, XCD_MAPS_ABORT_MSG_NAME) &&
           XCD_MAPS_ABORT_MSG_FLAGS == mi->map.flags)
        {
            p = mi->map.start;
            if(0 != xcd_util_ptrace_read_fully(self->pid, p, &magic, sizeof(uint64_t))) continue;
            if(XCD_MAPS_ABORT_MSG_MAGIC_1 != magic) continue;

            p += sizeof(uint64_t);
            if(0 != xcd_util_ptrace_read_fully(self->pid, p, &magic, sizeof(uint64_t))) continue;
            if(XCD_MAPS_ABORT_MSG_MAGIC_2 != magic) continue;

            return mi->map.start;
        }
    }

    return 0;
}

uintptr_t xcd_maps_find_pc(xcd_maps_t *self, const char *pathname, const char *symbol)
{
    xcd_maps_item_t *mi;
    xcd_elf_t       *elf;
    uintptr_t        addr = 0;

    TAILQ_FOREACH(mi, &(self->maps), link)
    {
        if(NULL != mi->map.name && 0 == strcmp(mi->map.name, pathname))
        {
            //get ELF
            if(NULL == (elf = xcd_map_get_elf(&(mi->map), self->pid, (void *)self))) return 0;

            //get rel addr (offset)
            if(0 != xcd_elf_get_symbol_addr(elf, symbol, &addr)) return 0;

            return xcd_map_get_abs_pc(&(mi->map), addr, self->pid, (void *)self);
        }
    }

    return 0; //not found
}

int xcd_maps_record(xcd_maps_t *self, int log_fd)
{
    int              r;
    xcd_maps_item_t *mi;
    uintptr_t        size;
    uintptr_t        total_size = 0;
    size_t           max_size = 0;
    size_t           max_offset = 0;
    size_t           width_size = 0;
    size_t           width_offset = 0;
    uintptr_t        load_bias = 0;
    char             load_bias_buf[64] = "\0";
    char            *name;
    char            *prev_name = NULL;

    //get width of size and offset columns
    TAILQ_FOREACH(mi, &(self->maps), link)
    {
        size = mi->map.end - mi->map.start;
        if(size > max_size) max_size = size;
        if(mi->map.offset > max_offset) max_offset = mi->map.offset;
    }
    while(0 != max_size)
    {
        max_size /= 0x10;
        width_size++;
    }
    if(0 == width_size) width_size = 1;
    while(0 != max_offset)
    {
        max_offset /= 0x10;
        width_offset++;
    }
    if(0 == width_offset) width_offset = 1;

    //dump
    if(0 != (r = xcc_util_write_str(log_fd, "memory map:\n"))) return r;
    TAILQ_FOREACH(mi, &(self->maps), link)
    {
        //get load_bias
        if(NULL != mi->map.elf && 0 != (load_bias = xcd_elf_get_load_bias(mi->map.elf)))
            snprintf(load_bias_buf, sizeof(load_bias_buf), " (load bias 0x%"PRIxPTR")", load_bias);
        else
            load_bias_buf[0] = '\0';

        //fix name and load_bias
        if(NULL != mi->map.name)
        {
            if(NULL == prev_name)
                name = mi->map.name;
            else if(0 == strcmp(prev_name, mi->map.name) && '\0' == load_bias_buf[0])
                name = ">"; //same as prev line
            else
                name = mi->map.name;
        }
        else
        {
            name = "";
        }

        //save prev name
        prev_name = mi->map.name;

        //update total size
        size = mi->map.end - mi->map.start;
        total_size += size;

        if(0 != (r = xcc_util_write_format(log_fd,
                                           "    %0"XCC_UTIL_FMT_ADDR"-%0"XCC_UTIL_FMT_ADDR" %c%c%c %*"PRIxPTR" %*"PRIxPTR" %s%s\n",
                                           mi->map.start, mi->map.end,
                                           mi->map.flags & PROT_READ ? 'r' : '-',
                                           mi->map.flags & PROT_WRITE ? 'w' : '-',
                                           mi->map.flags & PROT_EXEC ? 'x' : '-',
                                           width_offset, mi->map.offset,
                                           width_size, size,
                                           name, load_bias_buf))) return r;
    }
    if(0 != (r = xcc_util_write_format(log_fd, "    TOTAL SIZE: 0x%"PRIxPTR"K (%"PRIuPTR"K)\n\n",
                                       total_size / 1024, total_size / 1024))) return r;

    return 0;
}
