[OS]/Embedded

[펌] gcc 이야기(6)

하늘을닮은호수M 2005. 9. 6. 22:20
반응형

gcc 이야기(6)

글쓴이 : holelee (2002년 06월 29일 오후 07:47)

[ 임베디드강좌/이규명 ] @ KELP

=== 시작하기에 앞서
gcc라는 컴파일러를 이용하여 C 언어 프로그램을 컴파일 하기 위해서 알아야 할 기본적인 옵션 및 발생할 수 있는 에러에 대해 초보자를 대상으로 작성된 글입니다. 고급 사용자라면 읽으실 필요가 없을 것으로 생각됩니다. Architecture dependent한 부분은 가능한 배제하였습니다. 단 gcc의 사용은 Linux를 비롯한 Unix 계열의 OS에서 사용된다는 가정을 하였습니다. 또한 이 글에 대한 모든 내용은 본인이 사용하고 있는 alzza linux 6.1에서 gcc-2.91.66을 바탕으로 하고 있습니다. gcc에 대하여 좀더 많은 것을 알고 싶으신 분은 gcc manpage와 gcc manual을 참조하시기 바랍니다.
이 글에 나오는 모든 내용이 정확하다고 할 수는 없으며, 그 글에 나오는 내용을 따라 gcc를 사용하는데 있어서 문제가 발생할 경우, 본인은 책임을 지지 않습니다. 이 글에 대한 저작권은 본인(holelee)에게 있습니다. 글에 대해 잘못된 점이나 지적할 사항이 있으신 분은 저 위의 “holelee”를 클릭하여 메일을 보내 주시기 바랍니다.


=== 시작 및 복습
gcc 이야기(5)에서 이미 linking 과정에서 하는 일의 본질과 library에 대해서 알아보았습니다. 그리고 흔히 발생하는 에러 메시지 두 가지가 발생하는 이유와 대처 방법에 대해서 알아보고 library와 관련된 옵션 몇가지를 알아보았습니다. 하지만 아직도 linking 과정 전부를 알기 위해서는 좀더 검토해야 할 것들 있습니다. 그것들에 대해서 차근 차근 알아보도록 하겠습니다.

== entry 이야기
application을 작성하고 compile, linking 과정이 지나면 실행 파일이 만들어집니다. 그리고 그 실행 파일이 수행될 때는 메모리로 load되어 수행이 시작된다는 사실을 알고 있습니다. 여기서 한가지 의문이 생기는데, “과연 코드의 어떤 부분에서 수행이 시작되는가?”입니다. 답이 너무 뻔한가요? main함수부터 수행된다고 답하시겠죠? 다소 충격적이겠지만 “땡”입니다. main함수부터 수행되지 않고 그전에 수행되는 코드가 존재합니다. 그 먼저 수행되는 코드에서 하는 일은 여러 가지가 있는데 그냥 건너 뛰도록 하겠습니다. 아무튼 그 코드에서 main함수를 호출해 주고 main함수가 return하면 exit 시스템호출을 불러 줍니다. 그래서 main이 맨 처음 수행되는 것처럼 보이고 main이 return하면 프로그램 수행이 종료되는 겁니다. 그럼 그 코드는 어디 있을까요? 시스템에 따라서 다르겠지만 일반적으로 /lib혹은 /usr/lib 디렉토리에 crt1.o라는 이름의 object 파일이 있는데 그 object 파일 안에 있는 _start라는 이름의 함수(?)가 맨 먼저 수행되는 녀석입니다.
결국 보통 application의 entry는 _start함수가 됩니다.
그럼 crt1.o object 파일 역시 같이 linking되어야 겠죠? gcc를 이용해 linking을 수행할 때 command line에 아무 이야기를 해주지 않아도 자동으로 crt1.o 파일이 함께 linking됩니다. 실제로는 crt1.o 뿐 아니라 비슷한 crt*.o 파일들도 같이 linking되는데요. 그렇게 같이 linking되고 있는 object파일들을 startup file이라고 부르는 것 같습니다.(-nostdlib 옵션 설명할 때 잠시 나왔던 startup file이 바로 이 녀석들입니다.)
여기서 한 가지 의문사항이 떠오를만 합니다. 그럼 ld는 _start파일이 entry인지 어떻게 알고, 다른 이름의 함수를 entry로 할 수는 없는걸까요? 의문의 해결은 아래 linking script부분에서 해결될 겁니다.

== 실행 파일에 남아 있는 정보
linking의 결과 실행파일이 생겼는데, 보통 linux에서는 실행파일 형식이 ELF라는 포멧을 가집니다.(linux 시스템에 따라 다를 수 있는지 모르겠네요.) ELF는 Executable and Linkable Format의 약자입니다. 보통 linux 시스템에서의 relocatable object 파일의 형식도 ELF인데요, 실제로 실행파일과 relocatable object 파일과는 조금 다른 형식을 가집니다. 암튼 그건 상식으로 알아두고, 그럼
실행파일에 있는 정보는 무엇일까요?
이제까지의 알아낸 정보들을 모두 종합하면 알 수 있습니다. 우선 실행 파일이라는 녀석이 결국은
relocatable object를 여러 개 쌓아놓은 녀석이므로 원래 relocatable object 파일이 가지고 있던 code와 data 정보는 모두 남아있을 겁니다. 그리고 entry를 나타내는 address가 있어야 수행을 할 수 있겠죠? 또, dynamic linking을 했을 경우 관련된 shared object 정보도 남아있어야 하겠죠.
실행 파일 속에 남아있는 data는 relocatable object에 있는 data처럼 프로그램 수행에 필요한 data가 있고 그냥 실행 파일을 설명하는 정보로서의 data가 있습니다. 예를 들어 –g 옵션을 주고 컴파일한 실행파일에서 디버깅 정보들은 실행과는 전혀 관계 없죠. 따라서 그러한 정보들은 실행 파일 수행시에 메모리에 load될 필요도 없습니다.(load하면 메모리 낭비니깐) 실행 파일 속에 남아있는 code와 data는 relocatable object처럼 특별한 단위로 저장되어 있습니다. ELF 표준에서는 segment라고 부르는데 보통의 경우는 object 파일처럼 section이라는 말이 쓰입니다. reloctable object 파일과 마찬가지로 code는 text section에 저장되고 프로그램 수행 중에 필요한 data가 성격에 따라 나누어져 data, rodata, bss section이란 이름으로 저장되어 있습니다. 그 section단위로 메모리로 load될 필요가 있는지에 대한 flag정보가 있고 각 section이 load될 address(location과정에서 정했겠죠?)가 적혀 있어야 정확하게 loading을 할 수 있습니다.
기타로 symbol reference resolving이 끝났는데도 ELF형식의 실행파일은 보통의 경우 많은 symbol 정보를 그냥 가지고 있는 경우가 있습니다. symbol 정보 역시 수행에는 하등 관계가 없으므로 없애도 되는데, strip이라고 하는 binutils안에 있는 tool로 없앨 수 있습니다.

== linking script
흠 이제 좀 어려운 이야기를 할 차례입니다. Location과정에서 어떤 절대 address를 기준으로 각 section들을 쌓는지, 그리고 entry는 어떤 symbol인지에 대한 정보를 linker에게 알려줄 필요가 있습니다. 보통 application의 경우는 시스템 마다 표준(?, 예를 들어 entry는 _start다 하는 식)이 있는지라 별로 문제될 것은 없는데, bootloader나 kernel을 만들 때는 그런 정보를 사용자가 넘겨 주어야 할 필요가 있습니다. 그런 것들을 ld의 command line argument로 넘길 수도 있지만 보통의 경우는 linking script라고 하는 텍스트 형식의 파일 안에 저장하여 그 script를 참조하라고 알려 줍니다.(아무래도 command line argument로 넘겨 줄 수 있는 정보가 한계가 있기 때문이라고 생각이 듭니다. location과 entry에 관한 내용 중에 ld의 command line argument로 줄 수 있는 옵션이 몇가지 있으나 한계가 있습니다.) ld의 옵션 –T으로 linking script 파일 이름을 넘겨 주게 됩니다.(gcc의 옵션 아님) linux kernel source를 가지고 있는 분은 arch/*/*.lds 파일을 함 열어 보세요. 그게 linking script고, 초기 절대 address하고 section별로 어떻게 쌓으라는 지시어와 entry, 실행 파일의 형식 등을 적어 놓은 내용이 보일 겁니다. 물론 한 줄 한 줄 해석이 된다면 이런 글을 읽으실 필요가 없습니다. 그 script를 한 줄 한 줄 정확히 해석해 내려면 GNU ld manual 등을 읽으셔야 할 것입니다.

== linux의 insmod
이곳 KELP 사이트에 오시는 많은 분들은 특성상 linux kernel을 구성하고 device driver 등은 linux kernel module(이하 module) 형식으로 run-time에 올릴 수 있다는 것을 아실 겁니다. module을 run-time에 kernel에 넣기 위해서 사용하는 명령어가 insmod죠.(modprobe도 가능)
module이라는 것이 만들어 지는 과정을 잘 살펴 보시면 gcc의 옵션중에 -c옵션으로 컴파일만 한다는 것을 알 수 있습니다. 확장자는 .o를 사용하구요. 그럼 relocatable object 파일이겠네요. 당연히 ELF형식이겠구요.
그럼 이 module이 linux kernel과 어떻게 합쳐질까요? 당연히 linking 과정을 거쳐야 됩니다. 일종의 run-time linking인데요. 당연히 module은 kernel내의 많은 함수와 전역 변수를 참조합니다. 그렇지 않다면 그 module은 linux kernel의 동작과는 전혀 관계 없는 의미 없는 module이 될테니까요. 그럼 참조되고 있는 symbol을 resolving하기 위해서는 symbol의 절대 address를 알아야 겠네요. 그 내용은 linux kernel 내부에 table로 존재합니다. /proc/ksyms라고 하는 파일을 cat해보시면 절대 address와 symbol 이름을 살펴보실 수 있을 겁니다. 살펴보시면 아시겠지만 생각보다 적은 양이죠? 적은 이유는 그 table이 linux kernel source에 있는 전역 symbol의 전부를 포함한 것이 아니라 kernel source 내부나 module 내부에서 EXPORT_SYMBOL()과 같은 특별한 방법으로 선언된(?, 이 선언은 C 언어 문법의 declaration과는 다릅니다.) symbol들만 포함하기 때문입니다. 다른 전역 symbol 들은 module 프로그래밍에 별 필요가 없다고 생각되어 지는 녀석들이기 때문에 빠진 겁니다. 따라서
EXPORT_SYMBOL()등으로 선언된 symbol들만 사용하여 module을 작성해야 합니다.
당연히 linking 과정을 거치기 때문에 앞서 설명드린 linking에서 발생할 수 있는 에러들이 발생할 수 있습니다. 제일 많이 발생할 수 있는 것은 역시 undefined reference 에러일 겁니다. gcc의 에러와는 조금 다른 메시지가 나오겠지만 결국은 같은 내용입니다.

== map 파일
linking 과정을 끝내면 당연히 모든 symbol에 대한 절대 address가 정해지게 됩니다. 그 정보를 알면 프로그램 디버깅에 도움이 될 수도 있으니 알았으면 좋겠죠. ld의 옵션중에 '-Map 파일이름'이라는 옵션이 있는데 우리가 원하는 정보를 문서 파일 형식으로 만들어 줍니다. 그 파일을 보통 map 파일이라고 부르죠.
symbol과 address 정보 말고 section에 대한 정보도 있고 많은 정보가 들어 있습니다.
linux kernel을 컴파일을 하고 나면 나오는 결과 중에 System.map이라는 파일이 있는데 이 녀석이 바로 ld가 만들어 준 map 파일의 내용 중에 symbol과 symbol의 절대 address가 적혀 있는 파일입니다. linux kernel panic으로 특정 address에서 kernel이 죽었다는 메시지가 console에 나오면 이 System.map 파일을 열어서 어떤 함수에서 죽었는지 알아볼 수도 있습니다.

== 옵션 넘기기
gcc의 이야기 맨 처음에 gcc는 단순히 frontend로 command line으로 받은 옵션을 각 단계를 담당하고 있는 tool로 적절한 처리를 하여 넘겨준다고 말씀드렸습니다. 위에서 나온 ld의 옵션 -T와 -Map 과 같은 옵션은 gcc에는 대응하는 옵션이 존재하지 않습니다. 이런 경우 직접 ld를 실행할 수도 있고 gcc에게 이런 옵션을 ld에게 넘겨 주라고 요청할 수 있습니다. 하지만 application을 컴파일할 때는 ld를 직접 실행하는 것은 조금 부담이 되므로,
gcc에 옵션을 넘기라고 요청하는 방법이 조금 쉽다고 볼 수 있습니다. 그런 경우 사용되는 것이 -Wl 옵션인데 간단히 이용해 보도록 하겠습니다.
$ gcc -o hello -static -Wl,-Map,hello.map hello.c
그럼 hello.map이라는 매우 큰 문서 파일이 만들어 집니다. 한번 살펴 보세요.(-static 옵션을 안 넣으면 살펴볼 내용이 별로 없을까봐 추가했습니다.)
실제로는 -Wl 옵션처럼 as에게도 옵션을 넘겨 줄 수 있는 -Wa와 같은 옵션이 있는데 쓰는 사람을 본 적이 없습니다.

=== 끝
이상 gcc를 사용할 때 필요한 지식에 대해서 간략히 알아보았습니다. 더 많은 정보를 얻고 싶은 분들은 gcc, cpp, as, ld 등의 manpage와 manual을 참조하시길 바랍니다.

반응형

'[OS] > Embedded' 카테고리의 다른 글

[펌] gprof를 사용한 프로파일링  (0) 2005.09.09
[펌] gcc 이야기(5)  (0) 2005.09.06
[펌] gcc 이야기(4)  (0) 2005.09.06
[펌] gcc 이야기(3)  (0) 2005.09.06
[펌] gcc 이야기(1)  (0) 2005.09.06