본문 바로가기

Computer Science/Computer Systems

[Lecture 6] Linking and Loading - Part III

#Software Engineering Aspects

#Static Libraries

정적 라이브러리(static library) 또는 정적 링크 라이브러리(static-linked library)는 컴파일 시 호출자에서 해결되고 컴파일러, 링커 또는 바인더에 의해 대상 응용 프로그램으로 복사 되어 객체 파일과 독립 실행 파일을 생성하는 일련의 루틴, 외부 함수 및 변수이다.

  • 일반적으로 사용되는 함수를 개체 .o 모듈로 미리 컴파일하고, 그 .o 모듈을 .static 라이브러리라고 불리는 archive로 패키징한다.
    • .a archive는 간단한 순차 archive를 생성하는 ar(1) 명령에 의해 유지 관리된다.
    • 중요: .o 모듈은 archive에 포함되거나 전혀 포함되지 않기 때문에 C 라이브러리(libc) 또는 Math 라이브러리(libm)와 같은 일반적인 라이브러리는 수천 개의 .o 파일을 포함한다.
  • 링커는 링크 프로세스에 포함할 .o 모듈을 어떻게 선택할까?
  • 라이브러리 간의 dependency, 즉 라이브러리 B의 모듈 2.o에 정의된 라이브러리 A에 외부 참조 R이 있는 경우, 라이브러리 간의 종속성은 어떻게 처리될까?

#A closer look at the linking process (sans libraries)

  • 링커는 command-line에 지정된 순서대로 .o 모듈을 처리한다
  • 이미 처리된 일부 모듈에 의해 정의된 전역 기호 집합 D를 유지한다.
  • 이미 처리된 일부 모듈에서reference했지만 아직 정의가 나타나지 않은 전역 기호 집합 U를 유지한다.
  • 처리된 각 .o 모듈에 대해, 이미 D에 있지 않은 경우 U에 새로운 외부 reference를 추가한다.
  • D에 추가하고 U에서 이 .o 모듈에 의해 정의된 전역 기호를 제거한다(D에 이미 있는 경우 "multiply defined" 오류 보고).
  • 마지막에 U에 기호가 남아 있는 경우 "undefined symbol" 오류를 보고함.
  • 이 설명은 전역 기호에만 적용됨. 로컬 기호 참조는 항상 해당 로컬 기호 정의에서 확인된다

#Extending the linking process to static libraries

  • 규칙: 라이브러리를 처리할 때 링커는 현재 U 집합에 있는 기호를 정의하는 경우에만 이 라이브러리의 .o 모듈을 포함한다
    • 위에서 "현재"는 command line에 지정된 처리 순서에서 위치를 나타낸다.
    • 라이브러리의 다른 .o 모듈에서 참조하는 기호를 정의하는 동일한 라이브러리의 .o 파일이 포함된다.
  • 장점:
    • 필요한 .o 파일만 포함
    • 먼저 나열될 라이브러리의 정의를 지정하여 라이브러리 기호를 재정의할 수 있다
  • 단점:
    • 링크 동작은 command-line에 .o 파일과 라이브러리가 나열되는 정확한 순서에 따라 달라진다
    • 라이브러리를 특정 순서(클래식 -lXm -lXt -lX11)로 나열하거나 상호 dependency가 있는 경우 여러 번 나열하거나 특수 링커 그룹화 옵션(--start-group/--end-group)을 사용해야 할 수 있다

    • 오류가 발생하기 쉽고 혼동하기 쉽다
      • 링커 맵은 링커가 기호를 확인하는 방법을 추적하는 데 좋다.

#Drawbacks of Static Libraries

  • 많은 프로그램에서 기능을 사용하는 경우 코드 복제
    • 예: c 라이브러리
    • 파일 시스템에 대용량 실행 파일을 저장하기 위한 비용
    • 이러한 실행 파일을 로드하는 각 프로세스에 대해 더 많은 메모리가 필요한 비용. 동일한 라이브러리를 사용하더라도 프로세스 간에 이 메모리를 공유할 수 없음
  • 모든 업데이트를 수행하려면 해당 코드를 사용하는 각 실행 파일의 재컴파일(및 재배포)이 필요하다.
    • 시스템 라이브러리에 업데이트를 푸시하는 데 비용이 많이 든다
  • 그 반대는 정적으로 연결된 바이너리가 모든 dependency을 포함하고 있으며, 기본 OS가 API/ABI라는 시스템을 지원하는 한 작동한다(Linux는 여전히 1990년대에 구축된 바이너리를 실행함).

#Shared Libraries

  • 공유 개체(.so) 또는 Windows에서 dynamic-link libraries(DLL)로 불림
  • shared library는 런타임에 프로세스의 가상 주소 공간에 로드된다
  • 이는 동적 링커/클라이언트(LD linux.so/ld-linux-x86-64.so(Linux의 경우)와 빌드 도구의 협력을 통해 구현된다.
    • 실행 파일에 로드 시 해결될 외부 참조(U)가 여전히 포함되어 있다
    • recursive: 동적으로 연결된 라이브러리가 dependency를 가질 수 있다
  • 또한 플러그 인 기반 시스템이나 응용 프로그램에서 처럼 런타임에 공유 개체를 로드 하려는 프로그램을 위해 dlopen을 통해 직접 액세스할 수 있다
    • flexible API
  • 이러한 공유 객체의 메모리는 서로 다른 가상 주소에 위치하더라도 여러 프로세스에 의해 공유될 수 있다(메모리는 읽기 전용이어야 하며 콘텐츠가 매핑된 위치에 종속되지 않아야 함).
  • 프로그램과 라이브러리가 정적으로 연결된 것처럼 동일한 의미론(semantics)을 유지한다

#Implementation of Shared Libraries

  • Position-Independent code(라이브러리 내 참조를 처리함)
    • 64비트 x86: PC 상대 주소 지정 모드 / PC-relative addressing mode ($rip)
    • 32비트 x86: $eip의 값를 얻기 위해 "PC 구체화" 트릭 필요
  • Indirection (라이브러리 간 참조 또는 실행 파일에서 라이브러리로의 참조에 필요)
  • 라이브러리가 변수 x에 대해 전역 함수를 정의하는 경우, f 및 &x 주소는 라이브러리가 로드될 때까지 알 수 없다
    • Solution: 간접 함수 호출(PLT(Procedure Linking Table)의 entries을 통해)
    • trampolines을 통한 주문형 로딩: 첫 번째 액세스 트리거가 동적 링커로 점프한다
    • 후속 점프는 로드된 기능으로 직행한다
  • 일반적으로 공유 라이브러리는 런타임에 한계 비용(marginal cost)을 발생시킨다.