From 9be33a3ebe3b72090a4f01e87271f27bf675165e Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Fri, 18 Sep 2026 01:11:14 +0200 Subject: [PATCH] Moved stdio wrapping to app-module app-module is the owning module for this feature --- Modules/app-module/CMakeLists.txt | 19 ++++++++++-- .../app-module/source/stdio_wrap.cpp | 14 ++++----- Modules/app-module/tests/CMakeLists.txt | 20 ++---------- Platforms/platform-posix/CMakeLists.txt | 9 ++++++ .../source/pthread_stack_wrap.c | 4 +-- Tactility/CMakeLists.txt | 31 ++----------------- .../app/fileselection/FileSelection.cpp | 2 +- .../Source/app/inputdialog/InputDialog.cpp | 2 +- Tactility/Tests/CMakeLists.txt | 16 ++-------- 9 files changed, 45 insertions(+), 72 deletions(-) rename Tactility/Source/AppStdioWrap.cpp => Modules/app-module/source/stdio_wrap.cpp (92%) diff --git a/Modules/app-module/CMakeLists.txt b/Modules/app-module/CMakeLists.txt index 91572260b..007bac6f3 100644 --- a/Modules/app-module/CMakeLists.txt +++ b/Modules/app-module/CMakeLists.txt @@ -14,11 +14,26 @@ tactility_add_module(app-module # Tells source/io.cpp its real-syscall fallback must go through __real_read/write/close() rather # than calling ::read/::write/::close() directly, on every platform where those are wrapped: via -# -Wl,--wrap= on ESP-IDF and non-Apple POSIX (see Tactility/CMakeLists.txt and the top-level -# CMakeLists.txt), or via AppStdioWrap.cpp's dyld interpose on Apple (whose linker lacks --wrap). +# -Wl,--wrap= on ESP-IDF (see the top-level CMakeLists.txt) and non-Apple POSIX (below), or via +# source/stdio_wrap.cpp's dyld interpose on Apple (whose linker lacks --wrap). tactility_get_module_name(app-module MODULE_NAME) target_compile_definitions(${MODULE_NAME} PRIVATE TT_APP_IO_WRAPS_STDIO) +# Routes every read()/write()/close() and printf-family call to source/stdio_wrap.cpp's __wrap_* +# functions; see that file for what they do and why. ESP-IDF wraps the same symbols itself (see +# the top-level CMakeLists.txt); Apple gets the same routing via dyld interpose instead, self- +# registered in that file. PUBLIC so every consumer of this module (Tactility, and any test target +# that links app-module directly) gets the flags too. +if (NOT ESP_PLATFORM AND NOT APPLE) + tactility_get_module_name(app-module MODULE_NAME) + target_link_options(${MODULE_NAME} PUBLIC + "-Wl,--wrap=read" "-Wl,--wrap=write" "-Wl,--wrap=close" + "-Wl,--wrap=printf" "-Wl,--wrap=fprintf" "-Wl,--wrap=vprintf" "-Wl,--wrap=vfprintf" + "-Wl,--wrap=puts" "-Wl,--wrap=fputs" "-Wl,--wrap=putchar" "-Wl,--wrap=fputc" + "-Wl,--wrap=getchar" "-Wl,--wrap=fgetc" "-Wl,--wrap=fgets" + ) +endif () + # install.cpp resolves an installed binary's fixed bin//.so path itself, so it # needs the same platform-arch string app-posix-module's own CMakeLists.txt defines for its # loader (the ESP32 equivalent, CONFIG_IDF_TARGET, comes for free from sdkconfig.h there). diff --git a/Tactility/Source/AppStdioWrap.cpp b/Modules/app-module/source/stdio_wrap.cpp similarity index 92% rename from Tactility/Source/AppStdioWrap.cpp rename to Modules/app-module/source/stdio_wrap.cpp index 6227698eb..9c326d2b6 100644 --- a/Tactility/Source/AppStdioWrap.cpp +++ b/Modules/app-module/source/stdio_wrap.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -// Paired with -Wl,--wrap=read/write/close on POSIX (Tactility/CMakeLists.txt) and ESP32 +// Paired with -Wl,--wrap=read/write/close on POSIX (this module's own CMakeLists.txt) and ESP32 // (top-level CMakeLists.txt); self-registered via dyld interpose below on Apple, whose linker // doesn't support --wrap. #include @@ -69,14 +69,14 @@ TT_DYLD_INTERPOSE(__wrap_close, close) // region glibc stdio wraps // // libc's printf/fprintf/etc are compiled into the C library and call an internal, non-exported -// write() alias - --wrap=write/the read/write/close interpose above can't reach that call, only -// calls WE make to the public symbol. These wraps instead redirect calls WE make to printf/ -// fprintf/etc, the same trick as read/write/close above. Newlib (ESP-IDF) doesn't have this gap - -// its stdio does call the wrappable syscall stubs - so this block is POSIX-only. +// write() alias, which --wrap=write/the read/write/close interpose above can't reach: only calls +// WE make to the public symbol. These wraps instead redirect calls WE make to printf/fprintf/etc, +// the same trick as read/write/close above. Newlib (ESP-IDF) doesn't have this gap: its stdio does +// call the wrappable syscall stubs, so this block is POSIX-only. // // Scoped to the printf/getc families only: fread/fwrite take an arbitrary FILE* and are already // used sitewide for real file I/O (e.g. File.cpp's readBinaryInternal), so wrapping them would -// route every such call through this file's stdin/stdout check - a correctness risk for unrelated +// route every such call through this file's stdin/stdout check, a correctness risk for unrelated // code that isn't worth taking here. putc/getc are excluded too since libc defines them as // macros, not real calls, so wrapping those symbols wouldn't reliably intercept them. @@ -99,7 +99,7 @@ char* __real_fgets(char* buffer, int size, FILE* stream); #ifdef __APPLE__ // --wrap synthesizes these automatically elsewhere; on Apple they're defined here via -// dlsym(RTLD_NEXT, ...) instead - see the read/write/close __real_* block above for why. +// dlsym(RTLD_NEXT, ...) instead. See the read/write/close __real_* block above for why. #include extern "C" { diff --git a/Modules/app-module/tests/CMakeLists.txt b/Modules/app-module/tests/CMakeLists.txt index 617333680..f445ec848 100644 --- a/Modules/app-module/tests/CMakeLists.txt +++ b/Modules/app-module/tests/CMakeLists.txt @@ -6,28 +6,12 @@ enable_language(C CXX ASM) file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/source/*.cpp) add_executable(AppModuleTests EXCLUDE_FROM_ALL ${TEST_SOURCES}) -# Provides __wrap_read/write/close: paired with the -Wl,--wrap= below on non-Apple platforms (see -# Tactility/CMakeLists.txt), or self-registered via dyld interpose on Apple (see the file itself). -target_sources(AppModuleTests PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../../../Tactility/Source/AppStdioWrap.cpp) - target_include_directories(AppModuleTests PRIVATE ${DOCTESTINC} ${CMAKE_CURRENT_LIST_DIR}/../private) add_test(NAME AppModuleTests COMMAND AppModuleTests) -# Matches app-module's own TT_APP_IO_WRAPS_STDIO (see Modules/app-module/CMakeLists.txt): io.cpp -# calls __real_read/write/close() on every POSIX platform, so any final link of it needs these -# wraps too, or those symbols go unresolved. The printf-family/getc-family flags are needed for -# the same reason: AppStdioWrap.cpp compiles those __wrap_* functions unconditionally on this -# platform, and they reference __real_vfprintf/fputs/fputc/fgetc/fgets. --wrap provides all of -# these __real_* symbols on non-Apple; AppStdioWrap.cpp provides its own on Apple (see that file). -if (NOT APPLE) - target_link_options(AppModuleTests PRIVATE - "-Wl,--wrap=read" "-Wl,--wrap=write" "-Wl,--wrap=close" - "-Wl,--wrap=printf" "-Wl,--wrap=fprintf" "-Wl,--wrap=vprintf" "-Wl,--wrap=vfprintf" - "-Wl,--wrap=puts" "-Wl,--wrap=fputs" "-Wl,--wrap=putchar" "-Wl,--wrap=fputc" - "-Wl,--wrap=getchar" "-Wl,--wrap=fgetc" "-Wl,--wrap=fgets" - ) -endif () +# No --wrap flags or stdio_wrap.cpp source needed here: linking app-module below already brings +# both along (see its own CMakeLists.txt). target_link_libraries(AppModuleTests PUBLIC TactilityKernel diff --git a/Platforms/platform-posix/CMakeLists.txt b/Platforms/platform-posix/CMakeLists.txt index 35f3136e3..4f6c929b7 100644 --- a/Platforms/platform-posix/CMakeLists.txt +++ b/Platforms/platform-posix/CMakeLists.txt @@ -6,3 +6,12 @@ add_library(platform-posix OBJECT) target_sources(platform-posix PRIVATE ${SOURCES}) #target_include_directories(platform-posix PUBLIC include/) target_link_libraries(platform-posix PUBLIC TactilityKernel) + +# Routes every pthread_attr_setstack() call to __wrap_pthread_attr_setstack() in source/pthread_stack_wrap.c, +# which no-ops it. see that file for why. +# --wrap is a GNU ld option; Apple's linker doesn't support it, so that file instead self-registers a dyld interpose +# for this symbol on Apple, needing no link flag. PUBLIC so every consumer of this OBJECT library +# (Tactility, and every test target that links platform-posix directly) gets it applied too. +if (NOT APPLE) + target_link_options(platform-posix PUBLIC "-Wl,--wrap=pthread_attr_setstack") +endif () diff --git a/Platforms/platform-posix/source/pthread_stack_wrap.c b/Platforms/platform-posix/source/pthread_stack_wrap.c index faf71146f..ce277b057 100644 --- a/Platforms/platform-posix/source/pthread_stack_wrap.c +++ b/Platforms/platform-posix/source/pthread_stack_wrap.c @@ -9,8 +9,8 @@ * No-op'ing the call instead leaves every task's pthread_attr_t at its pthread_attr_init() default, * so pthread_create() gives it a real, properly sized stack. * - * Linked via -Wl,--wrap=pthread_attr_setstack (Tactility/CMakeLists.txt) on non-Apple platforms; - * reused as a dyld interpose target below on Apple platforms, whose linker lacks --wrap. + * Linked via -Wl,--wrap=pthread_attr_setstack (this module's own CMakeLists.txt) on non-Apple + * platforms; reused as a dyld interpose target below on Apple platforms, whose linker lacks --wrap. */ int __wrap_pthread_attr_setstack(pthread_attr_t* attr, void* stackaddr, size_t stacksize) { (void)attr; diff --git a/Tactility/CMakeLists.txt b/Tactility/CMakeLists.txt index b175f2dff..3c17b699a 100644 --- a/Tactility/CMakeLists.txt +++ b/Tactility/CMakeLists.txt @@ -185,34 +185,9 @@ else () # Exports Tactility's own symbols (-rdynamic) so a dlopen()ed app-posix-module app can resolve # calls back into it - the OS-native equivalent of app-esp32-module's custom symbol resolver. set_target_properties(Tactility PROPERTIES ENABLE_EXPORTS ON) - # Routes every pthread_attr_setstack() call (FreeRTOS's POSIX port hands each task a stack - # carved out of its own heap through this) to __wrap_pthread_attr_setstack() in - # Platforms/platform-posix/source/pthread_stack_wrap.c, which no-ops it - see that file for why. - # --wrap is a GNU ld option; Apple's linker doesn't support it, so that file instead - # self-registers a dyld interpose for this symbol on Apple, needing no link flag here. - if (NOT APPLE) - target_link_options(Tactility PRIVATE "-Wl,--wrap=pthread_attr_setstack") - # Routes every read()/write()/close() call to Tactility/Source/AppStdioWrap.cpp's - # __wrap_read/write/close(), which forward into app_io_read/write/close() - the fd-table - # dispatch that lets an app's own stdio (e.g. a fileselection dialog's printf'd result - # path) reach an AppStream a parent bound via app_manager_start_with_streams(). Mirrors - # what ESP-IDF's build already does for the ESP32 target (see top-level CMakeLists.txt). - # On Apple, AppStdioWrap.cpp self-registers the same routing via dyld interpose instead. - target_link_options(Tactility PRIVATE "-Wl,--wrap=read" "-Wl,--wrap=write" "-Wl,--wrap=close") - # glibc's printf/fprintf/etc don't call the public write() symbol internally (they're - # already compiled into libc.so, out of --wrap's reach), so the read/write/close wrap - # above can't see them. Newlib (ESP-IDF) doesn't have this gap - its stdio does call the - # wrappable syscall stubs - so this block is POSIX-only. Wrapping these symbols instead - # redirects OUR OWN calls to them (the only ones --wrap can rewrite) through - # AppStdioWrap.cpp's __wrap_* functions, which check the target stream (stdout/stdin) and - # fall back to the real libc function for any other FILE*. Apple gets the same routing via - # dyld interpose, self-registered in that file - see its own #ifdef __APPLE__ block. - target_link_options(Tactility PRIVATE - "-Wl,--wrap=printf" "-Wl,--wrap=fprintf" "-Wl,--wrap=vprintf" "-Wl,--wrap=vfprintf" - "-Wl,--wrap=puts" "-Wl,--wrap=fputs" "-Wl,--wrap=putchar" "-Wl,--wrap=fputc" - "-Wl,--wrap=getchar" "-Wl,--wrap=fgetc" "-Wl,--wrap=fgets" - ) - endif () + # pthread_attr_setstack and read/write/close/printf-family aren't wrapped here: linking + # platform-posix and app-module already brings those wraps along (see their own + # CMakeLists.txt files). endif () # diff --git a/Tactility/Source/app/fileselection/FileSelection.cpp b/Tactility/Source/app/fileselection/FileSelection.cpp index 63a84228b..a771bb234 100644 --- a/Tactility/Source/app/fileselection/FileSelection.cpp +++ b/Tactility/Source/app/fileselection/FileSelection.cpp @@ -85,7 +85,7 @@ int32_t appMain(int argc, char* argv[]) { if (ctx.resultCode == 0) { // The parent captures this via an AppStream bound to our stdout (see startWithMode()) - - // see AppStdioWrap.cpp for how printf() itself gets routed there on POSIX. + // see Modules/app-module/source/stdio_wrap.cpp for how printf() itself gets routed there on POSIX. LOG_I(TAG, "Result: %s", ctx.resultPath.c_str()); printf("%s", ctx.resultPath.c_str()); } diff --git a/Tactility/Source/app/inputdialog/InputDialog.cpp b/Tactility/Source/app/inputdialog/InputDialog.cpp index f69c19f22..d9fd4efd7 100644 --- a/Tactility/Source/app/inputdialog/InputDialog.cpp +++ b/Tactility/Source/app/inputdialog/InputDialog.cpp @@ -138,7 +138,7 @@ int32_t appMain(int argc, char* argv[]) { if (ctx.resultCode == 0) { // The caller captures this via an AppStream bound to our stdout (see start()); see - // AppStdioWrap.cpp for how printf() itself gets routed there on POSIX. + // Modules/app-module/source/stdio_wrap.cpp for how printf() itself gets routed there on POSIX. printf("%s", ctx.resultText.c_str()); } return ctx.resultCode; diff --git a/Tactility/Tests/CMakeLists.txt b/Tactility/Tests/CMakeLists.txt index ac2c2e7f3..0f282e2a1 100644 --- a/Tactility/Tests/CMakeLists.txt +++ b/Tactility/Tests/CMakeLists.txt @@ -19,19 +19,9 @@ target_include_directories(TactilityTests PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../P add_test(NAME TactilityTests COMMAND TactilityTests) -# Matches Tactility/CMakeLists.txt's own set of --wrap flags: this binary also compiles -# AppStdioWrap.cpp and links app-module (whose io.cpp calls __real_read/write/close() on every -# POSIX platform - see TT_APP_IO_WRAPS_STDIO in Modules/app-module/CMakeLists.txt), so it needs -# the same wraps applied on non-Apple, or those go unresolved; AppStdioWrap.cpp provides its own -# on Apple via dyld interpose (see that file). -if (NOT APPLE) - target_link_options(TactilityTests PRIVATE - "-Wl,--wrap=pthread_attr_setstack" "-Wl,--wrap=read" "-Wl,--wrap=write" "-Wl,--wrap=close" - "-Wl,--wrap=printf" "-Wl,--wrap=fprintf" "-Wl,--wrap=vprintf" "-Wl,--wrap=vfprintf" - "-Wl,--wrap=puts" "-Wl,--wrap=fputs" "-Wl,--wrap=putchar" "-Wl,--wrap=fputc" - "-Wl,--wrap=getchar" "-Wl,--wrap=fgetc" "-Wl,--wrap=fgets" - ) -endif () +# No --wrap flags declared here: linking platform-posix and app-module below already brings all +# of pthread_attr_setstack/read/write/close/the printf family along (see their own CMakeLists.txt +# files). target_link_libraries(TactilityTests PRIVATE TactilityKernel