/*
 * Copyright (c) 2023 Huawei Technologies Co.,Ltd.
 *
 * CBB is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 *
 * cm_blackbox.c
 *
 *
 * IDENTIFICATION
 *    src/cm_utils/cm_blackbox.c
 *
 * -------------------------------------------------------------------------
 */

#ifndef _WIN32
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <ucontext.h>
#include <cm_signal.h>
#include <sys/prctl.h>
#include "cm_timer.h"
#include "cm_file.h"
#include "cm_blackbox.h"
#include "cm_backtrace.h"

__thread bool32 g_in_backtrace = CM_FALSE;

#ifdef __cplusplus
extern "C" {
#endif

#if (!defined(__cplusplus)) && (!defined(NO_CPP_DEMANGLE))
#define NO_CPP_DEMANGLE
#endif

#ifndef NO_CPP_DEMANGLE
#include <cxxabi.h>
#ifdef __cplusplus
using __cxxabiv1::__cxa_demangle;
#endif
#endif

#if (defined __x86_64__)
#define REGFORMAT "%s0x%016llx\n"
#elif (defined __aarch64__)
#define REGFORMAT "x[%02d]    0x%016llx\n"
#endif

static bool32 need_print_sig_addr(uint32 sig_num)
{
    const uint32 sig_list[] = {SIGILL, SIGBUS, SIGFPE, SIGSEGV};
    for (uint32 i = 0; i < ARRAY_NUM(sig_list); i++) {
        if (sig_num == sig_list[i]) {
            return CM_TRUE;
        }
    }
    return CM_FALSE;
}

static void print_sig_extend_info(box_excp_item_t *excep_info)
{
    if (excep_info->sig_code <= 0) {
        LOG_BLACKBOX_INF("Sending User = %u\n", excep_info->sig_uid);
        LOG_BLACKBOX_INF("Sending Process = %d\n", excep_info->sig_pid);
        return;
    }
    if (need_print_sig_addr(excep_info->sig_index)) {
        LOG_BLACKBOX_INF("Exception Addr = 0x%016llx\n", (uint64)excep_info->sig_addr);
    }
}

void cm_print_sig_info(box_excp_item_t *excep_info, void *cpu_info)
{
    LOG_BLACKBOX_INF("\n==================exception info==================\n");
    LOG_BLACKBOX_INF("Exception Date = %s\n", excep_info->date);
    LOG_BLACKBOX_INF("Exception Number = %u\n", excep_info->sig_index);
    LOG_BLACKBOX_INF("Exception Code = %d\n", excep_info->sig_code);
    LOG_BLACKBOX_INF("Exception Name = %s\n", excep_info->sig_name);
    LOG_BLACKBOX_INF("Exception Process = 0x%016llx\n", excep_info->loc_id);
    LOG_BLACKBOX_INF("Exception Thread = 0x%016llx\n", (uint64)excep_info->thread_id);
    LOG_BLACKBOX_INF("Exception Process name = %s\n", excep_info->loc_name);
    print_sig_extend_info(excep_info);
    LOG_BLACKBOX_INF("Version = %s\n", excep_info->version);
    LOG_BLACKBOX_INF("Platform = %s\n", excep_info->platform);
}

void cm_print_reg(box_reg_info_t *reg_info)
{
    LOG_BLACKBOX_INF("Register Contents:\n");
#if (defined __x86_64__)
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RAX    ", reg_info->rax);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RBX    ", reg_info->rbx);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RCX    ", reg_info->rcx);   
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RDX    ", reg_info->rdx);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RSI    ", reg_info->rsi);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RDI    ", reg_info->rdi);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RBP    ", reg_info->rbp);  
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RSP    ", reg_info->rsp);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  R8     ", reg_info->r8);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  R9     ", reg_info->r9);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  R10    ", reg_info->r10);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  R11    ", reg_info->r11);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  R12    ", reg_info->r12);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  R13    ", reg_info->r13);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  R14    ", reg_info->r14);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  R15    ", reg_info->r15);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  RIP    ", reg_info->rip);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  EFLAGS ", reg_info->eflags);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  CS     ", reg_info->cs);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  ERR    ", reg_info->err); 
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  TRAPNO ", reg_info->trapno);  
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  OM     ", reg_info->oldmask);
    LOG_BLACKBOX_INF(REGFORMAT, "reg:  CR2    ", reg_info->cr2);            
#elif (defined __aarch64__)
    for (uint32 i = 0; i < 31; i++) {
        LOG_BLACKBOX_INF(REGFORMAT, i, reg_info->reg[i]); 
    }
    LOG_BLACKBOX_INF("sp      0x%016llx\n", reg_info->sp);
    LOG_BLACKBOX_INF("pc      0x%016llx\n", reg_info->pc);
#endif
}

void cm_print_assembly(box_reg_info_t *reg_info)
{
    unsigned char *pc = NULL;
    LOG_BLACKBOX_INF("\nAssembly instruction:");
#if (defined __x86_64__)
    pc = (unsigned char*)reg_info->rip;
#elif (defined __aarch64__)
    pc = (unsigned char*)reg_info->sp;
#endif
    for (int32 i = -8; i < 16; i++) {
        if (i % 8 == 0) {
            if (i == 0) {
                LOG_BLACKBOX_INF("\n%s ", ">");
            } else {
                LOG_BLACKBOX_INF("\n  ");
            }
        }
        LOG_BLACKBOX_INF("%02x ", *(pc + i));
    }
    LOG_BLACKBOX_INF("\n");
}

status_t cm_proc_sign_init(box_excp_item_t *excep_info)
{
    errno_t ret = memset_s(excep_info, sizeof(box_excp_item_t), 0, sizeof(box_excp_item_t));
    if (ret != EOK) {
        CM_THROW_ERROR(ERR_SYSTEM_CALL, ret);
        return CM_ERROR;
    }
    return CM_SUCCESS;
}

void cm_proc_get_register_info(box_reg_info_t *cpu_info, ucontext_t *uc)
{
    if (cpu_info == NULL || uc == NULL) {
        return;
    }
#if (defined __x86_64__)
    cpu_info->rax = uc->uc_mcontext.gregs[REG_RAX];
    cpu_info->rbx = uc->uc_mcontext.gregs[REG_RBX];
    cpu_info->rcx = uc->uc_mcontext.gregs[REG_RCX];
    cpu_info->rdx = uc->uc_mcontext.gregs[REG_RDX];
    cpu_info->rsi = uc->uc_mcontext.gregs[REG_RSI];    
    cpu_info->rdi = uc->uc_mcontext.gregs[REG_RDI];
    cpu_info->rbp = uc->uc_mcontext.gregs[REG_RBP];
    cpu_info->rsp = uc->uc_mcontext.gregs[REG_RSP];
    cpu_info->r8 = uc->uc_mcontext.gregs[REG_R8];
    cpu_info->r9 = uc->uc_mcontext.gregs[REG_R9];
    cpu_info->r10 = uc->uc_mcontext.gregs[REG_R10];
    cpu_info->r11 = uc->uc_mcontext.gregs[REG_R11];
    cpu_info->r12 = uc->uc_mcontext.gregs[REG_R12];
    cpu_info->r13 = uc->uc_mcontext.gregs[REG_R13];
    cpu_info->r14 = uc->uc_mcontext.gregs[REG_R14];
    cpu_info->r15 = uc->uc_mcontext.gregs[REG_R15];
    cpu_info->rip = uc->uc_mcontext.gregs[REG_RIP];
    cpu_info->eflags = uc->uc_mcontext.gregs[REG_EFL];
    cpu_info->cs = uc->uc_mcontext.gregs[REG_CSGSFS];
    cpu_info->err = uc->uc_mcontext.gregs[REG_ERR];
    cpu_info->trapno = uc->uc_mcontext.gregs[REG_TRAPNO];
    cpu_info->oldmask = uc->uc_mcontext.gregs[REG_OLDMASK];
    cpu_info->cr2 = uc->uc_mcontext.gregs[REG_CR2];
#elif (defined __aarch64__)
for (uint32 i = 0; i < 31; i++) {
        cpu_info->reg[i] = uc->uc_mcontext.regs[i]; 
    }
    cpu_info->sp = uc->uc_mcontext.sp;
    cpu_info->pc = uc->uc_mcontext.pc;
#endif
}

void cm_proc_sig_get_fixed_header(box_excp_item_t *excep_info)
{
    uint32 loop = 0;
    char *platform_name = NULL;
    char *loc_name = NULL;
    excep_info->magic = (uint32)BOX_EXCP_MAGIC;
    excep_info->trace_tail[loop] = (uint32)BOX_TAIL_MAGIC;
    for (loop = 1; loop < BOX_SPACE_SIZE; loop++) {
        excep_info->trace_tail[loop] = (uint32)BOX_TAIL_MAGIC;
    }
    excep_info->loc_id = cm_sys_pid();
    platform_name = cm_sys_platform_name();
    errno_t rc = strncpy_s(excep_info->platform, CM_NAME_BUFFER_SIZE, platform_name, strlen(platform_name));
    if (rc != EOK) {
        LOG_DEBUG_ERR("Fail to copy platform name %s", platform_name);
        excep_info->platform[0] = '\0';
    }
    loc_name = cm_sys_program_name();
    rc = strncpy_s(excep_info->loc_name, CM_FILE_NAME_BUFFER_SIZE + 1, loc_name, strlen(loc_name));
    if (rc != EOK) {
        LOG_DEBUG_ERR("Fail to copy loc name %s", loc_name);
        excep_info->loc_name[0] = '\0';
    }
}

void cm_proc_sig_get_header(box_excp_item_t *excep_info, int32 sig_num, siginfo_t *siginfo, void *context)
{
    excep_info->sig_index = (uint32)sig_num;
    excep_info->thread_id = pthread_self();
    if (siginfo != NULL) {
        excep_info->sig_code = siginfo->si_code;
        if (excep_info->sig_code <= 0) {
            excep_info->sig_uid = siginfo->si_uid;
            excep_info->sig_pid = siginfo->si_pid;
        } else if (need_print_sig_addr(excep_info->sig_index)) {
            excep_info->sig_addr = siginfo->si_addr;
        }
    }
    (void)cm_date2str(g_timer()->now, "yyyy-mm-dd hh24:mi:ss.ff3", excep_info->date, CM_MAX_TIME_STRLEN);
}

#define CM_MAP_BUFFER_LEN 512
void cm_save_proc_maps_file(box_excp_item_t *excep_info)
{
    int32 fd;
    ssize_t cnt;
    char buffer[CM_MAP_BUFFER_LEN] = {0};
    (void)sprintf_s(buffer, sizeof(buffer), "/proc/%u/maps", (uint32)excep_info->loc_id);
    LOG_BLACKBOX_INF("Proc maps information:\n");
    if (cm_open_file_ex(buffer, O_SYNC | O_RDONLY | O_BINARY, S_IRUSR, &fd) != CM_SUCCESS) {
        return;
    }
    cnt = read(fd, buffer, sizeof(buffer) - 1);
    while (cnt > 0) {
        ((char *)buffer)[cnt] = '\0';
        LOG_BLACKBOX_INF("%s", buffer);
        cnt = read(fd, buffer, sizeof(buffer) - 1);
    }
    LOG_BLACKBOX_INF("\n");
    cm_close_file(fd);
}

#define CM_PROC_MEM_BUFFER_LEN 512

void cm_save_proc_meminfo_file(void)
{
    int32 fd;
    ssize_t cnt;
    char buffer[CM_PROC_MEM_BUFFER_LEN] = {0};
    LOG_BLACKBOX_INF("Proc memory information:\n");
    if (cm_open_file_ex(BOX_PROC_MEMINFO_PATH, O_SYNC | O_RDONLY | O_BINARY, S_IRUSR, &fd) != CM_SUCCESS) {
        return;
    }
    cnt = read(fd, buffer, sizeof(buffer) - 1);
    while (cnt > 0) {
        ((char *)buffer)[cnt] = '\0';
        LOG_BLACKBOX_INF("%s", buffer);
        cnt = read(fd, buffer, sizeof(buffer) - 1);
    }
    LOG_BLACKBOX_INF("\n");
    cm_close_file(fd);
}

sig_buf_node_t g_sig_bt_buffer = {0};

void cm_sig_collect_backtrace(uint32 log_id, thread_t* thread, const char *format, ...)
{
    int32 cnt;
    int32 len;
    errno_t ret;
    char log_head[CM_MAX_LOG_HEAD_LENGTH] = {0};
    if (thread == NULL || thread->closed || thread->id == 0) {
        return;
    }
    cm_spin_lock(&g_sig_bt_buffer.lock, NULL);
    g_sig_bt_buffer.status = SIG_BUFFER_COLLECTING;
    len = snprintf_s(log_head, CM_MAX_LOG_HEAD_LENGTH, CM_MAX_LOG_HEAD_LENGTH - 1, "\n");
    if (len < 0) {
        cm_spin_unlock(&g_sig_bt_buffer.lock);
        return;
    }
    va_list args;
    va_start(args, format);
    ret = vsnprintf_s(log_head + len, CM_MAX_LOG_HEAD_LENGTH - len, CM_MAX_LOG_HEAD_LENGTH - len - 1, format, args);
    va_end(args);
    if (ret < 0) {
        cm_spin_unlock(&g_sig_bt_buffer.lock);
        return;
    }
    len += ret;
    ret = snprintf_s(log_head + len, CM_MAX_LOG_HEAD_LENGTH - len, CM_MAX_LOG_HEAD_LENGTH - len - 1, " stack info\n");
    if (ret < 0) {
        cm_spin_unlock(&g_sig_bt_buffer.lock);
        return;
    }
    cnt = 0;
    pthread_kill(thread->id, SIG_BACKTRACE);
    while (g_sig_bt_buffer.status == SIG_BUFFER_COLLECTING && cnt < 100) {
        cm_sleep(1);
        cnt++;
    }
    if (g_sig_bt_buffer.status == SIG_BUFFER_COLLECTED) {
        if (log_id== LOG_BLACKBOX) {
            LOG_BLACKBOX_INF("%s", log_head);
            LOG_BLACKBOX_INF("%s", g_sig_bt_buffer.buf);
        } else {
            LOG_DYNAMIC_INF("%s", log_head);
            LOG_DYNAMIC_INF("%s", g_sig_bt_buffer.buf);
        }
    }
    g_sig_bt_buffer.status = SIG_BUFFER_IDLE;
    cm_spin_unlock(&g_sig_bt_buffer.lock);
}

void cm_print_call_link(box_reg_info_t *reg_info)
{
    void *array[CM_MAX_BLACK_BOX_DEPTH] = {0};
    void *cfa_addr[CM_MAX_BLACK_BOX_DEPTH] = {0};
    stack_name_array call_stack[CM_MAX_BLACK_BOX_DEPTH] = {0};
    bool32 dump_cfa = CM_TRUE;
    void *pc = NULL;
    uint32 start_size;
    if (g_in_backtrace) {
        LOG_BLACKBOX_INF("\nIn backtrace, can not collect backtrace\n");
        return;
    }
    size_t size = (size_t)cm_backtrace(array, cfa_addr, CM_MAX_BLACK_BOX_DEPTH, &dump_cfa);
    cm_backtrace_symbols(array, size, call_stack);
#if defined(_DEBUG) || defined(DEBUG) || defined(DB_DEBUG_VERSION)
    start_size = CM_MIN_BLACK_BOX_DEPTH;
#else
    start_size = CM_INIT_BLACK_BOX_DEPTH + 1;
#endif
    if (size <= start_size) {
        LOG_BLACKBOX_INF("print stack failed, backtrace stack size %u is not correct\n", (uint32)size);
        return;
    }
    LOG_BLACKBOX_INF("\nStack information when exception\n");
    for (uint32 i = start_size; i < (uint32)size; i++) {
        LOG_BLACKBOX_INF("  %s\n", call_stack[i].data);
    }
    if (!dump_cfa) {
        return;
    }
    LOG_BLACKBOX_INF("\nDump each stack frame memory:\n");
    for (uint32 i = start_size; i < (uint32)size - 1; i++) {
        LOG_BLACKBOX_INF("Frame %u %s\n", i - CM_INIT_BLACK_BOX_DEPTH, call_stack[i].data);
#if (defined __x86_64__)
        pc = (i == start_size) ? (void *)reg_info->rsp : cfa_addr[i];
#elif (defined __aarch64__)
        pc = (i == start_size) ? (void *)reg_info->reg[29] : cfa_addr[i];
#endif
        LOG_BLACKBOX_INF("Stack area: %p - %p", i < size - 1 ? cfa_addr[i + 1] : 0x0, pc);
        cm_dump_mem_in_blackbox(pc, (uint32)(cfa_addr[i + 1] - pc));
        LOG_BLACKBOX_INF("\n\n");
    }
}

void cm_sig_backtrace_func_call(void)
{
    void *array[CM_MAX_BLACK_BOX_DEPTH] = {0};
    stack_name_array call_stack[CM_MAX_BLACK_BOX_DEPTH] = {0};
    uint32 offset = 0;
    int32 lens;
    bool32 dump_cfa = CM_FALSE;
    if (g_in_backtrace) {
        lens = snprintf_s(g_sig_bt_buffer.buf, SIG_STACK_MAX_BUFFER, SIG_STACK_MAX_BUFFER - 1, "%s\n",
            "In backtrace, do not trigger collection backtrace.");
        if (lens > 0) {
            g_sig_bt_buffer.offset = lens;
        }
        g_sig_bt_buffer.status = SIG_BUFFER_COLLECTED;
        return;
    }
    size_t size = (size_t)cm_backtrace(array, NULL, CM_MAX_BLACK_BOX_DEPTH, &dump_cfa);
    cm_backtrace_symbols(array, size, call_stack);
    if (size <= CM_MIN_BLACK_BOX_DEPTH) {
        g_sig_bt_buffer.status = SIG_BUFFER_ERROR;
        return;
    }
    for (uint32 i = CM_MIN_BLACK_BOX_DEPTH; i < (uint32)size; i++) {
        lens = snprintf_s(g_sig_bt_buffer.buf + offset, SIG_STACK_MAX_BUFFER - offset,
            SIG_STACK_MAX_BUFFER - offset - 1, "%s\n", call_stack[i].data);
        if (lens <= 0) {
            break;
        }
        offset += lens;
    }
    g_sig_bt_buffer.offset = offset;
    CM_MFENCE;
    g_sig_bt_buffer.status = SIG_BUFFER_COLLECTED;
    return;
}

void cm_sig_backtrace_func(int32 sig_num, siginfo_t *sig_info, void *context)
{
    if (g_sig_bt_buffer.status != SIG_BUFFER_COLLECTING) {
        return;
    }
    if (!cm_spin_try_lock(&g_sig_bt_buffer.thread_lock)) {
        return;
    }
    sigset_t obj_sign_mask;
    sigset_t save_mask;
    (void)sigfillset(&obj_sign_mask);
    pthread_sigmask(SIG_SETMASK, &obj_sign_mask, &save_mask);
    cm_sig_backtrace_func_call();
    pthread_sigmask(SIG_SETMASK, &save_mask, NULL);
    cm_spin_unlock(&g_sig_bt_buffer.thread_lock);
}

#ifdef __cplusplus
}
#endif
#endif