Skip to content

Instantly share code, notes, and snippets.

@benelog
Created April 10, 2026 13:23
Show Gist options
  • Select an option

  • Save benelog/7ccc6bc74af3d4a7b86f078aef4e2f53 to your computer and use it in GitHub Desktop.

Select an option

Save benelog/7ccc6bc74af3d4a7b86f078aef4e2f53 to your computer and use it in GitHub Desktop.
Dell XPS 13 9350 (Lunar Lake) 웹캠 패치 — Intel CVS SET_HOST_IDENTIFIER fix

웹캠 패치 완성까지의 과정

Dell XPS 13 9350 (Lunar Lake, OV02C10 센서) 웹캠 비작동 문제를 해결하기까지의 기록. 3/6~4/10, 약 1개월간 7+ 세션.


Phase 1: 증상 파악 (3/6)

Dell XPS 13 9350 (Lunar Lake)에서 웹캠 비작동. /dev/video0은 v4l2loopback 가상 디바이스일 뿐, 실제 카메라 없음. dmesg 에러 체인:

int3472-discrete: GPIO type 0x02 unknown
ov02c10: failed to find sensor: -6
intel_cvs: set_host_identifier cmd failed

Phase 2: 잘못된 가설 추적 (3/6 ~ 3/8 세션 5-6)

INT3472 GPIO type 0x02를 원인으로 보고, discrete.cOVTI02C1 + 0x02 → DOVDD 매핑을 추가하는 DKMS 패치(int3472-ov02c10-fix/1.0)를 작성. 빌드·설치까지 했으나 재부팅 후에도 동일 증상. DKMS 빌드/설치 불일치 문제도 겪음.

Phase 3: Root Cause 번복 (3/8 세션 7)

ACPI NVS 메모리를 직접 읽어 핵심 반전 발견:

  • GPIO type 0x02 unknownHIMX1092(다른 센서)용이지 OVTI02C1 무관
  • LCHS=1 (Connected Camera 모드)에서 OVTI02C1은 INT3472가 아닌 Intel CVS 경로로 전원 공급
  • 진짜 원인: CVS 드라이버가 프로토콜 1.0 미지원SET_HOST_IDENTIFIER 무조건 호출 → -EIO → probe 실패 → 센서 전원 미공급

INT3472 DKMS 패치 폐기, 방향 전환.

Phase 4: 새 커널 대기 및 CVS 소스 분석 (3/16)

커널 6.17.0-1014 확인했으나 CVS 관련 수정 미포함. CVS 드라이버 소스 패치 작업으로 전환.

Phase 5: CVS DKMS 패치 + 해결 (4/4)

업스트림 intel/vision-drivers HEAD(cf5844d)를 베이스로 1줄 패치 적용:

// 변경 전: 무조건 호출 → 프로토콜 1.0에서 -EIO
ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);

// 변경 후: magic_num_support(프로토콜 2.0+)에서만 호출
if (icvs->magic_num_support) {
    ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);
}

DKMS vision-driver/1.0.0으로 빌드 + 권한 설정(video 그룹, udev 규칙) + GStreamer icamerasrc1920x1080 캡처 성공.

Phase 6: 업스트림 PR 제출 (4/10)

바이너리 역어셈블(objdump)로 Ubuntu 6.17.0-1017 패키지에 수정 미포함을 확인. intel/vision-drivers#35로 PR 제출. 업스트림 반영 전까지 DKMS 유지 필요.


Dead Ends

시도 결과
INT3472 DKMS 패치 (HID=OVTI02C1) INT3472:00의 센서가 HIMX1092이므로 무효
커널 6.11/6.14 다운그레이드 동일 root cause
media-ctl + v4l2-ctl --stream-mmap 직접 캡처 STREAMON 실패 (PSys 경유 필요)
libcamera (cam --list) IPU7 pipeline handler 미존재

관련 파일

파일 설명
cvs-src/vision-drivers-upstream/ 패치 적용된 CVS 드라이버 소스
install-vision-dkms.sh DKMS 설치 스크립트
reverse-engineering.md 커널 모듈 바이너리 역어셈블 비교 가이드
maintenance-checklist.md 커널/모듈 업데이트 시 유지보수 체크리스트
#!/bin/bash
set -e
SRC="/home/benelog/command/web-cam/cvs-src/vision-drivers-upstream"
DEST="/usr/src/vision-driver-1.0.0"
echo "=== Intel Vision Driver DKMS 설치 ==="
# 기존 DKMS 등록 제거 (있으면)
if dkms status vision-driver/1.0.0 2>/dev/null | grep -q "vision-driver"; then
echo "[1/5] 기존 DKMS 등록 제거..."
sudo dkms remove vision-driver/1.0.0 --all 2>/dev/null || true
sudo rm -rf "$DEST"
fi
# 소스 복사
echo "[2/5] 소스 복사 -> $DEST"
sudo cp -r "$SRC" "$DEST"
sudo rm -rf "$DEST/.git"
# DKMS 등록
echo "[3/5] DKMS 등록..."
sudo dkms add vision-driver/1.0.0
# 빌드
echo "[4/5] DKMS 빌드..."
sudo dkms build vision-driver/1.0.0
# 설치
echo "[5/5] DKMS 설치..."
sudo dkms install vision-driver/1.0.0
echo ""
echo "=== 설치 완료 ==="
dkms status
echo ""
echo "재부팅 후 적용됩니다: sudo reboot"

유지보수 체크리스트 — 커널/모듈 업데이트 시

현재 동작 환경 (2026-04-04 기준)

  • 커널: 6.17.0-1017-oem
  • DKMS: vision-driver/1.0.0 (업스트림 HEAD cf5844d + SET_HOST_IDENTIFIER 패치)
  • 카메라 HAL: libcamhal-ipu7x 0~git202602091007.b1f6ebe
  • udev: /etc/udev/rules.d/50-ipu7.rules (ipu7-psys0 권한)
  • 해결 기록: findings/2026-04-04-resolution.md

체크 1: 커널 업그레이드 후

커널이 바뀌면 DKMS가 자동 재빌드하지만, 패키지 모듈도 함께 업데이트될 수 있음.

# 1. DKMS 모듈이 새 커널에 빌드되었는지
dkms status

# 2. 실제 로드된 모듈이 DKMS 빌드인지
modinfo intel_cvs | grep filename
# OK:   /lib/modules/.../updates/dkms/intel_cvs.ko.zst
# FAIL: /lib/modules/.../ubuntu/vision/intel_cvs.ko.zst (패키지 원본)

# 3. CVS probe 성공 여부
journalctl -k -b 0 --no-pager | grep -E "cvs|CVS|INTC10DE" | head -10
# "Transfer of ownership success" 확인

# 4. 센서 바인딩
journalctl -k -b 0 --no-pager | grep -E "ov02c10|OVTI02C1" | head -10
# "bind ov02c10 3-0036" 확인

# 5. 캡처 테스트
gst-launch-1.0 icamerasrc device-name=ov02c10-uf num-buffers=1 \
  ! "video/x-raw,format=NV12" ! fakesink

DKMS 빌드 실패 시

새 커널에서 헤더가 바뀌면 빌드 실패할 수 있음:

# 빌드 로그 확인
cat /var/lib/dkms/vision-driver/1.0.0/build/make.log

# 헤더 설치 확인
dpkg -l | grep linux-headers-$(uname -r)

체크 2: linux-modules-vision-* 패키지 업데이트 후

Ubuntu가 intel/vision-drivers를 리베이스하면 패키지 모듈에 우리 패치가 포함될 수 있음.

# 패키지 모듈의 SET_HOST_IDENTIFIER 수정 여부 확인
zstd -d /lib/modules/$(uname -r)/ubuntu/vision/intel_cvs.ko.zst -o /tmp/intel_cvs.ko
strings /tmp/intel_cvs.ko | grep -i "host_identifier\|magic_num"

# 패키지 버전 확인
dpkg -l | grep linux-modules-vision

패키지가 패치를 포함하면 DKMS 모듈 불필요 → 제거 가능:

sudo dkms remove vision-driver/1.0.0 --all
sudo depmod -a
sudo reboot
# 재부팅 후 위 체크 1 반복

체크 3: libcamhal-ipu7x / gstreamer1.0-icamera 업데이트 후

# 플러그인 인식 확인
gst-inspect-1.0 icamerasrc 2>&1 | head -5
# "sensors/ov02c10-uf.json loaded!" 확인

# 캡처 테스트
gst-launch-1.0 icamerasrc device-name=ov02c10-uf num-buffers=1 \
  ! "video/x-raw,format=NV12" ! fakesink

체크 4: udev 규칙

재부팅 후 /dev/ipu7-psys0 권한 확인:

stat /dev/ipu7-psys0
# 기대: crw-rw---- root:video

규칙 파일이 없으면 재생성:

echo 'SUBSYSTEM=="intel-ipu7-psys", MODE="0660", GROUP="video"' \
  | sudo tee /etc/udev/rules.d/50-ipu7.rules
sudo udevadm control --reload-rules

체크 5: libcamera 지원 추가 시

향후 libcamera에 IPU7 pipeline handler가 추가되면 cam --list에서 카메라가 보이고, Cheese 등 일반 앱에서도 사용 가능해짐:

cam --list
# 카메라가 보이면 libcamera 경유로 전환 가능

롤백

# DKMS 모듈 제거 (패키지 원본으로 복원)
sudo dkms remove vision-driver/1.0.0 --all
sudo depmod -a
sudo reboot

소스 위치

  • 패치 소스: ~/command/web-cam/cvs-src/vision-drivers-upstream/
  • DKMS 설치 소스: /usr/src/vision-driver-1.0.0/
  • 설치 스크립트: ~/command/web-cam/install-vision-dkms.sh

Intel CVS 드라이버 패치 리포트

Dell XPS 13 9350 (2024) 웹캠이 동작하지 않았던 원인과 자체 패치가 필요했던 이유를 설명한다.


1. Lunar Lake의 카메라 아키텍처

전통적인 노트북 카메라 구조

일반적인 노트북 카메라는 단순한 경로를 따른다:

센서(OV02C10) ──MIPI CSI-2──→ ISP(IPU) ──→ V4L2 ──→ 앱
       ↑
  INT3472 (전원 관리: GPIO로 dvdd/avdd 제어)

INT3472 컨트롤러가 GPIO를 통해 센서에 전원을 공급하고, 센서는 MIPI CSI-2 레인으로 ISP에 직접 연결된다. 드라이버 스택이 성숙하고 대부분의 Linux 배포판에서 잘 동작한다.

Lunar Lake의 Connected Camera (CVS) 모드

Intel Core Ultra 200V (Lunar Lake)는 Computer Vision Sensing (CVS) 이라는 새로운 카메라 서브시스템을 도입했다. AI 비전 처리를 위한 저전력 전용 컨트롤러가 추가된 구조다:

센서(OV02C10) ──MIPI CSI-2──→ IPU7 ISP ──→ Camera HAL ──→ 앱
       ↑                          ↑
  CVS 컨트롤러(INTC10DE)          │
       ↑                     소유권 이전
  USBIO 브리지(INTC10B5)    (GPIO 핸드셰이크)
       ↑
    USB I2C

CVS 모드에서는:

  1. 전원 경로가 다르다. 센서는 INT3472가 아닌 CVS 컨트롤러 경로로 전원을 받는다.
  2. 소유권 협상이 필요하다. CVS 컨트롤러가 먼저 센서를 초기화하고, I2C 프로토콜 협상을 마친 뒤, GPIO 핸드셰이크로 IPU7에 센서 소유권을 넘긴다.
  3. 프로토콜 버전 협상이 있다. CVS 컨트롤러와 센서 펌웨어 간에 매직 넘버(0xCAFEB0BA)로 프로토콜 버전(1.0 vs 2.0+)을 식별한다.

이 머신의 구성

ACPI 테이블을 직접 읽어서 확인한 이 머신의 카메라 구성:

ACPI 속성 의미
LCHS 1 Connected Camera 모드 활성
L1CL 0xFF OVTI02C1은 INT3472에 의존하지 않음
LNK1._DEP {CVSS, VIC1} CVS 컨트롤러 + USB I2C 브리지에 의존

이 설정에서 OVTI02C1(OV02C10 센서)은 전원과 초기화를 전적으로 CVS 경로에 의존한다. CVS probe가 실패하면 센서에 전원이 공급되지 않고, 모든 것이 멈춘다.


2. 무엇이 실패했는가

증상

Intel CVS driver i2c-INTC10DE:00: magic number in dev response not supported
Intel CVS driver i2c-INTC10DE:00: cvs_find_magic_num_support:Device protocol is 1.0
Intel CVS driver i2c-INTC10DE:00: cvs_common_probe:set_host_identifier cmd failed
Intel CVS driver i2c-INTC10DE:00: probe with driver Intel CVS driver failed with error -5
ov02c10 i2c-OVTI02C1:00: failed to find sensor: -6

실패 체인

CVS probe 시작
  → cvs_find_magic_num_support(): "프로토콜 1.0" 확인 (magic_num_support = false)
  → cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0): 무조건 호출
  → 프로토콜 1.0 펌웨어가 이 명령을 이해하지 못함 → -EIO
  → CVS probe 실패
  → 센서 소유권 이전 안 됨
  → ov02c10 드라이버가 I2C로 센서 접근 시도 → 전원 없음 → -ENXIO
  → 웹캠 사용 불가

근본 원인은 한 줄이다: 프로토콜 1.0 장치에 프로토콜 2.0+ 전용 명령(SET_HOST_IDENTIFIER)을 무조건 보내는 것.


3. 왜 이런 버그가 생겼는가 — 업스트림 커밋 이력 추적

Intel vision-drivers 리포의 커밋 이력을 추적하면 버그가 만들어진 과정이 보인다:

타임라인

날짜 커밋 내용 SET_HOST_IDENTIFIER 상태
2024-10-22 93244d7 SET_HOST_IDENTIFIER 명령 최초 추가 if (!i2c_shared) 가드 (부분적 보호)
2024-11-26 23f3733 magic_num_support 필드 도입, 매직 넘버 검증 추가 변경 없음
2025-03-12 69d20a1 코드 정리 중 i2c_shared 가드 제거 무조건 호출로 변경
2025-06-04 14d0181 "Fix I2C read for protocol 1.0 firmware" cvs_get_device_cap만 가드, SET_HOST_IDENTIFIER 누락
2025-11-12 a8d772f 업스트림 HEAD (현재) 여전히 무조건 호출

버그가 만들어진 두 단계

1단계 — 69d20a1 (2025-03-12): 기존 가드 제거

-  if (!icvs->i2c_shared) {
-      ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);
-      ...
-  }
+  ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);

"Remove LJCA WA | Code Cleanup" 커밋에서 코드 정리를 하면서 i2c_shared 조건 분기를 제거했다. 원래 i2c_shared는 완벽한 가드는 아니었지만, 일부 장치에서 SET_HOST_IDENTIFIER 호출을 건너뛸 수 있게 해줬다. 이 제거로 SET_HOST_IDENTIFIER가 모든 장치에서 무조건 실행되게 바뀌었다.

2단계 — 14d0181 (2025-06-04): 불완전한 수정

+  ret = cvs_find_magic_num_support(icvs);
+  if (ret)
+      goto exit;
+
+  if (icvs->magic_num_support) {
+      ret = cvs_get_device_cap(&icvs->cv_fw_capability);
+      if (ret)
+          goto exit;
+  }
+
   ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);  // ← 가드 없음!

"Fix I2C read for protocol 1.0 firmware"라는 제목으로 프로토콜 1.0 지원을 추가했다. cvs_find_magic_num_support() 함수를 만들어 프로토콜 버전을 감지하고, cvs_get_device_cap()magic_num_support 조건으로 감쌌다. 하지만 바로 아래의 SET_HOST_IDENTIFIER 호출은 같은 조건으로 감싸는 것을 빠뜨렸다.

같은 함수 안에서, 같은 패턴의 가드가 필요한 두 호출 중 하나만 수정한 전형적인 누락이다.


4. 왜 자체 패치가 필요했는가

Ubuntu 패키지의 한계

Ubuntu의 linux-modules-vision-*-oem 패키지는 Intel 업스트림의 특정 시점 스냅샷을 빌드한다. 현재 설치된 6.17.0-1017.17 패키지는 업스트림 HEAD(a8d772f, 2025-11-12)를 기반으로 하지만, 업스트림 자체에 버그가 있으므로 패키지를 아무리 업데이트해도 수정되지 않는다.

바이너리 역어셈블(objdump -d)로 두 모듈의 cvs_common_probe 함수를 비교하여 확인했다:

Ubuntu 패키지 — 가드 없음:

152a: movzx  ebx, BYTE PTR [r12+0x4c]  ; magic_num_support 로드
153c: je     1553                        ; magic_num_support==0 → cvs_get_device_cap만 스킵
1553: xor    edx, edx                   ; SET_HOST_IDENTIFIER 가드 없음
1557: mov    edi, 0x805                  ; SET_HOST_IDENTIFIER 무조건 호출
155c: call   cvs_write_i2c

DKMS (패치 적용) — 가드 있음:

155a: movzx  eax, BYTE PTR [r12+0x4c]  ; magic_num_support 재확인
156d: test   bl, bl
156f: je     12c7                        ; magic_num_support==0 → SET_HOST_IDENTIFIER 완전 스킵
1579: mov    edi, 0x805                  ; magic_num_support==1일 때만 도달
157e: call   cvs_write_i2c

noble-proposed6.17.0-1018-oem changelog에도 관련 수정 언급이 없다.

업스트림도 고쳐지지 않은 상태

2026-04-10 기준, 업스트림 intel/vision-drivers HEAD에도 이 버그가 그대로 있었다. Intel 내부 개발팀이 주로 프로토콜 2.0+ 장치로 테스트하기 때문에, 프로토콜 1.0 장치에서만 발생하는 이 문제를 발견하지 못한 것으로 추정된다.

패치 내용

-		ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);
-		if (ret) {
-			dev_err(cvs->dev, "%s:set_host_identifier cmd failed", __func__);
-			goto exit;
+		if (icvs->magic_num_support) {
+			ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);
+			if (ret) {
+				dev_err(cvs->dev, "%s:set_host_identifier cmd failed", __func__);
+				goto exit;
+			}
 		}

기존 cvs_get_device_cap() 가드와 완전히 동일한 패턴. 한 줄의 if문 추가.

DKMS (vision-driver/1.0.0)로 빌드하여 updates/dkms/intel_cvs.ko.zst에 설치했고, 이 경로가 패키지 모듈(ubuntu/vision/)보다 우선 로드된다.

업스트림 PR 제출

2026-04-10에 intel/vision-drivers#35로 수정 PR을 제출했다. 머지 후 Ubuntu 패키지에 반영되면 DKMS를 제거할 수 있다.


5. 전에는 잘 되다가 왜 갑자기 안 됐을까 — 추정

이 머신을 처음 사용하기 시작했을 때부터 웹캠이 동작하지 않았다. 하지만 Dell이 이 모델을 출시할 때는 당연히 카메라가 동작하는 상태로 테스트했을 것이다. 무엇이 바뀌었을까?

추정 1: Ubuntu OEM 커널의 vision-drivers 스냅샷 시점

가장 유력한 원인이다. Ubuntu OEM 커널 패키지가 빌드되는 시점에 따라 포함되는 vision-drivers 버전이 달라진다.

시기 업스트림 상태 SET_HOST_IDENTIFIER
~2024-08 (Ubuntu 초기 패키징) 93244d7 이전 SET_HOST_IDENTIFIER 명령 자체가 없음 → 문제없음
2024-10 ~ 2025-03 93244d7 ~ 69d20a1 이전 if (!i2c_shared) 가드 있음 → 장치에 따라 동작
2025-03 이후 69d20a1 이후 무조건 호출 → 프로토콜 1.0에서 실패
2025-06 이후 14d0181 이후 cvs_get_device_cap만 가드, SET_HOST_IDENTIFIER는 여전히 무조건 → 실패

Dell 출하 시점에 설치된 Ubuntu는 SET_HOST_IDENTIFIER가 추가되기 전(93244d7, 2024-10-22 이전)의 vision-drivers를 사용했거나, i2c_shared 가드가 있던 버전이었을 가능성이 높다. 이후 OEM 커널 업데이트로 새 스냅샷이 적용되면서 무조건 호출 버전이 들어온 것이다.

추정 2: 센서 펌웨어 업데이트

CVS 컨트롤러의 펌웨어가 업데이트되면서 프로토콜 협상 동작이 바뀌었을 가능성도 있다. 하지만 이 머신에서 CVS 펌웨어를 명시적으로 업데이트한 적이 없고, LVFS(Linux Vendor Firmware Service)를 통한 자동 업데이트도 확인되지 않았으므로 가능성은 낮다.

추정 3: 커널의 모듈 로드 순서 변경

Lunar Lake의 카메라 스택은 의존성이 복잡하다:

usbio → gpio_usbio → i2c_usbio → intel_cvs → ov02c10 → intel_ipu7_isys

커널 업데이트로 모듈 로드 순서나 ACPI 의존성 해석이 미묘하게 바뀌면, 이전에는 CVS probe가 타이밍상 성공하던 것이 실패할 수도 있다. 다만 이 경우는 -EIO가 아닌 타임아웃이나 다른 에러가 나올 가능성이 높아, 직접적 원인으로 보기는 어렵다.

종합 판단

추정 1이 가장 유력하다. Ubuntu OEM 커널 패키지가 업스트림 vision-drivers의 새 스냅샷을 가져오면서, SET_HOST_IDENTIFIER 무조건 호출 코드가 포함된 것이 직접적 원인이다.

구체적으로는 69d20a1 (2025-03-12, "Remove LJCA WA | Code Cleanup")에서 i2c_shared 가드가 제거된 시점 이후의 스냅샷이 Ubuntu 패키지에 반영된 것이 breaking change다. 그리고 14d0181 (2025-06-04, "Fix I2C read for protocol 1.0 firmware")이 이를 수정하려 했으나 불완전했다.


6. 왜 이 문제가 흔하지 않은가

이 버그가 광범위하게 보고되지 않는 이유:

  1. Lunar Lake 자체가 새로운 플랫폼이다. 2024년 하반기 출시로 Linux 사용자 기반이 아직 작다.
  2. CVS 모드는 일부 구성에서만 활성화된다. 같은 Lunar Lake라도 LCHS=0 (전통 모드)이면 CVS 드라이버를 거치지 않는다.
  3. 프로토콜 1.0이 구형이다. 최신 CVS 펌웨어는 프로토콜 2.0+을 지원하므로 SET_HOST_IDENTIFIER가 성공한다. Dell XPS 13 9350의 OV02C10은 프로토콜 1.0 펌웨어를 사용하는, 상대적으로 드문 조합이다.
  4. Windows에서는 드라이버 스택이 다르다. Intel은 Windows용 드라이버를 별도로 관리하므로, Linux 전용 업스트림 리포의 버그가 Windows에서는 나타나지 않는다.

7. 해결 방법

패치만으로는 부족하고 3가지를 모두 적용해야 동작한다.

1. CVS DKMS 패치

DKMS vision-driver/1.0.0으로 빌드하여 패키지 모듈을 오버라이드:

# 설치 스크립트
~/command/web-cam/install-vision-dkms.sh

2. 권한 설정

# video 그룹 추가 (재부팅 필요)
sudo usermod -aG video $USER

# IPU7 PSys 장치 권한 (udev 규칙)
echo 'SUBSYSTEM=="intel-ipu7-psys", MODE="0660", GROUP="video"' \
  | sudo tee /etc/udev/rules.d/50-ipu7.rules

3. 캡처 (GStreamer icamerasrc)

IPU7는 libcamera pipeline handler가 없어 cam --list에 표시되지 않는다. Intel Camera HAL + GStreamer icamerasrc로 동작:

gst-launch-1.0 icamerasrc device-name=ov02c10-uf num-buffers=3 \
  ! "video/x-raw,format=NV12" ! jpegenc ! filesink location=/tmp/test.jpg

최종 동작 상태

항목 상태
DKMS 모듈 vision-driver/1.0.0/updates/dkms/intel_cvs.ko.zst
CVS probe Transfer of ownership success
ov02c10 센서 BOUND — CSI2 port 0, 2 lanes
Video devices /dev/video1~/dev/video32 (IPU7), /dev/video0 (v4l2loopback)
캡처 1920x1080 NV12 정상

8. 현재 상태 (2026-04-10)

항목 상태
웹캠 동작 1920x1080 캡처 성공 (GStreamer icamerasrc)
업스트림 PR intel/vision-drivers#35 제출 완료
Ubuntu 패키지 반영 업스트림 머지 대기 중
DKMS 제거 가능 시점 업스트림 머지 → Ubuntu 스냅샷 갱신 → OEM 커널 릴리스 후

커널 모듈 바이너리 역어셈블 비교 가이드

이 문서는 소스 코드 없이 두 커널 모듈(.ko)의 차이를 역어셈블로 비교하는 방법을 다룬다. 2026-04-10 세션에서 Ubuntu 패키지의 intel_cvs.ko와 DKMS 패치 버전을 비교할 때 사용한 기법을 정리했다.


1. 왜 바이너리 비교가 필요한가

커널 모듈 패키지는 대부분 소스를 포함하지 않는다. Ubuntu의 linux-modules-vision-*-oem 패키지는 Intel 업스트림의 특정 시점 스냅샷을 빌드한 결과물이다. 어떤 커밋까지 포함되었는지 changelog에 명시되지 않는 경우가 많아, "이 수정이 반영되었는가?"를 확인하려면 바이너리를 직접 들여다봐야 한다.


2. 도구와 기본 개념

2.1 핵심 도구

도구 용도 설치
zstd .ko.zst 압축 해제 apt install zstd
objdump ELF 바이너리 역어셈블 apt install binutils
nm 심볼 테이블 조회 binutils에 포함
modinfo 모듈 메타데이터 확인 kmod에 포함
strings 바이너리 내 문자열 추출 binutils에 포함
md5sum 바이너리 동일성 확인 coreutils에 포함

2.2 ELF과 .ko

커널 모듈(.ko)은 ELF(Executable and Linkable Format) relocatable object 파일이다. 일반 실행 파일과 달리:

  • 재배치 가능(relocatable): 커널에 로드될 때 주소가 결정된다. 역어셈블에서 call 대상이 0x0000으로 보이는 건 빌드 시 주소가 확정되지 않았기 때문이다.
  • 섹션 구조: .text(코드), .rodata(읽기 전용 데이터/문자열), .data(전역 변수), .symtab(심볼 테이블) 등으로 구성된다.
  • 최근 Ubuntu OEM 커널은 모듈을 zstd로 압축하므로 먼저 해제가 필요하다.

2.3 x86-64 호출 규약 (System V AMD64 ABI)

Linux 커널은 System V AMD64 ABI를 따른다. 함수 호출 시 인자가 레지스터에 순서대로 들어간다:

인자 순서 레지스터 예시
1번째 rdi (32비트: edi) cvs_write_i2c(cmd, buf, len)
2번째 rsi cvs_write_i2c(cmd, buf, len)
3번째 rdx (32비트: edx) cvs_write_i2c(cmd, buf, len)
4번째 rcx
5번째 r8
6번째 r9
반환값 rax ret = cvs_write_i2c(...)

따라서 mov edi, 0x805 다음에 call이 오면, 첫 번째 인자로 0x805를 넘기는 함수 호출이다. 소스에서 SET_HOST_IDENTIFIER = 0x0805를 알고 있으면 이 패턴으로 해당 호출을 찾을 수 있다.

2.4 자주 보이는 x86-64 명령어

역어셈블 출력을 읽을 때 자주 등장하는 명령어:

명령어 의미 예시
mov 값 복사 mov edi, 0x805 → edi에 0x805 저장
movzx 작은 값을 0 확장하여 복사 movzx eax, BYTE PTR [r12+0x4c] → 1바이트 읽어서 32비트로
test 비트 AND 후 플래그만 설정 (값 안 바뀜) test eax, eax → eax가 0인지 검사
cmp 뺄셈 후 플래그만 설정 cmp al, 0x1 → al과 1 비교
je / jne 조건 점프 (equal / not equal) je 12c7 → 같으면 0x12c7로 점프
ja / jle 조건 점프 (above / less-or-equal) ja 153d → 크면 0x153d로
call 함수 호출 call 1284
xor reg, reg 레지스터를 0으로 초기화 xor edx, edx → edx = 0
push / pop 스택 저장/복원 함수 프롤로그/에필로그
lea 주소 계산 (실제 메모리 접근 안 함) lea rdi, [r12+0x4d] → 포인터 전달

2.5 C 코드와 어셈블리 대응

아래는 이번 분석에서 실제로 나타난 패턴이다.

C 코드:

if (icvs->magic_num_support) {          // [r12+0x4c]가 magic_num_support 필드
    ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);
}

가드가 있는 어셈블리 (DKMS 버전):

movzx  eax, BYTE PTR [r12+0x4c]  ; icvs->magic_num_support 로드 (1바이트)
and    ebx, 0x1                    ; bool 변환
test   bl, bl                      ; 0인지 검사
je     12c7                        ; 0이면 → SET_HOST_IDENTIFIER 건너뜀
xor    edx, edx                    ; 3번째 인자: len = 0
xor    esi, esi                    ; 2번째 인자: buf = NULL
mov    edi, 0x805                  ; 1번째 인자: SET_HOST_IDENTIFIER
call   cvs_write_i2c

가드가 없는 어셈블리 (패키지 버전):

; (magic_num_support 체크가 위에서 cvs_get_device_cap만 가드)
; je로 여기까지 점프해 옴 — SET_HOST_IDENTIFIER 앞에 추가 분기 없음
xor    edx, edx                    ; len = 0
xor    esi, esi                    ; buf = NULL
mov    edi, 0x805                  ; SET_HOST_IDENTIFIER
call   cvs_write_i2c              ; 무조건 실행됨

핵심 차이: mov edi, 0x805 직전에 test + je(조건 점프)가 있느냐 없느냐.


3. 실전 절차

3.1 모듈 추출 및 기본 비교

# 압축 해제
zstd -d -c /lib/modules/$(uname -r)/ubuntu/vision/intel_cvs.ko.zst > /tmp/pkg.ko
zstd -d -c /lib/modules/$(uname -r)/updates/dkms/intel_cvs.ko.zst > /tmp/dkms.ko

# 동일성 확인
md5sum /tmp/pkg.ko /tmp/dkms.ko
# → 체크섬이 다르면 바이너리가 다른 것

# 메타데이터 확인
modinfo /tmp/pkg.ko
modinfo /tmp/dkms.ko

# 문자열 추출로 빠르게 힌트 얻기
strings /tmp/pkg.ko | grep -i "magic_num\|set_host"

3.2 심볼 테이블 비교

nm /tmp/pkg.ko | grep -i "probe\|magic_num\|host_ident"
nm /tmp/dkms.ko | grep -i "probe\|magic_num\|host_ident"

t는 로컬 심볼, T는 글로벌 심볼. 함수 주소와 이름을 확인할 수 있다.

3.3 특정 함수 역어셈블

# Intel 문법으로 역어셈블 (AT&T 문법보다 읽기 쉬움)
objdump -d -M intel --no-show-raw-insn /tmp/pkg.ko \
  | sed -n '/<cvs_common_probe>:/,/^$/p' > /tmp/pkg_probe.asm

objdump -d -M intel --no-show-raw-insn /tmp/dkms.ko \
  | sed -n '/<cvs_common_probe>:/,/^$/p' > /tmp/dkms_probe.asm

옵션 설명:

  • -d: .text 섹션 역어셈블
  • -M intel: Intel 문법 사용 (mov dst, src 순서. 기본은 AT&T mov src, dst)
  • --no-show-raw-insn: 기계어 바이트 생략 (가독성 향상)

3.4 상수로 호출 지점 찾기

소스에서 #define 또는 enum 값을 알면 역어셈블에서 해당 상수를 검색한다:

# SET_HOST_IDENTIFIER = 0x0805
grep "0x805" /tmp/pkg_probe.asm
grep "0x805" /tmp/dkms_probe.asm

이 상수가 edi에 들어가면 첫 번째 인자이므로 함수 호출 직전이다. 그 위에 조건 분기가 있는지 확인하면 "가드가 있느냐 없느냐"를 판단할 수 있다.

3.5 diff로 구조적 차이 확인

diff /tmp/pkg_probe.asm /tmp/dkms_probe.asm

재배치 주소가 다르므로 je 1553 vs je 12c7 같은 점프 대상 차이가 대량으로 나온다. 이것은 무시하고, 명령어 자체가 추가/삭제/변경된 부분에 집중한다.


4. 구조체 필드 오프셋 읽기

BYTE PTR [r12+0x4c]에서 0x4c은 구조체 내 필드의 오프셋이다. 소스가 있으면 pahole 또는 직접 계산으로 확인할 수 있다:

# pahole: DWARF 디버그 정보에서 구조체 레이아웃 추출
pahole -C icvs_data /tmp/dkms.ko 2>/dev/null

디버그 정보가 없으면 (보통 배포 모듈은 strip됨) 소스에서 구조체 선언을 보고 필드 순서와 타입 크기로 오프셋을 계산한다. 이번 경우 magic_num_supportu8 타입이고 오프셋 0x4c에 위치함을 두 모듈에서 동일하게 확인했다.


5. 재배치와 0x0000 문제

.ko 파일의 call 대상이 0x0000이거나 바로 다음 주소로 보이는 경우가 많다. 이는 아직 재배치(relocation)가 적용되지 않은 상태이기 때문이다.

# 재배치 정보와 함께 역어셈블
objdump -d -r -M intel /tmp/pkg.ko | grep -A2 "call.*0000"

-r 옵션을 추가하면 각 call 아래에 어떤 심볼로 재배치되는지 표시된다:

127f: call   1284 <cvs_common_probe+0x104>
      1280: R_X86_64_PLT32  cvs_write_i2c-0x4

이렇게 하면 call 대상이 cvs_write_i2c임을 확인할 수 있다.


6. 주의사항

컴파일러 최적화

같은 소스라도 GCC 버전, 최적화 레벨(-O2 등), 컴파일 옵션에 따라 명령어 순서와 레지스터 선택이 달라진다. 따라서:

  • 정확히 같은 명령어 시퀀스를 기대하지 마라. 논리적 흐름(분기 구조)을 비교해야 한다.
  • 인라인 최적화로 함수 호출이 사라지거나, 루프가 풀리거나, 분기가 역전될 수 있다.
  • DKMS 빌드와 패키지 빌드의 GCC 버전이 다르면 코드 구조가 상당히 달라질 수 있다. 이번 경우는 동일 머신에서 빌드하여 차이가 적었다.

소스가 있으면 소스를 봐라

역어셈블은 소스를 구할 수 없을 때의 최후 수단이다. 소스가 있다면 diff가 훨씬 정확하고 빠르다. 이번에는 Ubuntu 패키지의 빌드 소스를 직접 구할 수 없어서 바이너리 비교가 필요했다.

false positive 주의

상수 검색으로 찾은 mov edi, 0x805가 반드시 SET_HOST_IDENTIFIER 호출은 아닐 수 있다. 같은 값이 다른 맥락에서 사용될 수 있으므로, 반드시 주변 컨텍스트(어떤 함수 안인지, 어떤 call 앞인지)를 확인해야 한다.


7. 이번 분석에서 겪은 실수와 교훈

SET_HOST_IDENTIFIER 상수를 잘못 추정

처음에 mov edx, 0x7SET_HOST_IDENTIFIER로 오인했다. 실제로는 SET_HOST_IDENTIFIER = 0x0805이고 edi에 들어가야 했다. 소스에서 상수 값과 함수 시그니처를 먼저 확인한 뒤 역어셈블을 검색해야 한다.

호출 규약과 인자 위치

cvs_write_i2c(cmd, buf, len)에서 cmdedi (1번째), lenedx (3번째)다. edx에 들어가는 0x7은 길이 값일 뿐 명령 코드가 아니었다. x86-64 호출 규약을 항상 의식해야 한다.

분기 대상 주소 vs 명령어 패턴

diff 출력에서 점프 대상 주소 차이(je 1553 vs je 12c7)가 대량으로 나온다. 이것을 하나하나 따라가면 시간만 낭비된다. 명령어 자체의 추가/삭제에 집중하고, 필요할 때만 점프 대상을 추적해야 한다.


8. 참고 자료

x86-64 어셈블리 기초

System V AMD64 ABI (호출 규약)

objdump & binutils

ELF 포맷

  • ELF Format - Linux Foundation — ELF 공식 스펙. 섹션 구조, 재배치 등을 이해할 때 참고.
  • readelf -a module.ko — ELF 헤더, 섹션, 심볼 테이블을 사람이 읽을 수 있는 형태로 출력.

커널 모듈 구조

  • Linux Kernel Module Programming Guide — 커널 모듈의 빌드, 로딩, 심볼 해석 과정을 설명. DKMS 모듈이 패키지 모듈보다 우선 로드되는 이유(updates/dkms/ 경로 우선순위)를 이해하는 데 도움.
  • DKMS Documentation — Dell이 관리하는 DKMS 프로젝트. 커널 업데이트 시 모듈 자동 빌드 메커니즘.

디버그 정보 활용

  • pahole and BTF — Andrii Nakryiko의 BTF/pahole 설명. 구조체 레이아웃을 추출하는 고급 기법.
  • objdump -d -S module.ko — 디버그 정보가 있으면 역어셈블에 소스 코드를 인터리브로 표시. DKMS 빌드 시 CONFIG_DEBUG_INFO가 켜져 있으면 사용 가능.

실전 역공학

  • Ghidra — NSA가 공개한 무료 역공학 도구. objdump보다 훨씬 강력한 디컴파일(C 코드 복원), 크로스 레퍼런스, 그래프 뷰를 제공한다. 복잡한 분석에는 이쪽이 적합.
  • Compiler Explorer (Godbolt) — C 코드를 입력하면 실시간으로 어셈블리를 보여준다. "이 C 코드가 어떤 어셈블리로 컴파일되는가"를 실험할 때 매우 유용.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment