Linking?
메모리에 로드(복사)하고 실행할 수 있는 단일 파일로 다양한 코드와 데이터를 수집하고 결합하는 프로세스
- Memory에 Load하고 실행할 수 있는 단일 파일로, 다양한 코드와 데이터를 수집하고 결합하는 Process
- Compile time, Load time, Run time에 Linking이 수행된다.
- Linking은 최신 시스템에서 Linker라고 하는 프로그램에 의해 자동으로 수행된다.
Why we have to learn about linking?
- build large programs
- Linker가 reference를 해결하는 방법, Library가 무엇인지, 그리고 Linker가 reference를 해결하기 위해 Library를 사용하는 방법을 이해하지 않는 한, 이러한 종류의 오류는 당황스럽고 좌절스러울 것이다.
- avoid dangerous programming errors
- 여러 전역 변수를 잘못 정의한 프로그램은 경고 없이 Linker를 통과할 수 있다.
- 그 결과 프로그램은 Rum time에 의도치 않은 결과를 낳을 수 있으며, 디버깅하기 매우 어렵다.
- understand how language scoping rules are implemented
- global, local 변수와 어떤 차이점이 있는가?
- static 속성이 붙은 변수는 어떤 의미를 가지는가?
- understand other important systems concepts
- Linker에 의해 생성된 실행 가능한 객체 파일은 프로그램 Load 및 Excution, Virtual Memory, Paging, Memory Mapping과 같은 중요한 시스템 기능에서 핵심적인 역할을 한다.
- enable you to exploit shared libraries
- 현대 OS에서 Shared Library와 Dynamic Linking의 중요성이 높아지면서 Linking은 프로그래머에게 강력한 힘을 제공하는 정교한 Process다.
- ex) 많은 소프트웨어 제품은 Run time에 축소된 Binary 파일을 Upgrade하기 위해 Shared Library를 사용한다.
- 많은 Web Server들이 동적 콘텐츠를 제공하기 위해 Shared Library의 Dynamic Linking에 의존한다.
Example
// 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;
}
Static Linking
프로그램은 Compiler Driver에 의해 번역되고 Link된다.
linux> gcc -Og -o prog main.c sum.c
linux> ./prog
Compiler Driver
- C Preprocessor (cpp)
- main.c 파일을 ASCII Intermdiate file인 main.i로 번역한다.
- C Compiler (cc1)
- main.i 으로 ASCII assembly 언어로 된 main.s로 번역한다.
- Assembler (as)
- main.s 을 binary relocatable object 파일인 main.o로 번역한다.
relocatable: 뒤에서 설명한다.
- main.s 을 binary relocatable object 파일인 main.o로 번역한다.
- Linker (ld)
- 위 과정을 sum.c도 동일하게 거친다.
- 필요한 object 파일인 main.o와 sum.o 을 결합하여 binary executable object 파일인 prog을 만든다.
relocatable:
- 어떤 Symbol들은 main.c에서 sum.c 코드를 참조할 수 있고 그 반대도 할 수 있다.
- 각각의 Symbol에 Access하는 주소, 메모리에 올라갈 때 어디에 올라갈지 정해져 있지 않다.
- Linking을 해야 가상 메모리 주소의 어디에 올라가는지 해당하는 코드의 상대적인 offest을 알 수 있다.
- 즉, 가상 메모리의 주소의 위치를 모르기 때문에 relocate 해야 한다.
- 그래서 나중에 relocate 되는 object 파일이라고 한다.
Why Linkers?
Reason 1: Modularity
- 프로그램은 하나의 단일 덩어리가 아닌 더 작은 소스 파일의 모음으로 작성될 수 있다.
- 흔히 사용하는 함수들을 Library로 만들어서 Build 할 수 있다.
- Math, Standard C
Reason 2: Efficiency
- Time: Separate Compilation
- 수정된 파일만 다시 Compile 하고 Link 할 수 있다.
- 수정되지 않은 다른 파일들은 다시 Compile 할 필요 없다.
- Space: Libraries
- 흔히 사용하는 함수들을 한 파일에 모아 놓을 수 있다.
- 실행 파일과 실행 중인 메모리는 실제로 사용되는 함수들의 코드만 포함된다.
What Do Linkers Do?
Step 1: Symbol resolution
- Symbol(전역 변수, 함수 등)들을 정의하고 나중에 참조하여 사용할 수 있다.
void swap() {…} /* define symbol swap */
swap(); /* reference symbol swap */
int *xp = &x; /* define symbol xp, reference x */
- Symbol 정의는 Assembler에 의해 Symbol table의 Object 파일에 저장된다.
- Symbol table은 structs 배열이다.
- 각 entry는 이름, 크기, 위치를 포함하고 있다.
- Symbol이 해결되는 단계에서, Linker는 각 Symbol의 참조를 정확히 한 개의 Symbol 정의와 관계를 짓는다.
Step 2: Relocation
- 분리된 '코드와 Data Section'을 하나로 병합한다.
- .o 파일의 상대 위치에서 실행 파일의 최종 절대 메모리 위치로 Symbol을 재배치한다.
- 이러한 symbol들의 모든 참조를 새로운 위치로 Reflect 하도록 Update 한다.
Objects and Symbols
Three Kinds of Object Files (Modules)
Object 파일들은 세 가지로 분류될 수 있다.
- Relocatable object file (.o)
- Binary Code와 Data를 가지고 있으며, 실행 가능한 Object 파일을 만들기 위해 Compile time에
재배치 가능한 다른 Ojbect 파일들과 결합될 수 있는 것을 가리킨다.
- Binary Code와 Data를 가지고 있으며, 실행 가능한 Object 파일을 만들기 위해 Compile time에
- Executable object file (a.out)
- Binary Code와 Data를 가지고 있으며, 메모리로 직접 Load 되어 실행될 수 있는 것을 가리킨다.
- Shared object file (.so)
- 재배치 가능한 오브젝트 파일의 특별한 타입으로,
Load time 또는 Run time에 동적으로 메모리로 Load 되고 Linking 될 수 있는 것을 가리킨다. - Windows에서 Dynamic Link Libraries (DLLs)라고 불린다.
- 재배치 가능한 오브젝트 파일의 특별한 타입으로,
Executable and Linkable Format (ELF)
- objcet 파일들의 표준 Binary 형식
- One unified format for
- Relocatable object files (.o),
- Executable object files (a.out)
- Shared object files (.so)
ELF Object File Format
.o 파일, a.out 파일, .so 파일들은 다음과 같은 형식을 갖고 생성된다.
- ELF header (16 B)
- Word Size, Byte Ordering, File Type(.o, exec, .so), Machine Type, File Offest of Section Header Table 등
- Section header table
- 각 Section의 Offset과 Size
- .text section
- Compile 된 프로그램의 Machind code
- .rodata section
- 읽기 전용 Data: jump tables
- .data section
- 초기화된 전역 변수들
- 초기화된 Static 속성을 갖는 변수들
- 공간을 차지함.
- .bss section
- 초기화되지 않은 전역 변수들
- -> 파일 안에서 공간을 차지하지 않는다.
- object 파일에 있는 내용으로 초기화되지 않았기 때문에 실제 해당하는 공간을 차지하지 않고
위치만 가지고 있다. - 초기화된 전역 변수와 초기화되지 않은 변수를 구분함으로써 공간 효율성을 높일 수 있다.
- -> object 파일이 생성되면 이렇게 section을 구분함으로써 공간을 차지하지 않아서
파일의 크기가 작아진다. - 나중에 이런 변수들이 메모리에 올라갈 때 0으로 초기화되는데 그때 공간을 차지한다.
- bss -> Block Started by Symbol
- -> Better Save Space
- 공간을 차지하지 않음
- 초기화되지 않은 전역 변수들
- .symtab section
- 프로그램에서 정의되고 참조되는 함수들과 전역 변수들에 대한 정보를 갖는 Symbol table
- 지역 변수들은 이곳에 들어가지 않는다.
- 지역 변수는 Link와 전혀 상관없다. -> Run time에 Stack에 들어간다.
- .rel .text section
- .text section에 있는 location에 대한 list를 가지고 있는데 나중에 Linker가 Object 파일들을 Linking하게 되면 이 Location들은 바뀔 수 있다. 즉, 재배치 가능한 symbol의 주소를 가지고 있다.
- executable object 파일에서 변경되어야 하는 명령어의 주소를 가지고 있다.
- 변경을 위한 명령어들을 가지고 있다.
- .rel .data section
- Module에 의해 Reference가 되거나 정의가 되어 있는 전역 변수 또는 함수들에 대한 Relocation 정보를 가지고 있다.
- 병합된 executable object에서 변경되어야 하는 Pointer Data의 주소를 가지고 있다.
- .debuf section
- symbolic 디버깅을 위한 정보를 가지고 있다.
- gcc -g하면 이곳에 필요한 정보가 들어간다.
Symbols and Symbol Tables
- 각 relocatable object module, m은 symbol table을 가지고 있다.
- symbol table은 m에 의해 정의되고 참조되는 symbol에 대한 정보를 가지고 있다.
- Linker 관점에서 세 종류의 symbol이 있다.
- Global Symbols
- 다른 module에 참조될 수 있는 module m에 의해 정의된 Symbol
- non-static C 함수, non-static 전역 변수
- External Symbols
- module m에 의해 참조되지만 다른 module에 의해 정의된 Global Symbol
- Local Symbols
- 특정한 module m에 의해서만 정의되고 참조되는 Symbol
- Static 속성을 가지는 C 함수와 전역 변수
- Local 변수와 다른 의미다.
- Global Symbols
Local linker symbols vs. Local program variables
Local 변수는 Stack에 들어가고, Local Symbol은 Symbol table에 들어간다.
- static으로 선언하지 않은 변수들은 symbol table에 들어가지 않는다.
단, 전역 변수는 local symbol로 본다. -> symbol table에 들어간다. - static으로 선언하지 않은 local 변수들은 run time에 stack에서 관리되고 linker와 관련 없다.
헷갈리는 것.
symbol table은 .symtab에 있고, .symtab은 module에 의해 정의되고 참조되는 전역 함수, 변수에 대한 정보를 저장한다.
전역 변수는 local symbol로 보는 것이 맞나..? global symbol로 보는 것이 맞나?
global symbol은 symbol table에 들어가나?
Step 1: Symbol Resolution
Local Symbols
- Local non-static C variables vs. local static C variables
- local non-static C variables: stored on the stack
- local static C variables: stored in either .bss, or .data
int f() {
static int x = 0;
return x;
}
int g() {
static int x = 1;
return x;
}
- Compiler는 x에 대한 정의를 .data에 각각 공간을 할당한다.
- Symbol table에 x.1, x.2와 같이 서로 구분이 되도록 Local symbol을 생성한다.
How Linker Resolves Duplicate Symbol Definitions
Linker가 중복으로 선언된 Symbol을 어떻게 Resolve 할까?
- 프로그램 Symbol들은 Strong 또는 Weak으로 나뉜다.
- Strong: 함수 이름들, 초기화된 전역 변수들
- Weak: 초기화되지 않은 전역 변수들
Linker’s Symbol Rules
Strong과 Weak을 기준으로 규칙을 정했다.
- Rule 1: Multiple strong symbols are not allowed
- 각 Item마다 한 번만 정의될 수 있다.
- 위배될 경우 Linker error 발생
- ex) a.c에서 int cnt = 5, b.c에서 int cnt = 10 -> strong 2개
- Rule 2: Given a strong symbol and multiple weak symbols, choose the strong symbol
- 'Weak Symbol'에 대한 참조는 'Strong Symbol'로 확인된다.
- Rule 3: If there are multiple weak symbols, pick an arbitrary one
Linker Puzzles
Two weak definitions of x (rule 3)
#include <strio.h>
void f(void);
int x;
int main() {
x = 15213;
f();
printf("x = %d\n", x);
return 0;
}
int x;
void f() {
x = 15212
}
- Compile시 에러는 발생하지 않는다.
- Run-time에 bug가 발생할 수 있다.
- 프로그래머가 f()에서 x가 15212로 변하는 것을 예상하지 않았다면,
의도하지 않은 결과가 발생할 수 있다.
- 프로그래머가 f()에서 x가 15212로 변하는 것을 예상하지 않았다면,
Another example (rule 2)
#include <stdio.h>
void f(void);
int y = 15212;
int x = 15213;
int main() {
f();
printf("x = 0x%x y = 0x%x \n", x, y);
return 0;
}
double x;
void f() {
x = -0.0;
}
- x의 주소가 0x601020, y의 주소가 0x601024라고 가정한다.
- f() 함수에 x에 8바이트만큼 -0.0으로 값을 변화시키면 y가 negative 0이 될 수 있다.
=> memory overwrite
Global Variables
- 가급적 사용을 피한다.
- 사용한다면,
- static 사용하기.
- 전역 변수를 선언하면 초기화하기
- 외부에서 전역 변수를 참조할 때 extern 키워드 사용하기.
extern 키워드란? https://seonbicode.tistory.com/10
Step 2: Relocation
- Step 1: Symbol resolution
- Linker가 Symbol resolution을 완료하면
코드의 각 Symbol Reference를 정확히 하나의 Symbol Definition과 연결한다. - 이때 Linker는 입력 Object Module에 있는 Code, Data Section의 정확한 크기를 알 수 있다.
- Linker가 Symbol resolution을 완료하면
- Next Step is the relocation step (Step 2)
- 입력 module을 병합하고 각 symbol에 run-time 주소를 할당한다.
Relocation Entries
- Assembler가 Object Module을 생성할 때 코드와 데이터가 최종적으로 메모리에 저장될 위치를 알 수 없다.
- module이 참조되는 전역 변수의 외부 정의 함수의 위치도 모른다.
- 따라서 Assembler가 최종 위치를 알 수 없는 Object에 대한 참조를 발견할 때마다
Object 파일을 Excutable 파일로 병합할 때 참조를 수정하는 방법을
Linker에 알려주는 Relocation Entry를 생성한다. - Code에 대한 Relocation Entry는 .rel .text section에 저장한다.
- Data에 대한 Relocation Entry는 .rel .data section에 저장한다.
Relocation Entries
- Linking 하기 전에 obj 파일 안에서 array와 sum에 대한 주소가 결정되지 않았다고 마킹되어 있다.
- Linker가 Linking 할 때 할당된다.
- mov $0x0, %edi
- array 주소를 edi에 넣어야 한다.
- 그전에 array 주소를 찾아야 한다.
- callq 13 <main+0x13>
- sum 함수로 Jump 해야 하는데 주소를 모르는 상황.
- Linking할 때 sum.c 에서 결정이 되는 것이어서 main에서 알 수 없다.
Linker에 의해 Linking 된 후 .text section
- 4번째 줄의 0x601018은 array 주소가 resolve 된 것이다.
- 5번째 줄 4004e8은 sum이라는 함수를 가리킨다.
- Using PC-relative addressing for sum(): 0x4004e8 = 0x4004e3 + 0x5
- PC에서 전의 주소를 더한 만큼이 sum의 위치를 찾을 수 있다.
Loading Executable Object Files
Static Linking and Dynamic Linking
Packaging Commonly Used Functions
- 어떻게 자주 사용되는 함수들을 패키지로 만들 수 있을까?
- Math, I/O, memory management, string manipulation, ...
- 1. API들을 코드에 다 넣는다.
- 생성되는 실행 가능한 파일의 크기가 커진다.
- 공간, 시간 적으로 비효율적이다.
- 2. 함수 또는 기능별로 파일을 분리한다.
- 적절한 binary와 프로그램을 명시적으로 Link한다.
- 효율적이지만, 프로그래머가 일일이 해줘야 한다.
Old-fashioned Solution: Static Libraries
- 관련된 relocatable obj 파일을 인덱스를 사용하여 파일 한 개로 연결한다. (called an archive)
- 하나 이상의 Archive에서 Symbol을 검색하여 확인되지 않은 외부 참조를 해결하도록 Linker를 향상한다.
- Archive의 Member 파일이 reference를 resolve하면 실행 가능한 파일로 Link한다.
Creating Static Libraries
Commonly Used Libraries
- libc.a (C standard library)
- libm.a (C math library)
Linking with Static Libraries
Compile Time에 Linking이 일어난다.
Using Static Libraries
- 외부의 참조를 resolve하기 위한 Linker 알고리즘
- Command Line 순서에 따라 .o 파일들과 .a 파일들을 스캔한다.
- 스캔하는 동안, 현재 resolve되지 않은 참조 목록을 유지한다.
- 각각의 새로운 .o , .a 파일인 obj가 발견되면 obj에 정의된 Symbol에 대해
목록에서 unresolved reference를 resolve한다.
- Command line 순서가 잘못되었기 때문에 두 번째에서 에러가 발생한다.
- Command Line 마지막에 library를 넣어야 한다.
Modern Solution: Shared Libraries
Dynamic Library는 Shared Library, Shared Linking이라고도 한다.
- Static Library는 Compile time에 Static Linking을 통해서 Link한다.
- 문제점
- 저장된 실행 파일이나 실행하고 있는 실행 파일에서 같은 Library를 가지고 있다.
=> 중복된 콘텐츠를 갖고 있다. 공간적인 비용을 지불한다. - 어떤 Library의 버그를 고칠 때 각 프로그램을 다시 Compile과 Linking을 해야 한다.
- 저장된 실행 파일이나 실행하고 있는 실행 파일에서 같은 Library를 가지고 있다.
- 문제점
- Shared Libraries
- Load-time 또는 Run-time에 동적으로 응용 프로그램에 Load되고 Link되는 코드 및 데이터가 포함된 Object
- Dynamic link library, DLLs, .so 파일로 불린다.
- Dynamic Linking은 실행 파일의 첫 번째 load와 run(load-time linking)에 일어난다.
- Dynamic Linking은 프로그램 실행에 일어난다. (run-time linking)
- In Linux, this is done by calls to the dlopen() interface.
- Distributing software.
- High-performance web servers.
- Runtime library interpositioning.
- In Linux, this is done by calls to the dlopen() interface.
- Shared library routines은 여러 Process에서 공유할 수 있다.
- Load-time 또는 Run-time에 동적으로 응용 프로그램에 Load되고 Link되는 코드 및 데이터가 포함된 Object
Dynamic Linking at Load-time
- addvec.c multvec.c 파일로 shared library를 생성한다.
- main2.o를 만들고 Linking할 때 Linker가 부분적인 Linking을 통해 prog21을 만든다.
- 부분적이라는 것은 main2.o에서 addvec, multvec 함수가 있었다고 하면
Static Linking에선 이 Library를 모두 main2.o와 병합하여 prog21을 만들었다. - Dynamic Linking에선 main2.o에서 사용되고 있는 함수를 main2.o가 참조하겠다는 체크만 해놓는다.
- 부분적이라는 것은 main2.o에서 addvec, multvec 함수가 있었다고 하면
- 프로그램을 실행할 때 Loader가 libc.so, libvector.so에서
해당하는 외부 참조가 되고 있는 Symbol들을 resolve하면서 Linking한다. - Loading할 때 Dynamic Linker에 의해서 메모리에 완전히 Link된 실행파일을 생성한다.
- 메모리에 올라갈 때 Dynamic Linking이 일어난다.
- A, B 프로그램이 있을 때 둘 다 메모리에 Load 되는 시점에 Linking이 되기 때문에
메모리에 Shared Library가 하나만 있으면 된다. - Static Library는 각각에 완전히 포함되어 있다.
Static Linking
Static Linking은 말 그대로 실행파일을 만들 때 라이브러리를 같이 포함시켜 .exe 파일을 만드는 것을 정적 링킹이라고 한다.
- 장점
- 정적 라이브러리를 사용해 컴파일을 하면 링커가 프로그램이 필요로 하는 부분을 라이브러리에서 찾아 실행파일에 바로 복사를 한다.
- 실행파일에 다 들어가 있기 때문에 라이브러리가 필요가 없다.
- 미리 컴파일되어 있기 때문에 컴파일 시간이 단축된다.
- 직접 구현한 코드를 라이브러리화 시켜 기술 유출 방지로 사용 가능하다.
- 단점
- 실행 파일 내에 라이브러리 코드가 저장되기 때문에 메모리를 어마어마하게 잡아먹는다.
- 특히, 멀티유저 시스템인LINUX, UNIX OS의 경우 1번은 더욱 심각하다.
Dynamic Linking
그러면 Dynamic Linking은 무엇일까.
정적 링킹을 쓰니까 메모리에 쓸데없이 똑같은 게 너무 많이 들어있는 게 아닌가.
그래서 이 cout과 같이 많이 쓰는 라이브러리는 메모리에 하나만 올리자!
그리고 이 프로그램이 cout을 호출할 때는 메모리에 있는 cout으로 점프해 그쪽으로 간 후에 실행한 다음에 다시 돌아오게 하자는 것이 Dynamic Lingking이다.
현재 Linux, Unix, 뿐만 아니라 Windows에서도 별다른 옵션을 주지 않으면 Linking은 Dynamic Linking(동적 링킹)을 사용하게 된다.
이 동적 링크 라이브러리를 바로 DLL(Dynamic Link Library)라고 한다. 즉 윈도에서 동적 링킹 할 때 사용되는 라이브러리인데 이 dll파일들은 보통 windows 시스템 디렉터리에 존재한다.
# DLL(Winodws) == Shared Library(Linux, Unix)
Linux나 Unix에서는 DLL이라 부르지 않고 Shared Library라고 부른다.
따라서 .so라고 쓰거나 .sa라고 쓴다.
만약 "Hello World"를 C++ 라이브러리랑 같이 링킹을 해서 실행을 했는데 메모리에 dll파일이 없다. 그러면 실행을 못하니 운영체제가 메모리에 dll파일을 load시켜주는 것이다. 이렇게 메모리에 한번 적재되고 나면 그다음부터는 적재된 dll파일이 수행되는 것이다.
따라서 동적 라이브러리는 프로그램이 실행될 때 링크된다.
# Stub(스텁) 이란?
- 라이브러리가 메모리에 존재하지 않을 때, 라이브러리가 메모리에 상주할 수 있도록 라이브러리 루틴을 적절히 적재하는 방법을 알려주는 작은 코드 조각을 `stub`이라고 한다.
- 스텁은 모든 라이브러리 루틴에 들어 있는데 A라이브러리의 a루틴이 호출되면 stub이 루틴 주소로 대체 되어 그 다음에 그 코드가 한번 더 실수행 될 때는 Dynamic Linking 없이 바로 주소를 참조해 실행할 수 있게 되는 것.
- 런타임시 해당 루틴이 불리면 그 스텁은 자신을 그 루틴이 들어있는 주소값과 바꿔치기 하는 것이다.
- 한 번 스텁이 불리게 되면 스텁이 있었다는 사실은 사라지고 주소가 되어 한몸이 되는 것.
- 단 한개의 원본을 사용하게 해주어 코드가 중복 적재 되지 않아 메모리 절감 효과를 일으킨다.
- 장점
- 메모리 요구사항이 훨씬 적다.
- 단점
- 프로그램 영역에서 라이브러리가 저장되어 있는 주소로 점프를 하게 되어 성능상 약간의 overhead가 발생한다.
- Dynamic Linking을 위한 불필요한 코드가 추가된다.(점프해야 하니까)
성능상에서는 정적 링킹이 좋지만, 메모리 관리 차원에서는 동적 링킹이 좋다.
추가로 동적 링킹이 좋은 점은, 우리가 direct X라는 게임 라이브러리를 활용한 게임을 즐긴다고 할 때, 이 게임에 성능이 업그레이드되면 그 게임을 다시 사는 것이 아니라 Library의 버전만 up 하면 되는 것이다. 그냥 업데이트만 해주면 되는 것!
dll 파일은 따로 그대로 저장되기 때문에 원래 코드와 라이브러리가 별도로 있는 것이므로 블랙 코드는 그대로 있고, library만 버전 업되면 그거에 대한 성능을 그대로 받을 수 있게 되는 것이다.
출처: https://coder-in-war.tistory.com
Dynamic Linking at Run-time
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];
int main() {
void * handle;
void( * addvec)(int *, int *, int *, int);
char * error;
/* Dynamically load the shared library that contains addvec() */
handle = dlopen("./libvector.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
...
/* Get a pointer to the addvec() function we just loaded */
addvec = dlsym(handle, "addvec");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(1);
}
/* Now we can call addvec() just like any other function */
addvec(x, y, z, 2);
printf("z = [%d %d]\n", z[0], z[1]);
/* Unload the shared library */
if (dlclose(handle) < 0) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
return 0;
}
- dlopen 함수를 사용해서 libvector.so라는 Dynamic Library를 load한다.
- handle에서 addvec라는 이름을 가진 함수를 linking한다.
Linking Summary
- Linking은 여러 Object 파일로 프로그램을 구성할 수 있는 기술이다.
- Linking은 Program Lifetime의 다른 시간에 일어난다.
- Compile time (when a program is compiled)
- Load time (when a program is loaded into memory)
- Run time (while a program is executing)
- Liniking을 이해하면 심각한 오류를 방지하고 더 나은 프로그래머가 될 수 있다.
Library Interpositioning
- 프로그래머가 임의의 함수에 대한 호출을 가로챌 수 있는 Linking 기술
- 언제 발생하는가?
- Compile time: 코드가 컴파일될 때
- Link time: relocatable object 파일이 static하게 excutable object 파일로 link될 때
- Load / Run time: excutable object 파일이 메모리에 Load, dynamic link된 후 실행될 때
Some Interpositioning Applications
- Security
- Confinement (sandboxing)
- Behind the scenes encryption
- Debugging
- Monitoring and Profiling
- 함수 호출 횟수를 셀 때
- Characterize call sites and arguments to functions
- Malloc tracing
- Detecting memory leaks
- Generating address traces
기존의 코드를 다시 코딩하여 맞춤 적용하는 기술.
Example program
#include <stdio.h>
#include <malloc.h>
int main() {
int *p = malloc(32);
free(p);
return(0);
}
- Goal:
- 할당된 Block과 Free Block의 주소와 크기를
프로그램을 중단하지 않고, 소스 코드를 수정하지 않고 추적한다.
- 할당된 Block과 Free Block의 주소와 크기를
- Thre solutions:
- compile, link, load/run time에 libmalloc과 free 함수를 interpose한다.
Compile-time Interpositioning
#ifdef COMPILETIME
#include <malloc.h>
#include <stdio.h>
/* malloc wrapper function */
void * mymalloc(size_t size) {
void * ptr = malloc(size);
printf("malloc(%d)=%p\n", (int)size, ptr);
return ptr;
}
/* free wrapper function */
void myfree(void * ptr) {
free(ptr);
printf("free(%p)\n", ptr);
}
#endif
기존의 malloc와 free를 mymalloc, myfree로 대체한다.
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)
void *mymalloc(size_t size);
void myfree(void *ptr);
linux> make intc
gcc -Wall -DCOMPILETIME -c mymalloc.c
gcc -Wall -I. -o intc int.c mymalloc.o
linux> make runc
./intc
malloc(32)=0x1edc010
free(0x1edc010)
Link-time Interpositioning
#ifdef LINKTIME
#include <stdio.h>
void * __real_malloc(size_t size);
void __real_free(void * ptr);
/* malloc wrapper function */
void * __wrap_malloc(size_t size) {
void * ptr = __real_malloc(size);/* Call libc malloc */
printf("malloc(%d) = %p\n", (int)size, ptr);
return ptr;
}
/* free wrapper function */
void __wrap_free(void * ptr) {
__real_free(ptr);/* Call libc free */
printf("free(%p)\n", ptr);
}
#endif
- "-Wl" Flag는 인자를 Linker에 전달하여 각 쉼표를 공백으로 대체한다.
- "--wrap,malloc" 인자는 Linker에게 특별한 방법으로 Reference를 해결하도록 지시한다.
- malloc에 대한 참조가 __wrap_malloc으로 resolve된다.
- __real_malloc에 대한 참조가 malloc으로 resolve된다.
Load/Run-time Interpositioning
#ifdef RUNTIME
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
/* malloc wrapper function */
void * malloc(size_t size) {
void * ( * mallocp)(size_t size);
char * error;
mallocp = dlsym(RTLD_NEXT, "malloc");/* Get addr of libc malloc */
if ((error = dlerror()) != NULL) {
fputs(error, stderr);
exit(1);
}
char * ptr = mallocp(size);/* Call libc malloc */
printf("malloc(%d) = %p\n", (int)size, ptr);
return ptr;
}
/* free wrapper function */
void free(void *ptr) {
void (*freep)(void *) = NULL;
char *error;
if (!ptr)
return;
freep = dlsym(RTLD_NEXT, "free"); /* Get address of libc free */
if ((error = dlerror()) != NULL) {
fputs(error, stderr);
exit(1);
}
freep(ptr); /* Call libc free */
printf("free(%p)\n", ptr);
}
#endif
linux> make intr
gcc -Wall -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
gcc -Wall -o intr int.c
linux> make runr
(LD_PRELOAD="./mymalloc.so" ./intr)
malloc(32) = 0xe60010
free(0xe60010)
- dlsym을 사용하여 mallocp에 malloc의 포인터를 사용할 때 받아온다.
- mallocp(size) -> malloc 호출
- LD_PRELOAD 환경 변수는 dynamic linker에 mymalloc.so을
먼저 확인하여 unresolved ref(ex: malloc)를 해결하도록 지시한다.
Interpositioning Recap
- Compile Time
- malloc/free 호출이 malloc/myfree 호출로 매크로 확장됨
- Link Time
- Linker에게 특별한 이름으로 트릭을 써서 해결
- malloc -> __wrap_malloc
- __real_malloc ->malloc
- Linker에게 특별한 이름으로 트릭을 써서 해결
- Load / Run Time
- dynamic linking을 사용하여 Library malloc/free를 다른 이름으로 Load하는 사용지 지정 malloc/free 구현