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

/*-
* Copyright (c) 2007 John Birrell ([email protected])
* Copyright (c) 2014 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_loc.c,v 1.5 2024/03/03 17:37:32 christos Exp $");
ELFTC_VCSID("Id: libdwarf_loc.c 3070 2014-06-23 03:08:33Z kaiwang27");

/*
* Given an array of bytes of length 'len' representing a
* DWARF expression, compute the number of operations based
* on there being one byte describing the operation and
* zero or more bytes of operands as defined in the standard
* for each operation type. Also, if lbuf is non-null, store
* the opcode and oprand in it.
*/
static int
_dwarf_loc_fill_loc(Dwarf_Debug dbg, Dwarf_Locdesc *lbuf, uint8_t pointer_size,
   uint8_t offset_size, uint8_t version, uint8_t *p, int len)
{
       int count;
       uint64_t operand1;
       uint64_t operand2;
       uint8_t *ps, *pe, s;

       count = 0;
       ps = p;
       pe = p + len;

       /*
        * Process each byte. If an error occurs, then the
        * count will be set to -1.
        */
       while (p < pe) {

               operand1 = 0;
               operand2 = 0;

               if (lbuf != NULL) {
                       lbuf->ld_s[count].lr_atom = *p;
                       lbuf->ld_s[count].lr_offset = p - ps;
               }

               switch (*p++) {
               /* Operations with no operands. */
               case DW_OP_deref:
               case DW_OP_reg0:
               case DW_OP_reg1:
               case DW_OP_reg2:
               case DW_OP_reg3:
               case DW_OP_reg4:
               case DW_OP_reg5:
               case DW_OP_reg6:
               case DW_OP_reg7:
               case DW_OP_reg8:
               case DW_OP_reg9:
               case DW_OP_reg10:
               case DW_OP_reg11:
               case DW_OP_reg12:
               case DW_OP_reg13:
               case DW_OP_reg14:
               case DW_OP_reg15:
               case DW_OP_reg16:
               case DW_OP_reg17:
               case DW_OP_reg18:
               case DW_OP_reg19:
               case DW_OP_reg20:
               case DW_OP_reg21:
               case DW_OP_reg22:
               case DW_OP_reg23:
               case DW_OP_reg24:
               case DW_OP_reg25:
               case DW_OP_reg26:
               case DW_OP_reg27:
               case DW_OP_reg28:
               case DW_OP_reg29:
               case DW_OP_reg30:
               case DW_OP_reg31:

               case DW_OP_lit0:
               case DW_OP_lit1:
               case DW_OP_lit2:
               case DW_OP_lit3:
               case DW_OP_lit4:
               case DW_OP_lit5:
               case DW_OP_lit6:
               case DW_OP_lit7:
               case DW_OP_lit8:
               case DW_OP_lit9:
               case DW_OP_lit10:
               case DW_OP_lit11:
               case DW_OP_lit12:
               case DW_OP_lit13:
               case DW_OP_lit14:
               case DW_OP_lit15:
               case DW_OP_lit16:
               case DW_OP_lit17:
               case DW_OP_lit18:
               case DW_OP_lit19:
               case DW_OP_lit20:
               case DW_OP_lit21:
               case DW_OP_lit22:
               case DW_OP_lit23:
               case DW_OP_lit24:
               case DW_OP_lit25:
               case DW_OP_lit26:
               case DW_OP_lit27:
               case DW_OP_lit28:
               case DW_OP_lit29:
               case DW_OP_lit30:
               case DW_OP_lit31:

               case DW_OP_dup:
               case DW_OP_drop:

               case DW_OP_over:

               case DW_OP_swap:
               case DW_OP_rot:
               case DW_OP_xderef:

               case DW_OP_abs:
               case DW_OP_and:
               case DW_OP_div:
               case DW_OP_minus:
               case DW_OP_mod:
               case DW_OP_mul:
               case DW_OP_neg:
               case DW_OP_not:
               case DW_OP_or:
               case DW_OP_plus:

               case DW_OP_shl:
               case DW_OP_shr:
               case DW_OP_shra:
               case DW_OP_xor:

               case DW_OP_eq:
               case DW_OP_ge:
               case DW_OP_gt:
               case DW_OP_le:
               case DW_OP_lt:
               case DW_OP_ne:

               case DW_OP_nop:
               case DW_OP_push_object_address:
               case DW_OP_form_tls_address:
               case DW_OP_call_frame_cfa:
               case DW_OP_stack_value:
               case DW_OP_GNU_push_tls_address:
               case DW_OP_GNU_uninit:
                       break;

               /* Operations with 1-byte operands. */
               case DW_OP_const1u:
               case DW_OP_pick:
               case DW_OP_deref_size:
               case DW_OP_xderef_size:
                       operand1 = *p++;
                       break;

               case DW_OP_const1s:
                       operand1 = (int8_t) *p++;
                       break;

               /* Operations with 2-byte operands. */
               case DW_OP_call2:
               case DW_OP_const2u:
               case DW_OP_bra:
               case DW_OP_skip:
                       operand1 = dbg->decode(&p, 2);
                       break;

               case DW_OP_const2s:
                       operand1 = (int16_t) dbg->decode(&p, 2);
                       break;

               /* Operations with 4-byte operands. */
               case DW_OP_call4:
               case DW_OP_const4u:
               case DW_OP_GNU_parameter_ref:
                       operand1 = dbg->decode(&p, 4);
                       break;

               case DW_OP_const4s:
                       operand1 = (int32_t) dbg->decode(&p, 4);
                       break;

               /* Operations with 8-byte operands. */
               case DW_OP_const8u:
               case DW_OP_const8s:
                       operand1 = dbg->decode(&p, 8);
                       break;

               /* Operations with an unsigned LEB128 operand. */
               case DW_OP_constu:
               case DW_OP_plus_uconst:
               case DW_OP_regx:
               case DW_OP_piece:
               case DW_OP_GNU_deref_type:
               case DW_OP_GNU_convert:
               case DW_OP_GNU_reinterpret:
                       operand1 = _dwarf_decode_uleb128(&p);
                       break;

               /* Operations with a signed LEB128 operand. */
               case DW_OP_consts:
               case DW_OP_breg0:
               case DW_OP_breg1:
               case DW_OP_breg2:
               case DW_OP_breg3:
               case DW_OP_breg4:
               case DW_OP_breg5:
               case DW_OP_breg6:
               case DW_OP_breg7:
               case DW_OP_breg8:
               case DW_OP_breg9:
               case DW_OP_breg10:
               case DW_OP_breg11:
               case DW_OP_breg12:
               case DW_OP_breg13:
               case DW_OP_breg14:
               case DW_OP_breg15:
               case DW_OP_breg16:
               case DW_OP_breg17:
               case DW_OP_breg18:
               case DW_OP_breg19:
               case DW_OP_breg20:
               case DW_OP_breg21:
               case DW_OP_breg22:
               case DW_OP_breg23:
               case DW_OP_breg24:
               case DW_OP_breg25:
               case DW_OP_breg26:
               case DW_OP_breg27:
               case DW_OP_breg28:
               case DW_OP_breg29:
               case DW_OP_breg30:
               case DW_OP_breg31:
               case DW_OP_fbreg:
                       operand1 = _dwarf_decode_sleb128(&p);
                       break;

               /*
                * Oeration with two unsigned LEB128 operands.
                */
               case DW_OP_bit_piece:
               case DW_OP_GNU_regval_type:
                       operand1 = _dwarf_decode_uleb128(&p);
                       operand2 = _dwarf_decode_uleb128(&p);
                       break;

               /*
                * Operations with an unsigned LEB128 operand
                * followed by a signed LEB128 operand.
                */
               case DW_OP_bregx:
                       operand1 = _dwarf_decode_uleb128(&p);
                       operand2 = _dwarf_decode_sleb128(&p);
                       break;

               /*
                * Operation with an unsigned LEB128 operand
                * representing the size of a block, followed
                * by the block content.
                *
                * Store the size of the block in the operand1
                * and a pointer to the block in the operand2.
                */
               case DW_OP_implicit_value:
               case DW_OP_GNU_entry_value:
                       operand1 = _dwarf_decode_uleb128(&p);
                       operand2 = (Dwarf_Unsigned) (uintptr_t) p;
                       p += operand1;
                       break;

               /* Target address size operand. */
               case DW_OP_addr:
               case DW_OP_GNU_addr_index:
               case DW_OP_GNU_const_index:
                       operand1 = dbg->decode(&p, pointer_size);
                       break;

               /* Offset size operand. */
               case DW_OP_call_ref:
                       operand1 = dbg->decode(&p, offset_size);
                       break;

               /*
                * The first byte is address byte length, followed by
                * the address value. If the length is 0, the address
                * size is the same as target pointer size.
                */
               case DW_OP_GNU_encoded_addr:
                       s = *p++;
                       if (s == 0)
                               s = pointer_size;
                       operand1 = dbg->decode(&p, s);
                       break;

               /*
                * Operand1: DIE offset (size depending on DWARF version)
                * DWARF2: pointer size
                * DWARF{3,4}: offset size
                *
                * Operand2: SLEB128
                */
               case DW_OP_GNU_implicit_pointer:
                       if (version == 2)
                               operand1 = dbg->decode(&p, pointer_size);
                       else
                               operand1 = dbg->decode(&p, offset_size);
                       operand2 = _dwarf_decode_sleb128(&p);
                       break;

               /*
                * Operand1: DIE offset (ULEB128)
                * Operand2: pointer to a block. The block's first byte
                * is its size.
                */
               case DW_OP_GNU_const_type:
                       operand1 = _dwarf_decode_uleb128(&p);
                       operand2 = (Dwarf_Unsigned) (uintptr_t) p;
                       s = *p++;
                       p += s;
                       break;

               /* All other operations cause an error. */
               default:
                       count = -1;
                       goto done;
               }

               if (lbuf != NULL) {
                       lbuf->ld_s[count].lr_number = operand1;
                       lbuf->ld_s[count].lr_number2 = operand2;
               }

               count++;
       }

done:
       return (count);
}

int
_dwarf_loc_expr_add_atom(Dwarf_Debug dbg, uint8_t *out, uint8_t *end,
   Dwarf_Small atom, Dwarf_Unsigned operand1, Dwarf_Unsigned operand2,
   int *length, Dwarf_Error *error)
{
       uint8_t buf[64];
       uint8_t *p, *pe;
       uint64_t offset;
       int len;

       if (out != NULL && end != NULL) {
               p = out;
               pe = end;
       } else {
               p = out = buf;
               pe = &buf[sizeof(buf)];
       }

       switch (atom) {
       /* Operations with no operands. */
       case DW_OP_deref:
       case DW_OP_reg0:
       case DW_OP_reg1:
       case DW_OP_reg2:
       case DW_OP_reg3:
       case DW_OP_reg4:
       case DW_OP_reg5:
       case DW_OP_reg6:
       case DW_OP_reg7:
       case DW_OP_reg8:
       case DW_OP_reg9:
       case DW_OP_reg10:
       case DW_OP_reg11:
       case DW_OP_reg12:
       case DW_OP_reg13:
       case DW_OP_reg14:
       case DW_OP_reg15:
       case DW_OP_reg16:
       case DW_OP_reg17:
       case DW_OP_reg18:
       case DW_OP_reg19:
       case DW_OP_reg20:
       case DW_OP_reg21:
       case DW_OP_reg22:
       case DW_OP_reg23:
       case DW_OP_reg24:
       case DW_OP_reg25:
       case DW_OP_reg26:
       case DW_OP_reg27:
       case DW_OP_reg28:
       case DW_OP_reg29:
       case DW_OP_reg30:
       case DW_OP_reg31:

       case DW_OP_lit0:
       case DW_OP_lit1:
       case DW_OP_lit2:
       case DW_OP_lit3:
       case DW_OP_lit4:
       case DW_OP_lit5:
       case DW_OP_lit6:
       case DW_OP_lit7:
       case DW_OP_lit8:
       case DW_OP_lit9:
       case DW_OP_lit10:
       case DW_OP_lit11:
       case DW_OP_lit12:
       case DW_OP_lit13:
       case DW_OP_lit14:
       case DW_OP_lit15:
       case DW_OP_lit16:
       case DW_OP_lit17:
       case DW_OP_lit18:
       case DW_OP_lit19:
       case DW_OP_lit20:
       case DW_OP_lit21:
       case DW_OP_lit22:
       case DW_OP_lit23:
       case DW_OP_lit24:
       case DW_OP_lit25:
       case DW_OP_lit26:
       case DW_OP_lit27:
       case DW_OP_lit28:
       case DW_OP_lit29:
       case DW_OP_lit30:
       case DW_OP_lit31:

       case DW_OP_dup:
       case DW_OP_drop:

       case DW_OP_over:

       case DW_OP_swap:
       case DW_OP_rot:
       case DW_OP_xderef:

       case DW_OP_abs:
       case DW_OP_and:
       case DW_OP_div:
       case DW_OP_minus:
       case DW_OP_mod:
       case DW_OP_mul:
       case DW_OP_neg:
       case DW_OP_not:
       case DW_OP_or:
       case DW_OP_plus:

       case DW_OP_shl:
       case DW_OP_shr:
       case DW_OP_shra:
       case DW_OP_xor:

       case DW_OP_eq:
       case DW_OP_ge:
       case DW_OP_gt:
       case DW_OP_le:
       case DW_OP_lt:
       case DW_OP_ne:

       case DW_OP_nop:
       case DW_OP_GNU_push_tls_address:
               *p++ = atom;
               break;

       /* Operations with 1-byte operands. */
       case DW_OP_const1u:
       case DW_OP_const1s:
       case DW_OP_pick:
       case DW_OP_deref_size:
       case DW_OP_xderef_size:
               *p++ = atom;
               *p++ = (uint8_t) operand1;
               break;

       /* Operations with 2-byte operands. */
       case DW_OP_const2u:
       case DW_OP_const2s:
       case DW_OP_bra:
       case DW_OP_skip:
               *p++ = atom;
               offset = 0;
               dbg->write(p, &offset, operand1, 2);
               p += 2;
               break;

       /* Operations with 4-byte operands. */
       case DW_OP_const4u:
       case DW_OP_const4s:
               *p++ = atom;
               offset = 0;
               dbg->write(p, &offset, operand1, 4);
               p += 4;
               break;

       /* Operations with 8-byte operands. */
       case DW_OP_const8u:
       case DW_OP_const8s:
               *p++ = atom;
               offset = 0;
               dbg->write(p, &offset, operand1, 8);
               p += 8;
               break;

       /* Operations with an unsigned LEB128 operand. */
       case DW_OP_constu:
       case DW_OP_plus_uconst:
       case DW_OP_regx:
       case DW_OP_piece:
               *p++ = atom;
               len = _dwarf_write_uleb128(p, pe, operand1);
               assert(len > 0);
               p += len;
               break;

       /* Operations with a signed LEB128 operand. */
       case DW_OP_consts:
       case DW_OP_breg0:
       case DW_OP_breg1:
       case DW_OP_breg2:
       case DW_OP_breg3:
       case DW_OP_breg4:
       case DW_OP_breg5:
       case DW_OP_breg6:
       case DW_OP_breg7:
       case DW_OP_breg8:
       case DW_OP_breg9:
       case DW_OP_breg10:
       case DW_OP_breg11:
       case DW_OP_breg12:
       case DW_OP_breg13:
       case DW_OP_breg14:
       case DW_OP_breg15:
       case DW_OP_breg16:
       case DW_OP_breg17:
       case DW_OP_breg18:
       case DW_OP_breg19:
       case DW_OP_breg20:
       case DW_OP_breg21:
       case DW_OP_breg22:
       case DW_OP_breg23:
       case DW_OP_breg24:
       case DW_OP_breg25:
       case DW_OP_breg26:
       case DW_OP_breg27:
       case DW_OP_breg28:
       case DW_OP_breg29:
       case DW_OP_breg30:
       case DW_OP_breg31:
       case DW_OP_fbreg:
               *p++ = atom;
               len = _dwarf_write_sleb128(p, pe, operand1);
               assert(len > 0);
               p += len;
               break;

       /*
        * Operations with an unsigned LEB128 operand
        * followed by a signed LEB128 operand.
        */
       case DW_OP_bregx:
               *p++ = atom;
               len = _dwarf_write_uleb128(p, pe, operand1);
               assert(len > 0);
               p += len;
               len = _dwarf_write_sleb128(p, pe, operand2);
               assert(len > 0);
               p += len;
               break;

       /* Target address size operand. */
       case DW_OP_addr:
               *p++ = atom;
               offset = 0;
               dbg->write(p, &offset, operand1, dbg->dbg_pointer_size);
               p += dbg->dbg_pointer_size;
               break;

       /* All other operations cause an error. */
       default:
               DWARF_SET_ERROR(dbg, error, DW_DLE_LOC_EXPR_BAD);
               return (DW_DLE_LOC_EXPR_BAD);
       }

       if (length)
               *length = p - out;

       return (DW_DLE_NONE);
}

int
_dwarf_loc_fill_locdesc(Dwarf_Debug dbg, Dwarf_Locdesc *llbuf, uint8_t *in,
   uint64_t in_len, uint8_t pointer_size, uint8_t offset_size,
   uint8_t version, Dwarf_Error *error)
{
       int num;

       assert(llbuf != NULL);
       assert(in != NULL);
       assert(in_len > 0);

       /* Compute the number of locations. */
       if ((num = _dwarf_loc_fill_loc(dbg, NULL, pointer_size, offset_size,
           version, in, in_len)) < 0) {
               DWARF_SET_ERROR(dbg, error, DW_DLE_LOC_EXPR_BAD);
               return (DW_DLE_LOC_EXPR_BAD);
       }

       llbuf->ld_cents = num;
       if (num <= 0)
               return (DW_DLE_NONE);

       if ((llbuf->ld_s = calloc(num, sizeof(Dwarf_Loc))) == NULL) {
               DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
               return (DW_DLE_MEMORY);
       }

       (void) _dwarf_loc_fill_loc(dbg, llbuf, pointer_size, offset_size,
           version, in, in_len);

       return (DW_DLE_NONE);
}

int
_dwarf_loc_fill_locexpr(Dwarf_Debug dbg, Dwarf_Locdesc **ret_llbuf, uint8_t *in,
   uint64_t in_len, uint8_t pointer_size, uint8_t offset_size,
   uint8_t version, Dwarf_Error *error)
{
       Dwarf_Locdesc *llbuf;
       int ret;

       if ((llbuf = malloc(sizeof(Dwarf_Locdesc))) == NULL) {
               DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
               return (DW_DLE_MEMORY);
       }
       llbuf->ld_lopc = 0;
       llbuf->ld_hipc = ~0ULL;
       llbuf->ld_s = NULL;

       ret = _dwarf_loc_fill_locdesc(dbg, llbuf, in, in_len, pointer_size,
           offset_size, version, error);
       if (ret != DW_DLE_NONE) {
               free(llbuf);
               return (ret);
       }

       *ret_llbuf = llbuf;

       return (ret);
}

int
_dwarf_loc_add(Dwarf_Die die, Dwarf_Attribute at, Dwarf_Error *error)
{
       Dwarf_Debug dbg;
       Dwarf_CU cu;
       int ret;

       assert(at->at_ld == NULL);
       assert(at->u[1].u8p != NULL);
       assert(at->u[0].u64 > 0);

       cu = die->die_cu;
       assert(cu != NULL);

       dbg = cu->cu_dbg;
       assert(dbg != NULL);

       ret = _dwarf_loc_fill_locexpr(dbg, &at->at_ld, at->u[1].u8p,
           at->u[0].u64, cu->cu_pointer_size, cu->cu_length_size == 4 ? 4 : 8,
           cu->cu_version, error);

       return (ret);
}