전역 변수 선언시 초기 값을 할당해서 선언하면 해당 전역 변수가 점유 하는 공간이 할당된됨
BSS영역
초기화하지 않은 전역 변수가 있음
초기화 하지 않은 전역 변수이므로 빈드 완료되어 생성된 바이너리 파일에는 심벌과 크기만 들어 있음
해당 프로젝트에서는 text 영역의 크기를 1MB (0x00000000 ~ 0x000FFFFF), data영역과 bss영역의 할당은 각 모드별로 1MB씩 그냥 할당한다. (크기를 그렇게 신경 안쓰고 일단 할당) RTOS의 위에서 동작할 task stack 영역도 필요하다. 총 64개의 task를 지원할 것이고 task마다 1MB씩 stack 영역을 할당하므로 총 64MB 할당한다. 그 다음 전역 변수용으로 1MB를 할당한다. 남은 공간은 heap으로 사용한다.
부팅 초기시 exeption handler로 처리되며 해당 부분을 위해 Entry.S를 위와 같이 수정하자. 코드는 exception vector table에 각 handler로 jump 하는 코드만 작성되었다. 각 handler의 동작은 구현되지 않고 무한루프로 동작된다.
$ cd project_directory
$ make clean && make all
$ make debug
$ (다른 console에서) make gdb
(gdb) target remote:1234
(gdb) continue
컨트롤 + C로 정지
(gdb) info registers
위의 코드는 .h 파일 두개를 추가 후 SP 설정 및 초기화를 위해 reset_handler 부분을 수정한 것이다. 모든 동작 모드를 한 번씩 순회하면서 스택 꼭대기 메모리 주소를 SP에 설정하는 코드이다. SP에 해당모드의 시작 주소를 넣는 것이 아닌 TOP의 주소를 넣는 것은 스택이 높은 주소에서 낮은 주소로 자라는 특징을 가지고 있기 때문이다.( 하지만 이것은 설계자 마음이다. 꼭 Stack이 위에서 아래로 heap은 밑에서 위로 자라는 방향은 아니다.)
ASM 컴파일러를 산용해서 컴파일 할 경우 전처리기가 없기 때문에 .h파일을 읽어올 수 없다. 따라서, 컴파일러를 gcc로 변경한다. 추가적으로 .h 파일들이 존재하는 include 디렉토리도 추가한다. 위의 makefile 내용으로 수정한다.
make all을 수행한 결과이다. 이후 QEMU <-> gdb를 이용해 sp의 메모리 위치를 확인해 보자.
34번째 라인까지 실행되고 첫번째 SVC 동작 모드 스택이 설정된다. SVC 모드 스택은 0x00300000 ~ 0x003FFFFF까지 메모리 주소 영역이다. 스택과 스택 경계에 4B를 비워두도록 크기를 설정 했으므로 0x003FFFFC여야 한다. 위 그림의 info register 결과에서 볼수 있듯이 sp의 값이 해당 값을 가리키고 있음을 알수 있다.
CPSR을 보면 마지막 바이트가 0xd3이다. 이것은 11010011(2)이며, 마지막 하위 5비트만 보면 10011이다. 이것은 16진수로 0x13을 나타낸다. SVC 동작 모드를 설정하는 값이다.
ARM의 CPSR 레지스터의 bit 필드를 나타낸다. 하위 5bit는 해당 모드의 동작 상태를 나타내며 0b10011은 SVC를 나타낸다. 이렇게 모든 모드들에 대해서 sp를 정해 준다. 실제 보드에서는 sp뿐만이 아닌 하드웨어 시스템 클럭 설정, 메모리 컨트롤러 초기화 같은 일들도 필요하다. 이것은 추후에 Cortex-M3를 공부하며 진행하도록 하겠다.
BL main을 추가해준다. 이것을 통해 asm의 동작이 끝난후 C언어의 main문으로 함수가 점프하게 된다.
build/Mainc.C를 생성 후 아래 내용 입력
#include "stdint.h"
void main(void)
{
uint32_t* dummyAddr = (uint32_t*)(1024*1024*100);
*dummyAddr = sizeof(long);
}
BL 브랜치 명령으로 점프 하려면 점프 대상 레이블이 같은 파일 안에 있어야 한다. 다른 파일에 있다면 링커가 링킹할 수 있도록 레이블을 .global로 선언해야 한다. 컴파일러는 C언어 함수 이름을 링커가 자동으로 접근할 수 있는 전역 심벌로 만든다. 전역 심볼은 어셈블리로 사용할떄 .global 지시어로 선언하고 C언어로 할때는 extern으로 사용한다. 반대로 어셈블리에서 .global로 선언한 이름은 c언어에서 함수 호출로 진입할 수 있다.
make all 수행후 gdb를 통해 실행파일을 run한 후 정지 시키자. 그럼 펌웨어가 main() 함수를 실행하고 나면 다시 reset handler로 돌아가서 무한루프는 도는 것을 확인할 수 있다. 그래서 '컨트롤+c' 명령을 입력하기 전까진 종료되지 않는다. 이후 x/8wx 메모리 주소 명령을 입력한다. 이 명령은 입력된 메모리 주소부터 8개를 4바이트씩 16진수로 값을 출력하라는 명령이다. 그래서 0x6400000 메모리 주소의 값을 4바이트씩 8개 출력을한다. 출력 값은 4가 나온다.
$sudo apt install gcc-arm-none-eabi //arm용 cross 컴파일러 설치
$sudo apt install qemu-system-arm //arm아키텍처를 가상화해서 사용하기 위한 qemu 설치
$mkdir os_project
$cd os_project
$mkdir boot
$cd boot
OS프로젝트 디렉토리를 하나 생성후 boot에 필요한 코드들을 포함하기 위해 boot 디렉토리를 생성한다.
boot 디렉토리 내에서 Entry.S를 생성한다.
.text //text ~ end까지는 text section을 의미함
.code 32 //명령어의 크기가 32bit임을 뜻함
//.global은 C언어에서 extern과 같은 역할
.global vector_start //vector_start의 주소와 vector_end의 주소를 외부 파일에서 심볼로 읽을수 있게 설정
.global vector_end
vector_start:
MOV R0, R1 //동작 수행
vector_end:
.space 1024,0 //해당 위치부터 104바이트를 0으로 채우기
.end //text section 끝
실행파일 구조
하나의 executable file이 완성되면 linking 등 여러가지 작업에 의해 파일이 생성된다. 이떄 .text, .data, .bss 등 다양한 section이 존재하게 되며 Entry.S의 .text ~ .end 까지를 text section이라 한다. .text 섹션에는 code들이 존재한다.
arm-none-eabi-object: object파일에서 바이너리 파일만 추출한다. object 파일에는 심볼 정보 등이 포함되어 있다.
1) 0001 e1a0 : MOV R0, R1를 의미
2) * 계속 반속 되는 것을 의미
3) 4B align이므로 0000404가 보여짐
실행파일을 만들기 위해서는 다수의 object 파일들을 묶어야 한다. 이 역할을 링커가 하게 되며 링커가 어떤 정보를 바탕으로 해당 동작을 수행하게 되는데 이때 정보들을 모은 파일을 링커 스크립터라고 한다. 하드웨어 환경에 맞춰 section(text, data, rodata 등)을 배치해야 한다.
$ cd ~/os_project/
$ vi navilos.ld
링커 스크립터를 만들자.
ENTRY(vector_start) //시작 위치의 심벌을 저장
SECTIONS //3 ~ 19줄 까지의 블록이 섹션 배치 설정 정보를 가짐
{
. = 0x0; //첫 번째 섹션이 메모리 주소 0x00000000에 위치 한다는 것을 나타냄
.text : //text 섹션의 배치 순서를 지정
{
*(vector_start) //0x000000000에 reset_vector가 위치해야 함
*(.text .rodata)
}
.data :
{
*(.data)
}
.bss :
{
*(.bss)
}
}