diff --git a/.codespellrc b/.codespellrc index cf28f7d966377..e532eb60df46e 100644 --- a/.codespellrc +++ b/.codespellrc @@ -41,6 +41,7 @@ ignore-words-list = infor, inport, ist, + lief, lod, mot, mis, diff --git a/arch/sim/include/arch.h b/arch/sim/include/arch.h index d83f17fa1ce61..1634e63c4a0bb 100644 --- a/arch/sim/include/arch.h +++ b/arch/sim/include/arch.h @@ -27,4 +27,42 @@ #ifndef __ARCH_SIM_INCLUDE_ARCH_H #define __ARCH_SIM_INCLUDE_ARCH_H +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* _sinit and _einit mark the beginning and end of the C++ constructor + * array. They are mapped to platform-specific linker symbols: + * macOS: section$start$__DATA_CONST$__mod_init_func / + * section$end$__DATA_CONST$__mod_init_func (Mach-O auto) + * On macOS, the section type flags are patched post-link to prevent + * dyld from auto-running constructors before NuttX is initialized. + */ + +#ifdef CONFIG_HAVE_CXXINITIALIZE +# if defined(CONFIG_HOST_MACOS) +extern void (*_sinit[])(void) + __asm("section$start$__DATA_CONST$__mod_init_func"); +extern void (*_einit[])(void) + __asm("section$end$__DATA_CONST$__mod_init_func"); +# endif +#endif + +#undef EXTERN +#ifdef __cplusplus +} +#endif + #endif /* __ARCH_SIM_INCLUDE_ARCH_H */ diff --git a/arch/sim/src/Makefile b/arch/sim/src/Makefile index 78a46793b4016..3e5f82ed906aa 100644 --- a/arch/sim/src/Makefile +++ b/arch/sim/src/Makefile @@ -139,24 +139,15 @@ sim_hostfs.c: hostfs.h STDLIBS += -lpthread ifeq ($(CONFIG_HOST_MACOS),y) -ifeq ($(CONFIG_HAVE_CXXINITIALIZE),y) - # Note: sim_macho_init.c is not in CSRCS because it's picky about - # the place in the object list for linking. Namely, its constructor - # should be the first one in the executable. - HEADSRC = sim_macho_init.c - - # sim_macho_init.c is not compatible with chained fixups. - # cf. https://github.com/apache/nuttx/issues/15208 - ifeq ($(shell $(LD) -ld_classic -no_fixup_chains 2>&1 | grep "unknown option"),) + ifeq ($(CONFIG_HAVE_CXXINITIALIZE),y) LDLINKFLAGS += -ld_classic -no_fixup_chains LDFLAGS += -Wl,-ld_classic,-no_fixup_chains endif -endif -# Keep the simulator executable from exporting NuttX symbols. Otherwise dyld -# may resolve host libc references against NuttX's internal libc implementation -# during process initialization, which can crash before main() runs. -LDFLAGS += -Wl,-exported_symbol,__mh_execute_header + # Keep the simulator executable from exporting NuttX symbols. Otherwise dyld + # may resolve host libc references against NuttX's internal libc implementation + # during process initialization, which can crash before main() runs. + LDFLAGS += -Wl,-exported_symbol,__mh_execute_header else STDLIBS += -lrt endif @@ -517,6 +508,9 @@ else $(Q) $(call LINK_ALLSYMS_KASAN) $(Q) $(call LINK_ALLSYMS_KASAN) $(Q) $(call LINK_ALLSYMS_KASAN) +endif +ifeq ($(CONFIG_HOST_MACOS)$(CONFIG_HAVE_CXXINITIALIZE),yy) + $(Q) python3 $(ARCH_SRCDIR)/patch_macho_initsection.py $(TOPDIR)/$@ endif $(Q) $(NM) $(TOPDIR)/$@ | \ grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \ diff --git a/arch/sim/src/cmake/Toolchain.cmake b/arch/sim/src/cmake/Toolchain.cmake index e326dca663e54..01e11909ccccb 100644 --- a/arch/sim/src/cmake/Toolchain.cmake +++ b/arch/sim/src/cmake/Toolchain.cmake @@ -135,6 +135,9 @@ endif() if(CONFIG_COVERAGE_ALL) if(CONFIG_ARCH_TOOLCHAIN_GCC) add_compile_options(-fprofile-arcs -ftest-coverage -fno-inline) + if(APPLE) + add_link_options(-fprofile-arcs -ftest-coverage) + endif() elseif(CONFIG_ARCH_TOOLCHAIN_CLANG) add_compile_options(-fprofile-instr-generate -fcoverage-mapping) add_link_options(-fprofile-instr-generate) diff --git a/arch/sim/src/patch_macho_initsection.py b/arch/sim/src/patch_macho_initsection.py new file mode 100755 index 0000000000000..777a61c4ac023 --- /dev/null +++ b/arch/sim/src/patch_macho_initsection.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +############################################################################ +# arch/sim/src/patch_macho_initsection.py +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. +# +############################################################################ + +"""Patch Mach-O init section type flags to prevent dyld from +auto-running C++ constructors. + +Changes MOD_INIT_FUNC_POINTERS (0x9) section types to REGULAR (0x0) +so that dyld ignores them. NuttX will invoke the constructors +explicitly from lib_cxx_initialize(). + +Requires: pip install lief +""" + +import argparse +import subprocess +import sys + +try: + import lief +except ImportError: + print( + "Error: lief is required. Install with: pip install lief", + file=sys.stderr, + ) + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser(description="Patch Mach-O init section type flags") + parser.add_argument("binary", help="Path to the Mach-O binary") + args = parser.parse_args() + + binary = lief.MachO.parse(args.binary) + if binary is None: + print(f"Error: failed to parse {args.binary}", file=sys.stderr) + sys.exit(1) + + fat = binary.at(0) + T = lief.MachO.Section.TYPE + patched = 0 + for section in fat.sections: + if section.type == T.MOD_INIT_FUNC_POINTERS: + section.type = T.REGULAR + patched += 1 + + if patched: + fat.write(args.binary) + + # Re-sign the binary on macOS: writing the file invalidates the + # ad-hoc signature that ld attached at link time, which causes the + # kernel to SIGKILL the process at exec with + # "CODE SIGNING: rejecting invalid page ...". + if sys.platform == "darwin": + subprocess.run( + ["codesign", "--force", "--sign", "-", args.binary], + check=True, + ) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/arch/sim/src/sim/CMakeLists.txt b/arch/sim/src/sim/CMakeLists.txt index 47d1efd8888d9..491a8eeea3ef4 100644 --- a/arch/sim/src/sim/CMakeLists.txt +++ b/arch/sim/src/sim/CMakeLists.txt @@ -29,7 +29,9 @@ set(HOSTSRCS) set(HOST_INCLUDE_DIRS) set(STDLIBS pthread) -if(CONFIG_COVERAGE_TOOLCHAIN) +if(CONFIG_COVERAGE_TOOLCHAIN + AND CONFIG_ARCH_TOOLCHAIN_GCC + AND NOT APPLE) list(APPEND STDLIBS gcov) endif() @@ -150,14 +152,30 @@ list( sim_hosttime.c sim_hostuart.c) -# Note: sim_macho_init.c is picky about the place in the object list for -# linking. Namely, its constructor should be the first one in the executable. -# For now, we are just assuming no other files in HOSTSRCS provide constructors. if(CONFIG_HOST_MACOS) - if(CONFIG_HAVE_CXXINITIALIZE) - list(APPEND HOSTSRCS sim_macho_init.c) - target_link_options(nuttx PRIVATE -Wl,-ld_classic,-no_fixup_chains) - endif() + # Provide a sim/macOS specific override of clock_gettime() that translates + # Darwin clock IDs to NuttX's CLOCK_MONOTONIC. Keep all macOS specific logic + # in arch/sim/ instead of polluting sched/clock/clock_gettime.c. + list(APPEND SRCS macos/sim_clock_gettime.c) +endif() + +if(CONFIG_HOST_MACOS AND CONFIG_HAVE_CXXINITIALIZE) + # Keep classic __mod_init_func format so post-link lief patching works + target_link_options(nuttx PRIVATE -Wl,-ld_classic,-no_fixup_chains) + + # NOTE: add_custom_command(TARGET ...) can only be used in the directory where + # the target was created. The `nuttx` executable is created in the top-level + # CMakeLists.txt, so we cannot attach a POST_BUILD command to it here. Use a + # custom target hooked into `nuttx_post` instead. + add_custom_target( + sim_patch_macho_initsection ALL + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/../patch_macho_initsection.py + $ + DEPENDS nuttx + COMMENT "Patching Mach-O init section type flags") + add_dependencies(nuttx_post sim_patch_macho_initsection) endif() if(CONFIG_SIM_CAMERA_V4L2) diff --git a/arch/sim/src/sim/macos/sim_clock_gettime.c b/arch/sim/src/sim/macos/sim_clock_gettime.c new file mode 100644 index 0000000000000..eb7b7bd1293ec --- /dev/null +++ b/arch/sim/src/sim/macos/sim_clock_gettime.c @@ -0,0 +1,94 @@ +/**************************************************************************** + * arch/sim/src/sim/macos/sim_clock_gettime.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Rust code (and any other code) built for the macOS host uses Darwin's libc + * clock IDs. When such code is linked into the NuttX simulator, those + * Darwin-specific values reach NuttX's clock_gettime() implementation + * instead of Darwin's one. Translate them here so that the common + * sched/clock implementation does not need to know about Darwin clock IDs. + * + * _CLOCK_MONOTONIC == 6 (Darwin / XNU) + * _CLOCK_UPTIME_RAW == 8 (Darwin / XNU) + */ + +#define DARWIN_CLOCK_MONOTONIC 6 +#define DARWIN_CLOCK_UPTIME_RAW 8 + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: clock_gettime + * + * Description: + * sim/macOS specific override of clock_gettime() that translates Darwin + * clock IDs (CLOCK_MONOTONIC=6, CLOCK_UPTIME_RAW=8) into NuttX's + * CLOCK_MONOTONIC before delegating to the common nxclock_gettime() + * implementation. + * + * This override is required because Rust crates built for the macOS host + * call libc::clock_gettime() with Darwin's clock IDs. When the resulting + * object code is linked into the NuttX simulator, the call is resolved to + * NuttX's clock_gettime() instead of Darwin's one. + * + * The common POSIX clock_gettime() in sched/clock/clock_gettime.c is + * declared as a weak symbol, so this strong definition takes precedence + * when CONFIG_HOST_MACOS is enabled. + * + ****************************************************************************/ + +__attribute__((visibility("hidden"))) +int clock_gettime(clockid_t clock_id, struct timespec *tp) +{ + int ret; + + if (clock_id == DARWIN_CLOCK_MONOTONIC || + clock_id == DARWIN_CLOCK_UPTIME_RAW) + { + clock_id = CLOCK_MONOTONIC; + } + + ret = nxclock_gettime(clock_id, tp); + if (ret < 0) + { + set_errno(-ret); + return ERROR; + } + + return OK; +} diff --git a/arch/sim/src/sim/posix/sim_macho_init.c b/arch/sim/src/sim/posix/sim_macho_init.c deleted file mode 100644 index 28f1c3ad0c4cf..0000000000000 --- a/arch/sim/src/sim/posix/sim_macho_init.c +++ /dev/null @@ -1,135 +0,0 @@ -/**************************************************************************** - * arch/sim/src/sim/posix/sim_macho_init.c - * - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. The - * ASF licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - ****************************************************************************/ - -/**************************************************************************** - * Included Files - ****************************************************************************/ - -#include - -#include -#include -#include - -/**************************************************************************** - * Private Data - ****************************************************************************/ - -typedef void (*init_func_t)(int argc, const char *argv[], - const char *envp[], const char *apple[]); -extern init_func_t mod_init_func_start \ -__asm("section$start$__DATA$__mod_init_func"); -extern init_func_t mod_init_func_end \ -__asm("section$end$__DATA$__mod_init_func"); - -static void noop(int argc, const char *argv[], const char *envp[], - const char *apple[]) -{ - /* nothing */ -} - -static init_func_t *g_saved_init_funcs; -static unsigned int g_num_saved_init_funcs; -static int g_saved_argc; -static const char **g_saved_argv; -static const char **g_saved_envp; -static const char **g_saved_apple; - -static void -allow_write(const void *start, const void *end) -{ - const size_t page_size = sysconf(_SC_PAGE_SIZE); - const size_t page_mask = ~(page_size - 1); - void *p = (void *)((uintptr_t)start & page_mask); - size_t sz = ((uintptr_t)end - (uintptr_t)p + page_size - 1) & ~page_mask; - - /* It seems that Monterey (12.1) maps the section read-only. - * Make it writable as we want to patch it. - * This was not necessary for Mojave. - * Ignore failures as this might not be critical, depending on - * the OS version. - */ - - mprotect(p, sz, PROT_READ | PROT_WRITE); -} - -__attribute__((constructor)) -static void save_and_replace_init_funcs(int argc, const char *argv[], - const char *envp[], - const char *apple[]) -{ - init_func_t *fp; - unsigned int nfuncs = &mod_init_func_end - &mod_init_func_start; - - assert(nfuncs > 0); - g_num_saved_init_funcs = nfuncs - 1; - if (g_num_saved_init_funcs == 0) - { - /* This function is the only constructor in the binary. - * no need to apply the following hack. - */ - - return; - } - - g_saved_argc = argc; - g_saved_argv = argv; - g_saved_envp = envp; - g_saved_apple = apple; - - g_saved_init_funcs = malloc(g_num_saved_init_funcs * - sizeof(*g_saved_init_funcs)); - allow_write(&mod_init_func_start, &mod_init_func_end); - int i = 0; - for (fp = &mod_init_func_start; fp < &mod_init_func_end; fp++) - { - if (*fp == save_and_replace_init_funcs) - { - assert(i == 0); - } - else - { - g_saved_init_funcs[i - 1] = *fp; - *fp = noop; - } - i++; - } -} - -/**************************************************************************** - * Public Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: macho_call_saved_init_funcs - ****************************************************************************/ - -void -macho_call_saved_init_funcs(void) -{ - unsigned int i; - for (i = 0; i < g_num_saved_init_funcs; i++) - { - g_saved_init_funcs[i](g_saved_argc, g_saved_argv, g_saved_envp, - g_saved_apple); - } -} diff --git a/libs/libc/misc/lib_cxx_initialize.c b/libs/libc/misc/lib_cxx_initialize.c index 6b1d1970066aa..1f75d5ba8d403 100644 --- a/libs/libc/misc/lib_cxx_initialize.c +++ b/libs/libc/misc/lib_cxx_initialize.c @@ -38,10 +38,6 @@ * External References ****************************************************************************/ -#if defined(CONFIG_ARCH_SIM) && defined(CONFIG_HOST_MACOS) -extern void macho_call_saved_init_funcs(void); -#endif - /**************************************************************************** * Public Functions ****************************************************************************/ @@ -69,9 +65,6 @@ void lib_cxx_initialize(void) if (inited == 0) { -#if defined(CONFIG_ARCH_SIM) && defined(CONFIG_HOST_MACOS) - macho_call_saved_init_funcs(); -#else initializer_t *initp; sinfo("_sinit: %p _einit: %p\n", _sinit, _einit); @@ -93,7 +86,6 @@ void lib_cxx_initialize(void) initializer(); } } -#endif inited = 1; } diff --git a/sched/clock/clock_gettime.c b/sched/clock/clock_gettime.c index 7d5b0566090d8..3c404da4e5a72 100644 --- a/sched/clock/clock_gettime.c +++ b/sched/clock/clock_gettime.c @@ -215,7 +215,7 @@ int nxclock_gettime(clockid_t clock_id, FAR struct timespec *tp) * ****************************************************************************/ -int clock_gettime(clockid_t clock_id, FAR struct timespec *tp) +int weak_function clock_gettime(clockid_t clock_id, FAR struct timespec *tp) { int ret;