본문 바로가기
임베디드 개발/TMS320F2838x (C28x)

TMS320F28388D ] 링커커맨드 파일과 컴파일러 섹션

by eteo 2024. 8. 16.

 

 

 

 

 

링커커맨드 파일과 컴파일러 섹션에 대한 이해...

 

 

Compiler Sections

 

C 코드는 컴파일러를 통해 어셈블리 코드로 변환되고 어셈블리 코드는 어셈블러를 거쳐 오브젝트 파일이 된다. 모든 C 코드는 섹션이라고 불리는 여러 부분으로 분리될 수 있는데 컴파일러는 컴파일 과정에서 C 코드를 분석하여 각 섹션에 들어갈 데이터를 구분한다. 그리고 오브젝트 파일의 구성을 보면 각 섹션별로 데이터가 저장되며 이외에도 변수, 함수 등의 심볼 정보를 가지고 있는 심볼 테이블이 포함된다.

 

이러한 섹션의 이름에는 .text, .stack, .bss 등 공통적으로 사용되는 것들도 있지만 툴체인마다 섹션의 이름이 약간씩 다르거나 추가적인 섹션이 있을 수 있다. C28x 컴파일러의 섹션은 다음과 같다.

 

Section Name Description Section Type Link Location
.text 모든 실행 코드와 컴파일러 생성 상수가 들어있으며 읽기 전용이다. Initialized FLASH
.cinit 초기값이 있는 전역 및 정적 변수를 초기화하기 위한 초기값 테이블이 들어있으며 읽기 전용이다. Initialized FLASH
.const 문자열 리터럴 및 const 한정자로 정의된 상수가 포함되며 읽기 전용이다. Initialized FLASH
.switch 스위치 명령문에 대한 테이블이 들어있다. Initialized FLASH
.bss 초기값이 없는 전역 및 정적 변수에 대한 공각을 예약한다. Uninitialized RAM
.stack 스택공간으로 함수 호출 시 지역변수를 저장하고 함수 호출의 반환 주소 등을 관리하기 위해 사용된다. Uninitialized RAM
.sysmem 동적 메모리 할당에 사용되는 공간이다. Uninitialized RAM, C28x 스택 포인터는 16비트이므로 .stack 영역은 64k word 이하여야 한다.
.data 초기값이 있는 전역 및 정적 변수에 대한 공간을 예약한다. Initialized RAM
.reset Reset Vector로 BOOT ROM에 있는 _c_init00 루틴 주소가 들어있다. Initialized 고정주소, 링커 커맨드 파일에서 Dummy Section을 의미하는 TYPE = DSECT로 설정한다.

 

 

 

이 중에서 .stack과 .sysmem 섹션을 제외한 나머지 섹션은 빌드 시점에 그 크기와 위치가 결정되고 .stack과 .sysmem 섹션은 그 시작지점은 정해져 있지만 크기와 변수의 위치 등은 런타임에 유동적으로 결정된다.

 

 

 

다음은 C 코드를 컴파일러 섹션별로 분리해본 것이다.

 

 

 

 

 

 

Linker Command 

오브젝트 파일은 메모리 주소 정보를 가지고 있지 않으며, 재배치 가능한 형태로 저장된다. 링커 커맨드 파일은 이러한 여러 오브젝트 파일을 결합하여 실행 가능한 파일을 생성할 때 각 코드와 데이터 섹션을 적절한 메모리 위치에 배치하도록 하는 개발자의 지시 사항이 적힌 파일이다. 링커 커맨드 파일의 작성법은 사용하는 툴체인에 따라 다르며, 각자 정해진 양식과 문법이 있다.

 

 

 

 

Linker Command File 예시

 

링커 커맨드 파일은 크게 대상 시스템의 메모리 구성을 링커에게 설명하는 MEMORY { } 부분과 어떤 섹션을 어떤 메모리 영역에 어떻게 배치할지 지정하는 SECTIONS { } 부분으로 나뉜다.

 

MEMORY
{
   RAMM             : origin = 0x0001B1, length = 0x000647
   RAMLS            : origin = 0x008000, length = 0x004000
   BEGIN            : origin = 0x080000, length = 0x000002
   FLASH            : origin = 0x080002, length = 0x03FFEE
   RESET            : origin = 0x3FFFC0, length = 0x000002
}

SECTIONS
{
   codestart        : > BEGIN, ALIGN(8)
   .text            : > FLASH, ALIGN(8)
   .cinit           : > FLASH, ALIGN(8)
   .switch          : > FLASH, ALIGN(8)
   .const           : > FLASH, ALIGN(8)
   .reset           : > RESET, TYPE = DSECT
   .stack           : > RAMM
   .bss             : > RAMLS
   .data            : > RAMLS
   .sysmem          : > RAMLS
}

 

 

 

 

 

 

.cinit 섹션에 대해서

 

위에서 툴체인마다 섹션의 이름과 정의가 약간씩 다를 수 있다고 설명했는데 그 중 하나가 바로 .cinit이다. 보통 초기값이 있는 전역 변수는 .data 섹션으로 잡히고 .data 섹션은 FLASH에 로드되고 RAM에 복사된다. 그래서 .elf 파일을 분석해 어떤 글로벌 변수의 초기값을 확인하려면 심볼 테이블에서 해당 변수의 주소를 찾고 .data 섹션에서 해당 주소를 보면 아래처럼 그 초기값을 알 수 있다.

 

someValue의 초기값을 0x12345678으로 설정했다.

 

 

그런데 C28x에선 초기값이 있는 전역변수의 RAM 공간을 확보하는 .data 섹션과 그 초기값을 가지고 있는 .cinit 섹션이 분리가 되어있기 때문에 .out 파일을 분석해서 글로벌 변수의 초기값이 위치한 곳과 매핑하기가 어렵다. 아래 사진을 보면 .cinit 섹션의 시작 위치가 817f0이고 someValue 변수의 초기값은 오프셋 e 위치에 저장되어 있다. 처음엔 심볼테이블에서 .data 섹션만 출력하고 주소순으로 오름차순 정렬해서 오프셋을 찾으면 .cinit에 저장된 위치를 알 수있을거라 생각했는데 그것도 아니어서 어떤 방식으로 .cinit이 팩킹되는지 궁금하다.

 

역시 someValue의 초기값을 0x12345678으로 설정했다.