Exception Table (extable)

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

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

프로세스가 특정 주소를 access 하다가 exception이 발생하는 여러 fault(fsr_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”

 

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

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

exception-table-1

 

프로세스 주소 공간(process address space)을 access하는 커널 API

  • get_user(), strlen_user(), strnlen_user()
    • __get_user_asm_byte()
    • __get_user_asm_word()
  • put_user(), clear_user()
    • __put_user_asm_byte()
    • __put_user_asm_word()
    • __put_user_asm_dword()
  • 이 외에 영역 검사 및 futex 등 몇 개 더 있다.

 

copy_from_user()

arch/arm/include/asm/uaccess.h

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
        if (access_ok(VERIFY_READ, from, n))
                n = __copy_from_user(to, from, n);
        else /* security hole - plug it */
                memset(to, 0, n);
        return n;
}

process(user) address space의 가상 주소 from에서 n 바이트만큼 커널의 가상 주소 to 에 데이터를 복사한다.

 

access_ok()

arch/arm/include/asm/uaccess.h

#define access_ok(type, addr, size)     (__range_ok(addr, size) == 0)

 

__range_ok()

arch/arm/include/asm/uaccess.h

/* We use 33-bit arithmetic here... */
#define __range_ok(addr, size) ({ \
        unsigned long flag, roksum; \
        __chk_user_ptr(addr);   \
        __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \
                : "=&r" (flag), "=&r" (roksum) \
                : "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \
                : "cc"); \
        flag; })

 

__get_user_asm_byte()

arch/arm/include/asm/uaccess.h

#define __get_user_asm_byte(x, addr, err)                       \
        __asm__ __volatile__(                                   \
        "1:     " TUSER(ldrb) " %1,[%2],#0\n"                   \
        "2:\n"                                                  \
        "       .pushsection .fixup,\"ax\"\n"                   \
        "       .align  2\n"                                    \
        "3:     mov     %0, %3\n"                               \
        "       mov     %1, #0\n"                               \
        "       b       2b\n"                                   \
        "       .popsection\n"                                  \
        "       .pushsection __ex_table,\"a\"\n"                \
        "       .align  3\n"                                    \
        "       .long   1b, 3b\n"                               \
        "       .popsection"                                    \
        : "+r" (err), "=&r" (x)                                 \
        : "r" (addr), "i" (-EFAULT)                             \
        : "cc")

 

__copy_to_user()

arch/arm/lib/uaccess_with_memcpy.c

unsigned long
__copy_to_user(void __user *to, const void *from, unsigned long n)
{
        /*
         * This test is stubbed out of the main function above to keep
         * the overhead for small copies low by avoiding a large
         * register dump on the stack just to reload them right away.
         * With frame pointer disabled, tail call optimization kicks in
         * as well making this test almost invisible.
         */     
        if (n < 64)     
                return __copy_to_user_std(to, from, n);
        return __copy_to_user_memcpy(to, from, n);
}

 

__copy_to_user_memcpy()

arch/arm/lib/uaccess_with_memcpy.c

static unsigned long noinline
__copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
{
        int atomic;

        if (unlikely(segment_eq(get_fs(), KERNEL_DS))) {
                memcpy((void *)to, from, n);
                return 0;
        }

        /* the mmap semaphore is taken only if not in an atomic context */
        atomic = in_atomic();

        if (!atomic)
                down_read(&current->mm->mmap_sem);
        while (n) {
                pte_t *pte;
                spinlock_t *ptl;
                int tocopy;

                while (!pin_page_for_write(to, &pte, &ptl)) {
                        if (!atomic)
                                up_read(&current->mm->mmap_sem);
                        if (__put_user(0, (char __user *)to))
                                goto out;
                        if (!atomic)
                                down_read(&current->mm->mmap_sem);
                }

                tocopy = (~(unsigned long)to & ~PAGE_MASK) + 1;
                if (tocopy > n)
                        tocopy = n;

                memcpy((void *)to, from, tocopy);
                to += tocopy;
                from += tocopy;
                n -= tocopy;

                if (pte)
                        pte_unmap_unlock(pte, ptl);
                else
                        spin_unlock(ptl);
        }
        if (!atomic)
                up_read(&current->mm->mmap_sem);

out:
        return n;
}

 

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_extable(__start___ex_table, __stop___ex_table-1, addr);
        if (!e)
                e = search_module_extables(addr);
        return e;
}

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

 

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();
        list_for_each_entry_rcu(mod, &modules, list) {
                if (mod->state == MODULE_STATE_UNFORMED)
                        continue;
                if (mod->num_exentries == 0)
                        continue;

                e = search_extable(mod->extable,
                                   mod->extable + mod->num_exentries - 1,
                                   addr);
                if (e)
                        break;
        }
        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을 리턴한다.

 

search_extable()

lib/extable.c

#ifndef ARCH_HAS_SEARCH_EXTABLE
/*
 * 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 *first,
               const struct exception_table_entry *last,
               unsigned long value)
{
        while (first <= last) {
                const struct exception_table_entry *mid;

                mid = ((last - first) >> 1) + first;
                /*
                 * careful, the distance between value and insn
                 * can be larger than MAX_LONG:
                 */
                if (mid->insn < value)
                        first = mid + 1;
                else if (mid->insn > value)
                        last = mid - 1;
                else
                        return mid;
        }
        return NULL;
}
#endif

Exception Table의 insn 가상 주소가 인수 value로 binary 검색을 통해 인덱스를 찾아 반환한다. 검색이 실패한 경우 0을 반환한다.

 

구조체 및 전역변수

exception_table_entry 구조체

/*
 * 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에 대한 예외 처리 코드를 가리키는 가상 주소

fsr_info[]

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 핸들러 함수가 등록된다.

 

참고

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.