Hi Aaron,

On Wed, Sep 30, 2026 at 03:06:32PM -0400, Aaron Merey wrote:
> Lazy-loading of the srclines and srcfiles is protected by a per-CU
> mutex src_lock, but multiple threads querying different CUs might try
> to load the srclines/files concurrently.  This creates a data race
> in get_lines_or_files where one thread attempts to read a files_lines_s
> field while another thread sets it.

The src_lock is per CU, but the files_lines_tree is per Dwarf.

> Fix this by adding a new per-Dwarf mutex lines_files_lock that is held
> for the duration of get_lines_or_files.  eu_tfind and eu_tsearch calls
> in get_lines_or_files have been replaced with their _nolock variants.

That looks like the right fix.

> Signed-off-by: Aaron Merey <[email protected]>
> ---
>  libdw/dwarf_begin_elf.c   |  1 +
>  libdw/dwarf_end.c         |  1 +
>  libdw/dwarf_getsrclines.c | 31 ++++++++++++++++++++-----------
>  libdw/libdwP.h            |  4 ++++
>  4 files changed, 26 insertions(+), 11 deletions(-)
> 
> diff --git a/libdw/dwarf_begin_elf.c b/libdw/dwarf_begin_elf.c
> index e157d103..0521e53c 100644
> --- a/libdw/dwarf_begin_elf.c
> +++ b/libdw/dwarf_begin_elf.c
> @@ -449,6 +449,7 @@ valid_p (Dwarf *result)
>        mutex_init (result->dwarf_lock);
>        mutex_init (result->macro_lock);
>        mutex_init (result->dwp_lock);
> +      mutex_init (result->lines_files_lock);
>        eu_search_tree_init (&result->cu_tree);
>        eu_search_tree_init (&result->tu_tree);
>        eu_search_tree_init (&result->split_tree);

init new lines_files_lock

> diff --git a/libdw/dwarf_end.c b/libdw/dwarf_end.c
> index a7799773..aeb0c422 100644
> --- a/libdw/dwarf_end.c
> +++ b/libdw/dwarf_end.c
> @@ -136,6 +136,7 @@ dwarf_end (Dwarf *dwarf)
>        mutex_fini (dwarf->dwarf_lock);
>        mutex_fini (dwarf->macro_lock);
>        mutex_fini (dwarf->dwp_lock);
> +      mutex_fini (dwarf->lines_files_lock);
>  
>        /* Free the pubnames helper structure.  */
>        free (dwarf->pubnames_sets);

free new lines_files_lock

> diff --git a/libdw/dwarf_getsrclines.c b/libdw/dwarf_getsrclines.c
> index 5d48a934..cdd3bcc0 100644
> --- a/libdw/dwarf_getsrclines.c
> +++ b/libdw/dwarf_getsrclines.c
> @@ -1332,9 +1332,13 @@ get_lines_or_files (Dwarf *dbg, Dwarf_Off 
> debug_line_offset,
>                   const char *comp_dir, unsigned address_size,
>                   Dwarf_Lines **linesp, Dwarf_Files **filesp)
>  {
> +  int ret = -1;
> +
> +  mutex_lock (dbg->lines_files_lock);
> +

Set initial result and lock.

>    struct files_lines_s fake = { .debug_line_offset = debug_line_offset };
> -  struct files_lines_s **found = eu_tfind (&fake, &dbg->files_lines_tree,
> -                                        files_lines_compare);
> +  struct files_lines_s **found = eu_tfind_nolock (&fake, 
> &dbg->files_lines_tree,
> +                                               files_lines_compare);
>    if (found == NULL)
>      {
>        /* This .debug_line is being read for the first time.  */

use nolock variant because already under lines_files_lock.

> @@ -1342,7 +1346,7 @@ get_lines_or_files (Dwarf *dbg, Dwarf_Off 
> debug_line_offset,
>        if (data == NULL
>         || __libdw_offset_in_section (dbg, IDX_debug_line,
>                                       debug_line_offset, 1) != 0)
> -     return -1;
> +     goto out;
>  
>        const unsigned char *linep = data->d_buf + debug_line_offset;
>        const unsigned char *lineendp = data->d_buf + data->d_size;
> @@ -1359,19 +1363,20 @@ get_lines_or_files (Dwarf *dbg, Dwarf_Off 
> debug_line_offset,
>       {
>         if (read_srcfiles (dbg, linep, lineendp, comp_dir, address_size,
>                            NULL, &node->files) != 0)
> -         return -1;
> +         goto out;
>       }
>        else if (read_srclines (dbg, linep, lineendp, comp_dir, address_size,
>                        &node->lines, &node->files, false) != 0)
> -     return -1;
> +     goto out;
>  

OK, goto out.

>        node->debug_line_offset = debug_line_offset;
>  
> -      found = eu_tsearch (node, &dbg->files_lines_tree, files_lines_compare);
> +      found = eu_tsearch_nolock (node, &dbg->files_lines_tree,
> +                              files_lines_compare);

nolock variant.

>        if (found == NULL)
>       {
>         __libdw_seterrno (DWARF_E_NOMEM);
> -       return -1;
> +       goto out;
>       }
>      }
>    else if (*found != NULL
> @@ -1384,7 +1389,7 @@ get_lines_or_files (Dwarf *dbg, Dwarf_Off 
> debug_line_offset,
>        if (data == NULL
>         || __libdw_offset_in_section (dbg, IDX_debug_line,
>                                       debug_line_offset, 1) != 0)
> -     return -1;
> +     goto out;
>  
>        const unsigned char *linep = data->d_buf + debug_line_offset;
>        const unsigned char *lineendp = data->d_buf + data->d_size;
> @@ -1393,7 +1398,7 @@ get_lines_or_files (Dwarf *dbg, Dwarf_Off 
> debug_line_offset,
>  
>        if (read_srclines (dbg, linep, lineendp, comp_dir, address_size,
>                        &node->lines, &node->files, true) != 0)
> -     return -1;
> +     goto out;
>      }
>    else if (*found != NULL
>          && (*found)->files == NULL
> @@ -1401,7 +1406,7 @@ get_lines_or_files (Dwarf *dbg, Dwarf_Off 
> debug_line_offset,
>      {
>        /* If srclines were read then srcfiles should have also been read.  */
>        __libdw_seterrno (DWARF_E_INVALID_DEBUG_LINE);
> -      return -1;
> +      goto out;
>      }
>  
>    if (linesp != NULL)

All goto out.

> @@ -1410,7 +1415,11 @@ get_lines_or_files (Dwarf *dbg, Dwarf_Off 
> debug_line_offset,
>    if (filesp != NULL)
>      *filesp = (*found)->files;
>  
> -  return 0;
> +  ret = 0;
> +
> +out:
> +  mutex_unlock (dbg->lines_files_lock);
> +  return ret;

Set ret OK, unlock then return ret.

>  }
>  
>  int
> diff --git a/libdw/libdwP.h b/libdw/libdwP.h
> index d21164db..7d0b09a0 100644
> --- a/libdw/libdwP.h
> +++ b/libdw/libdwP.h
> @@ -268,6 +268,10 @@ struct Dwarf
>    /* Synchronize lazy-loading of dwp_dwarf and dwp_fd in try_dwp_file.  */
>    mutex_define(, dwp_lock);
>  
> +  /* Synchronize lazy-loading of Dwarf_Lines and Dwarf_Files in
> +     get_lines_or_files.  */
> +  mutex_define(, lines_files_lock);
> +
>    /* Internal memory handling.  This is basically a simplified thread-local
>       reimplementation of obstacks.  Unfortunately the standard obstack
>       implementation is not usable in libraries.  */

define new lines_files_lock

All looks fine.

Thanks,

Mark

Reply via email to