Apple이 KernelKit이라는 이름으로 커널 확장 계층에 Embedded Swift 런타임을 도입한 정황과, macOS 및 iOS 27 바이너리 분석을 통해 드러난 현재 상태를 살펴봅니다.
WWDC 이후, 저는 Devon Maloney가 올린 슬라이드를 봤는데, 거기에는 Apple이 27 릴리스를 위해 “핵심 운영체제 커널의 일부를 Swift로 작성하기 시작했다”고 적혀 있었습니다. 메모리 안전한 커널을 향한 첫걸음이라는 것이죠. 이게 대체 무슨 뜻일까요?!
당연히 저는 하던 일을 멈추고 iOS 27 kernelcache를 샅샅이 뒤졌습니다. 아쉽게도 거기서 바로 뭔가 나오지는 않았습니다. 그래도 완전히 헛수고는 아니었습니다. macOS 27에서 Embedded Swift 런타임을 발견했는데, 하필이면 com.apple.kec.pthread 안에 들어 있었습니다. 그리고 루트 파일시스템을 더 뒤져 보니 Apple이 이 전체 작업에 이름까지 붙여 두었다는 사실도 알게 됐습니다. 바로 KernelKit입니다.
이걸 해부해 봅시다.
XNU의 C/C++ 코어(Mach, BSD, IOKit)는 손대지 않았습니다. Swift는 확장 계층의 특정 KernelKit kext 내부에 있는 작은 Embedded 런타임으로만 등장합니다.
macOS 27 루트 볼륨(26A5353q)에는 /System/DriverKit 바로 옆에 두 개의 kext가 있습니다.
pthread 쪽 Info.plist에서부터 흥미로워집니다.
그리고 version.plist:
즉 kernelkit.macosx27.0.internal이라는 내부 SDK가 존재하고, libpthread_kernelkit은 일반 kext와 동일한 libpthread 소스에서 빌드되는 별도의 Xcode 타깃입니다. Libm도 같은 방식입니다: ProjectName: Libm_kernelkit.
이걸 보며 DriverKit의 데자뷔가 느껴진다면, 정확히 핵심을 짚은 겁니다. /System 아래에 자체 디렉터리가 있고, 자체 SDK가 있고, 자체 Mach-O 플랫폼 상수가 있으며, 7년 뒤에 같은 플레이북을 다시 쓰고 있는 셈입니다.
이 바이너리들의 LC_BUILD_VERSION는 다음과 같이 나옵니다.
현재 Mach-O 플랫폼 enum은 24(visionOSExclaveKit)까지 올라갑니다. 공개 헤더, LLVM, loader.h 어디에도 아직 25가 무엇인지 나와 있지 않습니다. 제가 상수를 추가하기 전까지 ipsw는 그저 Platform(25)라고만 출력했습니다.
그다음에는 iOS 27 kernelcache의 pthread kext를 확인해 봤습니다. iOS 26에서 보였던, 지루할 정도로 익숙한 LC_BUILD_VERSION 부재를 예상하면서 말이죠.
이건 두 가지 이유에서 눈에 띕니다. LC_BUILD_VERSION는 Mach-O가 자신이 어떤 OS용으로 빌드되었는지 기록하는 표식이고, Apple은 이를 이름이 아니라 숫자로 추적합니다. 작년 iOS 26의 이 kext 빌드에는 이런 표식이 아예 없었으므로, 여기서 이것이 존재한다는 사실 자체가 새롭습니다. 그리고 그 숫자는 25가 아니라 26입니다. 방금 전 macOS가 보고한 값과 다르죠. Apple은 모든 OS의 커널 Swift 코드를 하나의 공용 플랫폼 아래 묶을 수도 있었겠지만, 대신 각 OS마다 자체 플랫폼을 부여했습니다. macOS는 25, iOS는 26이며, 나머지는 아래 표에 있습니다.
실제 이름을 확인하기 위해 Xcode 27 베타 링커에서 표를 직접 뽑아냈습니다. ld는 96바이트짜리 플랫폼 설명자 정적 배열을 유지하는데(Platform.cpp에서 옴), uint32 ID는 각각의 +0x20 오프셋에 있습니다. 이미 알려진 23/24(visionOS-exclaveCore/Kit) 항목에 맞춰 보정해 보니, 새로 추가된 여섯 개는 다음과 같습니다.
이 표는 30에서 끝납니다. 여섯 개 모두 27 계열에서 새로 등장했습니다. iOS 26.6의 pthread kext(libpthread-539)에는 LC_BUILD_VERSION가 아예 없습니다. 지금까지 실제 배포 바이너리에서 확인한 것은 25와 26뿐입니다.
TargetConditionals.h에는 TARGET_OS_KERNELKIT이 없고, Contents/Developer/Platforms/ 아래에도 KernelKit.platform은 없습니다. 하지만 툴체인 바이너리 안의 cstring이 몇 가지 단서를 더 제공합니다.
ld:
tapi와 swift-frontend:
OS별 변형이 여섯 개입니다. bridgeOS도 하나를 받았는데, 아무래도 Touch Bar가 iOS보다 먼저 인커널 Swift를 필요로 하는 모양입니다. TARGET_OS_KERNELKIT 전처리기 조건부도 존재합니다. 링커는 /System/KernelKit/usr/lib/swift를 검색 경로로 하드코딩하고 있는데, 이는 오늘날 pthread 안에 정적으로 구워 넣은 범위를 넘어설 정도로 커졌을 때 인커널 Swift 표준 라이브러리가 어디에 자리 잡게 될지를 보여 줍니다. 그리고 ld의 오류 문자열 kernelKit can only be used with -r, -kext and -static는 dylib나 실행 파일 출력은 없고, kext 번들과 오브젝트 파일만 있다는 점을 확인해 줍니다.
링커, TBD 스텁 도구, Swift 컴파일러는 이미 모두 이 대상을 타깃으로 삼을 수 있습니다. Apple이 아직 헤더와 SDK를 공개하지 않았을 뿐입니다.
/System/KernelKit/의 pthread 바이너리가 kernelcache에 들어가는 바로 그 물건입니다. UUID가 일치하기 때문에 알 수 있습니다.
같은 libpthread-553 소스에서 두 번 빌드된 것입니다. /System/Library/Extensions와 KDK에 실려 있는 “일반” macOS 플랫폼 빌드에는 Swift가 없습니다. 반면 KernelKit 플랫폼 빌드는 /System/KernelKit에 있고, kernelcache에 프리링크되며, Embedded Swift 런타임이 정적으로 링크되어 있습니다.
실제로 그 안에 들어 있는 것은 다음과 같습니다.
_swift_embedded_* 계열은 공개 Swift 저장소의 stdlib/public/core/EmbeddedRuntime.swift와 대응합니다. $e 맹글링 접두사는 문서화된 Embedded Swift 접두사이며(일반 Swift는 $s를 사용합니다. 이 변경은 #77923에서 활성화됐습니다), _$es16_emptyBoxStorageSi_Sitvp를 디맹글링하면 Swift._emptyBoxStorage : (Swift.Int, Swift.Int)가 됩니다.
__swift5_* 리플렉션 섹션은 하나도 없는데, 이는 Embedded Swift로서는 올바른 모습입니다. 제네릭은 모노모픽화되고 런타임 메타데이터가 없습니다. 전체 런타임 크기는 __TEXT_EXEC.__text 기준 약 2.4 KB입니다.
이게 트렌치코트를 입은 37개의 ret 명령어가 아닌지 확인하려고 IDA에서도 열어 봤습니다. swift_release는 실제 원자적 refcount 감소 루틴이며, 언더플로우 시 brk #1 트랩이 있고 0이 되면 _swift_embedded_invoke_heap_object_destroy를 호출합니다. swift_once는 spinwait가 있는 CAS 원샷입니다. swift_dynamicCast는 메타데이터 체인을 따라가고 existential을 처리하는 500바이트짜리 구현입니다. _emptyBoxStorage의 데이터는 (0, 0xFFFFFFFFFFFFFFFF)인데, 이는 불멸의 빈 박스 싱글턴입니다. 이로써 이것이 가짜가 아니라 진짜 런타임임이 확인됩니다.
com.apple.kec.Libm은 한동안 macOS kernelcache에 들어 있었습니다(26.6, 소스 버전 3312에도 있었습니다). 새로워진 점은 이것이 KernelKit SDK(Libm_kernelkit, 플랫폼 25, 소스 버전 3326)로 다시 빌드되어 /System/KernelKit으로 옮겨졌다는 것입니다. 수학 관련 심볼 65개짜리입니다: _cbrt, __sincos_stret, __ceilf16, float16 내장 함수 같은 것들 말이죠. 자체 Swift 심볼은 없습니다. 아마 Swift의 Double/Float 연산이 링크할 대상을 제공하기 위해 KernelKit 계열로 편입된 것으로 보입니다.
그다음 IDA에서 공개 Swift 진입점 swift_retain, swift_release, swift_once, swift_dynamicCast, swift_allocEmptyBox에 대한 xref를 확인했습니다. 내부 호출자는 swift_dynamicCast가 자기 정리를 위해 swift_release를 호출하는 것뿐이었습니다. 10년 묵은 pthread C 코드(_psynch_mutexwait, _bsdthread_create)는 Swift를 전혀 건드리지 않습니다.
이후 macOS kernelcache의 fileset 엔트리 370개 전체에서 다른 곳에 swift_* 참조가 있는지도 스캔해 봤습니다. 즉 이 런타임에 링크하는 다른 kext가 있는지 확인한 것입니다.
결과는 단 하나, 바로 이것들을 정의하는 그 kext뿐이었습니다. 37개 심볼 중 23개는 전역 export이지만, kernelcache 안의 다른 어떤 컴포넌트도 이를 import하지 않습니다. 런타임은 링크되어 있고, 부팅 시 로드되며, 아무 일도 하지 않은 채 대기 중입니다.
iOS 27은 한 단계 더 뒤에 있습니다. pthread와 Libm은 KernelKit 플랫폼 바이너리(플랫폼 26)이지만, Swift 런타임은 아예 링크되어 있지 않습니다. 즉 같은 libpthread-553 소스이되 빌드 설정만 다릅니다.
현재 Swift 커널 런타임은 macOS에만 있고, 참조도 없습니다. 제가 보기에는 출시 순서가 이렇습니다. 먼저 SDK와 런타임을 배포하고, 베타 사이클 한두 번 동안 아무것도 망가뜨리지 않는지 지켜본 뒤, 그다음 _swift_retain 같은 것들에 링크하는 실제 Swift 커널 컴포넌트를 넣기 시작하는 겁니다. iOS는 지금 플랫폼 배선부터 깔고, 런타임은 나중에 받는 셈이죠.
XNU 자체는 여전히 전부 C/C++입니다. KDK의 kernel.release.t6050.dSYM(t6050은 M5 Pro SoC입니다)에는 2,106개의 DWARF 컴파일 유닛이 있고, DW_AT_language를 분해해 보면 1,855개가 DW_LANG_C11, 249개가 DW_LANG_C_plus_plus_14, 2개가 DW_LANG_Mips_Assembler, 그리고 DW_LANG_Swift는 0개입니다. t6041(M4 Max)과 KASAN 빌드도 같습니다. 컴파일러는 소스 파일마다 이런 항목을 하나씩 내보내므로, 심볼 스트리핑 뒤에 이게 숨어 있을 수는 없습니다. 어떤 Swift가 오더라도, 그것은 Mach를 다시 쓰는 방식이 아니라 KernelKit 컴포넌트 형태로 올 것입니다.
그리고 Apple 내부 누군가 KernelKit.macosx.sdk를 유출해 주고 싶다면, 저는 그에 걸맞은 존중을 담아 다루겠습니다.
추신: 오래 읽어 주신 분들을 위해 덧붙이자면, 네, Swift와 exclaves에 대해서도 곧 블로그에 쓸 예정입니다. 조금만 기다려 주세요.