vpnhide/kmod/BUILDING.md
okhsunrog e35cf1a6b9 refactor: overhaul kmod build system, fix kernel module bugs
Build system:
- Replace hardcoded paths in Makefile with env vars (KERNEL_SRC, CLANG_DIR)
- Add .env.example and .envrc for direnv-based config
- Simplify build-zip.sh to delegate to make instead of duplicating build command
- Rewrite BUILDING.md: 5-step happy path with direnv, standalone prep as appendix
- Remove redundant quick-reference script and step 7 (manual module.lds hack)

Kernel module (vpnhide_kmod.c):
- Fix fib_route_seq_show hook: save seq_file pointer and buffer position in entry
  handler instead of reading regs->regs[0] in return handler (which holds the
  return value on arm64, not the original argument). Rewrite buffer scanning as
  clean forward iteration with memmove compaction.
- Remove dead SIOCGIFCONF case from dev_ioctl hook (confirmed in kernel source:
  SIOCGIFCONF goes through sock_ioctl -> dev_ifconf, not dev_ioctl on GKI 6.1)
- Fix header comment: remove false tcp4_seq_show claim, correct rtnl symbol name

Test app:
- Auto-run checks on launch (LaunchedEffect) for easier adb-driven testing
2026-04-11 18:50:18 +03:00

291 lines
8.9 KiB
Markdown

# Building vpnhide-kmod for a new device
This guide walks through building the kernel module for any Android
device with a GKI 2.0 kernel (Android 12+, kernel 5.10+). The process
was developed and tested on Pixel 8 Pro (android14-6.1) and applies
identically to any GKI generation — only the kernel branch and
Module.symvers change.
Total time: ~15 minutes once toolchain is set up.
Download size: ~500 MB (shallow kernel clone) + ~100 MB (toolchain if
not already available).
## Prerequisites
- A rooted Android device with KernelSU or Magisk (for adb root shell)
- Linux host (Arch, Ubuntu, Debian — anything with make, clang, git)
- `adb` connected to the device
- `modprobe` on the host (for extracting CRCs from .ko files)
- [`direnv`](https://direnv.net/) (for automatic env var loading)
## Step 1: Identify the GKI generation
```bash
adb shell uname -r
```
Example outputs and what they mean:
| `uname -r` output | GKI generation | Kernel branch |
|---|---|---|
| `5.10.xxx-android13-4-g...` | android13-5.10 | `android13-5.10` |
| `5.15.xxx-android14-6-g...` | android14-5.15 | `android14-5.15` |
| `6.1.xxx-android14-11-g...` | android14-6.1 | `android14-6.1` |
| `6.1.xxx-android15-8-g...` | android15-6.1 | `android15-6.1` |
| `6.6.xxx-android15-...` | android15-6.6 | `android15-6.6` |
The `androidXX` part is the GKI generation (kernel branch name), NOT
the Android version running on the device. A Pixel 7 Pro running
Android 16 still has an `android13-5.10` kernel because the generation
is frozen at manufacturing time.
## Step 2: Clone the full Pixel kernel tree
Google ships a single `android14-6.1` kernel covering all Pixels from 6 to
9a. The full tree includes kernel sources, prebuilt toolchain, device trees,
and all out-of-tree Google modules — everything needed with no extra downloads:
```bash
mkdir ~/pixel-kernel && cd ~/pixel-kernel
repo init --depth=1 \
-u https://android.googlesource.com/kernel/manifest \
-b android-gs-shusky-6.1-android16
repo sync -c --no-tags -j$(nproc)
```
Then build once to produce all artifacts:
```bash
./build_shusky.sh # or build_raviole.sh etc. — same kernel image for all
```
Set an env var pointing to the tree root — subsequent steps reference it:
```bash
export PIXEL_KERNEL_TREE=~/pixel-kernel
```
### Useful paths within the tree
| Path | Purpose |
|---|---|
| `$PIXEL_KERNEL_TREE/aosp/` | Full GKI kernel source tree — browse subsystems, understand exported APIs, read in-tree driver implementations |
| `$PIXEL_KERNEL_TREE/out/shusky/dist/kernel-headers.tar.gz` | Kernel headers for building against (extract and use as `KDIR`) |
| `$PIXEL_KERNEL_TREE/out/shusky/dist/vmlinux.symvers` | Symbol versions for `modpost` — use instead of extracting CRCs from device .ko files |
| `$PIXEL_KERNEL_TREE/out/shusky/dist/System.map` | Symbol addresses for debugging |
| `$PIXEL_KERNEL_TREE/private/google-modules/` | Reference implementations of out-of-tree modules (Kconfig, Makefile patterns) |
| `$PIXEL_KERNEL_TREE/prebuilts/clang/host/linux-x86/clang-r487747c/bin` | Bundled clang 17.0.2 — exact compiler used to build the kernel |
`raviole/dist/` and `shusky/dist/` contain identical `vmlinux.symvers` and
`Image` since it's the same kernel build — use either.
### Prepare kernel headers for module builds
```bash
mkdir -p ~/pixel-kernel-headers
tar -xzf $PIXEL_KERNEL_TREE/out/shusky/dist/kernel-headers.tar.gz \
-C ~/pixel-kernel-headers
cp $PIXEL_KERNEL_TREE/out/shusky/dist/vmlinux.symvers \
~/pixel-kernel-headers/Module.symvers
```
Then skip to step 3.
### Alternative: shallow clone (non-Pixel or different GKI generation)
If you don't need the full tree, clone just the kernel source:
```bash
BRANCH="android13-5.10" # ← replace with your generation from step 1
git clone --depth=1 -b $BRANCH \
https://android.googlesource.com/kernel/common \
~/kernel-source
```
You'll then need to prepare it manually — see [Preparing a standalone kernel source](#preparing-a-standalone-kernel-source) at the end.
---
## Step 3: Configure .env
The build system uses `direnv` to load `KERNEL_SRC` and `CLANG_DIR` from
a `.env` file. Copy the example and fill in your paths:
```bash
cd kmod/
cp .env.example .env
```
Edit `.env`:
```bash
# Pixel kernel tree approach (after extracting headers above):
KERNEL_SRC=~/pixel-kernel-headers
CLANG_DIR=~/pixel-kernel/prebuilts/clang/host/linux-x86/clang-r487747c/bin
# Or standalone kernel source approach:
# KERNEL_SRC=~/kernel-source
# CLANG_DIR=/path/to/clang/bin
```
Allow direnv to load it:
```bash
direnv allow
```
From now on, entering the `kmod/` directory automatically exports
`KERNEL_SRC` and `CLANG_DIR`. The Makefile reads both from the environment.
## Step 4: Build and package
```bash
make # builds vpnhide_kmod.ko
./build-zip.sh # builds .ko (if needed) + packages vpnhide-kmod.zip
```
## Step 5: Install and test
```bash
adb push vpnhide-kmod.zip /sdcard/Download/
# Install via KernelSU-Next manager -> Modules -> Install from storage
# Reboot
```
After reboot, verify:
```bash
# Module loaded?
adb shell "su -c 'lsmod | grep vpnhide'"
# kretprobes registered?
adb shell "su -c 'dmesg | grep vpnhide'"
# UIDs loaded?
adb shell "su -c 'cat /proc/vpnhide_targets'"
```
Pick target apps via the WebUI in KernelSU-Next manager.
---
## Preparing a standalone kernel source
If you used the shallow clone (non-Pixel path), you need to prepare the
kernel source before building. If you used the Pixel kernel tree with
extracted headers, skip this section entirely.
### Pull .config from the device
```bash
adb shell "su -c 'gzip -d < /proc/config.gz'" > ~/kernel-source/.config
```
If `/proc/config.gz` doesn't exist, use the GKI defconfig:
```bash
cd ~/kernel-source
make ARCH=arm64 LLVM=1 CC=$CLANG_DIR/clang gki_defconfig
```
### Generate Module.symvers from device .ko files
The Module.symvers file contains CRC checksums for every exported
kernel symbol. These must match the running kernel exactly
(CONFIG_MODVERSIONS):
```bash
# Pull all vendor modules from the device
mkdir -p /tmp/device-modules
adb shell "su -c 'ls /vendor/lib/modules/*.ko'" | tr -d '\r' | while read ko; do
adb shell "su -c 'cat $ko'" > "/tmp/device-modules/$(basename $ko)"
done
# Extract CRCs from all modules and build Module.symvers
for ko in /tmp/device-modules/*.ko; do
modprobe --dump-modversions "$ko" 2>/dev/null
done | sort -u -k2 | \
awk '{printf "%s\t%s\tvmlinux\tEXPORT_SYMBOL\t\n", $1, $2}' \
> ~/kernel-source/Module.symvers
echo "Generated Module.symvers with $(wc -l < ~/kernel-source/Module.symvers) symbols"
```
Expect 3000-5000 symbols. If you get 0, check that `modprobe` is
installed (`apt install kmod` or `pacman -S kmod`).
Alternative: if the device's ROM has a `-kernels` repo on GitHub
(e.g. `crdroidandroid/android_device_google_shusky-kernels`), you can
download the .ko files from there instead of pulling from device.
### Prepare headers
```bash
cd ~/kernel-source
# Create empty ABI symbol list (GKI build expects it)
touch abi_symbollist.raw
# Generate headers
make ARCH=arm64 LLVM=1 LLVM_IAS=1 \
CC=$CLANG_DIR/clang LD=$CLANG_DIR/ld.lld AR=$CLANG_DIR/llvm-ar \
NM=$CLANG_DIR/llvm-nm OBJCOPY=$CLANG_DIR/llvm-objcopy \
OBJDUMP=$CLANG_DIR/llvm-objdump STRIP=$CLANG_DIR/llvm-strip \
CROSS_COMPILE=aarch64-linux-gnu- \
olddefconfig prepare
```
**Common issue:** `make prepare` may fail on `tools/bpf/resolve_btfids`
due to host clang version mismatch. This is fine — the module can
still build without BTF. Ignore this error.
### Set UTS_RELEASE (vermagic)
KernelSU bypasses vermagic checks, so any value works if using KSU.
For Magisk or manual insmod, the vermagic must match `uname -r`.
```bash
cd ~/kernel-source
# For KernelSU users (any placeholder works):
echo '#define UTS_RELEASE "6.1.0-vpnhide"' > include/generated/utsrelease.h
echo -n "6.1.0-vpnhide" > include/config/kernel.release
# For Magisk users (must match exactly):
KVER="$(adb shell uname -r | tr -d '\r')"
echo "#define UTS_RELEASE \"$KVER\"" > include/generated/utsrelease.h
echo -n "$KVER" > include/config/kernel.release
```
Then set `KERNEL_SRC=~/kernel-source` in your `.env` and proceed to step 4.
---
## Troubleshooting
**`insmod: Exec format error`**
- Vermagic mismatch (Magisk doesn't bypass it). Set UTS_RELEASE to
exact `uname -r` value.
- Or Module.symvers CRCs don't match — re-extract from device .ko files.
**`insmod: File exists`**
- Module already loaded. `rmmod vpnhide_kmod` first.
**`modprobe --dump-modversions: no output`**
- .ko files might be stripped. Try pulling from a different path
(`/vendor/lib/modules/`, `/system/lib/modules/`,
`/lib/modules/$(uname -r)/`).
**`make prepare` fails on resolve_btfids**
- Ignore — BTF is optional. The module builds without it.
**No `/proc/config.gz`**
- Use `make gki_defconfig` instead.
**kretprobe not firing (ioctl not filtered)**
- Check `dmesg | grep vpnhide` for registration messages.
- Check `/proc/vpnhide_targets` has the right UIDs.
- The target app's UID changes on reinstall — re-resolve via WebUI.