Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

92107
469
422724


[라즈베리파이] 인터럽트 백터 어셈블리 코드 및 동작 분석 5. 인터럽트

인터럽트가 발생하면 __irq_svc 벡터로 점프합니다. 물론 해당 프로세스는 하던 일을 멈출 수 밖에 없는데요.
그럼 인터럽트 벡터에서 어떤 동작을 하는 지 어셈블리 코드를 분석하겠습니다.

#__irq_svc 코드 리뷰
[1]: 스택 공간을 0x4C 바이트만큼 확보합니다.

[2]--[3]: 현재 실행 중인 레지스터 R0부터 R14, PC까지 스택에 푸쉬합니다.

[4]: 0x80705398 메모리 공간에 있는 메모리 덤프 0x80c089ac를 로딩합니다.
    0x80c089ac는 handle_arch_irq란 함수 포인터 역할을 하는 변수인데 이 변수에는 gic_handle_irq가 지정되어 있습니다.
crash> p -x handle_arch_irq
handle_arch_irq = $3 = 0xc0100680
crash> sym 0xc0100680
c0100680 (t) gic_handle_irq /home001/austin/src/raspberry_kernel/drivers/irqchip/irq-gic.c: 439

[5]: 라즈베리 파이 커널은 ARM 프로세서 이므로 gic_handle_irq 함수를 호출합니다.
80705320 <__irq_svc>:
80705320: e24dd04c sub sp, sp, #76 ; 0x4c  //<< --[1]
80705324: e31d0004 tst sp, #4
80705328: 024dd004 subeq sp, sp, #4
8070532c: e88d1ffe stm sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip} //<< --[2]
80705330: e8900038 ldm r0, {r3, r4, r5}
80705334: e28d7030 add r7, sp, #48 ; 0x30
80705338: e3e06000 mvn r6, #0
8070533c: e28d204c add r2, sp, #76 ; 0x4c
80705340: 02822004 addeq r2, r2, #4
80705344: e52d3004 push {r3} ; (str r3, [sp, #-4]!)
80705348: e1a0300e mov r3, lr
8070534c: e887007c stm r7, {r2, r3, r4, r5, r6}
80705350: e1a096ad lsr r9, sp, #13
80705354: e1a09689 lsl r9, r9, #13
80705358: e5990008 ldr r0, [r9, #8]  //<< --[3]
8070535c: e3a0147f mov r1, #2130706432 ; 0x7f000000
80705360: e5891008 str r1, [r9, #8]
80705364: e58d004c str r0, [sp, #76] ; 0x4c
80705368: ebeb60e3 bl 801dd6fc <trace_hardirqs_off>
8070536c: e59f1024 ldr r1, [pc, #36] ; 80705398 <__irq_svc+0x78>  //<< --[4]
80705370: e1a0000d mov r0, sp
80705374: e28fe000 add lr, pc, #0
80705378: e591f000 ldr pc, [r1]   //<< --[5]
8070537c: ebeb6091 bl 801dd5c8 <trace_hardirqs_on>
80705380: e59d104c ldr r1, [sp, #76] ; 0x4c
80705384: e5891008 str r1, [r9, #8]
80705388: e16ff005 msr SPSR_fsxc, r5
8070538c: e24d0004 sub r0, sp, #4
80705390: e1801f92 strex r1, r2, [r0]
80705394: e8ddffff ldm sp, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, sp, lr, pc}^
80705398: 80c089ac .word 0x80c089ac

#handle_arch_irq 설정  코드 리뷰
handle_arch_irq 주소는 아래 함수에서 확인할 수 있습니다. 코드를 보아 set_handle_irq 함수 호출로 handle_arch_irq 전역 변수에 함수 포인터를 지정합니다.
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
80b036d4: e1a0c00d mov ip, sp
80b036d8: e92dd800 push {fp, ip, lr, pc}
80b036dc: e24cb004 sub fp, ip, #4
if (handle_arch_irq)
80b036e0: e59f300c ldr r3, [pc, #12] ; 80b036f4 <set_handle_irq+0x20>
80b036e4: e5932000 ldr r2, [r3]
80b036e8: e3520000 cmp r2, #0
return;

handle_arch_irq = handle_irq;
80b036ec: 05830000 streq r0, [r3]
80b036f0: e89da800 ldm sp, {fp, sp, pc}
80b036f4: 80c089ac .word 0x80c089ac

gic_handle_irq 함수 포인터 지정은 아래 __gic_init_bases 함수 내 set_handle_irq(gic_handle_irq); 구문에서 수행됩니다.
[linux/drivers/irqchip/irq-gic.c]
 static int __init __gic_init_bases(struct gic_chip_data *gic,
                                    int irq_start,
                                    struct fwnode_handle *handle)
{
// .. 생략 
         if (gic == &gic_data[0]) {
                 /*
                  * Initialize the CPU interface map to all CPUs.
                  * It will be refined as each CPU probes its ID.
                  * This is only necessary for the primary GIC.
                  */
                 for (i = 0; i < NR_GIC_CPU_IF; i++)
                         gic_cpu_map[i] = 0xff;
 #ifdef CONFIG_SMP
                 set_smp_cross_call(gic_raise_softirq);
 #endif
                 cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
                                           "AP_IRQ_GIC_STARTING",
                                           gic_starting_cpu, NULL);
                 set_handle_irq(gic_handle_irq);

#CONFIG_PREEMPT 코드 리뷰
__irq_svc 어셈블리 코드에서 아래 C코드에서 보이는 CONFIG_PREEMPT 컨피그로 묶여 있는 코드가 안 보입니다. 
__irq_svc:
        svc_entry
        irq_handler

#ifdef CONFIG_PREEMPT
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        ldr     r0, [tsk, #TI_FLAGS]            @ get flags
        teq     r8, #0                          @ if preempt count != 0
        movne   r0, #0                          @ force flags to 0
        tst     r0, #_TIF_NEED_RESCHED
        blne    svc_preempt
#endif

여기서 CONFIG_PREEMPT 컨피그가 켜져 있는 지 간단히 확인하는 유용한 팁을 공유 드리겠습니다. 
#error "CONFIG_PREEMPT" 란 매크로 코드를 추가하고 컴파일을 하면 컴파일 에러가 발생하지 않습니다.
즉 이 컨피그는 꺼져 있다는 말입니다.
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S
index 9f157e7..19f3115 100644
--- a/arch/arm/kernel/entry-armv.S
+++ b/arch/arm/kernel/entry-armv.S
@@ -219,6 +219,7 @@ __irq_svc:
        irq_handler

 #ifdef CONFIG_PREEMPT
+#error "CONFIG_PREEMPT"
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        ldr     r0, [tsk, #TI_FLAGS]            @ get flags
        teq     r8, #0                          @ if preempt count != 0

다른 플렛폼에서는 CONFIG_PREEMPT 컨피그가 켜져 있는데 라즈베리파이는 리눅스 커널 교육용 SoC라
안정성(Stability)을 위해 이 피쳐를 끈 것 같습니다.

덧글

  • 공부중입니다. 2021/01/06 16:44 # 삭제 답글

    안녕하세요. 인터럽트 벡터를 분석하던 중 궁금한 것이 있어서 질문드립니다. arm64기반 보드로 공부중입니다.

    el1_irq:
    kernel_entry 1
    ffffff8008083a80:a90007e0 stpx0, x1, [sp]
    ffffff8008083a84:a9010fe2 stpx2, x3, [sp,#16]
    ffffff8008083a88:a90217e4 stpx4, x5, [sp,#32]
    ffffff8008083a8c:a9031fe6 stpx6, x7, [sp,#48]
    ffffff8008083a90:a90427e8 stpx8, x9, [sp,#64]
    ffffff8008083a94:a9052fea stpx10, x11, [sp,#80]
    ffffff8008083a98:a90637ec stpx12, x13, [sp,#96]
    ffffff8008083a9c:a9073fee stpx14, x15, [sp,#112]
    ffffff8008083aa0:a90847f0 stpx16, x17, [sp,#128]
    ffffff8008083aa4:a9094ff2 stpx18, x19, [sp,#144]
    ffffff8008083aa8:a90a57f4 stpx20, x21, [sp,#160]
    ffffff8008083aac:a90b5ff6 stpx22, x23, [sp,#176]
    ffffff8008083ab0:a90c67f8 stpx24, x25, [sp,#192]
    ffffff8008083ab4:a90d6ffa stpx26, x27, [sp,#208]
    ffffff8008083ab8:a90e77fc stpx28, x29, [sp,#224]
    ffffff8008083abc:9104c3f5 addx21, sp, #0x130
    ffffff8008083ac0:d538411c mrsx28, sp_el0
    ffffff8008083ac4:f9402394 ldrx20, [x28,#64]
    ffffff8008083ac8:f90093f4 strx20, [sp,#288]
    ffffff8008083acc:b2409bf4 movx20, #0x7fffffffff

    1.)
    __irq_svc는 스택포인터에서 레지스터용량만큼 주소를 빼고 저장을 시도하는데
    위와같이 el1_irq라는 인터럽트 벡터에서를 보면 스택포인터에 바로 저장을 시작하더라고요. 그렇게 저장을 한다면 스택에 쌓인 함수들 위에 덮어쓰는 것이 아닌지요?


    2.) kernel_entry 1 이라는 것이 무슨 의미인지 혹시 알려주실 수 있나요? 혹시 익셉션 레벨1으로 들어간다는 뜻일까요?

    3) dwc 드라이버라는 것이 구글링을 해도 usb드라이버인것은 알겠는데 확실하게 무엇인지 잘 모르겠습니다.

  • AustinKim 2021/01/06 18:42 #

    1) 아래 링크에 있는 익셉션 벡터의 코드를 보면,
    http://rousalome.egloos.com/10022288

    아래와 같이 el1_irq 레이블을 브랜치하기 전에 스택 공간을 확보하는 명령어를 확인할 수 있습니다.

    ffffff8008082280: d10503ff sub sp, sp, #0x140
    ffffff8008082284: 8b2063ff add sp, sp, x0
    ffffff8008082288: cb2063e0 sub x0, sp, x0
    ffffff800808228c: 37700080 tbnz w0, #14, ffffff800808229c <vectors+0x29c>
    ffffff8008082290: cb2063e0 sub x0, sp, x0
    ffffff8008082294: cb2063ff sub sp, sp, x0
    ffffff8008082298: 1400042a b ffffff8008083340 <el1_irq>

    el1_irq 레이블을 브랜치하기 전에 스택 공간을 확보하는 것 같습니다.

    2) kernel_entry을 아래와 같이 매크로로 정의되어 있는데요.

    https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm64/kernel/entry.S
    .macrokernel_entry, el, regsize = 64
    .ifregsize == 32
    movw0, w0// zero upper 32 bits of x0

    아규먼트로 주는 1은 익셉션 레벨을 뜻합니다.

    3) 아래 링크에 있는 내용을 참고하세요.
    http://ctrlacv.blogspot.com/2020/03/usb-dwc.html

    혹시 더 궁금한 점이 있으면 댓글을 남겨주세요.
  • 공부중입니다. 2021/01/06 19:21 # 삭제 답글

    감사합니다.
    책보고 검색할 때는 잘 안나오는데 도움주셔서 감사합니다.
  • AustinKim 2021/01/06 19:27 #

    도움이 됐다니 다행이네요.
    눈이 많이 오는데 눈길 조심하세요.
댓글 입력 영역