Patchwork Handle PIEs in libbacktrace

login
register
mail settings
Submitter Jakub Jelinek
Date Dec. 6, 2013, 7:50 a.m.
Message ID <20131206075033.GE892@tucnak.redhat.com>
Download mbox | patch
Permalink /patch/297610/
State New
Headers show

Comments

Jakub Jelinek - Dec. 6, 2013, 7:50 a.m.
Hi!

With the new tsan tests, I've noticed that libbacktrace symbolization
doesn't work when the binary is a PIE.
The problem is that in that case we obviously can't use base_address
of 0, the PIE typically will not have 0 bias, that is actually the sole
point of PIEs that their base address is randomized.  So, in that case
we need to wait until dl_iterate_phdr to determine the proper bias.
The executable is always the first in the search scope and/or list of
loaded modules, so we just check if the first callback gives us a dlpi_name
== NULL entry and assume it is the PIE.

Regtested on x86_64-linux, ok for trunk?

The rest of this mail except the libbacktrace patch is for the sanitizer
folks.

With this patch check-gcc tsan.exp passes fully again, but check-g++ does
not, there seems to be important differences between tsan symbolization
and say asan symbolization.  First of all, if symbolizer fails (as without
this patch for tsan because the executables are PIEs), we get something
like:
WARNING: ThreadSanitizer: data race (pid=18998)
  Write of size 4 at 0x7f4a88f0a08c by thread T1:
    #0 <null> <null>:0 (tiny_race+0x000000000b19)

  Previous write of size 4 at 0x7f4a88f0a08c by main thread:
    #0 <null> <null>:0 (tiny_race+0x000000000b7a)
    #1 __libc_start_main <null>:0 (libc.so.6+0x003c08c21b74)

  As if synchronized via sleep:
    #0 sleep ../../../../libsanitizer/tsan/tsan_interceptors.cc:239 (libtsan.so.0+0x000000048c65)
    #1 <null> <null>:0 (tiny_race+0x000000000b0a)

  Location is global '<null>' of size 0 at 0x000000000000 (tiny_race+0x00000020208c)

  Thread T1 (tid=19000, running) created by main thread at:
    #0 pthread_create ../../../../libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x0000000461b3)
    #1 <null> <null>:0 (tiny_race+0x000000000b6b)
    #2 __libc_start_main <null>:0 (libc.so.6+0x003c08c21b74)

The <null> <null>:0 or <null>:0 look very ugly, e.g. asan
I think just prints the exe/dso name + offset in that case.

And the reason why check-g++ tsan.exp fails even with this patch is
that apparently tsan doesn't try to demangle the symbol names, so we get
e.g.:
FAIL: c-c++-common/tsan/atomic_stack.c  -O0  output pattern test, is ==================
WARNING: ThreadSanitizer: data race (pid=22075)
  Atomic write of size 4 at 0x7fe8c9b630ac by thread T1:
    #0 __tsan_atomic32_fetch_add ../../../../libsanitizer/tsan/tsan_interface_atomic.cc:468 (libtsan.so.0+0x00000001ec7e)
    #1 _Z7Thread1Pv /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:11 (atomic_stack.exe+0x000000000d90)

  Previous write of size 4 at 0x7fe8c9b630ac by thread T2:
    #0 _Z7Thread2Pv /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:16 (atomic_stack.exe+0x000000000dde)

  As if synchronized via sleep:
    #0 sleep ../../../../libsanitizer/tsan/tsan_interceptors.cc:239 (libtsan.so.0+0x000000048c65)
    #1 _Z7Thread1Pv /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:10 (atomic_stack.exe+0x000000000d7a)

  Location is global 'Global' of size 4 at 0x7fe8c9b630ac (atomic_stack.exe+0x0000002020ac)

  Thread T1 (tid=22080, running) created by main thread at:
    #0 pthread_create ../../../../libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x0000000461b3)
    #1 main /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:22 (atomic_stack.exe+0x000000000e2a)

  Thread T2 (tid=22081, finished) created by main thread at:
    #0 pthread_create ../../../../libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x0000000461b3)
    #1 main /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:23 (atomic_stack.exe+0x000000000e4b)

Is the non-demangling intentional?  If yes, I guess we'd need to adjust
the testcases to cope with both mangled and non-mangled names, otherwise
please change tsan to demangle, the symbolizer has functions for it.

2013-12-06  Jakub Jelinek  <jakub@redhat.com>

	* elf.c (ET_DYN): Undefine and define again.
	(elf_add): Add exe argument, if true and ehdr.e_type is ET_DYN,
	return early -1 without closing the descriptor.
	(struct phdr_data): Add exe_descriptor.
	(phdr_callback): If pd->exe_descriptor is not -1, for very first
	call if dlpi_name is NULL just call elf_add with the exe_descriptor,
	otherwise backtrace_close the exe_descriptor if not -1.  Adjust
	call to elf_add.
	(backtrace_initialize): Adjust call to elf_add.  If it returns
	-1, set pd.exe_descriptor to descriptor, otherwise set it to -1.


	Jakub
Dmitriy Vyukov - Dec. 6, 2013, 8:19 a.m.
On Fri, Dec 6, 2013 at 11:50 AM, Jakub Jelinek <jakub@redhat.com> wrote:
> Hi!
>
> With the new tsan tests, I've noticed that libbacktrace symbolization
> doesn't work when the binary is a PIE.
> The problem is that in that case we obviously can't use base_address
> of 0, the PIE typically will not have 0 bias, that is actually the sole
> point of PIEs that their base address is randomized.  So, in that case
> we need to wait until dl_iterate_phdr to determine the proper bias.
> The executable is always the first in the search scope and/or list of
> loaded modules, so we just check if the first callback gives us a dlpi_name
> == NULL entry and assume it is the PIE.
>
> Regtested on x86_64-linux, ok for trunk?
>
> The rest of this mail except the libbacktrace patch is for the sanitizer
> folks.
>
> With this patch check-gcc tsan.exp passes fully again, but check-g++ does
> not, there seems to be important differences between tsan symbolization
> and say asan symbolization.  First of all, if symbolizer fails (as without
> this patch for tsan because the executables are PIEs), we get something
> like:
> WARNING: ThreadSanitizer: data race (pid=18998)
>   Write of size 4 at 0x7f4a88f0a08c by thread T1:
>     #0 <null> <null>:0 (tiny_race+0x000000000b19)
>
>   Previous write of size 4 at 0x7f4a88f0a08c by main thread:
>     #0 <null> <null>:0 (tiny_race+0x000000000b7a)
>     #1 __libc_start_main <null>:0 (libc.so.6+0x003c08c21b74)
>
>   As if synchronized via sleep:
>     #0 sleep ../../../../libsanitizer/tsan/tsan_interceptors.cc:239 (libtsan.so.0+0x000000048c65)
>     #1 <null> <null>:0 (tiny_race+0x000000000b0a)
>
>   Location is global '<null>' of size 0 at 0x000000000000 (tiny_race+0x00000020208c)
>
>   Thread T1 (tid=19000, running) created by main thread at:
>     #0 pthread_create ../../../../libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x0000000461b3)
>     #1 <null> <null>:0 (tiny_race+0x000000000b6b)
>     #2 __libc_start_main <null>:0 (libc.so.6+0x003c08c21b74)
>
> The <null> <null>:0 or <null>:0 look very ugly, e.g. asan
> I think just prints the exe/dso name + offset in that case.


Tsan can not work w/o symbolization (because it relies on online
suppressions, etc). So there is little sense in making it prettier.


> And the reason why check-g++ tsan.exp fails even with this patch is
> that apparently tsan doesn't try to demangle the symbol names, so we get
> e.g.:

Demangling must be done by the symbolizer.
+samsonov for this


> FAIL: c-c++-common/tsan/atomic_stack.c  -O0  output pattern test, is ==================
> WARNING: ThreadSanitizer: data race (pid=22075)
>   Atomic write of size 4 at 0x7fe8c9b630ac by thread T1:
>     #0 __tsan_atomic32_fetch_add ../../../../libsanitizer/tsan/tsan_interface_atomic.cc:468 (libtsan.so.0+0x00000001ec7e)
>     #1 _Z7Thread1Pv /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:11 (atomic_stack.exe+0x000000000d90)
>
>   Previous write of size 4 at 0x7fe8c9b630ac by thread T2:
>     #0 _Z7Thread2Pv /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:16 (atomic_stack.exe+0x000000000dde)
>
>   As if synchronized via sleep:
>     #0 sleep ../../../../libsanitizer/tsan/tsan_interceptors.cc:239 (libtsan.so.0+0x000000048c65)
>     #1 _Z7Thread1Pv /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:10 (atomic_stack.exe+0x000000000d7a)
>
>   Location is global 'Global' of size 4 at 0x7fe8c9b630ac (atomic_stack.exe+0x0000002020ac)
>
>   Thread T1 (tid=22080, running) created by main thread at:
>     #0 pthread_create ../../../../libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x0000000461b3)
>     #1 main /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:22 (atomic_stack.exe+0x000000000e2a)
>
>   Thread T2 (tid=22081, finished) created by main thread at:
>     #0 pthread_create ../../../../libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x0000000461b3)
>     #1 main /usr/src/gcc/gcc/testsuite/c-c++-common/tsan/atomic_stack.c:23 (atomic_stack.exe+0x000000000e4b)
>
> Is the non-demangling intentional?  If yes, I guess we'd need to adjust
> the testcases to cope with both mangled and non-mangled names, otherwise
> please change tsan to demangle, the symbolizer has functions for it.
>
> 2013-12-06  Jakub Jelinek  <jakub@redhat.com>
>
>         * elf.c (ET_DYN): Undefine and define again.
>         (elf_add): Add exe argument, if true and ehdr.e_type is ET_DYN,
>         return early -1 without closing the descriptor.
>         (struct phdr_data): Add exe_descriptor.
>         (phdr_callback): If pd->exe_descriptor is not -1, for very first
>         call if dlpi_name is NULL just call elf_add with the exe_descriptor,
>         otherwise backtrace_close the exe_descriptor if not -1.  Adjust
>         call to elf_add.
>         (backtrace_initialize): Adjust call to elf_add.  If it returns
>         -1, set pd.exe_descriptor to descriptor, otherwise set it to -1.
>
> --- libbacktrace/elf.c.jj       2013-11-19 08:47:37.000000000 +0100
> +++ libbacktrace/elf.c  2013-12-06 08:26:53.273602062 +0100
> @@ -96,6 +96,7 @@ dl_iterate_phdr (int (*callback) (struct
>  #undef ELFDATA2LSB
>  #undef ELFDATA2MSB
>  #undef EV_CURRENT
> +#undef ET_DYN
>  #undef SHN_LORESERVE
>  #undef SHN_XINDEX
>  #undef SHN_UNDEF
> @@ -171,6 +172,8 @@ typedef struct {
>
>  #define EV_CURRENT 1
>
> +#define ET_DYN 3
> +
>  typedef struct {
>    b_elf_word   sh_name;                /* Section name, index in string tbl */
>    b_elf_word   sh_type;                /* Type of section */
> @@ -512,7 +515,7 @@ elf_syminfo (struct backtrace_state *sta
>  static int
>  elf_add (struct backtrace_state *state, int descriptor, uintptr_t base_address,
>          backtrace_error_callback error_callback, void *data,
> -        fileline *fileline_fn, int *found_sym, int *found_dwarf)
> +        fileline *fileline_fn, int *found_sym, int *found_dwarf, int exe)
>  {
>    struct backtrace_view ehdr_view;
>    b_elf_ehdr ehdr;
> @@ -591,6 +594,12 @@ elf_add (struct backtrace_state *state,
>        goto fail;
>      }
>
> +  /* If the executable is ET_DYN, it is either a PIE, or we are running
> +     directly a shared library with .interp.  We need to wait for
> +     dl_iterate_phdr in that case to determine the actual base_address.  */
> +  if (exe && ehdr.e_type == ET_DYN)
> +    return -1;
> +
>    shoff = ehdr.e_shoff;
>    shnum = ehdr.e_shnum;
>    shstrndx = ehdr.e_shstrndx;
> @@ -847,6 +856,7 @@ struct phdr_data
>    fileline *fileline_fn;
>    int *found_sym;
>    int *found_dwarf;
> +  int exe_descriptor;
>  };
>
>  /* Callback passed to dl_iterate_phdr.  Load debug info from shared
> @@ -862,17 +872,32 @@ phdr_callback (struct dl_phdr_info *info
>    fileline elf_fileline_fn;
>    int found_dwarf;
>
> -  /* There is not much we can do if we don't have the module name.  */
> +  /* There is not much we can do if we don't have the module name,
> +     unless executable is ET_DYN, where we expect the very first
> +     phdr_callback to be for the PIE.  */
>    if (info->dlpi_name == NULL || info->dlpi_name[0] == '\0')
> -    return 0;
> +    {
> +      if (pd->exe_descriptor == -1)
> +       return 0;
> +      descriptor = pd->exe_descriptor;
> +      pd->exe_descriptor = -1;
> +    }
> +  else
> +    {
> +      if (pd->exe_descriptor != -1)
> +       {
> +         backtrace_close (pd->exe_descriptor, pd->error_callback, pd->data);
> +         pd->exe_descriptor = -1;
> +       }
>
> -  descriptor = backtrace_open (info->dlpi_name, pd->error_callback, pd->data,
> -                              &does_not_exist);
> -  if (descriptor < 0)
> -    return 0;
> +      descriptor = backtrace_open (info->dlpi_name, pd->error_callback,
> +                                  pd->data, &does_not_exist);
> +      if (descriptor < 0)
> +       return 0;
> +    }
>
>    if (elf_add (pd->state, descriptor, info->dlpi_addr, pd->error_callback,
> -              pd->data, &elf_fileline_fn, pd->found_sym, &found_dwarf))
> +              pd->data, &elf_fileline_fn, pd->found_sym, &found_dwarf, 0))
>      {
>        if (found_dwarf)
>         {
> @@ -893,13 +918,15 @@ backtrace_initialize (struct backtrace_s
>                       backtrace_error_callback error_callback,
>                       void *data, fileline *fileline_fn)
>  {
> +  int ret;
>    int found_sym;
>    int found_dwarf;
>    fileline elf_fileline_fn;
>    struct phdr_data pd;
>
> -  if (!elf_add (state, descriptor, 0, error_callback, data, &elf_fileline_fn,
> -               &found_sym, &found_dwarf))
> +  ret = elf_add (state, descriptor, 0, error_callback, data, &elf_fileline_fn,
> +                &found_sym, &found_dwarf, 1);
> +  if (!ret)
>      return 0;
>
>    pd.state = state;
> @@ -908,6 +935,7 @@ backtrace_initialize (struct backtrace_s
>    pd.fileline_fn = &elf_fileline_fn;
>    pd.found_sym = &found_sym;
>    pd.found_dwarf = &found_dwarf;
> +  pd.exe_descriptor = ret < 0 ? descriptor : -1;
>
>    dl_iterate_phdr (phdr_callback, (void *) &pd);
>
>
>         Jakub
Jakub Jelinek - Dec. 6, 2013, 8:25 a.m.
On Fri, Dec 06, 2013 at 12:19:45PM +0400, Dmitry Vyukov wrote:
> > And the reason why check-g++ tsan.exp fails even with this patch is
> > that apparently tsan doesn't try to demangle the symbol names, so we get
> > e.g.:
> 
> Demangling must be done by the symbolizer.
> +samsonov for this

So why does asan_report.cc have then:
static const char *MaybeDemangleGlobalName(const char *name) {
  // We can spoil names of globals with C linkage, so use an heuristic
  // approach to check if the name should be demangled.
  return (name[0] == '_' && name[1] == 'Z')
             ? Symbolizer::Get()->Demangle(name)
             : name;
}
and uses it where it wants to demangle?  From what I can see, even when
you are using llvm-symbolizer, sanitizer_common doesn't pass -demangle
option to it.

	Jakub
Dmitriy Vyukov - Dec. 6, 2013, 8:51 a.m.
On Fri, Dec 6, 2013 at 12:25 PM, Jakub Jelinek <jakub@redhat.com> wrote:
> On Fri, Dec 06, 2013 at 12:19:45PM +0400, Dmitry Vyukov wrote:
>> > And the reason why check-g++ tsan.exp fails even with this patch is
>> > that apparently tsan doesn't try to demangle the symbol names, so we get
>> > e.g.:
>>
>> Demangling must be done by the symbolizer.
>> +samsonov for this
>
> So why does asan_report.cc have then:
> static const char *MaybeDemangleGlobalName(const char *name) {
>   // We can spoil names of globals with C linkage, so use an heuristic
>   // approach to check if the name should be demangled.
>   return (name[0] == '_' && name[1] == 'Z')
>              ? Symbolizer::Get()->Demangle(name)
>              : name;
> }
> and uses it where it wants to demangle?

I would say this is a defect. It's symbolizer responsibility.

> From what I can see, even when
> you are using llvm-symbolizer, sanitizer_common doesn't pass -demangle
> option to it.


It defaults to true:
static cl::opt<bool>
ClDemangle("demangle", cl::init(true), cl::desc("Demangle function names"));
Alexey Samsonov - Dec. 6, 2013, 8:53 a.m.
(now in plain-text mode).

ASan calls this function only for global variables (not functions). It
is needed because
we use a different machinery to pass global names to ASan runtime - we
register globals
by a call from instrumented code, which also tells runtime the
(mangled) global name.

OTOH, in TSan we rely on symbolization to demangle names of global variables.

>
>  From what I can see, even when
> you are using llvm-symbolizer, sanitizer_common doesn't pass -demangle
> option to it.


-demangle defaults to "on" in llvm-symbolizer.


On Fri, Dec 6, 2013 at 12:51 PM, Dmitry Vyukov <dvyukov@google.com> wrote:
> On Fri, Dec 6, 2013 at 12:25 PM, Jakub Jelinek <jakub@redhat.com> wrote:
>> On Fri, Dec 06, 2013 at 12:19:45PM +0400, Dmitry Vyukov wrote:
>>> > And the reason why check-g++ tsan.exp fails even with this patch is
>>> > that apparently tsan doesn't try to demangle the symbol names, so we get
>>> > e.g.:
>>>
>>> Demangling must be done by the symbolizer.
>>> +samsonov for this
>>
>> So why does asan_report.cc have then:
>> static const char *MaybeDemangleGlobalName(const char *name) {
>>   // We can spoil names of globals with C linkage, so use an heuristic
>>   // approach to check if the name should be demangled.
>>   return (name[0] == '_' && name[1] == 'Z')
>>              ? Symbolizer::Get()->Demangle(name)
>>              : name;
>> }
>> and uses it where it wants to demangle?
>
> I would say this is a defect. It's symbolizer responsibility.
>
>> From what I can see, even when
>> you are using llvm-symbolizer, sanitizer_common doesn't pass -demangle
>> option to it.
>
>
> It defaults to true:
> static cl::opt<bool>
> ClDemangle("demangle", cl::init(true), cl::desc("Demangle function names"));
Ian Taylor - Dec. 6, 2013, 2:36 p.m.
On Thu, Dec 5, 2013 at 11:50 PM, Jakub Jelinek <jakub@redhat.com> wrote:
>
> 2013-12-06  Jakub Jelinek  <jakub@redhat.com>
>
>         * elf.c (ET_DYN): Undefine and define again.
>         (elf_add): Add exe argument, if true and ehdr.e_type is ET_DYN,
>         return early -1 without closing the descriptor.
>         (struct phdr_data): Add exe_descriptor.
>         (phdr_callback): If pd->exe_descriptor is not -1, for very first
>         call if dlpi_name is NULL just call elf_add with the exe_descriptor,
>         otherwise backtrace_close the exe_descriptor if not -1.  Adjust
>         call to elf_add.
>         (backtrace_initialize): Adjust call to elf_add.  If it returns
>         -1, set pd.exe_descriptor to descriptor, otherwise set it to -1.

Please update the comment for elf_add to explain the return value.
This patch is OK with that change.

Thanks.

Ian

Patch

--- libbacktrace/elf.c.jj	2013-11-19 08:47:37.000000000 +0100
+++ libbacktrace/elf.c	2013-12-06 08:26:53.273602062 +0100
@@ -96,6 +96,7 @@  dl_iterate_phdr (int (*callback) (struct
 #undef ELFDATA2LSB
 #undef ELFDATA2MSB
 #undef EV_CURRENT
+#undef ET_DYN
 #undef SHN_LORESERVE
 #undef SHN_XINDEX
 #undef SHN_UNDEF
@@ -171,6 +172,8 @@  typedef struct {
 
 #define EV_CURRENT 1
 
+#define ET_DYN 3
+
 typedef struct {
   b_elf_word	sh_name;		/* Section name, index in string tbl */
   b_elf_word	sh_type;		/* Type of section */
@@ -512,7 +515,7 @@  elf_syminfo (struct backtrace_state *sta
 static int
 elf_add (struct backtrace_state *state, int descriptor, uintptr_t base_address,
 	 backtrace_error_callback error_callback, void *data,
-	 fileline *fileline_fn, int *found_sym, int *found_dwarf)
+	 fileline *fileline_fn, int *found_sym, int *found_dwarf, int exe)
 {
   struct backtrace_view ehdr_view;
   b_elf_ehdr ehdr;
@@ -591,6 +594,12 @@  elf_add (struct backtrace_state *state,
       goto fail;
     }
 
+  /* If the executable is ET_DYN, it is either a PIE, or we are running
+     directly a shared library with .interp.  We need to wait for
+     dl_iterate_phdr in that case to determine the actual base_address.  */
+  if (exe && ehdr.e_type == ET_DYN)
+    return -1;
+
   shoff = ehdr.e_shoff;
   shnum = ehdr.e_shnum;
   shstrndx = ehdr.e_shstrndx;
@@ -847,6 +856,7 @@  struct phdr_data
   fileline *fileline_fn;
   int *found_sym;
   int *found_dwarf;
+  int exe_descriptor;
 };
 
 /* Callback passed to dl_iterate_phdr.  Load debug info from shared
@@ -862,17 +872,32 @@  phdr_callback (struct dl_phdr_info *info
   fileline elf_fileline_fn;
   int found_dwarf;
 
-  /* There is not much we can do if we don't have the module name.  */
+  /* There is not much we can do if we don't have the module name,
+     unless executable is ET_DYN, where we expect the very first
+     phdr_callback to be for the PIE.  */
   if (info->dlpi_name == NULL || info->dlpi_name[0] == '\0')
-    return 0;
+    {
+      if (pd->exe_descriptor == -1)
+	return 0;
+      descriptor = pd->exe_descriptor;
+      pd->exe_descriptor = -1;
+    }
+  else
+    {
+      if (pd->exe_descriptor != -1)
+	{
+	  backtrace_close (pd->exe_descriptor, pd->error_callback, pd->data);
+	  pd->exe_descriptor = -1;
+	}
 
-  descriptor = backtrace_open (info->dlpi_name, pd->error_callback, pd->data,
-			       &does_not_exist);
-  if (descriptor < 0)
-    return 0;
+      descriptor = backtrace_open (info->dlpi_name, pd->error_callback,
+				   pd->data, &does_not_exist);
+      if (descriptor < 0)
+	return 0;
+    }
 
   if (elf_add (pd->state, descriptor, info->dlpi_addr, pd->error_callback,
-	       pd->data, &elf_fileline_fn, pd->found_sym, &found_dwarf))
+	       pd->data, &elf_fileline_fn, pd->found_sym, &found_dwarf, 0))
     {
       if (found_dwarf)
 	{
@@ -893,13 +918,15 @@  backtrace_initialize (struct backtrace_s
 		      backtrace_error_callback error_callback,
 		      void *data, fileline *fileline_fn)
 {
+  int ret;
   int found_sym;
   int found_dwarf;
   fileline elf_fileline_fn;
   struct phdr_data pd;
 
-  if (!elf_add (state, descriptor, 0, error_callback, data, &elf_fileline_fn,
-		&found_sym, &found_dwarf))
+  ret = elf_add (state, descriptor, 0, error_callback, data, &elf_fileline_fn,
+		 &found_sym, &found_dwarf, 1);
+  if (!ret)
     return 0;
 
   pd.state = state;
@@ -908,6 +935,7 @@  backtrace_initialize (struct backtrace_s
   pd.fileline_fn = &elf_fileline_fn;
   pd.found_sym = &found_sym;
   pd.found_dwarf = &found_dwarf;
+  pd.exe_descriptor = ret < 0 ? descriptor : -1;
 
   dl_iterate_phdr (phdr_callback, (void *) &pd);