이번에는 비트 마스크를 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;
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_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
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
[리눅스커널] ssize_t와 size_t의 실체 (0) | 2023.05.06 |
---|---|
[Crash-Utility][리눅스커널][디버깅] Radix Tree(라덱스-트리) 디버깅 하기(크래시 유틸리티) (0) | 2023.05.06 |
[리눅스커널] smp thread에 대해서 (0) | 2023.05.06 |
[리눅스커널][디버깅] 슬럽(슬랩) 캐시 오브젝트 T32로 메모리 디버깅하기 (0) | 2023.05.06 |
[리눅스커널][디버깅] 유저공간 High Memory Zone 페이지 할당 과정 (0) | 2023.05.06 |