[OS]/Embedded

[펌] gcc 이야기(4)

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

gcc 이야기(4)

글쓴이 : holelee (2002년 04월 22일 오후 09:03)

[ 임베디드강좌/이규명 ] @ 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”를 클릭하여 메일을 보내 주시기 바랍니다.


=== 시작 및 복습
이제 Assemble 과정입니다. 이제 중간을 지나왔네요. Assemble 과정에는 별로 지적할 만한 것은 없습니다. 그럼 시작합니다.

=== Assemble 과정
Assemble 과정은 앞선 과정과 동일하게 gcc라는 frontend가 as라는 실행 파일을 호출(?)하여 수행됩니다. 그런데 as는 cpp와 cc1과는 달리 gcc 패키지 안에 존재하는 것이 아니라 별도의 binutils라고 하는 패키지에 존재합니다.
binutils 패키지 안에는 as를 비롯해 linking을 수행하는 ld, library 파일을 만드는 ar, object 파일을 보거나 복사할 수 있는 objdump, objcopy 등 여러 가지 툴이 들어 있습니다.

이제 Assemble 과정이 하는 일을 자세히(?) 알아보도록 하겠습니다.

== Assemble 과정이 하는 일
(1) 입력 : Assembly 소스 코드
(2) 출력 : relocatable object 코드
(3) 하는 일 : assemble(너무 간단한가요?)

역시나 간단하네요. 입력은 당연히 C 언어 컴파일 과정을 거치면 나오는 Assembly 소스 코드입니다. Assemble 과정을 거치면 소위 기계어(machine language)라는 결과가 relocatable object 형식으로 나오겠죠. “relocatable”이라는 말이 어려우면 그냥 object 코드라고 해 두죠. 어짜피 나중에 설명이 나올테니깐.
이제 직접 수행해봐야 겠죠? shell의 command line에 다음과 같이 입력하면 됩니다.
$ gcc –c hello.c
지겨운 hello.c를 썼습니다. 결과는 hello.o라고 하는 파일이 나옵니다. hello.o는 binary형식의 파일이니깐 editor로 열어봐야 정보를 얻기 힘듭니다. 당연히 위의 예는 assemble 과정만 수행한 것이 아니라 C preprocessing 과정, C 언어 컴파일 과정, Assemble 과정을 수행했겠죠. Assemble 과정만 수행하고 싶으면 다음과 같이 입력하면 됩니다.
$ gcc –c hello.s
역시 hello.o가 생기겠죠. hello.s는 C 언어 컴파일 과정에서 –S 옵션으로 만들었던 그 파일입니다. 별로 관심이 안 생기면 as를 직접 수행할 수도 있습니다. 다음과 같습니다.
$ as –o hello.o hello.s
역시 hello.o가 생기죠?

Assemble 과정 이야기가 짧기 때문에 늘려서 써 봤습니다(이해해 주시길…).

(*) –c 옵션
많이 쓰는 옵션이죠. Assemble 과정까지의 과정만 수행하고 linking 과정을 수행하지 말라는 옵션입니다. 여러 개의 C 소스 파일로 이루어진 프로그램을 컴파일 할 때 모든 소스 파일을 assemble 과정까지 수행하고 맨 마지막에 linking하죠. 보통은 Makefile을 많이 이용하는데 그 때 많이 쓰이는 옵션이죠.

Assemble 과정에서는 더 이상 기억해야 하는 옵션도 없고 이게 끝입니다. C 언어 컴파일 과정에서 말씀 드린 바대로 C 언어 컴파일 과정이 끝난 C 소스 파일은 문법적으로 완전하다고 볼 수 있으므로 assemble 과정에서 Error나 Warning 나는 경우는 없습니다. 만약 Error나 Warning이 나는 경우가 있다면 gcc의 inline assemble을 이용했을 때, 그 inline assemble 소스 코드에 있는 문제 때문에 생길 수 있습니다. 안타깝지만 error나 warning 메시지가 나온 다면 C 소스 파일과 line number 정보는 없습니다. 잘 알아서 처리하는 수 밖에 다른 방법은 없는 것 같습니다. inline assemble 같은 것을 사용하지 않았는데도 error나 warning이 난다면 gcc의 버그라고 생각하셔도 무방합니다.


여기서 끝내면 너무 짧으니까 재미있는(?) 이야기나 하도록 하죠. 이 이야기는 linking 과정을 이해하기 위해서 필요할 지 모르니 조금 어렵더라도 참고 읽어 두시길 바랍니다.

== relocatable object 코드 파일 내용
어떤 정보가 object 파일 안에 들어있을까요? 당연히 code와 data가 들어 있습니다. C 컴파일 과정에서 C 언어 함수 안에 있는 내용들이 assembly mnemonic 들로 바뀌었고 그것이 assemble되어 기계어(machine language)가 되었을 겁니다. 그 부분이 code를 이루겠죠. C 언어 소스 코드에 있는 나머지는 전역 변수(external variable)와 정적 변수(static variable)들이 data를 이룰 겁니다. 또한 문자열 상수를 비롯한 상수도 data에 들어 있겠죠. 또한 프로그램 수행에 쓰이지는 않고 단순한 정보로서 들어 있는 data들도 있습니다. 예를 들어 –g 옵션을 주고 컴파일 하면 프로그램의 디버깅 정보(변수, 함수 이름, C 소스 파일이름, line number 등)가 data에 속한다고 볼 수 있습니다. 그런데 code와 data가 무질서하게 섞여 있는 것은 아니고 section이라고 불리우는 단위로 서로 구분되어 저장되어 있습니다. Code는 text section에 들어 있고, data는 성격에 따라 data section, bss section, rodata section 등에 나누어져 저장되어 있습니다.(text, data, bss, rodata 등의 section 이름은 그냥 관습적인 것입니다.) 아무튼 section 이야기는 이 정도만 우선 알아두시면 될 듯 싶습니다. 좀 더 복잡한 이야기를 할 수도 있지만 초보자에게는 별 필요 없을 듯 하여 그만 두겠습니다.

== Symbol 이야기
relocatable object code안에 code와 data가 들어 있다고 했는데, 아주 중요한 것을 빠뜨렸습니다. 이 이야기는 linking 과정을 이해하기 위해 꼭 필요한 부분이므로 반드시 읽어 두셔야 할 듯 싶습니다.
우선 Symbol이 무엇인지 아시죠? C 언어 컴파일 과정에서 identifier와 함께 설명 드렸는데 잠시 다시 말씀 드리면 Symbol은 함수와 변수 이름입니다. 변수 중에 특히 관심두어야 할 것 들은 자동 변수(?,auto variable)들이 아닌 전역 변수(external variable)와 정적 변수(static variable) 입니다. 자동 변수는 함수의 stack frame에 존재하는 변수이기 때문에 현재 stack pointer(sp, 보통의 CPU의 register중에 하나)에 대한 offset으로 표현됩니다. 즉 현재 함수에서 자동 변수(auto variable)를 access(read/write)하고 싶으면 sp+상수의 어드레스를 access하면 되죠. 하지만 전역 변수와 정적 변수는 그냥 32bit(32bit CPU기준) 어드레스를 읽어야 합니다. stack pointer랑은 전혀 관계 없죠. 아무튼 여기서 관심을 두는 Symbol
은 함수, 전역 변수와 정적 변수의 이름이라고 할 수 있습니다.
이제 생각해 볼 것은 C 언어 소스 파일을 C preprocessing, C 언어 컴파일, assemble 과정을 거치면 완전한 기계어로 바꿀 수 있느냐 하는 점입니다. 완전히 기계어로 바꿀 수 있을 까요? C 언어 소스 파일 하나로 이루어지는 프로그램이라면 완전히 기계어로 바꾸는 것이 가능하겠지만 일반적으로는 불가능 합니다. 다음과 같은 예제를 살펴보죠.
-- start of test1.c
int func3(void); /* func3 선언 */
extern int mydata; /* mydata 선언 */

int func2(void) /* func2 정의 */
{
….
}

int func1(void) /* func1 정의 */
{
int i;
…..
func2();
…..
func3();
….
i = mydata + 3;
…..
}
-- end of test1.c


-- start of test2.c
int mydata = 3; /* mydata 정의 */
int func3(void) /* func3 정의 */
{
…..
}
-- end of test2.c

위의 예제를 컴파일 한다고 생각해보죠. test1.c에서 func1()의 내용을 기계어로 바꾸고 싶은데 func2()를 호출하는 시점에서는 별로 문제가 안됩니다. func2()는 같은 소스 코드 내에 존재하고 func2()를 호출하는 instruction과 func2()의 실제 위치(어드레스)의 차이를 계산해 낼 수 있으므로 상대 어드레스를 이용하는 함수 호출 instruction으로 완전히 기계어로 바꿀 수 있습니다. 그런데 문제는 func3()를 호출할 때는 func3()의 실제 위치(address)를 계산할 수 없다는 문제점이 있습니다. 당연히 동일한 파일에 존재하는 함수가 아니므로 그 함수가 존재하게 될 어드레스를 계산할 수 없겠죠. 어드레스를 모르는데 함수 호출 instruction을 완전히 만들 수 있을까요? 만들 수 없죠. 당연히 전역 변수 mydata를 access하는 부분도 마찬가지로 mydata의 어드레스를 모르므로 완전히 instruction으로 바꿀 수 없습니다. 그럼 어떻게 해야 될까요?
그때 assembler는 그냥 함수 어드레스 없는 함수 호출 instruction을 기계어로 바꾸어 놓습니다. 그런 다음에 그 instruction에 “func3()를 호출한다”라는 표지를 붙여 놓습니다.
그럼 그 후의 과정(linking 이겠죠?)에서 func3()의 address를 계산했을 때 그 빈 공간을 채워 넣게 됩니다. mydata와 같은 전역 변수도 마찬가지로 동작합니다. 그럼 test1.c을 컴파일할 때는 “func3()”, “mydata” 라는 표지를 사용해야 겠죠? 그럼 test2.c를 컴파일 할 때는 무엇이 필요할까요? 상식적으로 생각하면 “func3()”, “mydata”가 여기 있다라는 정보를 가지고 있어야 겠죠?
정리하면 object 파일 안에는 그 object 파일에 들어있는 symbol들(test1.o에서는 func1과 func2, test2.o에서는 func3와 mydata)에 대한 정보가 들어있고, 그 object 파일이 reference하고 있는 symbol들(test1.o에서 func3와 mydata 사용)에 대한 정보가 들어 있습니다. 이해 되시나요?

== Relocatable의 의미
위에서 object 코드라고 하지 않고 relocatable object 코드라고 지칭했는데 relocatable이 뜻하는 것을 잠시 집고 넘어 가겠습니다. Relocatable을 사전에서 찾아보면 “재배치가 가능한” 정도의 뜻입니다. “재배치가 가능한” 이라는 의미는 상당히 모호합니다. 좀 더 구체적으로 말씀드리면 위에서 설명된 symbol들의 절대 어드레스가 정해지지 않았다는 뜻입니다. 즉 test1.c의 func1()이 절대 어드레스 0x80000000에 존재해야 한다라고 정해지지 않고 어떤 절대 어드레스에 존재해도 관계 없다는 뜻입니다. 그런데 이 말과 헷갈리는 말이 한가지 더 있는데 그것은 position independent code입니다. C 언어 컴파일 과정에서 설명한 옵션중에 –f 시리즈가 있었습니다. 그 중에 –fpic라는 position independent code를 만들라고 강제하는 옵션이 있습니다. position independent code도 역시 절대 어드레스상에 어느 위치에 있어도 무방한 code를 지칭합니다. 하지만 두 가지는 분명 차이가 있는데… 에이, 그냥 넘어 가도록 하죠. 설명을 하려면 상당히 복잡하기 때문에… 그냥 relocatable은 절대 어드레스가 결정되지 않았다는 뜻, 그러나 position independent code와는 다른 말임을 알아 두세요.


이상 assemble 과정에 대한 이야기가 끝났습니다. assemble 과정 자체는 짧았는데 linking 과정에 대한 이야기가 약간 나오는 바람에 역시 길어졌습니다. 다음에는 linking과정에 대해서 알아 보도록 하겠습니다. linking 과정은 상당히 재미있고(?) 긴 이야기가 될 것 같습니다. 두 번에 나누어 적어야 되지 않을까 싶을 정도로…
오랜만에 납땜을 했더니 flux remover 냄새 때문인지 머리가 조금 아프네요. 그래도 납땜한 board가 잘 동작하여 기분은 좋습니다.

반응형

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

[펌] gcc 이야기(5)  (0) 2005.09.06
[펌] gcc 이야기(6)  (0) 2005.09.06
[펌] gcc 이야기(3)  (0) 2005.09.06
[펌] gcc 이야기(1)  (0) 2005.09.06
[펌] gcc 이야기(2)  (0) 2005.09.06