blob: 796b790092eba253a9c5b0399b29261d4494f724 [file] [log] [blame]
/* Return source lines of compilation unit.
Copyright (C) 2000, 2001, 2002 Red Hat, Inc.
Written by Ulrich Drepper <drepper@redhat.com>, 2000.
This program is Open Source software; you can redistribute it and/or
modify it under the terms of the Open Software License version 1.0 as
published by the Open Source Initiative.
You should have received a copy of the Open Software License along
with this program; if not, you may obtain a copy of the Open Software
License version 1.0 from http://www.opensource.org/licenses/osl.php or
by writing the Open Source Initiative c/o Lawrence Rosen, Esq.,
3001 King Ranch Road, Ukiah, CA 95482. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <dwarf.h>
#include <stdlib.h>
#include <string.h>
#include <libdwarfP.h>
struct dirlist
{
char *dir;
size_t len;
struct dirlist *next;
};
struct filelist
{
char *name;
Dwarf_Unsigned mtime;
Dwarf_Unsigned length;
struct filelist *next;
};
struct linelist
{
Dwarf_Line line;
struct linelist *next;
};
/* Adds a new line to the matrix. We cannot definte a function because
we want to use alloca. */
#define NEW_LINE(end_seq) \
do { \
/* Add the new line. */ \
new_line = (struct linelist *) alloca (sizeof (struct linelist)); \
new_line->line = (Dwarf_Line) malloc (sizeof (struct Dwarf_Line_s)); \
if (new_line == NULL) \
{ \
/* XXX Should we bother to free the memory? */ \
__libdwarf_error (dbg, error, DW_E_NOMEM); \
return DW_DLV_ERROR; \
} \
\
/* Set the line information. */ \
new_line->line->addr = address; \
new_line->line->file = file; \
new_line->line->line = line; \
new_line->line->column = column; \
new_line->line->is_stmt = is_stmt; \
new_line->line->basic_block = basic_block; \
new_line->line->end_sequence = end_seq; \
new_line->line->prologue_end = prologue_end; \
new_line->line->epilogue_begin = epilogue_begin; \
\
new_line->next = linelist; \
linelist = new_line; \
++nlinelist; \
} while (0)
int
dwarf_srclines (die, linebuf, linecount, error)
Dwarf_Die die;
Dwarf_Line **linebuf;
Dwarf_Signed *linecount;
Dwarf_Error *error;
{
Dwarf_CU_Info cu = die->cu;
Dwarf_Debug dbg = cu->dbg;
Dwarf_Attribute stmt_list;
Dwarf_Attribute comp_dir_attr;
char *comp_dir;
Dwarf_Unsigned offset;
Dwarf_Small *linep;
Dwarf_Small *lineendp;
Dwarf_Small *header_start;
Dwarf_Unsigned header_length;
Dwarf_File files;
Dwarf_Line *lines;
unsigned int unit_length;
unsigned int version;
unsigned int opcode_base;
Dwarf_Small *standard_opcode_lengths;
unsigned int minimum_instruction_length;
unsigned int default_is_stmt;
int line_base;
unsigned int line_range;
int length;
struct dirlist comp_dir_elem;
struct dirlist *dirlist;
unsigned int ndirlist;
struct dirlist **dirarray;
struct filelist *filelist;
unsigned int nfilelist;
struct filelist null_file;
Dwarf_Unsigned address;
size_t file;
size_t line;
size_t column;
int is_stmt;
int basic_block;
int prologue_end;
int epilogue_begin;
struct linelist *linelist;
unsigned int nlinelist;
int res;
/* The die must be for a compilation unit. */
if (unlikely (die->abbrev->tag != DW_TAG_compile_unit))
{
__libdwarf_error (die->cu->dbg, error, DW_E_NO_CU);
return DW_DLV_ERROR;
}
/* The die must have a statement list associated. */
res = dwarf_attr (die, DW_AT_stmt_list, &stmt_list, error);
if (unlikely (res != DW_DLV_OK))
return res;
/* Get the offset into the .debug_line section. */
res = dwarf_formudata (stmt_list, &offset, error);
if (unlikely (res != DW_DLV_OK))
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
return res;
}
/* We need a .debug_line section. */
if (dbg->sections[IDX_debug_line].addr == NULL)
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_NO_DEBUG_LINE);
return DW_DLV_ERROR;
}
linep = (Dwarf_Small *) dbg->sections[IDX_debug_line].addr + offset;
lineendp = ((Dwarf_Small *) dbg->sections[IDX_debug_line].addr
+ dbg->sections[IDX_debug_line].size);
/* Test whether at least the first 4 bytes are available. */
if (unlikely (linep + 4 > lineendp))
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
/* Get the compilation directory. */
res = dwarf_attr (die, DW_AT_comp_dir, &comp_dir_attr, error);
if (unlikely (res == DW_DLV_ERROR)
|| (res == DW_DLV_OK
&& unlikely (dwarf_formstring (comp_dir_attr, &comp_dir, error)
== DW_DLV_ERROR)))
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
else if (res == DW_DLV_OK)
dwarf_dealloc (dbg, comp_dir_attr, DW_DLA_ATTR);
else
comp_dir = NULL;
/* Read the unit_length. */
unit_length = read_4ubyte_unaligned (dbg, linep);
linep += 4;
length = 4;
if (unit_length == 0xffffffff)
{
if (unlikely (linep + 8 > lineendp))
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
unit_length = read_8ubyte_unaligned (dbg, linep);
linep += 8;
length = 8;
}
/* Check whether we have enough room in the section. */
if (unlikely (linep + unit_length > lineendp))
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
lineendp = linep + unit_length;
/* The next element of the header is the version identifier. */
version = read_2ubyte_unaligned (dbg, linep);
if (unlikely (version != DWARF_VERSION))
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_VERSION_ERROR);
return DW_DLV_ERROR;
}
linep += 2;
/* Next comes the header length. */
if (length == 4)
{
header_length = read_4ubyte_unaligned (dbg, linep);
linep += 4;
}
else
{
header_length = read_8ubyte_unaligned (dbg, linep);
linep += 8;
}
header_start = linep;
/* Next the minimum instruction length. */
minimum_instruction_length = *linep++;
/* Then the flag determining the default value of the is_stmt
register. */
default_is_stmt = *linep++;
/* Now the line base. */
line_base = *((signed char *) linep)++;
/* And the line range. */
line_range = *linep++;
/* The opcode base. */
opcode_base = *linep++;
/* Remember array with the standard opcode length (-1 to account for
the opcode with value zero not being mentioned). */
standard_opcode_lengths = linep - 1;
linep += opcode_base - 1;
/* First comes the list of directories. Add the compilation directory
first since the index zero is used for it. */
comp_dir_elem.dir = comp_dir;
comp_dir_elem.len = comp_dir ? strlen (comp_dir) : 0;
comp_dir_elem.next = NULL;
dirlist = &comp_dir_elem;
ndirlist = 1;
while (*linep != 0)
{
struct dirlist *new_dir = (struct dirlist *) alloca (sizeof (*new_dir));
new_dir->dir = (char *) linep;
new_dir->len = strlen ((char *) linep);
new_dir->next = dirlist;
dirlist = new_dir;
++ndirlist;
linep += new_dir->len + 1;
}
/* Skip the final NUL byte. */
++linep;
/* Rearrange the list in array form. */
dirarray = (struct dirlist **) alloca (sizeof (*dirarray));
while (ndirlist-- > 0)
{
dirarray[ndirlist] = dirlist;
dirlist = dirlist->next;
}
comp_dir_elem.dir = comp_dir;
comp_dir_elem.len = comp_dir ? strlen (comp_dir) : 0;
comp_dir_elem.next = NULL;
dirlist = &comp_dir_elem;
ndirlist = 1;
/* Now read the files. */
null_file.name = "???";
null_file.mtime = 0;
null_file.length = 0;
null_file.next = NULL;
filelist = &null_file;
nfilelist = 1;
while (*linep != 0)
{
struct filelist *new_file =
(struct filelist *) alloca (sizeof (*new_file));
char *fname;
size_t fnamelen;
Dwarf_Unsigned diridx;
/* First comes the file name. */
fname = (char *) linep;
fnamelen = strlen (fname);
linep += fnamelen + 1;
/* Then the index. */
get_uleb128 (diridx, linep);
if (unlikely (diridx >= ndirlist))
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DIR_IDX);
return DW_DLV_ERROR;
}
if (*fname == '/')
/* It's an absolute path. */
new_file->name = strdup (fname);
else
{
new_file->name = (char *) malloc (dirarray[diridx]->len + 1
+ fnamelen + 1);
if (new_file->name != NULL)
{
char *cp = new_file->name;
if (dirarray[diridx]->dir != NULL)
/* This value could be NULL in case the DW_AT_comp_dir
was not present. We cannot do much in this case.
The easiest thing is to convert the path in an
absolute path. */
cp = stpcpy (cp, dirarray[diridx]->dir);
*cp++ = '/';
strcpy (cp, fname);
}
}
if (new_file->name == NULL)
{
/* XXX Should we bother to free all the memory? */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_NOMEM);
return DW_DLV_ERROR;
}
/* Next comes the modification time. */
get_uleb128 (new_file->mtime, linep);
/* Finally the length of the file. */
get_uleb128 (new_file->length, linep);
new_file->next = filelist;
filelist = new_file;
++nfilelist;
}
++linep;
/* Consistency check. */
if (unlikely (linep != header_start + header_length))
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
/* We are about to process the statement program. Initialize the
state machine registers (see 6.2.2 in the v2.1 specification). */
address = 0;
file = 1;
line = 1;
column = 0;
is_stmt = default_is_stmt;
basic_block = 0;
prologue_end = 0;
epilogue_begin = 0;
/* Process the instructions. */
linelist = NULL;
nlinelist = 0;
while (linep < lineendp)
{
struct linelist *new_line;
unsigned int opcode;
unsigned int u128;
int s128;
/* Read the opcode. */
opcode = *linep++;
/* Is this a special opcode? */
if (likely (opcode >= opcode_base))
{
/* Yes. Handling this is quite easy since the opcode value
is computed with
opcode = (desired line increment - line_base)
+ (line_range * address advance) + opcode_base
*/
int line_increment = line_base + (opcode - opcode_base) % line_range;
unsigned int address_increment = (minimum_instruction_length
* ((opcode - opcode_base)
/ line_range));
/* Perform the increments. */
line += line_increment;
address += address_increment;
/* Add a new line with the current state machine values. */
NEW_LINE (0);
/* Reset the flags. */
basic_block = 0;
prologue_end = 0;
epilogue_begin = 0;
}
else if (opcode == 0)
{
/* This an extended opcode. */
unsigned int len;
/* The length. */
len = *linep++;
/* The sub-opecode. */
opcode = *linep++;
switch (opcode)
{
case DW_LNE_end_sequence:
/* Add a new line with the current state machine values.
The is the end of the sequence. */
NEW_LINE (1);
/* Reset the registers. */
address = 0;
file = 1;
line = 1;
column = 0;
is_stmt = default_is_stmt;
basic_block = 0;
prologue_end = 0;
epilogue_begin = 0;
break;
case DW_LNE_set_address:
if (cu->address_size == 4)
address = read_4ubyte_unaligned (dbg, linep);
else
address = read_8ubyte_unaligned (dbg, linep);
linep += cu->address_size;
break;
case DW_LNE_define_file:
{
struct filelist *new_file;
char *fname;
size_t fnamelen;
unsigned int diridx;
Dwarf_Unsigned mtime;
Dwarf_Unsigned length;
fname = (char *) linep;
fnamelen = strlen (fname);
linep += fnamelen + 1;
get_uleb128 (diridx, linep);
get_uleb128 (mtime, linep);
get_uleb128 (length, linep);
new_file = (struct filelist *) alloca (sizeof (*new_file));
if (fname[0] == '/')
new_file->name = strdup (fname);
else
{
new_file->name = (char *) malloc (dirarray[diridx]->len + 1
+ fnamelen + 1);
if (new_file->name != NULL)
{
char *cp = new_file->name;
if (dirarray[diridx]->dir != NULL)
/* This value could be NULL in case the
DW_AT_comp_dir was not present. We
cannot do much in this case. The easiest
thing is to convert the path in an
absolute path. */
cp = stpcpy (cp, dirarray[diridx]->dir);
*cp++ = '/';
strcpy (cp, fname);
}
}
if (new_file->name == NULL)
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_NOMEM);
return DW_DLV_ERROR;
}
new_file->mtime = mtime;
new_file->length = length;
new_file->next = filelist;
filelist = new_file;
++nfilelist;
}
break;
default:
/* Unknown, ignore it. */
linep += len - 1;
break;
}
}
else if (opcode <= DW_LNS_set_epilog_begin)
{
/* This is a known standard opcode. */
switch (opcode)
{
case DW_LNS_copy:
/* Takes no argument. */
if (unlikely (standard_opcode_lengths[opcode] != 0))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
/* Add a new line with the current state machine values. */
NEW_LINE (0);
/* Reset the flags. */
basic_block = 0;
/* XXX Whether the following two lines are necessary is
unclear. I guess the current v2.1 specification has
a bug in that it says clearing these two registers is
not necessary. */
prologue_end = 0;
epilogue_begin = 0;
break;
case DW_LNS_advance_pc:
/* Takes one uleb128 parameter which is added to the address. */
if (unlikely (standard_opcode_lengths[opcode] != 1))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
get_uleb128 (u128, linep);
address += minimum_instruction_length * u128;
break;
case DW_LNS_advance_line:
/* Takes one sleb128 parameter which is added to the line. */
if (unlikely (standard_opcode_lengths[opcode] != 1))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
get_sleb128 (s128, linep);
line += s128;
break;
case DW_LNS_set_file:
/* Takes one uleb128 parameter which is stored in file. */
if (unlikely (standard_opcode_lengths[opcode] != 1))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
get_uleb128 (u128, linep);
file = u128;
break;
case DW_LNS_set_column:
/* Takes one uleb128 parameter which is stored in column. */
if (unlikely (standard_opcode_lengths[opcode] != 1))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
get_uleb128 (u128, linep);
column = u128;
break;
case DW_LNS_negate_stmt:
/* Takes no argument. */
if (unlikely (standard_opcode_lengths[opcode] != 0))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
is_stmt = 1 - is_stmt;
break;
case DW_LNS_set_basic_block:
/* Takes no argument. */
if (unlikely (standard_opcode_lengths[opcode] != 0))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
basic_block = 1;
break;
case DW_LNS_const_add_pc:
/* Takes no argument. */
if (unlikely (standard_opcode_lengths[opcode] != 0))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
address += (minimum_instruction_length
* ((255 - opcode_base) / line_range));
break;
case DW_LNS_fixed_advance_pc:
/* Takes one 16 bit parameter which is added to the address. */
if (unlikely (standard_opcode_lengths[opcode] != 1))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
address += read_2ubyte_unaligned (dbg, linep);
linep += 2;
break;
case DW_LNS_set_prologue_end:
/* Takes no argument. */
if (unlikely (standard_opcode_lengths[opcode] != 0))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
prologue_end = 1;
break;
case DW_LNS_set_epilog_begin:
/* Takes no argument. */
if (unlikely (standard_opcode_lengths[opcode] != 0))
{
/* XXX Free memory. */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_INVALID_DWARF);
return DW_DLV_ERROR;
}
epilogue_begin = 1;
break;
}
}
else
{
/* This is a new opcode the generator but not we know about.
Read the parameters associated with it but then discard
everything. */
int n;
/* Read all the parameters for this opcode. */
for (n = standard_opcode_lengths[opcode]; n > 0; --n)
{
Dwarf_Unsigned u128;
get_uleb128 (u128, linep);
}
/* Next round, ignore this opcode. */
continue;
}
}
/* Put all the files in an array. */
files = (Dwarf_File) malloc (sizeof (struct Dwarf_File_s)
+ nfilelist * sizeof (Dwarf_Fileinfo));
if (files == NULL)
{
/* XXX Should we bother to free all the memory? */
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_NOMEM);
return DW_DLV_ERROR;
}
files->nfiles = nfilelist;
while (nfilelist-- > 0)
{
files->info[nfilelist].name = filelist->name;
files->info[nfilelist].mtime = filelist->mtime;
files->info[nfilelist].length = filelist->length;
filelist = filelist->next;
}
/* Remember the debugging descriptor. */
files->dbg = dbg;
/* Now put the lines in an array. */
lines = (Dwarf_Line *) malloc (nlinelist * sizeof (Dwarf_Line));
if (lines == NULL)
{
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
__libdwarf_error (dbg, error, DW_E_NOMEM);
return DW_DLV_ERROR;
}
*linebuf = lines;
*linecount = nlinelist;
while (nlinelist--)
{
lines[nlinelist] = linelist->line;
linelist->line->files = files;
linelist = linelist->next;
}
dwarf_dealloc (dbg, stmt_list, DW_DLA_ATTR);
return DW_DLV_OK;
}