본문 바로가기
Linux

링킹

by manchesterandthecity 2020. 10. 5.

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

 

 

 

출처 :

miamiaow.egloos.com/3166009

 

유닉스 리눅스 프로그래밍 필수 유틸리티 5장 ld linker

01 링킹의 기본 이해  링킹과정: 결합과 재배치(relocation) 링킹 과정 절차 결합: ELF포맷으로 되어 있는 각 오브젝트를 섹션 종류별로 하나의 오브젝트로 합치는 과정 object 파일들의 각 섹션이(.text

miamiaow.egloos.com

 

댓글