본문 바로가기

Core BSP 분석/리눅스 커널 핵심 분석

[리눅스커널] 비트 마스크를 어셈블리 코드로 빨리 읽는 방법 - HARDIRQ_MASK, SOFTIRQ_MASK, NMI_MASK

이번에는 비트 마스크를 C 코드가 아닌 어셈블리 코드로 읽는 방법을 소개합니다.
 
in_interrupt() 함수 소개
 
in_interrupt() 함수는 현재 프로세스가 인터럽트 컨택스트인지 알려주는 기능입니다.
#define in_interrupt() (irq_count())
 
in_interrupt() 함수 코드를 보면 irq_count() 함수로 치환됩니다.
#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) 의 정체는 무엇일까요?
 
다시 소스 코드를 따라가 보니, 눈이 어지럽습니다. "이것을 어떻게 계산할까!!!" 란 생각이 드는군요.
#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_OFFSET | NMI_MASK) 마스크를 어셈블리 코드로 알아보기
 
C 코드 형태로 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK) 의 정체를 알기는 좀 복잡합니다.
그러면 위 마스크값들의 정체를 알기 위해 in_interrupt() 함수를 어셈블리 코드를 한 번 볼까요?
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_OFFSET, NMI_MASK 플래그를 어셈블리 코드로 계산해보기
 
어셈블리 코드 분석으로 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)의 정체가 0x001FFF00이란 사실을 알게 됐습니다.
이번엔 각각 플래그 값을 알아볼까요?
 
이번엔 tracing_generic_entry_update() 함수를 어셈블리 코드로 분석해 보겠습니다.
1 void
2 tracing_generic_entry_update(struct trace_entry *entry, unsigned long flags,
3       int pc)
4 {
5  struct task_struct *tsk = current;
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_OFFSET */
21 NSR:801E3BA0|                               cpy     r14,r13
22 NSR:801E3BA4|                               moveq   r14,#0x0         ; r14,#0
 
HARDIRQ_MASK, SOFTIRQ_OFFSET, NMI_MASK 플래그의 정체는 다음과 같습니다.
NMI_MASK : 0x100000
HARDIRQ_MASK :  0xF0000
SOFTIRQ_OFFSET :  0x100
 
 
"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
혹시 글을 읽고 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려 드리겠습니다!"
 
 
// 수정 내역
SOFTIRQ_OFFSET를 SOFTIRQ_MASK로 잘못 기입했다. 
수정 완료, 02/06/2020