/* $NetBSD: libdwarf_lineno.c,v 1.3 2016/02/20 02:43:41 christos Exp $ */ /*- * Copyright (c) 2009,2010 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.3 2016/02/20 02:43:41 christos Exp $"); ELFTC_VCSID("Id: libdwarf_lineno.c 3164 2015-02-19 01:20:12Z kaiwang27 "); static int _dwarf_lineno_add_file(Dwarf_LineInfo li, uint8_t **p, const char *compdir, Dwarf_Error *error, Dwarf_Debug dbg) { Dwarf_LineFile lf; const char *dirname; uint8_t *src; int slen; 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); } /* 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) { free(lf); DWARF_SET_ERROR(dbg, error, DW_DLE_MEMORY); return (DW_DLE_MEMORY); } snprintf(lf->lf_fullpath, slen, "%s/%s", dirname, lf->lf_fname); } } 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 } 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; const char *compdir; uint64_t length, hdroff, endoff; uint8_t *p; int dwarf_size, i, 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: 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); /* FIXME: verify version */ 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); /* * 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; } /* * 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; }