본문 바로가기
임베디드 개발/펌웨어

인텔 hex 포맷 이해하기 (+ Intel hex to Bin 변환)

by eteo 2024. 4. 23.

 

 

 

 

인텔 hex 포맷 이해하기

 

Intel Hex 란?

 

Intel HEX는 ASCII 텍스트 형식으로 이진 정보를 전달하는 파일 형식이다. 주로 MCU의 펌웨어 프로그래밍을 위해 흔히 사용된다. 이 파일 형식은 각 줄이 콜론(:)으로 시작해서 줄바꿈 문자(LF나 CR)로 구분되는 여러줄의 레코드로 구성되며 각 줄은 바이너리 정보를 16진수 ASCII 문자 형태로 담고 있다.

 

 

 

그럼 Intel Hex파일은 Binary파일과 실행파일(.elf 또는 .out)과는 어떻게 다를까?

 

STM32CubeIDE 툴을 사용해서 빌드하면 보통 .elf파일만 생성된다. .elf파일은 Executable and Linkable Format으로 변수 및 함수 심볼 정보, 시스템의 메모리 구조 등 메타데이터를 포함하고 있어 디버거 같은 툴을 사용하여 프로그램 실행 흐름을 추적하고 메모리를 분석하여 디버깅하는데 유용하게 사용될 수 있다.

 

한편 실제 디바이스에 프로그램을 업로드하기 위해 사용되는 최종 파일 형태는 .bin파일 또는 .hex파일인 경우가 많다.

 

  • .bin 파일 : .bin 파일은 1:1로 메모리에 매핑할 수 있는 순수한 바이너리 형식으로 특정 주소에 바로 로드해서 실행할 수 있는 코드와 데이터로 구성되어 있다. 따라서, 이 파일을 MCU의 플래시 메모리에 구우면 MCU는 리셋 후에 이 메모리 위치에서 실행을 시작할 수 있다.
  • .hex 파일 : .hex 파일은 코드와 데이터를 16진수 형식으로 표현한 파일 포맷이다. 데이터만 있는 .bin파일과는 달리 메모리 주소와 함께 데이터를 표현하기 때문에 주로 코드 또는 데이터가 메모리의 불연속적인 영역에 배치될 필요가 있을 때 유용하게 사용된다.

 

먼저 Intel hex format을 이해하기 전에 분석 대상이 될 Intel hex파일을 하나 생성해보자.

 

 

 

 

 


Intel Hex 파일 생성하기

다음은 간단한 blinky 프로젝트를 생성해 Properties > C/C++ Build > Setting > MCU Post build outputs 세팅을 통해 hex파일을 생성해봤다.

 

binary output도 같이 생성했으며 이렇게 설정하고 빌드를 하면 빌드 구성 폴더(디폴트로 Debug) 폴더 안에 .hex와 .bin 파일이 생성된다.

 

 

00_blinky.bin
0.01MB
00_blinky.hex
0.02MB

 

 

 

 

 

 

 


Intel Hex 형식

 

:020000040800F2

:100000000000032049070008C5060008CD060008C7

:04000005080007499F

:00000001FF

 

 

레코드 구조

 

  1. 시작 코드(Start code) : 한 문자, ASCII 문자 ':'(colon).
  2. 바이트 개수(Byte count) : 16진수 문자 두 자리, 데이터 필드에 있는 바이트(16진수 두 문자의 쌍)의 수를 가리킨다. 최대 바이트 개수는 255 (0xFF)다. 16 (0x10) 과 32 (0x20)가 흔히 쓰인다.
  3. 주소(Address) : 16진수 문자 네 자리, 데이터의 16비트 시작 주소의 옵셋 값을 표현한다. 데이터의 물리 주소는 앞서 지정된 기준 주소에 이 옵셋을 더해서 구한다. 이런 식으로 16비트 주소의 한계인 64 킬로바이트를 넘는 주소를 지정 한다. 기준 주소의 기본값은 0이고, 여러 종류의 레코드로 값을 바꿀 수 있다. 기준 주소와 주소는 항상 빅 엔디언으로 표현된다.
  4. 레코드 종류(Record type) : (아래 레코드 종류 참조), 16진수 문자 두 자리, 00에서 05, 데이터 필드의 종류를 정의한다.
  5. 데이터(Data) : 2n 개의 16진수 문자로 표현된 n 바이트의 데이터 열이다. 어떤 레코드는 이 필드가 빠져 있다(n이 0). 데이터 바이트들의 의미나 해석은 응용프로그래밍에 달렸다.
  6. 체크섬(Checksum) : 16진수 문자 두 자리, 레코드에 오류가 없음을 입증하는데 이용될 수 있는 계산된 값이다.

 

 

이 중에서 레코드가 어떤 정보를 담고 있는지 해석하기 위해 가장 중요한 필드는 4. 레코드 종류 필드이다. 왠지 맨 앞에 위치할 것 같았는데 실제로는 중간에 등장한다. 레코드의 종류는 다음의 6 종류가 있다.

 

 

 

레코드 종류

 

Hex code Record type Description Example
00 데이터 16비트 시작 주소와 데이터를 가진다. 데이터의 바이트 수가 주어진다. 오른쪽 예제는 0B 11개의 데이터 바이트를 가진다. (61, 64, 64, 72, 65, 73, 73, 20, 67, 61, 70) 이들 데이터가 0010 주소에서부터 연속으로 놓인다. :0B0010006164647265737320676170A7
01 파일의 끝 파일의 마지막 줄에 꼭 있어야 한다. 데이터 필드는 없다. 따라서 바이트 개수는 00이고, 주소 필드도 보통 0000이다. :00000001FF
02 확장된 세그먼트 주소 이 데이터 필드는 16비트 세그먼트 기준 주소다. 따라서 바이트 개수는 02다. 80x86 리얼 모드 주소와의 호환을 위한 것이다. 주소 필드는 무시된다. 보통 0000이다. 세그먼트 주소 02 레코드의 값이 16 곱해져서 이어지는 데이터 레코더의 주소에 더해져야 데이터의 물리 시작 주소가 된다. 이것은 1M까지의 주소 공간을 지원한다. :020000021200EA
03 시작 세그먼트 주소 80x86 프로세서에서, CS:IP 레지스터의 초기값을 주어진다. 주소 필드는 보통 0000이고, 바이트 개수는 04다. 첫 두 바이트가 CS 값이고, 다음 둘이 IP 값이다. :0400000300003800C1
04 확장된 선형 주소 4기가바이트까지 가능한 32비트 주소체계를 지원한다. 주소 필드는 보통 0000으로 무시되고 바이트 개수는 항상 02다. 빅 엔디안의 두 데이터 바이트가 이어지는 00 레코드의 절대 주소의 상위 16비트가이다. 04 종류의 레코드가 없는 00 레코드의 주소 상위 16비트는 기본값 0이다. 00 종류의 레코드의 절대 주소는 최근의 04 레코드 값이 상위 16비트가 되고, 00 레코드의 주소 값이 하위 16비트가 된다. :02000004FFFFFC
05 시작 선형 주소 주소 필드는 0000으로 사용하지 않고, 바이트 개수는 04다. 4바이트 데이터는 80386과 더 높은 CPU의 EIP 레지스터에 기록할 32비트 값을 나타낸다. :04000005000000CD2A

 

참고:

https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%85%94_HEX

 

 

 

 

이 중에서 위에 생성한 Intel Hex 파일은 04, 00, 05, 01 타입의 레코드만 존재한다.

 

그럼 실제 한 줄 씩 분석해보자.

 

 

 

 

 


Intel Hex 파일 분석하기

 

 


다음은 파일의 첫 줄이다.

:020000040800F2

  • 시작 코드(:)는 모든 레코드가 동일하게 가진다.
  • 바이트 수(02)는 데이터 필드에 2바이트가 있음을 의미한다.
  • 주소(0000)는 이 레코드의 타입이 04로 사용되지 않아 0000으로 채워진 것이다.
  • 레코드 타입(04)는 이 레코드가 확장 선형 주소 레코드임을 나타낸다. 이 레코드의 데이터 필드가 다음에 등장하는 모든 데이터 레코드의 16비트 상위 주소를 설정한다.
  • 데이터(0800)는 실제 상위 주소를 나타낸다. 이는 다음 데이터 레코드의 시작 주소에 추가된다.
  • 체크섬(F2)는 이 레코드의 오류 검사 값이다.

 

이를 통해 이후 등장하는 데이터 레코드의 상위 16비트 주소는 0x0800xxxx가 될 것임을 알 수 있다.

 

 

 

 


다음은 파일의 두 번째 줄이다.

:100000000000032049070008C5060008CD060008C7

  • 시작 코드(:)는 모든 레코드가 동일하게 가진다.
  • 바이트 수(10): 데이터 필드에 16바이트(10 in hexadecimal)가 있음을 의미한다.
  • 주소(0000): 이 데이터 레코드가 로드될 시작 주소를 나타낸다. 확장 선형 주소 레코드에 의해 설정된 상위 주소 0800과 결합되어 전체 주소는 08000000이 된다.
  • 레코드 타입(00): 데이터 레코드임을 나타낸다.
  • 데이터(0000032049070008C5060008CD060008): 메모리에 쓰일 바이너리 데이터이다.
  • 체크섬(C7): 이 레코드의 오류 검사 값이다.

 

실제로 바이너리 출력물을 보면 처음 등장하는 16바이트에 해당 데이터가 있다.

 

 

 

그리고 hex 파일을 보면 대부분의 데이터 레코드의 바이트 수가 0x10(16)이긴 한데 반드시 그런것은 아니다.

 

아래는 hex 파일에서 마지막에 등장하는 데이터 레코드인데 0x080017CC 주소부터 로드될 0x0C(12)바이트의 데이터를 담고 있다. 실제로 바이너리 파일을 살펴보면 파일의 끝 주소가 0x000017D7으로 두 파일의 정보가 동일한 것을 알 수 있다.

 

 

 

 

 

 

 


다음은 파일의 마지막에서 두 번째 줄이다.

:04000005080007499F

 

  • 시작 코드(:)는 모든 레코드가 동일하게 가진다.
  • 바이트 수(04): 데이터 필드에 4바이트가 있음을 의미한다.
  • 주소(0000): 시작 선형 주소 레코드에서는 주소 필드가 특별한 의미를 가지지 않으므로 0000으로 설정되어 무시된다.
  • 레코드 타입(05): 시작 선형 주소 레코드임을 나타낸다. 이 레코드는 CPU가 프로그램을 실행할 때 사용할 시작 주소를 지정한다.
  • 데이터(08000749): 프로그램의 실행 시작 주소를 나타낸다. 
  • 체크섬(9F): 이 레코드의 오류 검사 값이다.

 

레코드 타입 05는 프로그램 실행 시 시작 주소를 나타내는데 이는 ARM Cortex-M4 MCU에서 Reset Handler에 해당한다. 해당 레코드가 꼭 필요한지는 모르겠지만(해당 레코드를 삭제하고 바이너리 변환을 해봐도 변환된 바이너리 파일의 차이는 없었다.) 어쨌든 .map 파일을 살펴보면 리셋 핸들러의 주소는 0x08000748이고 여기서 +1한 값을 해당 레코드가 데이터로 가지고 있다.

 

 

 

여기서 Reset Handler의 +1한 주소를 가지고 있는 이유는 ARM 아키텍처에서 프로그램 카운터(PC)에 로드되는 주소의 최하위 비트(LSB)가 1로 설정되면 해당 함수를 Thumb 모드로 실행하라는 지시를 포함하기 때문이라고 한다. 

 

참고:

https://stackoverflow.com/questions/65884094/why-this-function-does-point-to-itself-with-a-offset-of-1

 

 


다음의 파일의 마지막 줄이다.

:00000001FF

 

  • 시작 코드(:): 모든 레코드가 동일하게 가진다. 
  • 바이트 수(00): 데이터 필드에 0바이트가 있음을 의미한다. 즉, 데이터 필드가 없다. 
  • 주소(0000): 파일 종료 레코드에서는 주소 필드가 사용되지 않아서 0000으로 채워졌다.
  • 레코드 타입(01): 파일 종료 레코드임을 나타낸다. 이 레코드는 파일의 끝을 나타낸다. 
  • 체크섬(FF): 이 레코드의 오류 검사 값이다.

 

 

 

 

 

 


Intel Hex to Bin 변환

 

 Intel HEX 파일을 바이너리(bin) 형식으로 변환하는 것은 흔하게 필요한 작업 중 하나이며, 여러 오픈소스 프로그램이 이를 지원한다. 대표적으로는 SRecord, hex2bin, GNU objcopy, Python IntelHex 라이브러리 등이 있다.

 

 

다음은 hex2bin 프로그램을 사용해서 hex파일을 binary로 변환해본 것인데 STM32CubeIDE의 binary 출력과 비교했을 때 동일하였다.

 

 

 

hex2bin 다운로드 링크:

https://sourceforge.net/projects/hex2bin/files/hex2bin/