업스트림 소스를 --depth 1로 clone해 rust_minimal.o를 빌드하는 데는 성공했지만, 그 트리는 지금 부팅된 우분투 배포 커널과 버전이 다른 별개의 트리였다. 실제로 insmod까지 되려면 지금 실행 중인 커널 자체를 기준으로 빌드해야 한다. 그 과정에서 네 가지 장벽을 만났고, 이 글은 각 장벽의 원인과 해결, 그리고 최종적으로 rust_template.ko를 insmod/dmesg/rmmod까지 확인한 결과를 정리한다. 환경은 이전과 동일한 UBUNTU_TEST_HOST(Ubuntu 24.04.4 LTS, 커널 6.17.0-35-generic)다.
| 장벽 | 증상 | 원인 | 해결 |
|---|---|---|---|
| 1. 심볼릭 링크 | rust/ 링크가 깨짐 | HWE 메타패키지 명명 버그로 링크가 기대하는 패키지명이 실제로 존재하지 않음 | 링크를 실제 설치된 경로로 직접 재생성 |
| 2. rustc ABI | rustavailable/E0514 실패 | rustup 배포 rustc는 버전 문자열이 같아도 배포판이 소스에서 직접 빌드한 rustc와 크레이트 메타데이터가 다름 | apt의 rustc-1.82 + rust-1.82-src 패키지로 교체 |
| 3. module! 매크로 API | params 필드 미지원 에러 | 배포 커널의 kernel crate는 2026-05-27 스냅샷이라 업스트림에 나중에 추가된 params 지원이 없음 | params 필드 제거 후 재작성 |
| 4. 컴파일러 불일치 | LLVM=1로 .mod.c 컴파일 실패 | 커널 자체는 GCC 13으로 빌드됐는데 LLVM=1이 전체 툴체인을 clang으로 바꾸려 해 충돌 | LLVM=1 제거, GCC로 빌드 |
배포 커널의 Rust 지원 확인
먼저 지금 부팅된 커널 자체가 CONFIG_RUST로 빌드됐는지부터 확인한다.
$ uname -r
6.17.0-35-generic
$ grep -E "^CONFIG_RUST=|^CONFIG_RUST_IS_AVAILABLE=" /boot/config-$(uname -r)
CONFIG_RUST_IS_AVAILABLE=y
CONFIG_RUST=y
$ ls -la /lib/modules/$(uname -r)/build
/lib/modules/6.17.0-35-generic/build -> /usr/src/linux-headers-6.17.0-35-generic
CONFIG_RUST=y가 확인됐고, out-of-tree 모듈 빌드의 표준 진입점인 build 심볼릭 링크도 이미 linux-headers 패키지로 준비되어 있었다.
1단계 장벽 — 깨진 rust 심볼릭 링크
헤더 트리 안의 rust/는 별도 패키지를 가리키는 심볼릭 링크인데, linux-lib-rust-6.17.0-35-generic 패키지를 설치해도 링크가 깨져 있었다.
$ sudo apt-get install -y linux-lib-rust-6.17.0-35-generic
...
Setting up linux-lib-rust-6.17.0-35-generic (6.17.0-35.35~24.04.1) ...
$ ls -la /usr/src/linux-headers-6.17.0-35-generic/ | grep rust
lrwxrwxrwx 1 root root 49 rust -> ../linux-hwe-6.17-lib-rust-6.17.0-35-generic/rust
$ ls /usr/src/ | grep rust
linux-lib-rust-6.17.0-35-generic
링크가 기대하는 linux-hwe-6.17-lib-rust-6.17.0-35-generic 패키지는 apt에 존재하지 않는다(hwe-6.17- 접두사가 빠진 이름으로만 설치됨 — HWE 메타패키지 쪽 명명 버그로 보인다). 링크를 실제 경로로 직접 재생성했다.
$ sudo ln -sfn ../linux-lib-rust-6.17.0-35-generic/rust \
/usr/src/linux-headers-6.17.0-35-generic/rust
$ ls /usr/src/linux-headers-6.17.0-35-generic/rust/ | head -3
bindings
bindings.o
build_error.o
2단계 장벽 — rustup stable로는 rustavailable부터 실패
링크를 고친 뒤 rustup stable(1.97.1)로 rustavailable을 돌리면, 커널의 커스텀 타깃 스펙 JSON을 최신 rustc가 다른 스키마로 해석해 깨진다.
error: error loading target specification: target-pointer-width:
invalid type: string "64", expected u16 at line 7 column 32
배포 커널의 rust/kernel.o는 rustc 1.82.0 (f6e511eec 2024-10-15) (built from a source tarball)로 미리 컴파일되어 있었다(/boot/config-6.17.0-35-generic의 CONFIG_RUSTC_VERSION_TEXT로 확인 가능). rustup으로 정확히 같은 버전(1.82.0)을 설치해도 결과는 나아지지 않았다.
$ rustup toolchain install 1.82.0
$ rustup run 1.82.0 rustc --version
rustc 1.82.0 (f6e511eec 2024-10-15)
$ make RUSTC=$(rustup which --toolchain 1.82.0 rustc) ...
error[E0514]: found crate `core` compiled by an incompatible version of rustc
= note: crate `core` compiled by rustc 1.82.0 (f6e511eec 2024-10-15)
(built from a source tarball): .../libcore.rmeta
= help: please recompile that crate using this compiler
버전·커밋 해시가 같아도 크레이트 메타데이터는 호환되지 않는다. (built from a source tarball)이 힌트다 — 배포 커널은 Canonical이 소스에서 직접 빌드한 rustc로 만들어졌고, rustup 공식 바이너리와는 내부 빌드 설정이 달라 semver가 같아도 ABI가 어긋난다.
해결책은 rustup이 아니라 apt가 제공하는, 배포판이 직접 빌드한 rustc를 쓰는 것이다.
$ sudo apt-get install -y rustc-1.82 rust-1.82-src
$ rustc-1.82 --version
rustc 1.82.0 (f6e511eec 2024-10-15) (built from a source tarball)
$ ls /usr/src/rustc-1.82.0/library/core/src/lib.rs
/usr/src/rustc-1.82.0/library/core/src/lib.rs
rust-1.82-src 패키지는 소스를 커널 빌드 스크립트가 기대하는 정확한 경로(/usr/src/rustc-<version>/library)에 그대로 풀어준다. 이 조합으로 바꾸자 rustavailable과 E0514 에러가 둘 다 사라졌다.
3단계 장벽 — module! 매크로의 params 필드 미지원
4부에서 쓴 params: 필드가 있는 템플릿을 그대로 컴파일하면 이번엔 새로운 에러가 난다.
error: proc macro panicked
= help: message: Unknown key "params". Valid keys are:
["type", "name", "authors", "description", "license", "alias", "firmware"]
배포 커널의 module! 매크로는 2026-05-27 스냅샷이라 업스트림에 나중에 추가된 params 지원이 아직 없다. params 필드를 빼고 다시 작성했다.
// SPDX-License-Identifier: GPL-2.0
//! Rust kernel module template sample.
use kernel::prelude::*;
module! {
type: RustTemplate,
name: "rust_template",
authors: ["Junorion"],
description: "Rust kernel module template",
license: "GPL",
}
struct RustTemplate {
id: u32,
}
impl kernel::Module for RustTemplate {
fn init(_module: &'static ThisModule) -> Result<Self> {
let id: u32 = 1;
if id == 0 {
pr_err!("id must be greater than 0\n");
return Err(EINVAL);
}
pr_info!("Rust template loaded (id={})\n", id);
Ok(RustTemplate { id })
}
}
impl Drop for RustTemplate {
fn drop(&mut self) {
pr_info!("Rust template unloaded (id was {})\n", self.id);
}
}4단계 장벽 — LLVM=1과 GCC로 빌드된 커널의 충돌
습관적으로 붙이던 LLVM=1을 쓰면 Rust 컴파일(RUSTC)은 성공하지만, 모듈 래퍼(.mod.c) 컴파일 단계에서 clang이 GCC 전용 플래그를 이해하지 못해 실패한다.
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0) 13.3.0
You are using: Ubuntu clang version 18.1.3
RUSTC [M] rust_template.o # 여기까지는 성공
clang: error: unknown argument: '-fconserve-stack'
clang: error: unsupported argument 'bounds-strict' to option '-fsanitize='
clang: error: unsupported option '-mrecord-mcount' for target 'x86_64-unknown-linux-gnu'
이 커널은 GCC 13(x86_64-linux-gnu-gcc-13)으로 빌드됐는데, LLVM=1은 CC/LD/AR 전체를 clang으로 바꿔 .config에 박힌 GCC 빌드 정보와 충돌한다. Rust 컴파일은 RUSTC만 지정하면 되므로 LLVM=1을 빼고 GCC로 나머지를 빌드하면 된다.
최종 빌드와 insmod/rmmod 실전 검증
out-of-tree 모듈용 Makefile은 표준 형태 그대로다.
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
obj-m += rust_template.o
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
네 가지 장벽을 다 걷어낸 뒤 빌드하면 마지막까지 통과한다.
$ make RUSTC=/usr/bin/rustc-1.82 BINDGEN=$(which bindgen)
RUSTC [M] rust_template.o
MODPOST Module.symvers
CC [M] rust_template.mod.o
CC [M] .module-common.o
LD [M] rust_template.ko
BTF [M] rust_template.ko
Skipping BTF generation for rust_template.ko due to unavailability of vmlinux
$ ls -la rust_template.ko
-rw-rw-r-- 1 noble noble 681320 rust_template.ko
실제로 로드해본다.
$ sudo insmod rust_template.ko
$ lsmod | grep rust_template
rust_template 12288 0
$ sudo dmesg | tail -3
rust_template: loading out-of-tree module taints kernel.
rust_template: module verification failed: signature and/or required key missing - tainting kernel
rust_template: Rust template loaded (id=1)
$ sudo rmmod rust_template
$ sudo dmesg | tail -1
rust_template: Rust template unloaded (id was 1)
Rust template loaded (id=1)과 Rust template unloaded (id was 1) 둘 다 실제 dmesg에 그대로 찍혔다. 컴파일 검증을 넘어, 지금 이 컴퓨터가 실행 중인 커널에 Rust 모듈을 실제로 얹고 내리는 것까지 확인한 것이다.
주의사항
- 서명되지 않은 out-of-tree 모듈은
insmod시점에 커널을 taint시킨다(“module verification failed” 경고). 실서비스 환경이라면 모듈 서명 절차가 별도로 필요하다. - 여기서 겪은 네 가지 문제(심볼릭 링크, rustc 버전,
params미지원, LLVM/GCC 충돌)는 이 Ubuntu 24.04 HWE 6.17.0-35 스냅샷 한정 사례다. 다른 배포판·다른 커널 버전에서는 증상이 다르거나 아예 없을 수 있다. 핵심은 “배포 커널이 어떤 rustc로, 어떤 C 컴파일러로 빌드됐는지”를CONFIG_RUSTC_VERSION_TEXT와 dmesg/insmod 경고에서 먼저 확인하고 거기에 맞추는 것이다. - 1~4부의 코드는 업스트림 최신 마스터를 clone해 컴파일 검증한 것이라, 이 글처럼 특정 배포 커널에 얹으려면 API 드리프트(이번엔
params)를 다시 확인해야 할 수 있다. - rustup으로 설치한 툴체인은 배포 커널이 자체 빌드한 rustc와 버전 문자열이 같아도 크레이트 메타데이터가 다를 수 있다는 걸 실제로 확인했다. 배포 커널 기준으로 빌드해야 한다면 rustup보다 apt의 버전별
rustc-N.NN패키지를 먼저 찾아보는 게 지름길이다.
마무리
“컴파일이 된다”와 “이 컴퓨터에 실제로 로드된다”는 다른 수준의 검증이다. 네 가지 장벽 모두 각각 다른 층위의 버전 불일치(패키징, rustc ABI, 매크로 API, C 툴체인)였다 — Rust for Linux를 실전에 쓰려면 코드 자체보다 빌드 환경 정합성에 시간을 더 쓰게 될 가능성이 높다.