'2011/07/05'에 해당되는 글 2건
- 2011.07.05 [Hack #22] GCC 확장기능 (빌트인, 애트리뷰트, 레이블)
- 2011.07.05 [Hack #20] 공유 라이브러리에 PIC를 사용하는 이유 1
[ 빌트인 함수 ] | |
♧ gcc에는 표준 라이브러리에 있을 듯한 몇몇 함수가 빌트인 함수로 마련되어 있음. 최적화 방법에 따라 소스에 쓰인 것과 다른 코드를 생성할 경우 있다.
▷ 예를 들면 printf(3) 함수의 경우 다음과 같이 문자열을 출력하는 코드에서는 실행할 때 pirntf(3)의 형식 문자열을 해석할 수 없으므로 다음과 같이 puts(3)를 호출하는 코드를 생성.
▷ 기본적으로는 같은 동작을 하고 더 빠르게 실행될 듯한 코드가 생성되어 문제는 없으나, LD_PRELOAD 등으로 오버라이드(override)해서 동작을 변경하고자 할 경우에는 주의 할 필요가 있다. 최적화를 통해 어떤 코드가 생성되는지 gcc/builtins.c에 프로그램 되어 있다.
|
[ 애트리뷰트 (__attribute__) ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
♧ 함수에 애트리뷰트를 덧붙여 선언하면 함수에 특별한 의미를 부여하거나 함수호출을 최적화 할 수 있다.
▶ 애트리뷰트 선언 2가지 방법
▶ 함수에 관한 애트리뷰트에는 다음과 같은 것들이 있다.
|
[ 레이블(label) 참조 ] | |||
♧ C에서는 그다지 사용되지 않지만 goto로 점프할 위치를 지정할 때 레이블을 사용.
▷ GCC에서 레이블은 &&로 참조하고 void *형 함수에 대입할 수 있다.
이와 같이 레이블을 변수에 대입할 수 있기 때문에 변수 값에 따라 점프할 위치를 변경하도록 하는 코드는 레이블로의 참조를 요소로 갖는 배열로도 구현할 수 있다. 레이블을 &&로 참조한 값은 void *에 대입할 수 있는 포인터형이므로 뺄셈으로 오프셋을 얻을 수 있다. 따라서 다음과 같은 코드를 작성할 수 있다.
|
[ 정리 ]
GCC는 C99표준에 준거하도록 노력하는 한편, 소스코드를 기술하기 쉽도록 하기 위한
확장 기능을 제공하고 있다. GCC의 확장기능임을 이해한 후 이 기능을 사용하면 편리한
경우가 있다. 예를 들면 리눅스 커널에는 GCC확장 기능을 이용한 코드를 볼 수 있다.
이러한 코드를 파악하려 할 경우에는 GCC 확장기능에 대한 이해가 필수적이다.
'컴퓨터 서적 정리 > Binary Hacks' 카테고리의 다른 글
[Hack #20] 공유 라이브러리에 PIC를 사용하는 이유 (1) | 2011.07.05 |
---|---|
[Hack #19] 링크할 때 심볼 충돌 방지하기 (0) | 2011.07.04 |
[Hack #18] C와 C++ 프로그램 링크 방법 (0) | 2011.07.04 |
[Hack #17] ar - 정적 라이브러리 다루기 (0) | 2011.07.04 |
[Hack #16] strip - 오브젝트 파일에서 심볼 삭제 (0) | 2011.07.04 |
♧ 통상 GNU/리눅스의 공유 라이브러리를 만들 때는
각각의 .C 파일을 PIC(Position Independent Code)가 되도록 컴파일 한다.
그러나 실은 PIC로 컴파일 하지 안아도 공유 라이브러리는 만들 수 있다.
그러면 굳이 PIC로 컴파일 하는 이유가 있는 것일까?
▶ fpic.c 작성
#include <stdio.h> void func() { printf(""); printf(""); printf(""); } |
☞ PIC로 컴파일하기 위해 gcc -fpic 또는 fPIC 옵션을 지정한다.
-fpic :: 좀더 고속으로 코드를 생성할 가능성이 있지만,
CPU에 따라 -fpic로 생성할 수 있는 GOT(Glocal Offset Table)의 크기에 제한이 있다.
-fPIC :: CPU에 관계없이 사용할 수 있다. 여기서는 -fPIC를 사용한다.
(※ x86에서는 -fpic와 -fPIC가 동일)
% gcc -o fpic-no-pic.s -S fpic.c % gcc -fPIC -o fpic-pic.s -S fpic.c |
위와 같이 생성된 어셈블리어의 소스코드를 보면 PIC 버전은 printf를
PLT(Procedure Linkage Table)를 경유해서 호출하는 것을 알 수 있다.
(※ 우분투 7.04에서 테스트)
다음에는 공유 라이브러리를 만든다.
% gcc -shared -o fpic-no-pic.so fpic.c % gcc -shared -fPIC -o fpic-pic.so fpic.c |
▷ 위 공유 라이브러리의 동적 섹션(dynamic section)을 readelf 명령으로 보면,
비 PIC공유 라이브러리에서는 TEXTREL 이라는 엔트리가 있고 (텍스트 내의 재배치 필요),
RELCOUNT(재배치 수)가 5로, PIC 공유 라이브러리보다 3만큼 크다.
그 이유는 printf()를 3회 호출하기 때문이다.
▷ PIC 공유 라이브러리에서의 RELCOUNT가 0이 아닌 이유는 gcc가 기본적으로 사용하는
시작 파일에 포함된 코드 때문이다. gcc에 -nostartfiles 옵션을 지정하면 이 값은 0이 된다.
[ PIC와 비 PIC 공유 라이브러리의 성능 비교 ] | ||||||||||||
♧ 위 예에서는 비 PIC 버전은 실행 시에(동적 링크 시에) 5개의 주소가 재배치 되어야 한다고 했다. 그러면 재배치 수가 매우 커지면 어떻게 될 것인가?
공유 라이브버리를 PIC버전과 비 PIC버전printf()를 천만 번 호출 ( 셸 스크립트로 실행 비교) ▶ 다음 셸 스크립트를 실행하면 printf()를 천만 번 호출하는 공유 라이브러리를 비 PIC버전과 PIC 버전으로 만들고, 각각을 링크한 실행 파일 fpic-no-pic와 fpic-pic를 생성.
비 PIC 버전이 첫 회 2.15초, 두 번째 이후에는 약 0.55초가 걸리고,
PIC 버전은 최초 0.02초, 두 번째 이후에는 0.00초가 되었다.
☞ main()의 내용은 없으므로 비 PIC 버전은 동적 링크할 때 재배치에 2.15 ~ 0.55초를 필요로 함을 알 수 있다. 실행 환경은 Xeon 2.8GHz + Debian GNU/리눅스 sarge + GCC 3.3.5이다.
▷ 비 PIC 버전의 단점은 실행할 때 재배치에 시간이 걸린다는 점만이 아니다. 재배치가 필요한 부분의 코드를 재작성 하기 위해 『텍스트 섹션 내의 재배치가 필요한 페이지를 로드 → 재작성 → copy on write 발생 → 다른 프로세스와 텍스트를 공유할 수 없음』 이라는 사태가 발생. 즉, 여기서는 텍스트(프로그램 코드)를 다른 프로세스와 공유할 수 있는 『공유』라이브러리의 주요한 장점이 사라지고 만다.
한편, 비 PIC 버전의 fpic-no-pic.so 와 PIC 버전의 fpic-pic.so 파일 크기를 비교하면, 전자는 268MB, 후자는 134MB로 크게 차이가 난다. readelf -S로 섹션 데이터를 보면 다음과 같은 차이가 있다.
비 PIC 버전은 코드(.text) 크기는 PIC 버전보다 작지만, 재배치에 필요한 정보(.rel.dyn)가 상당한 용량을 차지.
[ 정리 ] 여기서는 공유 라이브러리를 작성할 때 PIC로 컴파일 해야 하는 필요성에 대해 알아보았다. 비 PIC 공유 라이브러리를 작성할 수는 있지만 실행할 때 재배치에 시간이 소요되고 다른 프로세스와 코드(.text)를 공유할 수 없는 커다란 단점이 있다. 따라서 공유 라이브러리를 작성할 때는 .c 파일을 PIC로 컴파일 하도록 한다. |
'컴퓨터 서적 정리 > Binary Hacks' 카테고리의 다른 글
[Hack #22] GCC 확장기능 (빌트인, 애트리뷰트, 레이블) (0) | 2011.07.05 |
---|---|
[Hack #19] 링크할 때 심볼 충돌 방지하기 (0) | 2011.07.04 |
[Hack #18] C와 C++ 프로그램 링크 방법 (0) | 2011.07.04 |
[Hack #17] ar - 정적 라이브러리 다루기 (0) | 2011.07.04 |
[Hack #16] strip - 오브젝트 파일에서 심볼 삭제 (0) | 2011.07.04 |