티스토리 뷰

심화

Linker Script - 링커 스크립트

Just4Fun 2016. 10. 1. 15:37

일반적으로 컴퓨터에서 실행되는 어플리케이션 프로그램을 C나 C++같은 언어로 개발할 때에는, 만들어진 실행파일이 물리적으로 어디에 저장되어 있는지, 실행될 때 메모리의 어느 주소에 어떤 구조로 로드되는지 몰라도 프로그램을 실행하는데 전혀 문제될 것이 없다.


하지만, 임베디드 시스템 프로그램을 개발할 때에는 얘기가 조금 달라진다.

프로그래머는 CPU에 따른 메모리 맵이 어떻게 생겼는지 알아야 하고, 이러한 메모리 맵에 맞도록 실행파일을 만들어야 한다.

이번 글에서 설명하고자 하는 링커 스크립트라는 것이 바로 CPU의 메모리 맵에 따라 실행파일을 어떻게 생성할 것인지를 알려주게 된다.


이 블로그의 입문과정 첫번째 글인 "hello, world"에서 컴파일 과정을 전체적으로 간단히 설명하였는데, 마지막 단계가 오브젝트 파일들과 라이브러리 파일들을 합쳐서 최종 실행파일로 만들어주는 링크 과정이라는 것을 얘기하였다.


Atmel Studio 프로그램안에 있는 컴파일러는 GCC 컴파일러를 사용하고, GCC에 들어있는 링크 프로그램의 이름은 "ld"이다.  엄격하게 말하면 "ld"와 몇개의 다른 프로그램들은 GCC에 포함되어 있는것이 아니고 binutil이라는 프로그램 패키지에 포함되어 있다.  하지만 인터넷에서 GNU LD라고 검색하면 ld에 관한 많은 정보를 얻을 수 있으므로 필요한 문서들을 참조하기 바란다.  이 블로그에서는 가장 기본적인 내용에 대해서만 설명하도록 하겠다.


먼저 링크 과정을 그림으로 표현하면 다음과 같다.

위의 그림은 두개의 오브젝트 파일을 가지고 링커가 실행 파일을 생성하는 것을 보여주고 있는데, 실행파일을 이루는 각각의 섹션들의 주소를 지정해 주는것도 링커의 주요 기능중 하나이다.   이때 각 섹션들의 주소값이 얼마인지 알려주기 위하여 링커 스크립트 파일을 작성하여 링커가 참고하도록 해준다.   실제로는 오브젝트 파일뿐만 아니라 컴파일러나 C라이브러리에서 제공하는 라이브러리 파일들도 함께 링크 과정에 넣어주게 된다.


위의 그림에서 foo1.o와 foo2.o 파일을 input file이라고 부르고, input file안에 있는 섹션들을 input section 이라고 부른다.

링크 과정을 거치면 input section들을 합쳐서 같은 종류의 섹션들을 하나의 섹션으로 만드는데 이렇게 만들어진 섹션들을 output section이라고 부른다.

input section과 output section이 무엇인지 알고 있어야 링커 스크립트 파일을 이해하기 쉽다.



위의 그림은 일반적인 임베디드 시스템의 메모리 맵의 예를 보여주고 있다.


읽기 전용 메모리인 ROM에 해당되는 64Kbyte 크기의 Flash 메모리는 0x08000000부터 시작되고 그 안에는 ISR Vector, Text, ROdata, Data 섹션들이 포함되어 있다.


읽기와 쓰기가 가능한 RAM 메모리는 0x20000000에서부터 시작되고 그 크기는 8Kbyte이다.  RAM영역에는 Data, BSS, Heap, Stack이 포함되어 있다.  


그런데 Data 섹션은 ROM과 RAM 영역 모두에 포함되어 있다.  단지 다른것은 Flash에는 LMA라고 표시되어 있고, RAM에는 VMA라고 표시되어 있다.  이에 대한 내용도 링커 스크립트 파일에서 설명된다.


그럼, 이제 실제 AVR CPU용 실행파일을 만들기 위한 링커 스크립트 파일이 어떻게 생겼는지 알아보도록 하겠다.  설명을 위하여 심화과정중 패킷통신 프로젝트를 이용하겠다.


지금까지 많은 프로젝트를 수행하면서 한번도 링커 스크립트 파일을 따로 작성하지 않았지만 보드에서 프로그램이 잘 동작되었다.  이렇게 프로젝트를 빌드할 때 특정한 링커 스크립트 파일을 지정해 주지 않으면, 컴파일러 내에 포함되어 있는 링커 스크립트 파일을 참조하여 실행 파일을 만들어 준다.  어떤 파일을 참조하는지 알고 싶으면 "--verbose" 옵션을 사용하여 링크 과정에서 참조하는 링커 스크립트 파일이 어떤것인지 알수있다.


프로젝트 속성창을 연다음 Toolchain > AVR/GNU Linker > Miscellaneous를 선택하면 위의 그림처럼 보여진다.  Other Linker Flags에 "-Wl,--verbose"라고 입력한 다음 다시 프로젝트를 빌드하면 다음과 같은 결과 화면을 볼 수 있다.



ATmega328P를 이용한 프로젝트에서는 "c:\program files (x86)\atmel\studio\7.0\toolchain\avr8\avr8-gnu-toolchain\avr\bin\../lib\ldscripts/avr5.xn" 파일을 이용하여 링커를 수행한 것을 알 수 있다.  이 파일을 찾아서 에디터 프로그램으로 열어보면 링커 스크립트 파일이 어떻게 생겼는지 볼 수 있다.


ATmega328P를 빌드하는데 사용된 "avr5.xn" 파일을 열어 주요 내용을 차례로 설명하도록 하겠다.


링커 스크립트 파일은 C언어와 마찬가지로 /* ... */ 를 이용하여 주석처리를 한다.

주석 문장을 건너뛰고 처음 나오는 내용은 다음과 같다.

OUTPUT_FORMAT()은 링크 과정에서 endian을 어떻게 설정하는지에 따라 실행파일의 endian 포맷을 다르게 생성하도록 한다.  세개의 옵션이 있는데 첫번째 옵션은 특별한 endian 옵션이 없을때 사용한다.  링크 옵션중에 "-EB"가 있으면 "big endian" 형식의 결과물을 생성하도록 한다.  이때 OUTPUT_FORMAT 명령의 두번째 옵션을 사용한다.  "-EL"이 지정된 경우에는 little endian 형식으로 만들기 위하여 OUTPUT_FORMAT의 세번째 옵션이 사용된다.  AVR의 경우 little endian만 지원하므로 특별한 링크 옵션을 사용하지 않게 되고, 따라서 OUTPUT_FORMAT의 첫번째 옵션을 이용하게 된다.

OUTPUT_ARCH()는 CPU의 architecture가 무엇인지 알려준다.  CPU마다 고유의 archtecture를 가지는데 이에 따라 어떻게 실행 파일을 만들것인지 링커에게 알려주는 것이다.  ATmega328P는 AVR:5에 해당되는 architecture로 되어있음을 알 수 있다.



MEMORY

{

  name [(attr)] : ORIGIN = origin, LENGTH = len

  ...

}

MEMORY 명령은 위와 같은 형식으로 만들어지는데 메모리 블럭의 시작주소와 해당 블럭의 크기를 설정한다.  output section들이 이 중에서 어떤 메모리 블럭에 들어가게 되는지 알려주면 해당 주소를 차례대로 할당받게 된다.


링커 스크립트의 다음 내용이 SECTIONS 명령어이다.

중요한 내용만 표시해 보면 다음과 같다.


3~8번 라인은 .text라는 output 섹션에 대한 내용을 다루고 있다.  위의 예에서는 .vectors라는 input section만 표시되어 있는데 실제로는 아주 많은 input section의 내용들이 포함되어 있다.  { }안에 표현되어 있는 모든 input section들을 .text 라는 이름을 갖는 output section에 들어가고 MEMORY에서 지정된 text라는 메모리 블럭의 주소 공간에 할당되도록 한다.


9~20까지는 .data 섹션에 대해서 설정하는 내용이다.  기본적인 모습은 .text와 다를게 없지만 마지막 20번째 라인을 주의깊게 보아야 한다.

}  > data AT> text 라고 되어 있는데 .data 섹션은 메모리 블럭중 data 메모리 블럭의 주소를 가지게 되어 있다.  하지만 실제로 물리적으로 놓이는 위치는 text메모리 블럭에 놓이게 된다.  이게 무슨말인지 헷갈릴 수 있으므로 map 파일을 참고 하여 설명하도록 하겠다.

프로젝트를 빌드하면 그 결과물들은 Debug 폴더에 저장되어 있는데 그 중에서 map 파일을 열어서 다음 내용을 찾아본다.


위의 내용은 링커 스크립트 파일의 MEMORY 명령으로 설정한 내용과 동일함을 알 수 있다.  참고로 마지막 줄을 보면 .data 섹션은 data 메모리 블럭의 시작주소부터 할당되는 것이 아니라 0x00800100에서 시작된다는 것을 볼 수 있다.



map 파일을 계속 내려가다 보면 위의 내용을 볼 수 있다.  .text 섹션은 MEMORY 에서 지정한 대로 0x00000000에서 시작되고, .text 섹션의 크기는 0x700인것을 알려준다.


그 다음에 나오는 .data 섹션은 0x00800100에서 주소가 시작되고 있음을 알 수 있고, 실제 크기는 0이라고 알려준다.  크기가 0이므로 input section에 .data 섹션에 할당된 변수가 없다는 뜻이 된다.  .data 섹션 크기 다음에 보면 load address 0x00000700이라는것을 볼 수 있다.  즉, .data 섹션의 시작주소는 0x800100이지만 실제 로드되어 있는 주소는 0x0700이라는 뜻이 된다.  0x0700이라는 주소는 .text 섹션이 가지는 주소가 0x000 ~ 0x6FF이고 .data 섹션의 load address가 .text 섹션에 이어져서 할당되어야 하므로 0x700이 되는 것이다.


위에 나오는 메모리 맵 그림에서 data 섹션이 두개로 표시되는 이유가 바로 이것 때문이다.  0x800100이 VMA(Virtual Memory Address)에 해당되고, 0x0700이 LMA(Load Memory Address)에 해당된다.  LMA는 링커에 의해서 실행 파일이 만들어 질 때 결정되어 지는 주소이고, VMA에는 프로그램이 실행되고 있을 때 참조하는 주소이다.

즉, 프로그램이 reset 벡터에서 처음 시작 되는 시점에는 data 섹션의 내용들은 LMA주소에 존재하게 되고, VMA 주소에는 아무런 데이터가 없다.  main() 함수가 시작되기 전에 누군가 LMA주소에 있는 data 섹션의 내용들을 VMA 주소로 복사해 주어야 한다.  또한 BSS 섹션은 main() 함수가 시작될 때에는 모두 0으로 초기화 되어 있어야 한다.

이러한 동작을 수행해 주는 코드가 .text 섹션에 포함되어 있다.  이러한 동작을 해주는 코드를 crt0라고 부르는데 다음 글에서 자세히 설명하도록 하겠다.


나머지 내용들은 크게 중요하지 않으므로 여기에서는 설명을 생략하기로 하고, 좀 더 자세한 내용을 알고 싶은 경우에는 GNU ld 메뉴얼을 인터넷에서 다운받아 읽어 보기 바란다.



심화 과정 목차


'심화' 카테고리의 다른 글

startup.S  (5) 2016.10.01
crt0  (0) 2016.10.01
패킷 통신 - Request/Reply  (0) 2016.08.12
패킷 통신 - Message ID  (0) 2016.08.12
패킷 통신 - CRC  (2) 2016.08.11
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함