Exception -5- (Extable)

<kernel v5.4>

Extable

Exception Table(extable)에는 프로세스 주소 공간(process address space)을 access하는 커널 API를 사용한 코드의 주소와 해당 API의 예외 처리 코드의 주소가 함께 담겨 있다.

  • 커널이 프로세스용으로 할당된 페이지를 읽다 에러가 발생한 경우 페이지 테이블에 아직 로드(lazy load)가 되어 있지 않아 추가적으로 로드를 해야 하는 경우도 있고 그렇지 않고 정말 access 할 수 없는 공간으로 access를 시도하다 발생한 exception인 경우도 있다. 따라서 이에 대한 처리를 하기 위한 코드들이 호출될 수 있도록 설계되었다.

프로세스가 특정 주소를 access 하다가 exception이 발생하는 여러 fault(ARM32에서는 fsr_info[], ARM64에서는 fault_info[])별 처리 핸들러 함수를 실행시킬 때 extable을 검사하여 에러 진원지와 동일한 엔트리를 찾은 경우 해당 fixup 코드를 실행시킨다.

  • 다음은 fsr_info[]에 연결되어 있는 처리함수들이다.
    • do_translation_fault()
      • “section translation fault”
    • do_page_fault()
      • “page translation fault”
      • “page permission fault”
    • do_sect_fault()
      • “section permission fault”

 

다음 함수들에서 .fixup 섹션과 __ex_table 섹션을 사용한다.

  • get_user(), strlen_user(), strnlen_user()
    • __get_user_asm()
  • put_user(), clear_user()
    • __put_user_asm()
  • 이 외에 영역 검사 및 futex 등 몇 개 더 있다.

 


초기화 (정렬)

Main Exception Table이 정렬되어 있지 않은 경우 정렬한다.

  • Exception Table은 다음 두 가지가 있다.
    • Main Exception Table
    • 각 Driver에서 사용하는 Exception Table
  • 컴파일 타임에 툴에 의해 Exception table을 정렬하고, 툴에 의해 main_extable_sort_needed 변수 값까지 1로 바꿔주면  커널 부트업 처리 시 정렬을 할 필요가 없다.
    • CONFIG_BUILDTIME_EXTABLE_SORT 커널 옵션을 사용하여 빌드 타임에 Exception table을 정렬하게 한다.

 

sort_main_extable-1

 

sort_main_extable()

kernel/extable.c

/* Sort the kernel's built-in exception table */
void __init sort_main_extable(void)
{
        if (main_extable_sort_needed && __stop___ex_table > __start___ex_table) {
                pr_notice("Sorting __ex_table...\n");
                sort_extable(__start___ex_table, __stop___ex_table);
        }      
}

Main 커널에서 사용하는 Exception table을 정렬되어 있지 않은 경우 정렬한다.

 

kernel/extable.c

/* Cleared by build time tools if the table is already sorted. */
u32 __initdata __visible main_extable_sort_needed = 1;

 

sort_extable()

kernel/extable.c

void sort_extable(struct exception_table_entry *start,
                  struct exception_table_entry *finish)
{       
        sort(start, finish - start, sizeof(struct exception_table_entry),
             cmp_ex_sort, swap_ex);
}

Exception table의 시작 부터 끝 까지 정렬한다.

  • swap_ex 매크로는 ARCH_HAS_RELATIVE_EXTABLE이 사용되는 x86, powerpc, s390, arm64 아키텍처 등에서만 generic한 swap_ex() 함수를 사용하고, 그렇지 않은 경우 NULL로 치환된다.

 

cmp_ex_sort() – generic

lib/extable.c

/*      
 * The exception table needs to be sorted so that the binary
 * search that we use to find entries in it works properly.
 * This is used both for the kernel exception table and for
 * the exception tables of modules that get loaded.
 */
static int cmp_ex(const void *a, const void *b)
{       
        const struct exception_table_entry *x = a, *y = b;
        
        /* avoid overflow */
        if (ex_to_insn(x) > ex_to_insn(y))
                return 1;
        if (ex_to_insn(x) < ex_to_insn(y))
                return -1;
        return 0;
}

a와 b를 비교한다.

  • sort_extable() 함수가 ARCH_HAS_SORT_EXTABLE 가 사용되지 않는 아키텍처에서 사용할 수 있는 generic 루틴이다.
    • sparc 아키텍처만 ARCH_HAS_SORT_EXTABLE을 지원하고, 나머지는 위의 generic 함수를 사용한다.

 

swap_ex() – generic

lib/extable.c

static void swap_ex(void *a, void *b, int size)
{
        struct exception_table_entry *x = a, *y = b, tmp;
        int delta = b - a;

        tmp = *x;
        x->insn = y->insn + delta;
        y->insn = tmp.insn - delta;

#ifdef swap_ex_entry_fixup
        swap_ex_entry_fixup(x, y, tmp, delta);
#else
        x->fixup = y->fixup + delta;
        y->fixup = tmp.fixup - delta;
#endif
}

a와 b를 치환한다.

 

__ex_table – ARM

arch/arm/kernel/vmlinux.lds.S

#ifdef CONFIG_DEBUG_RODATA
        . = ALIGN(1<<SECTION_SHIFT);
#endif
        RO_DATA(PAGE_SIZE)

        . = ALIGN(4);
        __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {
                __start___ex_table = .;
#ifdef CONFIG_MMU
                *(__ex_table)
#endif
                __stop___ex_table = .;
        }

__ex_table은 .rodata 섹션 뒤에 위치한다.

 

__ex_table – ARM64

arch/arm64/kernel/vmlinux.lds.S

        .text : {                       /* Real text segment            */
                _stext = .;             /* Text and read-only data      */
                        __exception_text_start = .;
                        *(.exception.text)
                        __exception_text_end = .;
                        IRQENTRY_TEXT
                        SOFTIRQENTRY_TEXT
                        ENTRY_TEXT
                        TEXT_TEXT
                        SCHED_TEXT
                        CPUIDLE_TEXT
                        LOCK_TEXT
                        KPROBES_TEXT
                        HYPERVISOR_TEXT
                        IDMAP_TEXT
                        HIBERNATE_TEXT
                        TRAMP_TEXT
                        *(.fixup)
                        *(.gnu.warning)
                . = ALIGN(16);
                *(.got)                 /* Global offset table          */
        }

        . = ALIGN(SEGMENT_ALIGN);
        _etext = .;                     /* End of text section */

        RO_DATA(PAGE_SIZE)              /* everything from this point to     */
        EXCEPTION_TABLE(8)              /* __init_begin will be marked RO NX */
        NOTES

__ex_table은 EXCEPTION_TABLE() 매크로로 정의되고, .rodata 섹션 뒤에 위치한다.

 

include/asm-generic/vmlinux.lds.h

/*
 * Exception table
 */
define EXCEPTION_TABLE(align)                                          \
        . = ALIGN(align);                                               \
        __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {               \
                __start___ex_table = .;                                 \
                KEEP(*(__ex_table))                                     \
                __stop___ex_table = .;                                  \
        }

 


Exception 처리

다음 그림은 커널 코드가 process space에 있는 한 페이지에 접근하다 exception이 발생한 경우를 보여준다.

  • 페이지를 access할 때 exception이 발생하는 경우 Data Abort 또는 Prefetch Abort 등의 exception이 발생하고 해당 exception vector로 jump를 하여 관련 함수를 수행한다.

exception-table-1

 

search_exception_tables()

kernel/extable.c

/* Given an address, look for it in the exception tables. */
const struct exception_table_entry *search_exception_tables(unsigned long addr)
{
        const struct exception_table_entry *e;

        e = search_kernel_exception_table(addr);
        if (!e)
                e = search_module_extables(addr);
        return e;
}

Main exception table과 전체 모듈에 있는 exception table의 insn 가상주소가 인수 addr와 같은 경우 해당 엔트리를 반환하고 검색되지 않는 경우 null을 리턴한다.

 

search_kernel_exception_table()

lib/extable.c

/* Given an address, look for it in the kernel exception table */
const
struct exception_table_entry *search_kernel_exception_table(unsigned long addr)
{
        return search_extable(__start___ex_table,
                              __stop___ex_table - __start___ex_table, addr);
}

메인 커널에 위치한 exception table을 대상으로 가상 주소 @addr가 검색되는 경우 해당 엔트리를 반환한다. 그 외의 경우 null을 반환한다.

 

search_extable() – generic

lib/extable.c

/*
 * Search one exception table for an entry corresponding to the
 * given instruction address, and return the address of the entry,
 * or NULL if none is found.
 * We use a binary search, and thus we assume that the table is
 * already sorted.
 */
const struct exception_table_entry *
search_extable(const struct exception_table_entry *base,
               const size_t num,
               unsigned long value)
{
        return bsearch(&value, base, num,
                       sizeof(struct exception_table_entry), cmp_ex_search);
}

@base에 위치한 exception 테이블에서 가상 주소 @value를 검색하되 최대 @num 엔트리 수만큼 검색한다.

  • ARCH_HAS_SEARCH_EXTABLE을 사용하는 sparc 등의 아키텍처를 제외한 아키텍처들은 위의 generic 함수를 사용한다.

 

bsearch()

lib/bsearch.c

/*
 * bsearch - binary search an array of elements
 * @key: pointer to item being searched for
 * @base: pointer to first element to search
 * @num: number of elements
 * @size: size of each element
 * @cmp: pointer to comparison function
 *
 * This function does a binary search on the given array.  The
 * contents of the array should already be in ascending sorted order
 * under the provided comparison function.
 *
 * Note that the key need not have the same type as the elements in
 * the array, e.g. key could be a string and the comparison function
 * could compare the string with the struct's name field.  However, if
 * the key and elements in the array are of the same type, you can use
 * the same comparison function for both sort() and bsearch().
 */
void *bsearch(const void *key, const void *base, size_t num, size_t size,
              int (*cmp)(const void *key, const void *elt))
{
        const char *pivot;
        int result;

        while (num > 0) {
                pivot = base + (num >> 1) * size;
                result = cmp(key, pivot);

                if (result == 0)
                        return (void *)pivot;

                if (result > 0) {
                        base = pivot + size;
                        num--;
                }
                num >>= 1;
        }

        return NULL;
}
EXPORT_SYMBOL(bsearch);
NOKPROBE_SYMBOL(bsearch);

바이너리 검색을 수행한다.

 

search_module_extables()

kernel/module.c

/* Given an address, look for it in the module exception tables. */
const struct exception_table_entry *search_module_extables(unsigned long addr)
{
        const struct exception_table_entry *e = NULL;
        struct module *mod;

        preempt_disable();
        mod = __module_address(addr);
        if (!mod)
                goto out;

        if (!mod->num_exentries)
                goto out;

        e = search_extable(mod->extable,
                           mod->num_exentries,
                           addr);
out:
        preempt_enable();

        /*
         * Now, if we found one, we are running inside it now, hence
         * we cannot unload the module, hence no refcnt needed.
         */
        return e;
}

전체 모듈을 루프를 돌며 각각의 모듈에 있는 exception table의 insn 가상주소가 인수 addr와 같은 경우 해당 엔트리를 반환하고 검색되지 않는 경우 null을 리턴한다.

 


구조체 및 전역변수

exception_table_entry 구조체 – generic

include/asm-generic/extable.h

/*
 * The exception table consists of pairs of addresses: the first is the
 * address of an instruction that is allowed to fault, and the second is
 * the address at which the program should continue.  No registers are
 * modified, so it is entirely up to the continuation code to figure out
 * what to do.
 *
 * All the routines below use bits of fixup code that are out of line
 * with the main instruction path.  This means when everything is well,
 * we don't even have to jump over them.  Further, they do not intrude
 * on our cache or tlb entries.
 */

struct exception_table_entry
{
        unsigned long insn, fixup;
};
  • insn
    • process(user) space에 접근하는 커널 API의 가상 주소
  • fixup
    • 해당 API에 대한 예외 처리 코드를 가리키는 가상 주소

exception_table_entry 구조체 – ARM64

arch/arm64/include/asm/extable.h

struct exception_table_entry
{
        int insn, fixup;
};

ARM64의 경우 generic 코드와 다르게 unsigned long 대신 int를 사용하는 것을 알 수 있다.

 

fsr_info[] – ARM32

arch/arm/mm/fsr-2level.c

static struct fsr_info fsr_info[] = {
        /*
         * The following are the standard ARMv3 and ARMv4 aborts.  ARMv5
         * defines these to be "precise" aborts.
         */
        { do_bad,               SIGSEGV, 0,             "vector exception"                 },
        { do_bad,               SIGBUS,  BUS_ADRALN,    "alignment exception"              },
        { do_bad,               SIGKILL, 0,             "terminal exception"               },
        { do_bad,               SIGBUS,  BUS_ADRALN,    "alignment exception"              },
        { do_bad,               SIGBUS,  0,             "external abort on linefetch"      },
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "section translation fault"        },
        { do_bad,               SIGBUS,  0,             "external abort on linefetch"      },
        { do_page_fault,        SIGSEGV, SEGV_MAPERR,   "page translation fault"           },
        { do_bad,               SIGBUS,  0,             "external abort on non-linefetch"  },
        { do_bad,               SIGSEGV, SEGV_ACCERR,   "section domain fault"             },
        { do_bad,               SIGBUS,  0,             "external abort on non-linefetch"  },
        { do_bad,               SIGSEGV, SEGV_ACCERR,   "page domain fault"                },
        { do_bad,               SIGBUS,  0,             "external abort on translation"    },
        { do_sect_fault,        SIGSEGV, SEGV_ACCERR,   "section permission fault"         },
        { do_bad,               SIGBUS,  0,             "external abort on translation"    },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "page permission fault"            },
        /*
         * The following are "imprecise" aborts, which are signalled by bit
         * 10 of the FSR, and may not be recoverable.  These are only
         * supported if the CPU abort handler supports bit 10.
         */
        { do_bad,               SIGBUS,  0,             "unknown 16"                       },
        { do_bad,               SIGBUS,  0,             "unknown 17"                       },
        { do_bad,               SIGBUS,  0,             "unknown 18"                       },
        { do_bad,               SIGBUS,  0,             "unknown 19"                       },
        { do_bad,               SIGBUS,  0,             "lock abort"                       }, /* xscale */
        { do_bad,               SIGBUS,  0,             "unknown 21"                       },
        { do_bad,               SIGBUS,  BUS_OBJERR,    "imprecise external abort"         }, /* xscale */              
        { do_bad,               SIGBUS,  0,             "unknown 23"                       },
        { do_bad,               SIGBUS,  0,             "dcache parity error"              }, /* xscale */              
        { do_bad,               SIGBUS,  0,             "unknown 25"                       },
        { do_bad,               SIGBUS,  0,             "unknown 26"                       },
        { do_bad,               SIGBUS,  0,             "unknown 27"                       },
        { do_bad,               SIGBUS,  0,             "unknown 28"                       },
        { do_bad,               SIGBUS,  0,             "unknown 29"                       },
        { do_bad,               SIGBUS,  0,             "unknown 30"                       },
        { do_bad,               SIGBUS,  0,             "unknown 31"                       },
};

exception이 발생했을 때 fault가 32개로 구분되며 해당 fault 핸들러 함수가 등록된다.

  • 3레벨 페이지 테이블을 사용하는 경우 arch/arm/mm/fsr-3level.c 파일을 참고한다.

 

fault_info[] – ARM64

arch/arm64/mm/fault.c

static const struct fault_info fault_info[] = {
        { do_bad,               SIGKILL, SI_KERNEL,     "ttbr address size fault"       },
        { do_bad,               SIGKILL, SI_KERNEL,     "level 1 address size fault"    },
        { do_bad,               SIGKILL, SI_KERNEL,     "level 2 address size fault"    },
        { do_bad,               SIGKILL, SI_KERNEL,     "level 3 address size fault"    },
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 0 translation fault"     },
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 1 translation fault"     },
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 2 translation fault"     },
        { do_translation_fault, SIGSEGV, SEGV_MAPERR,   "level 3 translation fault"     },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 8"                     },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 1 access flag fault"     },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 2 access flag fault"     },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 3 access flag fault"     },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 12"                    },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 1 permission fault"      },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 2 permission fault"      },
        { do_page_fault,        SIGSEGV, SEGV_ACCERR,   "level 3 permission fault"      },
        { do_sea,               SIGBUS,  BUS_OBJERR,    "synchronous external abort"    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 17"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 18"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 19"                    },
        { do_sea,               SIGKILL, SI_KERNEL,     "level 0 (translation table walk)"      },
        { do_sea,               SIGKILL, SI_KERNEL,     "level 1 (translation table walk)"      },
        { do_sea,               SIGKILL, SI_KERNEL,     "level 2 (translation table walk)"      },
        { do_sea,               SIGKILL, SI_KERNEL,     "level 3 (translation table walk)"      },
        { do_sea,               SIGBUS,  BUS_OBJERR,    "synchronous parity or ECC error" },    // RR
eserved when RAS is implemented
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 25"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 26"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 27"                    },
        { do_sea,               SIGKILL, SI_KERNEL,     "level 0 synchronous parity error (translatii
on table walk)"     },      // Reserved when RAS is implemented
        { do_sea,               SIGKILL, SI_KERNEL,     "level 1 synchronous parity error (translatii
on table walk)"     },      // Reserved when RAS is implemented
        { do_sea,               SIGKILL, SI_KERNEL,     "level 2 synchronous parity error (translatii
on table walk)"     },      // Reserved when RAS is implemented
        { do_sea,               SIGKILL, SI_KERNEL,     "level 3 synchronous parity error (translatii
on table walk)"     },      // Reserved when RAS is implemented
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 32"                    },
        { do_alignment_fault,   SIGBUS,  BUS_ADRALN,    "alignment fault"               },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 34"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 35"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 36"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 37"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 38"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 39"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 40"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 41"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 42"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 43"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 44"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 45"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 46"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 47"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "TLB conflict abort"            },
        { do_bad,               SIGKILL, SI_KERNEL,     "Unsupported atomic hardware update fault"
    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 50"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 51"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "implementation fault (lockdown abort)" },
        { do_bad,               SIGBUS,  BUS_OBJERR,    "implementation fault (unsupported exclusivee
)" },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 54"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 55"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 56"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 57"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 58"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 59"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 60"                    },
        { do_bad,               SIGKILL, SI_KERNEL,     "section domain fault"          },
        { do_bad,               SIGKILL, SI_KERNEL,     "page domain fault"             },
        { do_bad,               SIGKILL, SI_KERNEL,     "unknown 63"                    },
};

 

참고

 

 

 

Exception -1- (ARM32 Vector)

<kernel v5.4>

ARM32 Exception Vector

non-secure PL1에서 동작하는 리눅스 커널 위주로 벡터테이블을 설명한다.

특징

  • irq 처리
    • irq 처리 중 fiq 처리는 가능하지만 irq 재진입(irq preemption)은 허용하지 않는다.
    • ARM32 아키텍처는 irq preemption을 허용하지만 ARM 리눅스 커널이 이를 허용하지 않는 설계로 구현되어 있다.
    • ARM64 아키텍처는 특수한 Pesudo-NMI를 사용하는 시스템에서만 nmi 관련 디바이스에 한해 irq preemption을 허용한다.
  • fiq 처리
    • 리눅스 커널의 fiq 핸들러 함수인 handle_fiq_as_nmi()에 처리 코드를 추가하여 사용하여야 한다.
      • 디폴트 처리로 아무것도 하지 않는다. (nop)
      • 예) rpi2의 경우 usb 호스트 드라이버(dwc_otg)에서 사용한다.
    • 일반적으로 secure 펌웨어에서 fiq를 처리하고, 리눅스 커널에서는 fiq를 처리하지 않는다.

 

모드별 Exception 벡터 테이블

다음 그림은 각 모드별 ARM Exception Vector 테이블을 보여준다.

  • 붉은 박스가 하이퍼 모드를 사용하지 않을 때의 리눅스 커널이 동작하는 벡터 테이블을 보여준다.

 

Exception 벡터 테이블 주소

벡터 테이블의 주소는 arm 모드 및 Security Extension 사용 유무에 따라 각각 다르다.

  • Security Extension 미사용
    • low 벡터 주소
      • SCTLR.V=0
      • 0x0000_0000
    • high 벡터 주소
      • SCTLR.V=1
      • 0xffff_0000
  • Security Extension 사용
    • non-secure 및 secure 공통
      • VBAR 지정한 주소
        • SCTLR.V=0
      • high 벡터 주소
        • SCTLR.V=1
    • Monitor
      • MVBAR 지정한 주소
  • Hyper Extensino 사용
    • HVBAR 지정한 주소

 

Exception 시 CPU 모드별 스택

  • irq, fiq, abt(pabt & babt), und
    • ARM32 리눅스 커널은 위의 exception이 발생하는 경우 각각 3 워드 초소형 스택을 사용하여 돌아갈 주소등을 저장하고, SVC 모드로 전환한 후 커널 스택을 사용한다.
  • svc
    • 커널 스택
      • 커널 스레드 동작 중에 exception 발생한 경우 부트업 시 생성한 커널용 스택을 사용한다.
  • usr
    • 유저용 커널 스택
      • 유저 process가 동작 중이었으면 유저 process마다 유저 스택과 커널 스택이 생성되며, exception이 발생하는 경우 동작 중인 유저 process용 커널 스택을 사용한다.

 

Exception Stub 흐름

ARM에서 Exception이 발생하면 CPU는 지정된 벡터테이블(low/high)로 강제 jump되고 해당 엔트리에는 각 exception을 처리할 수 있는 코드로의 branch 코드가  수행된다.

 

아래 그림은 8개의 exception vector entry 중 5개 엔트리의 경우 각각 16개의 모드 테이블을 갖고 있고 exception 되기 전의 모드를 인덱스로 하여 해당 루틴으로 이동하는 것을 보여준다.

  • stub 코드들은 벡터로 부터 1 페이지 아래 위치한 주소에 복사되어 사용된다.

 

Vector 선언

__vectors_start

arch/arm/kernel/entry-armv.S

        .section .vectors, "ax", %progbits
.L__vectors_start:
        W(b)    vector_rst
        W(b)    vector_und
        W(ldr)  pc, __vectors_start + 0x1000
        W(b)    vector_pabt
        W(b)    vector_dabt
        W(b)    vector_addrexcptn
        W(b)    vector_irq
        W(b)    vector_fiq
  • 8개의 exception vector 엔트리에는 각각을 처리할 수 있는 stub 코드로 이동할 수 있는 branch 코드로 되어 있다.
  • 3번 째 엔트리의 경우 SVC 엔트리로 syscall을 담당한다. 벡터 위치+0x1000에 담겨있는 주소로 이동하는 코드가 있는데 해당 위치는 vector_swi 변수 주소를 담고 있다.

 

Vector가 저장되는 섹션 위치

arch/arm/kernel/vmlinux.lds.S

#ifdef CONFIG_STRICT_KERNEL_RWX
        . = ALIGN(1<<SECTION_SHIFT);
#else
        . = ALIGN(PAGE_SIZE);
#endif
        __init_begin = .;

        ARM_VECTORS
        INIT_TEXT_SECTION(8)
        .exit.text : {
                ARM_EXIT_KEEP(EXIT_TEXT)
        }
        .init.proc.info : {
                ARM_CPU_DISCARD(PROC_INFO)
        }
        .init.arch.info : {
                __arch_info_begin = .;
                *(.arch.info.init)
                __arch_info_end = .;
        }
        .init.tagtable : {
                __tagtable_begin = .;
                *(.taglist.init)
                __tagtable_end = .;
        }
#ifdef CONFIG_SMP_ON_UP
        .init.smpalt : {
                __smpalt_begin = .;
                *(.alt.smp.init)
                __smpalt_end = .;
        }
#endif
        .init.pv_table : {
                __pv_table_begin = .;
                *(.pv_table)
                __pv_table_end = .;
        }

        INIT_DATA_SECTION(16)
  • __init_begin 바로 다음에 ARM_VECTORS 매크로가 위치한 것을 확인할 수 있다.

 

arch/arm/kernel/vmlinux.lds.h

/*
 * The vectors and stubs are relocatable code, and the
 * only thing that matters is their relative offsets
 */
#define ARM_VECTORS                                                     \
        __vectors_start = .;                                            \
        .vectors 0xffff0000 : AT(__vectors_start) {                     \
                *(.vectors)                                             \
        }                                                               \
        . = __vectors_start + SIZEOF(.vectors);                         \
        __vectors_end = .;                                              \
                                                                        \
        __stubs_start = .;                                              \
        .stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {            \
                *(.stubs)                                               \
        }                                                               \
        . = __stubs_start + SIZEOF(.stubs);                             \
        __stubs_end = .;                                                \
                                                                        \
        PROVIDE(vector_fiq_offset = vector_fiq - ADDR(.vectors));
  • 0xffff_0000 주소로 시작하는 __vectors_start ~ __vectors_end 사이의 .vectors 섹션에 벡터가 포함됨을 알 수 있다.
  • 벡터 + 0x1000_0000 주소로 시작하는 __stubs_start ~ __stubs_end 사이의 .stubs 섹션에 syscall 관련 코드가 포함된다.

 

Vector 설치

exception 벡터 및 stub 코드들은 아래의 루틴에서 설치된다.

  • paging_init()->devicemaps_init()->early_trap_init()

 


Vector 핸들러

 

vector_stub 매크로

arch/arm/kernel/entry-armv.S

/*
 * Vector stubs.
 *
 * This code is copied to 0xffff1000 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not exceed
 * a page size.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
        .macro  vector_stub, name, mode, correction=0
        .align  5

vector_\name:
        .if \correction
        sub     lr, lr, #\correction
        .endif

        @
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
        @ (parent CPSR)
        @
        stmia   sp, {r0, lr}            @ save r0, lr
        mrs     lr, spsr
        str     lr, [sp, #8]            @ save spsr

        @
        @ Prepare for SVC32 mode.  IRQs remain disabled.
        @
        mrs     r0, cpsr
        eor     r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
        msr     spsr_cxsf, r0

        @
        @ the branch table must immediately follow this code
        @
        and     lr, lr, #0x0f
 THUMB( adr     r0, 1f                  )
 THUMB( ldr     lr, [r0, lr, lsl #2]    )
        mov     r0, sp
 ARM(   ldr     lr, [pc, lr, lsl #2]    )
        movs    pc, lr                  @ branch to handler in SVC mode
ENDPROC(vector_\name)

        .align 2
        @ handler addresses follow this label
1:
        .endm

Exception이 발생되면 8개의 exception vector 엔트리 중 해당하는 exception 위치로 jump 되는데 이 중 5개의 exception 엔트리에서 사용되는 공통 매크로 루틴에서는 exception 벡터로 점프되기 직전의 최대 16개 모드별 테이블에 해당하는 루틴을 찾아 수행한다. 수행 전에 svc 모드로 변환한다.

  • correction은 루틴이 수행된 후 다시 리턴될 주소를 조정한다. exception이 발생될 때의 파이프라인 위치에 따라 보정 주소가 다르다.
    • IRQ, FIQ, Data Abort의 경우 리턴 주소-4로 보정한다.
    • Prefetch Abort의 경우 리턴 주소-8로 보정한다.
    • Undefined 및 SWI의 경우 보정 없이 리턴 주소를 사용한다.
    • Reset은 리턴하지 않으므로 해당사항 없다.
  • irq, fiq, abt, und 모드에서 사용하는 스택
    • cpu_init() 함수에서 3 word 초소형 static 배열을 4개의 스택으로 설정하였다.
    • 이 스택에는 다음 3가지 항목을 보관한다.
      • scratch되어 파괴될 r0 레지스터
      • 되돌아갈 주소가 담긴 lr 레지스터(exception 시 pc -> lr_<exception>에 복사되는데 이 값에 correction 값을 보정하여 저장해둔다.)
      • 복원되어야 할 psr 레지스터 (exception 시 cpsr -> spsr_<exception>에 복사되는데 이 값을 저장해둔다.)
  • ldm/stm에서 사용하는 ^ 접미사 용도
    • stm
      • user 모드의 sp, lr 레지스터가 뱅크되어 일반적으로는 다른 모드에서  접근할 수 없어서 user 모드의 sp, lr 레지스터 값을 access하고자할 때 사용
    • ldm
      • pc가 포함된 경우 spsr->cpsr로 복사된다
      • 기존 모드로 복귀할 때 spsr(백업받아 두었던) -> cpsr로 복사한다.

 

.stubs 섹션의 시작

arch/arm/kernel/entry-armv.S

        .section .stubs, "ax", %progbits
__stubs_start:
        @ This must be the first word
        .word   vector_swi

stub 영역은 시스템 콜이 호출되는 vector_swi 부터 시작하여 다음과 같은 순으로 위치한다.

  • vector_swi
  • vector_rst
  • vector_irq
  • vector_dabt
  • vector_pabt
  • vector_und
  • vector_addrexcptn
  • vector_fiq

 

vector_rst (Reset)

arch/arm/kernel/entry-armv.S

vector_rst:
 ARM(   swi     SYS_ERROR0      )
 THUMB( svc     #0              )
 THUMB( nop                     )
        b       vector_und

SYS_ERROR0 소프트웨어 인터럽트를 발생시켜 시스템 콜을 호출하게 한다.

 

vector_irq (IRQ)

arch/arm/kernel/entry-armv.S

/*
 * Interrupt dispatcher
 */
        vector_stub     irq, IRQ_MODE, 4

        .long   __irq_usr                       @  0  (USR_26 / USR_32)
        .long   __irq_invalid                   @  1  (FIQ_26 / FIQ_32)
        .long   __irq_invalid                   @  2  (IRQ_26 / IRQ_32)
        .long   __irq_svc                       @  3  (SVC_26 / SVC_32)
        .long   __irq_invalid                   @  4
        .long   __irq_invalid                   @  5
        .long   __irq_invalid                   @  6
        .long   __irq_invalid                   @  7
        .long   __irq_invalid                   @  8
        .long   __irq_invalid                   @  9
        .long   __irq_invalid                   @  a
        .long   __irq_invalid                   @  b
        .long   __irq_invalid                   @  c
        .long   __irq_invalid                   @  d
        .long   __irq_invalid                   @  e
        .long   __irq_invalid                   @  f
  • USR 모드에서 인터럽트가 발생한 경우 __irq_usr 루틴을 호출한다.
  • FIQ 및 IRQ 모드에서 인터럽트가 발생한 경우 __irq_invalid 루틴을 호출한다.
  • SVC 모드에서 인터럽트가 발생한 경우 __irq_svc 루틴을 호출한다.

 

vector_dabt (Data Abort)

arch/arm/kernel/entry-armv.S

/*
 * Data abort dispatcher
 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
 */
        vector_stub     dabt, ABT_MODE, 8

        .long   __dabt_usr                      @  0  (USR_26 / USR_32)
        .long   __dabt_invalid                  @  1  (FIQ_26 / FIQ_32)
        .long   __dabt_invalid                  @  2  (IRQ_26 / IRQ_32)
        .long   __dabt_svc                      @  3  (SVC_26 / SVC_32)
        .long   __dabt_invalid                  @  4
        .long   __dabt_invalid                  @  5
        .long   __dabt_invalid                  @  6
        .long   __dabt_invalid                  @  7
        .long   __dabt_invalid                  @  8
        .long   __dabt_invalid                  @  9
        .long   __dabt_invalid                  @  a
        .long   __dabt_invalid                  @  b
        .long   __dabt_invalid                  @  c
        .long   __dabt_invalid                  @  d
        .long   __dabt_invalid                  @  e
        .long   __dabt_invalid                  @  f

 

vector_pabt (Prefetch Abort)

arch/arm/kernel/entry-armv.S

/*
 * Prefetch abort dispatcher
 * Enter in ABT mode, spsr = USR CPSR, lr = USR PC
 */
        vector_stub     pabt, ABT_MODE, 4

        .long   __pabt_usr                      @  0 (USR_26 / USR_32)
        .long   __pabt_invalid                  @  1 (FIQ_26 / FIQ_32)
        .long   __pabt_invalid                  @  2 (IRQ_26 / IRQ_32)
        .long   __pabt_svc                      @  3 (SVC_26 / SVC_32)
        .long   __pabt_invalid                  @  4
        .long   __pabt_invalid                  @  5
        .long   __pabt_invalid                  @  6
        .long   __pabt_invalid                  @  7
        .long   __pabt_invalid                  @  8
        .long   __pabt_invalid                  @  9
        .long   __pabt_invalid                  @  a
        .long   __pabt_invalid                  @  b
        .long   __pabt_invalid                  @  c
        .long   __pabt_invalid                  @  d
        .long   __pabt_invalid                  @  e
        .long   __pabt_invalid                  @  f

 

vector_und (Undefined Instruction)

arch/arm/kernel/entry-armv.S

/*
 * Undef instr entry dispatcher
 * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 */
        vector_stub     und, UND_MODE

        .long   __und_usr                       @  0 (USR_26 / USR_32)
        .long   __und_invalid                   @  1 (FIQ_26 / FIQ_32)
        .long   __und_invalid                   @  2 (IRQ_26 / IRQ_32)
        .long   __und_svc                       @  3 (SVC_26 / SVC_32)
        .long   __und_invalid                   @  4
        .long   __und_invalid                   @  5
        .long   __und_invalid                   @  6
        .long   __und_invalid                   @  7
        .long   __und_invalid                   @  8
        .long   __und_invalid                   @  9
        .long   __und_invalid                   @  a
        .long   __und_invalid                   @  b
        .long   __und_invalid                   @  c
        .long   __und_invalid                   @  d
        .long   __und_invalid                   @  e
        .long   __und_invalid                   @  f

        .align  5

 

vector_addrexcptn (Address Exception Handler)

arch/arm/kernel/entry-armv.S

/*=============================================================================
 * Address exception handler
 *-----------------------------------------------------------------------------
 * These aren't too critical.
 * (they're not supposed to happen, and won't happen in 32-bit data mode).
 */
vector_addrexcptn:
        b       vector_addrexcptn

 

vector_fiq (FIQ)

arch/arm/kernel/entry-armv.S

/*=============================================================================
 * FIQ "NMI" handler
 *-----------------------------------------------------------------------------
 * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86
 * systems.
 */
        vector_stub     fiq, FIQ_MODE, 4

        .long   __fiq_usr                       @  0  (USR_26 / USR_32)
        .long   __fiq_svc                       @  1  (FIQ_26 / FIQ_32)
        .long   __fiq_svc                       @  2  (IRQ_26 / IRQ_32)
        .long   __fiq_svc                       @  3  (SVC_26 / SVC_32)
        .long   __fiq_svc                       @  4
        .long   __fiq_svc                       @  5
        .long   __fiq_svc                       @  6
        .long   __fiq_abt                       @  7
        .long   __fiq_svc                       @  8
        .long   __fiq_svc                       @  9
        .long   __fiq_svc                       @  a
        .long   __fiq_svc                       @  b
        .long   __fiq_svc                       @  c
        .long   __fiq_svc                       @  d
        .long   __fiq_svc                       @  e
        .long   __fiq_svc                       @  f

        .globl  vector_fiq_offset
        .equ    vector_fiq_offset, vector_fiq

 

참고

 

TLB Cache (API)

 

local_flush_tlb_kernel_page()

arch/arm/include/asm/tlbflush.h

static inline void local_flush_tlb_kernel_page(unsigned long kaddr)
{
        const unsigned int __tlb_flag = __cpu_tlb_flags;

        kaddr &= PAGE_MASK;

        if (tlb_flag(TLB_WB))
                dsb(nshst);

        __local_flush_tlb_kernel_page(kaddr);
        tlb_op(TLB_V7_UIS_PAGE, "c8, c7, 1", kaddr);

        if (tlb_flag(TLB_BARRIER)) {
                dsb(nsh);
                isb();
        }
}
  • __cpu_tlb_flags
    • cpu_tlb_fns 구조체로 만들어진 전역 cpu_tlb.tlb_flags로 연결된 define 문
    • rpi2:
      • TLB_WB | TLB_BARRIER | TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE |
        TLB_V7_UIS_ASID | TLB_V7_UIS_BP
  • kaddr &= PAGE_MASK;
    • kaddr에 대해 페이지 단위로 round down 한다.
  • if (tlb_flag(TLB_WB)) dsb(nshst);
    • 아키텍처가 TLB_WB 플래그를 지원하면 dsb(nshst) 명령을 수행한다.
      •  TLB_WB
        • (1 << 31)
    • dsb(option)
      • __asm__ __volatile__ (“dsb ” #option : : : “memory”)
      • 모든 cache operation이 끝날 때까지 기다린다.
  • __local_flush_tlb_kernel_page(kaddr);
    • 요청 페이지에 대한 TLB 캐시를 flush 한다.
  • tlb_op(TLB_V7_UIS_PAGE, “c8, c7, 1”, kaddr);
    • 아키텍처가 TLB_V7_UIS_PAGE를 지원하면 TLBIMVA(unified TLB Invalidate by MVA and ASID) 명령을 수행
    • TLB_V7_UIS_PAGE
      • (1 << 20)
    • rpi2:
      • TLBIMVA가 수행된다.
  • if (tlb_flag(TLB_BARRIER)) { dsb(nsh); isb(); }
    • 아키텍처가 TLB_BARRIER를 지원하면 dsb, isb를 수행한다.

 

tlb_flag()

arch/arm/include/asm/tlbflush.h

#define tlb_flag(f)     ((always_tlb_flags & (f)) || (__tlb_flag & possible_tlb_flags & (f)))
  • f(플래그)가 always_tlb_flags에 있거나 __tlb_flag가 possible_tlb_flags에 있는 경우 true

 

tlb_op()

arch/arm/include/asm/tlbflush.h

#define tlb_op(f, regs, arg)    __tlb_op(f, “p15, 0, %0, ” regs, arg)

  • f(플래그)가 always_tlb_flag에 있는 경우 TLB 레지스터에 대한 어셈블리 명령을 수행한다.
  • 그렇지 않은 경우 f가 possible_tlb_flags와 __tlb_flag 둘 다에 있는 경우 어셈블리 명령을 수행한다.

 

__tlb_op()

arch/arm/include/asm/tlbflush.h

#define __tlb_op(f, insnarg, arg)                                       \
        do {                                                            \
                if (always_tlb_flags & (f))                             \
                        asm("mcr " insnarg                              \
                            : : "r" (arg) : "cc");                      \
                else if (possible_tlb_flags & (f))                      \
                        asm("tst %1, %2\n\t"                            \
                            "mcrne " insnarg                            \
                            : : "r" (arg), "r" (__tlb_flag), "Ir" (f)   \
                            : "cc");                                    \
        } while (0)

instruction을 실행시키되 다음의 조건을 만족해야 한다.

  • arm의 모든 아키텍처가 지원하는 명령인지 비교하여 일치하는 경우
  • 현재 아키텍처가 지원하는 명령인지 비교하여 일치하는 경우에는 cpu가 지원하는 명령인지도 추가적으로 비교한다.
  • if (always_tlb_flags & (f)) asm(“mcr ” insnarg : : “r” (arg) : “cc”);
    • 요청 flag가 alway_tlb_flags에 있는 경우 insnarg 어셈블리 명령을 수행한다.
  • else if (possible_tlb_flags & (f)) asm(“tst %1, %2\n\t” “mcrne ” insnarg : : “r” (arg), “r” (__tlb_flag), “Ir” (f) : “cc”);
    • 그렇지 않고 요청 flag가 possible_tlb_flags에 있을 때
  • asm(“tst %1, %2\n\t” “mcrne ” insnarg : : “r” (arg), “r” (__tlb_flag), “Ir” (f) : “cc”);
    • flag가 __tlb_flag에 있는 경우 insarg 어셈블리 명령을 수행한다.

TLB 플래그들

always_tlb_flags
#define always_tlb_flags        (v4_always_flags & \
                                 v4wbi_always_flags & \
                                 fr_always_flags & \
                                 v4wb_always_flags & \
                                 fa_always_flags & \
                                 v6wbi_always_flags & \
                                 v7wbi_always_flags)

arm의 모든 아키텍처가 지원하는 명령어

  • TLB_WB | TLB_BARRIER

 

possible_tlb_flags
#define possible_tlb_flags      (v4_possible_flags | \
                                 v4wbi_possible_flags | \
                                 fr_possible_flags | \
                                 v4wb_possible_flags | \
                                 fa_possible_flags | \
                                 v6wbi_possible_flags | \
                                 v7wbi_possible_flags)

arm 아키텍처 중 현재 아키텍처가 지원하는 명령어

  • ARMv7
    • TLB_WB | TLB_BARRIER | TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE |
      TLB_V7_UIS_ASID | TLB_V7_UIS_BP | TLB_DCLEAN | TLB_V6_U_FULL | TLB_V6_U_PAGE | TLB_V6_U_ASID | TLB_V6_BP

 

__tlb_flag

현재 cpu가 지원하는 명령어 플래그

  • rpi2:
    • TLB_WB | TLB_BARRIER | TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE | TLB_V7_UIS_ASID | TLB_V7_UIS_BP

 

__local_flush_tlb_kernel_page()

arch/arm/include/asm/tlbflush.h

static inline void __local_flush_tlb_kernel_page(unsigned long kaddr)
{
        const int zero = 0;
        const unsigned int __tlb_flag = __cpu_tlb_flags;

        tlb_op(TLB_V4_U_PAGE, "c8, c7, 1", kaddr);
        tlb_op(TLB_V4_D_PAGE, "c8, c6, 1", kaddr);
        tlb_op(TLB_V4_I_PAGE, "c8, c5, 1", kaddr);
        if (!tlb_flag(TLB_V4_I_PAGE) && tlb_flag(TLB_V4_I_FULL))
                asm("mcr p15, 0, %0, c8, c5, 0" : : "r" (zero) : "cc");

        tlb_op(TLB_V6_U_PAGE, "c8, c7, 1", kaddr);
        tlb_op(TLB_V6_D_PAGE, "c8, c6, 1", kaddr);
        tlb_op(TLB_V6_I_PAGE, "c8, c5, 1", kaddr);
}
  • 아키텍처에 맞는 TLB operation 코드가 선택 수행된다.
    • TLBIMVA(unified TLB Invalidate by MVA and ASID)
    • DTLBIMVA(Data TLB Invalidate by MVA and ASID)
    • ITLBIMVA(Instruction TLB Invalidate by MVA and ASID)
    • ITLBIALL(Instruction TLB Invalidate all)
    • rpi2:
      • 해당되는 플래그가 없어서 아무것도 수행하지 않는다.

 

clean_pmd_entry()

arch/arm/include/asm/tlbflush.h

static inline void clean_pmd_entry(void *pmd)
{
        const unsigned int __tlb_flag = __cpu_tlb_flags;

        tlb_op(TLB_DCLEAN, "c7, c10, 1  @ flush_pmd", pmd);
        tlb_l2_op(TLB_L2CLEAN_FR, "c15, c9, 1  @ L2 flush_pmd", pmd);
}

TLB 엔트리 하나를 clean 한다.

  • tlb_op(TLB_DCLEAN, “c7, c10, 1  @ flush_pmd”, pmd);
    • 아키텍처가 TLB_DCLEAN을 지원하는 경우 tlb operation “mcr c7, c10, 1″을  실행한다.
      • Clean data or unified cache line by MVA to PoC
  • tlb_l2_op(TLB_L2CLEAN_FR, “c15, c9, 1  @ L2 flush_pmd”, pmd);
    • 아키텍처가 TLB_L2CLEAN_FR을 지원하는 경우 tlb operation “mcr c15, c9, 1″을  실행한다.

 

구조체

cpu_tlb_fns 구조체

struct cpu_tlb_fns {
        void (*flush_user_range)(unsigned long, unsigned long, struct vm_area_struct *);
        void (*flush_kern_range)(unsigned long, unsigned long);
        unsigned long tlb_flags;
};
  • flush_user_range
    • rpi2: v7wbi_flush_user_tlb_range() 함수를 가리킨다.
  • flush_kern_range
    • rpi2: v7wbi_flush_kern_tlb_range() 함수를 가리킨다.
  • tlb_flags
    • 현재 cpu가 지원하는 TLB 캐시 관련 플래그
    • rpi2:
      • TLB_WB | TLB_BARRIER | TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE | TLB_V7_UIS_ASID | TLB_V7_UIS_BP

참고