GNU Binutils 2.47 is out, released on Sunday July twenty sixth by chief maintainer Nick Clifton, and the change most people will actually feel is a new linker optimization level. Passing -O 0 to ld tells it to stop combining the contents of mergeable sections, which trades a slightly bigger binary for a faster link, and during a day of incremental rebuilds that is usually the trade you want. Alongside it come --start-lib and --end-lib for treating loose object files as archive members, a --debug-dir option in objdump and readelf, symbol annotation in two disassemblers, a pile of new RISC-V extensions, and one quiet deprecation worth checking.
The short answer
Nick Clifton announced GNU Binutils 2.47 on the info-gnu list on July twenty sixth. The headline for day to day work is a new linker optimization level, -O 0, which stops the BFD linker combining the contents of mergeable sections and buys link speed at the cost of a larger binary. The release also brings --start-lib and --end-lib to ld, --debug-dir to objdump and readelf, --map-global-vars to objdump, symbol annotation in the AArch64 and x86 disassemblers, several ratified RISC-V extensions, and the deprecation of the 32-bit s390 target.
Toolchain releases usually pass without anyone noticing, which is the point of them. This one has a couple of things worth five minutes, mostly because they change what you can put in a build script rather than what the compiler emits.
A linker level that goes the other way
Optimization levels normally ask a tool to work harder. The new -O 0 in the BFD linker asks it to work less.
What it turns off is the merging of mergeable sections. When the compiler emits a string literal or a read only constant, it can mark the section SHF_MERGE, which is a promise to the linker that identical entries are interchangeable and can be collapsed into one. The linker takes that promise and deduplicates. On a project with a lot of strings, and C++ projects have an enormous number of them, that pass is a real slice of link time.
Setting -O 0 skips it. You get a bigger output file, because the duplicates stay, and you get the link back faster. That is a straightforward win in the edit, build, run loop where you link dozens of times an hour and nobody cares how large the intermediate binary is, and it is a straightforwardly bad idea in a release build. The useful move is to wire it to the same switch your build system already uses to distinguish debug from release, so it applies automatically and nobody has to remember it.
Archive semantics without building an archive
--start-lib and --end-lib wrap a run of object files on the command line and tell the linker to treat them the way it treats members of a static archive. The difference matters: object files listed plainly are all linked in, while archive members are pulled in only when they resolve a symbol that is currently undefined.
Getting that selective behaviour used to mean actually creating a .a, running ar, and carrying the archive around as a build artifact. Now you can get it from the link line directly.
If this sounds familiar, it is because other linkers have offered these options for a long time and build systems have been written against them. Having them in GNU ld mostly removes a conditional from those build systems, which is a small thing that will quietly stop a lot of people from having to care which linker is in front of them.
Two related options, --link-mapless and --no-link-mapless, arrive in the same release for controlling how the linker handles input without a map.
Better answers out of objdump and readelf
Two additions here, both aimed at the moment when you are reading a binary and it will not tell you what you need.
--debug-dir=<DIR> in both objdump and readelf points the tool at a directory holding separate debug info files. Every distribution strips debug symbols into their own files these days and ships them separately, and the tools have historically looked in a path decided at build time. If your symbols are somewhere else, unpacked into a scratch directory or pulled out of a container layer, you can now say so instead of arranging the filesystem to match the tool's expectations.
--map-global-vars in objdump prints the locations and types of global variables. That is the sort of thing you can already extract by reading the symbol table and cross referencing the debug info by hand, which is to say it is the sort of thing nobody does at three in the morning.
The disassemblers also learned to annotate. On AArch64, -M annotate displays symbols for undefined instructions. On x86 and x86_64, -M annotate-immediates displays symbols associated with immediate values, which turns a screen of magic numbers into something you can read.
RISC-V, and one deprecation
The RISC-V additions follow the usual rhythm. Ratified extensions land in the assembler in batches, and 2.47 brings zalasr, svrsw60t59b, zvabd and smpmpmt at version 1.0, a group of zvq and zvf dot product extensions, and vendor extensions from SpacemiT including the xsmtvdot variants.
The deprecation is the 32-bit s390 target. The 64-bit s390x target is untouched and is what current mainframe Linux runs on, so the practical audience for this notice is very small. If you are in it, the time to say so is now rather than after the code is gone.
One structural note for packagers: 2.47 is an odd numbered release, and under the project's release policy odd numbered releases ship only the binutils tarball without the deprecated gold linker sources. If your packaging expects gold to be in the tree, that expectation needs updating.
Sources and further reading
- The 2.47 release announcement by Nick Clifton on info-gnu
- Phoronix: GNU Binutils 2.47 released with more RISC-V extensions, new options
- GNU Binutils project page
- Binutils releases on sourceware
Frequently asked questions
What does the new ld -O 0 level actually do?
It tells the BFD linker to stop combining the contents of mergeable sections. Those are the sections marked SHF_MERGE, typically string literals and read only constants, where the linker normally deduplicates identical entries so the same string is stored once instead of forty times. That deduplication costs time proportional to how much of it there is, and on a large C++ tree there is a great deal of it. Setting -O 0 skips the pass, so you link faster and ship a binary that carries the duplicates. It is a development time setting, not something to put in a release build.
Why would I want --start-lib and --end-lib?
They let you wrap a group of ordinary object files on the command line and have the linker treat them as if they were members of an archive, which means it only pulls in the ones that resolve an undefined symbol rather than linking all of them unconditionally. That is the behaviour you get from a .a file without having to build one. Other linkers have carried these options for years, so the practical effect in 2.47 is that build systems written against them no longer need a special case for GNU ld.
What is --debug-dir for?
It tells objdump and readelf where to look for separate debug info files. Distributions routinely strip debug symbols into their own files and ship them in a separate package, and until now the tools searched a compiled in path. If you have debug symbols unpacked somewhere non standard, in a container image layer or a build artifact directory, --debug-dir points the tool at them instead of leaving you with a stripped binary and no line numbers.
Which RISC-V extensions were added?
The release adds assembler and disassembler support for zalasr, svrsw60t59b, zvabd and smpmpmt at version 1.0, a set of zvq and zvf dot product extensions, and vendor extensions from SpacemiT including the xsmtvdot family. If you are not writing RISC-V assembly this changes nothing for you, but it is the usual pattern for the architecture: ratified extensions land in the toolchain in batches, and the assembler is generally the first place they show up.
Is anything being removed?
Support for the 32-bit s390 target is deprecated with this release. The 64-bit s390x target is unaffected and is what essentially all current mainframe Linux runs on, so this is a cleanup of a target with almost no remaining users rather than a change anyone should have to plan around. Worth noting only if you maintain something that still builds 31-bit or 32-bit s390 objects, in which case now is the moment to say so upstream.