본문 바로가기
Exception Vector Table (EVT)과 각 Handler의 구현

이제까지 마르고 닳도록 Exception이 발생하면 어디로 branch하네 뭐네 하면서 실제로 어떻게 생겨먹은 지는 한번도 구경한 적이 없지요. 그런 의미에서 Vector Table의 구현과 실제를 한번 구경해 보시는 것이 좋을 듯 합니다요.
Vector Table은 각각의 Exception이 일어 났을 때 정해진 곳으로 branch한다고 했으니까 그 용도에 맞는 처리 routine들이 들어 있으면 되겠지요. 그때그때 정해진 주소를 table로 다시 한번 회상해 BoA요. 일단 High Vector나 Low Vector나 다 똑같은 녀석들이니까, Low Vector를 기준으로 한번 질러 보시지요.

; ADDRESS EXCEPTION MODE ON ENTRY;----------------------------------------------------------------------------; 0x00000000 Reset Supervisor;----------------------------------------------------------------------------; 0x00000004 Undefined Exception Undefined;----------------------------------------------------------------------------; 0x00000008 Software Interrupt Supervisor;----------------------------------------------------------------------------; 0x0000000C Abort (prefetch) Abort;----------------------------------------------------------------------------; 0x00000010 Abort (data) Abort;----------------------------------------------------------------------------; 0x00000014 Nothing Nothing

;----------------------------------------------------------------------------; 0x00000018 IRQ IRQ;----------------------------------------------------------------------------; 0x0000001C FIQ FIQ

Reset이 나면 0x0, Undef Exception 나면 0x4 뭐 이런 식으로 32bit씩 자리를 차지하고 있습니다. 이 시점에서 도대체 4byte씩만 가지고 Exception을 처리하기에는 너무 비좁다는 생각이 들지 않나요? Instruction 한 개 들어갈만한 공간 가지고 Exception을 처리하기에는 너무나 버겁죠. 자. 그럼 실제 구현은 어떻게 해야 할 것인가..

AREA INIT_VECTOR, CODE, READONLY → INIT_VECTOR라는 부분 기억해 두세요. CODE32 ; 32 bit ARM instruction set. ENTRY

B Reset_Handler ; 0x0 B Undefined_Handler ; 0x4 B SWI_Handler ; 0x8 B Prefetch_Handler ; 0xC B Abort_Handler ; 0x10 B Nothing_Handler ; 0x14 B IRQ_Handler ; 0x18 B FIQ_Handler ; 0x1C

짜잔! 이런 식이죠. 각 주소별로 어디론가 branch하는 코드가 들어 있는 거에요. 각각의 Handler로 Jump하게 되어 있지요. 4byte씩 ARM mode에서 돌게 하기 위해서 위에 CODE32가 떡~하니 자리 잡고 있고요. Exception에 진입하면 ARM의 기본 state는 ARM state입니다. 기억하시죠? Reset이 나면 Reset_Handler로, Data Abort가 나면 Abort_Handler로 Jump! 오호라, 그렇군요. 홋! 그러니까, 다른 곳에 각각의 Handler를 구현 한 후, Exception Vector에서 Jump하도록 구현하면 되어요. 여기서 또하나 잠깐, 그러면 요놈들은 어떻게 0x0부터 차례로 메모리에 자리잡게 할꺼냐! 하면. 이건 Scatter Loading을 이용해서 컴파일 할 때 0x0부터 자리잡게 하고서, Flash에 저장할 때 0x0부터 차례대로~ 구워 놓으면 되겠죠. 또는 SDRAM이 0x0부터 시작하는 거라면 0x0부터 쫘라락 복사하면 되겠구요. Vector Table을 vectors.s에 만들어 놓고서 컴파일한 후, Scatter Loading File에 아래와 같이 적어주면 0x0부터 잘~ 만들어 집니다.

BOOT_ROM 0x0 {
       BOOT_RAM 0x0 0x4000 ; 0x0부터 시작이야 잘 기억해둬 {
            vectors.o (INIT_VECTOR, +FIRST) ; vectors.s를 컴파일하는데, INIT_VECTOR AREA를 맨 앞에 두어라.

알간? (참고로 +LAST는 맨 마지막에 놓으라는 얘기야) ..........
     }
}

요거요거.. High Vector로 만들고 싶으면?

BOOT_ROM 0x0 {
         BOOT_RAM 0xFFFF0000 0x4000 ; 0xFFFF0000 부터 시작이야 잘 기억해둬. {

              vectors.o (INIT_VECTOR, +FIRST) ; vectors.s를 컴파일하는데, INIT_VECTOR AREA를 맨 앞에 두어라. 알간? ..........
      }
}

으랏차차 엄청 간단하죠? 뭐 인생 별거 있습니까? 다~ 이런 식이죠 머. 자, 가만 보니까.. 재미있는 거 생각나지 않나요? 각 Handler들을 그때 그때 막 바꾸고 싶어요.. 그럴 땐 어떻게 하면 좋을까요? (좀 변태 같지만, Advanced 한 system만들고 싶으시면 이런 Technique도 알아야 해요!) 엄청 간단합니다. 원래 Vector Table에 branch 하라는 명령어들이 좌르륵 나열되어 있잖아요. 그러면 그 자리에 branch하라는 명령어 말고 다른 거 넣어두면 되겠죠.

뭐 굳이 예를 든다면, 0x00 B Reset_Handler 0x04 ldr pc, 0x300x08 ldr pc, 0x340x0C ldr pc, 0x380x10 ldr pc, 0x3C0x14 ldr pc, 0x40 0x18 ldr pc, 0x440x1C ldr pc, 0x48..........0x30 dcd 0x1000 ; Undefined Handler0x34 dcd 0x2000 ; SWI Handler0x38 dcd 0x3000 ; Prefectch Handler0x3C dcd 0x4000 ; Abort Handler0x44 dcd 0x6000 ; IRQ Handler0x48 dcd 0x7000 ; FIQ Handler

요렇게 하고서, 0x30, 0x34, 0x38, 0x3C, 0x44, 0x48에 들어 있는 값을 바꿔주면 그때 그때 상황에 맞추어 handler를 바꿀 수 있는 기지를 발휘할 수 있겠죠. Reset Handler는 왜 그대로 썼느냐. Reset Handler도 바꿔줘도 되긴 하는데요. Entry point니까, 그냥 두는 매너에요. Handler에 무엇을 채워 넣으면 될까요? 그것들을 고민해 봐야겠죠. 각 Handler에 뭘 넣느냐는 각자의 입맛과 취향에 따라 다르니까, 보통의 경우에 들어가는 것들을 알아 보도록 하시죠.

1. Reset Handler 는 Power On이 되면 진입하게 되는 Default Handler니까 다시 어디론가 돌아가야 하는 부담이 없고요. 처리도 없습니다.

2. Undefined Handler 보통 Undefined Exception이 발생하는 이유는 ARM core가 Memory에서 Op code를 decode했더니 모르는 Op code더라.. 라는 상황이죠. 이런 상황은 어떨 때 벌어 질까욧. 한가지는 정말 모르는 경우에 이런 경우가 발생하겠죠. 흔치 않겠지만 XIP Memory, Software가 실행되고 있는 Memory, 가 Corrupt 되었거나, Software가 저장된 저장 매체가 Corrupt되면 이런 현상이 일어나겠죠. 이런 일이 벌어지면 Memory를 제대로 control 하고 있는 것인지 timing이 적당한 건지 Design Review부터 다시 해봐야 합니다. 하지만 다른 한가지는 일부러 Undefined Exception을 내는 경우가 있겠죠. 일부러 내는 건 어떤 상황일까요. ARM Core 혼자 만으로는 Computing Power가 다 나오지 않을 때 Co processor를 달아서 ARM이 혼자 해야 할 일을 덜어주고 싶겠죠. 이럴 때 Co processor에게 뭔가 명령을 내려주고 싶을 때, 일부러 Undefined Exception을 내고 그 Handler에서 Co processor에게 명령을 내려주는 루틴을 넣어둘 수도 있어요. Handler 자체는 어떻게 구현되느냐 하면, Undef Exception의 경우 자기 전용 Register가 R13_und, R14_und, SPSR_und 밖에 없으니까 하던 곳으로 돌아가려면, R0~R12까지는 backup을 해두어야 Exception 났던 곳으로 돌아가도 문제가 없겠죠. 그리고 LR(R14)도 backup해 두어야 돌아갈 수 있겠죠? UNDEF_Handler stmfd sp!, {r0-r12, r14} ; Register들을 backup 해 두고요.

구현해 넣고 싶은 내용들을 막 넣고요.

ldmfd sp!, {r0-12, pc}^ ; Register들을 다시 Restore하면 되죠. Undef Exception은 LR에 Undef exception이 난 명령어 바로 다음을 가리키는 값이 들어가니까, Handler에 들어와서 Register들만 backup하면 되겠네요.

3. Prefetch Abort Handler 그러면 Prefetch Abort Handler같은 거에서는 어떤 일을 하면 좋을까요? 또 별거 있나요? Prefetch Abort Handler나 Data Abort Handler에 들어오면 보통 문제가 발생했다는 것을 외부에 알리는 일을 하거나, information을 남겨놓는 일을 남겨 놓지요. 안 만나면 좋은 Exception이니까 어떻게든 만천하에 알려서 문제가 수정될 수 있도록 하는 routine을 끼워 넣는 일이 보통입니다. Debugging 용도의 Code들이 삽입되는 게 보통인데요, MMU를 쓰는 경우에는 Prefetch Abort의 경우 스와핑으로 잘 알려진 Demand Loading을 사용하느라 그런 일이 벌어질 때도 있어요. Demand Loading이란 건 XIP를 하는 Memory가 너무 작으니까, Software를 모두 Loading하는 게 아니라, 실제 실행 해야 하는 부분만 XIP Memory에 Loading 해서 사용하는 고급(?) 기법이지요. PAbort의 경우에도 진입했다가 복귀할 때는 Undef Exception과 같은 처리를 하면 되니까, PAbort_Handler stmfd sp!, {r0-r12, r14} ; Register들을 backup 해 두고요.

구현해 넣고 싶은 내용들을 막 넣고요.

ldmfd sp!, {r0-12, pc}^ ; Register들을 다시 Restore하면 되죠. 요런 식으로 구현한답니다.

4. Data Abort Handler DAbort의 경우 Align이 안 맞는 Data를 Access할 때도 발생하니까, 이런 Align을 강제로 맞춰서 원상 복구하는 형태의 Handler를 넣어놓기도 한답니다. Abort mode는 자기 전용 Register가 R13_abt, R14_abt, SPSR_abt 3개 있으니까, Handler 진입 시 R0~R12를 bakcup하고요, 이전 mode로 돌아가기 위하여 LR을 backup해 놓는 게 좋지요 DAbort Handler는 다른 Handler와 달리, 임무를 마치고 돌아갈 시점이 LR - 8이 되어야 하므로 맨 앞에 lr을 교정하는 명령어가 하나 들어가면 됩니다. DAbort_Handler sub lr, lr, #8 ; 돌아갈 주소 교정 stmfd sp!, {r0-r12, r14} ; Register들을 backup 해 두고요. 구현해 넣고 싶은 내용들을 막 넣고요.

ldmfd sp!, {r0-12, pc}^ ; Register들을 다시 Restore하면 되죠. 6. FIQ, IRQ는 FIQ, IRQ는 앞에서 살펴 보았듯이, LR-8를 해주면, Exception이 발생한 시점에 처리된 명령어의 알아 낼 수 있으니까, Handler에 진입하자마자 LR을 가공해 주면 됩니다. 결국엔 Exception 발생 당시에 처리된 명령어의 다음 바로 다음 번 주소가 Interrupt를 처리하고 난 후에 계속 진행되어야 할 명령어니까, LR-4를 해주면 다음 처리할 명령어를 찾을 수 있는 게지요. 그리고 FIQ의 경우에는 R8~R14하고 SPSR을 자기 전용으로 갖고 있으니까, 진입할 때, R0~R7까지만 backup 해주고요, IRQ의 경우에는 다른 여타 Exception과 마찬가지로 R13, R14, SPSR만 자기 전용으로 갖고 있으니까, R0~R12를 backup해 주고, 두 mode 같은 원리로 돌아갈 주소 LR을 backup해 주면 됩니다. IRQ_Handler sub lr, lr, #4 stmfd sp!, {r0-r12, r14} ; Register들을 backup 해 두고요. 구현해 넣고 싶은 내용들을 막 넣고요. ldmfd sp!, {r0-r12, r14}^ FIQ_Handler sub lr, lr, #4 stmfd sp!, {r0-r7,lr} 구현해 넣고 싶은 내용들을 막 넣고요. ldmfd sp!, {r0-r7,pc}^ 7. SWI SWI_Handler는 Undef Exception Handler랑 똑같이 처리하면 되겠죠.

5. SWI_Handler

stmfd sp!, {r0-r12, r14} ; Register들을 backup 해 두고요. 구현해 넣고 싶은 내용들을 막 넣고요.

ldmfd sp!, {r0-12, pc}^ ; Register들을 다시 Restore하면 되죠. 각 Handler에 뭘 넣느냐는 각자의 입맛과 취향에 따라 다르니까, 잘 생각하고 구현하시면 되겠고, 이게 헷갈리시면 Pipe line과 Exception관계에서의 Table을 수시로 보시고 생각해 보세요. 아.. 쉽게 외우는 방법이 있으면 좋으련만.. 그것도 쉽지 않다는 이런 핑계.

 

FIQ Exception Vector는 Vector Table중 가장 마지막에 자리 하는데요, 이유가 다 있습니다. FIQ는 Register copy도 안 하려고 전용 Register까지 갖고 있는 형편이잖아요. Exception Vector의 맨 마지막에 자리하면 branch 안 해도 되는 장점이 있는 거지요. FIQ_Handler branch 안하고 곧바로 0x1C부터 FIQ_Handler를 구현해 버리면 곧바로 처리 가능하겠죠. 이런 것도 참 세심하게 준비되어 있다니까요.

 

아.. css를 바꾸면서 이 페이지는 마치 Jquery 코드처럼 Enter가 모두 없어졌군요 ㅠㅠ 

아 왜 아무도 말 해 주지 않은 거에요?

그런데, 괜찮은 거 같기도 하군요?!

 
 
Linked at 친절한 임베디드 시스템 개발자.. at 2009/10/01 12:32

... ⓖ Exception Vector Table (EVT)과 각 Handler의 구현 ... more

친절한 임베디드 시스템 개발자 되기 강좌 글 전체 리스트 (링크) -



댓글





친절한 임베디드 개발자 되기 강좌 글 전체 리스트 (링크) -