체크섬은 데이터의 무결성을 확인하기 위하여 사용되어 집니다. IAR Embedded Workbench에서 사용자 지정 구간의 무결성을 확인하기 위한 체크섬 생성 및 기록 기능이 지원됩니다. 이번 글에서는 체크섬 사용에 대한 프로젝트 설정 및 응용 방법에 대하여 살펴보도록 하겠습니다.


고성용 이사 / IAR 시스템즈
Sung-Yong.Ko@iar.com


체크섬 계산
IAR Embedded Workbench에 통합되어있는 ielftool 도구에 의하여 사용자가 지정한 특정 주소범위에서 체크섬을 계산됩니다. 체크섬의 계산은 ELF 심볼 이미지에 삽입되며 응용 프로그램은 체크섬을 이용하여 무결성을 확인합니다.
응용 프로그램의 무결성확인을 위한 체크섬을 사용하려면 다음의 사항을 수행해야 합니다.

- ielftool에 의하여 계산되어진 체크섬의 저장 공간, 이름, 크기 등이 정해져야한다.
- ielftool에서 체크섬을 계산하기위한 알고리즘 선택과 그에 상응하는 코드가 응용 프로그램에 삽입 되어있어야 한다.
- 체크섬을 이용하여 무결성을 확인할 메모리 범위가 결정 되어야하고 ielftool과 소스 코드에 설정 되어야 한다.

체크섬 설정
먼저 체크섬의 사용 설정을 위하여 프로젝트의 Options 에서 Linker Category 선택 후 Checksum 탭으로 이동합니다. 사용하지 않는 코드 메모리 영역 채우기(Fill unused code memory)를 선택합니다.
Fill pattern 항목에 사용되지 않는 코드 메모리에 채울 패턴을 입력합니다. 기본 값은 0xFF입니다. 다음으로 체크섬이 계산될 주소 범위를 설정합니다. 시작 주소와 끝 주소를 설정합니다. (그림 1)

▲ 1

체크섬 생성을 위한 ‘Generate checksum’을 체크합니다. 다음 활성화되는 체크섬을 생성하기 위한 정보를 설정합니다. (그림 2)

▲ 2


자세한 체크섬의 항목 설정은 아래의 표.1을 참조하십시오.
▲ 표1

위의 설정을 완료 후 프로젝트를 Build 하시면 Checksum이 계산되어 코드 데이터 영역의 가장 끝부분에 추가되어 집니다.

체크섬의 위치는 .map 파일에서 가능합니다. 프로젝트 Options에서 Linker Category의 List 탭에서 Generate linker map file을 체크한 후 Build합니다. 프로젝트의 Output 그룹에 .map 이 생성 됩니다. map 파일을 확인하시면 ENTRY LIST에서 체크섬 관련 심볼들의 위치와 크기를 확인 할 수 있습니다.(그림 3)

▲ 3


체크섬 위치 변경
체크섬은 ielftool에 의하여 생성된 후 기본적으로 ROM 영역의 메모리 가장 끝부분에 위치하게됩니다. 하지만 사용자의 임의 설정에 따라 체크섬의 위치를 변경 할 수 있습니다.

응용 프로그램 코드의 시작에 위치 시키기

define block CHECKSUM { readonly section .checksum };
place in ROM_region { readonly, first block CHECKSUM };

위의 코드를 사용하는 .icf파일에 추가합니다. (그림 4)

▲ 4

※이 경우, 응용 프로그램의 시작은 interrupt vector와 checksum 값 다음에 위치하게됩니다. 일반적으로 interrupt vector는 주소 0x0에 위치합니다.

응용 프로그램 코드의 끝에 위치 시키기

define block CHECKSUM { readonly section .checksum };
place in ROM_region { readonly, last block CHECKSUM };

위의 코드를 사용하는 .icf파일에 추가합니다. (그림 5)

▲ 5

임의의 주소에 위치 시키기

place at address mem:[사용자 지정 주소] { readonly section .checksum };

위의 코드를 사용하는 .icf파일에 추가합니다. (그림 6)

▲ 6

Ex) place at address mem:0x0000aaaa { readonly section .checksum };

※__checksum 심볼은 체크섬 계산 시작과 끝의 범위 밖에 위치하기를 권장합니다. 범위 내에 존재한다면 체크섬 계산 시 변경되지 않은 __checksum 위치의 값을 포함하여 계산 후 __checksum 위치에 기록합니다. 이 후, 무결성 확인을 위하여 체크섬 계산 시작과 끝의 범위의 체크섬을 계산하면 변경된 __checksum 값 때문에 저장되어있던 체크섬 값과 다르게 됨으로 무결성을 확인 할 수 없습니다.

체크섬 활용하기
체크섬을 활용하여 응용 프로그램의 시작 시 다운로드 되어있는 응용 프로그램의 무결성 확인이 가능합니다. 이는 응용 프로그램의 동작에 대한 신뢰성의 지표로 사용 가능합니다. 응용 프로그램이 정상적으로 다운로드 되어있으며 다운로드된 응용 프로그램의 변형이 없다는 것을 확인 가능합니다.
활용 방법은 간단합니다.

※프로젝트의 Options에서 Checksum generation 활성화를 합니다. Start Address와 End Address는 map파일을 참조하여 코드의 시작주소와 끝주소로 지정합니다.
※__checksum_begin, __checksum_end 심볼을 참조하여 응용 프로그램 시작 시 체크섬을 계산합니다. 이때, 프로젝트 Options에서 활성화하였던 Checksum generation 방법과 동일한 방법으로 구현되어있어야 합니다.
※__checksum을 참조하여 저장되어있는 체크섬과 응용 프로그램의 시작시 계산된 체크섬을 비교합니다.
※ 만약, 저장되어진 체크섬과 응용 프로그램 시작시 계산된 체크섬이 다르다면 다운로드 되어진 응용프로그램의 무결성을 보장할 수 없다고 가정하게 됩니다.

Basic version
C로 구현된 가장 기본적인 형식은 아래와 같습니다.

typedef unsigned char * ptr;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint32_t;


uint16_t
crc_impl(uint16_t sum, ptr p, uint32_t len)
{
while (len--)
{
uint8_t byte = *p++;
for (int i = 0; i < 8; ++i)
{
uint16_t osum = sum;
sum <<= 1;
if (byte & 0x80)
sum |= 1 ;
if (osum & 0x8000)
sum ^= 0x1021; // the polynomial
byte <<= 1;
}
}
return sum;
}

 

 

static const uint16_t t[256] = {
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,0x8108,0x9129,0xa14a,
0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,
0x72f7,0x62d6,0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,0x2462,
0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,0xa56a,0xb54b,0x8528,0x9509,
0xe5ee,0xf5cf,0xc5ac,0xd58d,0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,
0x46b4,0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,0x48c4,0x58e5,
0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,
0x9969,0xa90a,0xb92b,0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,0x6ca6,0x7c87,0x4ce4,
0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,
0x8d68,0x9d49,0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,0xff9f,
0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,0x9188,0x81a9,0xb1ca,0xa1eb,
0xd10c,0xc12d,0xf14e,0xe16f,0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,
0x6067,0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,0x02b1,0x1290,
0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,
0xe54f,0xd52c,0xc50d,0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,0x26d3,0x36f2,0x0691,
0x16b0,0x6657,0x7676,0x4615,0x5634,0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,
0xb98a,0xa9ab,0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,0xcb7d,
0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,0x4a75,0x5a54,0x6a37,0x7a16,
0x0af1,0x1ad0,0x2ab3,0x3a92,0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,
0x8dc9,0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,0xef1f,0xff3e,
0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,
0x3eb2,0x0ed1,0x1ef0
};
uint16_t crc(uint16_t sum, ptr p, uint32_t len)
{
while (len--)
sum = t[(sum >> 8) ^ *p++] ^ (sum << 8);
return sum;
}

 

ROM byte table version
요즘 테이블을 이용한 CRC 구현 방식을 가장 많이 사용하고 있습니다. ielftool과 XLINK에서도 테이블 방식을 사용하고 있습니다.

RAM byte table version
프로그램에서 테이블을 생성하는 것도 물론 가능합니다. 이런 경우에는 ROM의 사용 공간을 줄일 수 있지만 RAM의 사용 공간이 늘어나게 됩니다.

ROM nibble table version
앞의 테이블 방식은 256개의 항목을 사용하며, 각 항목 당 2바이트로 구성되면 512바이트의 테이블 공간이 필요합니다. 더 작은 단위를 생각해 보면 4비트, 즉 니블(nibble)이 될 것입니다. 니블은 16개의 항목을 가지므로 테이블의 항목을 줄일 수 있습니다.


void construct_crc_table(uint16_t poly, void * space)
{
uint16_t * crc_table = (uint16_t *)space;
for (uint16_t i = 0; i < 256 ; ++i)
{
uint16_t r = i << 8;
for (int j = 0 ; j < 8 ; ++j)
r = (r & 0x8000) ? (r << 1) ^ poly : (r << 1);
crc_table[i] = r;
}
}
uint16_t crc(void * table, uint16_t sum, ptr p, uint32_t len)
{
uint16_t * t = (uint16_t *)table;
while (len--)
sum = t[(sum >> 8) ^ *p++] ^ (sum << 8);
return sum;
}



static const uint16_t t[] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
};
uint16_t crc_nibble_rom(uint16_t sum, ptr p, uint32_t len)
{
while (len--) {
// hi nibble
sum = t[(sum>>12)^(*p >> 4)]^(sum<<4);
// lo nibble
sum = t[(sum>>12)^(*p++ & 0xF)]^(sum<<4);
}
return sum;
}


void construct_nibble_table(uint16_t poly, void * space)
{
uint16_t * crc_table = (uint16_t *)space;
for (uint16_t i = 0; i < 16 ; ++i) {
uint16_t r = i << 12;
for (int j = 0 ; j < 4 ; ++j)
r = (r & 0x8000) ? (r << 1) ^ poly : (r << 1);
crc_table[i] = r;
}
}
uint16_t crc_nibble_ram(void * table, uint16_t sum, ptr p, uint32_t len)
{
uint16_t * t = (uint16_t *)table;
while (len--) {
// hi nibble
sum = t[(sum>>12)^(*p >> 4)]^(sum<<4);
// lo nibble
sum = t[(sum>>12)^(*p++ & 0xF)]^(sum<<4);
}
return sum;
}


RAM nibble table version
CRC32등과 같은 체크섬 값을 생성하는 자세한 내용은 아래 링크를 참조하십시오.
또한 체크섬 구현에대한 자세한 사항은 예제에서 확인 가능합니다. 아래의 링크에서체크섬에 대한 예제 및 자료를 확인하십시오.
(http://netstorage.iar.com/SuppDB/Public/SUPPORT/007098/Check_Sum_511.zip
http://supp.iar.com/Support/?Note=26457&=OK)


맺음말
다양한 방식을 이용한 체크섬은 Critical system에서 코드의 신뢰성 확보와 신뢰성 있는 코드의 동작을 보장하는 방법 중 하나로 유용하게 사용 됩니다. 또한 체크섬을 활용하여 안전한 코드와 데이터를 구축하시기 바랍니다.
감사합니다.


이 기사를 공유합니다
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지