From 387ccce50140f2aa7a2b32b0b89d802ef04eaeb0 Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Sat, 11 Apr 2026 14:01:45 +0800 Subject: [PATCH 1/2] hal: Add empty rtapi_app_exit to enum component rtapi_app.h unconditionally declares EXPORT_SYMBOL for both rtapi_app_main and rtapi_app_exit, so every module is expected to provide both entry points. enum.c only implemented rtapi_app_main, which slipped past GNU ld but trips lld 17+'s default --no-undefined-version (LLVM D135402) when the generated version script references the missing symbol: ld.lld: error: version script assignment of 'global' to symbol 'rtapi_app_exit' failed: symbol not defined Provide an empty rtapi_app_exit so enum.c satisfies the contract. Builds clean with clang 19 / ld.lld 19 for a uspace configuration. Fixes #3191. --- src/hal/components/enum.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hal/components/enum.c b/src/hal/components/enum.c index 0e1a979782f..061d4271d20 100644 --- a/src/hal/components/enum.c +++ b/src/hal/components/enum.c @@ -184,6 +184,8 @@ int rtapi_app_main(void){ } +void rtapi_app_exit(void) {} + static void decode(void *v_inst, long period){ (void)period; int i; From d32fedcadee25f94bcfcf622dc7d87df31fc379d Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Sat, 11 Apr 2026 17:34:15 +0800 Subject: [PATCH 2/2] rtapi: Refuse to load uspace components missing rtapi_app_exit rtapi_app.h mandates that every component exports both rtapi_app_main and rtapi_app_exit, but do_load_cmd() only checked for main and do_unload_cmd() silently skipped the exit call when it was absent. That let enum.c slip into the tree without rtapi_app_exit for years (Fixes #3191), visible only once lld 17+'s default --no-undefined-version made it a hard link error. Check for rtapi_app_exit in do_load_cmd() alongside rtapi_app_main and refuse to load the component otherwise, so future omissions surface immediately at load time with a clear error message. While here, also correct the dlsym cast for rtapi_app_exit in both do_load_cmd() and do_unload_cmd() from int(*)(void) to void(*)(void) to match the component's actual declaration. The int version was copy-pasted from the rtapi_app_main lookup and while harmless in practice (the return value was never read) it misrepresents the function signature. --- src/rtapi/uspace_rtapi_app.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index 094c999c8b3..bda630254d0 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -289,6 +289,12 @@ static int do_load_cmd(const string& name, const vector& args) { modules.erase(name); return -1; } + if(!DLSYM(module, "rtapi_app_exit")) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: component is missing rtapi_app_exit\n", name.c_str()); + dlclose(module); + modules.erase(name); + return -1; + } int result; result = do_comp_args(module, args); @@ -320,7 +326,7 @@ static int do_unload_cmd(const string& name) { rtapi_print_msg(RTAPI_MSG_ERR, "%s: not loaded\n", name.c_str()); return -1; } else { - int (*stop)(void) = DLSYM(w, "rtapi_app_exit"); + void (*stop)(void) = DLSYM(w, "rtapi_app_exit"); if(stop) stop(); modules.erase(modules.find(name)); dlclose(w);