Skip to content

arch/sim: replace macOS C++ constructor runtime hack with post-link patch#18886

Open
xiaoxiang781216 wants to merge 2 commits into
apache:masterfrom
xiaoxiang781216:macos
Open

arch/sim: replace macOS C++ constructor runtime hack with post-link patch#18886
xiaoxiang781216 wants to merge 2 commits into
apache:masterfrom
xiaoxiang781216:macos

Conversation

@xiaoxiang781216
Copy link
Copy Markdown
Contributor

Summary

Replace the macOS sim C++ global constructor runtime hack (sim_macho_init.c)
with a post-link Mach-O section patching scheme so dyld no longer auto-runs
constructors before NuttX is initialized.

Impact

  • Area: arch/sim (macOS host only). Linux behavior is unchanged.

Testing

  • Build host: macOS (Ventura VM)
  • Target: sim:nsh with CONFIG_HAVE_CXX=y / CONFIG_HAVE_CXXINITIALIZE=y
  • Verified via standalone test (test_sinit7) that the C++ constructor is
    deferred past main() and is only invoked when explicitly called through
    the _sinit[]/_einit[] loop in lib_cxx_initialize().

Details

  1. Link with -Wl,-ld_classic,-no_fixup_chains to keep the classic
    __mod_init_func pointer format (prevents ld64 from converting it to
    __init_offsets).
  2. Post-link, run patch_macho_initsection.py (python3 + lief) to patch
    the Mach-O section type flags from MOD_INIT_FUNC_POINTERS /
    INIT_FUNC_OFFSETS to REGULAR, so dyld skips them.
  3. Use the Mach-O auto-generated boundary symbols
    section$start$__DATA_CONST$__mod_init_func /
    section$end$__DATA_CONST$__mod_init_func, mapped via __asm() labels in
    arch/sim/include/arch.h to the common _sinit[]/_einit[] names used
    by lib_cxx_initialize().
  4. Delete the old sim_macho_init.c runtime hack and the macOS-specific
    macho_call_saved_init_funcs() path in lib_cxx_initialize.c.

Dependency: pip install lief on the build host when
CONFIG_HAVE_CXXINITIALIZE=y.

Copilot AI review requested due to automatic review settings May 16, 2026 12:18
@github-actions github-actions Bot added Arch: simulator Issues related to the SIMulator Area: OS Components OS Components issues Size: M The size of the change in this PR is medium labels May 16, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the macOS simulator C++ constructor deferral mechanism with post-link Mach-O section patching, so dyld does not run constructors before NuttX initialization.

Changes:

  • Removes the old runtime Mach-O constructor interception path.
  • Adds macOS linker flags and post-build patching for Mach-O init sections.
  • Maps macOS Mach-O section boundary symbols to _sinit / _einit.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
libs/libc/misc/lib_cxx_initialize.c Uses the common _sinit / _einit constructor loop for macOS too.
arch/sim/src/sim/posix/sim_macho_init.c Removes the previous runtime constructor-saving hack.
arch/sim/src/sim/CMakeLists.txt Adds macOS link options and a post-build Mach-O patch command.
arch/sim/src/patch_macho_initsection.py Adds the LIEF-based Mach-O section type patcher.
arch/sim/src/Makefile Adds macOS linker flags and invokes the patcher in make builds.
arch/sim/include/arch.h Adds macOS-specific _sinit / _einit asm symbol mappings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread arch/sim/src/Makefile Outdated
Comment thread arch/sim/src/sim/CMakeLists.txt
Comment thread arch/sim/include/arch.h
Comment thread arch/sim/src/Makefile Outdated
Comment thread arch/sim/src/sim/CMakeLists.txt
Comment thread arch/sim/src/patch_macho_initsection.py
Comment thread arch/sim/src/Makefile Outdated
…atch

The sim architecture needs to defer C++ global constructors until after
NuttX kernel initialization completes. On macOS this was previously done
via a runtime hack (sim_macho_init.c): a __attribute__((constructor))
function intercepted constructors, saved them, and replayed them later.
That approach was fragile because it depended on constructor ordering
and required mprotect() to patch the read-only __mod_init_func section
at runtime.

This commit replaces the runtime hack with a post-link patching scheme:

  1. Link with -Wl,-ld_classic,-no_fixup_chains to keep the classic
     __mod_init_func pointer format (prevents ld64 from converting it
     to __init_offsets).
  2. Post-link, run patch_macho_initsection.py (python3 + lief) to
     patch Mach-O section type flags from
     MOD_INIT_FUNC_POINTERS/INIT_FUNC_OFFSETS to REGULAR, so dyld
     skips them entirely.
  3. Use the Mach-O auto-generated boundary symbols
     section$start$__DATA_CONST$__mod_init_func /
     section$end$__DATA_CONST$__mod_init_func, mapped via __asm()
     labels in arch/sim/include/arch.h to the common _sinit[]/_einit[]
     names used by lib_cxx_initialize().

Linux behavior is unchanged.

Changes:
  - arch/sim/include/arch.h: add macOS __asm() declarations for
    _sinit/_einit
  - arch/sim/src/Makefile: drop sim_macho_init.c HEADSRC handling,
    always pass -ld_classic,-no_fixup_chains on macOS, run
    patch_macho_initsection.py after link when CONFIG_HAVE_CXXINITIALIZE
  - arch/sim/src/sim/CMakeLists.txt: same for the CMake build
  - arch/sim/src/patch_macho_initsection.py: new lief-based patcher
  - arch/sim/src/sim/posix/sim_macho_init.c: deleted (135-line hack)
  - libs/libc/misc/lib_cxx_initialize.c: remove
    macho_call_saved_init_funcs special case; single unified loop

Testing:
  - macOS (Ventura VM): verified lief patching with standalone test
    (test_sinit7) confirming the constructor is deferred past main()
    and only invoked when explicitly called via the _sinit/_einit loop.

Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
@toku-mac
Copy link
Copy Markdown

Hello. Regarding arch/sim/src/sim/CMakeLists.txt.

  add_custom_command(
    TARGET nuttx
    POST_BUILD
    COMMAND
      ${Python3_EXECUTABLE}
      ${CMAKE_CURRENT_SOURCE_DIR}/../patch_macho_initsection.py
      $<TARGET_FILE:nuttx>
    COMMENT "Patching Mach-O init section type flags")
endif()

add_custom_command(TARGET ...) can only be used in the directory where the target was created.
Since the nuttx executable is created in the top-level CMakeLists.txt, attaching a POST_BUILD command to it from here would not work.
Please 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
      $<TARGET_FILE:nuttx>
    DEPENDS nuttx
    COMMENT "Patching Mach-O init section type flags")
  add_dependencies(nuttx_post sim_patch_macho_initsection)
endif()

@toku-mac
Copy link
Copy Markdown

about arch/sim/src/patch_macho_initsection.py.
On macOS 11 and later with Apple Silicon, binary re-signing is required.

Writing the file invalidates the ad-hoc signature that ld attached at link time, which causes the kernel to SIGKILL the process at exec.

import argparse
import subprocess
import sys
    if patched:
        fat.write(args.binary)

        if sys.platform == "darwin":
            subprocess.run(
                ["codesign", "--force", "--sign", "-", args.binary],
                check=True,
            )

    return 0

@xiaoxiang781216
Copy link
Copy Markdown
Contributor Author

xiaoxiang781216 commented May 17, 2026

@toku-mac I have added you to my repo collaborator, please run the below command in your local nuttx git:

git remote add xiaoxiang781216 git@github.com:xiaoxiang781216/incubator-nuttx.git
git fetch xiaoxiang781216
git branch --track macos xiaoxiang781216/macos
git checkout macos
do the change you want
git commit --amend -s
git push -f xiaoxiang781216

since it's simpler to modify and update this patch by you directly.

…atch

- Invoke `codesign` and after lief rewrites the binary, so the modified
   Mach-O remains loadable on macOS.
- Run the patch script via a dedicated `sim_patch_macho_initsection`
  custom target attached to `nuttx_post`, instead of a POST_BUILD
  command on the `nuttx` target.

Signed-off-by: Shoji Tokunaga <toku@mac.com>
@toku-mac
Copy link
Copy Markdown

Hello everyone.
I pushed commit a73cb17. Please confirm.

Environment

  • Mac mini Apple M1
  • macOS 26.5
  • CLT: 26.5.0.0.1777544298
  • Xcode: 26.5
  • cmake: 4.3.2
  • ninja: 1.13.2
  • Python 3.14.5 (with lief installed)

apps branch: add_apple_cargo_cmake (commit 97716525)

Preparation

rm -rf build-debug
make distclean
cmake -S . -B build-debug -DBOARD_CONFIG=sim:nsh -GNinja
cmake --build build-debug -t menuconfig

Configure

CONFIG_SYSTEM_TIME64=y
CONFIG_FS_LARGEFILE=y
CONFIG_TLS_NELEM=16
CONFIG_DEV_URANDOM=y
CONFIG_EXAMPLES_HELLO_RUST_CARGO=y
CONFIG_EXAMPLES_HELLO_RUST_CARGO_STACKSIZE=8192
# CONFIG_COVERAGE_TOOLCHAIN is not set
CONFIG_COVERAGE_NONE=y

Build

cmake --build build-debug
...
   Compiling std_detect v0.1.5 (/Users/toku/.rustup/toolchains/nightly-2026-04-29-aarch64-apple-darwin/lib/rustlib/src/rust/library/std_detect)
   Compiling addr2line v0.25.1
   Compiling proc_macro v0.0.0 (/Users/toku/.rustup/toolchains/nightly-2026-04-29-aarch64-apple-darwin/lib/rustlib/src/rust/library/proc_macro)
   Compiling itoa v1.0.18
   Compiling pin-project-lite v0.2.17
   Compiling memchr v2.8.0
   Compiling tokio v1.52.3
   Compiling hello v0.1.0 (/Users/toku/nuttxspace/apps/examples/rust/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 15.41s
[1223/1228] Linking C executable nuttx
ld: warning: -ld_classic is deprecated and will be removed in a future release
[1225/1228] Patching Mach-O init section type flags
/Users/toku/nuttxspace/nuttx/build-debug/nuttx: replacing existing signature
/Users/toku/nuttxspace/nuttx/build-debug/nuttx: valid on disk
/Users/toku/nuttxspace/nuttx/build-debug/nuttx: satisfies its Designated Requirement

Execute

./build-debug/nuttx

NuttShell (NSH) NuttX-12.13.0
nsh> help
help usage:  help [-v] [<cmd>]

    .           cmp         fdinfo      mkrd        rm          true
    [           dirname     free        mount       rmdir       truncate
    ?           df          help        mv          set         uname
    alias       dmesg       hexdump     pidof       kill        umount
    unalias     echo        losetup     poweroff    pkill       unset
    basename    env         ln          quit        sleep       uptime
    break       exec        ls          printf      usleep      watch
    cat         exit        mkdir       ps          source      xd
    cd          expr        mkfatfs     pwd         test        wait
    cp          false       mkfifo      readlink    time

Builtin Apps:
    dd                  nsh                 ostest              hello
    dumpstack           sh                  gpio                hello_rust_cargo
nsh> uname -a
NuttX 12.13.0 eae41cab13-dirty May 18 2026 10:43:05 sim sim
nsh> hello_rust_cargo
{"name":"John","age":30}
{"name":"Jane","age":25}
Deserialized: Alice is 28 years old
Pretty JSON:
{
  "name": "Alice",
  "age": 28
}

thread '<unnamed>' (540079) panicked at /Users/toku/.rustup/toolchains/nightly-2026-04-29-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/sys/pal/unix/time.rs:107:68:
called `Result::unwrap()` on an `Err` value: Os { code: 0, kind: Uncategorized, message: "Unknown error 0" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
nsh>

The last Rust tokio-related panic I'll fix in another PR.

@xiaoxiang781216
Copy link
Copy Markdown
Contributor Author

Hello everyone. I pushed commit a73cb17. Please confirm.

The last Rust tokio-related panic I'll fix in another PR.

@toku-mac Thank for help verifying this patch work on your machine. @yamt please review the new approach to fix macOS c++ initialization order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: simulator Issues related to the SIMulator Area: OS Components OS Components issues Size: M The size of the change in this PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] our hack around __mod_init_func doesn't work for the latest macOS with chained fixups

4 participants