사전 툴체인
개념

툴체인

gabury1고친 사람 github-actions[bot]

툴체인은 소스 코드를 실행 파일로 바꿔 주는 도구 한 벌입니다. 한 도구가 내놓은 결과를 다음 도구가 받아 이어 가기 때문에, 도구를 뜻하는 tool 에 사슬을 뜻하는 chain 을 붙여 toolchain 이라고 부릅니다. 도구들이 같은 약속을 따라야 결과가 맞물리므로 따로 고르지 않고 한 벌로 맞춥니다. 주로 C 같은 컴파일 언어의 빌드 도구를 가리키지만 다른 언어의 빌드 도구 묶음도 같은 이름으로 부릅니다.

쉽고 빠른 이해

툴체인은 소스 코드를 실행 파일로 만드는 도구 묶음입니다. C 프로그램이라면 컴파일러, 어셈블러, 링커, 표준 라이브러리가 한 벌에 들어갑니다.

도구를 한 벌로 맞추는 이유는 서로의 결과물을 읽어야 하기 때문입니다. 제각각 모은 도구는 앞 도구가 쓴 파일을 뒤 도구가 못 읽거나, 만들어진 프로그램이 실행하자마자 멈추기도 합니다.

어떻게 도나:

  1. 컴파일러가 소스 코드를 기계어에 가까운 글로 옮깁니다
  2. 어셈블러가 그 글을 기계어 조각 파일로 바꿉니다
  3. 링커가 조각들과 라이브러리를 이어 실행 파일 하나로 만듭니다

개발자는 대개 명령 한 줄만 칩니다. 그 명령이 나머지 도구를 차례로 불러 줍니다.

대가도 있습니다. 실행할 기계의 종류마다 툴체인을 한 벌씩 따로 갖춰야 합니다.

C 처럼 컴파일하는 언어는 빌드할 때마다 툴체인을 씁니다. 파이썬처럼 소스를 바로 실행하는 언어는 C 로 짠 확장을 설치할 때 말고는 쓸 일이 드뭅니다.

상세

공장의 조립 라인을 떠올리면 됩니다. 앞 기계가 내놓은 부품을 다음 기계가 받아 다듬습니다. 마지막 기계에서 완제품이 나옵니다. 라인의 기계들은 같은 규격의 부품을 주고받도록 한꺼번에 맞춰 들입니다.

C 로 쓴 파일 하나가 실행 파일이 되는 과정부터 따라갑니다. 그 과정을 보면 도구를 왜 한 벌로 맞추는지가 드러납니다.

소스가 실행 파일이 되는 사슬

C 소스 파일 하나가 실행 파일이 되기까지는 도구 넷을 거칩니다. 앞에서 셋으로 줄였던 과정에 첫 단계인 전처리기를 더하면 넷이 됩니다. 표준 라이브러리는 도구가 아니라 링커가 이어 붙이는 재료입니다.

flowchart TD
    SRC["소스 코드"] --> PP["전처리기"]
    PP -->|전처리된 C 소스| CC["컴파일러"]
    CC -->|어셈블리어| AS["어셈블러"]
    AS -->|목적 파일| LD["링커"]
    LIB["표준 라이브러리"] --> LD
    LD --> EXE["실행 파일"]

첫 도구는 전처리기입니다. #include 로 불러온 헤더 파일의 내용을 소스에 끼워 넣습니다. #define 으로 정해 둔 이름도 값으로 바꿉니다. 결과는 여전히 C 소스입니다.

다음은 컴파일러입니다. C 소스를 어셈블리어로 옮깁니다. 어셈블리어는 CPU(Central Processing Unit, 중앙 처리 장치)가 알아듣는 명령을 사람이 읽을 수 있는 낱말로 적은 언어입니다. 문법 오류와 타입 오류는 이 단계에서 잡힙니다.

어셈블러는 어셈블리어를 기계어로 바꿔 목적 파일에 담습니다. 기계어는 CPU 가 바로 실행하는 숫자 명령입니다. 목적 파일은 기계어가 들어 있지만 혼자서는 실행되지 않는 조각입니다.

목적 파일은 다른 파일에 있는 함수를 아직 주소가 아니라 이름으로만 가리킵니다. 마지막 도구인 링커가 목적 파일들을 이어 붙이며 이 이름에 실제 주소를 채웁니다. 그렇게 실행 파일 하나가 나옵니다.

링커는 이때 표준 라이브러리도 함께 잇습니다. 표준 라이브러리는 화면 출력이나 파일 열기처럼 거의 모든 프로그램이 쓰는 기능을 미리 컴파일해 둔 코드입니다.

그런데 표준 라이브러리는 흔히 실행 파일 안에 복사되지 않습니다. 실행 파일에는 어느 라이브러리가 필요한지만 적어 두고, 실행할 때 그 기계에 깔린 것을 불러옵니다. 이렇게 실행할 때 따로 불러오는 라이브러리를 공유 라이브러리라고 합니다.

개발자가 이 넷을 하나씩 부르는 일은 드뭅니다. C 컴파일러 명령 cc 에 소스 파일을 주면 전처리기부터 링커까지 차례로 불러 줍니다. 뒤에서 도구들을 불러 주는 이 역할 때문에 cc 같은 명령을 컴파일러 드라이버라고도 부릅니다.

옵션을 주면 사슬 중간에서 멈추게 할 수 있습니다. 아래 네 줄은 같은 파일을 한 단계씩 더 멀리 보낸 것입니다.

$ cc -E main.c   # 전처리 결과만 출력
$ cc -S main.c   # 어셈블리어 main.s
$ cc -c main.c   # 목적 파일 main.o
$ cc main.o      # 링크해 a.out 생성

마지막 줄의 a.out 은 이름을 따로 주지 않았을 때 붙는 기본 실행 파일 이름입니다. 사슬이 머릿속의 그림이 아니라 따로 떼어 부를 수 있는 도구들이라는 것이 보입니다. 옵션 없이 마지막 줄까지 한 번에 가는 것이 평소의 빌드입니다.

툴체인이라는 말은 좁게는 이 네 단계 도구와 라이브러리만 가리킵니다. 넓게는 디버거까지 넣습니다. 디버거는 실행 중인 프로그램을 멈춰 세우고 변수 값을 들여다보는 도구입니다. 빌드 결과를 다루는 도구라서 같은 묶음으로 함께 배포되는 경우가 많습니다.

도구를 한 벌로 맞추는 까닭

도구들은 서로의 출력을 입력으로 받습니다. 그래서 몇 가지 약속을 같이 지켜야 합니다. 약속이 하나라도 어긋나면 사슬이 끊깁니다.

첫째 약속은 명령어 집합입니다. 명령어 집합은 CPU 가 알아듣는 명령의 목록입니다. 노트북에 흔한 x86-64 와 스마트폰에 흔한 Arm 은 서로 다른 명령어 집합을 씁니다. 컴파일러가 Arm 용 어셈블리어를 내놓았다면 어셈블러도 Arm 명령을 알아야 합니다.

둘째 약속은 목적 파일과 실행 파일의 형식입니다. 링커는 어셈블러가 쓴 목적 파일을 읽을 수 있어야 합니다. 운영체제마다 쓰는 형식이 다릅니다. 리눅스는 ELF(Executable and Linkable Format), 윈도우는 PE(Portable Executable), macOS 는 Mach-O 형식을 씁니다.

셋째 약속은 ABI(Application Binary Interface, 애플리케이션 바이너리 인터페이스)입니다. 함수를 부를 때 인자를 어디에 두는지, 자료형 하나가 몇 바이트인지 같은 기계어 수준의 약속입니다. 컴파일러가 만든 코드와 표준 라이브러리의 코드가 같은 ABI 를 따라야 서로를 부를 수 있습니다.

약속이 어긋나면 대개 링크 단계에서 형식이 안 맞는다며 멈춥니다. 더 곤란한 것은 빌드가 끝난 뒤에 드러나는 경우입니다. 앞에서 본 공유 라이브러리가 이 경우를 만듭니다. 리눅스에서 흔히 쓰는 C 표준 라이브러리인 glibc 가 빌드한 기계보다 오래된 판으로 실행할 기계에 깔려 있으면, 프로그램이 시작하자마자 version 'GLIBC_…' not found 같은 오류를 내며 멈춥니다.

그래서 컴파일러와 링커, 라이브러리를 판까지 맞춘 한 벌로 다룹니다. 운영체제 배포판의 개발 패키지나 SDK(Software Development Kit, 소프트웨어 개발 키트)가 이 한 벌을 묶어서 줍니다.

호스트와 타깃

툴체인에는 어느 기계용 실행 파일을 만드는지가 정해져 있습니다. 만든 실행 파일이 돌아갈 기계를 타깃이라고 합니다. 툴체인 자신이 돌아가는 기계는 호스트라고 합니다.

네이티브 툴체인 크로스 툴체인
호스트와 타깃 같다 다르다
예 리눅스 서버에서 그 서버용으로 빌드 x86-64 노트북에서 Arm 보드용으로 빌드

표의 오른쪽처럼 다른 기계용으로 빌드하는 것을 크로스 컴파일이라고 합니다. 임베디드 장비는 기계가 작아서 그 위에서 빌드를 돌리기 어렵습니다. 그래서 개발자 컴퓨터에 크로스 툴체인을 깔고 빌드한 결과만 장비로 옮깁니다.

크로스 툴체인은 타깃의 라이브러리와 헤더 파일도 따로 갖춰야 합니다. 호스트에 깔린 라이브러리는 호스트의 기계어로 되어 있어서 타깃용 실행 파일에 섞을 수 없기 때문입니다. 타깃의 라이브러리와 헤더를 호스트의 폴더 하나에 모아 둔 것을 sysroot라고 부릅니다.

크로스 툴체인의 명령에는 흔히 타깃 이름이 앞에 붙습니다. 한 컴퓨터에 여러 벌을 깔아도 헷갈리지 않게 하려는 것입니다.

aarch64-linux-gnu-gcc # 64비트 Arm, 리눅스
x86_64-linux-gnu-gcc  # x86-64, 리눅스

두 이름은 앞 토막만 다릅니다. 앞 토막은 CPU 종류, 운영체제, ABI 를 적은 것입니다. 이 토막을 타깃 트리플이라고 부릅니다. 타깃이 늘어날 때마다 툴체인도 한 벌씩 늘어납니다.

맨 끝의 gcc 는 이 툴체인의 C 컴파일러 명령입니다. 셋째 토막 gnu 는 앞에서 본 glibc 를 쓰는 ABI 라는 뜻입니다. 둘 다 바로 아래 절에서 다시 나옵니다.

GNU 툴체인과 LLVM 툴체인

가장 널리 쓰이는 두 벌이 있습니다. GNU 프로젝트가 만든 GNU 툴체인과 LLVM 프로젝트에서 나온 LLVM 툴체인입니다.

역할 GNU 툴체인 LLVM 툴체인
컴파일러 GCC(GNU Compiler Collection) Clang
링커 ld lld
디버거 GDB(GNU Debugger) LLDB(LLVM Debugger)

두 벌은 역할마다 짝이 되는 도구를 하나씩 갖고 있습니다. 부품을 섞어 쓸 수도 있습니다. Clang 으로 컴파일한 목적 파일을 GNU 링커로 잇는 식입니다. 섞을 수 있는 것은 두 벌이 같은 목적 파일 형식과 ABI 를 따르기 때문입니다.

다른 언어에서 부르는 툴체인

자바에서는 JDK(Java Development Kit, 자바 개발 키트)가 툴체인 노릇을 합니다. 자바 컴파일러 javac, 표준 라이브러리, 실행기 java, 묶음 도구 jar 가 한 벌로 들어 있습니다. 프로젝트마다 JDK 판을 맞추는 것도 C 툴체인을 판까지 맞추는 것과 같은 이유입니다.

자바 컴파일러는 기계어 대신 바이트코드를 내놓습니다. 바이트코드는 특정 CPU 가 아니라 JVM(Java Virtual Machine, 자바 가상 머신)이 읽는 명령입니다. 그래서 자바의 사슬에는 어셈블러와 링커 단계가 따로 없습니다. 클래스끼리 잇는 일은 실행 중에 JVM 이 맡습니다.

러스트는 rustup 이라는 설치 도구로 툴체인을 고릅니다. 한 벌에는 컴파일러 rustc, 표준 라이브러리, 빌드와 패키지를 맡는 cargo 가 들어 있습니다. 판을 바꿀 때는 이 셋을 함께 바꿉니다.

파이썬처럼 인터프리터가 소스를 바로 읽어 실행하는 언어는 이 사슬 없이 돌아갑니다. 그래도 C 로 짠 확장 모듈을 설치할 때는 C 툴체인이 필요합니다. pip install 이 확장 모듈을 빌드하다 컴파일러를 못 찾아 실패하는 것이 이 경우입니다.

프런트엔드 개발에서는 타입스크립트를 자바스크립트로 옮기는 컴파일러와 파일을 묶는 번들러(webpack 등)도 한데 묶어 툴체인이라고 부릅니다. 입력을 받아 배포할 결과물로 바꾸는 도구 사슬이라는 뜻은 같습니다.

관련 항목

툴체인을 이루는 도구

전처리기 · 컴파일러 · 어셈블러 · 링커 · 표준 라이브러리 · 헤더 파일 · 디버거 · 컴파일러 드라이버 · binutils

툴체인이 거쳐 가며 만드는 파일

소스 코드 · 어셈블리어 · 목적 파일 · 실행 파일 · 정적 라이브러리 · 공유 라이브러리 · 기계어

툴체인의 도구들이 함께 지키는 약속

명령어 집합 · ABI · 호출 규약 · ELF · PE · Mach-O

호스트와 타깃이 다를 때 필요한 구성

크로스 컴파일 · 크로스 컴파일러 · 타깃 트리플 · sysroot · 임베디드

툴체인을 구현한 제품

GCC · Clang · LLVM · GDB · LLDB · lld · glibc · MSVC

툴체인을 불러 쓰는 빌드 도구

빌드 · 빌드 시스템 · Make · CMake · Gradle · Cargo

다른 언어에서 툴체인 노릇을 하는 도구 묶음

JDK · rustup · 바이트코드 · JVM · 인터프리터 · 런타임

다른 이름: toolchain · tool chain