컴파일된 object 파일들을 linking해서 exe파일로 만드는 과정에서 어떤 일들이 일어나는지 알아보자
// main.c
int sum(int *a, int n)
int array[2] = {1, 2}
int main(){
int val = sum(array, 2);
return val;
}
// sum.c
int sum(int *a, int n){
int i, s = 0;
for(i = 0; i < n; i++){
s += a[i];
}
return s;
}
main.c의 sum 함수는 어떤 과정을 통해서 sum.c의 sum 함수를 불러와서 기능을 수행할 수 있을까?
위와 같이 여러 개로 이루어진 파일들을 연결하는 과정을 Linking이라고 한다.
먼저 cpp(C Pre-Processor 전처리) 과정을 거친다. #include, #define 같은 과정을 통해 code에서 해당하는 부분들을 전처리해준다.
cc1이라는 과정을 통해서 어셈블러 코드로 변환된다 C->assembly
마지막으로 어셈블러 코드를 통해 .o파일(바이너리 파일)을 만들어 준다.
위 과정을 거치면 main.c, sum.c 파일 모두. o파일이 생기게 된다.
이때 생성된 .o파일들은 Linker를 통해서 실행 가능한 object파일로 변환된다.
위와 같은 방법을 통해 Linking을 하는 이유
모듈화(printf 같은 함수가 모듈화가 되었기 때문에 쉽게 불러와서 사용할 수 있다.)
효율성(개별 컴파일 가능, (Static Linking, Dynamic Linking) 나중에 설명.)
Linker는 무슨 일을 하는가?
심볼들을 연결시켜 준다.(Symbol : 변수명, 함수명, etc..)
Object 파일들의 종류
Relocatable object file (.o file) : 컴파일이 다 된 바이너리 파일이지만 실제 메모리에 로드되어 실행될 수 없는 상태, 심볼들의 연결이 아직 이루어지지 않음
Executable object file (a.out file) : 실제로 실행 가능한 object 파일
Shared object file (.so file) : 일반적으로 '라이브러리' 라고 불리는 파일, 동적으로 Link 될 수 있는 파일
Excutable and Linkable Format (ELF) 에는 어떤 정보들이 있는가
ELF header : 파일의 type, 단어의 크기 등과 같은 아주 기본적인 정보가 있는 부분
Segment header table : Page Size, 가상 메모리 segments, segment 크기 등 파일 실행에 필요한 정보들이 있는 부분
.text section : 일반적인 코드가 있는 부분
.rodata section :jump tables 같은 읽기 전용 데이터가 있는 부분
.data section: 초기화된 전역변수 부분
.bss section :초기화되지 않은 전역변수, (Block Started by Symbol, Better Save Space 의 약자)
.symtab section : Symbol table, static변수 부분
.rel .text section : .text section의 정보를 재조정 하기위한 정보를 가짐
.rel .data section : .date section의 전역변수들의 정보를 재배치 하기위한 정보를 가짐
.debug section : 디버깅을 위한 정보를 가짐(gcc -g)
Linker Symbols
global symbols : non-static 전역변수, non-static 함수
External symbol : 외부 변수를 참조할 때 사용하는 symbole
Local symbols : 내부 함수, static 전역변수 (지역변수와는 다름 -> stack에 생성되는 변수(.data section, .bss section 과 연결). 실행 시점과는 연관X)
// main.c
int sum(int *a, int n)
//array : global symbol
int array[2] = {1, 2}
//main : global symbol
int main(){
// Linking을 하기전에는 sum이라는 함수의 주소를 알 수 없어서 비워둠
// Linking이후에 sum.c에서 sum함수를 연결하고, sum의 주소를 입력해줌
int val = sum(array, 2);
// val : stack에 생성(Linker 는 local variable에는 관여 X)
return val;
}
// sum.c
//sum : global symbol
int sum(int *a, int n){
//(Linker 는 local variable에는 관여 X)
int i, s = 0;
for(i = 0; i < n; i++){
s += a[i];
}
return s;
}
//symbols.c
int time;
int foo(int a){
int b = a + 1;
return b;
}
int main(int argc, char* argv[]){
printf("%d\n", foo(5));
return 0;
}
위 symbols.c에서 symbol table(global v, function, static local...)에 포함되는 변수는 어떤게 있을까?
Names:
time : global 변수
foo : 함수
a : 지역변수 (symbol table X)
b : 지역변수 (symbol table X)
argc : 지역변수 (symbol table X)
argv : 지역변수 (symbol table X)
main : 함수
printf : 함수
$ readelf -s symbols.o
gcc -c를 이용해 .o 파일을 만든 후, 위 코드를 이용해서 Symbol table을 확인할 수 있다.
Local Symbols
local non-static variables : stack에 저장
local static variables : .bss or .data에 저장
static int x = 15;
int f(){
static int x = 17;
return x++;
}
int g(){
static int x = 19;
return x += 14;
}
int h(){
return x += 27;
}
위 코드에서 x라는 변수가 많이 사용된다. f, g함수 내부에서 static int로 x를 선언해서 사용하고, h 함수에서도 x를 사용한다. 변수명이 같은 경우(f, g 함수 내부) x의 symbol은 x.1721, x.1724 같은 유니크한 심볼로 만들어 진다.(충돌 방지)
Strong : 초기화된 전역변수
Weak : 초기화되지 않은 전역변수, extern 으로 선언된 변수
만약 변수명이 같아 충돌이 일어난다면 Symbols Rules에 따라서 변수를 linking 한다
Rule1 : Strong 우선(가까이 있는 변수 우선 X), Strong 이 2개 이상이면 error
Rule2 : Weak만 여러개 있는 경우 : Weak 변수중 아무거나 사용(danger)
Global 변수의 사용법
//c1.c
#include "global.h"
int f(){
return g+1;
}
//global.h
#ifdef INITIALIZE
int g = 23;
static int init = 1;
#else
extern int g;
static int init = 0;
#endif
//c2.c
#define INITIALIZE
#include <stdio.h>
#include "global.h"
int g = 0;
int main(){
if(init)
// something
int t = f();
printf("Call f %d\n", t);
return 0;
}