Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] 인터럽트 디버깅 - in_interrupt #CS [라즈베리파이][커널]인터럽트

in_interrupt 함수는 현재 구동 중인 프로세스 스택 Top 주소에 위치한 current_thread_info()->preempt_count 멤버이며 인터럽트 서비스 루틴이 실행되기 전 __irq_enter 함수에서 HARDIRQ_OFFSET를 더하고 인터럽트 서비스 루틴이 종료되면 해제합니다.

이제 커널 소스를 조금 수정해서 라즈베리안에서는 in_interrupt 매크로 값이 뭔지 확인하겠습니다. 

인터럽트 핸들러에 아래 코드를 반영하고 로그를 받아보면 in_interrupt() 함수 리턴값을 볼 수 있습니다.
diff --git a/drivers/mmc/host/bcm2835-sdhost.c b/drivers/mmc/host/bcm2835-sdhost.c
index 273b1be05..5f57b3dab 100644
--- a/drivers/mmc/host/bcm2835-sdhost.c
+++ b/drivers/mmc/host/bcm2835-sdhost.c
@@ -1472,6 +1472,16 @@ static irqreturn_t bcm2835_sdhost_irq(int irq, void *dev_id)
  struct bcm2835_host *host = dev_id;
  u32 intmask;
 
+ void *stack;
+ struct thread_info *current_thread;
+
+ stack = current->stack;  //<<--[1]
+ current_thread = (struct thread_info*)stack; //<<--[2]
+
+ printk("[+] in_interrupt: 0x%08x,preempt_count = 0x%08x, stack=0x%08lx \n", 
+ (unsigned int)in_interrupt(), (unsignedint)current_thread->preempt_count, (long unsigned int)stack); //<<--[3]
+
+
  spin_lock(&host->lock);
 
  intmask = bcm2835_sdhost_read(host, SDHSTS);

위와 같이 코드를 변경하기 전 bcm2835_sdhost_irq 함수는 다음과 같았어요. void *stack부터 시작하는 +라고 표시한 코드를 u32 intmask; 와 spin_lock(&host->lock); 코드 사이에 추가하면 됩니다.
[drivers/mmc/host/bcm2835-sdhost.c]
static irqreturn_t bcm2835_sdhost_irq(int irq, void *dev_id)
{
irqreturn_t result = IRQ_NONE;
struct bcm2835_host *host = dev_id;
u32 intmask;
// <<--패치 코드 추가 위치
spin_lock(&host->lock);

이제 그럼 패치 코드를 찬찬히 리뷰해볼까요?
[1]: 아래 코드는 현재 실행 중인 프로세스의 태스크 디스크립터 정보를 얻어옵니다.
+ stack = current->stack;

그럼 현재 실행 중인 코드에서 태스크 디스크립터 정보를 어떻게 얻어올까요? 이때 current란 매크로를 쓰면 됩니다. 이는 커널에서 제공하는 매크로인데 커널에서 자주 사용합니다. struct task_struct 구조체에 stack이란 멤버에 스택 Top 주소가 저장돼 있거든요. 이 스택 Top 주소로 preempt_count 멤버 변수에 접근할 수 있습니다.

[2]: 스택 탑(Top) 주소에 struct thread_info 정보가 있다고 배웠죠? 이 구조체로 캐스팅합니다.
+ current_thread = (struct thread_info*)stack;
 
[3]: in_interrupt와 struct thread_info.preempt_count 값을 출력합니다.
+ printk("[+] in_interrupt: 0x%08x,preempt_count = 0x%08x, stack=0x%08lx \n", 
+ (unsigned int)in_interrupt(), (unsignedint)current_thread->preempt_count, (long unsigned int)stack);  

커널 로그를 받아보면, in_interrupt() 함수는 현재 실행 중인 프로세스 스택에 저장된 struct thread_info->preempt_count 값에서 HARDIRQ_OFFSET 비트만 출력하기 위해 HARDIRQ_MASK로 마스킹한 결과를 리턴한다는 것을 알 수 있습니다.
[0.911605] Indeed it is in host mode hprt0 = 00021501
[1.001692] mmc1: new high speed SDIO card at address 0001
[1.037804] [+] in_interrupt: 0x00010000,preempt_count = 0x00010000, stack=0x80c00000 
[1.039271] [+] in_interrupt: 0x00010000,preempt_count = 0x00010000, stack=0x80c00000 
[1.041839] [+] in_interrupt: 0x00010000,preempt_count = 0x00010000, stack=0x80c00000 
[1.042911] mmc0: host does not support reading read-only switch, assuming write-enable
[1.044570] [+] in_interrupt: 0x00010000,preempt_count = 0x00010000, stack=0x80c00000 
[1.046503] mmc0: new high speed SDHC card at address aaaa
[1.046995] mmcblk0: mmc0:aaaa SB16G 14.8 GiB May 13 13:07:23 raspberrypi kernel:
 
위 로그에서 preempt_count 값은 0x00010000이고 in_interrupt 값은 0x00010000이죠? 0x00010000 값에서 HARDIRQ_OFFSET 비트 값만 뽑아서 출력한 값이 0x00010000값 인데요. “in_interrupt: 0x00010000” 이 로그의 의미는 “현재 인터럽트 처리 중”이라는 것이며 현재 코드가 인터럽트 컨택스트라는 사실을 알려줍니다. 

이렇게 코드 분석 후 패치 코드를 작성해서 실제 시스템에서 출력되는 결괏값을 확인하면 더 오랫동안 머릿속에 남습니다.


덧글

댓글 입력 영역