Bug 33103 - elfutils fails in-tree build due to ./stack binary #included via <stack>
Summary: elfutils fails in-tree build due to ./stack binary #included via <stack>
Status: RESOLVED FIXED
Alias: None
Product: elfutils
Classification: Unclassified
Component: general (show other bugs)
Version: unspecified
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2025-06-24 08:02 UTC by Sergei Trofimovich
Modified: 2025-06-26 20:09 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
Project(s) to access:
ssh public key:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Sergei Trofimovich 2025-06-24 08:02:07 UTC
https://sourceware.org/pipermail/elfutils-devel/2024q3/007281.html has a bit of prior discussion and even a has fix submitted (https://sourceware.org/git/?p=elfutils.git;a=commitdiff;h=b426c4db31e7c80d4262abdd845d2ece0c9a841c). Unfortunately the fix still fails in some scenarios: in-tree libc++ builds.

The simplest way to reproduce the build on `gcc` failure is to add `#include <stack>` into `src/srcfiles.cxx`:

--- a/src/srcfiles.cxx
+++ b/src/srcfiles.cxx
@@ -40,6 +40,7 @@
 #include <cassert>
 #include <gelf.h>
 #include <memory>
+#include <stack> /* simulate transitive include on libc++ */

 #ifdef ENABLE_LIBDEBUGINFOD
 #include "debuginfod.h"

Now we can fail the build on `gcc`:

$ autoreconf -ifv && ./configure --enable-maintainer-mode && make && make check
...
Making all in src
  CXX      srcfiles.o
In file included from srcfiles.cxx:43:
./stack:1:1: error: stray '\177' in program
    1 | <U+007F>ELF<U+0002><U+0001><U+0001><U+0000><

This happens because src/Makefile still contains `-I .`:

    srcdir = .
    ...
    AM_CPPFLAGS = -iquote . -I$(srcdir) ...

As a workaround nixpkgs now uses out-of-tree builds as:

        mkdir build-tree
        cd build-tree
        ../configure ...

But it would be nice to fix the collision for in-tree builds.

For libc++ build failure `<stack>` is included transitively via <iostream>:

In file included from srcfiles.cxx:50:
In file included from /nix/store/7mk6c0p1jvxm3vq2my5swi5jlidby1h7-libcxx-x86_64-unknown-linux-gnu-19.1.7-dev/include/c++/v1/iostream:43:
In file included from /nix/store/7mk6c0p1jvxm3vq2my5swi5jlidby1h7-libcxx-x86_64-unknown-linux-gnu-19.1.7-dev/include/c++/v1/istream:1367:
In file included from /nix/store/7mk6c0p1jvxm3vq2my5swi5jlidby1h7-libcxx-x86_64-unknown-linux-gnu-19.1.7-dev/include/c++/v1/ostream:194:
In file included from /nix/store/7mk6c0p1jvxm3vq2my5swi5jlidby1h7-libcxx-x86_64-unknown-linux-gnu-19.1.7-dev/include/c++/v1/format:246:
./stack:1:1: error: expected unqualified-id
    1 | <U+007F>ELF<U+0002><U+0001><U+0001>...
Comment 1 Mark Wielaard 2025-06-24 22:12:09 UTC
Do you know why libc++ does this transitive include of stack?
Is this an issue with libc++ for any standard header name because they might be indirectly included in libc++ headers?
Do you know why this doesn't seem to be a problem when using libstdc++?
Comment 2 Sergei Trofimovich 2025-06-25 05:28:57 UTC
I don't think c++ standard guarantees exact transitive headers included by other headers. I think the difference is the implementation detail of libstdc++ vs libc++.

libstdc++'s <format> does not include <stack> (and also does not get enabled without -std=c++20):
- https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/std/format;h=46bd5d5ee6a0e72520776ea21610e84ef5c6aca9;hb=HEAD#l45
- https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/std/ostream;h=3a0a0d35df1d69f620bfbe0e1d94a790904193f2;hb=HEAD#l44

libc++'s <format> does include it (and only does it before -std=c++20):
- https://github.com/llvm/llvm-project/blob/08b8d467d4253373e77a075c03e25281dee8ad15/libcxx/include/format#L252
- https://github.com/llvm/llvm-project/blob/08b8d467d4253373e77a075c03e25281dee8ad15/libcxx/include/ostream#L201

Both implementations slowly clean their header structure up going forward.
Comment 3 Mark Wielaard 2025-06-25 15:21:00 UTC
I don't understand why this is only a problem with srcdir == builddir.
But maybe we can extend the original solution to use -iquote for srcdir. And only have -I for lib and the top builddir (for <config.h>)?

Does the following work for you?

diff --git a/config/eu.am b/config/eu.am
index 5157a34ee552..1361044804f5 100644
--- a/config/eu.am
+++ b/config/eu.am
@@ -31,7 +31,7 @@
 ##
 
 DEFS = -D_GNU_SOURCE -DHAVE_CONFIG_H -DLOCALEDIR='"${localedir}"'
-AM_CPPFLAGS = -iquote . -I$(srcdir) -I$(top_srcdir)/lib -I..
+AM_CPPFLAGS = -iquote. -iquote$(srcdir) -I$(top_srcdir)/lib -I$(abs_top_builddir)
 
 # Drop the 'u' flag that automake adds by default. It is incompatible
 # with deterministic archives.
diff --git a/libdw/libdwP.h b/libdw/libdwP.h
index 25d53d31c7c8..b77db1423fe0 100644
--- a/libdw/libdwP.h
+++ b/libdw/libdwP.h
@@ -32,8 +32,8 @@
 #include <stdbool.h>
 #include <pthread.h>
 
-#include <libdw.h>
-#include <dwarf.h>
+#include "libdw.h"
+#include "dwarf.h"
 #include "eu-search.h"
Comment 4 Sergei Trofimovich 2025-06-25 20:59:54 UTC
(In reply to Mark Wielaard from comment #3)
> I don't understand why this is only a problem with srcdir == builddir.

AFAIU the difference in in-tree/out-of-tree is the following:

in-tree:

    - builder process is in src/
    - src/ contains freshly built 'stack' binary
    - clang is ran as 'clang++ -iquote . -I . srcfiles.cxx'
    - <stack> is looked up from '-I .' and finds ./stack ELF

out-of-tree:

    - builder process is in build-tree/src/
    - build-tree/src/ contains freshly built 'stack' binary
    - clang is ran as 'clang++ -iquote . -I ../../src srcfiles.cxx'
    - <stack> is looked up from '-I ../../src' and does not find ./stack (as it's in current ./stack build tree, source tree is clean)

> But maybe we can extend the original solution to use -iquote for srcdir. And
> only have -I for lib and the top builddir (for <config.h>)?
> 
> Does the following work for you?
> 
> diff --git a/config/eu.am b/config/eu.am
> index 5157a34ee552..1361044804f5 100644
> --- a/config/eu.am
> +++ b/config/eu.am
> @@ -31,7 +31,7 @@
>  ##
>  
>  DEFS = -D_GNU_SOURCE -DHAVE_CONFIG_H -DLOCALEDIR='"${localedir}"'
> -AM_CPPFLAGS = -iquote . -I$(srcdir) -I$(top_srcdir)/lib -I..
> +AM_CPPFLAGS = -iquote. -iquote$(srcdir) -I$(top_srcdir)/lib
> -I$(abs_top_builddir)
>  
>  # Drop the 'u' flag that automake adds by default. It is incompatible
>  # with deterministic archives.
> diff --git a/libdw/libdwP.h b/libdw/libdwP.h
> index 25d53d31c7c8..b77db1423fe0 100644
> --- a/libdw/libdwP.h
> +++ b/libdw/libdwP.h
> @@ -32,8 +32,8 @@
>  #include <stdbool.h>
>  #include <pthread.h>
>  
> -#include <libdw.h>
> -#include <dwarf.h>
> +#include "libdw.h"
> +#include "dwarf.h"
>  #include "eu-search.h"

Yes, I confirm that fixes build failure for my in-tree clang setup.
Comment 5 Mark Wielaard 2025-06-26 13:20:28 UTC
(In reply to Sergei Trofimovich from comment #4)
> Yes, I confirm that fixes build failure for my in-tree clang setup.

Thanks for testing, I committed that fix plus some comments to hopefully make debugging these issues easier next time.

commit 76bd5f6bea9bbeed68b1434455e4904d0fc22b04
Author: Mark Wielaard <mark@klomp.org>
Date:   Thu Jun 26 15:06:07 2025 +0200

    config: Adjust AM_CPPFLAGS for srcdir and .. path includes
    
    When building with clang and libc++ some standard headers might try
    including <stack> even when no source file requests such include
    directly. When building with srcdir == builddir this might clash in
    the src dir since we then build a stack binary there and the src dir
    also has srcfiles.cxx which might include some standard c++ headers.
    
    Work around this by removing -I.. from AM_CPPFLAGS and replacing it
    with -I(abs_top_builddir) where the <config.h> file can be found. And
    use -iquote for srcdir so it doesn't get included in the search path
    for the system <header> includes (only for "header" includes).
    
    Note that DEFAULT_INCLUDES might add . and srcdir back.  So
    DEFAULT_INCLUDES is disabled explicitly in src/Makefile.am (where the
    stack binary is build). We could also use the nostdinc automake option
    to completely suppress that, but that needs more auditing of various
    installed vs not-installed header files.
    
            * config/eu.am (AM_CPPFLAGS): Use -iquote for $(srcdir) and
            replace -I.. with -I$(abs_top_builddir).
            * libdw/libdwP.h: Include "libdw.h" and "dwarf.h" instead of
            the system headers <libdw.h> and <dwarf.h>.
    
    https://sourceware.org/bugzilla/show_bug.cgi?id=33103
    
    Signed-off-by: Mark Wielaard <mark@klomp.org>
Comment 6 Mark Wielaard 2025-06-26 20:09:58 UTC
Note that a followup commit was necessary to fix building one testcase that broke make check:

commit 65d383a7e653524388ff2ea382be3eac1d04061f (HEAD -> main)
Author: Mark Wielaard <mark@klomp.org>
Date:   Thu Jun 26 21:36:04 2025 +0200

    libdw: Add DEFAULT_INCLUDES to CHECK_DEF_FLAGS
    
    DEFAULT_INCLUDES includes -I. which is needed for compiling the
    testcases with -DMAIN_CHECK=1.
    
    This fixes make check after commit 76bd5f6bea9b "config: Adjust
    AM_CPPFLAGS for srcdir and .. path includes"
    
            * libdw/Makefile.am (CHECK_DEF_FLAGS): Add DEFAULT_INCLUDES.
    
    Signed-off-by: Mark Wielaard <mark@klomp.org>