From 68b242d52b2f701bf9f74c7a15bbe2e962f53e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Jos=C3=A9=20Garc=C3=ADa=20Garc=C3=ADa?= Date: Wed, 19 Aug 2026 23:58:24 +0200 Subject: [PATCH 1/5] Enable native ELF TLS in the GCC target compiler Local Exec / TPIDRURO is the model per vitasdk_native_tls_design.md phase 1; verified against a standalone gcc-base build that it emits STT_TLS/SHF_TLS/PT_TLS and R_ARM_TLS_LE32 instead of __emutls_*. Assisted-by: Claude Opus 5 --- cmake/recipes/GccCommonArgs.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/recipes/GccCommonArgs.cmake b/cmake/recipes/GccCommonArgs.cmake index 82e7c0a..910120e 100644 --- a/cmake/recipes/GccCommonArgs.cmake +++ b/cmake/recipes/GccCommonArgs.cmake @@ -36,7 +36,7 @@ set(common_gcc_configure_args --disable-libstdcxx-pch --disable-nls --disable-shared - --disable-tls + --enable-tls --with-gnu-as --with-gnu-ld --with-newlib From 397c6c87064e49262311154e0188d955825287b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Jos=C3=A9=20Garc=C3=ADa=20Garc=C3=ADa?= Date: Thu, 20 Aug 2026 15:40:51 +0200 Subject: [PATCH 2/5] binutils: patch the armvita emulation for Vita's TLS ABI Vita's TP points straight at .tdata, with none of the ARM EABI's 8-byte TCB header in front of it, and the loader expects .tdata/.tbss to share the same (read-only) segment as module_start. Two small patches, scoped to the armvita emulation only, teach the linker both. Assisted-by: Claude Opus 5 --- cmake/recipes/BuildHostDependencies.cmake | 4 +- .../binutils/0006-tls-in-rodata-segment.patch | 64 +++++++++++ .../0007-tls-tcb-size-per-emulation.patch | 100 ++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 patches/binutils/0006-tls-in-rodata-segment.patch create mode 100644 patches/binutils/0007-tls-tcb-size-per-emulation.patch diff --git a/cmake/recipes/BuildHostDependencies.cmake b/cmake/recipes/BuildHostDependencies.cmake index e9e5b74..365217d 100644 --- a/cmake/recipes/BuildHostDependencies.cmake +++ b/cmake/recipes/BuildHostDependencies.cmake @@ -15,7 +15,9 @@ function(toolchain_deps toolchain_deps_dir toolchain_install_dir toolchain_suffi "${PROJECT_SOURCE_DIR}/patches/binutils/0002-fix-broken-reloc.patch|1" "${PROJECT_SOURCE_DIR}/patches/binutils/0003-fix-elf-vaddr.patch|1" "${PROJECT_SOURCE_DIR}/patches/binutils/0004-fix-interworking-veneers.patch|1" - "${PROJECT_SOURCE_DIR}/patches/binutils/0005-genscripts-mkdir.patch|1") + "${PROJECT_SOURCE_DIR}/patches/binutils/0005-genscripts-mkdir.patch|1" + "${PROJECT_SOURCE_DIR}/patches/binutils/0006-tls-in-rodata-segment.patch|1" + "${PROJECT_SOURCE_DIR}/patches/binutils/0007-tls-tcb-size-per-emulation.patch|1") set(gdb_patch_series "${PROJECT_SOURCE_DIR}/patches/gdb.patch|1" "${PROJECT_SOURCE_DIR}/patches/gdb-zlib.patch|1" diff --git a/patches/binutils/0006-tls-in-rodata-segment.patch b/patches/binutils/0006-tls-in-rodata-segment.patch new file mode 100644 index 0000000..e092b59 --- /dev/null +++ b/patches/binutils/0006-tls-in-rodata-segment.patch @@ -0,0 +1,64 @@ +diff --git a/ld/scripttempl/elf.sc b/ld/scripttempl/elf.sc +--- a/ld/scripttempl/elf.sc ++++ b/ld/scripttempl/elf.sc +@@ -729,6 +729,27 @@ + ${TEXT_PLT+${PLT_NEXT_DATA+${PLT} ${OTHER_PLT_SECTIONS}}} + EOF + ++if [ -n "${TLS_IN_RODATA}" ]; then ++cat <.sh's TCB_SIZE. */ ++ int tcb_size; + }; + + void bfd_elf32_arm_set_target_params +diff --git a/bfd/elf32-arm.c b/bfd/elf32-arm.c +--- a/bfd/elf32-arm.c ++++ b/bfd/elf32-arm.c +@@ -3046,8 +3046,6 @@ + bool non_a8_stub; + }; + +-/* The size of the thread control block. */ +-#define TCB_SIZE 8 + + /* ARM-specific information about a PLT entry, over and above the usual + gotplt_union. */ +@@ -3385,6 +3383,12 @@ + /* True if the target system uses FDPIC. */ + int fdpic_p; + ++ /* Size of the reserved TCB ahead of the thread pointer for the Local ++ Exec TLS model - see struct elf32_arm_params::tcb_size. Defaults to ++ the standard ARM EABI value of 8; set from ld via ++ bfd_elf32_arm_set_target_params. */ ++ bfd_vma tcb_size; ++ + /* Fixup section. Used for FDPIC. */ + asection *srofixup; + }; +@@ -4057,6 +4061,7 @@ + ret->use_rel = true; + ret->obfd = abfd; + ret->fdpic_p = 0; ++ ret->tcb_size = 8; + + if (!bfd_hash_table_init (&ret->stub_hash_table, stub_hash_newfunc, + sizeof (struct elf32_arm_stub_hash_entry))) +@@ -9030,6 +9035,7 @@ + globals->fix_arm1176 = params->fix_arm1176; + globals->cmse_implib = params->cmse_implib; + globals->in_implib_bfd = params->in_implib_bfd; ++ globals->tcb_size = (bfd_vma) params->tcb_size; + + BFD_ASSERT (is_arm_elf (output_bfd)); + elf_arm_tdata (output_bfd)->no_enum_size_warning +@@ -9940,12 +9946,13 @@ + tpoff (struct bfd_link_info *info, bfd_vma address) + { + struct elf_link_hash_table *htab = elf_hash_table (info); ++ struct elf32_arm_link_hash_table *arm_htab = elf32_arm_hash_table (info); + bfd_vma base; + + /* If tls_sec is NULL, we should have signalled an error already. */ + if (htab->tls_sec == NULL) + return 0; +- base = align_power ((bfd_vma) TCB_SIZE, htab->tls_sec->alignment_power); ++ base = align_power (arm_htab->tcb_size, htab->tls_sec->alignment_power); + return address - htab->tls_sec->vma + base; + } + +diff --git a/ld/emultempl/armelf.em b/ld/emultempl/armelf.em +--- a/ld/emultempl/armelf.em ++++ b/ld/emultempl/armelf.em +@@ -46,7 +46,8 @@ + 1, /* fix_arm1176 */ + -1, /* merge_exidx_entries */ + 0, /* cmse_implib */ +- NULL /* in_implib_bfd */ ++ NULL, /* in_implib_bfd */ ++ ${TCB_SIZE-8} /* tcb_size */ + }; + static char *in_implib_filename = NULL; + +diff --git a/ld/emulparams/armvita.sh b/ld/emulparams/armvita.sh +--- a/ld/emulparams/armvita.sh ++++ b/ld/emulparams/armvita.sh +@@ -3,6 +3,11 @@ + TEXT_START_ADDR=0x81000000 + MAXPAGESIZE="0x10000" + COMMONPAGESIZE="0x10000" ++# Vita's TPIDRURO points directly at .tdata, no reserved TCB header ahead of ++# it (confirmed on real hardware: the kernel copies the TLS template to ++# TPIDRURO+0, not the ARM EABI "Variant 1" default of TPIDRURO+8). See ++# elf32_arm_params::tcb_size in bfd/elf32-arm.h. ++TCB_SIZE=0 + # module_info's tls_start is an offset relative to the same segment as the + # entry point (see vita-toolchain's sce-elf.c) - keep .tdata/.tbss in the + # read-only segment, alongside it, instead of the default writable one. From 4a090045c34fc3b4482d1ae66b8f85d2051dbc5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Jos=C3=A9=20Garc=C3=ADa=20Garc=C3=ADa?= Date: Thu, 27 Aug 2026 19:24:41 +0200 Subject: [PATCH 3/5] Build newlib with its reent in native TLS Pairs with the newlib side (struct _reent thread-local, kernel TLS slot 0x89 no longer holding it). Assisted-by: Claude Opus 5 --- cmake/recipes/BuildSdkComponents.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/recipes/BuildSdkComponents.cmake b/cmake/recipes/BuildSdkComponents.cmake index 84d0d83..98bea63 100644 --- a/cmake/recipes/BuildSdkComponents.cmake +++ b/cmake/recipes/BuildSdkComponents.cmake @@ -47,6 +47,7 @@ ExternalProject_Add(newlib # Multibyte/UTF-8: sin esto setlocale queda clavado en US-ASCII y toda la # familia mbrtowc/wcrtomb es inoperante (descubierto con musl libc-test) --enable-newlib-mb + --enable-newlib-reent-thread-local BUILD_COMMAND ${compiler_flags} ${toolchain_tools} ${wrapper_command} $(MAKE) INSTALL_COMMAND $(MAKE) install DESTDIR=${CMAKE_INSTALL_PREFIX} # Save the commit id for tracking purposes From 489488bbc3f31f5fc0ab99ec8b186beb63da62e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Jos=C3=A9=20Garc=C3=ADa=20Garc=C3=ADa?= Date: Thu, 27 Aug 2026 21:48:27 +0200 Subject: [PATCH 4/5] gcc: default the Vita target to local-exec TLS Design phase 1 says the main executable uses Local Exec, but nothing enforced it: for a TLS symbol defined in another translation unit gcc falls back to initial-exec, which wants a GOT entry the Vita loader never fills. Linking a newlib with its reent in TLS produced 36 R_ARM_TLS_IE32 against _tls_errno plus 15 R_ARM_TLS_LDO32, and vita-elf-create refused them. CC1_SPEC only supplies the default, so -ftls-model= on the command line still wins. Assisted-by: Claude Opus 5 --- patches/gcc/0001-vita-target.patch | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/patches/gcc/0001-vita-target.patch b/patches/gcc/0001-vita-target.patch index 7809936..b49f43d 100644 --- a/patches/gcc/0001-vita-target.patch +++ b/patches/gcc/0001-vita-target.patch @@ -15,7 +15,19 @@ diff --git a/gcc/config/arm/arm.h b/gcc/config/arm/arm.h index 30e1d6dc9..bd5f7b19c 100644 --- a/gcc/config/arm/arm.h +++ b/gcc/config/arm/arm.h -@@ -731,6 +731,10 @@ extern const int arm_arch_cde_coproc_bits[]; +@@ -87,8 +87,10 @@ extern tree arm_bf16_ptr_type_node; + #undef CPP_SPEC + #define CPP_SPEC "%(subtarget_cpp_spec)" + + #ifndef CC1_SPEC +-#define CC1_SPEC "" ++/* The loader hands each thread its TLS block at a fixed offset from ++ TPIDRURO; every model but local-exec wants a GOT it never fills. */ ++#define CC1_SPEC "%{!ftls-model=*:-ftls-model=local-exec}" + #endif + + /* This macro defines names of additional specifications to put in the specs +@@ -731,6 +733,10 @@ extern const int arm_arch_cde_coproc_bits[]; #define WCHAR_TYPE_SIZE BITS_PER_WORD #endif @@ -26,7 +38,7 @@ index 30e1d6dc9..bd5f7b19c 100644 /* Sized for fixed-point types. */ #define SHORT_FRACT_TYPE_SIZE 8 -@@ -1996,7 +2000,7 @@ enum arm_auto_incmodes +@@ -1996,7 +2002,7 @@ enum arm_auto_incmodes /* signed 'char' is most compatible, but RISC OS wants it unsigned. unsigned is probably best, but may break some code. */ #ifndef DEFAULT_SIGNED_CHAR From 0e31043f7ce8de422be98c67c9af560c28138cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Jos=C3=A9=20Garc=C3=ADa=20Garc=C3=ADa?= Date: Sun, 30 Aug 2026 13:38:22 +0200 Subject: [PATCH 5/5] gcc: let libstdc++ use newlib's __cxa_thread_atexit on the Vita target libstdc++ only defers thread_local destructor registration to libc when HAVE___CXA_THREAD_ATEXIT is defined at its configure time, and the --with-newlib branch never checks for the symbol: it hardcodes its feature list. Without the define, libsupc++ compiles its own fallback, which keeps the destructor list in a pthread key, only runs it when the prebuilt library sees gthreads as active (a weak pthread_cancel reference, satisfied only by a whole-archive libpthread), and never on a raw kernel thread. Newlib on Vita now provides __cxa_thread_atexit, running the destructors from the thread-exit path on every exit path, so define it for arm-vita-eabi in that branch. Patched in both configure.ac and the generated configure, since the build does not regenerate it. Assisted-by: Claude Fable 5 --- cmake/recipes/GccCommonArgs.cmake | 3 +- .../0005-libstdcxx-cxa-thread-atexit.patch | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 patches/gcc/0005-libstdcxx-cxa-thread-atexit.patch diff --git a/cmake/recipes/GccCommonArgs.cmake b/cmake/recipes/GccCommonArgs.cmake index 910120e..b193e63 100644 --- a/cmake/recipes/GccCommonArgs.cmake +++ b/cmake/recipes/GccCommonArgs.cmake @@ -13,7 +13,8 @@ set(gcc_patch_series "${PROJECT_SOURCE_DIR}/patches/gcc/0001-vita-target.patch|1" "${PROJECT_SOURCE_DIR}/patches/gcc/0002-vita-driver.patch|1" "${PROJECT_SOURCE_DIR}/patches/gcc/0003-libgomp-vita.patch|1" - "${PROJECT_SOURCE_DIR}/patches/gcc/0004-host-compat.patch|1") + "${PROJECT_SOURCE_DIR}/patches/gcc/0004-host-compat.patch|1" + "${PROJECT_SOURCE_DIR}/patches/gcc/0005-libstdcxx-cxa-thread-atexit.patch|1") list(JOIN gcc_patch_series "^" gcc_patch_series_arg) if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") # GCC on OSX (Clang in diguise) needs more bracket nesting depth to compile gcc diff --git a/patches/gcc/0005-libstdcxx-cxa-thread-atexit.patch b/patches/gcc/0005-libstdcxx-cxa-thread-atexit.patch new file mode 100644 index 0000000..259388d --- /dev/null +++ b/patches/gcc/0005-libstdcxx-cxa-thread-atexit.patch @@ -0,0 +1,29 @@ +--- a/libstdc++-v3/configure.ac ++++ b/libstdc++-v3/configure.ac +@@ -339,6 +339,11 @@ + # on a hosted environment. + if test "x${with_newlib}" = "xyes"; then + os_include_dir="os/newlib" ++ case "${host}" in ++ arm-vita-eabi*) ++ AC_DEFINE(HAVE___CXA_THREAD_ATEXIT) ++ ;; ++ esac + AC_DEFINE(HAVE_HYPOT) + + # GLIBCXX_CHECK_STDLIB_SUPPORT +--- a/libstdc++-v3/configure ++++ b/libstdc++-v3/configure +@@ -27343,6 +27343,12 @@ + # on a hosted environment. + if test "x${with_newlib}" = "xyes"; then + os_include_dir="os/newlib" ++ case "${host}" in ++ arm-vita-eabi*) ++ $as_echo "#define HAVE___CXA_THREAD_ATEXIT 1" >>confdefs.h ++ ++ ;; ++ esac + $as_echo "#define HAVE_HYPOT 1" >>confdefs.h + +