Repository navigation
libs: Keep the coverage of a loadable module with CONFIG_COVERAGE_MINI. - #20813
Merged
Merged
Conversation
casaroli
requested review from
anchao,
jerpelea,
simbit18 and
xiaoxiang781216
as code owners
October 19, 2026 13:15
|
casaroli
force-pushed
the
cmake-module-no-coverage
branch
from
October 19, 2026 22:32
ecaee95 to
d1212c8
Compare
casaroli
force-pushed
the
cmake-module-no-coverage
branch
from
October 20, 2026 22:27
d1212c8 to
c3f244b
Compare
With CONFIG_COVERAGE_MINI, __gcov_init() adds the gcov_info of each object file to a list, and __gcov_dump() and __gcov_reset() walk this list. A loadable module that is built with the coverage options adds its own gcov_info, which is in the memory of the module. After the module is unloaded, the list still points to this memory, and the next dump or reset reads freed memory. Add __gcov_unload(), which removes from the list each gcov_info in an address range. libelf_uninit() calls it for the memory of a module before it frees the memory. This is the path of dlclose(), rmmod() and the exit of a program that exec() loaded. struct module_s now always keeps the sizes of the text and the data, and binfmt saves them too. A mutex protects the list, because a module can load or unload while a dump runs. The unload does not dump the data of the module. The dump writes the data of all object files, and it can run in the context of a task that exits. To keep the data of a module, dump it while the module is loaded: with "gcov -d", or with a call to __gcov_dump() in the module. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Code that is built with the GCC coverage options calls __gcov_init() from a constructor and __gcov_exit() from a destructor, refers to __gcov_merge_add(), and calls __gcov_fork() in place of fork(). With CONFIG_COVERAGE_MINI the image has these functions, but the system symbol table does not export them, so an instrumented module does not load: "Exported symbol "__gcov_init" not found". Declare them in gcov.h and add them to libc.csv, together with __gcov_dump() and __gcov_reset(), so that a module can also dump or reset the coverage data. The other coverage runtimes do not change: libgcov of CONFIG_COVERAGE_TOOLCHAIN keeps its functions hidden, and the clang runtimes need other functions. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
A loadable module of a flat or protected build can use the coverage of CONFIG_COVERAGE_ALL with CONFIG_COVERAGE_MINI and GCC, and its data goes away when the module is unloaded. Say so, say how to keep the data, and say that the other runtimes do not export their functions. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
casaroli
force-pushed
the
cmake-module-no-coverage
branch
from
October 21, 2026 11:54
c3f244b to
c1d976f
Compare
xiaoxiang781216
approved these changes
Oct 21, 2026
acassis
approved these changes
Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CONFIG_COVERAGE_ALLadds-fprofile-arcs -ftest-coverage(clang:-fprofile-instr-generate -fcoverage-mapping) to the compile options. A loadable module of a flat or protected build takes them too: in CMake through the targets ofnuttx_add_application(), and in both build systems through crt0. The module then refers to__gcov_init(),__gcov_merge_add()and__gcov_exit(). The image has them, but the system symbol table does not export them, so no module loads:Exporting the functions is not enough.
__gcov_init()adds thegcov_infoof the module to the list of the runtime, and the list keeps it after the module is unloaded, so the next dump or reset reads freed memory. What a module can get depends on the runtime:CONFIG_COVERAGE_MINIwith GCC (libs/libbuiltin/libgcc/gcov.c): modules keep their coverage.__gcov_unload()removes from the list eachgcov_infoin the memory of a module, andlibelf_uninit()calls it before it frees the module (dlclose(),rmmod(), the exit of anexec()program). A mutex protects the list.libc.csvexports__gcov_init,__gcov_merge_add,__gcov_exit,__gcov_fork,__gcov_dumpand__gcov_reset.CONFIG_COVERAGE_TOOLCHAIN(libgcov),CONFIG_COVERAGE_COMPILER_RTand clang withCONFIG_COVERAGE_MINI: not changed here, as agreed in the review. libgcov keeps its functions hidden, has no call to remove onegcov_info, and the__gcov_exit()of a module would dump and unlink the data of the whole image; the clang runtimes find their data through the linker sections of the image. A module built with the coverage options still does not load with these runtimes; a later PR can handle them.The unload does not dump the data of the module: the dump writes the data of all object files and can run in the context of an exiting task. To keep the data of a module, dump it while the module is loaded (
gcov -d <path>, or__gcov_dump()in the module). The documentation commit describes both behaviours inDocumentation/os/libs/libbuiltin.rst.Impact
CONFIG_COVERAGE_MINIwith GCC and loadable modules: the modules load and have coverage data.struct module_salways keepstextsizeanddatasize(twosize_t, before only with the module procfs).CONFIG_COVERAGE_MINI,__gcov_unload()is an empty macro.Testing
qemu-armv8a:nsh(flat, Arm GNU 14.2, QEMU HVF) withCONFIG_COVERAGE_ALL,CONFIG_COVERAGE_MINI,CONFIG_ELF,CONFIG_LIBC_DLFCN,CONFIG_EXECFUNCS_SYSTEM_SYMTAB,CONFIG_MM_FILL_ALLOCATIONS(freed memory is overwritten) andCONFIG_NAME_MAX=255(for the long.gcdanames).helloand two test programs as instrumented modules from a 9p share;get_environ_ptrexported for the test as in #20807:hello,gcovt(crt0.o has 9 gcov symbols, the modules refer to__gcov_init)gcovt 9 0 dump(the module calls__gcov_dump()).gcdafiles, with the one ofgcovtgcovt 11 3 &, thengcov -dwhile it sleeps.gcdaofgcovt;gcovon the host: the loop line ran 12 times,odd++5 timesgcovt <n>+ 25 xhello, thengcov -r -dgcovdl 40: 40 xdlopen()/dlclose()of an instrumentedlibgcovso.so,__gcov_reset()after eachdlclose(),__gcov_dump()gcovdl: PASSostestExiting with status 0libelf_uninit()gcov_infoofhello)The CMake build of the same configuration: crt0 has the 9 gcov symbols,
gcovtloads 21 times and dumps its data.qemu-armv8a:knsh(kernel build, HVF):ostestexits with status 0, as doesosteston the defaultqemu-armv8a:nsh. The programs of the export (init,sh,hello,ostest,getprime,dd) are byte-identical to master, except the commit and build-date string inshandinit.This version (rebased on master d748a36),
qemu-armv8a:nshwithCONFIG_COVERAGE_ALL, apps master,helloas a loadable module from a 9p share:CONFIG_COVERAGE_MINIwith Make:helloand crt0 keep coverage,Hello, World!!; with CMake: the same symbols.CONFIG_COVERAGE_TOOLCHAIN:hellodoes not load, as on master.tools/checkpatch.sh -c -u -m -gpasses.