| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- Kernel
- tp link
- Router
- fini array
- physmap
- System Management Mode
- ROP
- mips
- exploit
- 라우터
- SMM
- 공유기
- 익스플로잇
- hardware hakcing
- UART
- Cross-cache attack
- 커널
- Slab free list poisoning
- openwrt
- protection ring
- mr 100
- linux ring 권한 구조
- Pwnable
- exit handler overwrite
- kernel exploit
- memory
- e
- tl mr 100
- Today
- Total
haehet
컴파일 과정 본문
컴파일 과정은 크게 전처리, 컴파일, 어셈블, 링킹 단계로 구성된다.
*두 번째 단계의 이름을 '컴파일'로 동일하게 부르기 때문에 혼동을 주의하자!
설명은 간단히 hello, world!를 출력하는 프로그램을 컴파일 해보면서 하겠따
//example.c
#include <stdio.h>
#define FORMAT_STRING "%s"
#define MESSAGE "hello, world\n"
int main(int argc, char *argv[]) {
printf(FORMAT_STRING, MESSAGE);
return 0;
}
1. 전처리 단계
c언어로 작성된 소스 코드 파일에는 매크로(컴파일 전에 텍스트를 치환하는 기능, #define 등 )와 #include지시어가 있다. 전처리 단계에서는 #include과 #define 같은 매크로를 모두 먼저 처리한다. 이를 통해 순수하게 컴파일할 C언어 코드만을 남긴다.
gcc -E -P example.c을 통해 전처리 수행 결과를 확인해보자.
typedef long unsigned int size_t;
typedef __builtin_va_list __gnuc_va_list;
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
/* ... */
extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__))
__attribute__ ((__access__ (__write_only__, 1)));
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
/* ... */
int main(int argc, char *argv[]) {
printf("%s", "hello, world\n");
return 0;
}
출력 결과물을 보면 헤더 파일 속 모든 종류의 타입 정의, 전역 변수 설정, 함수 프로토타입 같은 정보가 포함되어있다.
2. 컴파일 단계
컴파일 단계에서는 전처리가 된 코드를 어셈블리 언어로 변환한다. 여기서 컴파일러가 왜 바로 기계어 코드로 변환을 하지 않고 어셈블리 언어로 변환을 할까? 그 이유는 다양한 언어들로 프로그램을 만들 때 있다. 다양한 언어들로 프로그램을 짜면 각 언어 마다 각각 대응되는 기계어를 지정해줘야 하는데 이는 비효율적이므로 각 언어마다 어셈블리어로 대응을 시키고 그걸 다시 기계어로 번역한다.
gcc -S -masm=intel example.c을 통해 example.s 파일을 생성하고 파일 내용을 확인해보자.
.file "example.c"
.intel_syntax noprefix
.text
.section .rodata
.LC0:
.string "hello, world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
sub rsp, 16
mov DWORD PTR -4[rbp], edi
mov QWORD PTR -16[rbp], rsi
lea rax, .LC0[rip]
mov rdi, rax
call puts@PLT
mov eax, 0
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
3. 어셈블 단계
어셈블 단계에서는 어셈블리코드를 기계어로 본역하는 과정이 일어난다. 이때 목적파일이 생기게 된다.
gcc -c example.c를 통해 목적 파일을 생성하고 file example.c를 통해 목적파일의 정보를 확인해보자
*목적파일은 일반적인 방식으로는 실행을 시키거나 내용물을 확인하기 힘들다.
example.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
*여기서 위 단어들의 각각의 의미는 설명하지 않겠다
4. 링킹 단계
링킹 단계는 모든 목적 파일들을 하나의 실행 가능한 바이너리 형태로 연결시킨다. 링킹 단계는 컴파일러가 아닌 링커라는 프로그램이 수행한다. 링커는 모든 목적 파일을 병합해 하나의 응축된 실행 가능한 형태로 만들고, 메모리의 특정 주소 공간에 로드되도록 하는 역할을 수행한다.
'CS' 카테고리의 다른 글
| 보안기법 정리 (1) | 2025.06.07 |
|---|---|
| ELF 바이너리 포맷 (1) | 2025.05.22 |
