인프라

[문제 해결] 라즈베리파이 USB 가젯 설정: `echo`의 함정과 `printf`

하루이2222 2024. 12. 11. 19:45

라즈베리파이로 USB 가젯(Gadget), 특히 키보드나 마우스 같은 HID 장치를 만드는 프로젝트를 진행하다 보면 이런 막막한 상황에 부딪히곤 합니다. 분명히 모든 단계를 똑같이 따라 했는데도, 정작 PC에 연결하면 장치가 인식되지 않거나 "먹통"이 되는 문제입니다.

 

이 문제의 주범은  기본적인 명령어 하나일 가능성이 높습니다. 바로 echo -ne 입니다.

문제의 코드: echo -ne로 Report Descriptor 작성하기

USB HID 장치를 설정할 때, 가장 핵심적인 부분은 리포트 디스크립터(Report Descriptor)를 시스템 파일에 써주는 과정입니다. 이 디스크립터는 장치가 어떤 종류의 데이터를(예: 키보드 키 눌림, 마우스 좌표) 어떻게 보낼지 정의하는 일종의 '설명서'이며, 바이너리 데이터 형태로 작성되어야 합니다.

인터넷에서 흔히 볼 수 있는 문제의 코드는 다음과 같습니다.

# 🔴 문제가 발생할 가능성이 높은 코드
echo -ne "\\x05\\x01\\x09\\x06\\xA1\\x01...\\xC0" | sudo tee functions/hid.usb0/report_desc

이 코드의 의도는 \x05, \x01과 같은 16진수 이스케이프 시퀀스를 실제 바이트 값으로 변환하여 report_desc 파일에 쓰는 것입니다. 하지만 이 코드는 종종 실패합니다.

실패의 원인: echo는 약속을 지키지 않는다

echo 명령어는 셸 스크립트에서 가장 기본적이지만, 그 동작 방식은 보기보다 복잡하고 사용하는 셸(Shell)에 따라 일관되지 않습니다.

  1. 셸 의존성: echo -e 옵션은 백슬래시 이스케이프를 해석하라는 의미지만, 이 기능은 POSIX 표준이 아닙니다. 즉, 모든 셸이 이 옵션을 동일하게 지원한다고 보장할 수 없습니다.
  2. bash vs dash: 우리가 흔히 사용하는 bash 셸은 \xHH (16진수 바이트) 같은 확장된 시퀀스를 잘 해석합니다. 하지만 Raspberry Pi OS를 포함한 많은 데비안 계열 리눅스의 기본 시스템 셸(/bin/sh)은 dash입니다. dashecho\xHH 형식을 알지 못합니다.
  3. 결과: 손상된 디스크립터: dash 셸 환경에서 위 코드가 실행되면, \x05하나의 바이트(0x05)가 아닌, 네 개의 문자('\', 'x', '0', '5')로 파일에 기록됩니다.
    • 의도한 데이터 (정상): 05 01 A1 01 ... (바이너리 값)
    • 실제 기록된 데이터 (손상): \x05\x01\xA1\x01... (텍스트 문자열)

이렇게 텍스트로 손상된 '설명서'는 USB 호스트(PC)가 해석할 수 없으므로, 결국 장치 인식에 실패하게 되는 것입니다.

해결책:  printf 사용

이 문제를 해결하는 가장 확실하고 올바른 방법은 echo 대신 printf를 사용하는 것입니다.

  • printf란?: printf는 이름 그대로 포맷팅된(formatted) 출력을 위해 만들어진 표준 유틸리티입니다. \xHH를 포함한 다양한 이스케이프 시퀀스를 처리하는 방식이 모든 표준 셸에서 일관되게 동작합니다.

다음은 올바른 코드입니다.

# 🟢 안정적이고 올바른 코드
printf "\x05\x01\x09\x06\xA1\x01...\\xC0" | sudo tee functions/hid.usb0/report_desc

여기서 한 단계 더 나아가, sudo와 리디렉션(>) 문제를 가장 확실하게 해결하는 방법은 bash를 명시적으로 호출하는 것입니다.

 

# ✅ 가장 확실하고 권장되는 방법
sudo bash -c 'printf "\x05\x01\x09\x06\xA1\x01...\\xC0" > functions/hid.usb0/report_desc'

이 방식은 현재 사용자의 셸이 무엇이든 상관없이, printf를 완벽하게 이해하는 bash를 루트 권한으로 실행하여 명령을 처리하므로 가장 신뢰성이 높습니다.

결론

셸 스크립트에서 단순 텍스트를 출력할 때는 echo를 써도 괜찮지만, \n, \t 또는 \xHH와 같은 이스케이프 시퀀스가 포함된 데이터를 다룰 때는  printf를 사용 하는것이 좋다.