본문 바로가기

awesome-c Beginner 번역/Building C Projects

<비공식 번역>awesome-c Beginner번역: 13. Dynamic linking

1. Configuration

2. Standard directory dectection

3. Source file dependency calculation

4. Header file location

5. Header precompileation

6. Preprocessing

7. Compliation and assembly

8. Object file dependency calculation

9. Linking

10. Installation

11. Resource linking

12. Package generation

13. Dynamic linking

<주의!!>

nethack4.org의 'Building C Projects'의 공식적인 번역이 아니며 수를 받은 것 역시 아닙니다!!




13 : Dynamic linking ( 13 : 동적 연결 )


 실행시 일어나는 무언가로, 이는 빌드 시스템의 디자인과 무관하지만, 나는 완성에 대해 언급하고자 합니다. 지금까지 우리는 암묵적으로 '정적빌드'라 불리는 빌드는 런타임시 실행되는 모든 코드를 실행파일의 일부분으로 보았습니다. 그렇지만, 실제로 이는 일반적인 경우가 아닙니다; 실행파일은 다수의 실행파일들이 서로 공유하는 코드가 포함된 공유라이브러리(Linux의 .so, Windows의 .dll, Mac OS X의 .dylib)에 따라 달라집니다.


 개념적으로 동적연결은 보통 정적연결과 상당히 유사합니다; 동적링커는 공유라이브러리의 주소를 선택하고 이를 호출하는 실행파일의 주소공간에 매핑합니다. 이는 실행파일은 오브젝트 파일이 했던 것과 같은 재배치의 일종을 포함해야 함을 의미합니다. 여기 제 64비트 리눅스 시스템의 다섯가지의 재배치 케이스 입니다(readelf -r 옵션으로 출력하였습니다.).


Relocation section '.rela.dyn' at offset 0x3a8 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000600ff8  000200000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000601040  000400000005 R_X86_64_COPY     0000000000601040 stdout + 0

Relocation section '.rela.plt' at offset 0x3d8 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
000000601028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 fwrite + 0 


 우리는 이제 위에서 libc에 대해 연결되길 기다리는 익숙한 sdtoutfwrite를 알아볼 수 있습니다. 우리는 재배치를 위한 오프셋이 오브젝트 파일에 존재하는 것보다 더 높음을 볼 수 있습니다; 왜냐면 링커는 이미 실행 자체에 모든것을 위해 주소를 확인했기 때문입니다. 그리고 실행시에 어디에 재배치해야할지 알고 있습니다. 동적연결은 정적연결보다 어렵습니다. 왜냐면 OS는 실행되는 모든 프로세스 사이에 비트단위의 공유라이브러리를 원하기 때문입니다(그래서 물리적 메모리에서 단 한번의 복사로 유지될 수 있는); 64bit 리눅스에서, 해결책은 코드를 직접적으로 두는 것보다 다양한 동적 링커의 업데이트 테이블에 재배치하는 것입니다. 그리고 테이블을 통해 이들을 직접적으로 호출하는 것이죠. (이는 함수보단 변수이고 stdout에서는 동작하지 않을 것입니다; 링커는 이 문제를 분리된 테이블에 둠으로써 해결합니다.) 이는 또한 공유라이브러리는 메모리에서 이동될 수 있는 방법으로 작성되었고 여전히 잘 동작함을 의미합니다, 그러나 x86_64에서 프로세서는 프로그램 카운터를 기준으로 메모리 참조를 허용함으로써 이를 꽤 쉽게 만듭니다. (그 사이, 32비트 x86에서 이는 완벽한 악몽입니다; 그러나 이러한 경우는 컴파일러 대신 말할 수 있는 hoops로 분기합니다. 이 경우에 동적 링커는 메모리에 있는 공유라이브러리의 복사본을 모두 정규 링커가 하는 방법과 동일하게 재배치하여 패치합니다. 리눅스의 동적 링커는 실제로는 이같이 동작합니다; 그러나, x86_64에서는 이는 거부됩니다. 정말로 컴파일러의 작업이 수행해야하는 것임을 설명하면 시스템의 라이브러리 위치독립성을 확인합니다.


 부분적으로 여기에서 동적 링킹의 세세한 부분을 다루지는 않을 것입니다. 왜냐면 부분적으로는 빌드 시스템을 이해하는데에 연관이 없고, 그것들을 정확히 모르기 때문입니다. 그러나 해결해야할 이슈가 하나 있습니다 : 실제로 실행중에 끝나지 않는 코드인 공유 라이브러리에 대한 링크는 어떤 의미일까요? 그 답은 링커에게 정적 라이브러리로 보이는 파인 import library의 개념에 있습니다. 그러나 실제로는 코드 대신에 결과물(실행파일)에 재배치하는 것 뿐입니다. 리눅스에서, 각 공유 라이브러리는 자신을 위해서 라이브러리를 import할 수 있습니다. 이는 공유 라이브러리의 배포를 매우 쉽게해줍니다. 그러나 빌드 시스템은 혼란스러울 수 있습니다 (특히 실행중에 필요한 심볼이 있는 서로 다른 공유 라이브러리를 고려해야 하는 경우; 이 경우가 기술적으로 순환 종속성을 가지는 것은 아니지만, 여러분은 어떻게든 종속성을 없애기 위해 처음 import한 방법과 다른 방식으로 라이브러리를 import할 필요가 있습니다.). 윈도우에서, 라이브러리 import는 공유 라이브러리에서 얻은 symbols의 description을 dlltool로 생성된 파일로 분리하려는 경향이 있습니다; aimake는 실제로 복사하지 않고도 공유 라이브러리 description을 오브젝트 파일에게 줍니다 (따라서 순환 종속성은 깨지게 됩니다.).


 또한 프로그래머를 위한 중요한 포인트도 있습니다. 윈도우에서 공유 라이브러리로 부터 import는 동작을 위해서 특별한 코드를 소스에 생성할 필요가 있습니다. 마찬가지로 export도 공유 라이브러리 내부소스에 표기가 필요합니다. 리눅스에서, export는 소스에 따로 표기가 필요하지 않지만, 그들도 수행해야할 것이 있습니다. 명령줄 옵션에 의해서 빌드 시스템을 위해서 공유 라이브러리의 API의 non-export 심볼들을 숨기는 것입니다. 곧 이는 namespace 오염을 감소시킵니다.


 적절한 표시를 다루는 것은 보통 전처리기에 의해 수행됩니다. 일반적으로 여러분은 헤더파일에 공유 라이브러리의 API를 아래와 같이 선언하면 됩니다.


#ifdef IN_SHARED_LIBRARY_INTERNALS
# define IMPORT_EXPORT(x) AIMAKE_EXPORT(x)
#else
# define IMPORT_EXPORT(x) AIMAKE_IMPORT(x)
#endif

int IMPORT_EXPORT(function1) (void);
int IMPORT_EXPORT(function2) (int, int);


 그 다음, 공유 라이브를 사용하는 사람은 이 헤더를 참조(include)하기만 하면 됩니다; 공유 라이브러리는 자체적으로 #define IN_SHARED_LIBRARY_INTERNALS을 수행하고 이 헤더를 참조하고, 따라서 "export"정의 보다는 "import"정의를 얻게 됩니다. AIMAKE_EXPORTAIMAKE_IMPORT 매크로는 공유 라이브러리로 부터 import와 export가 필요할 한 시스템별 주석이 무엇이든 확장하는 aimake에 의해 정의됩니다.(정확한 구문은 gcc는 현재와 같은 포인터 리턴하는 함수와 상호작용하는 방식을 선호하지 않기 때문에 변경될 수 있습니다; 저는 현재 typedef으로 작업하고 있지만, 정말로 더 나은 방법이 필요합니다.)


지금까지 제가 말할 수 있는 건, 표기의 종류는 공유 라이브러리를 만듦에 필수적이고, 공유 라이브러리의 API는 어떤 방법으로든 지정되어야 합니다. 그러나 aimake는 주석을 파악하고 자동적으로 공유 라이브러리들이 아닌 실행파일을 생성합니다. 그래서 추가작업을 최소화 합니다.(약간은 개선점이 필요합니다. 현재 이 것은 제가 Mac OS에 대해 충분히  알지 못하는 관계로 Mac OS에서 동작하지 않습니다. 그리고 정말로 전체 매커니즘을 무시하고 대신에 정적 라이브러리를 생성할 어떤 방법이 있어야 합니다.)



출처1 : https://github.com/aleksandar-todorovic/awesome-c

출처2 : http://nethack4.org/blog/building-c.html