Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

11105
637
415734


[ARM] 익셉션이 발생하면 프로세스는 어떻게 동작할까 - ARMv7 Arm: Exception Overview

많은 학생이나 SW 개발자들은 "ARM의 익셉션 벡터 테이블을 열심히 읽어도 잘 모르겠다", 혹은 "무슨 내용인지 와 닿지 않는다"라는 불만을 토로합니다. 또한 "ARM 익셉션의 개념이 어렵다"라고 말하기도 합니다. 그 이유에 대해 곰곰히 생각해 봤는데, 소프트웨어 관점으로 익셉션이 무엇인지 분석하지 않았기 때문이라는 결론에 이르게 됐습니다.

여기서 말하는 소프트웨어란 용어는 조금 애매하고 추상적인 의미를 내포하는듯 합니다. 그런데 소프트웨어란 용어를 구체적으로 표현하면 "운영체제의 프로세스"라고 말할 수 있습니다. 소프트웨어 개발자가 입력한 코드는 CPU에 가까운 어셈블리 명령어로 실행되는데, 이를 실행하는 주인공은 프로세스이기 때문입니다. 

익셉션이 발생하면 프로세스는 어떻게 동작할까요? 어떤 익셉션이 발생하던 프로세스는 하던 일을 멈추고 갑자기 익셉션 벡터 주소에 있는 명령어를 실행하게 됩니다. 프로세스 입장에서는 순차적으로 명령어를 실행하다가 갑자기 순간 이동이 된다는 느낌을 받을 수 있습니다.

익셉션이 발생하는 어셈블리 코드 분석

이번에는 소스 코드를 보면서 프로세스 입장에서 익셉션이 발생하면 어떻게 동작하는지 살펴봅시다. 다음 예제 코드를 소개합니다.

01 struct process_struct {
02    void *stack;
03    int state;
04    struct task_struct *pre_process;
05 };
06
07 int proc_func_ptr(void *param)
08 {
09    struct process_struct *task;
10    int process_state = 0;
11
12    task = (struct process_struct*)param;
13    process_state = task->state;
14
15    return process_state;
16 }

예제 코드는 내용이 간단하므로 자세히 설명하지 않겠습니다. 

07번째 줄에 보이는 proc_func_ptr() 함수에 전달되는 인자는 param으로 포인터 형입니다.
param 인자에 적절한 주소가 전달되면 문제 없이 동작할 코드입니다.

그런데 예외적인 상황에서 param이란 인자가 NULL, 0x0을 가리키게 됐다고 가정합시다.

이 조건에서 12번째 줄이 실행되면 큰 문제는 발생하지 않습니다. 12번째 줄은 task란 포인터 형 변수에 param 포인터 형 인자가 가르키는 주소를 전달하는 동작이기 때문입니다.

그런데 13번째 줄을 실행할 때 proc_func_ptr() 함수는 실행을 멈춥니다. 그 이유는 무엇일까요? 바로 13번째 줄의 코드를 실행하는 ARM 프로세서는 익셉션을 감지하고 이미 지정된 주소인 익셉션 벡터로 프로그램 카운터로 브랜치하게 됩니다.

이해를 돕기 위해 proc_func_ptr() 함수를 어셈블리 명령어 포멧으로 보겠습니다.

00 8004a1d4 <proc_func_ptr>:
01 8004a1d4:   e1a0c00d    mov ip, sp
02 8004a1d8:   e92dd800    push    {fp, ip, lr, pc}
03 8004a1dc:   e24cb004    sub fp, ip, #4
04 8004a1e0:   e5900004    ldr r0, [r0, #4]
05 8004a1e4:   e89da800    ldm sp, {fp, sp, pc}

proc_func_ptr() 함수를 처음 실행할 때 어느 명령어를 실행할까요?
ARM 프로세서 입장에서는 0x8004a1d4 주소에 있는 'mov ip, sp' 명령어를 실행하게 됩니다.
'mov ip, sp' 명령어는 스택 포인터 주소를 ip(r11) 레지스터에 저장하는 동작이니 ARM 프로세서는 문제 없이 이 명령어를 실행합니다.

그런데 어느 명령어를 실행하다가 ARM 프로세서는 익셉션을 유발할까요? 바로 04번째 줄입니다.

04 8004a1e0:   e5900004    ldr r0, [r0, #4]

proc_func_ptr() 함수로 전달되는 포인터 형인 param 인자가 NULL을 가리키므로, r0 레지스터를 0x0입니다. 'ldr r0, [r0, #4]' 명령어를 실행하기 위해서는, r0 레지스터가 저장한 값에 4를 더한 0x4란 주소에 접근을 시도합니다.

그런데 ARM 프로세서는 0x4란 주소가 엑세스할 수 없는, 유효하지 않은 주소라고 판단하고 데이터 어보트 익셉션을 유발합니다. 이어서 ARM 프로세서는 데이터 어보트 익셉션에 해당하는 주소로 프로그램 카운터를 브랜치하게 됩니다.

그렇다면 데이터 어보트 익셉션에 해당하는 주소로 프로그램 카운터는 어디일까요? 
이해를 돕기 위해 아래와 같은 리눅스 커널의 익셉션 벡터 주소의 코드를 보겠습니다.

01 NSR:FFFF0000|EA0003FF    b       0xFFFF1004       ; vector_rst 
02 NSR:FFFF0004|EA000465    b       0xFFFF11A0       ; vector_und
03 NSR:FFFF0008|E59FFFF0    ldr     pc,0xFFFF1000
04 NSR:FFFF000C|EA000443    b       0xFFFF1120       ; vector_pabt
05 NSR:FFFF0010|EA000422    b       0xFFFF10A0       ; vector_dabt
06 NSR:FFFF0014|EA000481    b       0xFFFF1220       ; vector_addrexcptn
07 NSR:FFFF0018|EA000400    b       0xFFFF1020       ; vector_irq
08 NSR:FFFF001C|EA000487    b       0xFFFF1240       ; vector_fiq

리눅스 커널에서는 익셉션 벡터 테이블의 베이스 주소를 0xFFFF0000로 지정하므로, 데이터 어보트가 발생하면 익셉션 벡터 테이블의 베이스 주소 기준으로 +0x10만큼 떨어진 주소로 프로그램 카운터가 브랜치됩니다. 즉, 0xFFFF0010 주소로 프로그램 카운터가 브랜치되며, 해당 주소에 있는 'b 0xFFFF10A0' 명령어가 실행됩니다.

익셉션이 발생하면 프로세스는 하던 동작을 일단 멈춘다 

이번에는 proc_func_ptr() 함수를 실행 중인 프로세스 입장에서 데이터 어보트와 같은 익셉션이 발생하면 어떻게 동작하는지 생각해볼까요? 

프로세스 입장에서 실행하는 명령어는 다음과 같습니다.

00 8004a1d4 <proc_func_ptr>:
01 8004a1d4:   e1a0c00d    mov ip, sp
02 8004a1d8:   e92dd800    push    {fp, ip, lr, pc}
03 8004a1dc:   e24cb004    sub fp, ip, #4
04 8004a1e0:   e5900004    ldr r0, [r0, #4]
05 ffff0010:   e5900004     b       0xffff10a0       ; vector_dabt
 
04번째 줄에 보이는 8004a1e4 주소의 'ldr r0, [r0, #4]' 명령어를 실행하다가, 05번째 줄의 ffff0010 주소로 프로그램 카운터가 바뀌게 되는 겁니다. 이는 프로세스가 특정 기능을 수행하기 위한 코드가 멈춘다고 볼 수 있습니다. 달리 말하면, 프로세스 입장에서는 명령어가 실행되지 않는 대신 다른 주소로 이동하므로, 순간 이동이 되는 듯한 느낌을 받을 겁니다.

또한 프로세스 입장에서는 04~05번째 줄 사이에 무슨 일이 일어났는지 모를 겁니다.
이해를 돕기 위해 아래와 같이 어셈블리 명령어 사이에 보이는 05번째 줄의 주석을 봅시다.

01 8004a1d4:   e1a0c00d    mov ip, sp
02 8004a1d8:   e92dd800    push    {fp, ip, lr, pc}
03 8004a1dc:   e24cb004    sub fp, ip, #4
04 8004a1e0:   e5900004    ldr r0, [r0, #4]
05 // -> ARM 프로세서가 데이터 어보트 익셉션을 감지한 후 유발
06   ffff0010:   e5900004     b       0xffff10a0       ; vector_dabt

프로세스 입장에서는 03번째 줄이 마지막에 실행된 코드이며, 04번째 줄을 실행하다가 06번째 줄로 이동하게 됩니다. ARM 프로세서가 04번째 줄의 명령어를 실행하지 못해 익셉션을 유발했기 때문입니다.

Reference: ARM 프로세서 익셉션 소개

익셉션이란? 
익셉션의 주요 개념  
    ❑ 익셉션의 타입 소개
익셉션과 같이 배워야 하는 운영체제 지식


Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자




핑백

덧글

댓글 입력 영역