Proven C BookEnglish GitHub

97 임베디드의 도구 상자 — 컴파일러와 그 곁의 연장들

먼저 알아야 할 것

18장 컴파일러의 지형 · 컴파일러의 지형
83장 프로그램의 기억 배치 · 임베디드의 기억

돌아보기

18장에서 세상의 C 컴파일러 중 수적으로 가장 많은 갈래가 임베디드라고 했다. 왜 그런가 — gcc 하나가 다 겨냥할 수 있지 않은가?

답. 세 가지 이유가 있다. 첫째, gcc가 모르는 칩이 있다 — 회사가 자기 명령어 집합을 만들면 그 칩을 아는 컴파일러는 그 회사가 만든다. 둘째, 인증이다. 자동차·항공·의료 기기의 안전 규격은 “이 컴파일러가 규격에 맞게 검증됐다”는 증빙을 요구하는데, 그 검증과 책임을 함께 파는 것이 전문 회사들의 사업이다. 셋째, 코드 크기다. 메모리가 수십 KiB인 칩에서는 몇 %의 크기 차이가 제품이 되느냐 마느냐를 가른다.

이 장의 필요성과 맥락

임베디드를 마지막에 따로 두는 것은 그것이 부록 같은 주제여서가 아니다 — 오히려 C 가 가장 대체 불가능한 자리다. 다만 그 세계의 연장들은 18장의 컴파일러 지형과 83장의 기억 배치를 둘 다 알아야 뜻이 통해서, 두 장을 모두 지난 지금이 가장 늦고도 가장 이른 자리다.

이 장이 끝나면

칩 위에서 도는 C를 짜는 사람들이 무엇을 들고 일하는지 본다. 벤더마다 다른 컴파일러들(IAR, Arm, TI, 마이크로칩, 르네사스, SDCC 등), 그리고 make 와 git 곁에 놓이는 연장들 — 빌드 도구, 디버그 프로브와 온칩 디버깅, 화면 없는 기계에서 로그를 보는 법, 시뮬레이터, 정적 분석과 단위 시험, 그리고 크기를 재는 도구까지. 지금 다 설치할 필요는 없다. 이름과 쓰임을 아는 것이 목적이다.

이 장에서 답할 질문

  1. 이 많은 것을 다 배워야 임베디드를 시작할 수 있는가?

97.1 임베디드 컴파일러들

지금 현역인 것들만 추린다. 이름을 알아보는 것이 목적이므로 표로 간다.

컴파일러겨냥하는 칩성격
IAR Embedded WorkbenchArm·RISC-V·8051·MSP430·AVR·RX·RL78·RH850 등 다수상용. 인증(자동차·의료)과 코드 크기로 유명
Arm Compiler for Embedded (armclang)Arm Cortex 계열상용(Keil MDK 에 포함). Clang 기반
TI Arm Clang (tiarmclang)TI 의 Arm 코어무료. Clang 기반. TI 전용 확장
MPLAB XC8 / XC-DSC / XC32마이크로칩 PIC·AVR·SAM무료 사용 가능. XC32 는 Arm·MIPS
Renesas CC-RX / CC-RL르네사스 RX·RL78상용. 르네사스 칩 전용
Green Hills Optimizing CompilersArm·PowerPC·RISC-V 등상용. 안전 인증 시장의 오랜 강자
Wind River DiabArm·PowerPC 등상용. 항공·자동차 인증
SDCC8051·STM8·Z80·6502·PDK 등 8비트자유 소프트웨어. 작은 칩들의 표준
arm-none-eabi-gcc 등 GCC 계열Arm·RISC-V·AVR·MSP430 …자유 소프트웨어. 벤더 SDK 의 기본값
Clang/LLVM 임베디드 빌드Arm·RISC-V벤더 컴파일러들의 공통 바탕

표 97.1

두 흐름이 보인다. 하나는 18장에서 본 것과 같다 — 새로 만드는 컴파일러는 거의 다 LLVM 기반이다(Arm의 armclang, TI의 tiarmclang, 마이크로칩의 새 XC32). 다른 하나는 무료화다. 예전에는 최적화를 켜려면 유료 키가 필요한 컴파일러가 흔했는데, 마이크로칩은 근래 판에서 그 제한을 없앴다.

플랫폼 노트. 칩 회사가 주는 SDK 를 먼저 본다

실무의 시작은 컴파일러를 고르는 것이 아니라 칩 회사가 주는 개발 묶음 (SDK)을 받는 것인 경우가 많다. ST 의 STM32Cube, NXP 의 MCUXpresso, 에스프레시프의 ESP-IDF, 노르딕의 nRF Connect SDK 같은 것들이다. 그 안에 컴파일러(대개 GCC 나 Clang), 링커 스크립트, 시작 코드, 주변장치 라이브러리, 디버깅 설정이 이미 짝지어 들어 있다. 처음부터 도구를 하나씩 고르는 것은 그 묶음이 왜 그렇게 생겼는지 알고 난 뒤의 일이다.

97.2 make 와 git 곁의 연장들

이제 도구 쪽이다. 임베디드 개발자의 책상에는 make 와 git 말고도 여러 연장이 있다. 하는 일별로 묶어 본다.

① 빌드를 조립하는 것. make 는 여전히 바탕이지만, 그 위에 생성기를 얹는 것이 오늘의 관행이다. CMake 가 사실상의 표준이고(빌드 파일을 만들어 주고, Ninja 라는 빠른 실행기를 뒤에 두는 조합이 흔하다), Meson 도 쓰인다. 라이브러리·보드·툴체인을 통째로 관리하는 상위 도구로는 PlatformIO(여러 보드를 한 명령으로), Zephyr 프로젝트의 west, 에스프레시프의 idf.py 가 있다. 커널처럼 설정 항목이 많은 프로젝트는 Kconfig 로 기능을 켜고 끄고, 하드웨어 구성을 디바이스 트리로 적는다.

② 칩에 넣고 멈춰 세우는 것. 프로그램을 칩에 굽고(flash) 실행 중에 멈춰 들여다보려면 디버그 프로브라는 작은 하드웨어가 필요하다. SEGGER 의 J-Link, ST 의 ST-Link, 표준 규격인 CMSIS-DAP 계열이 흔하다. 프로브와 컴퓨터 사이를 잇는 소프트웨어가 OpenOCD, pyOCD, 벤더의 전용 서버이고, 그 위에서 17장의 gdb 가 그대로 일한다 — 다만 프로그램이 다른 기계에서 도는 원격 디버깅이라는 점이 다르다. 칩과 프로브는 JTAG 이나 SWD 라는 몇 가닥짜리 배선으로 연결된다.

③ 화면 없는 기계에서 로그를 보는 것. 임베디드 보드에는 대개 화면이 없다. 그래서 printf 의 출력을 어디로 보낼지가 문제가 된다. 전통적인 답은 시리얼 포트(UART)로 내보내고 컴퓨터에서 터미널 프로그램(picocom, minicom, screen, PuTTY)으로 받는 것이다. Arm 계열에는 더 빠른 길이 둘 있다 — 디버그 회선으로 문자를 흘려보내는 ITM/SWO, 그리고 메모리 버퍼를 프로브가 훔쳐보는 SEGGER 의 RTT 다. 디버거가 호스트의 입출력을 대신해 주는 세미호스팅도 있는데, 느려서 시험용으로만 쓴다.

반례. 임베디드에서 printf 를 아무 데나 쓰기

호스트에서 습관이 된 printf 디버깅(17장)이 임베디드에서는 세 가지 문제를 부른다. 크기 — 서식 해석기 전체가 링크되어 수십 KiB 를 먹는다. 속도 — UART 로 한 글자씩 내보내면 수 밀리초가 그냥 간다. 타이밍 — 그 지연이 실시간 동작을 바꿔 버려서, 찍는 순간 버그가 사라지는 고전적 상황이 만들어진다.

그래서 실무는 가벼운 대안을 쓴다. 정수만 다루는 축소판 printf, 형식 문자열을 칩에 넣지 않고 번호만 보내는 방식, 또는 위의 RTT 처럼 거의 공짜인 통로. 17장에서 말한 “디버거를 익히라”가 임베디드에서 특히 값진 이유이기도 하다.

④ 기계 없이 돌려 보는 것. 하드웨어가 아직 없거나 여러 대를 자동으로 시험해야 할 때는 시뮬레이터를 쓴다. QEMU 는 여러 보드를 흉내 내고, Renode 는 여러 대의 기기와 네트워크까지 함께 흉내 낼 수 있어 시험 자동화 (CI)에 자주 쓰인다. 벤더의 IDE 에도 명령어 수준 시뮬레이터가 딸려 오는 경우가 많다.

⑤ 코드를 사람 대신 읽는 것. 임베디드는 한번 출하하면 고치기 어렵고 (제품이 손에서 떠난다), 그래서 정적 분석의 값이 크다. 자유 도구로는 cppcheckclang-tidy(그리고 컴파일러의 -Wall -Wextra 자체), 상용 으로는 PC-lint Plus, PVS-Studio, Polyspace, Coverity 가 흔하다. 자동차 업계에서는 MISRA C 라는 코딩 규칙 집합을 준수했는지 검사하는 기능이 특히 중요하게 쓰인다 — “쓰지 말라고 정해 둔 C 의 부분집합만 쓰기” 라는 접근이다.

⑥ 시험하는 것. 칩 위에서 도는 코드도 단위 시험을 한다. C 용으로는 Unity(와 그 빌드 도구 Ceedling), CppUTest 가 널리 쓰이고, 하드웨어에 닿지 않는 로직은 호스트에서 컴파일해 시험하는 것이 관행이다 — 이때 호스트에서는 -fsanitize=address,undefined 같은 검사기를 켤 수 있어서 (17장), 칩에서는 못 하는 검사를 대신 받는다.

⑦ 크기와 배치를 재는 것. 메모리가 작으므로 “얼마나 들어갔는가”를 늘 잰다. size, nm, objdump, readelf 같은 바이너리 도구가 기본이고, 링커가 남기는 맵 파일을 읽어 무엇이 자리를 차지하는지 본다. bloaty, puncover 처럼 그 분석을 보기 좋게 해 주는 도구도 있다. 칩에 넣을 형식으로 바꾸는 데는 objcopy(ELF → bin/hex)나 srec_cat 을 쓴다.

⑧ 신호를 눈으로 보는 것. 소프트웨어 도구만으로 안 되는 순간이 온다. 핀에서 무슨 신호가 나가는지 봐야 할 때 로직 애널라이저(자유 소프트웨어 sigrok/PulseView 가 저가 장비를 지원한다)와 오실로스코프가 등장한다. “코드는 맞는데 안 되는” 문제의 절반은 배선과 전원이라는 것이 이 세계의 오랜 격언이다.

실제 사례. Compiler Explorer — 브라우저에서 여는 컴파일러 박물관

설치가 필요 없는 도구 하나를 적어 둔다. godbolt.org 라는 웹사이트 (Compiler Explorer)는 왼쪽에 C 코드를 적으면 오른쪽에 그 코드가 어떤 기계어가 되는지를 즉시 보여 준다. 컴파일러와 판(gcc 4 부터 최신까지, clang, MSVC, 임베디드용 arm 계열 등 수백 종)과 최적화 옵션을 골라 가며 비교할 수 있다.

이 책의 13장(컴파일러 최적화)에서 “편집자가 코드를 다시 쓴다”고 한 것을 눈으로 확인하는 가장 쉬운 방법이 이것이다 — x * 2 가 시프트가 되는 것도, -O0-O2 의 출력이 딴판인 것도, 루프가 통째로 사라지는 것도 몇 초 만에 볼 수 있다. 임베디드에서는 “이 코드가 몇 바이트짜리 명령이 되는가”를 확인하는 용도로도 쓴다.

문. 이 많은 것을 다 배워야 임베디드를 시작할 수 있는가?

답. 아니다. 시작에 필요한 최소 묶음은 넷이다 — 칩 회사의 SDK, 컴파일러 (대개 SDK 안에 들어 있다), 디버그 프로브 하나, 그리고 시리얼 터미널. 나머지는 필요가 생길 때 하나씩 는다. 이 장의 목적은 목록을 외우게 하려는 것이 아니라, 나중에 그 이름들을 만났을 때 어느 칸에 놓인 도구인지 바로 알아보게 하는 것이다.

복습 정리

하는 일도구
빌드 조립CMake·Ninja·Meson, PlatformIO·west·idf.py, Kconfig
칩에 굽고 멈추기J-Link·ST-Link·CMSIS-DAP + OpenOCD·pyOCD + gdb
로그 보기UART + 터미널, ITM/SWO, RTT, (느린) 세미호스팅
기계 없이 돌리기QEMU, Renode, 벤더 시뮬레이터
코드 읽히기cppcheck·clang-tidy, PC-lint Plus·PVS-Studio, MISRA
시험Unity·Ceedling·CppUTest, 호스트에서 sanitizer
크기 재기size·nm·objdump·readelf, 맵 파일, bloaty·puncover
신호 보기로직 애널라이저(sigrok), 오실로스코프
기계어 구경Compiler Explorer(godbolt.org)

표 97.2

임베디드의 책상을 둘러보았다. 18장의 컴파일러 지형과 96장의 도구 상자에 이어, C가 가장 깊이 뿌리내린 자리의 연장들까지 이름을 얻은 셈이다.

이제 마지막 장이다. 이 책이 지나온 길을 되짚고, 모던 C의 관행을 한 장의 지침으로 정리하며 책을 닫는다.