크로스컴파일 sysroot 헤더 없음, 어떻게 채우나
크로스컴파일에서 **xxx.h: No such file or directory**가 나면, 컴파일러가 호스트 /usr/include를 보거나 sysroot 안에 타깃 헤더가 빠진 경우가 많습니다. GCC Directory Options의 --sysroot, pkg-config 크로스 컴파일의 **PKG_CONFIG_SYSROOT_DIR**로 경로를 맞추고, 보드(또는 SDK) 루트에서 **헤더·라이브러리·.pc**만 채워 넣습니다. CMake toolchain·CMAKE_SYSROOT 배선은 aarch64-cmake-toolchain에만 짧게 맡기고, 여기서는 누락 헤더를 채우는 쪽만 다룹니다. 제휴·보드 실화·가격은 없습니다.
include 경로가 꼬이는 신호는?
한 줄 답: 파일이 없다와 호스트 경로를 보고 있다를 가릅니다. --sysroot가 먹으면 표준 include·lib가 dir/usr/include, dir/usr/lib로 옮겨지고, -I/-L에 = 또는 $SYSROOT로 시작하면 그 접두가 sysroot로 치환됩니다(GCC Directory Options).
대표 신호:
| 신호 | 보통 의미 |
|---|---|
fatal error: foo.h: No such file or directory | sysroot에 헤더 없음, 또는 --sysroot/-isysroot가 안 붙음 |
-E -v / -v에 호스트 /usr/include만 보임 | 크로스 드라이버에 sysroot가 안 전달됨 |
| 헤더는 찾았는데 ABI·아키텍처가 어긋남 | 호스트 헤더/라이브러리를 집음 — “없음”보다 위험한 성공 |
pkg-config --cflags가 /usr/include/...만 반환 | .pc는 읽었지만 sysroot prefix가 안 붙음 |
확인 예(툴체인 prefix는 환경에 맞게):
# include 탐색 순서 덤프 (전처리만)
aarch64-linux-gnu-gcc --sysroot=/opt/target-sysroot -E -v -xc /dev/null
# 특정 헤더가 sysroot 어디에 있는지
find /opt/target-sysroot -name 'foo.h' 2>/dev/null
GCC 요지:
--sysroot=dir: 헤더·라이브러리의 논리 루트. 원래/usr/include를 보면dir/usr/include를 봅니다.-isysroot: 헤더만 다른 루트를 줄 때(라이브러리는--sysroot유지). 둘 다 쓰면 역할이 갈립니다.-I=/usr/include/foo또는-I$SYSROOT/usr/include/foo:=/$SYSROOT가 sysroot 접두로 바뀝니다. 절대 경로-I/usr/...는 호스트를 직접 찌를 수 있습니다.
빌드 로그에 -I/usr/include가 sysroot 없이 반복되면, 플래그 생성기(pkg-config·CMake find)가 크로스 설정을 무시한 신호입니다. CMake로 --sysroot를 자동 부착하는 쪽은 aarch64-cmake-toolchain의 CMAKE_SYSROOT만 보면 됩니다.
pkg-config는 어떻게 맞추나?
한 줄 답: **PKG_CONFIG_SYSROOT_DIR**에 sysroot를 두고, **PKG_CONFIG_LIBDIR**로 타깃 .pc 검색 경로만 가리키며, 호스트를 섞지 않으려면 PKG_CONFIG_PATH를 비웁니다. sysroot 값은 -I/-L 쪽에 끼워지고, --variable로 꺼낸 경로에는 기본적으로 안 붙습니다(autotools.info pkg-config cross-compiling, pkgconf(1)).
최소 환경 예:
export SYSROOT=/opt/target-sysroot
export PKG_CONFIG_SYSROOT_DIR=$SYSROOT
export PKG_CONFIG_LIBDIR=$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig
# multiarch 트리면 예: $SYSROOT/usr/lib/aarch64-linux-gnu/pkgconfig 도 추가
export PKG_CONFIG_PATH=
검증:
pkg-config --exists libfoo && echo ok
pkg-config --cflags libfoo
pkg-config --libs libfoo
--cflags/--libs에 **$SYSROOT가 앞에 붙은 -I/-L**이 보이면 prefix 주입이 동작 중인 것입니다. 호스트 /usr/lib/pkgconfig의 .pc가 그대로 나오면 PKG_CONFIG_LIBDIR·PATH부터 다시 잡습니다.
실무 메모:
- 래퍼: 호스트용 pkg-config와 타깃용 설정을 섞지 않으려면
${CHOST}-pkg-config래퍼에서 위 변수를 export한 뒤 본체를 호출하는 패턴이 문서에 나옵니다. --variable=prefix등: sysroot가 안 붙을 수 있습니다. 크로스 빌드에서 그 값을 컴파일 플래그처럼 쓰면 호스트 경로가 새어 나갑니다..pc파일 자체:prefix=/usr처럼 타깃 레이아웃을 유지하고, 호스트 절대 경로를.pc에 하드코딩하지 않습니다. 검색은LIBDIR, 경로 접두는SYSROOT_DIR이 맡습니다.- Meson/CMake: 크로스 파일·toolchain에서 **타깃 pkg-config 바이너리(또는 래퍼)**를 가리키게 하는 편이, 전역 env만 믿는 것보다 안전합니다. CMake 쪽 find 모드는 위 toolchain 글의
CMAKE_FIND_ROOT_PATH_MODE_*를 따릅니다.
보드 이미지에서 무엇을 복사하나?
한 줄 답: sysroot는 타깃 루트 파일시스템의 개발에 필요한 부분입니다. 런타임 루트 전체를 통째로 옮기기보다, 레이아웃을 유지한 채 헤더·링크용 라이브러리·pkg-config를 채웁니다. SDK/sysroot를 배포판이 이미 주면 그걸 쓰고, 보드 루트(또는 루트fs 이미지)에서 채울 때도 같은 트리 규칙을 따릅니다.
보통 복사·동기화 대상(경로는 sysroot 기준):
| 경로 | 역할 |
|---|---|
usr/include/ | C/C++ 헤더(및 패키지별 하위 디렉터리) |
lib/, usr/lib/ | 공유 라이브러리·링커 스크립트·.so 심볼릭 링크 |
usr/lib/*/pkgconfig/, usr/share/pkgconfig/ | .pc (아키텍처 트리면 multiarch libdir 포함) |
(필요 시) usr/lib/*.a, 정적 링크용 아카이브 | 정적 링크할 때만 |
(필요 시) lib64 / usr/lib/<triplet>/ | 타깃 배포가 multiarch·lib64를 쓸 때 그대로 |
의도적으로 빼도 되는 것(빌드 sysroot 목적):
/home,/var/log, 캐시·저널, 컨테이너 런타임 상태- 디바이스 노드 전체(
/dev), 불필요한/proc·/sys마운트 내용 -dev/-devel없이 런타임.so만 있는 이미지에서 헤더를 기대하는 것 — 헤더는 개발 패키지·SDK·소스 설치 트리가 있어야 생깁니다
동기화 예(호스트에서, 권한·심볼릭 링크 유지):
SYSROOT=/opt/target-sysroot
ROOTFS=/mnt/target-rootfs # 마운트한 루트fs 또는 보드 NFS 루트
mkdir -p "$SYSROOT"
rsync -a --delete \
--include='usr/include/***' \
--include='lib/***' \
--include='usr/lib/***' \
--include='usr/share/pkgconfig/***' \
--include='usr/' --include='lib/' --include='usr/share/' \
--exclude='*' \
"$ROOTFS/" "$SYSROOT/"
필터는 배포 레이아웃에 맞게 조정합니다. 핵심은 **타깃에서 /usr/include/foo.h였으면 sysroot에서도 …/usr/include/foo.h**가 되게 하는 것입니다. --sysroot는 그 논리 루트만 바꿉니다.
채운 뒤:
test -f "$SYSROOT/usr/include/stdio.h" && echo libc-headers-ok
# 의존 패키지 헤더·.pc가 실제 쓰는지 한 번 더
pkg-config --exists libfoo && pkg-config --cflags libfoo
aarch64-linux-gnu-gcc --sysroot="$SYSROOT" -E -xc - <<'EOF'
#include <foo.h>
EOF
Yocto/Buildroot 등 이미 sysroot·SDK를 내보내는 빌드 시스템은 보드에서 수동 복사보다 그 산출물을 쓰는 편이 문서·버전과 맞습니다. 이 글의 복사는 “이미지에 헤더가 있는데 호스트 sysroot만 비어 있을 때”의 최소 절차입니다.
자주 묻는 질문
Q. 헤더만 복사하고 .so는 안 넣어도 되나요?
전처리·문법 검사만이면 헤더만으로도 충분할 수 있으나, 링크 단계에서 .so/링커 스크립트·종종 .pc의 -l이 필요합니다. include 오류를 고친 다음 바로 undefined reference가 나면 라이브러리·심볼릭 링크 트리를 의심합니다.
Q. -I에 호스트 경로를 직접 주면 안 되나요?
당장 컴파일은 될 수 있어도 타깃과 다른 헤더를 먹습니다. GCC가 안내한 =/…·$SYSROOT 형태나 --sysroot 표준 경로를 쓰는 편이 안전합니다.
Q. PKG_CONFIG_SYSROOT_DIR만 켜면 충분한가요?
충분하지 않은 경우가 많습니다. 어느 .pc를 읽을지(LIBDIR/PATH)와 읽은 경로에 접두를 붙일지(SYSROOT_DIR)는 별개입니다. 호스트 .pc를 읽으면 접두만 붙어 이상한 경로가 됩니다.
Q. CMake toolchain은 여기서 다시 짜나요?
아닙니다. CMAKE_SYSROOT·find 모드·컴파일러 지정은 aarch64-cmake-toolchain만 보고, 여기서는 그 sysroot 디렉터리를 무엇으로 채울지에 집중합니다.
정리
- 신호: “헤더 없음” vs “호스트 include를 봄” —
gcc --sysroot=… -E -v와find $SYSROOT -name '*.h'로 가릅니다. - pkg-config:
PKG_CONFIG_SYSROOT_DIR+ 타깃PKG_CONFIG_LIBDIR,PKG_CONFIG_PATH=—--cflags에 sysroot가 붙는지 확인합니다. - 채우기: 보드/루트fs·SDK에서
usr/include,lib/usr/lib,pkgconfig를 레이아웃 유지로 복사하고, 런타임 전용 이미지에-dev헤더가 없는지는 먼저 확인합니다. - CMake 배선은 재탕하지 않고 aarch64-cmake-toolchain으로만 연결합니다.
근거: GCC Directory Options (--sysroot, -isysroot), pkg-config cross-compiling, pkgconf(1) PKG_CONFIG_SYSROOT_DIR.