이것저것

[리버싱 1_2주차] 프로그램 빌드 과정 본문

카테고리 없음

[리버싱 1_2주차] 프로그램 빌드 과정

삼봉지구 물주먹 2026. 9. 16. 13:12

프로그램은 어떻게 실행이 되는가

C언어로 프로그램을 작성했다고 해서 바로 실행할 수 있는 것은 아니다. 우리가 작성한 소스 코드는 사람이 이해하기 쉬운 형태이기 때문에 CPU가 바로 실행할 수 없다.

 

그래서 소스 코드를 CPU가 실행할 수 있는 형태로 차례대로 변환하는 과정이 필요한데, 이것이 프로그램의 빌드 과정이다.

전체적인 순서는 다음과 같다.

.c
↓
전처리
↓
.i
↓
컴파일
↓
.s
↓
어셈블
↓
.o
↓
링크
↓
.exe

 

전처리

가장 먼저 전처리(Preprocessing)가 진행된다.

 

예를 들어 다음과 같은 코드가 있다.

#include <stdio.h>

int main() {
    printf("Hello");
    return 0;
}

 

여기서 #include처럼 # 으로 시작하는 전처리 지시문을 처리하고, #define 같은 매크로나 조건부 컴파일도 이 단계에서 처리된다.

 

특히 #include <stdio.h>가 있으면 필요한 헤더 파일의 내용이 소스 코드에 포함된다.

 

전처리가 끝난 결과는 보통 .i 파일로 볼 수 있다.

 

아직 이 단계에서는 C 코드의 형태가 남아 있으며, 기계어로 변환된 것은 아니다.

 

컴파일

전처리가 끝나는 코드는 컴파일러가 받아서 어셈블리 코드로 변환한다.

C 코드
↓
컴파일러
↓
어셈블리 코드

결과물은 .s 파일이다.

 

예를 들어 C 코드가 컴파일되면서 다음과 같은 어셈블리 코드가 만들어질 수 있다.

mov eax, 0
ret

 

여기서 중요한 것은 .s 파일 자체가 기계어는 아니라는 점이다.

 

mov, eax, ret 처럼 사람이 읽을 수 있는 어셈블리 언어로 작성된 텍스트다.

 

어셈블

이제 .s 파일을 어셈블러가 처리한다.

 

어셈블러는 사람이 읽을 수 있는 어셈블리 명령을 실제 기계어 바이트로 변환한다.

.s
↓
어셈블러
↓
.o

 

예를 들어 특정 x86 명령은 다음과 같은 바이트로 변환될 수 있다.

B8 00 00 00 00
C3

 

이렇게 만들어진 .o 파일에는 실제 기계어뿐만 아니라 심볼이나 재배치 정보 같은 것도 함께 들어 있다.

 

다만 .o 파일은 아직 완성된 실행 파일은 아니다.

 

링크

마지막으로 링커(Linker)가 .o 파일과 필요한 라이브러리들을 연결한다.

 

예를 들어 우리가 printf()를 사용했다면 프로그램을 실행하기 위해 printf가 실제로 어디에 존재하는지 연결해야 한다.

 

이런 작업을 링커가 처리하고 최종적으로 실행 파일을 만든다.

main.o
+
필요한 라이브러리
↓
링커
↓
hello.exe

 

Windows에서는 이렇게 만들어진 최종 실행 파일을 일반적으로 .exe 파일로 볼 수 있다.

 

빌드 과정 정리

각 단계에서 파일의 성격이 조금씩 달라진다.

.c
사람이 작성한 C 소스 코드

↓ 전처리

.i
전처리가 끝난 C 코드

↓ 컴파일

.s
어셈블리 소스 코드

↓ 어셈블

.o
기계어 + 심볼 + 재배치 정보 등이 들어 있는 오브젝트 파일

↓ 링크

.exe
실제로 실행할 수 있는 최종 실행 파일

 

리버싱에서는 반대로 생가갛면 된다.

 

리버싱에서는 완성된 실행 파일인 .exe를 가지고 프로그램이 어떻게 만들어졌고 어떤 동작을 하는지 거꾸로 분석해 나간다.