임베디드 레시피

Egloos | Log-in





링커 스크립트(Linker Script) 파일이 뭐죠?


뭘 알아야 이해를 하지
일반적으로 Startup.s와 main.c 소스를 컴파일을 하면 오브젝트 파일이 생성되죠. 이 오브젝트 파일이 모여서 하나의 바이너리로 만들어 지도록 도와주는 역할을 한답니다. 컴파일을 하고 나면 오브젝트 파일이 만들어 질 때 4개의 독립된 영역으로 나누어 진답니다. 코드 영역, 데이타 영역, 힙 영역, 스택 영역으로 나누어 지고, 세부 사항에 대해 이제부터 알아 보도록 할게요.

예를 들어, main.c 프로그램을 만들었어요.



▶코드 main.c


#include
int han_int, byul_int=15; ;- a
int main() ;- b
{ ;- c
int good; ;- d
char *house; ;- e

han_int=10; ;- f
han_int++; ;- g
good=good+5; ;- h
good++; ;- i

house=malloc(10); ;- j
return 0;
}


코드 영역은 프로그램 명령(Instructions)으로 구성되어 있고 임베디드 프로그램이 실행할 때 변하지 않는답니다. 씨 컴파일러에 의해 암 코어가 이해하는 기계어 명령으로 바뀌게 됐을 때 하드웨어 디버거로 보면 다음과 같아요.





▶코드 main.c


| int main()
6| {
SR:00000314|E1A0C00D__main:_____mov_____r12,r13
SR:00000318|E92DD800 stmdb r13!,{r11-r12,r14,pc}
SR:0000031C|E24CB004 sub r11,r12,#0x4
SR:00000320|E24DD008 sub r13,r13,#0x8
| int good;
| char *house;
|
10| han_int=10;


다음으로는 데이타 영역입니다. main.c 예제에서 a line에 데이타가 선언되어 있어요. 여기서 han_int는 초기화 되지 않는 데이타이고, hyul_int는 초기화된 데이타입니다. 이 두 개의 데이타는 각각 다른 영역에 저장이 됩니다. 다음은 힙 영역인데 main.c 예제에서 j line에서 malloc함수를 사용해서 house 데이타에 동적 메모리를 할당했어요. house[10] 공간 만큰 메모리를 확보 했답니다. 이제 마지막으로 스택 영역인데 주로 자동 변수, 함수로 파라메타 보냄, 복귀 번지 저장 등을 해요. main.c 예제에서 d line에서 good 데이타를 정의했는데, 이 데이타는 자동 변수이며 메모리 공간은 스택에서 할당이 된답니다.
이 모든 과정을 정리하면 아래 그림과 같아요.

 


오브젝트 파일이 하나가 아니라 두 개라면 Startup.o와 main.o를 링크해야 최종 바이너리를 만들 수 있겠죠. 링크에서 가장 중요한 부분 바로 링크 스크립트 파일이랍니다.
링크 스트립트는 위에 그림에 나와 있는 섹션 영역의 start와 end 영역을 개발자가 지정하도록 되어 있답니다. 즉, 개발자가 링크 스크립트를 만들 때 임베디드 제품의 메모리 맵을 확인한 다음 각 섹션 영역을 어떻게 지정할지 결정한 후 만드시면 된답니다. 일반적으로 .text_start와 .data_start 그리고 .bss_start 주소를 지정해 주면 됩니다.
두 개의 오브젝트 파일을 하나의 바이너리로 만들 때 링크는 해당 오브젝트에서 .text와 .data를 따로 분리하고 최종 바이너리로 만들 때는 같은 영역으로 몰아서 만들어 낸답니다.

 



arm-elf-size 명령어를 사용하면 해당 섹션 영역의 사이즈를 알 수가 있답니다.

CMD> arm-elf-size general.elf
   text        data     bss      dec      hex      filename
   8086    1992     244   10322    2852   general.elf

어떤 컴파일러를 사용하느냐에 따라 스케트 파일 또는 링크 스크립트 파일이라고 부르는데, 하는 역할은 대부분 비슷하답니다. 그럼 링크스크립트를 만들 때 무엇을 참고로 해서 만들어야 할까요? 또 만드는 방법은 어떻게 될까요?

세 개의 단계로 나누어 설명 드릴께요.
첫 번째, 엠시유 데이타 시트를 열어서 메모리 맵을 확인하셔야 해요. 데이타 시트를 열어서 메모리 맵을 보면 플래시 메모리 시작 주소와 플래시 메모리 사이즈 그리고 에스디램 시작 어드레스와 에스디램 사이즈가 얼마인지 확인을 하세요. 예를 들어, 아래와 같은 메모리 맵이 있다면
 



플래시 메모리의 시작 주소는 0x0000 0000가 됩니다. 플래시 메모리 사이즈는 타겟 보드에서 플래시 메모리를 찾은 다음 플래시 메모리 이름을 인터넷에서 찾아보면 사이즈가 나온답니다. 플래시 메모리는 노어 플래시를 사용하고 4MB라고 가정을 할게요. 다음으로 에스디램의 시작 주소는 0x3000 0000가 되고 에스디램 사이즈는 타겟 보드에 있는 에스디램을 찾은 후 에스디램 이름을 인터넷에서 검색을 하면 에스디램 사이즈가 나온답니다. 그럼 저희들은 에스디램 사이즈가 32MB라고 가정을 하겠습니다.

[노어 플래시 메모리]
시작 주소: 0x0000 0000
마지막 주소: 0x003F FFFF

[에스디램]
시작 주소: 0x3000 0000
마지막 주소: 0x31FF FFFF

두 번째, 메모리 영역을 어떻게 나눌 것인지 결정해야 하죠,
플래시 메모리에는 코드 영역과 데이타 영역을 지정하고, 에스디램 영역은 주로 BSS(Block Started Segment)의 시작 주소를 지정한답니다. 코드 영역의 시작 주소(.text_start)는 노어 플래시 메모리의 시작 주소로 많이 지정하기 때문에 .text_start는 0x0000 0000로 하죠. 다음으로 데이타 영역인데 보통 코드 영역의 마지막 주소+0x4부터 데이타 영역의 시작주소(.data_start)로 지정한답니다. 그러나 개발자들은 프로그램 코드의 마지막 주소를 알 수가 없기 때문에 .data_start 주소를 지정하지 않고 컴파일러가 자동으로 .text_end 주소+0x4번지로 지정하게 합니다. 하지만 개발자가 직접 주소를 지정한다면 arm-elf-size 명령어로 text 의 사이즈를 확인해 보니 8086KB 정도였으니, 넉넉하게 .data_start는 0x0001 0000라고 하죠. 다음으로 .bss_start 시작 주소인데, 주로 에스디램의 시작주소 이후로 많이하는 편입니다. 에스디램 영역의 시작 주소 03000 0000이니깐 .bss_start는 0x30020000 지정하지요. 모두 끝났습니다.
이제 링크 스크립트를 컴파일 옵션에 넣고 컴파일을 해 보도록 할게요.



▶코드 makefile


#### Option Definition ####
AFLAGS = -marm9tdmi -EL -M --gdwarf2
CFLAGS = -B$(GCC_EXEC_PREFIX) -g -gdwarf-2 -O0 -c -mcpu=arm9tdmi -mlittle-endian -mapcs-frame -mno-apcs-stack-check
LFLAGS = -eEVT -nostartfiles -Xlinker --script=linker.ld -lc




▶코드 linker.ld


OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
__TEXT_START__ = 0x00000000;
__DATA_START__ = 0x00010000;
__BSS_START__ = 0x30020000;


하드웨어 디버거로 링크 스크립트 파일에서 지정한 주소를 확인해 볼 수가 있어요.

___address___to_________|path\section___________________________|acc|init|physical
    P:00000000--00001F8F|\\general\.text                         |R-X|L-  |
                      P:00001F90|\\general\.glue_7                     |R-X|L-  |
                      P:00001F90|\\general\.glue_7t                    |R-X|L-  |
    D:00001F90--00001F95|\\general\.rodata                      |R--|L-  |
    D:00010000--000107C7|\\general\.data                        |RW-|L-  |
    D:30020000--300200F3|\\general\.bss                          |RW-|--  |


속성 권한이 보이고 코드영역, 데이타 영역, BSS 영역을 쉽게 구분할 수가 있네요.

이제 설명이 모두 끝났나요? 그런데 뭔가 이상하지 않나요? 빠진 설명이 있지 않나요?
힙 영역과 스택 영역의 주소를 지정에 대한 부분이군요. 링크 스크립트에서는 지정한 곳이 없었는데 이상하군요. 어디서 지정할까요?
힙 영역은 링크 스크립트에서 따로 지정하지 않아도 된답니다. malloc 함수 자체가 자동으로 메모리 영역을 잡아 주는데 .bss_end+4 주소 이후로 알아서 잡을 수가 있기 때문이죠.

하드웨어 디버거로 main.c 예제의 j line까지 실행했을 때 house 주소를 확인해 봤어요.

(auto char *) [SD:0x3003D7E8] house = 0x30020100

이 주소는 .bss_end 주소인 0x300200F3 이후라는 것을 알 수가 있지요.
마지막으로 스택 영역인데, 스택은 이상하게도 .stack_start와 .stack_end 주소가 다른 영역과 다르게 설정되는 것을 볼 수가 있습니다. 대부분의 start 주소가 end 주소보다 작은데 스택 영역만 큽니다. 왜 그를까요?
암 프로세서에서 스택은 상위 주소에서 하위 주소로 증가를 하기 때문이랍니다. 이제 스택 영역의 시작 주소가 어디지 지정하는지 살펴 봐야겠죠. 바로 startup.s 파일에서 지정한답니다.





▶코드 start.S


@STACK Initialization
STACK_BASEADDR=0x30040000
SVCStack=(STACK_BASEADDR-0x2800)

..<생략>...

BIC r0,r0,#MODEMASK|NOINT
ORR r1,r0,#SVCMODE
MSR cpsr_cxsf,r1
LDR sp,=SVCStack
MOV pc,lr




 야호~ Season1에 Scatter Loading File과 같은 겁니다. Scatter Loading File이 Linker Script의
           한 종류라고 보심 된다구요. 인생은 돌고 돈다. 구요.

by 히언 | 2010/06/06 22:31 | SOTO Story | 트랙백 | 핑백(1) | 덧글(7)

Linked at 친절한 임베디드 시스템 개발자.. at 2010/06/06 22:39

... sp; 302 Make file이 뭐죠? 303 링커스크립트파일(Linker Script file)이 뭐죠? 304 변수 선언이 중요한가요? 305 소스 수정 ... more

Commented by ruring at 2010/06/08 10:29
1등! 눈팅만하고가요ㅠ_ㅠ 프로잭트에 맵핑해야되는대 ㅠ_ㅠ 꼬이고있어서 ㅠ_ㅠ
Commented by soto at 2010/06/08 12:53
^^ 잘 되실 겁니다.
Commented by at 2010/06/09 08:07
잘 보고 갑니다.
항상 스고이데스네~~~
Commented by soto at 2010/06/09 20:30
공부하시는데 도움이 되셨나요?
Commented by highseek at 2010/06/12 23:00
잘 보고 갑니다.

근데 예시 프로그램이...(...)
Commented by soto at 2010/06/21 15:04
예시 프로그램이..( ) <-- 뭘까요? 궁금하군요..ㅎㅎ
Commented by highseek at 2010/06/21 15:16
이상하잖아요;

good 변수에는 뭐가 들어갈 지 예측도 못하겠고..

house는 메모리 릭이고..(...)
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.

◀ 이전 페이지          다음 페이지 ▶