티스토리 뷰
C 프로그램에서는 반드시 main() 함수가 하나 있어야 한다. 그 이유는 C 프로그램은 main() 함수로부터 시작되기 때문이다. 즉, 프로그램의 진입점으로 규정했기 때문에 반드시 main() 함수가 있어야 한다. 만약 main() 함수가 없으면 프로그램이 어디서부터 시작되어야할지 모르기때문에 컴파일러는 main() 함수가 없다고 에러를 발생하게 된다.
그러나 이미 앞에서 링커 스크립트를 설명할 때 main() 함수가 실행되기 이전에 처리해 두어야 하는 내용들이 있다고 하였다. 즉, ROM에 있는 .data 섹션을 RAM으로 복사해주고, .bss 섹션을 0으로 초기화 해주어야 한다. 이러한 일련의 초기화 코드를 crt0라고 통칭한다.
AVR 프로그램에 들어있는 crt0 코드는 어떻게 생겼는지 분석해 보고, 직접 crt0 코드를 작성하여 컴파일러에서 제공하는 crt0를 대체해 보도록 하겠다.
분석을 위하여 다음과 같은 프로그램을 작성해 보도록 한다.
위 프로그램을 실행 시키면 다음과 같은 화면을 볼 수 있다.
500msec마다 알파벳 한 글자씩 표시해 주는것을 보게 된다.
프로그램이 제대로 동작되는것을 확인하였으면 Debug 폴더에서 map파일과 lss 파일을 열어 본다. 또한 앞의 글에서 설명하였던 링커 스크립트 파일도 찾아서 열어본다.
모두 세개의 파일을 열어 어떤 절차를 거쳐서 main() 함수까지 프로그램이 진행되는지 분석해 보도록 하겠다.
이미 여러번에 걸쳐서 얘기해서 알고 있겠지만, AVR CPU에 전원이 인가되면 CPU는 0번지에 있는 명령어를 읽어 오는 동작으로 프로그램을 시작하게 된다.
lss 파일에서 0번지에는 어떤 명령어가 있고, 그 이후에 어떤 코드들이 실행되는지 찾아본다.
0번지에는 __ctors_end라는 심볼의 주소로 점프하는 코드가 들어있다.
__ctors_end 심볼은 0x68번지에 있으므로 0x68번지에서 다음 명령어를 읽어 실행하게 된다.
뭔지는 잘 모르겠지만 여섯개의 명령어를 처리하고 난 후 __do_copy_data라는 심볼의 코드를 수행하고, 이어서 __do_clear_bss를 수행한다. 그 뒤로는 .do_clear_bss_loop와 .do_clear_bss_start라는 심볼도 보인다. 마지막 줄에 보면 main 심볼을 call 하는 명령어를 볼 수 있다. 즉 main()함수를 실행하는 것이 된다.
이미 심볼 이름만 봐도 대충 어떤 동작을 할것인지 예측할수 있겠지만, 좀더 확실하고 쉬운 분석을 위하여 map 파일과 링커 스크립트 파일을 참조하여 실제로 어떤 동작을 하는지 분석해 보도록 하겠다.
map 파일에서 위의 내용을 볼 수 있다. 0x68번지는 .init2 섹션의 내용이 들어가 있고, 섹션의 크기는 12바이트가 되는것을 알수 있다.
링커 스크립트 파일에는 .init2 섹션에 대해서 다음과 같은 설정이 있는 것을 볼 수 있다.
"__zero_reg__를 클리어하고, 스택 포인터를 설정"한다라고 되어 있다.
이러한 정보를 가지고 코드를 분석해 보도록 하겠다.
먼저, "eor r1, r1"는 r1레지스터를 exclusive OR를 하므로 r1이 무조건 0으로 된다. EABI에서 설명한대로 r1레지스터는 항상 0이라고 되어 있기 때문에 강제로 0으로 만드는 코드이다. Clear __zero_reg__에 해당되는 코드이다.
"out 0x3f, r1"는 0x3F 주소에 r1레지스터의 값, 즉, 0을 쓰는 동작을 수행한다.
AVR data sheet 문서를 보면 0x3F에는 "SREG" 레지스터가 위치하고 있다. 즉, Status Register를 0으로 초기화 하는 동작을 수행하는 것이다.
"ldi r28, 0xFF", "ldi r29, 0x08"는 r28과 r29에 각각 0xFF와 0x08의 값을 넣어주는 동작을 수행한다. 이어지는 "out 0x3e, r29", "out 0x3d, r28" 코드는 0x3E와 0x3D에 r29, r28에 들어 있는 값을 넣어주는 것이다. 즉, 0x08FF를 넣어주는 것이 된다. 역시 data sheet 문서에 0x3D, 0x3E는 Stack pointer 레지스터라고 설명되어 있다. 즉, 스택 레지스터 값을 0x08FF값으로 설정하는 코드인 것이다.
위의 그림을 보면 왜 스택 포인터 레지스터에 0x08FF값을 써 주었는지 알 수 있다. ATmega328P의 SRAM 마지막 주소가 0x08FF이기 때문이다.
링커 스크립트에서 설명한 stack pointer를 설정하였다.
다음으로는 __do_copy_data로 시작되는 코드를 분석해 보도록 하겠다.
역시 링커 스크립트 파일에서 .init4 섹션에 대한 내용을 찾아보면 다음과 같은것을 볼 수 있다.
data와 BSS를 초기화한다라고 되어 있다. 즉, data 섹션과 bss 섹션을 초기화 한다는 뜻이 된다.
코드 분석에 앞서 map 파일에서 .data 섹션의 LMA와 VMA 주소를 먼저 확인한다.
0x800100이 VMA가 되고, 0x07C4가 LMA가 된다. 위의 내용을 참고하여 코드를 계속해서 분석해 보겠다.
"ldi r17, 0x01" r17레지스터를 1로 설정한다. 이 값은 .data 섹션의 VMA주소의 마지막 상위 바이트 값을 가르키게 된다.
"ldi r26, 0x00", "ldi r27, 0x01"는 X 레지스터를 설정하는 코드이다. AVR은 8비트 CPU이므로 주소값을 지정하기 위해서는 레지스터 두개를 연결하여야 한다. X레지스터 값은 0x0100이 되고 이 주소는 .data 섹션의 VMA 주소 시작값에 해당된다. 즉 destination address pointer 값이 되는 것이다.
"ldi r30, 0xC4", "ldi r31, 0x07"는 Z 레지스터 값을 설정하는 코드이다. 역시 주소를 지정하기 위하여 두개의 레지스터를 사용하였다. Z값이 0x07C4가 되었다. 즉, LMA주소값이 되며 source address pointer 값이 되는 것이다.
"rjmp .+4"는 현재 PC값보다 4큰 주소로 점프하는 코드이다. 이 명령어가 있는 위치가 0x7E이고 이 명령어가 수행될 때의 PC값은 이보다 다음 주소에 해당되는 0x80이 된다. 따라서 실제 점프되는 주소는 0x80 + 4에 해당되는 0x84가 된다.
"cpi r26, 0x42", "cpc r27, r17"는 X레지스터 값이 0x142와 비교하는 코드이다. .data 섹션의 크기가 0x42이므로 .data 섹션의 마지막 주소가 0x0142-1이 되고, X레지스터가 이 값보다 작으면 LMA에 있는 데이터를 VMA로 복사하는 동작을 반복하게 된다.
"brne .-10"는 앞에서 비교한 값이 같지 않으면 PC값보다 10작은 주소로 branch하는 코드이다. 역시 이코드가 있는 주소가 0x88이고, 이 때의 PC값은 0x8A가 되고, 10보다 작은 주소는 0x80이 된다. 즉, 0x80으로 점프하게 된다.
"lpm r0, Z+", "st X+, r0"는 Z레지스터가 가르키는 주소에서 한바이트 값을 읽어서 r0에 넣어주고, 다시 r0의 값을 X레지스터가 가르키는 주소에 써주는 동작을 수행한다.
그런다음 Z레지스터와 X레지스터의 주소값을 각각 1증가 시킨다. 즉, 다음 주소를 가르키도록 만든다.
이 과정을 반복하면 ROM에 있는 .data 섹션에 있는 값들을 RAM으로 복사하는 동작을 수행하게 된다.
이어서 __do_clear_bss로 시작되는 심볼의 코드를 분석해 보도록 하겠다. 역시 map 파일에서 .bss 섹션의 정보를 찾아본다.
.bss 섹션은 0x0142에서 시작되고, 그 크기는 0x20임을 알 수 있다. 이러한 정보를 가지고 코드 분석을 시작해 본다.
"ldi r18, 0x01"은 .bss 섹션의 마지막 주소를 비교할 때 사용된다.
"ldi r26, 0x42", "ldi r27, 0x01"는 X레지스터에 .bss 섹션의 시작 주소를 넣어 주는 코드이다. 즉, 0x142의 주소를 넣어 주는 것이다.
"rjmp .+2"는 일단 0x94번지로 점프하는 코드이다.
"cpi r26, 0x62", "cpc r27, r18", "brne .-8"는 X 레지스터의 값이 0x162와 같은지 비교해서 같지 않으면 0x92로 점프하게 된다. .bss 섹션이 0x142에서 시작되고 그 크기가 0x20이므로 .bss 섹션의 마지막 주소는 0x161이 된다.
0x92번지에는 다음과 같은 코드가 있다. "st X+, r1" X레지스터가 가르키는 주소에 r1에 있는 값을 써준다. 이미 얘기했듯이 r1은 항상 0의 값을 가지고 있어야 하므로 X가 가르키는 곳에 0을 써주는 것이다. 이러한 동작을 반복하면 .bss 섹션의 영역이 모두 0으로 초기화 되는 것이다.
.data 섹션의 복사와 .bss 섹션의 초기화가 끝나면 드디어 main() 함수로 진입하게 되는 것이다.
'심화' 카테고리의 다른 글
ISR(Interrupt Service Routine) (6) | 2016.10.02 |
---|---|
startup.S (5) | 2016.10.01 |
Linker Script - 링커 스크립트 (3) | 2016.10.01 |
패킷 통신 - Request/Reply (0) | 2016.08.12 |
패킷 통신 - Message ID (0) | 2016.08.12 |