mesonbuild / meson

The Meson Build System
http://mesonbuild.com
Apache License 2.0
5.54k stars 1.61k forks source link

Add a binutils module #6063

Open dcbaker opened 4 years ago

dcbaker commented 4 years ago

I think having a module for dealing with the tools provided by gnu binutils (and alternate implementations like the llvm ones) would be pretty useful.

In my case I'm using objcopy, and being able to have some of the arguments about the host and build machine populated automatically would be pretty handy.

There's also #5995, and having some sort of binutils.configure_linker_script seems useful.

I'm planning to get around to this myself, but there are some other things I need to get to first, including fixing regressions in 0.50. The main purpose of this issue is to collect other ideas that should be added to this module and solicit feedback.

jpakkane commented 4 years ago

Which features are those? IIRC the name-prefixed versions have (most of) the settings already so specifying objcopy in your cross file's executable section makes find_program('objcopy') work without additional setup (granted by usage of it has been fairly simple).

dcbaker commented 4 years ago

in particular the --binary-architecture and --output arguments to objcopy. Having to have logic to figure out what goes there for each supported architecture is a pain. being able to just have something like:

o = binutils.objcopy(
  output : 'foo.o',
  input : 'input.file',
  target : 'host' | 'build' # target?
  args : ['some', 'other' args'],
)

would be much nicer than what I have now, which is:

if host_machine.system() == 'linux' # TODO: solaris, bsd, etc
  if host_machine.cpu_family() == 'x86_64'
    b_arch = 'i386:x86_64'
    b_type = 'elf64-x86-64'
  elif host_machine.cpu_fmaily() == 'x86'
    b_arch = 'i386'
    b_type = 'elf32-i386'
  endif
endif
# TODO: all the other things

p = find_program('objcopy')
o = custom_target(
  ...
  command : [
    p, '--input', 'binary', '--output', b_type, '--binary-architecture', b_arch, '@INPUT@', '@OUTPUT@'
  ]
)

At least on my system there is no i686-linux-gnu-objcopy, in fact, the only "cross" objcopy's I have are mingw ones, even my arm toolchains don't have their own objcopy, just the x86_64-linux-gnu one.

bwidawsk commented 4 years ago

Would there be some way to specify the binutils, like llvm vs. gnu?

jpakkane commented 4 years ago

For comparison, Debian does have an i686-linux-gnu-objcopy.

marc-hb commented 4 years ago

I believe Meson doesn't let the user invoke the assembler or the linker directly, correct? The only two options are

  1. to rely on the compiler front-end (e.g.: [binaries].c -Wl,--script ... -Wl,--etc ...), or
  2. to step out of Meson and do everything "manually" with a custom_target( command: "ld <flags>...", )

Meson is still relatively new to me, sorry if I missed something here.

While C and C++ are highly portable languages, assembly and linker code are very far from it (and will probably never be). Consider the EDK2 project for instance: it supports a large and diverse set of operating systems and toolchains. However for x86 assembly it supports only one assembler, which I think rules out option 1.

So I hope this "Add a binutils module" issue #6063 is among others about adding new (and of course optional) as and ld fields in cross files and corresponding logic behind them. Is it? https://mesonbuild.com/Cross-compilation.html#defining-the-environment

jpakkane commented 4 years ago

You can already do that. The binaries entry overrides all lookups via find_program, not just compilers. If you have a program called flibflob, you can override that to whatever you want by putting a declaration for flibflob in your cross file.

marc-hb commented 4 years ago

You can already do that.

Was this an answer to "Would there be some way to specify the binutils, like llvm vs. gnu" ?

marc-hb commented 4 years ago
  1. to step out of Meson and do all the linking "manually" with a custom_target()

This proved easier than I thought, not the least thanks tofake_lib.extract_all_objects(), see pseudo-code below. This lets me point Meson at a very specific linker [script] without forcing any specific compiler or assembler. Freedom from the (tool)chain! It's early days but seems to work for now; should I expect any issue or important feature(s) missing because I'm hiding from meson the true linking nature of bare_metal's generation? Is this a hack and if yes is there any more "mesonic" way to do this?

# This library is not used, this is just a convenient way to
# compile everything in one step.
fake_lib = static_library(...
   '1.c', '2.S', ... , 
    c_args: [ '-fno-pic', ....]
 )

# fake_lib also solves this circular dependency problem  \o/
# https://github.com/mesonbuild/meson/pull/6061#issuecomment-547138357
#
# '@' is not allowed in linker scripts;
# search and replace 'fakelib@sta' with 'fakelib?sta'
# https://github.com/mesonbuild/meson/pull/291
lib_dir = '?'.join(fake_lib.get_id().split('@'))
very_not_portable_linker_script = configure_file(...
   configuration : { 'OBJSDIR': lib_dir }
)

very_specific_linker = find_program('my-ld')

bare_metal = custom_target(...
   input: fake_lib.extract_all_objects(),
   # [2019-11-12 comment]
   # Ideally, linker scripts should be part of the "input:", however:
   # 1. prefixing files("myscript.lds")[n] with '--script=' doesn't seem
   #    possible: "error: can only concatenate str (not "File") to str"
   # 2. prefixing only _some_ of the @INPUTn@ and not the object files
   #    seems difficult too.
   # So let's pretend linker scripts are "depend_files:" not part of the
   # "command:" and then sneak them into the command: behind
   # meson's back.
   depend_files: very_not_portable_linker_script,
   command: [ very_specific_linker ] + [ '@INPUT@' ]
           + [ '--script=' + very_not_portable_linker_script ]
           + [ '-o', '@OUTPUT@' ]
   ...
)
marc-hb commented 4 years ago
  1. to rely on the compiler front-end (e.g.: [binaries].c -Wl,--script ... -Wl,--etc ...),

I tried this with XCode 11 (clang v10) and it was a disaster. At first sight -fuse-ld=/my/home/built/linker seems to work but in fact not because this LLVM instance insists on passing a few macOS-specific options like -macosx_version_min 123 or -lto_library something.dylib - even when appending and prepending -fno-lto to the clang command line!

Granted this is just one front-end example, however I suspect most low-level / bare metal development requires invoking the linker directly because of the fine level of control required. I mean I suspect the front-end layer of indirection typically assumes too much and that gets in the way. I'm of course not considering targets that depend on a single, vendor-specific toolchain. Who cares about those :-)

Curious how high is "bare-metal" on the list of Meson requirements/priorities?

Found these: https://reviews.llvm.org/D25932 "Unconditionally pass -lto_library to the linker on Darwin" https://lists.llvm.org/pipermail/llvm-dev/2018-February/121135.html "[llvm-dev] how to avoid linking with libLTO?" The clang -mlinker-version=... workaround suggested at https://lists.llvm.org/pipermail/llvm-dev/2018-January/120215.html does remove a couple ld64-specific flags. Then it just fails on the next ld64 flags

linux/scripts/link-vmlinux.sh invokes ${LD} directly.

jpakkane commented 4 years ago

Bare metal is important to us. There are in fact commercial products that ship with firmwares built with Meson (or, at least, this seems very strongly to be the case given certain bugs and discussions I have had with people, but sadly there is no public info I can point people to).

marc-hb commented 4 years ago

The binaries entry overrides all lookups via find_program, not just compilers. If you have a program called flibflob, you can override that to whatever you want by putting a declaration for flibflob in your cross file.

Thanks! However it's more subtle and took me some time to figure it out. While powerful, I think it's very unusual for a string in any language to be both a symbol and a literal!

# 'ld' is *both* a property name and a literal fallback. find_program()
# gives precedence to whatever is in the last --cross-file that defines
# ld=... When not found, it falls back on searching for 'ld' in the
# PATH. Definitions in other --cross-file earlier on the command line
# are totally ignored.
# https://mesonbuild.com/Reference-manual.html#find_program
my_ld = find_program('ld')
marc-h38 commented 4 years ago

While C and C++ are highly portable languages, linker scripts are very far from it ...

At first sight -fuse-ld=/my/home/built/linker seems to work

In theory, -fuse-ld= allows you to decouple the choice of the linker from the choice of the compiler. After a bit more research and practice, usage of -fuse-ld= seems rare and most of the time it's only to switch between linkers "close" to each other like bfd and gold (which are even part of the same package). That flexibility doesn't seem to get that much testing in practice.

@dcbaker of Add a way to select the dynamic linker #6207 fame, any "real-world" experience of -fuse-ld= to share? From what you've seen, how is -fuse-ld= typically used and what for?

marc-h38 commented 4 years ago

Seems related to Solving the LD issue #6442

jhgorse commented 1 year ago

When invoked by clang the linker will do what it thinks is correct. For native macos builds, it has macos things:

# builtin ld
% clang -Wl,--version -v
Apple clang version 14.0.0 (clang-1400.0.29.102)
Target: arm64-apple-darwin21.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
 "/Library/Developer/CommandLineTools/usr/bin/ld" -demangle -lto_library /Library/Developer/CommandLineTools/usr/lib/libLTO.dylib -dynamic -arch arm64 -platform_version macos 12.0.0 12.3 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -o a.out -L/usr/local/lib --version -lSystem /Library/Developer/CommandLineTools/usr/lib/clang/14.0.0/lib/darwin/libclang_rt.osx.a
ld: unknown option: --version
clang: error: linker command failed with exit code 1 (use -v to see invocation)

# homebrew clang
% /opt/homebrew/opt/llvm/bin/lld
lld is a generic driver.
Invoke ld.lld (Unix), ld64.lld (macOS), lld-link (Windows), wasm-ld (WebAssembly) instead

# homebrew ld.lld for unix builds
% clang -Wl,--version -fuse-ld=/opt/homebrew/opt/llvm/bin/ld.lld -v
Apple clang version 14.0.0 (clang-1400.0.29.102)
Target: arm64-apple-darwin21.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
 "/opt/homebrew/opt/llvm/bin/ld.lld" -demangle -lto_library /Library/Developer/CommandLineTools/usr/lib/libLTO.dylib -dynamic -arch arm64 -platform_version macos 12.0.0 12.3 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -o a.out -L/usr/local/lib --version -lSystem /Library/Developer/CommandLineTools/usr/lib/clang/14.0.0/lib/darwin/libclang_rt.osx.a
ld.lld: error: unknown argument '-dynamic', did you mean '-Bdynamic'
ld.lld: error: unknown argument '-arch'
ld.lld: error: unknown argument '-platform_version'
ld.lld: error: unknown argument '-syslibroot'
Homebrew LLD 15.0.6 (compatible with GNU linkers)
clang: error: linker command failed with exit code 1 (use -v to see invocation)

For cross builds, it removes the macos things. For example, --target=arm-linux-gnu -mfloat-abi=hard for 32 bit arm linux target. Which I am exploring here: https://github.com/mesonbuild/wrapdb/issues/830

% clang -Wl,--version -fuse-ld=/opt/homebrew/opt/llvm/bin/ld.lld --target=arm-linux-gnu -mfloat-abi=hard -v
Apple clang version 14.0.0 (clang-1400.0.29.102)
Target: arm-unknown-linux-gnu
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
 "/opt/homebrew/opt/llvm/bin/ld.lld" -EL -X --eh-frame-hdr -m armelf_linux_eabi -dynamic-linker /lib/ld-linux-armhf.so.3 -o a.out crt1.o crti.o crtbegin.o -L/usr/lib/../lib -L/Library/Developer/CommandLineTools/usr/bin/../lib -L/usr/lib --version -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed crtend.o crtn.o
Homebrew LLD 15.0.6 (compatible with GNU linkers)

Cheers, Joe