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

/*-
* Copyright (c) 2009,2010,2023 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_lineno.c,v 1.5 2024/03/03 17:37:32 christos Exp $");
ELFTC_VCSID("Id: libdwarf_lineno.c 4019 2023-10-22 03:06:17Z kaiwang27");

static int
_dwarf_lineno_make_fullpath(Dwarf_Debug dbg, Dwarf_LineInfo li,
   Dwarf_LineFile lf, const char *compdir, Dwarf_Error *error)
{
       const char *dirname;
       int slen;

       /* Make full pathname if need. */
       if (*lf->lf_fname != '/') {
               dirname = compdir;
               if (lf->lf_dirndx > 0)
                       dirname = li->li_incdirs[lf->lf_dirndx - 1];
               if (dirname != NULL) {
                       slen = strlen(dirname) + strlen(lf->lf_fname) + 2;
                       if ((lf->lf_fullpath = malloc(slen)) == NULL) {
                               DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
                               return (DW_DLE_MEMORY);
                       }
                       snprintf(lf->lf_fullpath, slen, "%s/%s", dirname,
                           lf->lf_fname);
               }
       }

       return (DW_DLE_NONE);
}

static int
_dwarf_lineno_add_file(Dwarf_LineInfo li, uint8_t **p, const char *compdir,
   Dwarf_Error *error, Dwarf_Debug dbg)
{
       Dwarf_LineFile lf;
       uint8_t *src;
       int ret;

       src = *p;

       if ((lf = malloc(sizeof(struct _Dwarf_LineFile))) == NULL) {
               DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
               return (DW_DLE_MEMORY);
       }

       lf->lf_fullpath = NULL;
       lf->lf_fname = (char *) src;
       src += strlen(lf->lf_fname) + 1;
       lf->lf_dirndx = _dwarf_decode_uleb128(&src);
       if (lf->lf_dirndx > li->li_inclen) {
               free(lf);
               DWARF_SET_ERROR(dbg, error, DW_DLE_DIR_INDEX_BAD);
               return (DW_DLE_DIR_INDEX_BAD);
       }

       ret = _dwarf_lineno_make_fullpath(dbg, li, lf, compdir, error);
       if (ret != DW_DLE_NONE) {
               free(lf);
               return (ret);
       }

       lf->lf_mtime = _dwarf_decode_uleb128(&src);
       lf->lf_size = _dwarf_decode_uleb128(&src);
       STAILQ_INSERT_TAIL(&li->li_lflist, lf, lf_next);
       li->li_lflen++;

       *p = src;

       return (DW_DLE_NONE);
}

static int
_dwarf_lineno_run_program(Dwarf_CU cu, Dwarf_LineInfo li, uint8_t *p,
   uint8_t *pe, const char *compdir, Dwarf_Error *error)
{
       Dwarf_Debug dbg;
       Dwarf_Line ln, tln;
       uint64_t address, file, line, column, opsize;
       int is_stmt, basic_block, end_sequence;
       int ret;

#define RESET_REGISTERS                                         \
       do {                                                    \
               address        = 0;                             \
               file           = 1;                             \
               line           = 1;                             \
               column         = 0;                             \
               is_stmt        = li->li_defstmt;                \
               basic_block    = 0;                             \
               end_sequence   = 0;                             \
       } while(0)

#define APPEND_ROW                                              \
       do {                                                    \
               ln = malloc(sizeof(struct _Dwarf_Line));        \
               if (ln == NULL) {                               \
                       ret = DW_DLE_MEMORY;                    \
                       DWARF_SET_ERROR(dbg, error, ret);       \
                       goto prog_fail;                         \
               }                                               \
               ln->ln_li     = li;                             \
               ln->ln_addr   = address;                        \
               ln->ln_symndx = 0;                              \
               ln->ln_fileno = file;                           \
               ln->ln_lineno = line;                           \
               ln->ln_column = column;                         \
               ln->ln_bblock = basic_block;                    \
               ln->ln_stmt   = is_stmt;                        \
               ln->ln_endseq = end_sequence;                   \
               STAILQ_INSERT_TAIL(&li->li_lnlist, ln, ln_next);\
               li->li_lnlen++;                                 \
       } while(0)

#define LINE(x) (li->li_lbase + (((x) - li->li_opbase) % li->li_lrange))
#define ADDRESS(x) ((((x) - li->li_opbase) / li->li_lrange) * li->li_minlen)

       dbg = cu->cu_dbg;

       /*
        * Set registers to their default values.
        */
       RESET_REGISTERS;

       /*
        * Start line number program.
        */
       while (p < pe) {
               if (*p == 0) {

                       /*
                        * Extended Opcodes.
                        */

                       p++;
                       opsize = _dwarf_decode_uleb128(&p);
                       switch (*p) {
                       case DW_LNE_end_sequence:
                               p++;
                               end_sequence = 1;
                               APPEND_ROW;
                               RESET_REGISTERS;
                               break;
                       case DW_LNE_set_address:
                               p++;
                               address = dbg->decode(&p, cu->cu_pointer_size);
                               break;
                       case DW_LNE_define_file:
                               p++;
                               ret = _dwarf_lineno_add_file(li, &p, compdir,
                                   error, dbg);
                               if (ret != DW_DLE_NONE)
                                       goto prog_fail;
                               break;
                       default:
                               /* Unrecognized extened opcodes. */
                               p += opsize;
                       }

               } else if (*p > 0 && *p < li->li_opbase) {

                       /*
                        * Standard Opcodes.
                        */

                       switch (*p++) {
                       case DW_LNS_copy:
                               APPEND_ROW;
                               basic_block = 0;
                               break;
                       case DW_LNS_advance_pc:
                               address += _dwarf_decode_uleb128(&p) *
                                   li->li_minlen;
                               break;
                       case DW_LNS_advance_line:
                               line += _dwarf_decode_sleb128(&p);
                               break;
                       case DW_LNS_set_file:
                               file = _dwarf_decode_uleb128(&p);
                               break;
                       case DW_LNS_set_column:
                               column = _dwarf_decode_uleb128(&p);
                               break;
                       case DW_LNS_negate_stmt:
                               is_stmt = !is_stmt;
                               break;
                       case DW_LNS_set_basic_block:
                               basic_block = 1;
                               break;
                       case DW_LNS_const_add_pc:
                               address += ADDRESS(255);
                               break;
                       case DW_LNS_fixed_advance_pc:
                               address += dbg->decode(&p, 2);
                               break;
                       case DW_LNS_set_prologue_end:
                               break;
                       case DW_LNS_set_epilogue_begin:
                               break;
                       case DW_LNS_set_isa:
                               (void) _dwarf_decode_uleb128(&p);
                               break;
                       default:
                               /* Unrecognized extened opcodes. What to do? */
                               break;
                       }

               } else {

                       /*
                        * Special Opcodes.
                        */

                       line += LINE(*p);
                       address += ADDRESS(*p);
                       APPEND_ROW;
                       basic_block = 0;
                       p++;
               }
       }

       return (DW_DLE_NONE);

prog_fail:

       STAILQ_FOREACH_SAFE(ln, &li->li_lnlist, ln_next, tln) {
               STAILQ_REMOVE(&li->li_lnlist, ln, _Dwarf_Line, ln_next);
               free(ln);
       }

       return (ret);

#undef  RESET_REGISTERS
#undef  APPEND_ROW
#undef  LINE
#undef  ADDRESS
}

struct lnct {
       unsigned type;
       unsigned form;
};


static int
_dwarf_lineno_parse_lnct_desc(Dwarf_Debug dbg, int fmt, struct lnct **lnct,
   uint8_t *data, uint64_t *offsetp, Dwarf_Error *error)
{
       int i;

       if (fmt == 0) {
               *lnct = NULL;
               return (DW_DLE_NONE);
       }

       if ((*lnct = calloc(fmt, sizeof(struct lnct))) == NULL) {
               DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
               return (DW_DLE_MEMORY);
       }
       for (i = 0; i < fmt; i++) {
               (*lnct)[i].type = _dwarf_read_uleb128(data, offsetp);
               (*lnct)[i].form = _dwarf_read_uleb128(data, offsetp);
       }

       return (DW_DLE_NONE);
}

static int
_dwarf_lineno_lnct_path(Dwarf_Debug dbg, char **fname, unsigned form,
   uint8_t *data, uint64_t size, uint64_t *offsetp, int dwarf_size,
   Dwarf_Error *error)
{
       Dwarf_Unsigned sp;
       int ret;

       ret = DW_DLE_NONE;

       switch (form) {
       case DW_FORM_string:
               *fname = _dwarf_read_string(data, size, offsetp);
               break;
       case DW_FORM_strp:
               sp = dbg->read(data, offsetp, dwarf_size);
               *fname = _dwarf_strtab_get_table(dbg) + sp;
               break;
       case DW_FORM_line_strp:
               sp = dbg->read(data, offsetp, dwarf_size);
               *fname = _dwarf_strtab_get_line_table(dbg) + sp;
               break;
       case DW_FORM_strp_sup:
               sp = dbg->read(data, offsetp, dwarf_size);
               *fname = NULL;  /* TODO: support sup string table. */
               break;
       default:
               ret = DW_DLE_LNCT_DESC_BAD;
               DWARF_SET_ERROR(dbg, error, ret);
               break;
       }

       return (ret);
}

static int
_dwarf_lineno_lnct_dirndx(Dwarf_Debug dbg, Dwarf_Unsigned *dirndx,
   unsigned form, uint8_t *data, uint64_t *offsetp, Dwarf_Error *error)
{
       int ret;

       ret = DW_DLE_NONE;

       switch (form) {
       case DW_FORM_data1:
               *dirndx = dbg->read(data, offsetp, 1);
               break;
       case DW_FORM_data2:
               *dirndx = dbg->read(data, offsetp, 2);
               break;
       case DW_FORM_udata:
               *dirndx = _dwarf_read_uleb128(data, offsetp);
               break;
       default:
               ret = DW_DLE_LNCT_DESC_BAD;
               DWARF_SET_ERROR(dbg, error, ret);
               break;
       }

       return (ret);
}

static int
_dwarf_lineno_lnct_timestamp(Dwarf_Debug dbg, Dwarf_Unsigned *ts,
   unsigned form, uint8_t *data, uint64_t *offsetp, Dwarf_Error *error)
{
       int ret;

       ret = DW_DLE_NONE;

       switch (form) {
       case DW_FORM_udata:
               *ts = _dwarf_read_uleb128(data, offsetp);
               break;
       case DW_FORM_data4:
               *ts = dbg->read(data, offsetp, 4);
               break;
       case DW_FORM_data8:
               *ts = dbg->read(data, offsetp, 8);
               break;
       case DW_FORM_block:
               /* TODO: Not supported. */
       default:
               ret = DW_DLE_LNCT_DESC_BAD;
               DWARF_SET_ERROR(dbg, error, ret);
               break;
       }

       return (ret);
}

static int
_dwarf_lineno_lnct_size(Dwarf_Debug dbg, Dwarf_Unsigned *sz, unsigned form,
   uint8_t *data, uint64_t *offsetp, Dwarf_Error *error)
{
       int ret;

       ret = DW_DLE_NONE;

       switch (form) {
       case DW_FORM_udata:
               *sz = _dwarf_read_uleb128(data, offsetp);
               break;
       case DW_FORM_data1:
               *sz = dbg->read(data, offsetp, 1);
               break;
       case DW_FORM_data2:
               *sz = dbg->read(data, offsetp, 2);
               break;
       case DW_FORM_data4:
               *sz = dbg->read(data, offsetp, 4);
               break;
       case DW_FORM_data8:
               *sz = dbg->read(data, offsetp, 8);
               break;
       default:
               ret = DW_DLE_LNCT_DESC_BAD;
               DWARF_SET_ERROR(dbg, error, ret);
               break;
       }

       return (ret);
}

static int
_dwarf_lineno_lnct_md5(Dwarf_Debug dbg, Dwarf_Form_Data16 *md5,
   unsigned form, uint8_t *data, uint64_t *offsetp, Dwarf_Error *error)
{

       if (form != DW_FORM_data16) {
               DWARF_SET_ERROR(dbg, error, DW_DLE_LNCT_DESC_BAD);
               return (DW_DLE_LNCT_DESC_BAD);
       }

       memcpy(md5->fd_data, data + *offsetp, 16);
       offsetp += 16;

       return (DW_DLE_NONE);
}

int
_dwarf_lineno_init(Dwarf_Die die, uint64_t offset, Dwarf_Error *error)
{
       Dwarf_Debug dbg;
       Dwarf_Section *ds;
       Dwarf_CU cu;
       Dwarf_Attribute at;
       Dwarf_LineInfo li;
       Dwarf_LineFile lf, tlf;
       struct lnct *lnct;
       const char *compdir;
       uint64_t length, hdroff, endoff;
       uint8_t *p;
       int dwarf_size, fmt, i, j, ret;

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

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

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

       /*
        * Try to find out the dir where the CU was compiled. Later we
        * will use the dir to create full pathnames, if need.
        */
       compdir = NULL;
       at = _dwarf_attr_find(die, DW_AT_comp_dir);
       if (at != NULL) {
               switch (at->at_form) {
               case DW_FORM_strp:
               case DW_FORM_strp_sup:
               case DW_FORM_line_strp:
                       compdir = at->u[1].s;
                       break;
               case DW_FORM_string:
                       compdir = at->u[0].s;
                       break;
               default:
                       break;
               }
       }

       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;

       if (length > ds->ds_size - offset) {
               DWARF_SET_ERROR(dbg, error, DW_DLE_DEBUG_LINE_LENGTH_BAD);
               return (DW_DLE_DEBUG_LINE_LENGTH_BAD);
       }

       if ((li = calloc(1, sizeof(struct _Dwarf_LineInfo))) == NULL) {
               DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY);
               return (DW_DLE_MEMORY);
       }

       /*
        * Read in line number program header.
        */
       li->li_length = length;
       endoff = offset + length;
       li->li_version = dbg->read(ds->ds_data, &offset, 2);
       if (li->li_version == 5) {
               (void) dbg->read(ds->ds_data, &offset, 1); /* TODO */
               (void) dbg->read(ds->ds_data, &offset, 1); /* TODO */
       }
       li->li_hdrlen = dbg->read(ds->ds_data, &offset, dwarf_size);
       hdroff = offset;
       li->li_minlen = dbg->read(ds->ds_data, &offset, 1);
       if (li->li_version >= 4)
               li->li_maxop = dbg->read(ds->ds_data, &offset, 1);
       li->li_defstmt = dbg->read(ds->ds_data, &offset, 1);
       li->li_lbase = dbg->read(ds->ds_data, &offset, 1);
       li->li_lrange = dbg->read(ds->ds_data, &offset, 1);
       li->li_opbase = dbg->read(ds->ds_data, &offset, 1);
       STAILQ_INIT(&li->li_lflist);
       STAILQ_INIT(&li->li_lnlist);

       if ((int)li->li_hdrlen - 5 < li->li_opbase - 1) {
               ret = DW_DLE_DEBUG_LINE_LENGTH_BAD;
               DWARF_SET_ERROR(dbg, error, ret);
               goto fail_cleanup;
       }

       if ((li->li_oplen = malloc(li->li_opbase)) == NULL) {
               ret = DW_DLE_MEMORY;
               DWARF_SET_ERROR(dbg, error, ret);
               goto fail_cleanup;
       }

       /*
        * Read in std opcode arg length list. Note that the first
        * element is not used.
        */
       for (i = 1; i < li->li_opbase; i++)
               li->li_oplen[i] = dbg->read(ds->ds_data, &offset, 1);

       /*
        * Directory and filename parser for DWARF4 and below.
        */
       if (li->li_version <= 4) {

               /*
                * Check how many strings in the include dir string array.
                */
               length = 0;
               p = ds->ds_data + offset;
               while (*p != '\0') {
                       while (*p++ != '\0')
                               ;
                       length++;
               }
               li->li_inclen = length;

               /* Sanity check. */
               if (p - ds->ds_data > (int) ds->ds_size) {
                       ret = DW_DLE_DEBUG_LINE_LENGTH_BAD;
                       DWARF_SET_ERROR(dbg, error, ret);
                       goto fail_cleanup;
               }

               if (length != 0) {
                       if ((li->li_incdirs = malloc(length * sizeof(char *))) ==
                           NULL) {
                               ret = DW_DLE_MEMORY;
                               DWARF_SET_ERROR(dbg, error, ret);
                               goto fail_cleanup;
                       }
               }

               /* Fill in include dir array. */
               i = 0;
               p = ds->ds_data + offset;
               while (*p != '\0') {
                       li->li_incdirs[i++] = (char *) p;
                       while (*p++ != '\0')
                               ;
               }

               p++;

               /*
                * Process file list.
                */
               while (*p != '\0') {
                       ret = _dwarf_lineno_add_file(li, &p, compdir, error, dbg);
                       if (ret != DW_DLE_NONE)
                               goto fail_cleanup;
                       if (p - ds->ds_data > (int) ds->ds_size) {
                               ret = DW_DLE_DEBUG_LINE_LENGTH_BAD;
                               DWARF_SET_ERROR(dbg, error, ret);
                               goto fail_cleanup;
                       }
               }

               p++;

               /* Sanity check. */
               if (p - ds->ds_data - hdroff != li->li_hdrlen) {
                       ret = DW_DLE_DEBUG_LINE_LENGTH_BAD;
                       DWARF_SET_ERROR(dbg, error, ret);
                       goto fail_cleanup;
               }

               goto lnprog;
       }

       /*
        * DWARF5 has completely overhauled the dir/source file information
        * fields, which are incompatible with DWARF4 or lower.
        */

       lnct = NULL;
       fmt = dbg->read(ds->ds_data, &offset, 1);
       if ((ret = _dwarf_lineno_parse_lnct_desc(dbg, fmt, &lnct, ds->ds_data,
           &offset, error)) != DW_DLE_NONE)
               goto fail_cleanup;

       li->li_inclen = dbg->read(ds->ds_data, &offset, 1);
       if (li->li_inclen == 0) {
               if (fmt > 0) {
                       free(lnct);
                       ret = DW_DLE_DIR_COUNT_BAD;
                       DWARF_SET_ERROR(dbg, error, ret);
                       goto fail_cleanup;
               }

               goto file_entries;
       }

       if (fmt == 0) {
               ret = DW_DLE_DIR_COUNT_BAD;
               DWARF_SET_ERROR(dbg, error, ret);
               goto fail_cleanup;
       }
       if ((li->li_incdirs = malloc(length * sizeof(char *))) == NULL) {
               free(lnct);
               ret = DW_DLE_MEMORY;
               DWARF_SET_ERROR(dbg, error, ret);
               goto fail_cleanup;
       }
       for (i = 0; i < li->li_inclen; i++) {
               for (j = 0; j < fmt; j++) {
                       if (lnct[j].type != DW_LNCT_path) {
                               free(lnct);
                               ret = DW_DLE_LNCT_DESC_BAD;
                               DWARF_SET_ERROR(dbg, error, ret);
                               goto fail_cleanup;
                       }

                       ret = _dwarf_lineno_lnct_path(dbg, &li->li_incdirs[i],
                           lnct[j].form, ds->ds_data, ds->ds_size, &offset,
                           dwarf_size, error);
                       if (ret != DW_DLE_NONE) {
                               free(lnct);
                               goto fail_cleanup;
                       }
               }
       }
       if (lnct)
               free(lnct);

file_entries:

       lnct = NULL;
       fmt = dbg->read(ds->ds_data, &offset, 1);
       if ((ret = _dwarf_lineno_parse_lnct_desc(dbg, fmt, &lnct, ds->ds_data,
           &offset, error)) != DW_DLE_NONE)
               goto fail_cleanup;

       li->li_lflen = dbg->read(ds->ds_data, &offset, 1);
       if (li->li_lflen == 0) {
               if (fmt > 0) {
                       free(lnct);
                       ret = DW_DLE_FILE_COUNT_BAD;
                       DWARF_SET_ERROR(dbg, error, ret);
                       goto fail_cleanup;
               }

               p = ds->ds_data + offset;
               goto lnprog;
       }

       for (i = 0; i < li->li_lflen; i++) {
               if ((lf = malloc(sizeof(struct _Dwarf_LineFile))) == NULL) {
                       free(lnct);
                       ret = DW_DLE_MEMORY;
                       DWARF_SET_ERROR(dbg, error, ret);
                       goto fail_cleanup;
               }
               for (j = 0; j < fmt; j++) {
                       switch (lnct[j].type) {
                       case DW_LNCT_path:
                               ret = _dwarf_lineno_lnct_path(dbg,
                                   &lf->lf_fname, lnct[j].form, ds->ds_data,
                                   ds->ds_size, &offset, dwarf_size, error);
                               break;
                       case DW_LNCT_directory_index:
                               ret = _dwarf_lineno_lnct_dirndx(dbg,
                                   &lf->lf_dirndx, lnct[j].form, ds->ds_data,
                                   &offset, error);
                               break;
                       case DW_LNCT_timestamp:
                               ret = _dwarf_lineno_lnct_timestamp(dbg,
                                   &lf->lf_mtime, lnct[j].form, ds->ds_data,
                                   &offset, error);
                               break;
                       case DW_LNCT_size:
                               ret = _dwarf_lineno_lnct_size(dbg,
                                   &lf->lf_size, lnct[j].form, ds->ds_data,
                                   &offset, error);
                               break;
                       case DW_LNCT_MD5:
                               ret = _dwarf_lineno_lnct_md5(dbg,
                                   &lf->lf_md5, lnct[j].form, ds->ds_data,
                                   &offset, error);
                               break;
                       default:
                               ret = DW_DLE_LNCT_DESC_BAD;
                               DWARF_SET_ERROR(dbg, error, ret);
                               break;
                       }
                       if (ret != DW_DLE_NONE) {
                               free(lf);
                               free(lnct);
                               goto fail_cleanup;
                       }
               }
               ret = _dwarf_lineno_make_fullpath(dbg, li, lf, compdir, error);
               if (ret != DW_DLE_NONE) {
                       free(lf);
                       free(lnct);
                       goto fail_cleanup;
               }
               STAILQ_INSERT_TAIL(&li->li_lflist, lf, lf_next);
       }
       if (lnct)
               free(lnct);

       p = ds->ds_data + offset;

lnprog:
       /*
        * Process line number program.
        */
       ret = _dwarf_lineno_run_program(cu, li, p, ds->ds_data + endoff, compdir,
           error);
       if (ret != DW_DLE_NONE)
               goto fail_cleanup;

       cu->cu_lineinfo = li;

       return (DW_DLE_NONE);

fail_cleanup:

       STAILQ_FOREACH_SAFE(lf, &li->li_lflist, lf_next, tlf) {
               STAILQ_REMOVE(&li->li_lflist, lf, _Dwarf_LineFile, lf_next);
               if (lf->lf_fullpath)
                       free(lf->lf_fullpath);
               free(lf);
       }

       if (li->li_oplen)
               free(li->li_oplen);
       if (li->li_incdirs)
               free(li->li_incdirs);
       free(li);

       return (ret);
}

void
_dwarf_lineno_cleanup(Dwarf_LineInfo li)
{
       Dwarf_LineFile lf, tlf;
       Dwarf_Line ln, tln;

       if (li == NULL)
               return;
       STAILQ_FOREACH_SAFE(lf, &li->li_lflist, lf_next, tlf) {
               STAILQ_REMOVE(&li->li_lflist, lf,
                   _Dwarf_LineFile, lf_next);
               if (lf->lf_fullpath)
                       free(lf->lf_fullpath);
               free(lf);
       }
       STAILQ_FOREACH_SAFE(ln, &li->li_lnlist, ln_next, tln) {
               STAILQ_REMOVE(&li->li_lnlist, ln, _Dwarf_Line,
                   ln_next);
               free(ln);
       }
       if (li->li_oplen)
               free(li->li_oplen);
       if (li->li_incdirs)
               free(li->li_incdirs);
       if (li->li_lnarray)
               free(li->li_lnarray);
       if (li->li_lfnarray)
               free(li->li_lfnarray);
       free(li);
}

static int
_dwarf_lineno_gen_program(Dwarf_P_Debug dbg, Dwarf_P_Section ds,
   Dwarf_Rel_Section drs, Dwarf_Error * error)
{
       Dwarf_LineInfo li;
       Dwarf_Line ln;
       Dwarf_Unsigned address, file, line, spc;
       Dwarf_Unsigned addr0, maddr;
       Dwarf_Signed line0, column;
       int is_stmt, basic_block;
       int need_copy;
       int ret;

#define RESET_REGISTERS                                         \
       do {                                                    \
               address        = 0;                             \
               file           = 1;                             \
               line           = 1;                             \
               column         = 0;                             \
               is_stmt        = li->li_defstmt;                \
               basic_block    = 0;                             \
       } while(0)

       li = dbg->dbgp_lineinfo;
       maddr = (255 - li->li_opbase) / li->li_lrange;

       RESET_REGISTERS;

       STAILQ_FOREACH(ln, &li->li_lnlist, ln_next) {
               if (ln->ln_symndx > 0) {
                       /*
                        * Generate DW_LNE_set_address extended op.
                        */
                       RCHECK(WRITE_VALUE(0, 1));
                       RCHECK(WRITE_ULEB128(dbg->dbg_pointer_size + 1));
                       RCHECK(WRITE_VALUE(DW_LNE_set_address, 1));
                       RCHECK(_dwarf_reloc_entry_add(dbg, drs, ds,
                           dwarf_drt_data_reloc, dbg->dbg_pointer_size,
                           ds->ds_size, ln->ln_symndx, ln->ln_addr,
                           NULL, error));
                       address = ln->ln_addr;
                       continue;
               } else if (ln->ln_endseq) {
                       addr0 = (ln->ln_addr - address) / li->li_minlen;
                       if (addr0 != 0) {
                               RCHECK(WRITE_VALUE(DW_LNS_advance_pc, 1));
                               RCHECK(WRITE_ULEB128(addr0));
                       }

                       /*
                        * Generate DW_LNE_end_sequence.
                        */
                       RCHECK(WRITE_VALUE(0, 1));
                       RCHECK(WRITE_ULEB128(1));
                       RCHECK(WRITE_VALUE(DW_LNE_end_sequence, 1));
                       RESET_REGISTERS;
                       continue;
               }

               /*
                * Generate standard opcodes for file, column, is_stmt or
                * basic_block changes.
                */
               if (ln->ln_fileno != file) {
                       RCHECK(WRITE_VALUE(DW_LNS_set_file, 1));
                       RCHECK(WRITE_ULEB128(ln->ln_fileno));
                       file = ln->ln_fileno;
               }
               if (ln->ln_column != column) {
                       RCHECK(WRITE_VALUE(DW_LNS_set_column, 1));
                       RCHECK(WRITE_ULEB128(ln->ln_column));
                       column = ln->ln_column;
               }
               if (ln->ln_stmt != is_stmt) {
                       RCHECK(WRITE_VALUE(DW_LNS_negate_stmt, 1));
                       is_stmt = ln->ln_stmt;
               }
               if (ln->ln_bblock && !basic_block) {
                       RCHECK(WRITE_VALUE(DW_LNS_set_basic_block, 1));
                       basic_block = 1;
               }

               /*
                * Calculate address and line number change.
                */
               addr0 = (ln->ln_addr - address) / li->li_minlen;
               line0 = ln->ln_lineno - line;

               if (addr0 == 0 && line0 == 0)
                       continue;

               /*
                * Check if line delta is with the range and if the special
                * opcode can be used.
                */
               assert(li->li_lbase <= 0);
               if (line0 >= li->li_lbase &&
                   line0 <= li->li_lbase + li->li_lrange - 1) {
                       spc = (line0 - li->li_lbase) +
                           (li->li_lrange * addr0) + li->li_opbase;
                       if (spc <= 255) {
                               RCHECK(WRITE_VALUE(spc, 1));
                               basic_block = 0;
                               goto next_line;
                       }
               }

               /* Generate DW_LNS_advance_line for line number change. */
               if (line0 != 0) {
                       RCHECK(WRITE_VALUE(DW_LNS_advance_line, 1));
                       RCHECK(WRITE_SLEB128(line0));
                       line0 = 0;
                       need_copy = 1;
               } else
                       need_copy = basic_block;

               if (addr0 != 0) {
                       /* See if it can be handled by DW_LNS_const_add_pc. */
                       spc = (line0 - li->li_lbase) +
                           (li->li_lrange * (addr0 - maddr)) + li->li_opbase;
                       if (addr0 >= maddr && spc <= 255) {
                               RCHECK(WRITE_VALUE(DW_LNS_const_add_pc, 1));
                               RCHECK(WRITE_VALUE(spc, 1));
                       } else {
                               /* Otherwise we use DW_LNS_advance_pc. */
                               RCHECK(WRITE_VALUE(DW_LNS_advance_pc, 1));
                               RCHECK(WRITE_ULEB128(addr0));
                       }
               }

               if (need_copy) {
                       RCHECK(WRITE_VALUE(DW_LNS_copy, 1));
                       basic_block = 0;
               }

       next_line:
               address = ln->ln_addr;
               line = ln->ln_lineno;
       }

       return (DW_DLE_NONE);

gen_fail:
       return (ret);

#undef  RESET_REGISTERS
}

static uint8_t
_dwarf_get_minlen(Dwarf_P_Debug dbg)
{

       assert(dbg != NULL);

       switch (dbg->dbgp_isa) {
       case DW_ISA_ARM:
               return (2);
       case DW_ISA_X86:
       case DW_ISA_X86_64:
               return (1);
       default:
               return (4);
       }
}

static uint8_t oplen[] = {0, 1, 1, 1, 1, 0, 0, 0, 1};

int
_dwarf_lineno_gen(Dwarf_P_Debug dbg, Dwarf_Error *error)
{
       Dwarf_LineInfo li;
       Dwarf_LineFile lf;
       Dwarf_P_Section ds;
       Dwarf_Rel_Section drs;
       Dwarf_Unsigned offset;
       int i, ret;

       assert(dbg != NULL && dbg->dbgp_lineinfo != NULL);

       li = dbg->dbgp_lineinfo;
       if (STAILQ_EMPTY(&li->li_lnlist))
               return (DW_DLE_NONE);

       li->li_length = 0;
       li->li_version = 2;
       li->li_hdrlen = 0;
       li->li_minlen = _dwarf_get_minlen(dbg);
       li->li_defstmt = 1;
       li->li_lbase = -5;
       li->li_lrange = 14;
       li->li_opbase = 10;

       /* Create .debug_line section. */
       if ((ret = _dwarf_section_init(dbg, &ds, ".debug_line", 0, error)) !=
           DW_DLE_NONE)
               return (ret);

       /* Create relocation section for .debug_line */
       if ((ret = _dwarf_reloc_section_init(dbg, &drs, ds, error)) !=
           DW_DLE_NONE)
               goto gen_fail1;

       /* Length placeholder. (We only use 32-bit DWARF format) */
       RCHECK(WRITE_VALUE(0, 4));

       /* Write line number dwarf version. (DWARF2) */
       RCHECK(WRITE_VALUE(li->li_version, 2));

       /* Header length placeholder. */
       offset = ds->ds_size;
       RCHECK(WRITE_VALUE(li->li_hdrlen, 4));

       /* Write minimum instruction length. */
       RCHECK(WRITE_VALUE(li->li_minlen, 1));

       /*
        * Write initial value for is_stmt. XXX Which default value we
        * should use?
        */
       RCHECK(WRITE_VALUE(li->li_defstmt, 1));

       /*
        * Write line_base and line_range. FIXME These value needs to be
        * fine tuned.
        */
       RCHECK(WRITE_VALUE(li->li_lbase, 1));
       RCHECK(WRITE_VALUE(li->li_lrange, 1));

       /* Write opcode_base. (DWARF2) */
       RCHECK(WRITE_VALUE(li->li_opbase, 1));

       /* Write standard op length array. */
       RCHECK(WRITE_BLOCK(oplen, sizeof(oplen) / sizeof(oplen[0])));

       /* Write the list of include directories. */
       for (i = 0; (Dwarf_Unsigned) i < li->li_inclen; i++)
               RCHECK(WRITE_STRING(li->li_incdirs[i]));
       RCHECK(WRITE_VALUE(0, 1));

       /* Write the list of filenames. */
       STAILQ_FOREACH(lf, &li->li_lflist, lf_next) {
               RCHECK(WRITE_STRING(lf->lf_fname));
               RCHECK(WRITE_ULEB128(lf->lf_dirndx));
               RCHECK(WRITE_ULEB128(lf->lf_mtime));
               RCHECK(WRITE_ULEB128(lf->lf_size));
       }
       RCHECK(WRITE_VALUE(0, 1));

       /* Fill in the header length. */
       li->li_hdrlen = ds->ds_size - offset - 4;
       dbg->write(ds->ds_data, &offset, li->li_hdrlen, 4);

       /* Generate the line number program. */
       RCHECK(_dwarf_lineno_gen_program(dbg, ds, drs, error));

       /* Fill in the length of this line info. */
       li->li_length = ds->ds_size - 4;
       offset = 0;
       dbg->write(ds->ds_data, &offset, li->li_length, 4);

       /* Notify the creation of .debug_line ELF section. */
       RCHECK(_dwarf_section_callback(dbg, ds, SHT_PROGBITS, 0, 0, 0, error));

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

       return (DW_DLE_NONE);

gen_fail:
       _dwarf_reloc_section_free(dbg, &drs);

gen_fail1:
       _dwarf_section_free(dbg, &ds);

       return (ret);
}

void
_dwarf_lineno_pro_cleanup(Dwarf_P_Debug dbg)
{
       Dwarf_LineInfo li;
       Dwarf_LineFile lf, tlf;
       Dwarf_Line ln, tln;
       int i;

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

       li = dbg->dbgp_lineinfo;
       STAILQ_FOREACH_SAFE(lf, &li->li_lflist, lf_next, tlf) {
               STAILQ_REMOVE(&li->li_lflist, lf, _Dwarf_LineFile,
                   lf_next);
               if (lf->lf_fname)
                       free(lf->lf_fname);
               free(lf);
       }
       STAILQ_FOREACH_SAFE(ln, &li->li_lnlist, ln_next, tln) {
               STAILQ_REMOVE(&li->li_lnlist, ln, _Dwarf_Line, ln_next);
               free(ln);
       }
       if (li->li_incdirs) {
               for (i = 0; (Dwarf_Unsigned) i < li->li_inclen; i++)
                       free(li->li_incdirs[i]);
               free(li->li_incdirs);
       }
       free(li);
       dbg->dbgp_lineinfo = NULL;
}