2026-10-25
ARM 서버에서 Palworld UE4SS 실행하기 ① — ARM64 포팅은 성공했는데 왜 로드되지 않았을까
UE4SS의 ARM64 빌드와 네이티브 후킹 테스트는 통과했다. 하지만 Docker 안의 게임은 Box64로 실행되는 x86_64 바이너리였다. 포팅 과정과 아키텍처 판단을 되짚는다.
ARM 서버라는 정보만으로는 빌드 대상을 정할 수 없었다
ARM64 Linux 서버에서 ue4ss-linux-palworld를 빌드하고 Docker의 Palworld 서버에 적용한 과정을 네 편으로 기록한다. 이 글은 ARM64 포팅 시도, 2편은 x86_64 크로스 컴파일, 3·4편은 초기화 segfault를 추적하고 검증한 과정이다. 실험 결과는 2026년 10월 25일 한국 시간 기준이다.
출발점은 프로젝트의 빌드 명령이 ARM 서버에서 동작하지 않는다는 문제였다.
cmake --build build_linux_Dev_gcc --target UE4SS
처음에는 서버의 CPU에 맞춰 UE4SS도 ARM64로 바꾸는 방향을 택했다. 실제로 AArch64 공유 라이브러리를 만들었고, ARM 명령어 해석과 네이티브 detour 테스트도 통과했다. 그러나 이 파일은 실제 게임에 로드되지 않았다.
뒤늦게 확인한 실행 구조는 다음과 같았다.
ARM64 Linux 호스트
└─ ARM64 Docker 컨테이너
└─ Box64
└─ x86_64 Palworld 서버
└─ 필요한 UE4SS도 x86_64
UE4SS는 게임 프로세스 안에서 함수 주소를 찾고 후킹하는 라이브러리다. 이번 구성에서는 호스트 CPU보다 로드 대상인 게임 실행 파일의 아키텍처가 빌드 대상을 결정했다. Box64가 일부 시스템 라이브러리를 네이티브 래퍼로 처리하는 것과, 임의의 ARM64 UE4SS를 x86_64 게임에 넣는 것은 별개의 문제다.
먼저 확인했어야 할 세 가지
호스트, 컨테이너 이미지, 게임 실행 파일을 따로 확인해야 했다. 이번 환경의 결과는 다음과 같다.
| 확인 대상 | 결과 | 의미 |
|---|---|---|
| Linux 호스트 | AArch64 | 빌드 도구가 실행되는 CPU |
| Docker 이미지 | ARM64 | 컨테이너의 기본 사용자 공간 |
| PalServer-Linux-Shipping | x86-64 ELF | UE4SS가 들어갈 게임의 아키텍처 |
확인에는 uname, docker image inspect, file 또는 readelf를 사용할 수 있다. 아래 경로는 이번 서버의 호스트 측 게임 파일 경로다.
uname -m
docker image inspect thijsvanloef/palworld-server-docker:latest \
--format '{{.Architecture}}'
file /usr/local/src/docker/palworld/palworld/Pal/Binaries/Linux/PalServer-Linux-Shipping
컨테이너가 ARM64라는 결과만 보고 마지막 검사를 생략한 것이 초기 판단의 빈틈이었다. 반대로 게임 실행 파일까지 ARM64였다면 네이티브 포팅이 필요한 작업이었을 것이다.
ARM64 포팅에서 실제로 수정한 부분
이 시도는 컴파일러 옵션 하나를 바꾸는 작업으로 끝나지 않았다. 프로젝트 곳곳에 x86 명령어와 호출 규약에 관한 가정이 들어 있었다.
CMake의 아키텍처와 실제 컴파일러를 일치시키기
Linux64라는 빌드 구성 이름과 CPU 종류를 분리했다. 플랫폼 설정이 CMAKE_SYSTEM_PROCESSOR를 x86_64로 덮어쓰는 부분을 제거해 CMake의 감지 결과를 따르게 했다.
변수 이름만 AArch64로 바꾼다고 코드가 ARM으로 변환되지는 않는다. 네이티브 컴파일러, 의존성의 아키텍처 선택, Rust 산출물까지 같은 대상을 바라봐야 한다. 이전 설정이 남은 CMake 캐시도 재구성 대상이었다.
x86 디스어셈블러와 분기 해석 교체
funchook의 ARM64 경로에는 Capstone 백엔드를 선택했다. ASMHelper에는 A64의 직접 분기 B와 함수 호출 BL을 처리하는 별도 구현을 만들었다.
핵심은 명령어에 들어 있는 26비트 상대 변위를 부호 확장하고 4배 한 뒤, 분기 명령어 자체의 주소에 더하는 것이었다. 당시 구현은 앞으로 가는 분기뿐 아니라 음수 변위, 분기 체인과 순환도 검사했다.
단순 분기처럼 보이지 않는 코드를 억지로 건너뛰지도 않았다. this 포인터를 조정하는 thunk나 레지스터를 사용하는 veneer를 잘못 생략하면 함수 진입 시 인자와 레지스터 상태가 달라질 수 있기 때문이다.
ABI와 진단 코드 분리
Unreal의 문자 타입 전제에 맞춰 ARM64 C/C++ 빌드에는 -fsigned-char를 적용했다. CPU 계열 매크로, x86 전용 intrinsic 설정, 호출 규약 표시도 아키텍처별로 나눴다.
크래시 진단 역시 x86의 RIP, RDI 같은 레지스터만 읽을 수 없었다. AArch64 경로에서는 시그널 컨텍스트의 PC, X0, X1, X2를 출력하도록 바꿨다.
x86 시그니처를 ARM 코드에 적용하지 않기
Palworld의 x86 기계어 패턴과 검증된 vtable 보정값을 ARM 코드에 그대로 적용할 수는 없었다. ARM64 실험 경로에서는 export된 심볼이나 사용자가 제공한 시그니처·주소를 사용하도록 했다.
명령어가 유효하고 정렬되어 있다는 검사만으로 실제 함수의 인자와 반환 규약까지 검증되는 것은 아니다. 따라서 이 단계의 결과를 'ARM64 Palworld 지원 완료'라고 부를 수는 없었다.
빌드와 작은 테스트는 통과했다
당시 생성한 라이브러리는 AArch64 ELF였다. 별도로 만든 테스트는 실제 ARM 함수를 호출하고, detour를 설치한 뒤 trampoline을 거쳐 원래 함수를 실행하고, 마지막으로 후킹을 해제했다.
테스트 함수에 5를 넣었을 때 원래 결과는 12, 후킹 후 결과는 24, 해제 후에는 다시 12가 나오는 것을 확인했다. 분기 해석, 정렬 오류, 순환 분기, 잘못된 명령어도 검사했다.
CTest 결과에는 다음 문장이 남았다.
ARM64 branch resolution, instruction validation and native detour tests passed.
이 결과가 증명한 범위는 ARM64 백엔드와 작은 네이티브 함수의 후킹이다. 실제 게임의 함수 주소, vtable, 이벤트 호출까지 검증한 결과는 아니었다.
Docker에서 드러난 검증 기준의 문제
처음 Docker를 실행했을 때는 예상했던 preload 오류가 없었다. 하지만 UE4SS 로그가 갱신되지 않았고, 실제로 UE4SS preload가 설정되지 않은 상태였다. 오류가 없다는 사실만으로 로드 성공을 판정하면 이 상태도 성공으로 오인할 수 있었다.
Box64에 ARM64 라이브러리를 명시적으로 지정하자 결과가 분명해졌다.
Warning, cannot pre-load /palworld/libUE4SS.so
게임 프로세스에 UE4SS가 매핑되지 않았다. 반면 기존 x86_64 라이브러리로 바꿨을 때는 다음 로드 증거가 나타났다.
[BOX64] Using emulated /palworld/libUE4SS.so
그 뒤에는 초기화 segfault가 발생했다. 이때부터 문제는 '아키텍처가 달라 로드할 수 없음'에서 '로드한 라이브러리가 초기화 중 죽음'으로 바뀌었다.
ARM64 변경을 되돌리고 남긴 것
ARM64 포팅 변경은 패치와 실험 파일로 백업한 뒤 되돌렸다. 최종 운영 코드에는 이 포팅이 남아 있지 않다. 따라서 앞에서 소개한 ARM64 테스트와 구현은 현재 운영 빌드의 사용법이 아니라, 되돌린 실험의 기록이다.
최종 방향은 ARM64 서버에서 x86_64 UE4SS를 크로스 컴파일하고, Box64가 실행하는 게임에 넣는 것이었다. 포팅에 들어가기 전에 실행 파일의 ELF 아키텍처를 확인했다면 더 일찍 도달했을 결론이다.
이후 검증은 빌드, 로드, 초기화, 실제 Lua 실행을 각각 확인하는 방식으로 바꿨다. 2편에서는 그 첫 단계인 크로스 컴파일 환경과 빌드 수정 사항을 다룬다.
연재 글
- 1편 — ARM64 포팅 시도와 게임 아키텍처 확인 (현재 글)
- 2편 — x86_64 크로스 컴파일과 Docker preload
- 3편 — file_open 이후 초기화 segfault 추적
- 4편 — Box64 콜백 슬롯 수정과 실행 검증