본문 바로가기
myCortex

map 파일을 살펴보자

by irmus 2008. 9. 18.
map 파일은 링크와 관련된 여러가지 정보들을 텍스트로 표현한 파일이다. 보통때에는 크게 필요하지 않지만, 디버깅 시 결정적인 정보를 제공하기 때문에 map파일 보는법도 알아두고, 평상시 눈여겨 보는 습관을 만들어 두는 것이 좋다. map파일은 링크할 때 옵션을 주지 않으면 기본적으로 만들어지지 않지만 myCortex 에제에서는 map 파일이 생성되도록 옵션이 지정되어 있다.

map 파일은 gcc 폴더 아래에 만들어지며 확장자가 .map이다 설명의 편의를 위해 timer 예제를 대상으로 살펴보자. timer/gcc/timer.map 파일을 UltraEdit같은 텍스트 에디터로 열어보자. 없다면 메모장에서 열어도 무관하다.

우선 파일이 좀 크다는 것을 알 수 있을 것이다. timer 예제는 아주 간단한 예제임에도 불구하고 수백라인의 map 파일이 만들어진다. 물론 프로젝트가 복잡해지면 map 파일은 것잡을 수 없이 커지게 되고, 메모장에서 열어 보기 힘겨워 지기도 한다.

map파일은 대충 아래와 같은 항목들로 이루어져 있다.
  1. Archive member included because of file (symbol)
  2. Discarded input sections
  3. Memory Configuration
  4. Linker script and memory map


첫번째 Archive member included because of file (symbol) 색션은 이 프로젝트에서 사용된 라이브러리(아카이브)파일들에 대한 정보를 담고 있다. timer.map 파일의 이 색션 내용 중 첫번째 항목을 한번 살펴보자
../../../src/gcc/libdriver.a(gpio.o)
                              gcc/timer.o (GPIOPinRead)
이 항목은 timer.c 파일에서 GPIOPinRead() 함수를 사용하고 있기 때문에 libdriver.a 라이브러리가 사용되었다는 것을 말하고 있다. 특히 libdriver.a를 구성하는 파일들 중 gpio.c 안에 GPIOPinRead()함수가 있다는 것을 말한다.
이처럼 프로젝트에 어떤 라이브러리가 사용되고 있는지, 또 왜 그 라이브러리가 사용되었는지를 알 수 있다.


두번째 Discarded input sections 항목은 첫번째 항목에서 언급된 라이브러리 중 사용되지 않은, 즉 링크되지 않은 함수들을 열거하고 있다. 링커가 라이브러리를 링크할 때에는 그 라이브러리 내의 모든 함수를 모두 다 링크하지는 않는다. libdriver.a 라이브러리 내에 수백개의 함수가 있지만 정작 timer 예제에서 사용하는 함수는 고작 대여섯개 밖에 되지 않는 상황에서 필요하지도 않은 수백개의 함수를 모두 다 사용하면 최종 바이너리 사이즈가 커지기 때문이다. 이처럼 라이브러리 내에 있지만 쓰이지 않기 때문에 버려진 함수들을 이 항목에서 볼 수 있다.


Memory Configuration 항목은 관심있게 봐야하는 항목이다.
Name             Origin             Length             Attributes
FLASH            0x00000000         0x00040000         xr
SRAM             0x20000000         0x00010000         xrw
*default*        0x00000000         0xffffffff
프로젝트 바이너리의 전체 memory map을 한눈에 볼 수 있다. 사실 이 내용은 ld파일(link scriptor 파일)에서 지정한 내용을 간단하게 요약한 것이다.
FLASH 색션은 0~0x40000의 공간에 위치하고, xr, 즉 실행-읽기 가능한 영역임을 말한다.
SRAM 색션은 0x20000000~0x20010000의 공간에 위치하고, 실행-읽기-쓰기가 가능한 영역이다.
색션 이름은 ld 파일에서 정하기 나름이다.
이 예에서 FLASH가 0번지에서 시작하는 것을 볼 수 있는데, 이것으로부터 이 이미지가 bootloader용이 아니라 stand-alone용으로 만들어진 이미지임을 알 수 있다. 똑같은 timer 예제를 bootloader용으로 빌드하면 FLASH 색션의 시작 주소가 0x00000000이 아니라 0x00000800 으로 설정된다. 물론 length도 0x800만큼 줄어든다.


마지막 항목인 Linker script and memory map는 디버깅 할 때 아주 유용한 정보를 제공한다.
.text           0x00000000      0x880
                0x00000000                _text = .
 *(.isr_vector)
 .isr_vector    0x00000000       0xf0 gcc/startup_gcc.o
                0x00000000                g_pfnVectors
 *(.text*)
 .text          0x000000f0       0x54 gcc/startup_gcc.o
                0x000000fc                ResetISR
 .text          0x00000144       0xd8 gcc/timer.o
                0x00000144                __error__
                0x00000148                main
 .text.GPIODirModeSet
                0x0000021c       0x44 ../../../src/gcc/libdriver.a(gpio.o)
                0x0000021c                GPIODirModeSet
앞부분 조금만 살펴보자. 첫번째는 text 색션의 시작 주소(0x00000000)와 length(0x880)를 볼수 있다.
그리고 isr_vector 색션의 시작주소(0x00000000), length(0xf0)역시 보이고, 이 isr_vector 색션을 구성하는 내용은 startup_gcc.c 파일 중에서 g_pfnVectors라는 전역변수라는 것을 말하고 있다. 다시 말해서 g_pfnVectors 변수의 주소는 0x00000000이다. 여기서 잠깐 startup_gcc.c 파일의 g_pfnVectors 변수 선언 부분을 보면 __attribute__ ((section(".isr_vector")))와 같이 inline으로 section을 선언하고 있는 것을 볼 수 있다.
다음으로 startup_gcc.c 파일의 ResetISR 함수가 0x000000fc 주소에 위치하고 있으며 이 함수의 크기는 0x54 바이트임을 말한다.
그 아래에는 timer.c 파일이 0x00000144주소에 있으며 전체 length가 0xd8 바이트라고 말하고 있다. 그런데 timer.c 파일은 __error__ 함수와 main 함수 2개로 구성되며, __error__ 함수는 0x00000144 주소에, main 함수는 0x00000148 주소에 있다.
마지막으로 libdriver.a 라이브러리의 gpio.c 파일에 있는 GPIODirModeSet 함수가 0x0000021c 주소에 있다는 것을 알려준다.
이처럼 이 항목은 사용된 파일과 함수가 어느 주소에 위치하는 지를 알려준다.
ARM CPU 코어는 각종 exception이 발생했을 때 그 exception이 발생한 주소를 특정 레지스터에 백업해 주는 기능을 가지고 있다. JTAG 사용자라면 시스템이 죽어버렸을 때 exception handler에 break point를 걸어두고 원인을 제공한 곳의 주소를 볼 수 있다. 여기까지라면 주소를 본다로 그치겠지만 map 파일의 이 항목을 들여다 봄으로써 그 주소가 어떤 함수에 속하는 주소인지를 파악할 수 있게 된다. 여기까지 알게 되면 그 함수의 시작 부분에 break point를 걸어두고 다시 실행시켜서 fault가 발생하는 과정을 살펴볼 수 있게 된다.
간단한 시스템에서는 이정도의 디버깅 테크닉을 필요로 하는 경우는 드물지만 RTOS가 올라가고 여러 task가 동시에 돌아가면서 여러명의 개발자가 동시에 작업하는 시스템을 개발하는 경우라면 memory fault같은 버그가 종종 나오게 되고, 이런 테크닉을 모르는 사람은 그순간부터 "X고생"을 시작하게 되는것을 많이 봐 왔다.

좀 더 아래로 내려가면 rodata 색션에 대한 정보나 glue 색션, veneer 색션 vtable 색션 등에 대한 정보도 볼 수 있다. 다른 부분들은 그다지 중요하지 않지만 rodata, data, bss 색션은 프로그램 코드와 전역변수등이 들어가는 곳이므로 유심히 살펴보는 것이 좋다. 한가지 예로 bss색션을 보면 0x20000100에 startup_gcc.c 파일에 있는 무언가가 위치해 있다는 것을 알 수 있다. 사이즈는 따로 명기되어있지는 않지만 바로 다음 항목이 0x20000200에서 시작하니 0x100(256Byte)의 사이즈 일 것이다. 즉 C-stack으로 사용되는 startup_gcc.c의 pulStack[] 변수가 여기에 있다. pulStack 변수는 전역변수이지만 static 변수이기 때문에 map 파일에 심볼이름이 노출되지 않고, 따라서 unnamed로 처리되고 있는 것이다.

timer 예제가 워낙 간단한 프로젝트이다보니 map 파일도 간단하게 만들어져서 그다지 설명할 것이 없었지만, 자신이 만드는 프로젝트에서 만들어지는 map 파일을 평상시 관심있게 지켜보면서 함수나 변수가 어떻게 어디에 들어가는 지 등을 알아 두는 것이 많은 도움이 될 것이다.



section 간단히 살펴보기
  • text(혹은 code) : 프로그램 코드. instruction들이 저장된 영역. Read only이기 때문에 주로 ROM에 저장된다.
  • rodata(혹은 RO) : const 변수들을 위한 영역. 프로그램 코드가 아닌 변수이지만 const, 즉 read only이기 때문에 code와 같이 ROM 영역에 저장하는 것이 일반적이다.
  • data(혹은 RW) : 전역변수, static 변수 중 초기값이 지정된 변수들을 위한 영역. 초기값(data)이 있다는 의미와 read-write 가능하다는 의미로 이런 이름이 주로 사용된다.
  • bss(혹은 ZI) : 전역변수, static 변수 중 초기값이 없는 변수들을 위한 영역. data 색션과 같지만 초기값이 없는 변수들, 즉 Zero Initialization 영역이다.