GCC는 단순히 “C 코드를 기계어로 번역”하는 프론트엔드(compiler front-end) 역할이에요.
즉, GCC 자체는
- 어셈블러(as)
- 링커(ld)
- 아카이버(ar)
- 심볼 도구(nm, objdump, objcopy 등)
이런 저수준 바이너리 도구들에 의존합니다.
🔹 GCC는 “코드를 어셈블러 코드로 변환”한 뒤
🔹 GNU Binutils의 as 를 호출해 오브젝트 파일을 만들고
🔹 GNU Binutils의 ld 를 호출해 링크를 수행합니다.
즉, GCC는 “Binutils가 없으면 실행 파일을 만들 수 없는 존재”입니다.
그러므로 크로스 컴파일러에서는 '타킷 아키텍처용 Binutils가 필요합니다.
예를 들어,
지금 당신의 PC가 x86_64인데,
임베디드 장비용 ARM 프로그램을 빌드하려고 한다고 가정해봅시다.
- 로컬에 설치된 as, ld 등은 x86_64용입니다.
- 하지만 GCC가 만든 코드는 ARM용 기계어로 컴파일될 예정입니다.
- 따라서 ARM용 어셈블러와 링커가 필요합니다.
이게 바로 GNU Binutils를 크로스 타깃용으로 먼저 빌드해야 하는 이유입니다.
이렇게 설정해서 as, ld 등 Binutils를 ARM용으로 먼저 설치해야,
그다음 GCC가 “ARM용 코드 → ARM용 오브젝트 → ARM용 실행 파일”로 완전한 빌드 체인을 구성할 수 있습니다.
그럼 Binutils가 무엇인지, GCC와의 관계는 어떻게 되는건지 좀 더 자세히 알아볼까요?
GCC와 Binutils, 그리고 오브젝트 파일과 링크의 모든 것
프로그래밍을 하다 보면 “링크”, “오브젝트 파일”, “Binutils” 같은 단어를 자주 접하지만,
정확히 어떤 역할을 하는지 한 번쯤 헷갈릴 때가 있습니다.
이번 글에서는 GCC로 프로그램이 빌드되는 과정을 중심으로,
그 속에서 Binutils, 오브젝트 파일, 링커가 어떤 일을 하는지 자세히 살펴봅니다.
1. GCC와 GNU Binutils의 관계
GCC(GNU Compiler Collection)는 단순히 C 코드를 기계어로 바꾸는 컴파일러가 아닙니다.
코드를 번역하는 것 외에도, 링크(link) 과정에서 다른 툴들의 도움을 받습니다.
그 핵심 파트너가 바로 GNU Binutils입니다.
GNU Binutils는 “Binary Utilities”의 줄임말로,
바이너리 파일과 관련된 여러 유틸리티들의 집합입니다.
주요 도구들은 다음과 같습니다:
| as | 어셈블러: 어셈블리 → 오브젝트 파일(.o) |
| ld | 링커: 여러 .o 파일 → 실행 파일 또는 라이브러리 |
| objdump, nm, objcopy 등 | 오브젝트 파일 분석 및 변환 도구 |
즉, GCC는 소스 코드를 컴파일하고,
그 결과를 Binutils의 링커(ld) 로 넘겨서 실행 파일을 완성합니다.
따라서 GCC를 크로스 컴파일러로 빌드하려면 Binutils가 먼저 필요한 이유가 여기 있습니다.
2. 링크(Link)란 무엇인가?
“링크”는 여러 개의 오브젝트 파일(.o) 을 묶어
하나의 실행 가능한 프로그램으로 만드는 과정입니다.
이 일을 담당하는 도구가 바로 링커(linker) 입니다.
링크 과정에서 일어나는 일
- 심볼 해석(Symbol Resolution)
- 각 함수나 변수의 정의를 찾아 연결합니다.
- 예: main.o에서 add()를 호출하면 add.o의 add()와 연결.
- 주소 배정(Address Binding)
- 프로그램이 실행될 때 함수와 변수가 실제 메모리의 어느 주소에 위치할지 결정.
- 재배치(Relocation)
- 위에서 정한 주소로 코드 내의 참조(점프, 호출 등)를 수정.
결과적으로, 링크 과정을 거치면 하나의 완전한 실행 파일이 만들어집니다.
3. 링크와 PLT/GOT의 관계
여기서 “링크”는 단순히 코드를 이어붙이는 것 이상입니다.
특히 공유 라이브러리(.so) 와 연결될 때,
링커는 PLT(Procedure Linkage Table) 과 GOT(Global Offset Table) 을 사용해
“나중에 실제 주소를 채워 넣을 수 있는 구조”를 만들어 둡니다.
- 링크 시점:
실제 함수 주소를 모를 때 → “나중에 PLT/GOT에서 찾아라”라고 표시함. - 실행 시점:
런타임 로더(ld.so)가 실제 함수 주소를 GOT에 써 넣음.
즉,
링크는 “함수의 주소를 직접 써주는 것”이 아니라
“주소를 나중에 채워 넣을 수 있도록 구조를 만들어주는 것”입니다.
4. 오브젝트 파일은 어떤 구조일까?
많은 사람들이 “오브젝트 파일에는 어셈블리 코드가 들어있다”고 생각하지만,
정확히는 어셈블리 코드가 기계어로 변환된 결과가 들어 있습니다.
오브젝트 파일에는 이런 섹션이 들어 있습니다:
| .text | 실제 기계어 명령어 |
| .data | 초기화된 전역 변수 |
| .bss | 초기화되지 않은 전역 변수 |
| .rodata | 문자열 상수, 상수 데이터 |
| .symtab | 함수와 변수 이름 정보 (심볼 테이블) |
| .rel.text | 재배치 정보 (링커가 주소를 채울 부분) |
즉, 오브젝트 파일은 “사람이 읽는 코드”가 아니라
CPU가 실행할 수 있는 코드 조각 + 메타데이터의 묶음입니다.
5. 오브젝트 파일과 링크의 관계
이제 전체 구조를 한눈에 볼 수 있습니다
| C 소스 코드 | .c | 사람이 작성한 코드 |
| 어셈블리 코드 | .s | CPU 명령어 수준 코드 |
| 오브젝트 파일 | .o | 기계어 코드 조각 |
| 링커 결과 | 실행 파일 | 완전한 프로그램 |
즉,
오브젝트 파일은 “부분 프로그램”이고,
링커는 그것들을 하나로 합쳐 완전한 프로그램을 만드는 도구입니다.
6. 역사: 오브젝트 파일을 직접 개발하던 시절
오늘날 우리는 gcc 한 줄로 .o 파일을 얻지만,
예전에는 오브젝트 파일을 직접 작성하거나 조작하던 개발자들이 있었습니다.
| 1940~1950년대 | 어셈블러도 없었고, 개발자들이 직접 기계어를 입력함. 오브젝트 파일 개념 없음. |
| 1950~1960년대 | 어셈블러 등장. 개발자들이 “오브젝트 모듈”을 직접 만들고, 수동으로 링크. |
| 1970~1980년대 | UNIX의 등장과 함께 표준 오브젝트 파일 포맷(a.out, COFF) 확립. 컴파일러가 자동 생성. |
| 현재 | .o 파일은 자동 생성되며, 컴파일러·OS·보안·리버스 엔지니어링 분야만 직접 다룸. |
과거에는 심볼 주소를 직접 계산해 링크 테이블에 기록하기도 했습니다.
지금은 링커와 로더가 이 과정을 완전히 자동화해주죠.
정리
- Binutils는 GCC가 사용하는 바이너리 유틸리티 모음.
- 오브젝트 파일은 어셈블리 코드가 기계어로 번역된 결과물.
- 링커는 여러 오브젝트 파일을 하나로 묶어 실행 가능한 프로그램을 만듦.
- PLT/GOT은 동적 링크에서 함수 주소를 나중에 채워 넣을 수 있게 하는 테이블 구조.
- 과거에는 오브젝트 파일을 직접 관리하던 개발자들이 있었지만,
오늘날은 대부분의 작업을 컴파일러와 링커가 자동으로 처리한다.
💬 정리 한 줄 요약
오브젝트 파일은 “프로그램의 조각”,
링커는 “그 조각들을 하나로 엮는 접착제”,
Binutils는 “그 접착제를 다루는 도구 세트”다.
댓글