Is it possible to add additional options to the link command line generated by CMake as the result of some build action?
Specifically, I need to preserve some unreferenced symbols through a static library which implies something like --undefined=symbol
during linking. I cannot know these names ahead of time (at least, not without some giant table which is exactly what I am trying to avoid in the first place) but I can identify them based on their ELF section and name prefix. With nm
and a bit of parsing, I now have the list of symbols which needs to be provided on the link invocation. My only remaining problem is how to get them from the PRE_LINK custom command into the actual GCC linking incantation.
In general, is it possible to get CMake to adjust the invocation of one command based on the build-time output of another?
Additional Information
Some non-CMake things I have tried:
--whole-archive
: This works but I would prefer not to use it as this is an embedded project and I very much would like symbol exclusion for everything else that isn't in the named section.--whole-archive
: I was not able to get this working though I feel like it should. Ultimately, I only need the value of the symbol itself (these are pointers to driver init structures) but objcopy
required to also move the .data
section which I think would break linking.--retain-symbols-file
: If this didn't drop all other symbols, it would do exactly what I want.Unfortunately, I was not able to get this working as asked. Near as I can tell, CMake simply doesn't support variable modification while the actual build is running. If anyone knows better, please correct me.
That said, I was able to get a reasonable work-around. Posting it here in case it helps someone in the future.
In short, you can work around this as long as whatever it is you want to do can specify the information in an external file. Then, this external file can be declared at configure time since its name is known but its content won't be considered until link time. Finally, a custom command is injected to generate the file at the appropriate time.
Practically, this takes the following form in CMakeLists.txt
:
# Tell CMake about our additional linker script which will be generated in add_custom_command() below
target_link_options(${COMPONENT_TARGET} PUBLIC -T${CMAKE_CURRENT_BINARY_DIR}/driverinit.ld)
# Before linking, extract names of all .driver.init symbols so they can be fed to the linker for preservation when included into final ELF.
add_custom_command(TARGET ${COMPONENT_LIB}
COMMENT "Generating driverinit.ld..."
VERBATIM
PRE_LINK
COMMAND find . -name "*.c.obj" -exec ${CMAKE_NM} {} \;
| grep " __driverinit_[a-zA-Z0-9_]*$"
| cut "-d " -f3
| tr "\\n" " "
| xargs -I@ echo "EXTERN("@")" > driverinit.ld
)
This will, before final linking, find all object files for my current binary, extract all symbols which match __driverinit_.*
, clean them up to just a symbol name and output them to a linker script surrounded by EXTERN()
. The name of this linker script is fixed and can be fed to target_link_options()
ahead of time.
Note that, in my case, my project is for an ESP32 using their ESP-IDF build environment. In order to preserve the appropriate sections in the final output binary, I also had to include the below (this is not necessary if you control the build system).
CMakeLists.txt:
idf_component_register(
...
LDFRAGMENTS "${CMAKE_SOURCE_DIR}/drivers.lf")
drivers.lf:
# Declare a new fragment section, which will expand to all .driver.init* sections defined in component's C files
[sections:driver_init]
entries:
.driver.init+
# Define a scheme, named driver_default, to specify which physical regions the given sections should be placed in
[scheme:driver_default]
entries:
driver_init -> iram0_text
# Finally, specify which parts of what components will use newly created scheme
[mapping:driver_default]
archive: * # Match all archives
entries:
# All objests in archive will use "driver_default" scheme
* (driver_default);
driver_init -> iram0_text SURROUND(driver_init) KEEP() # Add flags for the "driver_init -> iram0_text" entry in the "driver_default" scheme