Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 26 additions & 44 deletions .github/workflows/make-test-multi-platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ jobs:
compiler: gcc
- os: ubuntu-latest
compiler: clang
# - os: macos-latest
# compiler: clang
# - os: macos-14
# compiler: clang
- os: macos-13
compiler: clang
- os: macos-latest
compiler: clang
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -70,50 +70,32 @@ jobs:
echo "--- done RUNNING $f ---"
done
working-directory: ${{ github.workspace }}/test
- name: Build and run tests (Macos)
if: ${{ matrix.os == 'macos-latest' }}
- name: Build and run tests (macOS)
if: ${{ startsWith(matrix.os, 'macos') }}
run: |
sysctl hw
make -f Makefile.linux64.clang
compiler=clang
for f in $(find . -type f -name "*.cpp"); do
if [[ -z "${f##*win.cpp}" ]]; then
continue
fi

f=${f%.cpp}

if [ ${compiler}="clang" ] && [[ $f == *virtual* ]]; then
continue
fi

sysctl -n machdep.cpu.brand_string || true
uname -m
# Build + post-process (macos_enable_stub.sh is invoked automatically
# by the Makefile: it patches __TEXT maxprot to rwx and re-signs the
# binary ad-hoc so cpp-stub's Apple code path can create a writable
# alias of __TEXT at runtime).
make -f Makefile.darwin.clang
for f in $(find . -maxdepth 1 -type f -perm -u+x ! -name "Makefile*" ! -name "*.cpp" ! -name "*.md"); do
echo "--- RUNNING $f ---"
printf '\x07' | dd of=${f} bs=1 seek=160 count=1 conv=notrunc
${f}
"$f"
echo "--- done RUNNING $f ---"
done
working-directory: ${{ github.workspace }}/test
- name: Build and run tests (Macos M1)
if: ${{ matrix.os == 'macos-14' }}
shell: bash

- name: CMake integration smoke test (macOS)
if: ${{ startsWith(matrix.os, 'macos') }}
run: |
sysctl hw
make -f Makefile.linux64.clang
compiler=clang
for f in $(find . -type f -name "*.cpp"); do
if [[ -z "${f##*win.cpp}" ]]; then
continue
fi

f=${f%.cpp}

if [ ${compiler}="clang" ] && [[ $f == *virtual* ]]; then
continue
fi

echo "--- RUNNING $f ---"
printf '\x07' | dd of=${f} bs=1 seek=160 count=1 conv=notrunc
${f}
echo "--- done RUNNING $f ---"
done
working-directory: ${{ github.workspace }}/test
# Verifies that downstream projects only need
# target_link_libraries(my_test PRIVATE cpp-stub)
# and no manual post-build step — the root CMakeLists.txt
# defers and attaches macos_enable_stub.sh automatically.
cmake -S example/cmake_smoke -B example/cmake_smoke/build
cmake --build example/cmake_smoke/build
./example/cmake_smoke/build/cmake_smoke
shell: bash
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.ccls-cache
replit*
*.dSYM/
106 changes: 106 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
cmake_minimum_required(VERSION 3.19)
project(cpp-stub CXX)

# Header-only interface library. Downstream usage:
#
# add_subdirectory(third_party/cpp-stub)
# add_executable(my_test test.cpp)
# target_link_libraries(my_test PRIVATE cpp-stub)
#
# On macOS the build system automatically patches the produced binary
# after link so cpp-stub can rewrite __TEXT at runtime; no manual step
# is required. On Linux/Windows the post-build hook is a no-op.

add_library(cpp-stub INTERFACE)
add_library(cpp-stub::cpp-stub ALIAS cpp-stub)

target_include_directories(cpp-stub INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/src")

if(APPLE)
target_include_directories(cpp-stub INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/src_darwin")
endif()

if(UNIX AND NOT APPLE)
target_include_directories(cpp-stub INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/src_linux")
endif()

if(WIN32)
target_include_directories(cpp-stub INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/src_win")
endif()

# -----------------------------------------------------------------------------
# macOS post-build automation
# -----------------------------------------------------------------------------
if(APPLE)
set(_CPP_STUB_ENABLE_TOOL
"${CMAKE_CURRENT_SOURCE_DIR}/tool/macos_enable_stub.sh"
CACHE INTERNAL "")

# Explicit opt-in helper for cases where the auto-detect below cannot
# see a target (e.g. the executable only links cpp-stub transitively
# through an intermediate static library).
function(cpp_stub_enable target)
get_target_property(_already "${target}" _CPP_STUB_POSTBUILD_ADDED)
if(_already)
return()
endif()
add_custom_command(TARGET "${target}" POST_BUILD
COMMAND "${_CPP_STUB_ENABLE_TOOL}" "$<TARGET_FILE:${target}>"
COMMENT "cpp-stub: patching __TEXT maxprot and re-signing ${target}"
VERBATIM)
set_target_properties("${target}" PROPERTIES
_CPP_STUB_POSTBUILD_ADDED TRUE)
endfunction()

# Recursively collect all targets in the directory tree rooted at `dir`.
function(_cpp_stub_collect_targets dir out_var)
set(_acc "")
get_property(_here DIRECTORY "${dir}" PROPERTY BUILDSYSTEM_TARGETS)
if(_here)
list(APPEND _acc ${_here})
endif()
get_property(_subs DIRECTORY "${dir}" PROPERTY SUBDIRECTORIES)
foreach(_s IN LISTS _subs)
_cpp_stub_collect_targets("${_s}" _sub)
if(_sub)
list(APPEND _acc ${_sub})
endif()
endforeach()
set(${out_var} "${_acc}" PARENT_SCOPE)
endfunction()

# Deferred callback: invoked at end-of-config of the top-level directory,
# by which time every target has been declared. For each executable that
# (directly) links cpp-stub, attach the post-build patch step.
function(_cpp_stub_auto_enable_all)
_cpp_stub_collect_targets("${CMAKE_SOURCE_DIR}" _targets)
foreach(_t IN LISTS _targets)
if(NOT TARGET "${_t}")
continue()
endif()
get_target_property(_type "${_t}" TYPE)
if(NOT _type STREQUAL "EXECUTABLE")
continue()
endif()
set(_links "")
get_target_property(_l "${_t}" LINK_LIBRARIES)
if(_l)
list(APPEND _links ${_l})
endif()
get_target_property(_il "${_t}" INTERFACE_LINK_LIBRARIES)
if(_il)
list(APPEND _links ${_il})
endif()
if("cpp-stub" IN_LIST _links OR "cpp-stub::cpp-stub" IN_LIST _links)
cpp_stub_enable("${_t}")
endif()
endforeach()
endfunction()

cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}"
CALL _cpp_stub_auto_enable_all)
endif()
67 changes: 66 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@
- Supported operating systems:
* [x] Windows
* [x] Linux
* [x] MacOS(x86-64, printf '\x07' | dd of=test_function bs=1 seek=160 count=1 conv=notrunc)
* [x] MacOS — both Intel (x86-64) and Apple Silicon (arm64). Because macOS
enforces W^X on `__TEXT` and caps `mprotect` at each segment's `maxprot`,
every binary that uses cpp-stub needs a tiny post-link adjustment
(lift `__TEXT.maxprot` to `rwx` and ad-hoc re-sign; see
[issue #49](https://github.com/coolxv/cpp-stub/issues/49)).
cpp-stub ships build-system helpers that do this automatically, so
downstream users normally do **not** have to call anything by hand.
See [the macOS integration section](#macos-integration) below.
- Supported hardware platform:
* [x] x86
* [x] x86-64
Expand Down Expand Up @@ -68,6 +75,64 @@
- -fprofile-arcs
- -ftest-coverage

## macOS integration

macOS requires every binary that uses cpp-stub to have its `__TEXT`
segment's `maxprot` lifted to `rwx` and be ad-hoc re-signed once, after
link. The library ships helpers that wire this up for you; on Linux and
Windows the same helpers do nothing.

### CMake (zero manual steps)

```cmake
add_subdirectory(third_party/cpp-stub)
add_executable(my_test test.cpp)
target_link_libraries(my_test PRIVATE cpp-stub)
```

The root `CMakeLists.txt` installs a deferred hook that finds every
executable that links `cpp-stub` and automatically attaches the
post-build patch step on Apple platforms. For the rare case where an
executable only links cpp-stub *transitively* through a static library,
call it explicitly:

```cmake
cpp_stub_enable(my_test)
```

### Makefile

```make
CPP_STUB_DIR := third_party/cpp-stub
include $(CPP_STUB_DIR)/mk/cpp-stub.mk

my_test: my_test.cpp
$(CXX) $(addprefix -I,$(CPP_STUB_INCLUDE)) ... -o $@ $<
@$(CPP_STUB_POSTLINK)
```

`$(CPP_STUB_POSTLINK)` expands to the enable-stub command on macOS and
to a no-op (`:`) on Linux/Windows, so the same rule works cross-platform.

### Xcode / Bazel / other

Add a run-script / `genrule` that invokes the helper once per built
executable:

```bash
third_party/cpp-stub/tool/macos_enable_stub.sh "$TARGET_BUILD_DIR/$EXECUTABLE_PATH"
```

### Why this step exists

See [issue #49](https://github.com/coolxv/cpp-stub/issues/49) and the
`__APPLE__` branch of [src/stub.h](src/stub.h) for the full story: the
kernel will not let `mprotect`/`mach_vm_protect` raise `__TEXT` above
its Mach-O `maxprot`, and Apple Silicon further enforces W^X at the
page-table level even when `maxprot=rwx`. `tool/macos_enable_stub.sh`
raises `maxprot` so that cpp-stub can obtain a writable alias of
`__TEXT` at runtime via `mach_vm_remap`.

## Code coverage statistics for linux g++
```
lcov -d build/ -z
Expand Down
61 changes: 60 additions & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
- 支持的操作系统 :
* [x] Windows
* [x] Linux
* [x] MacOS(x86-64, printf '\x07' | dd of=test_function bs=1 seek=160 count=1 conv=notrunc)
* [x] MacOS —— 同时支持 Intel (x86-64) 与 Apple Silicon (arm64)。由于 macOS
对 `__TEXT` 段施加 W^X 限制,并且 `mprotect` 不能超过段的 `maxprot`,
使用 cpp-stub 的每个可执行文件都需要在链接后做一次小处理(把 `__TEXT.maxprot`
抬到 `rwx` 并 ad-hoc 重新签名,见 [issue #49](https://github.com/coolxv/cpp-stub/issues/49))。
本仓库提供了构建系统集成助手,下游通常**不需要手动调用**任何脚本。
详见下文的 [macOS 集成说明](#macos-集成说明)。

- 支持的硬件平台 :
* [x] x86
Expand Down Expand Up @@ -69,6 +74,60 @@
- -fprofile-arcs
- -ftest-coverage

## macOS 集成说明

macOS 下每个使用 cpp-stub 的可执行文件都需要在链接完成后做一次小处理:
把 `__TEXT.maxprot` 抬到 `rwx`,并用 ad-hoc 重新签名。本仓库自带构建系统
助手帮你自动做这件事;Linux / Windows 下这些助手是空操作。

### CMake(零手动步骤)

```cmake
add_subdirectory(third_party/cpp-stub)
add_executable(my_test test.cpp)
target_link_libraries(my_test PRIVATE cpp-stub)
```

仓库根部的 `CMakeLists.txt` 会注册一个 deferred hook:在配置阶段结束时
遍历所有 executable,对每个直接链接 `cpp-stub` 的目标自动挂 POST_BUILD
步骤。只有当可执行文件通过中间静态库**间接**链接 cpp-stub 时,才需要
显式调用:

```cmake
cpp_stub_enable(my_test)
```

### Makefile

```make
CPP_STUB_DIR := third_party/cpp-stub
include $(CPP_STUB_DIR)/mk/cpp-stub.mk

my_test: my_test.cpp
$(CXX) $(addprefix -I,$(CPP_STUB_INCLUDE)) ... -o $@ $<
@$(CPP_STUB_POSTLINK)
```

`$(CPP_STUB_POSTLINK)` 在 macOS 上展开为 enable-stub 命令,
在 Linux/Windows 上展开为空操作 (`:`),同一份 Makefile 跨平台可用。

### Xcode / Bazel / 其它

加一条 run-script / `genrule`,对每个可执行文件调一次:

```bash
third_party/cpp-stub/tool/macos_enable_stub.sh "$TARGET_BUILD_DIR/$EXECUTABLE_PATH"
```

### 为什么需要这一步

参见 [issue #49](https://github.com/coolxv/cpp-stub/issues/49) 以及
[src/stub.h](src/stub.h) 的 `__APPLE__` 分支:内核不允许
`mprotect`/`mach_vm_protect` 把 `__TEXT` 的权限提升到 Mach-O 里
`maxprot` 之上,而 Apple Silicon 还在页表层面强制 W^X。
`tool/macos_enable_stub.sh` 负责把 `maxprot` 抬到 `rwx`,这样
cpp-stub 运行时才能通过 `mach_vm_remap` 拿到 `__TEXT` 的可写别名。


## 代码覆盖率, linux g++使用方法
```
Expand Down
13 changes: 13 additions & 0 deletions example/cmake_smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.19)
project(cpp_stub_cmake_smoke CXX)

set(CMAKE_CXX_STANDARD 11)

# Points at repository root (two levels up from this file).
add_subdirectory(../.. cpp-stub-build)

add_executable(cmake_smoke smoke.cpp)
target_link_libraries(cmake_smoke PRIVATE cpp-stub)

# Deliberately no cpp_stub_enable() call here: verifies that merely
# linking the interface target is enough on macOS.
Loading