/*
  Copyright (C) 2000-2006 Silicon Graphics, Inc.  All Rights Reserved.
  Portions Copyright 2007-2010 Sun Microsystems, Inc. All rights reserved.
  Portions Copyright 2009-2018 SN Systems Ltd. All rights reserved.
  Portions Copyright 2008-2018 David Anderson. All rights reserved.

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

  This program is distributed in the hope that it would be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  Further, this software is distributed without any warranty that it is
  free of the rightful claim of any third person regarding infringement
  or the like.  Any license provided herein, whether implied or
  otherwise, applies only to this software file.  Patent licenses, if
  any, provided herein do not apply to combinations of this program with
  other software, or any other product whatsoever.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write the Free Software Foundation, Inc., 51
  Franklin Street - Fifth Floor, Boston MA 02110-1301, USA.

*/

#include "globals.h"
#include "esb.h"
#include "esb_using_functions.h"
#include "sanitized.h"

static struct esb_s esb_string;

void
ranges_esb_string_destructor(void)
{
    esb_destructor(&esb_string);
}
/* Because we do not know what DIE is involved, if the
   object being printed has different address sizes
   in different compilation units this will not work
   properly: anything could happen. */
extern void
print_ranges(Dwarf_Debug dbg)
{
    Dwarf_Unsigned off = 0;
    int group_number = 0;
    int wasdense = 0;
    Dwarf_Error pr_err = 0;
    struct esb_s truename;
    char buf[DWARF_SECNAME_BUFFER_SIZE];
    unsigned loopct = 0;

    glflags.current_section_id = DEBUG_RANGES;
    if (!glflags.gf_do_print_dwarf) {
        return;
    }
#if 0
    {
        esb_constructor_fixed(&truename,buf,sizeof(buf));
        get_true_section_name(dbg,".debug_ranges",
            &truename,TRUE);
        printf("\n%s\n",sanitized(esb_get_string(&truename)));
    }
#endif

    /*  Turn off dense, we do not want  print_ranges_list_to_extra
        to use dense form here. */
    wasdense = glflags.dense;
    glflags.dense = 0;
    for (;;++loopct) {
        Dwarf_Ranges *rangeset = 0;
        Dwarf_Signed rangecount = 0;
        Dwarf_Unsigned bytecount = 0;

        /*  We do not know what DIE is involved, we use
            the older call here. */
        int rres = dwarf_get_ranges(dbg,off,&rangeset,
            &rangecount,&bytecount,&pr_err);
        if (!loopct) {
            esb_constructor_fixed(&truename,buf,sizeof(buf));
            get_true_section_name(dbg,".debug_ranges",
                &truename,TRUE);
            printf("\n%s\n",sanitized(esb_get_string(&truename)));
        }
        if (rres == DW_DLV_OK) {
            char *val = 0;
            printf(" Ranges group %d:\n",group_number);
            esb_empty_string(&esb_string);
            print_ranges_list_to_extra(dbg,off,
                rangeset,rangecount,bytecount,
                &esb_string);
            dwarf_ranges_dealloc(dbg,rangeset,rangecount);
            val = esb_get_string(&esb_string);
            printf("%s",sanitized(val));
            ++group_number;
        } else if (rres == DW_DLV_NO_ENTRY) {
            printf("End of %s.\n",sanitized(esb_get_string(&truename)));
            break;
        } else {
            /*  ERROR, which does not quite mean a real error,
                as we might just be misaligned reading things without
                a DW_AT_ranges offset.*/
            printf("End of %s..\n",sanitized(esb_get_string(&truename)));
            break;
        }
        off += bytecount;
    }
    glflags.dense = wasdense;
    esb_destructor(&truename);
}

/*  Extracted this from print_range_attribute() to isolate the check of
    the range list.
*/
static void
check_ranges_list(Dwarf_Debug dbg,
    UNUSEDARG Dwarf_Off die_off,
    Dwarf_Die cu_die,
    Dwarf_Unsigned original_off,
    Dwarf_Ranges *rangeset,
    Dwarf_Signed rangecount,
    Dwarf_Unsigned bytecount)
{
    Dwarf_Unsigned off = original_off;
    Dwarf_Signed index = 0;
    Dwarf_Addr base_address = glflags.CU_base_address;
    Dwarf_Addr lopc = 0;
    Dwarf_Addr hipc = 0;
    Dwarf_Bool bError = FALSE;
    Dwarf_Half elf_address_size = 0;
    Dwarf_Addr elf_max_address = 0;
    Dwarf_Error rlerr = 0;
    static boolean do_print = TRUE;
    const char *sec_name = 0;
    struct esb_s truename;
    char buf[DWARF_SECNAME_BUFFER_SIZE];

    esb_constructor_fixed(&truename,buf,sizeof(buf));
    get_true_section_name(dbg,".debug_ranges",
        &truename,FALSE);
    sec_name = esb_get_string(&truename);
    get_address_size_and_max(dbg,&elf_address_size,&elf_max_address,&rlerr);

    /* Ignore last entry, is the end-of-list */
    for (index = 0; index < rangecount - 1; index++) {
        Dwarf_Ranges *r = rangeset + index;

        if (r->dwr_addr1 == elf_max_address) {
            /* (0xffffffff,addr), use specific address (current PU address) */
            base_address = r->dwr_addr2;
        } else {
            /* (offset,offset), update using CU address */
            lopc = r->dwr_addr1 + base_address;
            hipc = r->dwr_addr2 + base_address;
            DWARF_CHECK_COUNT(ranges_result,1);

            /*  Check the low_pc and high_pc
                are within a valid range in
                the .text section */
            if (IsValidInBucketGroup(glflags.pRangesInfo,lopc) &&
                IsValidInBucketGroup(glflags.pRangesInfo,hipc)) {
                /* Valid values; do nothing */
            } else {
                /*  At this point may be we
                    are dealing with a
                    linkonce symbol */
                if (IsValidInLinkonce(glflags.pLinkonceInfo,
                    glflags.PU_name,lopc,hipc)) {
                    /* Valid values; do nothing */
                } else {
                    struct esb_s errbuf;

                    bError = TRUE;
                    esb_constructor(&errbuf);
                    esb_append_printf(&errbuf,
                        "%s: Address outside a "
                        "valid .text range",sanitized(sec_name));
                    DWARF_CHECK_ERROR(ranges_result,
                        esb_get_string(&errbuf));
                    esb_destructor(&errbuf);
                    if (glflags.gf_check_verbose_mode && do_print) {
                        /*  Update DIEs offset just for printing */
                        int dioff_res = dwarf_die_offsets(cu_die,
                            &glflags.DIE_overall_offset,
                            &glflags.DIE_offset,&rlerr);
                        if (dioff_res != DW_DLV_OK) {
                            print_error(dbg, "dwarf_die_offsets",dioff_res,
                                rlerr);
                        }
                        printf(
                            "Offset = 0x%" DW_PR_XZEROS DW_PR_DUx
                            ", Base = 0x%" DW_PR_XZEROS DW_PR_DUx
                            ", "
                            "Low = 0x%" DW_PR_XZEROS DW_PR_DUx
                            " (0x%" DW_PR_XZEROS  DW_PR_DUx
                            "), High = 0x%"
                            DW_PR_XZEROS  DW_PR_DUx
                            " (0x%" DW_PR_XZEROS DW_PR_DUx
                            ")\n",
                            off,base_address,lopc,
                            r->dwr_addr1,hipc,
                            r->dwr_addr2);
                    }
                }
            }
        }
        /*  Each entry holds 2 addresses (offsets) */
        off += elf_address_size * 2;
    }

    /*  In the case of errors, we have to print the range records that
        caused the error. */
    if (bError && glflags.gf_check_verbose_mode && do_print) {
        struct esb_s rangesstr;
        esb_constructor(&rangesstr);

        printf("\n");
        print_ranges_list_to_extra(dbg,original_off,
            rangeset,rangecount,bytecount,
            &rangesstr);
        printf("%s\n", sanitized(esb_get_string(&rangesstr)));
        esb_destructor(&rangesstr);
    }

    /*  In the case of printing unique errors, stop the printing of any
        subsequent errors, which have the same text. */
    if (bError && glflags.gf_check_verbose_mode &&
        glflags.gf_print_unique_errors) {
        do_print = FALSE;
    }
    esb_destructor(&truename);
}

/*  Records information about compilers (producers) found in the
    debug information, including the check results for several
    categories (see -k option). */
typedef struct {
    Dwarf_Off die_off;
    Dwarf_Off range_off;
} Range_Array_Entry;

/*  Array to record the DW_AT_range attribute DIE, to be used at the end
    of the CU, to check the range values; DWARF4 allows an offset relative
    to the low_pc as the high_pc value. Also, LLVM generates for the CU the
    pair (low_pc, at_ranges) instead of the traditional (low_pc, high_pc).
*/
static Range_Array_Entry *range_array = NULL;
static Dwarf_Unsigned range_array_size = 0;
static Dwarf_Unsigned range_array_count = 0;
#define RANGE_ARRAY_INITIAL_SIZE 64

/*  Allocate space to store information about the ranges; the values are
    extracted from the DW_AT_ranges attribute. The space is reused by all CUs.
*/
void
allocate_range_array_info()
{
    if (range_array == NULL) {
        /* Allocate initial range array info */
        range_array = (Range_Array_Entry *)
            calloc(RANGE_ARRAY_INITIAL_SIZE,sizeof(Range_Array_Entry));
        range_array_size = RANGE_ARRAY_INITIAL_SIZE;
    }
}

void
release_range_array_info()
{
    if (range_array) {
        free(range_array);
        range_array = 0;
        range_array_size = 0;
        range_array_count = 0;
    }
}

/*  Clear out values from previous CU */
static void
reset_range_array_info()
{
    if (range_array) {
        memset((void *)range_array,0,
            (range_array_count) * sizeof(Range_Array_Entry));
        range_array_count = 0;
    }
}

void
record_range_array_info_entry(Dwarf_Off die_off,Dwarf_Off range_off)
{
    /* Record a new detected range info. */
    if (range_array_count == range_array_size) {
        /* Resize range array */
        range_array_size *= 2;
        range_array = (Range_Array_Entry *)
            realloc(range_array,
            (range_array_size) * sizeof(Range_Array_Entry));
    }
    /* The 'die_off' is the Global Die Offset */
    range_array[range_array_count].die_off = die_off;
    range_array[range_array_count].range_off = range_off;
    ++range_array_count;
}

/*  Now that we are at the end of the CU, check the range lists */
void
check_range_array_info(Dwarf_Debug dbg)
{
    if (range_array && range_array_count) {
        /*  Traverse the range array and for each entry:
            Load the ranges
            Check for any outside conditions */
        Dwarf_Off original_off = 0;
        Dwarf_Off die_off = 0;
        Dwarf_Unsigned index = 0;
        Dwarf_Die cu_die;
        int res;
        Dwarf_Error ra_err = 0;

        /*  In case of errors, the correct DIE offset should be
            displayed. At this point we are at the end of the PU */
        Dwarf_Off DIE_overall_offset_bak = glflags.DIE_overall_offset;

        for (index = 0; index < range_array_count; ++index) {
            Dwarf_Ranges *rangeset = 0;
            Dwarf_Signed rangecount = 0;
            Dwarf_Unsigned bytecount = 0;

            /* Get a range info record */
            die_off = range_array[index].die_off;
            original_off = range_array[index].range_off;

            res = dwarf_offdie(dbg,die_off,&cu_die,&ra_err);
            if (res != DW_DLV_OK) {
                print_error(dbg,"dwarf_offdie",res,ra_err);
            }
            res = dwarf_get_ranges_a(dbg,original_off,cu_die,
                &rangeset,&rangecount,&bytecount,&ra_err);
            if (res == DW_DLV_OK) {
                check_ranges_list(dbg,die_off,cu_die,original_off,
                    rangeset,rangecount,bytecount);
            }
            dwarf_dealloc(dbg,cu_die,DW_DLA_DIE);
        };

        reset_range_array_info();

        /*  Point back to the end of the PU */
        glflags.DIE_overall_offset = DIE_overall_offset_bak;
    }
}
