01 링킹의 기본 이해
링킹과정: 결합과 재배치(relocation)
링킹 과정 절차
결합: ELF포맷으로 되어 있는 각 오브젝트를 섹션 종류별로 하나의 오브젝트로 합치는 과정
object 파일들의 각 섹션이(.text, .data, .bss 등) 종류별로 합쳐져 하나의 ELF 파일을 구성
relocation(재배치): 결합 과정에서 합쳐진 각 섹션을 실제 코드에 맞게 조정. 메모리에 바이너리 이미지가 로드될 위치(x86 리눅스는 0x8048000)를 시작으로 결합 과정이 끝난 마이너리에 각 심볼이 가지게 될 실제 주소를 구하고, 해당 심볼을 참조하는 부분에 대해 구한 주소를 설정
02 ELF 바이너리 포맷 구조
ELF 바이너리 포맷이란?
ELF(Executable and Linkable Format)
UNIX/LINUX 시스템에서 가장 널리 사용되는 바이너리 포맷
a.out (초창기 unix 시스템) -> COFF(Common Object File Format) (System V 초기)-> ELF (현재)
ELF 바이너리 포맷의 구조
ELF header |
|
Program header table (required for executables) |
재배치 불가능한 ELF 오브젝트 Ex) test실행 바이너리 파일 |
섹션 & 세그먼트 영역 |
|
Section header table (required for relocatables) |
재배치 가능한 ELF 오브젝트 Ex) main.o, funcs.o 바이너리 파일 |
ELF 헤더
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
ELF 헤더에는 어디서부터 어디까지가 프로그램 헤더 테이블이고, 어디서부터 어디까지가 섹션 헤더 테이블인지에 대한 정보가 들어있음
시스템 로더는 프로그램 헤더 테이블을 읽어 프로그램을 수행할 때 어디서부터 어디까지를 메모리로 로드해야하는지 알수있음
섹션 헤더테이블은 섹션정보를 담고 있는데, 섹션은 링커나 디버거가 ELF 바이너리 포맷을 해석하는 단위임
e.g.
>readelf -h test
/usr/include/elf.h 에 ELF 헤더 구조가 나와있음
e_phoff: 프로그램 헤더 테이블의 파일상 오프셋
e_shoff: 섹션 헤더 테이블의 파일상 오프셋
e_phentsize e_shentsize: 각 헤더의 크기
e_phnum, e_shnum: 헤더의 갯수
e_entry: 프로그램의 시작 위치 (_start 레이블)
프로그램 헤더 테이블
typedef struct
{
Elf64_Word p_type; /* Segment type */
Elf64_Word p_flags; /* Segment flags */
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment */
} Elf64_Phdr;
- 프로그램 헤더 구조체로 이루어진 배열, 하나의 엔트리는 하나의 세그먼트를 지칭
- 프로그램 헤더 구조체에는 세그먼트 타입, 파일상 위치, 크기, 메모리로 로드될 가상/물리 주소 등의 정보등이 담겨있음
- 셸에서 바이너리를 실행하면 시스템 로더는 바이너리의 프로그램 헤더 테이블을 읽어 바이너리에서 어떤 부분을 메로리로 로드해 프로세스를 만들것인지 알수 있음
e.g.
>readlelf -l test (프로그램 헤더 테이블 정보 보기)
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x000000000000091c 0x000000000000091c R E 200000
- 프로그램 헤더 타입
타입명 |
의미 |
LOAD |
메모리에 로드해야 되는 세그먼트 |
NULL |
사용되지 않는 프로그램 헤더 |
DYNAMIC |
동적 링크를 해야 되는 자료를 포함하는 세그먼트 |
INTERP |
인터프리터 이름을 포함하는 세그먼트 |
PHDR |
프로그램 헤더를 포함하는 세그먼트 |
NOTE |
노트 정보를 포함하는 세그먼트 |
SHLIB |
예약된 프로그램 헤더 |
섹션 헤더 테이블
- 섹션 헤더 구조체의 배열
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
e.g> readelf -S main.o
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000029 0000000000000000 AX 0 0 4
Address: 메모리로 로드될 때의 주소
Offset: 섹션이 파일상 어떤 위치에 있는지 나타냄
Size: 섹션의 사이즈
EntSize: .symtab, .rel.test, .rel.data 등과 같이 고정된 사이즈를 가지는 엔트리들로 이뤄진 섹션들의 경우에 한 엔트리의 크기를 바이트 단위로 나타내는 필드
Flags: 섹션의 속성을 나타냄 W(데이터를 가지고 있음), A(프로세스 실행 동안 메모리를 차지함), X(인스트럭션을 가지고 있음), M(프로세서 고유의 특징을 위해 예약해 놓음) 등이 있다.
Link: 다른 섹션에 의존해 해석해야 되는 섹션에서 의존하는 섹션 인덱스를 표시
Info: 의족하는 섹션에 대해 그 외 정보를 표시
Align: 섹션이 메모리에서 어떤 단위로 정렬되어야 하는지를 나타냄
각종 섹션의 종류
타입명 |
의미 |
NULL |
비활성 섹션으로, 관련된 섹션이 없음을 의미 |
PROGBITS |
프로그램에서 정의된 섹션. 프로그래머에 의해 명시적으로 정의되었을 수도 있고(__attribute__((__section__("섹션명"))) , 컴파일러에 의해 기본적으로 정의되었을 수도 있음 |
SYMTAB |
심볼 테이블을 가지고 있는 섹션 |
STRTAB |
스트링 테이블을 가지고 있는 섹션. 여기서 스트링이란 ELF 바이너리를 해석하기 위한 여러가지 문자열을 말함. (여러 심볼의 스트링, 섹션이름 스트링등) |
RELA |
명시적인 가수가 있는 재배치 엔트리를 가지고 있는 섹션. 여기서 명시적인 가수란 A=B+C 에서 B가 심볼이라 가정할 때 C처럼 더하는 수를 의미. RELA섹션은 재배치 엔트리들로 구성되는데, 각 엔트리에는 재배치해야 될 위치와 심볼명들이 들어있음. 그리고 하나의 오브젝트는 여러 개의 재배치 섹션을 가질 수 있음 |
HASH |
심볼의 해시 테이블을 가지고 있는 섹션. 동적 링킹하는 오브젝트는 심볼해시 테이블을 가지는데, 이런 해시 테이블이 들어있는 섹션을 의미 |
DYNAMIC |
동적 링킹을 위한 정보가 들어있는 섹션 |
NOTE |
파일 정보가 들어있는 섹션 |
NOBITS |
아무런 공간을 차지하지 않는 섹션 .bss 섹션이 이에 해당 |
REL |
명시적인 가수가 없는 재배치 엔트리를 가지는 섹션. 가수부분을 제외하고 RELA 섹션과 많은 부분이 같다. |
SHLIB |
의미없음 |
DYNSYM |
동적 링킹 심볼들이 아주 작게 들어가는 섹션 |
LOPROC |
프로세서 의존적인 정보를 포함하는 섹션 |
HIPROC |
프로세서 의존적인 정보를 포함하는 섹션 |
LOUSER |
애플리케이션을 위해 예약된 인덱스의 하위 한도를 포함하는 섹션 |
HIUSER |
애플리케이션을 위해 예약된 인덱스의 상위 한도를 포함하는 섹션 |
- ELF 바이너리 포맷에 포함되는 섹션 리스트
섹션명 |
설명 |
.text |
프로그램 수행에 필요한 인스트럭션이 들어가는 섹션 |
.rodata .rodata1 |
프로그램 메모리 이미지에 포함되는 read-only data가 들어가는 섹션 C 소스파일에 포함된 문자열이나 const 변수같은 읽기전용 data |
.data .data1 |
초기화된 전역 data혹은 static data를 포함하는 섹션. 여기서 초기화 되었다는 말은 0이 아닌 초기 값을 가지는 실제 메모리상에 잡히는 전역변수 혹은 static 변수와 같은 data를 의미 |
.bss |
초기화 되지 않은 전역 data 혹은 static data를 포함하는 섹션이다. 0인 초기값을 가지는 실제 메모리상에 잡하는 전역변수 혹은 staic변수 .bss 섹션은 실제 파일 이미지 상에는 포함되지 않고, 프로세스를 생성할 때 메모리 상에 공간이 할당된다. |
… |
|
03 ld를 사용한 링킹
-l : 링킹 될 라이브러리 지정
e.g> test 바이너리에 fl 라이브러리 포함
$ ld -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o main.o funcs.o -lc -lfl
-L: 라이브러리 검색 디렉토리 지정
$ ld -L/opt/lib -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o main.o funcs.o -lc -ltest
-static: 공유 라이브러리와 정적 라이브러리가 동시에 있는 경우 정적 라이브러리와 링킹
$ ld -static -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o main.o funcs.o -\( -lgcc -lgcc_eh -lc -\)
--> -\(, -\) 사이에 오는 라이브러리들에 대해 참조하는 심볼을 완전히 찾을 때까지 라이브러리들을 계속 스캔하며 찾음
-shared: 공유 라이브러와 링킹
-r: 부분링킹, 재배치 가능한 오브젝트들을 링킹해 하나의 큰 재배치 가능한 오브젝트로 만드는 것
$ ld -r -o sub.o memo.o calendar.o
알아두면 유용한 ld옵션
-M, --pring-map, -Map : 링크 맵 출력
$ ld -M -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o main.o funcs.o -lc
--> 링킹된 바이너리의 링크맵을 보여줌
-s/ --strip-all : 링킹된 오브젝트에서 심볼 정보를 제거
-S/ --strip-debug: 디버거 심볼정보만 제거
-x: 외부로 export되지 않는 모든 local symbol을 제거
-X: 모든 임시 local symbol을 제거
--oformat: 출력되는 바이너리 포맷 지정 (objdump -i 로 지정가능한 바이너리 포맷 확인 가능)
-rpath, -rpath-link: 공유 라이브러리 검색 패스 추가
-e/ --entry: 엔트리 포인터 설정
--section-start / -T[text|data|bss] : 섹션 위치 지정
$ ld -Ttext 0x0 -s --oformat binary -e startup -o bootld startup.o boot.o net.o
--> .text 섹션의 위치를 0번지로 설정
-T [링커스크립트 패스]: 기본 링커 스크립트 변경
$ ld -T test.lds -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o main.o funcs.o -lc
--wrap : 랩퍼함수 사용
$gcc -o test main.c -Wl, --wrap,malloc
--trace-symbol=[심볼명]: 심볼 정의 및 참조 찾기
--start-group, --end-group: 교차참조 해결
두 옵션 사이에 있는 정적 라이브러리에 대해서는 여러번 스캔하면서 심볼 참조를 찾아서 링킹에 포함시킴
04 링커 스크립트
링커스크립트: ld가 링킹 과정을 수행하는데 있어 모든 링킹 과정을 조절하는 스크립트
링커 스크립트의 이해
ld --verbose: default linker script 확인 가능
- 입력 오브젝트의 어떤 섹션들을 합쳐 출력 오브젝트의 어떤 섹션을 만들어라
- 어떤 섹션의 시작 주소는 몇 번지 부터이다.
- 어떤 주소를 가지는 (예: .test 섹션의 시작 위치등) 어떤 심볼을 정의ㅏ라
- 어떤 섹션의 시작 주소는 몇 바이트로 정렬하라
- 어떤 섹션은 버려라
등의 것들을 linker script로 ld에 지시 가능
링커 스크립트 기본 문법
1. SECTIONS 명령
SECTIONS
{
sections-명령
sections-명령
...
}
명령 타입:
- 출력섹션기술
- ENTRY 명령
- 심볼대입
- 오버레이 기술
2. 출력 섹션 기술
출력 오브젝트에 출력될 섹션들을 기술하는 명령
출력섹션명[address] [(type)] : [AT(lma)]
{
입력 섹션- 명령
입력 섹션- 명령
...
}[>region] [AT>lma_region] [:phdr :phdr ...] [=fileexp]
e.g.
==============
PHDRS { mydata PT_LOAD; } ; 해당 섹션을 포함시킬 세그먼트를 지정, mydata 세그먼트를 지정하고 .mysection을 mydata 세그먼트에 포함시킴
MEMORY { ; 시스템의 메모리 영역을 정의
ROM (rx): ORIGIN = 0X1000, LENGTH = 0X10000
SRAM : ORIGIN = 0X8000000, LENGTH = 0X100000
}
SECTIONS
{
. = 0x1000;
my_lma = ALIGN(4);
.mysection 0xA000000 : AT(my_lma) ; vma와 lma가 다를경우 lma를 명시
{
입력섹션 - 명령
입력섹션 - 명령
}
} >SRAM AT>ROM : mydata = 0x5A5A ; .mysection의 빈공간은 0x5A5A로 채움
=================
입력 섹션 명령 종류
-입력 섹션 기술
- 심볼 할당
- 직접 포함할 자료 값
- 특별한 출력 섹션 키워드
3. 입력 섹션 기술
생략
4. 심볼 대입 명령
생략
5. 사용 가능한 함수
생략
6. 기타 명령
생략
ld는 별로 쓸일 없을거 같아서 생략....ㅋ
06 라이브러리를 만들기
정적 라이브러리 : 컴파일 시 링커에 의해 라이브러리의 오브젝트 코드가 만들려는 바이너리에 추가되는 형태로 사용됨
공유 라이브러리 : 컴파일 시 라이브러리 함수가 사용된 곳에 공유 라이브러리를 사용한타는 표시만 해놓고 바이너리가 실행되는 순간에 동적 링커에 의해 링킹됨
정적 라이브러리 만들기
e.g.
$ar rscv libmy.a file1.o file2.o
$gcc -o like like.c -L./ -lmy
ar 명령 옵션
ar rus [라이브러리 이름] [오브젝트 파일들] : 기존 아카이브 파일에 오브젝트 추가
ar ds [라이브러리 이름] [오브젝트 파일들] : 기존 아카이브 파일에서 오브젝트 제거
ar x [라이브러리 이름] : 아카이브에서 오브젝트 파일 추출
ar t [라이브러리 이름] : 아카이브에 있는 파일 리스트 출력
공유 라이브러리 만들기
컴파일 시 -fPIC 옵션을 줘야함 (Position Independent Code)
e.g.
$gcc -fPIC -c file1.c file2.c
$gcc -shared -Wl,-soname,libmy.so.0 -o libmy.so.0.0/0 file1.o file2.o
$ln -s libmy.so.0.0.0 libmy.so # gcc 링크를 위한 파일
$ln -s libmy.so.0.0.0 libmy.so.0 # 동적 링크를 위한 파일
/etc/ld.so.conf 파일을 열어 libmy.so.0.0.0 파일이 있는 디렉토리를 추가
ldconfig 명령으로 /etc/ld.so.cache 새로 만들기
$gcc -o like like.c -L./ -lmy
출처 :
'Linux' 카테고리의 다른 글
Linux) 심볼릭 링크(Symbolic link) (0) | 2021.06.16 |
---|---|
리눅스) 커널 모드와 유저 모드, 시스템콜, 시스템 호출, 커널 동작 원리(x86 intel) (0) | 2020.11.21 |
리눅스) gcc 명령어 (0) | 2020.11.20 |
Vim Vi ) 복사, 붙이기, 합치기 (0) | 2020.11.20 |
댓글