Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to compile a statically linked executable with valac or gcc #1715

Closed
astavale opened this issue May 1, 2017 · 8 comments
Closed

Unable to compile a statically linked executable with valac or gcc #1715

astavale opened this issue May 1, 2017 · 8 comments

Comments

@astavale
Copy link
Contributor

astavale commented May 1, 2017

The intention was to run a simple test of cross-compilation with Meson. The idea was to create a statically linked executable compiled on Linux x86_64 that would run on an armv7 Android phone. The test was broken down in to two test outcomes:

  1. A statically linked executable for native that shows a statically linked executable can be built
  2. A statically linked executable for armv7 that tests cross compilation of statically linked executables

Unfortunately I am unable to figure out how to get outcome one, the statically linked native executable, to compile with Meson.

I have broken the problem down in to three examples:

  1. Compiling a Statically Linked Executable with valac
  2. Compiling a Statically Linked Executable with GCC
  3. Compiling a Statically Linked Executable with Meson

Compiling a Statically Linked Executable with valac

The Vala file is:

void main () {
	print ("Hello, World!");
}

Valac can also call the C compiler, so a single line valac command to build the binary is:
valac -X -static -X pthreads hello.vala

Using the command ldd hello reports:
not a dynamic executable
and using readelf --program-headers hello shows no DYNAMIC section:

Elf file type is EXEC (Executable file)
Entry point 0x400aa0
There are 6 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000015eb25 0x000000000015eb25  R E    200000
  LOAD           0x000000000015edd0 0x000000000075edd0 0x000000000075edd0
                 0x0000000000002194 0x0000000000008fe8  RW     200000
  NOTE           0x0000000000000190 0x0000000000400190 0x0000000000400190
                 0x0000000000000044 0x0000000000000044  R      4
  TLS            0x000000000015edd0 0x000000000075edd0 0x000000000075edd0
                 0x0000000000000070 0x00000000000000a8  R      8
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x000000000015edd0 0x000000000075edd0 0x000000000075edd0
                 0x0000000000000230 0x0000000000000230  R      1

So the output is what was requested - a statically linked binary.

Adding the -v switch to the valac command shows the C compiler command used:
cc -o '/home/hello' '/home/hello.vala.c' -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -lgobject-2.0 -lglib-2.0 '-static' '-pthread'

Compiling a Statically Linked Executable with GCC

The C file is:

#include <glib.h>

void main () {
	g_print ("Hello, World!\n");
}

This can be compiled with:
gcc -static hello.c $(pkg-config --cflags --libs --static glib-2.0) -o hello

Note: the order of the command arguments matters here because it is a statically linked executable. Dependant static libraries and C files must be placed before their dependencies. For more detail see:

The gcc command also produces a statically linked executable as expected. Using the command ldd hello reports:
not a dynamic executable
and using readelf --program-headers hello shows no DYNAMIC section:

Elf file type is EXEC (Executable file)
Entry point 0x400aa0
There are 6 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000015eac5 0x000000000015eac5  R E    200000
  LOAD           0x000000000015edd0 0x000000000075edd0 0x000000000075edd0
                 0x0000000000002194 0x0000000000008fe8  RW     200000
  NOTE           0x0000000000000190 0x0000000000400190 0x0000000000400190
                 0x0000000000000044 0x0000000000000044  R      4
  TLS            0x000000000015edd0 0x000000000075edd0 0x000000000075edd0
                 0x0000000000000070 0x00000000000000a8  R      8
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x000000000015edd0 0x000000000075edd0 0x000000000075edd0
                 0x0000000000000230 0x0000000000000230  R      1

Compiling a Statically Linked Executable with Meson

The meson.build file:

project( 'hello', 'vala', 'c' )
sources = 'hello.vala'
hello_deps = [ dependency('glib-2.0', static : true),
                dependency('gobject-2.0', static : true)
                ]
executable( 'hello',
            sources,
            dependencies: hello_deps,
            c_args: ['-static','-pthread','-v']
             )

A working binary is produced, but it is 9.8k in size, rather than the 1.6M for the statically linked executable. Using ldd hello reveals:

	linux-vdso.so.1 (0x00007ffed0f4a000)
	/usr/lib64/libv4l/v4l2convert.so (0x00007f1a99598000)
	libglib-2.0.so.0 => /lib64/libglib-2.0.so.0 (0x00007f1a9923e000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1a99020000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f1a98c5f000)
	libv4l2.so.0 => /lib64/libv4l2.so.0 (0x00007f1a98a4f000)
	/lib64/ld-linux-x86-64.so.2 (0x000055fe5efc3000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007f1a9884b000)
	libv4lconvert.so.0 => /lib64/libv4lconvert.so.0 (0x00007f1a985d1000)
	librt.so.1 => /lib64/librt.so.1 (0x00007f1a983c8000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f1a980c6000)
	libjpeg.so.62 => /lib64/libjpeg.so.62 (0x00007f1a97e6d000)

Clearly something is wrong, either an option is missing from the messon.build file or Meson itself can't currently compile statically linked executables.

The -v flag was passed as a C flag so the output from ninja also gives the GCC options:

[0/1] Regenerating build files
The Meson build system
Version: 0.41.0.dev1
Source dir: /home/03_static
Build dir: /home/03_static/build
Build type: native build
Project name: hello
Native c compiler: cc (gcc 5.3.1)
Native vala compiler: valac (valac 0.30.2)
Build machine cpu family: x86_64
Build machine cpu: x86_64
Build targets in project: 1
[1/2] Compiling c object 'hello@exe/hello@exe_hello.c.o'
Using built-in specs.
COLLECT_GCC=cc
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --disable-libgcj --with-isl --enable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 5.3.1 20160406 (Red Hat 5.3.1-6) (GCC) 
COLLECT_GCC_OPTIONS='-I' 'hello@exe' '-I' '.' '-I' '..' '-I' '/usr/include/glib-2.0' '-I' '/usr/lib64/glib-2.0/include' '-fdiagnostics-color=always' '-pipe' '-D' '_FILE_OFFSET_BITS=64' '-w' '-O0' '-g' '-static' '-pthread' '-v' '-MMD' '-MQ' 'hello@exe/hello@exe_hello.c.o' '-MF' 'hello@exe/hello@exe_hello.c.o.d' '-o' 'hello@exe/hello@exe_hello.c.o' '-c' '-mtune=generic' '-march=x86-64'
 /usr/libexec/gcc/x86_64-redhat-linux/5.3.1/cc1 -quiet -v -I hello@exe -I . -I .. -I /usr/include/glib-2.0 -I /usr/lib64/glib-2.0/include -MMD hello@exe/hello@exe_hello.c.d -MF hello@exe/hello@exe_hello.c.o.d -MQ hello@exe/hello@exe_hello.c.o -D_REENTRANT -D _FILE_OFFSET_BITS=64 hello@exe/hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase-strip hello@exe/hello@exe_hello.c.o -g -O0 -w -version -fdiagnostics-color=always -o - |
 as -v -W -I hello@exe -I . -I .. -I /usr/include/glib-2.0 -I /usr/lib64/glib-2.0/include --64 -o hello@exe/hello@exe_hello.c.o
GNU assembler version 2.25 (x86_64-redhat-linux) using BFD version version 2.25-17.fc23
GNU C11 (GCC) version 5.3.1 20160406 (Red Hat 5.3.1-6) (x86_64-redhat-linux)
	compiled by GNU C version 5.3.1 20160406 (Red Hat 5.3.1-6), GMP version 6.0.0, MPFR version 3.1.3, MPC version 1.0.2
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/5.3.1/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../../../x86_64-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
 hello@exe
 .
 ..
 /usr/include/glib-2.0
 /usr/lib64/glib-2.0/include
 /usr/lib/gcc/x86_64-redhat-linux/5.3.1/include
 /usr/local/include
 /usr/include
End of search list.
GNU C11 (GCC) version 5.3.1 20160406 (Red Hat 5.3.1-6) (x86_64-redhat-linux)
	compiled by GNU C version 5.3.1 20160406 (Red Hat 5.3.1-6), GMP version 6.0.0, MPFR version 3.1.3, MPC version 1.0.2
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 8e936f154214cabb844ca8511eff8faa
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/5.3.1/:/usr/libexec/gcc/x86_64-redhat-linux/5.3.1/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/5.3.1/:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/5.3.1/:/usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-I' 'hello@exe' '-I' '.' '-I' '..' '-I' '/usr/include/glib-2.0' '-I' '/usr/lib64/glib-2.0/include' '-fdiagnostics-color=always' '-pipe' '-D' '_FILE_OFFSET_BITS=64' '-w' '-O0' '-g' '-static' '-pthread' '-v' '-MMD' '-MQ' 'hello@exe/hello@exe_hello.c.o' '-MF' 'hello@exe/hello@exe_hello.c.o.d' '-o' 'hello@exe/hello@exe_hello.c.o' '-c' '-mtune=generic' '-march=x86-64'
[2/2] Linking target hello

The compile_commands.json file contains:

[
  {
    "directory": "/home/03_static/build",
    "command": "valac '--debug' '-d' 'hello@exe' '-C' '--pkg' 'glib-2.0' '--pkg' 'gobject-2.0' ../hello.gs",
    "file": "../hello.gs"
  },
  {
    "directory": "/home/03_static/build",
    "command": "cc  '-Ihello@exe' '-I.' '-I..' '-I/usr/include/glib-2.0' '-I/usr/lib64/glib-2.0/include' '-fdiagnostics-color=always' '-pipe' '-D_FILE_OFFSET_BITS=64' '-w' '-O0' '-g' '-static' '-pthread' '-v' '-MMD' '-MQ' 'hello@exe/hello@exe_hello.c.o' '-MF' 'hello@exe/hello@exe_hello.c.o.d' -o 'hello@exe/hello@exe_hello.c.o' -c 'hello@exe/hello.c'",
    "file": "hello@exe/hello.c"
  }
]

Both the gcc -v and compile_commands.json output do not show -lglib-2.0, which is output from pkg-config --cflags --libs --static glib-2.0. This may be related to #380 or there is a linker stage with ninja that doesn't produce any output.

From #1240 I assume people are producing statically linked executables, but issues #1104 and #1709 suggest this is not obvious or it does not work in everyone's case.

Any help appreciated...

@astavale
Copy link
Contributor Author

astavale commented May 2, 2017

The solution is to use the link_args keyword argument with executable () instead of c_args. For the Vala example the meson.build file is:

project( 'hello', 'vala', 'c' )
sources = 'hello.vala'
hello_deps = [ dependency('glib-2.0', static:true) ]
executable( 'hello',
            sources,
            dependencies: hello_deps,
            link_args: ['-static']
             )

The dependency () also needs the static: true keyword argument.

Using ninja -v helped to see the command for the linker stage.

@astavale astavale closed this as completed May 2, 2017
@arteymix
Copy link
Contributor

arteymix commented May 2, 2017

The link_args does nothing for static binary, they are just a concatenation of static objects. If you look into build.ninja, you'll see it's only calling ar.

What is preoccupying here is that dependency declared as static get linked dynamically. It appears that the pkg-config flags do not land at the right location (should be after the .o). I checked Meson code and it seems to only add --static to the call.

@astavale
Copy link
Contributor Author

astavale commented May 2, 2017

@arteymix:

The link_args does nothing for static binary, they are just a concatenation of static objects.

ldd hello shows not a dynamic executable and readelf --program-headers hello shows no DYNAMIC section. So I am pretty sure that link_args does produce a statically linked executable.

If you look into build.ninja, you'll see it's only calling ar.

ninja -v shows three stages:

  1. valac called to produce a C file
  2. cc called to produce an object file, the exact line I have is:
    cc '-Ihello@exe' '-I.' '-I..' '-I/usr/include/glib-2.0' '-I/usr/lib64/glib-2.0/include' '-fdiagnostics-color=always' '-pipe' '-D_FILE_OFFSET_BITS=64' '-w' '-O0' '-g' '-MMD' '-MQ' 'hello@exe/hello@exe_hello.c.o' '-MF' 'hello@exe/hello@exe_hello.c.o.d' -o 'hello@exe/hello@exe_hello.c.o' -c 'hello@exe/hello.c
  3. cc called to produce an executable, the exact line I have is:
    cc -o hello 'hello@exe/hello@exe_hello.c.o' '-Wl,--no-undefined' '-Wl,--as-needed' '-static' '-lglib-2.0' '-pthread'

It looks like it is using the C compiler to call whatever linker the C compiler uses. So the c_LINKER rule is being used in build.ninja, not the STATIC_LINKER as I think you are suggesting. I don't see that as a problem. A statically linked executable is still produced.

If you do readelf --all hello@exe/hello@exe_hello.c.o you will see there are no program headers, only section headers. The most informative section header being .symtab, which shows the symbols in that binary.

After stage 3 the executable has been produced and as you say this is a concatenation of static objects. That to my mind is the definition of a statically linked executable! If you look at readelf --all hello you will see there are now program headers and the symbol table has increased greatly with all the symbols from GLib. There is no DYNAMIC section in the program headers though.

So the three steps make sense to:

  1. Use vala to produce a C file
  2. Use C compiler to produce an ELF binary with only section headers
  3. Use C compiler to call linker to produce an ELF binary with added program headers

What is preoccupying here is that dependency declared as static get linked dynamically. It appears that the pkg-config flags do not land at the right location (should be after the .o). I checked Meson code and it seems to only add --static to the call.

What you seem to be suggesting is to generate the statically linked executable in two steps, rather than three. At the moment the pkg-config --static flags do land in the right place at stage 3. I don't see it as too much of a problem making it clear you want a statically linked executable by adding link_args so long as it is documented somewhere.

See also #1104 for comments on Windows and how that might impact on any changes. There is an interesting comment there:

I think we can implement part of this by adding a new option called default_runtime with shared and static

@andy5995
Copy link
Contributor

andy5995 commented Jan 9, 2022

I think we can implement part of this by adding a new option called default_runtime with shared and static

This seems like a good idea.

I was able to compile a statically linked binary with 'meson configure -Dc_link_args=-static'. First I tried using c_args; as @astavale noted above, usually -static can be added as an argument to gcc:

gcc -static hello.c $(pkg-config --cflags --libs --static glib-2.0) -o hello

But when I used '-Dc_args=-static', I just got a dynamically linked binary.

@eli-schwartz
Copy link
Member

eli-schwartz commented Jan 9, 2022

Your example gcc command does both compiling and linking in a single step. It's not really comparable.

The -static argument to GCC is a linker option, not a compile option. It doesn't affect *.o file generation, and GCC passes it to the linker (ld).

@andy5995
Copy link
Contributor

I see. What options can I give to meson configure that would build a binary program that normally links to ncurses shared libs, but instead build with the ncurses static library? For example, this program?

@eli-schwartz
Copy link
Member

dep_curses = dependency('curses')

If you always want to link statically, you can add the static: true kwarg.

#9603 proposed an option to make that a global configuration option tuneable.

Some.projects have homebrewed such an option like this:

dep_curses = dependency('curses', static: get_option('buildstatic'))

@andy5995
Copy link
Contributor

If you always want to link statically, you can add the static: true kwarg.

Normally, no. I wanted to build statically on my PC, then transfer the binary to a server I was shelled into.

I got some shared hosting yesterday, shelled in, cloned the rmw repo, but I couldn't build it because meson wasn't installed. Today I installed meson and ninja with pip3, but meson failed to complete reporting an outdated ninja version. Running 'ninja --version' threw errors about permissions (it was 1.10.2.3).

I have a satisfactory resolution for now though... I used an AppImage of rmw.

#9603 proposed an option to make that a global configuration option tuneable.

Seems like that would be what I'm looking for. Thanks @eli-schwartz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants