GCC -fPIC 옵션


답변:


526

위치 독립적 인 코드는 생성 된 기계 코드가 작동하기 위해 특정 주소에있는 것에 의존하지 않음을 의미합니다.

예를 들어 점프는 절대가 아닌 상대적인 것으로 생성됩니다.

의사 조립 :

PIC : 코드가 100 또는 1000인지에 관계없이 작동합니다.

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP

비 PIC : 코드가 주소 100 인 경우에만 작동합니다.

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP

편집 : 의견에 대한 답변.

코드가 -fPIC으로 컴파일 된 경우 라이브러리에 포함하기에 적합합니다. 라이브러리는 메모리의 원하는 위치에서 다른 주소로 재배치 할 수 있어야합니다. 라이브러리가 선호하는 주소에 이미로드 된 라이브러리가있을 수 있습니다.


36
이 예는 명확하지만 사용자는 옵션없이 공유 라이브러리 (.so) 파일을 만들면 어떤 차이가 있습니까? -fPIC이 없으면 내 라이브러리가 유효하지 않은 경우가 있습니까?
Narek

16
예, PIC가 아닌 공유 라이브러리를 작성하는 것은 오류 일 수 있습니다.
John Zwinck

92
좀 더 구체적으로 말하면, 공유 라이브러리는 프로세스간에 공유되어야하지만 항상 같은 주소에 라이브러리를로드 할 수있는 것은 아닙니다. 코드가 위치 독립적이지 않은 경우 각 프로세스에는 고유 한 사본이 필요합니다.
Simon Richter

19
@Narek : 하나의 프로세스가 동일한 가상 주소에서 둘 이상의 공유 라이브러리를로드하려는 경우 오류가 발생합니다. 라이브러리는 다른 라이브러리를로드 할 수있는 것을 예측할 수 없으므로 기존의 공유 라이브러리 개념에서는이 문제를 피할 수 없습니다. 가상 주소 공간은 여기서 도움이되지 않습니다.
Philipp

6
-fPIC프로세스에 하나의 기본 프로그램 만 존재하므로 프로그램 또는 정적 라이브러리를 컴파일 할 때 생략 할 수 있으므로 런타임 재배치가 필요하지 않습니다. 일부 시스템에서는 보안 강화를 위해 프로그램이 여전히 위치 독립적 입니다.
Simon Richter

61

나는 이미 말한 것을 더 간단한 방법으로 설명하려고 노력할 것입니다.

공유 라이브러리가로드 될 때마다 로더 (실행중인 모든 프로그램을로드하는 OS의 코드)는 객체가로드 된 위치에 따라 코드의 일부 주소를 변경합니다.

위의 예에서 비 PIC 코드의 "111"은 처음로드 될 때 로더에 의해 작성됩니다.

공유 객체가 아닌 경우 컴파일러가 해당 코드에서 일부 최적화를 수행 할 수 있기 때문에이를 원할 수 있습니다.

공유 객체의 경우 다른 프로세스가 해당 코드에 "링크"하려는 경우 동일한 가상 주소로 해당 프로세스를 읽어야합니다. 그렇지 않으면 "111"이 의미가 없습니다. 그러나 가상 공간은 이미 두 번째 프로세스에서 사용 중일 수 있습니다.


Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.-fpic으로 컴파일하면 -fpic이 존재하는 이유, 즉 성능상의 이유로 또는 재배치 할 수없는 로더가 있거나 다른 위치에 여러 사본이 필요하거나 더 많은 이유로 인해 이것이 올바르지 않다고 생각합니다.
robsn

항상 -fpic을 사용하지 않는 이유는 무엇입니까?
Jay

1
@Jay-각 함수 호출에 대해 하나 이상의 계산 (함수 주소)이 필요하기 때문입니다. 따라서 성능 측면에서는 필요하지 않으면 사용하지 않는 것이 좋습니다.
Roee Gavirel

45

공유 라이브러리에 내장 된 코드는 일반적으로 위치 독립적 인 코드 여야하므로 공유 라이브러리를 메모리의 모든 주소에 쉽게로드 할 수 있습니다. 이 -fPIC옵션은 GCC가 그러한 코드를 생성하도록합니다.


-fPIC플래그를 설정 하지 않고 공유 라이브러리를 메모리의 어떤 주소로로드하지 않습니까? 프로그램에 연결되어 있지 않습니까? 프로그램이 실행 중이면 운영 체제가 프로그램을 메모리에 업로드합니다. 뭔가 빠졌습니까?
Tony Tannous

1
는 IS -fPIC플래그는로드 할 수있는이 LIB 보장하기 위해 사용되는 가상 주소 를 연결하는 과정을? 이중 댓글에 대해 죄송합니다. 5 분이 지난 후에는 이전 댓글을 수정할 수 없습니다.
Tony Tannous

1
공유 라이브러리 구축 (생성 libwotnot.so)과 연결 ( -lwotnot)을 구분합니다. 연결하는 동안에 대해 번거로울 필요가 없습니다 -fPIC. 예전에는 공유 라이브러리를 빌드 할 때 -fPIC모든 오브젝트 파일이 공유 라이브러리에 빌드되는 데 사용 되었는지 확인해야 했습니다. 요즘 컴파일러는 기본적으로 PIC 코드로 빌드하기 때문에 규칙이 변경되었을 수 있습니다. 따라서 20 년 전에 중요했고 7 년 전에 중요했던 것은 요즘 덜 중요합니다. o / s 커널 외부의 주소는 '항상'가상 주소입니다.
Jonathan Leffler

그래서 이전에을 추가해야했습니다 -fPIC. 이 플래그를 전달하지 않으면 .so를 빌드 할 때 생성 된 코드를 사용중인 특정 가상 주소로로드해야합니까?
Tony Tannous

1
예. PIC 플래그를 사용하지 않으면 코드를 안정적으로 재배치 할 수 없었기 때문입니다. 코드가 PIC가 아닌 경우 ASLR (address space layout randomization)과 같은 것은 불가능합니다.
Jonathan Leffler

21

더 추가 중 ...

모든 프로세스는 동일한 가상 주소 공간을 갖습니다 (리눅스 OS에서 플래그를 사용하여 가상 주소의 임의 추출이 중지 된 경우 )

따라서 공유 링크가없는 하나의 exe 인 경우 (가설 시나리오), 우리는 항상 동일한 가상 주소를 동일한 asm 명령에 해를 끼치 지 않을 수 있습니다.

그러나 공유 객체를 exe에 연결하려면 공유 객체가 링크 된 순서에 따라 공유 객체에 할당 된 시작 주소를 확신하지 못합니다. 연결되는 프로세스에 따라 다른 가상 주소.

따라서 하나의 프로세스는 자체 가상 공간에서 0x45678910으로 시작 주소를 지정할 수 있으며 동시에 다른 프로세스는 0x12131415의 시작 주소를 제공 할 수 있으며 상대 주소 지정을 사용하지 않으면 전혀 작동하지 않습니다.

따라서 항상 상대 주소 지정 모드와 fpic 옵션을 사용해야합니다.


1
가상 가산기 설명에 감사드립니다.
Hot.PxL

2
정적 라이브러리에서 이것이 어떻게 문제가되지 않는지 누구나 설명 할 수 있습니까? 정적 라이브러리에서 -fPIC를 사용할 필요가없는 이유는 무엇입니까? 링크 시간은 컴파일 타임 (또는 실제로 직후)에 수행되지만 위치 종속 코드가있는 정적 라이브러리가 2 개라면 어떻게 링크됩니까?
Michael P

3
@MichaelP 오브젝트 파일에는 위치 종속 레이블이있는 테이블이 있으며 특정 obj 파일이 링크되면 모든 레이블이 그에 따라 업데이트됩니다. 공유 라이브러리에서는 수행 할 수 없습니다.
Slava

16

동적 라이브러리의 함수에 대한 링크는 라이브러리가로드되거나 런타임에 해결됩니다. 따라서 프로그램 실행시 실행 파일과 동적 라이브러리가 모두 메모리에로드됩니다. 고정 주소가 동일한 주소를 요구하는 다른 동적 라이브러리와 충돌 할 수 있으므로 동적 라이브러리가로드되는 메모리 주소를 미리 결정할 수 없습니다.


이 문제를 처리하기 위해 일반적으로 사용되는 두 가지 방법이 있습니다.

1. 재배치. 필요한 경우 실제로드 주소에 맞게 코드의 모든 포인터와 주소가 수정됩니다. 재배치는 링커와 로더에 의해 수행됩니다.

2. 위치 독립적 코드. 코드의 모든 주소는 현재 위치를 기준으로합니다. 유닉스 계열 시스템의 공유 객체는 기본적으로 위치 독립적 인 코드를 사용합니다. 특히 32 비트 모드에서 프로그램을 장시간 실행하는 경우 재배치보다 효율성이 떨어집니다.


" 위치 독립적 코드 " 라는 이름은 실제로 다음을 의미합니다.

  • 코드 섹션에는 재배치가 필요한 절대 주소가 아니라 자체 상대 주소 만 있습니다. 따라서 코드 섹션을 임의의 메모리 주소에로드하고 여러 프로세스간에 공유 할 수 있습니다.

  • 데이터 섹션은 종종 쓰기 가능한 데이터를 포함하므로 여러 프로세스간에 공유되지 않습니다. 따라서 데이터 섹션에는 재배치가 필요한 포인터 나 주소가 포함될 수 있습니다.

  • Linux에서 모든 공용 함수 및 공용 데이터를 대체 할 수 있습니다. 주 실행 파일의 함수가 공유 객체의 함수와 이름이 같은 경우 main에서 호출 할 때뿐만 아니라 공유 객체에서 호출 할 때 main의 버전이 우선합니다. 마찬가지로 main의 전역 변수가 공유 객체의 전역 변수와 이름이 같으면 공유 객체에서 액세스 할 때도 main의 인스턴스가 사용됩니다.


이 소위 기호 삽입은 정적 라이브러리의 동작을 모방하기위한 것입니다.

공유 객체에는이 "재정의"기능을 구현하기 위해 프로 시저 연결 테이블 (PLT)이라는 함수에 대한 포인터 테이블과 전역 오프셋 테이블 (GOT)이라는 변수에 대한 포인터 테이블이 있습니다. 함수 및 공용 변수에 대한 모든 액세스는이 테이블을 통과합니다.

ps 동적 연결을 피할 수없는 경우 위치 독립적 인 코드의 시간 소모적 인 기능을 피할 수있는 다양한 방법이 있습니다.

이 기사에서 더 많은 내용을 읽을 수 있습니다 : http://www.agner.org/optimize/optimizing_cpp.pdf


9

이미 게시 된 답변에 약간의 추가 사항 : 위치 독립적으로 컴파일되지 않은 객체 파일은 재배치 가능합니다. 재배치 테이블 항목이 포함되어 있습니다.

이러한 항목을 사용하면 로더 (프로그램을 메모리에로드하는 코드 비트)가 가상 주소 공간의 실제로드 주소에 맞게 절대 주소를 다시 쓸 수 있습니다.

운영 체제는 동일한 공유 객체 라이브러리에 연결된 모든 프로그램과 메모리에로드 된 "공유 객체 라이브러리"의 단일 복사본을 공유하려고합니다.

코드 주소 공간 (데이터 공간의 섹션과 달리)이 인접 할 필요는 없으며 특정 라이브러리에 링크 된 대부분의 프로그램은 상당히 고정 된 라이브러리 종속성 트리를 가지므로 대부분의 경우 성공합니다. 드물지만 불일치가있는 경우에는 메모리에 공유 객체 라이브러리의 사본이 두 개 이상 필요할 수 있습니다.

분명히, 프로그램 및 / 또는 프로그램 인스턴스 사이에서 라이브러리의로드 주소를 무작위 화하려고 시도하면 (이용 가능한 패턴을 만들 가능성을 줄이기 위해) 시스템이이 기능을 활성화 한 경우는 드물지 않습니다. 모든 공유 객체 라이브러리를 컴파일하려는 모든 시도가 위치 독립적이되도록해야합니다.

기본 프로그램 본문에서 이러한 라이브러리에 대한 호출도 재배치 가능하게되므로 공유 라이브러리를 복사해야 할 가능성이 훨씬 줄어 듭니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.