/*      $NetBSD: libdwarf_arange.c,v 1.5 2024/03/03 17:37:32 christos Exp $     */

/*-
* Copyright (c) 2009-2011 Kai Wang
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include "_libdwarf.h"

__RCSID("$NetBSD: libdwarf_arange.c,v 1.5 2024/03/03 17:37:32 christos Exp $");
ELFTC_VCSID("Id: libdwarf_arange.c 3029 2014-04-21 23:26:02Z kaiwang27");

void
_dwarf_arange_cleanup(Dwarf_Debug dbg)
{
       Dwarf_ArangeSet as, tas;
       Dwarf_Arange ar, tar;

       STAILQ_FOREACH_SAFE(as, &dbg->dbg_aslist, as_next, tas) {
               STAILQ_FOREACH_SAFE(ar, &as->as_arlist, ar_next, tar) {
                       STAILQ_REMOVE(&as->as_arlist, ar, _Dwarf_Arange,
                           ar_next);
                       free(ar);
               }
               STAILQ_REMOVE(&dbg->dbg_aslist, as, _Dwarf_ArangeSet, as_next);
               free(as);
       }

       if (dbg->dbg_arange_array)
               free(dbg->dbg_arange_array);

       dbg->dbg_arange_array = NULL;
       dbg->dbg_arange_cnt = 0;
}

int
_dwarf_arange_init(Dwarf_Debug dbg, Dwarf_Error *error)
{
       Dwarf_CU cu;
       Dwarf_ArangeSet as;
       Dwarf_Arange ar;
       Dwarf_Section *ds;
       uint64_t offset, dwarf_size, length, addr, range;
       int i, ret;

       ret = DW_DLE_NONE;

       if ((ds = _dwarf_find_section(dbg, ".debug_aranges")) == NULL)
               return (DW_DLE_NONE);

       if (!dbg->dbg_info_loaded) {
               ret = _dwarf_info_load(dbg, 1, 1, error);
               if (ret != DW_DLE_NONE)
                       return (ret);
       }

       offset = 0;
       while (offset < ds->ds_size) {

               if ((as = malloc(sizeof(struct _Dwarf_ArangeSet))) == NULL) {
                       DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
                       return (DW_DLE_MEMORY);
               }
               STAILQ_INIT(&as->as_arlist);
               STAILQ_INSERT_TAIL(&dbg->dbg_aslist, as, as_next);

               /* Read in the table header. */
               length = dbg->read(ds->ds_data, &offset, 4);
               if (length == 0xffffffff) {
                       dwarf_size = 8;
                       length = dbg->read(ds->ds_data, &offset, 8);
               } else
                       dwarf_size = 4;

               as->as_length = length;
               as->as_version = dbg->read(ds->ds_data, &offset, 2);
               if (as->as_version != 2) {
                       DWARF_SET_ERROR(dbg, error, DW_DLE_VERSION_STAMP_ERROR);
                       ret = DW_DLE_VERSION_STAMP_ERROR;
                       goto fail_cleanup;
               }

               as->as_cu_offset = dbg->read(ds->ds_data, &offset, dwarf_size);
               STAILQ_FOREACH(cu, &dbg->dbg_cu, cu_next) {
                       if (cu->cu_offset == as->as_cu_offset)
                               break;
               }
               if (cu == NULL) {
                       DWARF_SET_ERROR(dbg, error, DW_DLE_ARANGE_OFFSET_BAD);
                       ret = DW_DLE_ARANGE_OFFSET_BAD;
                       goto fail_cleanup;
               }
               as->as_cu = cu;

               as->as_addrsz = dbg->read(ds->ds_data, &offset, 1);
               as->as_segsz = dbg->read(ds->ds_data, &offset, 1);

               /* Skip the padding bytes.  */
               offset = roundup(offset, 2 * as->as_addrsz);

               /* Read in address range descriptors. */
               while (offset < ds->ds_size) {
                       addr = dbg->read(ds->ds_data, &offset, as->as_addrsz);
                       range = dbg->read(ds->ds_data, &offset, as->as_addrsz);
                       if (addr == 0 && range == 0)
                               break;
                       if ((ar = calloc(1, sizeof(struct _Dwarf_Arange))) ==
                           NULL) {
                               DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
                               goto fail_cleanup;
                       }
                       ar->ar_as = as;
                       ar->ar_address = addr;
                       ar->ar_range = range;
                       STAILQ_INSERT_TAIL(&as->as_arlist, ar, ar_next);
                       dbg->dbg_arange_cnt++;
               }
       }

       /* Build arange array. */
       if (dbg->dbg_arange_cnt > 0) {
               if ((dbg->dbg_arange_array = malloc(dbg->dbg_arange_cnt *
                   sizeof(Dwarf_Arange))) == NULL) {
                       DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
                       ret = DW_DLE_MEMORY;
                       goto fail_cleanup;
               }

               i = 0;
               STAILQ_FOREACH(as, &dbg->dbg_aslist, as_next) {
                       STAILQ_FOREACH(ar, &as->as_arlist, ar_next)
                               dbg->dbg_arange_array[i++] = ar;
               }
               assert((Dwarf_Unsigned)i == dbg->dbg_arange_cnt);
       }

       return (DW_DLE_NONE);

fail_cleanup:

       _dwarf_arange_cleanup(dbg);

       return (ret);
}

int
_dwarf_arange_gen(Dwarf_P_Debug dbg, Dwarf_Error *error)
{
       Dwarf_P_Section ds;
       Dwarf_Rel_Section drs;
       Dwarf_ArangeSet as;
       Dwarf_Arange ar;
       uint64_t offset;
       int ret;

       as = dbg->dbgp_as;
       assert(as != NULL);
       if (STAILQ_EMPTY(&as->as_arlist))
               return (DW_DLE_NONE);

       as->as_length = 0;
       as->as_version = 2;
       as->as_cu_offset = 0;   /* We have only one CU. */
       as->as_addrsz = dbg->dbg_pointer_size;
       as->as_segsz = 0;       /* XXX */

       /* Create .debug_arange section. */
       if ((ret = _dwarf_section_init(dbg, &ds, ".debug_aranges", 0, error)) !=
           DW_DLE_NONE)
               goto gen_fail0;

       /* Create relocation section for .debug_aranges */
       RCHECK(_dwarf_reloc_section_init(dbg, &drs, ds, error));

       /* Write section header. */
       RCHECK(WRITE_VALUE(as->as_length, 4));
       RCHECK(WRITE_VALUE(as->as_version, 2));
       RCHECK(_dwarf_reloc_entry_add(dbg, drs, ds, dwarf_drt_data_reloc, 4,
           ds->ds_size, 0, as->as_cu_offset, ".debug_info", error));
       RCHECK(WRITE_VALUE(as->as_addrsz, 1));
       RCHECK(WRITE_VALUE(as->as_segsz, 1));

       /* Pad to (2 * address_size) */
       offset = roundup(ds->ds_size, 2 * as->as_addrsz);
       if (offset > ds->ds_size)
               RCHECK(WRITE_PADDING(0, offset - ds->ds_size));

       /* Write tuples. */
       STAILQ_FOREACH(ar, &as->as_arlist, ar_next) {
               RCHECK(_dwarf_reloc_entry_add(dbg, drs, ds,
                   dwarf_drt_data_reloc, dbg->dbg_pointer_size, ds->ds_size,
                   ar->ar_symndx, ar->ar_address, NULL, error));
               if (ar->ar_esymndx > 0)
                       RCHECK(_dwarf_reloc_entry_add_pair(dbg, drs, ds,
                           dbg->dbg_pointer_size, ds->ds_size, ar->ar_symndx,
                           ar->ar_esymndx, ar->ar_address, ar->ar_eoff, error));
               else
                       RCHECK(WRITE_VALUE(ar->ar_range, dbg->dbg_pointer_size));
       }
       RCHECK(WRITE_VALUE(0, dbg->dbg_pointer_size));
       RCHECK(WRITE_VALUE(0, dbg->dbg_pointer_size));

       /* Fill in the length field. */
       as->as_length = ds->ds_size - 4;
       offset = 0;
       dbg->write(ds->ds_data, &offset, as->as_length, 4);

       /* Inform application the creation of .debug_aranges ELF section. */
       RCHECK(_dwarf_section_callback(dbg, ds, SHT_PROGBITS, 0, 0, 0, error));

       /* Finalize relocation section for .debug_aranges */
       RCHECK(_dwarf_reloc_section_finalize(dbg, drs, error));

       return (DW_DLE_NONE);

gen_fail:
       _dwarf_reloc_section_free(dbg, &drs);

gen_fail0:
       _dwarf_section_free(dbg, &ds);

       return (ret);
}

void
_dwarf_arange_pro_cleanup(Dwarf_P_Debug dbg)
{
       Dwarf_ArangeSet as;
       Dwarf_Arange ar, tar;

       assert(dbg != NULL && dbg->dbg_mode == DW_DLC_WRITE);
       if (dbg->dbgp_as == NULL)
               return;

       as = dbg->dbgp_as;
       STAILQ_FOREACH_SAFE(ar, &as->as_arlist, ar_next, tar) {
               STAILQ_REMOVE(&as->as_arlist, ar, _Dwarf_Arange, ar_next);
               free(ar);
       }
       free(as);
       dbg->dbgp_as = NULL;
}