[리눅스커널] 비트 마스크를 어셈블리 코드로 빨리 읽는 방법 - HARDIRQ_MASK, SOFTIRQ_MASK, NMI_MASK
이번에는 비트 마스크를 C 코드가 아닌 어셈블리 코드로 읽는 방법을 소개합니다.
<< in_interrupt() 함수 소개 >>
in_interrupt() 함수는 현재 프로세스가 인터럽트 컨택스트인지 알려주는 기능입니다.
https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/preempt.h #define in_interrupt() (irq_count())
in_interrupt() 함수 코드를 보면 irq_count() 함수로 치환됩니다.
https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/preempt.h #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \ | NMI_MASK))
irq_count() 함수 코드를 보면 preempt_count() 결과값과 다음 플래그간 AND BIT 오퍼레이션을 실행합니다.
HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK
(HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK) 의 정체는 무엇일까요?
다시 소스 코드를 따라가 보니, 눈이 어지럽습니다. "이것을 어떻게 계산할까!!!" 란 생각이 드는군요.
https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/preempt.h #define PREEMPT_BITS 8 #define SOFTIRQ_BITS 8 #define HARDIRQ_BITS 4 #define NMI_BITS 1 #define PREEMPT_SHIFT 0 #define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS) #define HARDIRQ_SHIFT (SOFTIRQ_SHIFT + SOFTIRQ_BITS) #define NMI_SHIFT (HARDIRQ_SHIFT + HARDIRQ_BITS) #define __IRQ_MASK(x) ((1UL << (x))-1) #define PREEMPT_MASK (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT) #define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT) #define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT) #define NMI_MASK (__IRQ_MASK(NMI_BITS) << NMI_SHIFT)
<< (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK) 마스크를 어셈블리 코드로 알아보기 >>
C 코드 형태로 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK) 의 정체를 알기는 좀 복잡합니다.
그러면 위 마스크값들의 정체를 알기 위해 in_interrupt() 함수를 어셈블리 코드를 한 번 볼까요?
https://elixir.bootlin.com/linux/v4.19.30/source/mm/vmalloc.c static struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long align, unsigned long flags, unsigned long start, unsigned long end, int node, gfp_t gfp_mask, const void *caller) { struct vmap_area *va; struct vm_struct *area; BUG_ON(in_interrupt());
__get_vm_area_node() 함수 가장 앞단에 in_interrupt() 함수를 호출합니다. 아주 좋은 예시입니다.
01 NSR:80265FEC|__get_vm_area_node: cpy r12,r13 02 NSR:80265FF0| push {r4-r9,r11-r12,r14,pc} 03 NSR:80265FF4| sub r11,r12,#0x4 ; r11,r12,#4 04 NSR:80265FF8| sub r13,r13,#0x8 ; r13,r13,#8 05 NSR:80265FFC| str r14,[r13,#-0x4]! 06 NSR:80266000| bl 0x8010FDBC ; __gnu_mcount_nc 07 NSR:80266004| cpy r14,r13 08 NSR:80266008| bic r12,r14,#0x1FC0 ; r12,r14,#8128 09 NSR:8026600C| bic r12,r12,#0x3F ; r12,r12,#63 10 NSR:80266010| ldr r14,[r12,#0x4] 11 NSR:80266014| ldr r12,0x80266104 12 NSR:80266018| cpy r8,r1 ; r8,align 13 NSR:8026601C| and r12,r12,r14 14 NSR:80266020| cmp r12,#0x0 ; r12,#0 15 NSR:80266024| cpy r4,r2 ; r4,flags 16 NSR:80266028| cpy r9,r3 ; r9,start
먼저 07~09번째 줄 코드를 보겠습니다.
07 NSR:80266004| cpy r14,r13 08 NSR:80266008| bic r12,r14,#0x1FC0 ; r12,r14,#8128 09 NSR:8026600C| bic r12,r12,#0x3F ; r12,r12,#63
스택 주소를 통해 프로세스 스택 최상단 주소에 접근하는 동작입니다.
위 어셈블리 코드 연산 결과 r12는 스택 최상단 주소를 저장하게 됩니다.
다음 10번째 줄 코드입니다.
10 NSR:80266010| ldr r14,[r12,#0x4]
스택 최상단 주소에 있는 struct thread_info 구조체 preempt_count 필드를 r14에 로딩합니다.
만약 r12가 0x800c0000이면 0x800C0004주소에 있는 preempt_count 필드의 0x00010002를 r14에 저장합니다.
(struct thread_info *) [-] (struct thread_info*)0x800c0000 (long unsigned int) [D:0x800C0000] flags = 0x0, (int) [D:0x800C0004] preempt_count = 0x00010002, (mm_segment_t) [D:0x800C0008] addr_limit = 0x0,
핵심 코드를 볼 차례입니다.
11 NSR:80266014| ldr r12,0x80266104 12 NSR:80266018| cpy r8,r1 ; r8,align 13 NSR:8026601C| and r12,r12,r14 14 NSR:80266020| cmp r12,#0x0 ; r12,#0
[11 행]: 0x80266104 주소에 있는 값을 r12에 저장합니다.
0x80266104 주소엔 0x001FFF00가 있습니다.
_____address|________0________4________8________C NSD:80266100| E7F001F2>001FFF00 00693EE0 809E02AC
(HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK) 의 정체가 0x001FFF00입니다.
[13 행]: r12와 r14 레지스터와 AND 비트 연산을 수행합니다.
이를 쉽게 표현하면 다음과 같습니다.
(struct thread_info 구조체 preempt_count 필드) & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)
어셈블리 코드 분석으로 다음 내용을 알게 됐습니다.
(HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK) = 0x001FFF00
<< HARDIRQ_MASK, SOFTIRQ_MASK, NMI_MASK 플래그를 어셈블리 코드로 계산해보기 >>
어셈블리 코드 분석으로 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)의 정체가 0x001FFF00이란 사실을 알게 됐습니다.
이번엔 각각 플래그 값을 알아볼까요?
이번엔 tracing_generic_entry_update() 함수를 어셈블리 코드로 분석해 보겠습니다.
https://elixir.bootlin.com/linux/v4.19.30/source/kernel/trace/trace.c 1 void 2 tracing_generic_entry_update(struct trace_entry *entry, unsigned long flags, 3 int pc) 4 { 5 struct task_struct *tsk = current; 6 7 entry->preempt_count = pc & 0xff; 8 entry->pid = (tsk) ? tsk->pid : 0; 9 entry->flags = 10 #ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT 11 (irqs_disabled_flags(flags) ? TRACE_FLAG_IRQS_OFF : 0) | 12 #else 13 TRACE_FLAG_IRQS_NOSUPPORT | 14 #endif 15 ((pc & NMI_MASK ) ? TRACE_FLAG_NMI : 0) | 16 ((pc & HARDIRQ_MASK) ? TRACE_FLAG_HARDIRQ : 0) | 17 ((pc & SOFTIRQ_OFFSET) ? TRACE_FLAG_SOFTIRQ : 0) |
위 코드 15~17번째 줄을 어셈블리 코드로 분석하는 것입니다.
tracing_generic_entry_update() 함수를 어셈블리 코드로 보면 다음과 같습니다.
01 NSR:801E3B50|tracing_generic_entry_update: cpy r12,r13 02 NSR:801E3B54| push {r4-r5,r11-r12,r14,pc} 03 NSR:801E3B58| sub r11,r12,#0x4 ; r11,r12,#4 04 NSR:801E3B5C| cpy r12,r13 05 NSR:801E3B60| bic r3,r12,#0x1FC0 ; r3,r12,#8128 06 NSR:801E3B64| bic r3,r3,#0x3F ; r3,r3,#63 07 NSR:801E3B68| ldr r3,[r3,#0x0C] 08 NSR:801E3B6C| bic r12,r12,#0x1FC0 ; r12,r12,#8128 09 NSR:801E3B70| strb r2,[r0,#0x3] ; pc,[r0,#3] 10 NSR:801E3B74| cmp r3,#0x0 ; tsk,#0 11 NSR:801E3B78| ldrne r3,[r3,#0x3A8] ; tsk,[r3,#936] 12 NSR:801E3B7C| bic r12,r12,#0x3F ; r12,r12,#63 13 NSR:801E3B80| tst r2,#0x100000 ; pc,#1048576 /* <<-- NMI_MASK */ 14 NSR:801E3B84| ldr r12,[r12] 15 NSR:801E3B88| moveq r5,#0x0 ; r5,#0 16 NSR:801E3B8C| movne r5,#0x40 ; r5,#64 17 NSR:801E3B90| tst r2,#0xF0000 ; pc,#983040 /* <<-- HARDIRQ_MASK */ 18 NSR:801E3B94| moveq r4,#0x0 ; r4,#0 19 NSR:801E3B98| movne r4,#0x8 ; r4,#8 20 NSR:801E3B9C| tst r2,#0x100 ; pc,#256 /* <<-- SOFTIRQ_MASK */ 21 NSR:801E3BA0| cpy r14,r13 22 NSR:801E3BA4| moveq r14,#0x0 ; r14,#0
HARDIRQ_MASK, SOFTIRQ_MASK, NMI_MASK 플래그의 정체는 다음과 같습니다.
NMI_MASK : 0x100000
HARDIRQ_MASK : 0xF0000
SOFTIRQ_MASK : 0x100
(개인블로그)
http://rousalome.egloos.com/
댓글 달기