mirror of
https://github.com/okhsunrog/vpnhide.git
synced 2026-04-28 06:31:27 +00:00
build: align native cdylib on 16 KiB; unify kmod/zygisk build scripts
Two related changes that ship together because they touch the same
build-script + docs surface and were verified together on-device.
16 KiB alignment
- zygisk/build.rs: pass `-Wl,-z,max-page-size=16384` to lld so the
cdylib's LOAD segments line up on 16 KiB pages. NDK r28+ already
does this by default, but the flag keeps r27 builds compatible.
- lsposed/native/build.rs: new file, same flag, for libvpnhide_checks.so.
- docs/development.md: bumped the NDK requirement to r28+ and noted
the 16 KiB rationale.
Verified via `llvm-readelf -l`: both libvpnhide_zygisk.so and
libvpnhide_checks.so now show `Align 0x4000` on every LOAD segment.
Unified build entry points
- kmod/build.py replaces kmod/build-zip.py. Single script that
auto-detects whether to build natively (we're inside the DDK image
or `--kdir` was passed) or to spawn `ghcr.io/ylarod/ddk-min` via
podman/docker. CI uses the same script with `--inside-container`.
- zygisk/build-zip.py renamed to zygisk/build.py for symmetry; logic
unchanged.
- kmod/BUILDING.md rewritten — local build is now one command:
`./kmod/build.py --kmi android14-6.1` (or `--all`). The old
hand-rolled podman/docker recipes are gone.
- .github/workflows/ci.yml updated to call the new entry points.
The DDK image tag in CI now has a comment pointing at
`DDK_IMAGE_TAG` in kmod/build.py as the source of truth.
- README.{md,en.md}, kmod/README.md, zygisk/README.md, docs/releasing.md,
scripts/build_lib.py: reference updates.
- README.en.md: also fixes a "bacame" typo and tightens the Windows
zygisk-build note (the aux.rs / libgit2 issue is still real).
Verified end-to-end on Pixel 8 Pro (husky, android14-6.1, Android 16):
APK installs, kmod + zygisk modules load, all 26 self-checks PASS in
Enforcing, 22/26 PASS in Permissive (the same 4 by-design FAILs as
before — kmod doesn't cover those paths in Permissive).
This commit is contained in:
parent
14f4765048
commit
35b3dcdf50
15 changed files with 439 additions and 252 deletions
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
|
@ -98,6 +98,8 @@ jobs:
|
|||
- android14-6.1
|
||||
- android15-6.6
|
||||
- android16-6.12
|
||||
# Tag here mirrors `DDK_IMAGE_TAG` in kmod/build.py — bump both
|
||||
# together so local builds and CI use the exact same image.
|
||||
container:
|
||||
image: ghcr.io/ylarod/ddk-min:${{ matrix.kmi }}-20260313
|
||||
env:
|
||||
|
|
@ -112,9 +114,7 @@ jobs:
|
|||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Build and package kernel module
|
||||
run: |
|
||||
cd kmod
|
||||
python3 ./build-zip.py --kmi $KMI
|
||||
run: python3 kmod/build.py --kmi $KMI --inside-container
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
|
|
@ -156,7 +156,7 @@ jobs:
|
|||
UPDATE_JSON_URL: https://raw.githubusercontent.com/okhsunrog/vpnhide/main/update-json/update-zygisk.json
|
||||
run: |
|
||||
cd zygisk
|
||||
python3 ./build-zip.py
|
||||
python3 ./build.py
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
|
|
|
|||
14
README.en.md
14
README.en.md
|
|
@ -194,8 +194,8 @@ Rows 1-6, 21, and 24 are the only vectors reachable by regular apps. Everything
|
|||
|
||||
## Building from source
|
||||
|
||||
- **kmod**: `cd kmod && make && ./build-zip.py` — see [kmod/BUILDING.md](kmod/BUILDING.md)
|
||||
- **zygisk**: `cd zygisk && ./build-zip.py` (Rust + NDK + cargo-ndk)
|
||||
- **kmod**: `./kmod/build.py --kmi android14-6.1` (or `--all`) — auto-spawns the DDK container via podman/docker. Full guide: [kmod/BUILDING.md](kmod/BUILDING.md).
|
||||
- **zygisk**: `cd zygisk && ./build.py` (Rust + NDK + cargo-ndk)
|
||||
- **lsposed**: `cd lsposed && ./gradlew assembleDebug` (JDK 17 + Rust + NDK + cargo-ndk)
|
||||
|
||||
### Notes for contributors stuck on Windows
|
||||
|
|
@ -208,17 +208,13 @@ If you're on Windows, there are some inconveniences with building some subprojec
|
|||
|
||||
For the next two, you'll (unfortunately) need to install [Docker for Windows](https://docs.docker.com/desktop/setup/install/windows-install/).
|
||||
|
||||
**kmod**:
|
||||
```powershell
|
||||
$env:KMI="android12-5.10"; docker run --rm -it -v "${PWD}:/workspace" -e KMI=$env:KMI -w /workspace "ghcr.io/ylarod/ddk-min:$($env:KMI)-20260313" bash -c 'cd kmod && python3 ./build-zip.py --kmi $KMI'
|
||||
```
|
||||
Be sure to use the same version of `ylarod/ddk-min` image (the date after KMI name) as used in the `ci.yml` workflow file.
|
||||
**kmod**: `python .\kmod\build.py --kmi android14-6.1` — the script picks up Docker and pulls the same `ddk-min` image that CI uses.
|
||||
|
||||
**zygisk**:
|
||||
```powershell
|
||||
docker run --rm -it -v "${PWD}:/workspace" -v "vpnhide_cargo_cache:/usr/local/cargo/registry" -w /workspace ghcr.io/okhsunrog/vpnhide/ci:latest bash -c 'cd zygisk && python3 ./build-zip.py'
|
||||
docker run --rm -it -v "${PWD}:/workspace" -v "vpnhide_cargo_cache:/usr/local/cargo/registry" -w /workspace ghcr.io/okhsunrog/vpnhide/ci:latest bash -c 'cd zygisk && python3 ./build.py'
|
||||
```
|
||||
The reason why `zygisk` can't be built directly is because source code of dependency `zygisk-api` contains a file named `aux.rs`. Cargo uses `libgit2` for git operations and it contains a guard, which forbids creating files _containing_ reserved Windows words. You'll get an error: `cannot checkout to invalid path 'src/aux.rs'; class=Checkout (20)`. [Someone reports](https://superuser.com/a/1929659), that it bacame possible to create files containing reserved words **with** an extension after some update, but it seems such behavior wasn't modified in `libgit2`.
|
||||
The reason `zygisk` can't be built directly on Windows is that the `zygisk-api` dependency contains a file named `aux.rs`. Cargo uses `libgit2` for git operations, and `libgit2` refuses to create files whose names _contain_ reserved Windows device names (`AUX`, `CON`, `NUL`, …). You'll get: `cannot checkout to invalid path 'src/aux.rs'; class=Checkout (20)`. [Someone reports](https://superuser.com/a/1929659) that some Windows update made it possible to create files containing reserved words **with** an extension, but `libgit2` hasn't been updated to relax the guard.
|
||||
|
||||
## Verified against
|
||||
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -194,8 +194,8 @@ vpnhide — это не один переключатель, а три разн
|
|||
|
||||
## Сборка из исходников
|
||||
|
||||
- **kmod**: `cd kmod && make && ./build-zip.py` — см. [kmod/BUILDING.md](kmod/BUILDING.md)
|
||||
- **zygisk**: `cd zygisk && ./build-zip.py` (Rust + NDK + cargo-ndk)
|
||||
- **kmod**: `./kmod/build.py --kmi android14-6.1` (или `--all`) — авто-запускает DDK-контейнер через podman/docker. Подробнее: [kmod/BUILDING.md](kmod/BUILDING.md).
|
||||
- **zygisk**: `cd zygisk && ./build.py` (Rust + NDK + cargo-ndk)
|
||||
- **lsposed**: `cd lsposed && ./gradlew assembleDebug` (JDK 17 + Rust + NDK + cargo-ndk)
|
||||
|
||||
### Заметки для контрибьюторов, застрявших на Windows
|
||||
|
|
@ -208,15 +208,11 @@ vpnhide — это не один переключатель, а три разн
|
|||
|
||||
Для следующих двух вам (к сожалению) потребуется установить [Docker for Windows](https://docs.docker.com/desktop/setup/install/windows-install/).
|
||||
|
||||
**kmod**:
|
||||
```powershell
|
||||
$env:KMI="android12-5.10"; docker run --rm -it -v "${PWD}:/workspace" -e KMI=$env:KMI -w /workspace "ghcr.io/ylarod/ddk-min:$($env:KMI)-20260313" bash -c 'cd kmod && python3 ./build-zip.py --kmi $KMI'
|
||||
```
|
||||
Обязательно используйте ту же версию образа `ylarod/ddk-min` (дата после названия KMI), которая используется в `ci.yml`.
|
||||
**kmod**: `python .\kmod\build.py --kmi android14-6.1` — скрипт сам подберёт docker и поднимет образ `ddk-min` (тот же, что в CI).
|
||||
|
||||
**zygisk**:
|
||||
```powershell
|
||||
docker run --rm -it -v "${PWD}:/workspace" -v "vpnhide_cargo_cache:/usr/local/cargo/registry" -w /workspace ghcr.io/okhsunrog/vpnhide/ci:latest bash -c 'cd zygisk && python3 ./build-zip.py'
|
||||
docker run --rm -it -v "${PWD}:/workspace" -v "vpnhide_cargo_cache:/usr/local/cargo/registry" -w /workspace ghcr.io/okhsunrog/vpnhide/ci:latest bash -c 'cd zygisk && python3 ./build.py'
|
||||
```
|
||||
Причина, по которой `zygisk` нельзя собрать напрямую, заключается в том, что исходный код зависимости `zygisk-api` содержит файл с именем `aux.rs`. Cargo использует `libgit2` для работы с git, в котором есть защита, запрещающая создавать файлы, _содержащие_ зарезервированные слова Windows. Вы получите ошибку: `cannot checkout to invalid path 'src/aux.rs'; class=Checkout (20)`. [Сообщают](https://superuser.com/a/1929659), что после какого-то обновления стало возможным создавать файлы, содержащие зарезервированные слова, **с** расширением, но, похоже, в `libgit2` это поведение не было изменено.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
_2026-04-26_
|
||||
|
||||
## English
|
||||
|
||||
Align native cdylib LOAD segments on 16 KiB so libvpnhide_zygisk.so and libvpnhide_checks.so load cleanly on Android 16 Pixel 8 Pro (and future 16 KiB-page hardware) without the "ELF LOAD not aligned" warning at app start
|
||||
|
||||
## Русский
|
||||
|
||||
Native-библиотеки (libvpnhide_zygisk.so, libvpnhide_checks.so) теперь выровнены на 16 КиБ. На Pixel 8 Pro с Android 16 (и будущем железе с 16 КиБ-страницами) больше нет предупреждения "сегмент LOAD не выровнен" при старте приложения
|
||||
|
|
@ -6,7 +6,7 @@ How to build vpnhide from source.
|
|||
|
||||
- **JDK 17 or later** — what the CI image installs (`openjdk-17-jdk-headless`); local builds with JDK 21 also work. The `lsposed/app` Gradle build sets `sourceCompatibility = 17` and `jvmTarget = "17"`.
|
||||
- **Android SDK** — install `platforms;android-35`, `build-tools;35.0.0`, `platform-tools` (via Android Studio or `cmdline-tools`). Export `ANDROID_HOME`.
|
||||
- **Android NDK r27c or later** — export `ANDROID_NDK_HOME` (or drop it in `$ANDROID_HOME/ndk/<version>/`, the scripts auto-detect). The Gobley Gradle plugin used by `lsposed/app` reads `ANDROID_NDK_ROOT`, not `ANDROID_NDK_HOME`, so export both (or alias one to the other) when invoking Gradle directly.
|
||||
- **Android NDK r28 or later** — export `ANDROID_NDK_HOME` (or drop it in `$ANDROID_HOME/ndk/<version>/`, the scripts auto-detect). The Gobley Gradle plugin used by `lsposed/app` reads `ANDROID_NDK_ROOT`, not `ANDROID_NDK_HOME`, so export both (or alias one to the other) when invoking Gradle directly. r27c builds compile, but the resulting cdylibs trigger an Android 16 KiB-page-size compatibility warning at app start on Pixel 8 Pro / future hardware (`сегмент LOAD не выровнен`); r28+ aligns LOAD segments on 16 KiB by default. (`zygisk/build.rs` and `lsposed/native/build.rs` also pass `-Wl,-z,max-page-size=16384` explicitly so older NDKs stay compatible — defence in depth.)
|
||||
- **Rust** (latest stable) with the Android target:
|
||||
```sh
|
||||
rustup target add aarch64-linux-android
|
||||
|
|
@ -58,7 +58,7 @@ keytool -genkey -v -keystore ~/vpnhide.jks \
|
|||
### zygisk module
|
||||
|
||||
```sh
|
||||
cd zygisk && ./build-zip.py
|
||||
cd zygisk && ./build.py
|
||||
# → zygisk/target/vpnhide-zygisk.zip
|
||||
```
|
||||
|
||||
|
|
@ -73,7 +73,13 @@ cd lsposed && ./gradlew :app:assembleRelease
|
|||
|
||||
### kernel module
|
||||
|
||||
Per-GKI-generation builds via DDK Docker/Podman images. See [kmod/BUILDING.md](../kmod/BUILDING.md) for the full guide (GKI identification, DDK commands, local-source builds with `direnv`).
|
||||
```sh
|
||||
./kmod/build.py --kmi android14-6.1 # one variant
|
||||
./kmod/build.py --all # every supported GKI
|
||||
# → vpnhide-kmod-<kmi>.zip at the repo root
|
||||
```
|
||||
|
||||
The script auto-spawns the `ghcr.io/ylarod/ddk-min:<kmi>-<TAG>` container via podman/docker (same image CI uses). For local kernel-source builds via `direnv` and the GKI matrix details, see [kmod/BUILDING.md](../kmod/BUILDING.md).
|
||||
|
||||
## Install on device
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,6 @@ This string goes into:
|
|||
- APK `versionName` (visible in Android Settings → Apps, diagnostic debug zip, `BuildConfig.VERSION_NAME`)
|
||||
- Inside the zip filenames (only for release tags; dev artifacts in CI keep a stable name)
|
||||
|
||||
The committed `module.prop` files are **not** modified — `build-zip.py` stages a copy, patches the version there, and zips. `lsposed/app/build.gradle.kts` evaluates `build-version.py` at configure time and sets `versionName` dynamically.
|
||||
The committed `module.prop` files are **not** modified — `kmod/build.py` and `zygisk/build.py` stage a copy, patch the version there, and zip. `lsposed/app/build.gradle.kts` evaluates `build-version.py` at configure time and sets `versionName` dynamically.
|
||||
|
||||
`versionCode` stays at the value baked in by the last `release.py` run (monotonically increasing integer required by Android/Magisk).
|
||||
|
|
|
|||
|
|
@ -2,67 +2,20 @@
|
|||
|
||||
Most users should download pre-built modules from [Releases](https://github.com/okhsunrog/vpnhide/releases) — builds are provided for all supported GKI generations. This guide is for contributors or users who need to build from source.
|
||||
|
||||
## Quick build with DDK (recommended)
|
||||
## Quick build
|
||||
|
||||
The easiest way to build is using the same DDK container images that CI uses. No kernel source clone, no toolchain setup.
|
||||
|
||||
### Docker
|
||||
One command — same script CI runs, no container invocation to memorize:
|
||||
|
||||
```bash
|
||||
# Pick your GKI generation (see "Identifying your GKI generation" below)
|
||||
KMI=android14-6.1
|
||||
|
||||
# Build the kernel module (run from repo root)
|
||||
docker run --rm -v $(pwd)/kmod:/work \
|
||||
ghcr.io/ylarod/ddk-min:${KMI}-20260313 sh -c "
|
||||
CLANG_DIR=\$(echo /opt/ddk/clang/clang-r*/bin) \
|
||||
KERNEL_SRC=/opt/ddk/kdir/${KMI} \
|
||||
make"
|
||||
|
||||
# Package as KSU module
|
||||
cp kmod/vpnhide_kmod.ko kmod/module/
|
||||
(cd kmod/module && zip -qr ../../vpnhide-kmod.zip .)
|
||||
./kmod/build.py --kmi android14-6.1 # one variant
|
||||
./kmod/build.py --all # every supported GKI
|
||||
```
|
||||
|
||||
### Podman (rootless + SELinux)
|
||||
The script auto-detects whether to build natively (you're already inside the DDK image, or you've pointed `--kdir` at a kernel source tree) or to spawn a `ghcr.io/ylarod/ddk-min:<kmi>-<TAG>` container via podman/docker. On rootless podman (Fedora etc) it adds `--userns=keep-id` and `:Z` automatically. The output is `vpnhide-kmod-<kmi>.zip` at the repo root.
|
||||
|
||||
On Fedora / RHEL-family systems with rootless Podman, the `docker run` above needs two extra flags:
|
||||
Requires `podman` or `docker`. The container image weighs ~1 GB per GKI variant on first pull.
|
||||
|
||||
- `--userns=keep-id` — maps the host UID into the container so the bind-mounted `/work` stays writable. Without it the build fails with `mkdir: cannot create directory '/work/.tmp_*': Permission denied`.
|
||||
- `:Z` on the `-v` mount — SELinux relabel for the bind mount.
|
||||
|
||||
```bash
|
||||
KMI=android14-6.1
|
||||
|
||||
podman run --rm --userns=keep-id -v "$(pwd)/kmod:/work:Z" \
|
||||
ghcr.io/ylarod/ddk-min:${KMI}-20260313 sh -c '
|
||||
CLANG_DIR=\$(echo /opt/ddk/clang/clang-r*/bin) \
|
||||
KERNEL_SRC=/opt/ddk/kdir/${KMI} \
|
||||
make'
|
||||
|
||||
# Package as KSU module
|
||||
cp kmod/vpnhide_kmod.ko kmod/module/
|
||||
(cd kmod/module && zip -qr ../../vpnhide-kmod.zip .)
|
||||
```
|
||||
|
||||
On non-SELinux distros with rootful Podman, the plain `docker run` command above works with just `s/docker/podman/`.
|
||||
|
||||
## Local build with kernel source
|
||||
|
||||
If you prefer building against a local kernel source tree (e.g. for development or debugging), use the Makefile with `direnv`:
|
||||
|
||||
```bash
|
||||
cd kmod/
|
||||
cp .env.example .env
|
||||
# Edit .env with paths to your kernel source and clang toolchain
|
||||
direnv allow
|
||||
make
|
||||
./build-zip.py
|
||||
```
|
||||
|
||||
See `.env.example` for the required variables. You need a prepared kernel source tree with headers and `Module.symvers`.
|
||||
|
||||
## Identifying your GKI generation
|
||||
### Identifying your GKI generation
|
||||
|
||||
```bash
|
||||
adb shell uname -r
|
||||
|
|
@ -82,10 +35,20 @@ The output looks like `6.1.75-android14-11-g...` — the generation is `android1
|
|||
| `6.6.xxx-android15-...` | android15-6.6 |
|
||||
| `6.12.xxx-android16-...` | android16-6.12 |
|
||||
|
||||
## Local build with kernel source
|
||||
|
||||
If you prefer building against a local kernel source tree (e.g. for development or debugging), point `--kdir` at it. The script then runs natively without spinning up a container:
|
||||
|
||||
```bash
|
||||
./kmod/build.py --kdir ~/kernels/android14-6.1 --kmi android14-6.1
|
||||
```
|
||||
|
||||
You can also drop a `kmod/.env` file with `KDIR=` / `KERNEL_SRC=` / `CLANG_DIR=` (see `.env.example`) and use [`direnv`](https://direnv.net/) to load it automatically. The script picks those up via env, no flag needed.
|
||||
|
||||
## Install and test
|
||||
|
||||
```bash
|
||||
adb push vpnhide-kmod.zip /sdcard/Download/
|
||||
adb push vpnhide-kmod-<kmi>.zip /sdcard/Download/
|
||||
# Install via KernelSU-Next manager -> Modules -> Install from storage
|
||||
# Reboot
|
||||
```
|
||||
|
|
@ -100,8 +63,12 @@ adb shell "su -c 'cat /proc/vpnhide_targets'"
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
**`insmod: Exec format error`** — symvers CRC mismatch. Use the DDK build (matched symvers).
|
||||
**`insmod: Exec format error`** — symvers CRC mismatch. Rebuild via the DDK container (`./kmod/build.py --kmi <kmi>`); the container image carries matched symvers.
|
||||
|
||||
**`insmod: File exists`** — module already loaded. `rmmod vpnhide_kmod` first.
|
||||
|
||||
**kretprobe not firing** — check `dmesg | grep vpnhide` for registration messages and `/proc/vpnhide_targets` for correct UIDs. Target app UIDs change on reinstall — re-resolve via the VPN Hide app.
|
||||
|
||||
**`./kmod/build.py` says "neither podman nor docker found"** — install one (`dnf install podman` / `apt install docker.io`), or build natively against a local kernel source via `--kdir`.
|
||||
|
||||
**Bumping the DDK image tag** — single source of truth is `DDK_IMAGE_TAG` in `kmod/build.py`. Both this script and `.github/workflows/ci.yml`'s kmod matrix pin to the same value, so update both together.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ CI builds are provided for all 7 GKI generations: `android12-5.10` through `andr
|
|||
See [BUILDING.md](BUILDING.md) for the full guide (DDK Docker build, kernel source preparation, toolchain setup, `Module.symvers` generation).
|
||||
|
||||
```bash
|
||||
cd kmod && ./build-zip.py
|
||||
./kmod/build.py --kmi android14-6.1
|
||||
```
|
||||
|
||||
## Install
|
||||
|
|
|
|||
|
|
@ -1,167 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Build and package the KernelSU/Magisk kernel module zip.
|
||||
|
||||
Assembles a module staging directory so the committed module.prop stays at
|
||||
its release version while the zip carries the actual build version (git describe).
|
||||
|
||||
Usage:
|
||||
python3 build-zip.py --kdir /path/to/kdir --kmi android14-5.15 # explicit args
|
||||
python3 build-zip.py --kdir /path/to/kdir --kmi android14-5.15 --out custom.zip # custom output
|
||||
# Or use environment variables: KDIR and KMI (CLI args override env vars)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
from build_lib import get_build_version, make_zip, version_sort_key # type: ignore[import-not-found]
|
||||
|
||||
|
||||
# Module file names
|
||||
KMOD_C = "vpnhide_kmod.c"
|
||||
KMOD_KO = "vpnhide_kmod.ko"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Build and package the kernel module zip.")
|
||||
parser.add_argument(
|
||||
"--kdir",
|
||||
type=str,
|
||||
help="Kernel source directory (overrides KDIR or KERNEL_SRC env var)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--kmi",
|
||||
type=str,
|
||||
help="Kernel module interface variant (e.g., android14-5.15) for module.prop (overrides KMI env var)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
type=str,
|
||||
help="Output zip filename (default: vpnhide-kmod-<kmi>.zip)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clang-dir",
|
||||
type=str,
|
||||
help="Clang binaries directory (overrides CLANG_DIR env var or auto-detects)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
kmod_dir = Path(__file__).resolve().parent
|
||||
os.chdir(kmod_dir)
|
||||
|
||||
# Resolve kdir: CLI arg > KDIR env var > KERNEL_SRC env var
|
||||
if args.kdir:
|
||||
kdir = args.kdir
|
||||
kdir_src = "--kdir CLI argument"
|
||||
else:
|
||||
kdir = os.environ.get("KDIR")
|
||||
kdir_src = "KDIR env var"
|
||||
if not kdir:
|
||||
kdir = os.environ.get("KERNEL_SRC")
|
||||
kdir_src = "KERNEL_SRC env var"
|
||||
if not kdir:
|
||||
print("Error: --kdir argument, KDIR, or KERNEL_SRC env var is required", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Using kdir from {kdir_src}: {kdir}")
|
||||
|
||||
# Resolve kmi: CLI arg > KMI env var
|
||||
if args.kmi:
|
||||
kmi = args.kmi
|
||||
kmi_src = "--kmi CLI argument"
|
||||
else:
|
||||
kmi = os.environ.get("KMI")
|
||||
kmi_src = "KMI env var"
|
||||
if not kmi:
|
||||
print("Error: --kmi argument or KMI env var is required", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Using kmi from {kmi_src}: {kmi}")
|
||||
|
||||
# Set up environment for make
|
||||
os.environ["KERNEL_SRC"] = kdir
|
||||
|
||||
# Resolve clang_dir: CLI arg > CLANG_DIR env var > auto-detect
|
||||
clang_dir = None
|
||||
clang_dir_src = None
|
||||
if args.clang_dir:
|
||||
clang_dir = args.clang_dir
|
||||
clang_dir_src = "--clang-dir CLI argument"
|
||||
else:
|
||||
clang_dir = os.environ.get("CLANG_DIR")
|
||||
if clang_dir:
|
||||
clang_dir_src = "CLANG_DIR env var"
|
||||
else:
|
||||
# Auto-detect: In CI, clang is at /opt/ddk/clang/clang-r*/bin
|
||||
clang_base = Path("/opt/ddk/clang")
|
||||
if clang_base.exists():
|
||||
clang_dirs = sorted(
|
||||
(d for d in clang_base.iterdir() if d.is_dir() and d.name.startswith("clang-")),
|
||||
key=lambda p: version_sort_key(p.name),
|
||||
)
|
||||
if clang_dirs:
|
||||
clang_dir = str(clang_dirs[-1] / "bin")
|
||||
clang_dir_src = "auto-detected from /opt/ddk/clang"
|
||||
if clang_dir:
|
||||
os.environ["CLANG_DIR"] = clang_dir
|
||||
print(f"Using clang-dir from {clang_dir_src}: {clang_dir}")
|
||||
else:
|
||||
print("Warning: clang-dir not set, using system PATH", file=sys.stderr)
|
||||
|
||||
# Build the kernel module — let make decide whether anything needs
|
||||
# rebuilding; its dependency tracking covers all sources, headers,
|
||||
# and the kernel .config, not just vpnhide_kmod.c.
|
||||
print("Building kernel module...")
|
||||
subprocess.run(["make", "strip"], check=True)
|
||||
|
||||
# Assemble the module staging directory so the committed module.prop
|
||||
# stays at its release version while the zip carries the actual build
|
||||
# version (git describe).
|
||||
staging = kmod_dir / "module-staging"
|
||||
if staging.exists():
|
||||
shutil.rmtree(staging)
|
||||
shutil.copytree(kmod_dir / "module", staging)
|
||||
shutil.copy(kmod_dir / KMOD_KO, staging / KMOD_KO)
|
||||
|
||||
# Get build version
|
||||
build_version = get_build_version(kmod_dir.parent)
|
||||
|
||||
# Stamp version into module.prop
|
||||
module_prop = staging / "module.prop"
|
||||
content = module_prop.read_text(encoding="utf-8")
|
||||
content = re.sub(r"^version=.*", f"version=v{build_version}", content, flags=re.MULTILINE)
|
||||
# Add gkiVariant and updateJson
|
||||
content = re.sub(r"^gkiVariant=.*", f"gkiVariant={kmi}", content, flags=re.MULTILINE)
|
||||
if not re.search(r"^gkiVariant=", content, flags=re.MULTILINE):
|
||||
content = content.rstrip() + f"\ngkiVariant={kmi}\n"
|
||||
update_json_url = f"https://raw.githubusercontent.com/okhsunrog/vpnhide/main/update-json/update-kmod-{kmi}.json"
|
||||
content = re.sub(r"^updateJson=.*", f"updateJson={update_json_url}", content, flags=re.MULTILINE)
|
||||
if not re.search(r"^updateJson=", content, flags=re.MULTILINE):
|
||||
content = content.rstrip() + f"\nupdateJson={update_json_url}\n"
|
||||
module_prop.write_text(content, encoding="utf-8")
|
||||
print(f"Stamped module.prop version=v{build_version} gkiVariant={kmi}")
|
||||
|
||||
# Create zip in parent directory (workspace root for CI)
|
||||
out_zip = kmod_dir.parent / (args.out if args.out else f"vpnhide-kmod-{kmi}.zip")
|
||||
if out_zip.exists():
|
||||
out_zip.unlink()
|
||||
|
||||
make_zip(staging, out_zip)
|
||||
shutil.rmtree(staging)
|
||||
|
||||
print()
|
||||
print(f"Built: {out_zip.name}")
|
||||
size_kb = out_zip.stat().st_size / 1024
|
||||
print(f" {out_zip} ({size_kb:.1f} KB)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
358
kmod/build.py
Executable file
358
kmod/build.py
Executable file
|
|
@ -0,0 +1,358 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Build the vpnhide kernel module zip — single entry point for both CI
|
||||
and local builds.
|
||||
|
||||
Two modes, picked automatically:
|
||||
|
||||
1. **Native build** — this Python process compiles `vpnhide_kmod.ko` and
|
||||
packages the zip directly. Used when:
|
||||
|
||||
- `--inside-container` is passed (CI does this for clarity), OR
|
||||
- `--kdir` is passed / `KDIR`|`KERNEL_SRC` is in env (local kernel
|
||||
source build via direnv), OR
|
||||
- `/opt/ddk/clang` is present (we're already inside the
|
||||
`ghcr.io/ylarod/ddk-min` image — auto-detects kdir + clang under
|
||||
`/opt/ddk`).
|
||||
|
||||
2. **Container build** — this Python process spawns podman/docker,
|
||||
bind-mounts the repo, and re-invokes itself with
|
||||
`--inside-container`. Used when none of the native conditions apply,
|
||||
i.e. the typical local `./kmod/build.py --kmi android14-6.1`
|
||||
workflow on a developer machine.
|
||||
|
||||
Either way the output is `vpnhide-kmod-<kmi>.zip` at the repo root,
|
||||
identical between CI and local.
|
||||
|
||||
Examples:
|
||||
./kmod/build.py --kmi android14-6.1 # local, default
|
||||
./kmod/build.py --all # every GKI variant
|
||||
./kmod/build.py --kdir ~/k/android14-6.1 --kmi android14-6.1
|
||||
# local kernel source
|
||||
|
||||
The DDK container tag (`DDK_IMAGE_TAG`) is the single source of truth for
|
||||
both this script and `.github/workflows/ci.yml`'s kmod matrix — keep them
|
||||
in sync when bumping.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
from build_lib import get_build_version, make_zip, version_sort_key # type: ignore[import-not-found]
|
||||
|
||||
|
||||
# Module file name on disk after `make`.
|
||||
KMOD_KO = "vpnhide_kmod.ko"
|
||||
|
||||
# Tag of `ghcr.io/ylarod/ddk-min:<kmi>-<TAG>`. Keep this in lockstep with
|
||||
# the same constant in `.github/workflows/ci.yml` so a bump rebuilds
|
||||
# locally and in CI from the exact same image.
|
||||
DDK_IMAGE_TAG = "20260313"
|
||||
|
||||
# Every GKI variant we publish a kmod for. Order matches the CI matrix.
|
||||
GKI_VARIANTS = (
|
||||
"android12-5.10",
|
||||
"android13-5.10",
|
||||
"android13-5.15",
|
||||
"android14-5.15",
|
||||
"android14-6.1",
|
||||
"android15-6.6",
|
||||
"android16-6.12",
|
||||
)
|
||||
|
||||
DEFAULT_KMI = "android14-6.1"
|
||||
|
||||
|
||||
# ----- Native build (in-container or local kernel-source) ------------------
|
||||
|
||||
|
||||
def detect_clang_dir() -> str | None:
|
||||
"""Pick the highest-versioned `clang-r*/bin` under `/opt/ddk/clang`,
|
||||
matching what the DDK image lays out. Used only when the user
|
||||
didn't pass --clang-dir or set CLANG_DIR."""
|
||||
clang_base = Path("/opt/ddk/clang")
|
||||
if not clang_base.is_dir():
|
||||
return None
|
||||
candidates = sorted(
|
||||
(d for d in clang_base.iterdir() if d.is_dir() and d.name.startswith("clang-")),
|
||||
key=lambda p: version_sort_key(p.name),
|
||||
)
|
||||
return str(candidates[-1] / "bin") if candidates else None
|
||||
|
||||
|
||||
def detect_kdir(kmi: str) -> str | None:
|
||||
"""`/opt/ddk/kdir/<kmi>` is laid out by the DDK image."""
|
||||
kdir = Path("/opt/ddk/kdir") / kmi
|
||||
return str(kdir) if kdir.is_dir() else None
|
||||
|
||||
|
||||
def native_build_one(
|
||||
kmod_dir: Path,
|
||||
kmi: str,
|
||||
kdir: str,
|
||||
clang_dir: str | None,
|
||||
out: Path | None,
|
||||
) -> int:
|
||||
"""Compile + package one .ko into one zip, in the current process."""
|
||||
print(f"[{kmi}] kdir={kdir}")
|
||||
print(f"[{kmi}] clang-dir={clang_dir or '(system PATH)'}")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["KERNEL_SRC"] = kdir
|
||||
if clang_dir:
|
||||
env["CLANG_DIR"] = clang_dir
|
||||
|
||||
# `make strip` does the actual kernel-module build. Let make decide
|
||||
# whether anything needs rebuilding — its dependency tracking covers
|
||||
# all sources, headers, and the kernel .config, not just our .c file.
|
||||
subprocess.run(["make", "-C", str(kmod_dir), "strip"], env=env, check=True)
|
||||
|
||||
# Stage the module skeleton from kmod/module/, drop the freshly built
|
||||
# .ko in, patch module.prop with the real build version + gkiVariant
|
||||
# + updateJson. The committed module.prop stays at the last release
|
||||
# version so PR diffs don't churn it.
|
||||
staging = kmod_dir / "module-staging"
|
||||
if staging.exists():
|
||||
shutil.rmtree(staging)
|
||||
shutil.copytree(kmod_dir / "module", staging)
|
||||
shutil.copy(kmod_dir / KMOD_KO, staging / KMOD_KO)
|
||||
|
||||
build_version = get_build_version(kmod_dir.parent)
|
||||
|
||||
module_prop = staging / "module.prop"
|
||||
content = module_prop.read_text(encoding="utf-8")
|
||||
content = re.sub(
|
||||
r"^version=.*", f"version=v{build_version}", content, flags=re.MULTILINE
|
||||
)
|
||||
if re.search(r"^gkiVariant=", content, flags=re.MULTILINE):
|
||||
content = re.sub(
|
||||
r"^gkiVariant=.*", f"gkiVariant={kmi}", content, flags=re.MULTILINE
|
||||
)
|
||||
else:
|
||||
content = content.rstrip() + f"\ngkiVariant={kmi}\n"
|
||||
update_json_url = (
|
||||
f"https://raw.githubusercontent.com/okhsunrog/vpnhide/main/update-json/"
|
||||
f"update-kmod-{kmi}.json"
|
||||
)
|
||||
if re.search(r"^updateJson=", content, flags=re.MULTILINE):
|
||||
content = re.sub(
|
||||
r"^updateJson=.*", f"updateJson={update_json_url}", content, flags=re.MULTILINE
|
||||
)
|
||||
else:
|
||||
content = content.rstrip() + f"\nupdateJson={update_json_url}\n"
|
||||
module_prop.write_text(content, encoding="utf-8")
|
||||
print(f"[{kmi}] stamped module.prop version=v{build_version} gkiVariant={kmi}")
|
||||
|
||||
out_zip = out if out else kmod_dir.parent / f"vpnhide-kmod-{kmi}.zip"
|
||||
if out_zip.exists():
|
||||
out_zip.unlink()
|
||||
make_zip(staging, out_zip)
|
||||
shutil.rmtree(staging)
|
||||
|
||||
size_kb = out_zip.stat().st_size / 1024
|
||||
print(f"[{kmi}] built {out_zip} ({size_kb:.1f} KB)")
|
||||
return 0
|
||||
|
||||
|
||||
def run_native_mode(args: argparse.Namespace, kmod_dir: Path) -> int:
|
||||
"""Resolve kdir + clang-dir from args/env/auto-detect and build each
|
||||
requested kmi natively. Multi-kmi only makes sense when kdir is the
|
||||
DDK layout `/opt/ddk/kdir/<kmi>` (auto-detected per kmi)."""
|
||||
kmis = _select_kmis(args)
|
||||
|
||||
explicit_kdir = args.kdir or os.environ.get("KDIR") or os.environ.get("KERNEL_SRC")
|
||||
explicit_clang = args.clang_dir or os.environ.get("CLANG_DIR")
|
||||
|
||||
if explicit_kdir and len(kmis) > 1:
|
||||
print(
|
||||
"error: --kdir / KDIR / KERNEL_SRC selects exactly one kernel tree, "
|
||||
"so building multiple --kmi values doesn't make sense. "
|
||||
"Drop --kdir to use auto-detection from /opt/ddk/kdir/<kmi>.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
if args.out and len(kmis) > 1:
|
||||
print("error: --out is only valid with a single --kmi", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
for kmi in kmis:
|
||||
kdir = explicit_kdir or detect_kdir(kmi)
|
||||
if not kdir:
|
||||
print(
|
||||
f"error[{kmi}]: no kernel source. Pass --kdir, set KDIR/KERNEL_SRC, "
|
||||
f"or run inside ghcr.io/ylarod/ddk-min where /opt/ddk/kdir/{kmi} exists.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
clang_dir = explicit_clang or detect_clang_dir()
|
||||
rc = native_build_one(kmod_dir, kmi, kdir, clang_dir, args.out)
|
||||
if rc:
|
||||
return rc
|
||||
return 0
|
||||
|
||||
|
||||
# ----- Container orchestration --------------------------------------------
|
||||
|
||||
|
||||
def find_runtime() -> tuple[str, bool]:
|
||||
"""(binary, is_podman). Prefer podman when both are present —
|
||||
rootless podman has the awkward SELinux/userns flags, so being
|
||||
explicit about it avoids surprises on Fedora-family hosts."""
|
||||
podman = shutil.which("podman")
|
||||
docker = shutil.which("docker")
|
||||
if podman:
|
||||
return podman, True
|
||||
if docker:
|
||||
return docker, False
|
||||
print(
|
||||
"error: neither podman nor docker found in PATH. Install one, or "
|
||||
"build natively by passing --kdir <kernel source> + --inside-container.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def container_build_one(
|
||||
runtime: str, is_podman: bool, repo_root: Path, kmi: str
|
||||
) -> None:
|
||||
image = f"ghcr.io/ylarod/ddk-min:{kmi}-{DDK_IMAGE_TAG}"
|
||||
mount_spec = f"{repo_root}:/work"
|
||||
cmd = [runtime, "run", "--rm"]
|
||||
if is_podman:
|
||||
# Rootless podman + Fedora SELinux: keep host UID inside so the
|
||||
# mount stays writable; ":Z" relabels the bind source so the
|
||||
# container can read/write it.
|
||||
cmd += ["--userns=keep-id"]
|
||||
mount_spec += ":Z"
|
||||
cmd += [
|
||||
"-v",
|
||||
mount_spec,
|
||||
"-w",
|
||||
"/work",
|
||||
image,
|
||||
"python3",
|
||||
"kmod/build.py",
|
||||
"--inside-container",
|
||||
"--kmi",
|
||||
kmi,
|
||||
]
|
||||
print(f"[{kmi}] {' '.join(cmd)}", flush=True)
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
|
||||
def run_container_mode(args: argparse.Namespace, repo_root: Path) -> int:
|
||||
if args.kdir or args.clang_dir or args.out:
|
||||
# These flags only make sense in native mode — refusing here is
|
||||
# better than silently dropping them after a 2-minute container
|
||||
# spin-up.
|
||||
print(
|
||||
"error: --kdir / --clang-dir / --out are only valid with native "
|
||||
"builds (pass --inside-container or run inside the DDK image).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
|
||||
kmis = _select_kmis(args)
|
||||
runtime, is_podman = find_runtime()
|
||||
print(f"Using {'podman' if is_podman else 'docker'} at {runtime}")
|
||||
for kmi in kmis:
|
||||
container_build_one(runtime, is_podman, repo_root, kmi)
|
||||
|
||||
print()
|
||||
print("Built artifacts (at repo root):")
|
||||
for kmi in kmis:
|
||||
out = repo_root / f"vpnhide-kmod-{kmi}.zip"
|
||||
marker = "ok" if out.is_file() else "MISSING"
|
||||
size = f"{out.stat().st_size / 1024:.1f} KB" if out.is_file() else ""
|
||||
print(f" [{marker}] {out.name} {size}")
|
||||
return 0
|
||||
|
||||
|
||||
# ----- Argument parsing ---------------------------------------------------
|
||||
|
||||
|
||||
def _select_kmis(args: argparse.Namespace) -> tuple[str, ...]:
|
||||
if args.all:
|
||||
return GKI_VARIANTS
|
||||
if args.kmi:
|
||||
return tuple(args.kmi)
|
||||
return (DEFAULT_KMI,)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build the vpnhide kernel module zip (CI + local).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--kmi",
|
||||
action="append",
|
||||
choices=GKI_VARIANTS,
|
||||
help=f"GKI variant to build (repeatable). Default: {DEFAULT_KMI}.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--all",
|
||||
action="store_true",
|
||||
help="Build every supported GKI variant (same matrix as CI).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inside-container",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Force native build in the current process. CI passes this "
|
||||
"explicitly; locally you only need it if you're inside a "
|
||||
"container (or have a kernel source set up) and the auto-"
|
||||
"detect didn't pick that up."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--kdir",
|
||||
type=str,
|
||||
help=(
|
||||
"Kernel source directory (overrides KDIR/KERNEL_SRC). Implies "
|
||||
"native mode."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clang-dir",
|
||||
type=str,
|
||||
help=(
|
||||
"Clang binaries directory (overrides CLANG_DIR; auto-detected "
|
||||
"from /opt/ddk/clang/clang-r* in DDK images)."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
type=Path,
|
||||
help="Output zip path (single --kmi only). Default: vpnhide-kmod-<kmi>.zip in repo root.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.all and args.kmi:
|
||||
parser.error("--all and --kmi are mutually exclusive")
|
||||
|
||||
kmod_dir = Path(__file__).resolve().parent
|
||||
repo_root = kmod_dir.parent
|
||||
|
||||
# Native conditions: explicit flag, explicit kernel source, or we're
|
||||
# already in a DDK image.
|
||||
native = (
|
||||
args.inside_container
|
||||
or bool(args.kdir)
|
||||
or "KDIR" in os.environ
|
||||
or "KERNEL_SRC" in os.environ
|
||||
or detect_clang_dir() is not None
|
||||
)
|
||||
if native:
|
||||
return run_native_mode(args, kmod_dir)
|
||||
return run_container_mode(args, repo_root)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
14
lsposed/native/build.rs
Normal file
14
lsposed/native/build.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
//! Build-time glue for the lsposed native cdylib (`libvpnhide_checks.so`).
|
||||
//!
|
||||
//! Only purpose right now: align ELF LOAD segments on 16 KiB so the
|
||||
//! library loads cleanly on 16 KiB-page Android devices (Pixel 8 Pro on
|
||||
//! Android 16, and any future hardware that ships with 16 KiB pages by
|
||||
//! default). NDK r28+ already does this by default, but passing the flag
|
||||
//! explicitly keeps r27 builds compatible too — defence in depth.
|
||||
//!
|
||||
//! See: <https://developer.android.com/guide/practices/page-sizes>
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rustc-link-arg=-Wl,-z,max-page-size=16384");
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"""Shared helpers for build scripts.
|
||||
|
||||
Used by kmod/build-zip.py, portshide/build-zip.py, zygisk/build-zip.py,
|
||||
Used by kmod/build.py, portshide/build-zip.py, zygisk/build.py,
|
||||
and scripts/build-version.py.
|
||||
|
||||
Stdlib-only on purpose: scripts/build-version.py is invoked from
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ Requirements:
|
|||
Build and package:
|
||||
|
||||
```bash
|
||||
./build-zip.py
|
||||
./build.py
|
||||
# Output: target/vpnhide-zygisk.zip (~180 KB)
|
||||
```
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ VPN interface prefixes: `tun`, `ppp`, `tap`, `wg`, `ipsec`, `xfrm`, `utun`, `l2t
|
|||
- `build.rs` -- drives CMake on the shadowhook submodule
|
||||
- `third_party/android-inline-hook/` -- submodule (our shadowhook fork)
|
||||
- `module/` -- KernelSU/Magisk module metadata
|
||||
- `build-zip.py` -- cross-compile + package script
|
||||
- `build.py` -- cross-compile + package script
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,14 @@ fn main() {
|
|||
println!("cargo:rustc-link-lib=static=shadowhook");
|
||||
println!("cargo:rustc-link-lib=log");
|
||||
|
||||
// Align ELF LOAD segments on 16 KiB so the cdylib loads cleanly on
|
||||
// 16 KiB-page Android devices (Pixel 8 Pro on Android 16 and any
|
||||
// future hardware that ships with 16 KiB pages by default). lld's
|
||||
// 4 KiB default would otherwise produce a startup warning, and on
|
||||
// future Androids may become a hard load failure.
|
||||
// See: <https://developer.android.com/guide/practices/page-sizes>
|
||||
println!("cargo:rustc-link-arg=-Wl,-z,max-page-size=16384");
|
||||
|
||||
// shadowhook's inline-patching code emits a libcall to `__clear_cache`
|
||||
// (I-cache flush after rewriting instructions). Rust's own
|
||||
// `compiler_builtins` doesn't export that symbol on aarch64-android,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue