diff options
Diffstat (limited to 'libdw/dwarf_getsrclines.c')
-rw-r--r-- | libdw/dwarf_getsrclines.c | 495 |
1 files changed, 383 insertions, 112 deletions
diff --git a/libdw/dwarf_getsrclines.c b/libdw/dwarf_getsrclines.c index d02c38db..1432b1db 100644 --- a/libdw/dwarf_getsrclines.c +++ b/libdw/dwarf_getsrclines.c @@ -1,7 +1,6 @@ /* Return line number information of CU. - Copyright (C) 2004-2010, 2013, 2014, 2015, 2016 Red Hat, Inc. + Copyright (C) 2004-2010, 2013, 2014, 2015, 2016, 2018 Red Hat, Inc. This file is part of elfutils. - Written by Ulrich Drepper <drepper@redhat.com>, 2004. This file is free software; you can redistribute it and/or modify it under the terms of either @@ -154,20 +153,9 @@ read_srclines (Dwarf *dbg, { int res = -1; + struct filelist *filelist = NULL; size_t nfilelist = 0; - unsigned int ndirlist = 0; - - struct filelist null_file = - { - .info = - { - .name = "???", - .mtime = 0, - .length = 0 - }, - .next = NULL - }; - struct filelist *filelist = &null_file; + size_t ndirlist = 0; /* If there are a large number of lines, files or dirs don't blow up the stack. Stack allocate some entries, only dynamically malloc @@ -177,16 +165,7 @@ read_srclines (Dwarf *dbg, #define MAX_STACK_FILES (MAX_STACK_ALLOC / 4) #define MAX_STACK_DIRS (MAX_STACK_ALLOC / 16) - struct dirlist - { - const char *dir; - size_t len; - }; - struct dirlist dirstack[MAX_STACK_DIRS]; - struct dirlist *dirarray = dirstack; - - /* We are about to process the statement program. Initialize the - state machine registers (see 6.2.2 in the v2.1 specification). */ + /* Initial statement program state (except for stmt_list, see below). */ struct line_state state = { .linelist = NULL, @@ -204,6 +183,17 @@ read_srclines (Dwarf *dbg, .discriminator = 0 }; + /* The dirs normally go on the stack, but if there are too many + we alloc them all. Set up stack storage early, so we can check on + error if we need to free them or not. */ + struct dirlist + { + const char *dir; + size_t len; + }; + struct dirlist dirstack[MAX_STACK_DIRS]; + struct dirlist *dirarray = dirstack; + if (unlikely (linep + 4 > lineendp)) { invalid_data: @@ -222,25 +212,45 @@ read_srclines (Dwarf *dbg, } /* Check whether we have enough room in the section. */ - if (unlikely (unit_length > (size_t) (lineendp - linep) - || unit_length < 2 + length + 5 * 1)) + if (unlikely (unit_length > (size_t) (lineendp - linep))) goto invalid_data; lineendp = linep + unit_length; /* The next element of the header is the version identifier. */ + if ((size_t) (lineendp - linep) < 2) + goto invalid_data; uint_fast16_t version = read_2ubyte_unaligned_inc (dbg, linep); - if (unlikely (version < 2) || unlikely (version > 4)) + if (unlikely (version < 2) || unlikely (version > 5)) { __libdw_seterrno (DWARF_E_VERSION); goto out; } + /* DWARF5 explicitly lists address and segment_selector sizes. */ + if (version >= 5) + { + if ((size_t) (lineendp - linep) < 2) + goto invalid_data; + size_t line_address_size = *linep++; + size_t segment_selector_size = *linep++; + if (line_address_size != address_size || segment_selector_size != 0) + goto invalid_data; + } + /* Next comes the header length. */ Dwarf_Word header_length; if (length == 4) - header_length = read_4ubyte_unaligned_inc (dbg, linep); + { + if ((size_t) (lineendp - linep) < 4) + goto invalid_data; + header_length = read_4ubyte_unaligned_inc (dbg, linep); + } else - header_length = read_8ubyte_unaligned_inc (dbg, linep); + { + if ((size_t) (lineendp - linep) < 8) + goto invalid_data; + header_length = read_8ubyte_unaligned_inc (dbg, linep); + } const unsigned char *header_start = linep; /* Next the minimum instruction length. */ @@ -250,13 +260,17 @@ read_srclines (Dwarf *dbg, uint_fast8_t max_ops_per_instr = 1; if (version >= 4) { - if (unlikely (lineendp - linep < 5)) + if (unlikely ((size_t) (lineendp - linep) < 1)) goto invalid_data; max_ops_per_instr = *linep++; if (unlikely (max_ops_per_instr == 0)) goto invalid_data; } + /* 4 more bytes, is_stmt, line_base, line_range and opcode_base. */ + if ((size_t) (lineendp - linep) < 4) + goto invalid_data; + /* Then the flag determining the default value of the is_stmt register. */ uint_fast8_t default_is_stmt = *linep++; @@ -277,31 +291,85 @@ read_srclines (Dwarf *dbg, goto invalid_data; linep += opcode_base - 1; - /* First comes the list of directories. Add the compilation - directory first since the index zero is used for it. */ - struct dirlist comp_dir_elem = - { - .dir = comp_dir, - .len = comp_dir ? strlen (comp_dir) : 0, - }; - ndirlist = 1; + /* To read DWARF5 dir and file lists we need to know the forms. For + now we skip everything, except the DW_LNCT_path and + DW_LNCT_directory_index. */ + uint16_t forms[256]; + unsigned char nforms = 0; + unsigned char form_path = -1; /* Which forms is DW_LNCT_path. */ + unsigned char form_idx = -1; /* And which is DW_LNCT_directory_index. */ + + /* To read/skip form data. */ + Dwarf_CU fake_cu = { + .dbg = dbg, + .sec_idx = IDX_debug_line, + .version = 5, + .offset_size = length, + .address_size = address_size, + .startp = (void *) linep, + .endp = (void *) lineendp, + }; /* First count the entries. */ - const unsigned char *dirp = linep; - unsigned int ndirs = 0; - while (*dirp != 0) + size_t ndirs = 0; + if (version < 5) + { + const unsigned char *dirp = linep; + while (*dirp != 0) + { + uint8_t *endp = memchr (dirp, '\0', lineendp - dirp); + if (endp == NULL) + goto invalid_data; + ++ndirs; + dirp = endp + 1; + } + ndirs = ndirs + 1; /* There is always the "unknown" dir. */ + } + else { - uint8_t *endp = memchr (dirp, '\0', lineendp - dirp); - if (endp == NULL) + if ((size_t) (lineendp - linep) < 1) + goto invalid_data; + nforms = *linep++; + for (int i = 0; i < nforms; i++) + { + uint16_t desc, form; + if ((size_t) (lineendp - linep) < 1) + goto invalid_data; + get_uleb128 (desc, linep, lineendp); + if ((size_t) (lineendp - linep) < 1) + goto invalid_data; + get_uleb128 (form, linep, lineendp); + + if (! libdw_valid_user_form (form)) + goto invalid_data; + + forms[i] = form; + if (desc == DW_LNCT_path) + form_path = i; + } + + if (nforms > 0 && form_path == (unsigned char) -1) + goto invalid_data; + + if ((size_t) (lineendp - linep) < 1) + goto invalid_data; + get_uleb128 (ndirs, linep, lineendp); + + if (nforms == 0 && ndirs != 0) + goto invalid_data; + + /* Assume there is at least 1 byte needed per form to describe + the directory. Filters out insanely large ndirs. */ + if (nforms != 0 && ndirs > (size_t) (lineendp - linep) / nforms) goto invalid_data; - ++ndirs; - dirp = endp + 1; } - ndirlist += ndirs; /* Arrange the list in array form. */ + ndirlist = ndirs; if (ndirlist >= MAX_STACK_DIRS) { + if (ndirlist > SIZE_MAX / sizeof (*dirarray)) + goto no_mem; dirarray = (struct dirlist *) malloc (ndirlist * sizeof (*dirarray)); if (unlikely (dirarray == NULL)) { @@ -310,20 +378,82 @@ read_srclines (Dwarf *dbg, goto out; } } - dirarray[0] = comp_dir_elem; - for (unsigned int n = 1; n < ndirlist; n++) + + /* Entry zero is implicit for older versions, but explicit for 5+. */ + struct dirlist comp_dir_elem; + if (version < 5) { - dirarray[n].dir = (char *) linep; - uint8_t *endp = memchr (linep, '\0', lineendp - linep); - assert (endp != NULL); - dirarray[n].len = endp - linep; - linep = endp + 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, + dirarray[0] = comp_dir_elem; + for (unsigned int n = 1; n < ndirlist; n++) + { + dirarray[n].dir = (char *) linep; + uint8_t *endp = memchr (linep, '\0', lineendp - linep); + assert (endp != NULL); + dirarray[n].len = endp - linep; + linep = endp + 1; + } + /* Skip the final NUL byte. */ + ++linep; + } + else + { + Dwarf_Attribute attr; + attr.code = DW_AT_name; + attr.cu = &fake_cu; + for (unsigned int n = 0; n < ndirlist; n++) + { + const char *dir = NULL; + for (unsigned char m = 0; m < nforms; m++) + { + if (m == form_path) + { + attr.form = forms[m]; + attr.valp = (void *) linep; + dir = dwarf_formstring (&attr); + } + + size_t len = __libdw_form_val_len (&fake_cu, forms[m], linep); + if ((size_t) (lineendp - linep) < len) + goto invalid_data; + + linep += len; + } + + if (dir == NULL) + goto invalid_data; + + dirarray[n].dir = dir; + dirarray[n].len = strlen (dir); + } } - /* Skip the final NUL byte. */ - ++linep; + + /* File index zero doesn't exist for DWARF < 5. Files are indexed + starting from 1. But for DWARF5 they are indexed starting from + zero, but the default index is still 1. In both cases the + "first" file is special and refers to the main compile unit file, + equal to the DW_AT_name of the DW_TAG_compile_unit. */ + struct filelist null_file = + { + .info = + { + .name = "???", + .mtime = 0, + .length = 0 + }, + .next = NULL + }; + filelist = &null_file; + nfilelist = 1; /* Allocate memory for a new file. For the first MAX_STACK_FILES - entries just return a slot in the preallocated stack array. */ + entries just return a slot in the preallocated stack array. + This is slightly complicated because in DWARF < 5 new files could + be defined with DW_LNE_define_file after the normal file list was + read. */ struct filelist flstack[MAX_STACK_FILES]; #define NEW_FILE() ({ \ struct filelist *fl = (nfilelist < MAX_STACK_FILES \ @@ -337,69 +467,181 @@ read_srclines (Dwarf *dbg, fl; }) /* Now read the files. */ - nfilelist = 1; - - if (unlikely (linep >= lineendp)) - goto invalid_data; - while (*linep != 0) + if (version < 5) { - struct filelist *new_file = NEW_FILE (); - - /* First comes the file name. */ - char *fname = (char *) linep; - uint8_t *endp = memchr (fname, '\0', lineendp - linep); - if (endp == NULL) - goto invalid_data; - size_t fnamelen = endp - (uint8_t *) fname; - linep = endp + 1; - - /* Then the index. */ - Dwarf_Word diridx; if (unlikely (linep >= lineendp)) goto invalid_data; - get_uleb128 (diridx, linep, lineendp); - if (unlikely (diridx >= ndirlist)) + while (*linep != 0) { - __libdw_seterrno (DWARF_E_INVALID_DIR_IDX); - goto out; - } + struct filelist *new_file = NEW_FILE (); - if (*fname == '/') - /* It's an absolute path. */ - new_file->info.name = fname; - else - { - new_file->info.name = libdw_alloc (dbg, char, 1, - dirarray[diridx].len + 1 - + fnamelen + 1); - char *cp = new_file->info.name; + /* First comes the file name. */ + char *fname = (char *) linep; + uint8_t *endp = memchr (fname, '\0', lineendp - linep); + if (endp == NULL) + goto invalid_data; + size_t fnamelen = endp - (uint8_t *) fname; + linep = endp + 1; - if (dirarray[diridx].dir != NULL) + /* Then the index. */ + Dwarf_Word diridx; + if (unlikely (linep >= lineendp)) + goto invalid_data; + get_uleb128 (diridx, linep, lineendp); + if (unlikely (diridx >= ndirlist)) { - /* 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); + __libdw_seterrno (DWARF_E_INVALID_DIR_IDX); + goto out; + } + + if (*fname == '/') + /* It's an absolute path. */ + new_file->info.name = fname; + else + { + new_file->info.name = libdw_alloc (dbg, char, 1, + dirarray[diridx].len + 1 + + fnamelen + 1); + char *cp = new_file->info.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. + Just keep the file relative. */ + cp = stpcpy (cp, dirarray[diridx].dir); + *cp++ = '/'; + } + strcpy (cp, fname); + assert (strlen (new_file->info.name) + < dirarray[diridx].len + 1 + fnamelen + 1); } - *cp++ = '/'; - strcpy (cp, fname); - assert (strlen (new_file->info.name) - < dirarray[diridx].len + 1 + fnamelen + 1); + + /* Next comes the modification time. */ + if (unlikely (linep >= lineendp)) + goto invalid_data; + get_uleb128 (new_file->info.mtime, linep, lineendp); + + /* Finally the length of the file. */ + if (unlikely (linep >= lineendp)) + goto invalid_data; + get_uleb128 (new_file->info.length, linep, lineendp); } + /* Skip the final NUL byte. */ + ++linep; + } + else + { + if ((size_t) (lineendp - linep) < 1) + goto invalid_data; + nforms = *linep++; + form_path = form_idx = -1; + for (int i = 0; i < nforms; i++) + { + uint16_t desc, form; + if ((size_t) (lineendp - linep) < 1) + goto invalid_data; + get_uleb128 (desc, linep, lineendp); + if ((size_t) (lineendp - linep) < 1) + goto invalid_data; + get_uleb128 (form, linep, lineendp); - /* Next comes the modification time. */ - if (unlikely (linep >= lineendp)) + if (! libdw_valid_user_form (form)) + goto invalid_data; + + forms[i] = form; + if (desc == DW_LNCT_path) + form_path = i; + else if (desc == DW_LNCT_directory_index) + form_idx = i; + } + + if (nforms > 0 && (form_path == (unsigned char) -1 + || form_idx == (unsigned char) -1)) goto invalid_data; - get_uleb128 (new_file->info.mtime, linep, lineendp); - /* Finally the length of the file. */ - if (unlikely (linep >= lineendp)) + size_t nfiles; + get_uleb128 (nfiles, linep, lineendp); + + if (nforms == 0 && nfiles != 0) + goto invalid_data; + + /* Assume there is at least 1 byte needed per form to describe + the file. Filters out insanely large nfiles. */ + if (nforms != 0 && nfiles > (size_t) (lineendp - linep) / nforms) goto invalid_data; - get_uleb128 (new_file->info.length, linep, lineendp); + + Dwarf_Attribute attr; + attr.cu = &fake_cu; + for (unsigned int n = 0; n < nfiles; n++) + { + const char *fname = NULL; + Dwarf_Word diridx = (Dwarf_Word) -1; + for (unsigned char m = 0; m < nforms; m++) + { + if (m == form_path) + { + attr.code = DW_AT_name; + attr.form = forms[m]; + attr.valp = (void *) linep; + fname = dwarf_formstring (&attr); + } + else if (m == form_idx) + { + attr.code = DW_AT_decl_file; /* Close enough. */ + attr.form = forms[m]; + attr.valp = (void *) linep; + if (dwarf_formudata (&attr, &diridx) != 0) + diridx = (Dwarf_Word) -1; + } + + size_t len = __libdw_form_val_len (&fake_cu, forms[m], linep); + if ((size_t) (lineendp - linep) < len) + goto invalid_data; + + linep += len; + } + + if (fname == NULL || diridx == (Dwarf_Word) -1) + goto invalid_data; + + size_t fnamelen = strlen (fname); + + if (unlikely (diridx >= ndirlist)) + { + __libdw_seterrno (DWARF_E_INVALID_DIR_IDX); + goto out; + } + + /* Yes, weird. Looks like an off-by-one in the spec. */ + struct filelist *new_file = n == 0 ? &null_file : NEW_FILE (); + + /* We follow the same rules as above for DWARF < 5, even + though the standard doesn't explicitly mention absolute + paths and ignoring the dir index. */ + if (*fname == '/') + /* It's an absolute path. */ + new_file->info.name = (char *) fname; + else + { + new_file->info.name = libdw_alloc (dbg, char, 1, + dirarray[diridx].len + 1 + + fnamelen + 1); + char *cp = new_file->info.name; + + /* In the DWARF >= 5 case, dir can never be NULL. */ + cp = stpcpy (cp, dirarray[diridx].dir); + *cp++ = '/'; + strcpy (cp, fname); + assert (strlen (new_file->info.name) + < dirarray[diridx].len + 1 + fnamelen + 1); + } + + /* For now we just ignore the modification time and file length. */ + new_file->info.mtime = 0; + new_file->info.length = 0; + } } - /* Skip the final NUL byte. */ - ++linep; /* Consistency check. */ if (unlikely (linep != header_start + header_length)) @@ -408,6 +650,9 @@ read_srclines (Dwarf *dbg, goto out; } + /* We are about to process the statement program. Most state machine + registers have already been initialize above. Just add the is_stmt + default. See 6.2.2 in the v2.1 specification. */ state.is_stmt = default_is_stmt; /* Apply the "operation advance" from a special opcode or @@ -557,11 +802,12 @@ read_srclines (Dwarf *dbg, 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++ = '/'; + cannot do much in this case. Just + keep the file relative. */ + { + cp = stpcpy (cp, dirarray[diridx].dir); + *cp++ = '/'; + } strcpy (cp, fname); } @@ -820,7 +1066,7 @@ read_srclines (Dwarf *dbg, free (state.linelist); state.linelist = ll; } - if (ndirlist >= MAX_STACK_DIRS) + if (dirarray != dirstack) free (dirarray); for (size_t i = MAX_STACK_FILES; i < nfilelist; i++) { @@ -918,6 +1164,31 @@ dwarf_getsrclines (Dwarf_Die *cudie, Dwarf_Lines **lines, size_t *nlines) struct Dwarf_CU *const cu = cudie->cu; if (cu->lines == NULL) { + /* For split units always pick the lines from the skeleton. */ + if (cu->unit_type == DW_UT_split_compile + || cu->unit_type == DW_UT_split_type) + { + /* We tries, assume we fail... */ + cu->lines = (void *) -1l; + + Dwarf_CU *skel = __libdw_find_split_unit (cu); + if (skel != NULL) + { + Dwarf_Die skeldie = CUDIE (skel); + int res = INTUSE(dwarf_getsrclines) (&skeldie, lines, nlines); + if (res == 0) + { + cu->lines = skel->lines; + *lines = cu->lines; + *nlines = cu->lines->nlines; + } + return res; + } + + __libdw_seterrno (DWARF_E_NO_DEBUG_LINE); + return -1; + } + /* Failsafe mode: no data found. */ cu->lines = (void *) -1l; cu->files = (void *) -1l; |