카테고리

asm (27) bootloader_x86_grub (1) C (92) compile (11) config (76) CPP (13) CSS (1) debugging (7) gimp (1) Go (1) html (1) Java (1) JavaScript (1) kernel (19) LibreOffice (3) Linux system progamming (21) MFC (1) opencv (4) OpenGL (1) PHP (1) Python (4) qemu (29) shell (3) socket (7) troubleshooting (2) ubuntu18.04 (2) windows (1)

2018/12/16

Linked, Link LD

Link
링커를 이해 하면 큰 프로그램을 구축 하는데 도움됨.
링커를 이해하면 여러 객체 파일에 같은 이름의 전역 변수를 만들 때 일어나는 것과 같은 위험한 프로그래밍 오류를 피할 수 있음.

링커을 지원하는 것은, 정적 라이브러리 특성을 사용하여 함수의 변수를 선언할 때 발생 하는 것과 같이 언어 범위 지정 규칙이 구현 되는 방식을 이해 하는데 도움됨.

링커 이해는 프로그램 로드 및 실행, 가상 메모리, 페이징 및 메모리 매핑과 같은 다른 시스템 개념을 이해 하는데 도움을 준다.

링커 이해를 통해 공유 라이브러리를 악용할 수 있다.

소스 파일 두개 준비.

cat main.c
#include <stdio.h>

void swqp();
int buf[2] = {1,2};
int main(void){
swap();
printf("buf[0] = %d, buf[1] = %d\n",buf[0], buf[1]);
return 0;
}

cat swap.c
extern int buf[];
int *buf0 = & buf[0];
static int *bufp1;

void swap(void){
int temp;
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}

오브젝트 생성 컴파일
gcc -c *.c

실행 파일 컴파일
gcc *.o -o lee_test

재배치 객체(.o) 파일
System code: .text
System data: .data

main.o
main() : .text
int buf[2] = {1,2] : .data

swap.o
swap() : .text
int *bufp0=&buf[0]] : .data
static int *bufp1 : .bss
swap()을 private으로 사용하더라도 .bss로 할당된다.

실행 객체 파일
Headers: .text
System code: .text
main(): .text
swap(): .text
More system code: .text

system data: .data
int buf[2] = {1,2}: .data
int *bufp0=&buf[0]: .data

int *bufpl: .bss

.symtab
.debug

오브젝트 형식 분석
sudo apt-get install objdump

CPU에 맞게 main.o 파일의 형식 분석한다.(-M intel 또는 amd)

objdump -d -M interl main.o
main.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0: 55                    push   rbp
   1: 48 89 e5              mov    rbp,rsp
   4: b8 00 00 00 00        mov    eax,0x0
   9: e8 00 00 00 00        call   e <main+0xe>
   e: 8b 15 00 00 00 00    mov    edx,DWORD PTR [rip+0x0]        # 14 <main+0x14>
  14: 8b 05 00 00 00 00    mov    eax,DWORD PTR [rip+0x0]        # 1a <main+0x1a>
  1a: 89 c6                mov    esi,eax
  1c: 48 8d 3d 00 00 00 00 lea    rdi,[rip+0x0]        # 23 <main+0x23>
  23: b8 00 00 00 00        mov    eax,0x0
  28: e8 00 00 00 00        call   2d <main+0x2d>
  2d: b8 00 00 00 00        mov    eax,0x0
  32: 5d                    pop    rbp
  33: c3                    ret


CPU에 맞게 swap.o 파일의 형식 분석한다.(-M intel 또는 amd)

objdump -d -M intel swap.o

swap.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <swap>:
   0: 55                    push   rbp
   1: 48 89 e5              mov    rbp,rsp
   4: 48 8d 05 00 00 00 00 lea    rax,[rip+0x0]        # b <swap+0xb>
   b: 48 89 05 00 00 00 00 mov    QWORD PTR [rip+0x0],rax        # 12 <swap+0x12>
  12: 48 8b 05 00 00 00 00 mov    rax,QWORD PTR [rip+0x0]        # 19 <swap+0x19>
  19: 8b 00                mov    eax,DWORD PTR [rax]
  1b: 89 45 fc              mov    DWORD PTR [rbp-0x4],eax
  1e: 48 8b 15 00 00 00 00 mov    rdx,QWORD PTR [rip+0x0]        # 25 <swap+0x25>
  25: 48 8b 05 00 00 00 00 mov    rax,QWORD PTR [rip+0x0]        # 2c <swap+0x2c>
  2c: 8b 12                mov    edx,DWORD PTR [rdx]
  2e: 89 10                mov    DWORD PTR [rax],edx
  30: 48 8b 05 00 00 00 00 mov    rax,QWORD PTR [rip+0x0]        # 37 <swap+0x37>
  37: 8b 55 fc              mov    edx,DWORD PTR [rbp-0x4]
  3a: 89 10                mov    DWORD PTR [rax],edx
  3c: 90                    nop
  3d: 5d                    pop    rbp
  3e: c3                    ret 

실행 파일 덤프
CPU에 맞게 lee_test 파일의 형식 분석한다.(-M intel 또는 amd)

objdump -d -M intel lee_test | less
000000000000068e <swap>:
 68e: 55                    push   rbp
...
...
...


링커의 맥락에서 세 가지 다른 종류의 심볼이 존재 함.
1. 전역 기호: 하나의 모듈에 정의되고 다른 모듈에서 참조 할 수 있는 심볼는 전역 심볼이라고 한다.

2. 외부 기호: 모듈에서 참조 하지만 일부 다른 모듈에서 정의 되는 전역 심볼임. 일반적으로 extern 키워드로 선언 된다.

비정적 함수와 비정적 전역 변수는 위의 두 범주에 속합니다.

3. 로컬 심볼: 단일 모듈로만 독점적으로 정의하고 참조 하는 심볼. 예를 들어 static 키워드로 선언 된 전역 변수나 함수는 해당 모듈에 대해 private이 된다.


심볼 테이블 확인
apt-get install readelf

readelf -s main.o
readelf -s main.o

Symbol table '.symtab' contains 14 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     9: 0000000000000000     8 OBJECT  GLOBAL DEFAULT    3 buf
    10: 0000000000000000    52 FUNC    GLOBAL DEFAULT    1 main
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

objdump -t main.o

main.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS* 0000000000000000 main.c
0000000000000000 l    d  .text 0000000000000000 .text
0000000000000000 l    d  .data 0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .rodata 0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame 0000000000000000 .eh_frame
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 g     O .data 0000000000000008 buf
0000000000000000 g     F .text 0000000000000034 main
0000000000000000         *UND* 0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000         *UND* 0000000000000000 swap
0000000000000000         *UND* 0000000000000000 printf

nm main.o
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 D buf
0000000000000000 T main
                 U printf
                 U swap

nm swap.o
                 U buf
0000000000000000 D bufp0
0000000000000000 b bufp1
0000000000000000 T swap

nm lee_test
0000000000200dc8 d _DYNAMIC
0000000000200fb8 d _GLOBAL_OFFSET_TABLE_
0000000000000750 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
00000000000008dc r __FRAME_END__
0000000000000770 r __GNU_EH_FRAME_HDR
0000000000201020 D __TMC_END__
0000000000201020 B __bss_start
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000201000 D __data_start
0000000000000610 t __do_global_dtors_aux
0000000000200dc0 t __do_global_dtors_aux_fini_array_entry
0000000000201008 D __dso_handle
0000000000200db8 t __frame_dummy_init_array_entry
                 w __gmon_start__
0000000000200dc0 t __init_array_end
0000000000200db8 t __init_array_start
0000000000000740 T __libc_csu_fini
00000000000006d0 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
0000000000201020 D _edata
0000000000201030 B _end
0000000000000744 T _fini
0000000000000508 T _init
0000000000000550 T _start
0000000000201010 D buf
0000000000201018 D bufp0
0000000000201028 b bufp1
0000000000201020 b completed.7696
0000000000201000 W data_start
0000000000000580 t deregister_tm_clones
0000000000000650 t frame_dummy
000000000000065a T main
                 U printf@@GLIBC_2.2.5
00000000000005c0 t register_tm_clones
000000000000068e T swap

정적 라이브러 구조
main.c
 Global 전역
  int buf[2] = {1, 2};
  int main{void}

 external
 {
swap();
 }

swap.c
 Global 전역
  int *bufp0 = &buf[0];
  void swap(void)

 external 확장
 Local 지역
  static int *bufp1;

 Linker knows nothing of temp
 {
int temp;
 }

정적 라이브 러리 컴파일
gcc --static main.o swap.o -o lee_test2

실행파일 사용된 메모리 맵 확인
nm lee_test2 | less

00000000006b9118 V DW.ref.__gcc_personality_v0
00000000004ab28c W _.stapsdt.base
0000000000448b60 W _Exit
00000000006b9000 d _GLOBAL_OFFSET_TABLE_
00000000006b9140 D _IO_2_1_stderr_
00000000006b9580 D _IO_2_1_stdin_
00000000006b9360 D _IO_2_1_stdout_
0000000000417560 T _IO_adjust_column
0000000000471740 T _IO_adjust_wcolumn
00000000004155c0 T _IO_cleanup
0000000000416ce0 T _IO_default_doallocate
0000000000417130 T _IO_default_finish
00000000004181e0 T _IO_default_imbue
0000000000418010 T _IO_default_pbackfail
00000000004181b0 T _IO_default_read
0000000000418190 T _IO_default_seek
0000000000417450 T _IO_default_seekoff
0000000000416c70 T _IO_default_seekpos
0000000000416b40 T _IO_default_setbuf
00000000004181d0 T _IO_default_showmanyc
00000000004181a0 T _IO_default_stat
0000000000417120 T _IO_default_sync
00000000004166e0 T _IO_default_uflow
00000000004166d0 T _IO_default_underflow
00000000004181c0 T _IO_default_write
00000000004168c0 T _IO_default_xsgetn
0000000000416740 T _IO_default_xsputn
....
....
....
all information...

정적으로 컴파일 해 실행 파일을 만들어기 때문에 모든 라이브러리 정보가 다 들어가 있디.

심볼 정의는 다음과 같이 구조체의 배열인 심볼 테이블에 컴파일러에 의해 저장된다.
링커는 각 심볼를 참조 정확히 하나의 심볼 정의와 연결 시킨다.

typedef struct{
int name; /* string table offset */
int value; /* section offset address of the symbol */
int size; /* object size in bytes */
char type:4, /* object, func, seciton, srcfile*/
binding:4; /* local of global */
char section; /* section header index */
} Elf_symbol;

링커 심볼 규칙
링커는 각 참조를 입력 재배치 가능 객체 파일의 심볼 테이블에서 정확히 하나의 심볼 정의와 연결하여 심볼 참조를 확인한다.

심볼 해석은 로컬 참조할 경우 간단하다. 그러나 다른 모듈에서 정의되고 다른 모듈로 참조 되는 전역 심볼에 대한 참조를 확인 하는 것은 까다롭다.

컴파일러가 현재 모듈에 정의되지 않은 심볼를 발견하면 다른 모듈에서 정의된 것으로 가정하고 링커 심볼 테이블 항목을 생성 한 다음 링커가 처리할 수 있도록 해당 심볼을 표시한다.

예를 들어, 반대쪽 코드 파일은 문제없이 컴파일되지만 링커에서는 foo 함수에 대한 참조를 확인할 수 없을 때 종료 한다.

void foo();
int main(void) {
foo();
return 0;
}

세 가지 유형의 링커 심볼 (global, external, locla)는 강함 또는 약한으로 표시된다.

1. 강력한 심볼: 함수 이름과 전역 초기화
2. 약한 심볼: 초기화 되지 않은 전역

p1.c
strong ----> int foo=5;
strong ----> p1(){ }

p2.c
weak ------> int foo;
strong ----> p2() { }

강력하고 약한 심볼의 개념을 염두에두고 UNIX 링커는 다중 정의 심볼를 처리 할 때 다음 규칙을 사용한다.

1. 규칙 1: 여러 개의 강력한 심볼는 허용 되지 않음.
2. 규칙 2: 강력한 심볼이 여러개와 약한 심볼이 있는 경우 문자열 심볼를 선택한다.
3. 규칙 3: 약한 심볼이 여러개인 경우 임의의 하나를 선택 한다.

메인 함수 두개 선언의 경우 링커의 해석
cat f1.c
int main(void){
return 0;
}

cat f2.c
int main(void){
return 0;
}

컴파일
gcc -c *.c

오브젝트 f1.o 덤프
objdump -t f1.o

f1.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS* 0000000000000000 f1.c
0000000000000000 l    d  .text 0000000000000000 .text
0000000000000000 l    d  .data 0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame 0000000000000000 .eh_frame
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 g     F .text 000000000000000b main

오브젝트 f2.o 덤프
objdump -t f2.o

f2.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS* 0000000000000000 f2.c
0000000000000000 l    d  .text 0000000000000000 .text
0000000000000000 l    d  .data 0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame 0000000000000000 .eh_frame
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 g     F .text 000000000000000b main

링커 병합 확인
gcc f1.o f2.o -o mysys

f2.o: In function `main':
f2.c:(.text+0x0): multiple definition of `main'
f1.o:f1.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

메인 함수가 두개 존재해, 병합 하지 못하고 있다.

동일한 전역변수 선언시 링커의 해석
cat f1.c
int x = 1234;

int main(void){
return 0;
}

cat f2.c
int x = 2345;

int func(){
return 0;
}

gcc -c *.c
gcc f1.o f2.o -o lee_test
f2.o:(.data+0x0): multiple definition of `x'
f1.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

전역 변수 x 가 두번 선언되어져 링커가 병합하지 못하고 있다.

링커의 해석 가능 하도록 수정한다.
cat f1.c
#include <stdio.h>
int func();
int x = 1;
int y = 2;
int main()
{
func();
printf("x = %d\n", x);
printf("y = %d\n", y);
return 0;
}

cat f2.c
#include <stdio.h>
float x;
int func()
{
x = 5.4;
return 0;
}

컴파일.
gcc -c *.c
gcc -o
./a.out

댓글 없음:

댓글 쓰기