Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

137199
1107
135865


[Linux][Kernel] 인터럽트(Interrupt) - request_threaded_irq 디버깅 [Linux][Kernel] IRQ..

irq_thread, request_threaded_irq 관련 데이터 구조와 코드에 대해서 이제 디버깅 좀 해볼까요?

실제 IRQ Descriptor 멤버들에 어떤 값들이 실려 있고 각 값들이 어떤 의미인지 정밀하게 파악하는 게 중요해요.
이 값들로 며칠 야근할 껄 하루 몇 시간 디버깅으로 마무리할 수 있거든요.

[1]: irq번호가 328
[2]: 하드웨어적인 핀먹스 값이 13인가 보네요
[3]. irq_data.state_use_accessors 값이 0x4002이네요
     아주 중요한 정보를 담고 있으니 이 놈은 따로 분석 좀 해야 겠네요.
[4]:  kstat_irqs는 각 per-cpu 별로 얼마나 IRQ가 Trigger되었는지 카운트 값을 알 수 있어요.
[5]:  thread_fn으로  touch_irq_thread이 등록되었군요.
[6]: 해당 irq thread의 task descriptor에요.
[7]: irq 이름 "touch" 네요.  
[8]: threads_active값이 2 이군요.
  (struct irq_desc *) (struct irq_desc *)0xDF4533C0 = 0xDF4533C0 = __bss_stop+0x1DD59510 -> (
    (struct irq_data) irq_data = (
      (unsigned int) irq = 328 = 0x0148, //<<--[1]
      (long unsigned int) hwirq = 13 = 0x0D,  //<<--[2]
      (unsigned int) node = 0 = 0x0,
      (unsigned int) state_use_accessors = 16386 = 0x4002, //<<--[3]
      (struct irq_chip *) chip = 0xC133AF6C = tegra_gp_irq.chip,
      (struct irq_domain *) domain = 0xE0002CC0 = __bss_stop+0x1E908E10,
      (void *) handler_data = 0x0 = ,
      (void *) chip_data = 0xC133AF44 = tegra_gp_irq.irq,
      (struct msi_desc *) msi_desc = 0x0 = ,
      (cpumask_var_t) affinity = (((long unsigned int [1]) bits = (15 = 0x0F)))),
    (unsigned int *) kstat_irqs = 0xC1315450 = crc32table_le[4][20], //<<--[4]
    (irq_flow_handler_t) handle_irq = 0xC016BE94 = handle_edge_irq,
    (struct irqaction *) action = 0xC355E1C0 = __bss_stop+0x1E64310 -> (
      (irq_handler_t) handler = 0xC0596A70 = touch_irq_handler,
      (void *) dev_id = 0xDF708010 = __bss_stop+0x1E00E160,
      (void *) percpu_dev_id = 0x0 = ,
      (struct irqaction *) next = 0x0 = ,
      (irq_handler_t) thread_fn = 0xC0597258 = touch_irq_thread,  //<<--[5]
      (struct task_struct *) thread = 0xDF545E80 //<<--[6]
      (unsigned int) irq = 328 = 0x0148,
      (unsigned int) flags = 8194 = 0x2002,
      (long unsigned int) thread_flags = 1 = 0x1,
      (long unsigned int) thread_mask = 1 = 0x1,
      (char *) name = 0xC0FB1B60 =  -> "touch", //<<--[7]
      (struct proc_dir_entry *) dir = 0xC355CC80 = __bss_stop+0x1E62DD0),
    (unsigned int) status_use_accessors = 1026 = 0x0402,
    (unsigned int) core_internal_state__do_not_mess_with_it = 32 = 0x20,
    (unsigned int) depth = 0 = 0x0,
    (unsigned int) wake_depth = 2 = 0x2,
    (unsigned int) irq_count = 391 = 0x0187,
    (long unsigned int) last_unhandled = 435903 = 0x0006A6BF,
    (unsigned int) irqs_unhandled = 2 = 0x2,
    (atomic_t) threads_handled = ((int) counter = 390 = 0x0186),
    (int) threads_handled_last = -2147483258 = 0x80000186,
    (raw_spinlock_t) lock = ((arch_spinlock_t) raw_lock = ((u32) slock = 78382252 = 0x04AC04AC, (struct
    (struct cpumask *) percpu_enabled = 0x0 = ,
    (struct cpumask *) affinity_hint = 0x0 = ,
    (struct list_head) affinity_notify = ((struct list_head *) next = 0xDF453424 = __bss_stop+0x1DD5957
    (struct work_struct) affinity_work = ((atomic_long_t) data = ((int) counter = 0 = 0x0), (struct lis
    (struct mutex) notify_lock = ((atomic_t) count = ((int) counter = 1 = 0x1), (spinlock_t) wait_lock
    (long unsigned int) threads_oneshot = 1 = 0x1,
    (atomic_t) threads_active = ((int) counter = 2 = 0x2), //<<--[8]
    (wait_queue_head_t) wait_for_threads = ((spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_s
    (struct proc_dir_entry *) dir = 0xC355C880 = __bss_stop+0x1E629D0,
    (int) parent_irq = 0 = 0x0,
    (struct module *) owner = 0x0 = ,
    (char *) name = 0x0 =  -> NULL)


중요한 정보를 담고 있는 멤버들에 대해서 분석 좀 해볼까요? 

- irq_data.state_use_accessors
[3]. irq_data.state_use_accessors 값이 0x4002 이라고 했어요.

state_use_accessors 멤버는 아래 enum 값을 OR 비트 마스크한 값이에요.
enum {
IRQD_TRIGGER_MASK = 0xf,
IRQD_SETAFFINITY_PENDING = (1 <<  8),
IRQD_NO_BALANCING = (1 << 10),
IRQD_PER_CPU = (1 << 11),
IRQD_AFFINITY_SET = (1 << 12),
IRQD_LEVEL = (1 << 13),
IRQD_WAKEUP_STATE = (1 << 14),
IRQD_MOVE_PCNTXT = (1 << 15),
IRQD_IRQ_DISABLED = (1 << 16),
IRQD_IRQ_MASKED = (1 << 17),
IRQD_IRQ_INPROGRESS = (1 << 18),  //<<-- 0x40000
IRQD_WAKEUP_ARMED = (1 << 19),
};

IRQF_TRIGGER_RISING 1
IRQF_TRIGGER_FALLING 2

state_use_accessors = IRQD_WAKEUP_STATE | IRQF_TRIGGER_FALLING 상태로 볼 수 있어요.
참고로, IRQD_IRQ_INPROGRESS 값이 0x40000 이니까  지금 348번 IRQ는 IRQ Context는 아닌 거죠.

- kstat_irqs

대부분 SMP를 지원하는 타겟에서는 IRQ가 랜덤하게 Trigger되거든요. 
이 값 kstat_irqs per-cpu 타입의 변수인데,  각 CPU별로 얼마나 IRQ가 Trigger되었는지를 확인할 수 있어요.

kstat_irqs 주소가 0xC1315450이니,
(unsigned int *) kstat_irqs = 0xC1315450

아래와 같이 per_cpu offset을 더해서 값을 확인하니, CPU0으로만 392 IRQ가 trigger되었네요.
  (unsigned int *) (unsigned int*)(0xC1315450+__per_cpu_offset[0]) = 0xC4B52450 = __bss_stop+0x34585A0 -> 392 = 0x0188 = '....'
  (unsigned int *) (unsigned int*)(0xC1315450+__per_cpu_offset[1]) = 0xC4B60450 = __bss_stop+0x34665A0 -> 0 = 0x0 = '....'
  (unsigned int *) (unsigned int*)(0xC1315450+__per_cpu_offset[2]) = 0xC4B6E450 = __bss_stop+0x34745A0 -> 0 = 0x0 = '....'
  (unsigned int *) (unsigned int*)(0xC1315450+__per_cpu_offset[3]) = 0xC4B7C450 = __bss_stop+0x34825A0 -> 0 = 0x0 = '....'


어디서 이 값이 증가되냐면, kstat_incr_irqs_this_cpu() 함수를 보세요.
아래 코드를 보면 알다싶히 per-cpu 타입이죠! kstat_irqs
static inline void kstat_incr_irqs_this_cpu(unsigned int irq, struct irq_desc *desc)
{
__this_cpu_inc(*desc->kstat_irqs);
__this_cpu_inc(kstat.irqs_sum);
}

bool
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;
bool handled = false;

raw_spin_lock(&desc->lock);

if (!irq_may_run(desc))
goto out;

desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(irq, desc);  //<<--
(where) IRQ가 처리되는 콜 스택
 => touch_irq_handler
 => handle_irq_event_percpu
 => handle_irq_event
 => handle_fasteoi_irq //<<--
 => generic_handle_irq
 => __handle_domain_irq
 => gic_handle_irq
 => __irq_svc
 => lpm_cpuidle_enter
 => lpm_cpuidle_enter
 => cpuidle_enter_state
 => cpuidle_enter
 => cpu_startup_entry
 => secondary_start_kernel

- irq thread state
[6] (struct task_struct *) thread = 0xDF545E80
irq thread도 kernel thread에서 생성된 거라 state machine으로 동작하거든요.

enum KTHREAD_BITS {
KTHREAD_IS_PER_CPU = 0,
KTHREAD_SHOULD_STOP,
KTHREAD_SHOULD_PARK,
KTHREAD_IS_PARKED,
};

이 상태를 점검해볼께요.

&to_kthread(current)->flags 매크로를 쓰면 이 값을 바로 알 수 있는데요.

vfork_done 주소가 0xC35B1F88이니 아래 container_of macro를 T32로 쓰면 바로 확인 가능해요.
해석하면, &to_kthread(current)->flags 값은 0이고 KTHREAD_IS_PER_CPU 를 의미하는군요.

이 값은 커널쓰레드 생성 시 kthread_create_on_cpu() 함수에서 그냥 세팅하는 값이라 별 의미는 없네요.

v.v %all container_of(0xC35B1F88, struct kthread, exited)
  (struct kthread *) container_of(0xC35B1F88, struct kthread, exited) = 0xC35B1F6C = _
    (long unsigned int) flags = 0 = 0x0 = '....',
    (unsigned int) cpu = 0,
    (void *) data = 0xC355E1C0 = __bss_stop+0x1E64310 -> ,
    (struct completion) parked = ((unsigned int) done = 0 = 0x0 = '....', (wait_queue_
    (struct completion) exited = ((unsigned int) done = 0 = 0x0 = '....', (wait_queue_

(where)
      (struct task_struct *) thread = 0xDF545E80
        (long int) state = 0 = 0x0,
        (void *) stack = 0xC35B0000 = __bss_stop+0x1EB6150,
// ..생략..
        (struct completion *) vfork_done = 0xC35B1F88


#define __to_kthread(vfork) \
container_of(vfork, struct kthread, exited)

static inline struct kthread *to_kthread(struct task_struct *k)
{
return __to_kthread(k->vfork_done);
}


위 디버깅 정보에서 매우 중요한 주소를 확인할 수 있는데요.
struct kthread.data가 0xC355E1C0이라는 거죠.
(void *) data = 0xC355E1C0

이 주소는 irq_thread() 함수 아규먼트 주소인데요.
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;


아래 __setup_irq() 함수에서 생성되는 irq_thread 생성 룰에 따라 넘겨주는 건데요.
상세 개념은 나중에 커널 쓰레드를 분석할 때 다룰 꺼에요.
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
struct irqaction *old, **old_ptr;
// .. 생략 ..
if (new->thread_fn && !nested) {
struct task_struct *t;
static const struct sched_param param = {
.sched_priority = MAX_USER_RT_PRIO/2,
};

t = kthread_create(irq_thread, new, "irq/%d-%s", irq,  //<<--
   new->name);

다시 원래 코드로 돌아가면, 0xC355E1C0 주소는 (struct irqaction *) 타입으로 캐스팅이 가능하네요
static int irq_thread(void *data)  // data = 0xC355E1C0
{
struct callback_head on_exit_work;
struct irqaction *action = data;

v.v %y %t  %s (struct irqaction *)0xC355E1C0
  (struct irqaction *) (struct irqaction *)0xC355E1C0 = 0xC355E1C0
    (irq_handler_t) handler = 0xC0596A70 = touch_irq_handler,
    (void *) dev_id = 0xDF708010 = __bss_stop+0x1E00E160,
    (void *) percpu_dev_id = 0x0 = ,
    (struct irqaction *) next = 0x0 = ,
    (irq_handler_t) thread_fn = 0xC0597258 = touch_irq_thread,
    (struct task_struct *) thread = 0xDF545E80 = __bss_stop+0x1DE4BFD0,
    (unsigned int) irq = 328,
    (unsigned int) flags = 8194,
    (long unsigned int) thread_flags = 1,
    (long unsigned int) thread_mask = 1,
    (char *) name = 0xC0FB1B60 =  -> "touch",
    (struct proc_dir_entry *) dir = 0xC355CC80 = __bss_stop+0x1E62DD0)


- threads_active
irq_thread 어떤 동작 중인지를 알려주는데요.
IRQ가 Trigger되면 호출되는 handle_irq_event_percpu() 함수에서 __irq_wake_thread() 함수콜로
threads_active 값이 +1로 증분되구요.

void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
// .. 생략 ..
atomic_inc(&desc->threads_active);

wake_up_process(action->thread);

irq_thread의 핸들 함수가 처리되고 난 후 wake_threads_waitq() 함수에서 -1만큼 감소시키죠.
이 값이 2란 이야기는 현재 irq_thread가 돌고 있는 중이라고 보면 되죠.
static void wake_threads_waitq(struct irq_desc *desc)
{
if (atomic_dec_and_test(&desc->threads_active))
wake_up(&desc->wait_for_threads);
}
 
static int irq_thread(void *data)
{
// .. 생략 ..
while (!irq_wait_for_interrupt(action)) {
irqreturn_t action_ret;

irq_thread_check_affinity(desc, action);

action_ret = handler_fn(desc, action);
if (action_ret == IRQ_HANDLED)
atomic_inc(&desc->threads_handled);

wake_threads_waitq(desc);
}

"어떤 분들은 뭐 이렇게 커널 코드를 볼 필요가 있을까"라고 질문을 던지는 분도 있는데요. 전 몸으로 때우면서 몸빵하면서 일하는 건 싫거든요.

덧글

댓글 입력 영역