1. Compilation
Compilation이란 HLL(High Level Language) source file을 executable binary file로 변환하는 작업이다.
excutable binary file은 컴퓨터가 직접 특정한 task를 수행할 수 있도록하는 instruction들을 포함한다.
이러한 excutable binary file의 형식은 OS에 종속되어있고, windows의 경우 .exe파일이다.
자바에서의 경우, .java 파일은 compilation과정을 통해 .class 파일로 변환된다.
class 파일은 JVM(Java Virtual Machine)이라는 가상의 기계(컴퓨터)를 통해 실행할 수 있고, OS위에서 JVM이 동작하기 때문에, class파일은 어떤 OS에서든지 실행가능하다.
실제로는 JVM이 class파일을 해당 OS에서 실행가능한 binary file로 변환하여 실행한다.
2. Compiler
맨처음 HLL source file은 compiler에 의해 assembly source file로 변환된다.
예를 들어 아래와 같은 test.c 코드가 있다고 해보자.
#include <stdio.h>
int main(){
int a = 0;
int b = a+5;
printf("%i\n", b);
return 0;
}
gcc -s test.c 와 같이 -s 옵션을 주어 assembly source file로 test.s 파일을 생성할 수 있다.
x86-62bit processor Architecture의 경우 다음과 같이 생성된다(Hardware에 의존)
int main() 부분의 경우 다음과 같이 변환된다
%rbp는 function call frame의 base pointer에 대한 register의 값이다.
function call frame은 함수가 호출될 때마다 생성되며, parameter, local variable 등을 저장하는 data structure이다.
%rsp는 stack의 최상단을 가리키는 stack pointer에 대한 register의 값이다.
pushq %rbp //%rbp값(이전함수의 bp)을 stack에 push, push후 %rsp의 값이 감소함
.seh_pushreg %rbp
movq %rsp, %rbp //%rsp의 값을 %rbp로 복사
.seh_setframe %rbp, 0
subq $48, %rsp //%rsp의 값을 48만큼 뺀다(48byte만큼 공간할당)
.seh_stackalloc 48
int a = 0 부분의 경우 다음과 같이 변환된다.
참고로 %eax는 general-perpose registor이다.
movl $0, -4(%rbp) //0을 %rbp에서 4byte뺀 위치에 복사
movl -4(%rbp), %eax // %rbp에서 4byte뺀 위치에 복사된 값을 %eax값로 복사
int b = a+5 부분의 경우 다음과 같이 변환된다.
addl $5, %eax //%eax의 값에 5만큼 더한다
printf("%i\n", b) 부분의 경우 다음과 같이 변환된다.
참고로 %edx, %rcx는 general-perpose registor이다.
%rip는 현재 실행중인 명령어의 메모리 주소를 가리키는 registor이다.
movl %eax, -8(%rbp) //%eax의 값을 %rbp에서 8byte 뺀 위치로 복사
movl -8(%rbp), %eax //%rbp에서 8byte 뺀 위치로 복사한 값을 %eax로 복사
movl %eax, %edx //%eax의 값을 %edx로 복사
leaq .LC0(%rip), %rcx //출력할 문자열의 시작주소를 %rcx에 저장
call printf //printf 호출
return 0 부분의 경우 다음과 같이 변환된다.
movl $0, %eax //값 0을 %eax에 복사
addq %48, %rsp //%rsp에 48을 더함, 할당한 공간 48byte를 해제하는 역할
popq %rbp // stack에서 데이터를 꺼내 %rbp에 저장, 맨 처음 pushq로 저장한 값을 %rbp에 복원
ret //stack에서 return address를 꺼내 %rip에 저장하여 실행흐름 이동
그 다음으로는 이런 코드가 따라온다.
Relocation information이라고 하며, linker가 최종적으로 executable file을 만들 때 최종적으로 코드와 데이터의 최종 memory address가 정해지는데(relocation), 이 때 다음 정보들이 활용된다.
.indent "GCC: (GNU) 5.4.0" // Assembly code의 들여쓰기 타입 지정
//symbol정의, prinf심볼이 외부(2)에서 정의되어 링크될 것이며, 32bit 함수임을 정의
.def printf; .scl ; 2; .type 32; .endef
3. Assembler
assembly source file은 assembler에 의해 object file로 변환된다.
gcc -c test.c 명령어를 사용하면, test.o 파일이 생성된다.
Object file은 주로 machine code인 binary code로 구성되어있으며, 인간이 읽기 어려운 형태이다.
내부의 구성은 보통 아래와 같은 구성으로 되어있다.
Header | Text segment | Data segment | Relocation information |
Symbol table | Debugging information |
Header : descriptive and control information
Code segment : executable code
Data segment : initialized static variables
Relocation information : linking시 메모리 주소 확정을 위해 필요한 정보(printf와 같은 외부참조)
Symbol Table : symbol information
4. Linker
linker은 여러 source file로 부터 변환된 여러 object file을 연결시키고, Standard IO와 같은 program library들을 연결시켜 하나의 executable file로 만들어주는 역할을 한다.
source file이 매우 많을 경우, 각각의 source file은 각각 object file을 생성하고, 나중에 linker를 통해 연결만 하면된다.
Relocation information을 통해, undefined symbol인 다른 object file이나 library의 function(printf와 같은)을 찾고, 어떻게 이들을 하나의 executable file로 결합할지를 결정한다. 하나의 실행파일의 끝에 위치하게 하여, 사용하도록 할 수 있다.
5. Loader
마지막으로, executable file의 실행을 시작하기 위해서, OS는 loader라는 프로그램을 실행시킨다.
이것은 일련의 과정을 필요로한다.
1) Allocate memory : main memory에 text/data segment를 위해 필요한 만큼 공간 할당
2) Copy text and data segments : text/data segment를 메모리의 각각의 공간에 복사
3) Copy program arguments onto the stack : 프로그램 실행할 때 command line에 작성한 명령어의 arguments를 stack에 복사(필요시 사용 위해)
4) Clear register, set stack pointer : register를 비우고, stack에 복사한 arguments를 올바르게 찾기 위해 stack pointer를 올바른 위치로 설정
5) Copy arguments off the stack and jump to main : stack에 저장된 argument를 적절한 register로 복사, main함수로 jump