디버깅(Debugging)이란 프로그램에서 버그를 찾아 수정하거나 에러를 처리해나가는 과정을 말한다. 일반적으로 소프트웨어 디버깅의 경우 비주얼 스튜디오 등의 툴을 활용해 터미널 환경에서 쉽게 디버깅 메세지를 출력할 수 있는 반면 임베디드 환경에서는 외부에 출력하기도 힘들고 하드웨어적인 부분까지 같이 고려해야 하므로 다소 어렵다. 때문에 하드웨어를 디버깅 할때는 JTAG 등의 전용 디버깅 장비를 활용하여 프로세서를 특정 시점에서 멈추게 하거나 각각의 명령어 단위로 하나씩 실행시키는 방법으로 진행하는데 이러한 방식을 하드웨어 디버깅, 혹은 소스 레벨 디버깅(Source Level Debugging)이라고 한다. 


이번 포스팅에서는 프로테우스 툴을 활용하여 전에 사용했던 외부 인터럽트 코드와 회로를 가지고 소스 레벨 디버깅하는 방법에 대해 다뤄보겠다.



1. 필요 파일


앞서 소스 레벨 디버깅을 위해서는 JTAG 같은 전용 장비가 필요하다고 언급했었다. 하지만 프로테우스에서는 cof 파일이나 elf 파일을 보드에 넣어주면 실제 컴파일러에서 디버깅 하는 것과 유사하게 소스 레벨 디버깅을 할 수 있는 기능을 지원한다. 그렇다면 여기서 cof와 elf 파일이 무엇이길래 위와 같은 일들을 가능하게 하는 것일까? 


cof 파일은 COFF(Common Object File Format) 형식의 파일을 말하는 것으로 어셈블러를 거쳐서 만들어진 오브젝트 파일에서 공통된 성질의 것들을 모아 관리하는 포맷을 의미한다. 잘 이해가되지 않는다면 프로그램의 컴파일 과정에 대해 떠올려보자. 개발자가 작성한 .c 파일은 어셈블러에 의해 기계어로 변환된 .o 오브젝트 파일로 생성되고 이렇게 생성된 각각의 오브젝트 파일을 링커가 합쳐서 실행 파일로 만들어진다. 그런데 이렇게 생성된 실행 파일은 수많은 프로그램들이 합쳐져 있기 때문에 프로그램 메모리에 일일이 분산시켜 옮기는 과정이 매우 번거롭고 많은 시간이 필요하는 문제점이 있다. 때문에 이러한 문제를 해결한 것이 바로 COFF 형식이다. 


COFF는 합쳐진 모든 프로그램과 데이터를 각각의 세션별로 구분하는 역할을 한다. 이 세션에는 컴파일된 코드가 들어가는 프로그램 코드 세션, 데이터가 저장되는 상수 데이터 세션 및 사용자 정의 데이터 세션 등 공통된 성질을 가지는 것들이 분류별로 나뉘어지며, 이로 인해 세션 별로 관리하기 용이하고 각 세션이 배치된 메모리의 영역과 크기도 확인할 수 있다. 또한 단순하게 기계어로 되어있는 HEX 파일에 비해 여러가지 프로그램에 대한 정보를 가지고 있기 때문에 디버깅에 유용하게 사용할 수 있다.


ELF (Executable and Linking Format) 의 개념도 마찬가지로 COFF와 비슷하며 단지 기존 형식의 단점을 개선하여 좀 더 복잡하고 그만큼 유연하게 동작할 수 있는 형식이다. 사실 임베디드 환경에서 COFF가 강력하기는 하지만 지금 사용하기에는 너무 오랜된 형식이며 컴파일러에 따라 해석이 조금씩 달라질 수 있는 문제점이 있기 때문에 가능하면 ELF 형식의 파일을 사용할 것을 권한다. 아래는 ELF 형식의 구성도이며 2가지 형태로 나눌 수 있는데, 먼저 왼쪽의 Linkable File은 Link 하기 전의 오브젝트 파일을 칭하고 오른쪽의 Executable File은 Link가 끝나고 실행 가능한 형태의 elf 파일을 말한다.  



보다 세부적인 내용들은 그 개념이 방대하기 때문에 따로 검색해볼 것을 추천하며 여기서는 우리가 시뮬레이션할 때 사용했던 HEX 파일과 어떤 차이점이 있는지만 명확하게 알고 넘어가도록 하자.



2. 디버깅 기능


코드비전에서는 기본적으로 cof 파일이, avrstudio에서는 elf 파일이 프로젝트 빌드시 자동으로 생성되며 필자는 코드비전 컴파일러를 사용하기 때문에 cof 파일로 디버깅을 진행하였다. 이전 포스팅에서 사용했던 회로를 가져오고 HEX 파일 대신 cof 파일을 보드에 넣어보자. 그리고 시뮬레이션을 시작한뒤 Debug 메뉴를 살펴보면 다음과 같은 항목들이 나타날 것이다.





이 기능들은 소스 레벨 디버깅에 있어서 많은 도움이된다. 먼저 소스 코드 항목을 눌러보면 신기하게도 우리가 컴파일한 코드가 그대로 프로테우스에서 확인할 수 있다. 왼쪽 숫자는 각 명령어의 주소를 가리키며 순차적으로 PC 레지스터에 저장된다. 여기서 명령어를 하나씩 실행시키면서 프로그램이 수행되는 순서를 모두 추적해볼 수 있고 명령어 라인 위에다 마우스를 더블 클릭하면 BreakPoint가 지정되어 시뮬레이션 수행 도중 해당하는 포인트에서 멈추게 할 수도 있다. 또한 Ctrl + D 단축키를 누르면 해당 소스 코드를 어셈블리어 레벨에서 해석도 가능하다.



다음은 2번째 항목인 Variables 이다. 여기서는 소스 코드에서 전역으로 설정한 변수들의 이름과 주소, 그리고 값을 표시해주며, 마우스 오른쪽 버튼을 눌러서 Add to Watch Window를 클릭하면 시뮬레이션 도중에 Watch Window 창을 통해 해당 변수들의 값이 바뀌는 것을 즉시에 확인해 볼 수 있다. 중간에 .bss 변수의 경우에는 COFF 세션의 한 종류이며 초기화되지 않은 전역 변수들이 들어가는 부분이니 고려하지 않아도 된다.



세번째는 CPU Register 항목으로 ATmega128에서 사용하는 레지스터들의 값들을 확인할 수 있다. 먼저 PC 레지스터에서는 현재 연산중인 명령어의 주소가 나오며, INSTRUCTION 에는 연산중인 명령어가 어셈블리어로 어떤 레지스터에 어떻게 연산되는지 보여준다. 그리고 SREG 레지스터는 현재 상태에 대한 것들을 보여주며, 그 옆에 ITHSVNZC 는 SREG 레지스터 각 비트의 값들을 표시한다. 마지막으로 CYCLE COUNT 는 각 명령어를 얼마나 실행했는지 알려주는 역할을 하게 된다.



간단하게나마 이렇게 주요 디버깅 기능들에 대해 설명해 보았으며 나머지 기능들도 상황에 따라 유용하게 쓰일 수 있으므로 따로 확인해볼 것을 권한다.



3. 디버깅 하기


그러면 이제 위 기능들을 활용하여 프로테우스 툴에서 디버깅을 진행해보도록 하자. 코드와 회로는 모두 전 포스팅을 참고하기 바라며 ATmega128에 cof 파일을 넣고 다음과 같이 세 부분에 Break Point를 설정하여 외부 인터럽트 0번의 동작 과정을 살펴보는 것이 목표이다.



시뮬레이션을 시작해서 외부 인터럽트에 연결된 버튼을 누르고 각 값들이 어떻게 바뀌는지 확인해보자.



가장 먼저 SREG 가 15로 바뀐 것이 눈에 보인다. 이는 외부 인터럽트가 동작할 때 다른 외부럽트가 접근하지 못하도록 설정되어 있기 때문에 자동으로 바뀐 것이며, 만약 중복 인터럽트를 허용하고 싶다면 코드 안에 SREG 레지스터의 I 비트를 다시 1로 설정해주면 가능하다. 또한 INSTRUCTION에 LDR R30, $01 이라는 명령어는 R30 레지스터에 1의 상수를 전송하는 역할을 한다. 이제 다음 Break Point로 넘어가보자.



이번에는 인터럽트 코드 안을 벗어나면서 SREG 레지스터가 다시 허용되었음을 확인할 수 있으며 status 변수에도 1의 값이 쓰여졌다. 이 값들이 어디에 있는지 확인하기 위해 변수 주소를 따라가면 위와 같이 Data Memory 기능에서 각각 500번지와 502번지에 1이 쓰여져 있음을 알 수 있다. 여기서는 501번지가 왜 0인지가 중요한데, 그 이유는 status가 2바이트인 int 자료형이기 때문이며 또한 ATmega128이 Little-endian 방식이기 때문에 하위 비트가 먼저 쓰여져 500번지에는 01이, 501번지에는 00이 쓰여졌다고 알아두도록 하자.


다음으로 넘어가면 temp 에는 0x02의 값이 쓰여지고 LED가 Shift 되게 된다. 그 다음에 status 가 0이 되면서 앞서 설정했던 Break Point가 모두 끝나게 되며 여기서 한가지 재밌는 부분이 status 가 0으로 초기화 될 때의 명령어 주소가 없다는 점이다. 실제로 명령어를 하나씩 실행해 나가면 status 변수가 2일 때의 코드 맨 밑에 있는 명령어로 동작하게 되는데, 필자의 생각으로는 같은 명령어가 중복되기 때문에 컴파일러가 코드를 최적화 하면서 2개의 명령어를 하나로 합치지 않았나 하는 생각이 든다. 정확히는 모르겠으나 재밌는 상황인건 분명하다.



이렇게 ATmega128의 소스 레벨 디버깅 방법에 대해서 프로테우스 툴을 사용하여 알아보았다. 사실 어렵게 보이긴 해도 가장 기초적인 부분만 소개했기 때문에 막상 해보면 별거 아니다라는 생각이 들 것이다. 펌웨어 단에서는 소프트웨어 뿐만 아니라 하드웨어도 매우 중요하기 때문에 이러한 디버깅 방법을 잘 익혀둬서 유용하게 써먹을 수 있기를 바란다.

 [출처] COFF|작성자 gwoook



'Electronic > AVR' 카테고리의 다른 글

외부 인터럽트  (0) 2017.06.08
인터럽트 이해하기  (0) 2017.06.06
프로테우스 사용하기  (1) 2017.06.02