3.1 디버깅이란?

 

버그를 수정하고 있다는 의미

리눅스 커널과 드라이버가 정상 동작할 때 자료구조와 함수 호출 흐름까지 파악하는 과정

 

1) 문제 해결 능력의 지름길

 

커널 디버깅은 문제 해결 능력 그 자체이다.

임베디드 리눅스 활용 분야

- 부팅 도중 커널 크래시 발생

- 인터럽트 핸들러를 설정했는데 호출되지 않음

- 시스템 응답 속도가 매우 느려짐

- 파일 복사가 안 됨

 

문제를 해결하기 위해서는 문제 발생의 원인을 분석해야 하고,

문제 발생의 원인은 문제 발생 시 확보한 커널 로그와 메모리 덤프를 정확히 분석해야 한다.

 

임베디드 BSP 개발에서 커널 로그와 메모리 덤프를 정확히 분석하는 스킬을 커널 디버깅이라고 한다.

 

※ 리눅스 커널을 구성하는 서브시스템이 정상적으로 동작할 때 파악할 내용

- 함수가 실행될 때 변경되는 자료구조

- 함수가 실행되는 빈도와 실행 시각

- 실행 중인 코드를 어떤 프로세스가 실행하는지 확인

프로그램이 정상적으로 동작할 때의 함수 호출 흐름과 자료구조를 알고 있어야 오류나 버그 발생 시 무엇이 문제인지 식별 가능하기 때문

 

★ 커널 로그 분석

 

https://www.unix.com/programming/148285-what-unbalanced-irq.html

 

① 오류 메시지를 커널의 어느 코드에서 출력했는지 확인

 

 

__enable_irq( ) 함수에서 오류 조건을 검출한 것으로 보이고, 어느 코드에서 콜 스택을 출력했는지 살펴봐야 함

 

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/irq/manage.c

 

irq_desc 구조체의 depth 필드는 인터럽트를 활성화하면 0, 비활성화하면 1을 설정

6번째 줄 코드는 인터럽트를 2번 활성화했을 때 실행되며 이미 인터럽트를 활성화했으면 depth 필드가 0인데 다시 활성화했기 때문에 콜 스택을 출력한다.

※ warn( ) 매크로 함수가 호출되면 커널 로그로 콜 스택을 출력한다.

 

② 소스코드에서 에러 메시지를 출력한 이유 살펴보기

 

 

IRQ 4 : 4번 인터럽트를 두 번 활성화했기 때문에 오류 발생

위 에러 메시지는 enable_irq( ) 함수에서 출력하는데 함수 내부에 있는 논리적 오류 때문이 아닌 

enable_irq( ) 함수를 호출한 드라이버 코드에 있는 오류 때문일 가능성이 높다.

 

 

커널 에러 메시지를 보면  svsknfdrvr 드라이버 모듈이 보이고,

만약 enable_irq( ) 함수를 호출한 코드가 svsknfdrvr 드라이버 코드에 있다면 관련 코드를 분석한 후 논리적인 오류가 있는지 점검해야 함

 

③ 필요에 따라 디버깅 코드를 작성해 다시 문제가 발생했을 때 추가 커널 로그 확보 시도

 

패치 코드를 입력해 문제 현상을 재현하여 커널 로그를 받아보면 누가 4번 인터럽트를 2번 활성화했는지 알려줄 것이다.

 

패치 코드 ( 디버깅 패치 )

 

 __disable_irq ( ) 함수와 __ enable_irq( ) 함수의 2번째 인자는 인터럽트 번호를 나타내는 irq이다.

위 코드에서는 이 인터럽트 번호가 4일 때 콜 스택을 호출한다.

위 패치 코드를 빌드해서 시스템에 반영한 후 문제를 다시 재현하면 4번 인터럽트를 활성화 혹은 비활 
성화할 때 콜스택을 커널 로그로 출력하여 어떤 코드에서 연속으로 활성화했는지 알아낼 수 있다.

 

 

2) 디버깅과 코드 학습 능력

 

디버깅을 하면서 리눅스 커널 코드를 함께 분석하면 다음과 같은 정보를 얻을 수 있다.

- 분석 대상 코드가 동작하는 콜 스택 
- 함수가 실행될 때 변경되는 자료구조 
- 함수가 실행되는 빈도와 실행 시각 
- 분석 대상 코드를 실행하는 프로세스

 

★ 커널 디버깅 과정 예시

 

※ 명령어 "cat /proc/interrupts" : 인터럽트의 세부 속성

 

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/irq/proc.c

 

"/* 이 부분에 번째 패치 코드를 입력하세요 */"라는 주석으로 표시된 부분에 다음 코드를 입력

 

 

irqaction 구조체 타입인 action 포인터형 변수가 NULL 이 아닐 때 action 인자와 함께 rpi_get_interrupt_info( ) 함수를 호출하는 코드

 

 

rpi_get_interrupt_info( ) 함수는 ftrace 로 다음 정보를 출력 하는 기능

- 프로세스 이름

- 인터럽트 번호

- 인터럽트 이름

- 인터럽트 핸들러 함수 이름

 

위와 같은 패치 코드를 입력한 이유는??

 

 

위와 같이 인터럽트의 속성 정보는 action 필드에 저장된다.

 

 

irqaction 구조체 오른쪽 부분에 표시된 주석이 인터럽트의 속성 정보이고,

이 정보를 라즈비안에서 확인하기 이해 패치 코드를 입력한 것이다.

 

ftrace를 설정하는 명령어로 작성한 셸 스크립트 코드

 

echo rpi_get_interrupt_info > /sys/kernel/debug/tracing/set_ftrace_filter 

패치 코드에서 작성한 rpi_get_interrupt_info( )라는 함수명을 /sys/kernel/debug/tracing/set_ftrace_filter 파일에 지정하는 명령어

→  rpi_get_interrupt_info( ) 함수를 누가 호출했는지 알 수 있음

 

./irq_trace_ftrace.sh : 셸 스크립트 실행

 

 

cat /proc/interrupts : rpi_get_interrupt_info( ) 함수가 호출되도록 커널을 동작시키는 역할

 

 

ftrace 로그를 라즈비안 시스템에서 안전하게 추출하기 위한 셸 스크립트 코드

 

get_ftrace.sh

 

./get_ftrace.sh : ftrace 로그가 현재 디렉터리에 복사되어 ftrace 로그를 확인할 수 있음

 

 

함수의 흐름은 11번째 줄에서 3번째 줄 방향으로 이어짐

01 : pid가 884인 cat 프로세스가 rpi_get_interrupt_info( ) 함수 호출

05 : seq_read( ) 함수에서 show_interrupts( ) 함수 호출

10 : sys_read( ) 함수가 호출된 후 유저 공간에서 read 시스템 콜 실행

 

egrep -nr irq_handler * : 현재 디렉토리에 있는 파일에서 irq_handler라는 문자열을 검색한 다음과 같은 결과 출력

- 인터럽트 번호

- 인터럽트 이름

- 인터럽트 핸들러 이름

 

ex)

 

- 인터럽트 번호 : 86

- 인터럽트 이름 : mmc1

- 인터럽트 핸들러 이름 : bcm2835 mmc_irq

 

 

3.2 printk( ) 함수

 

→ printk( ) 함수를 호출하면 커널 로그를 볼 수 있다.

 

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/kernel/process.c

 

위와 같이 레지스터 정보를 출력해준다.

 

▶ printk로 함수 심벌 정보를 보는 방법

 

- printk로 포인터를 출력하고 싶을 때는 %p를 쓰면 된다.

- %pS를 쓰면 함수 주소를 심벌로 출력한다. → 함수 포인터를 디버깅할 때 자주 쓰는 기법

 

- 리눅스 커널 코드에 printk 코드 입력해보기

 

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c

 

08 : 프로세스 이름 출력

09~10

- __ func __ : 현재 실행 중인 함수의 이름 
- __ LINE__: 현재 실행 중인 코드 라인 
- _builtin_return_address(0): 현재 실행 중인 함수를 호출한 함수의 주소 

 

 

빌드 후 설치하면 위와 같은 커널 로그를 볼 수 있고, 로그에 담긴 디버깅 정보는 다음과 같다.

- printk 로그를 실행한 포로세스의 이름은 kworker/2 : 3이다. 
- printk가 추가된 곳은 insert_wq_barrier( ) 함수의 2354번째 줄이다. 
- insert_wq_barrier( ) 함수는 workqueue_cpu_down_callback( ) 함수가 호출했다. 

 

★ printk를 쓸 때 주의해야 할 점

→ printk의 호출 빈도를 반드시 체크해야 한다.

리눅스 커널에서 1초에 수백 번 이상 호출되는 함수에 printk를 사용하면 시스템이 락업되거나 커널 패닉으로 오동작할 수 있음

∵ printk가 커널 입장에서 많은 비용이 드는 함수이기 때문

락업 : 리눅스 디바이스에서 마우스를 움직이거나 키보드를 입력해도 아무런 반응이 없는 상황

 

 

3.3 dump_stack( ) 함수

 

→ 커널 로그를 통해 커널 동작을 보여주는 기능

 

▶ dump_stack( ) 함수 사용법

 

- "linux/kernel.h" 헤더 파일 추가하기

- asmlinkage __visible void dump_stack (void); 인자와 반환값 모두 void

 

▶ dump_stack( ) 함수로 커널 로그에서 콜 스택 확인하기 

 

 

_do_fork( ) 함수 : 함수 콜 스택을 확인하기 위한 코드

 

부팅 후 출력되는 커널 로그

 

01 : 콜 스택을 실행한 프로세스 정보

02~07 : 콜 스택

06 : sys_clone( ) 함수에서 _do_fork( ) 함수를 호출한다고 알려줌

 

_do_fork ( ) 항수에 dump_stack ( ) 함수를 추가했을 때의 콜 스택

 

함수 호출은 07번째 줄에서 03번째 줄 방향으로 이뤄짐

 

★ dump_stack( ) 함수를 쓸 때 주의해야 할 점

→ dump_stack( ) 함수를 호출하여 콜 스택을 보려는 코드가 자주 호출되는지 점검해야 함

dump_stack( )은 현재 실행 중인 프로세스 스택 주소를 읽어 스택에 푸시된 프레임 포인터 레지스터를 읽고,

ARM 아키텍처의 함수 호출 규약에 따라 프레임 포인터 레지스터를 읽어 함수 호출 내역을 추적하는 동작 반복

 

 

3.4 ftrace

→ 리눅스 커널에서 제공하는 강력한 디버그 기능

 

- 함수 호출 흐름을 소스코드를 수정하지 않고도 볼 수 있음

- 커널의 세부 실행 정보 출력

- 1초에 수십 번 호출해도 성능에 부담 없음

- 커널 로그 함께 볼 수 있음

 

※ ftrace의 특징

① 인터럽트, 스케줄링 커널 타이머 등의 커널 동작을 상세히 추적한다.
② 함수 필터를 지정하면 지정한 함수를 호출한 함수와 전체 콜 스택까지 출력하며 코드를 수정할 필요가 없다.
③ 함수를 어느 프로세스가 실행하는지 알 수 있다.
④ 함수가 실행된 시각 정보를 알 수 있다. 
⑤ ftrace 로그를 활성화해도 시스템 동작에 부하를 거의 주지 않는다.

 

▶ ftrace 설정

 

 

/sys/kernel/debug/tracing에서 ftrace 설정 파일을 확인할 수 있다.

 

- sleep 1 : 1초 동안 딜레이를 주는 동작

- tracing_on : 트레이서 활성화

- tracer 설정

① nop : 기본 트레이서, ftrace 이벤트만 출력
② function : 함수 트레이서, set_ftrace filter로 지정한 함수를 누가 호출하는지 출력
③ function_graph : 함수 실행 시간과 세부 호출 정보를 그래프 포맷으로 출력

- set_ftrace_filter : 필터 설정

→ 리눅스 커널에 존재하는 모든 함수를 필터로 지정할 수는 없고, available_filter_functions 파일에 포함된 함수만 지정할 수 있음

 

▶ 추가 설정 파일

- buffer_size_kb : ftrace 로그의 버퍼 크기, 킬로바이트 단위, ftrace 로그를 더 많이 저장하고 싶을 때 설정

- available_filter_functions : 트레이싱할 수 있는 함수 목록을 저장한 파일

- events : ftrace에서 제공하는 이벤트의 종류를 알 수 있는 디렉터리

     - sched : 프로세스가 스케줄링되는 동작과 스케줄링 프로파일링을 트레이싱하는 이벤트를 볼 수 있음
     • sched_switch: 컨텍스토 스위칭 동작 
     • sched_wakeup: 프로세스를 우는 동작 
     - irq : 인터럽트와 Soft IRQ를 트레이싱하는 이벤트
     • irq_handler_entry: 인터럽트가 발생한 시각과 인터럽트 번호 및 이름을 출력 
     • irq_handler_exit: 인터럽트 핸들링이 완료 
     • softirq_raise: Soft IRQ 서비스 실행 요청 

     • softirq_entry: Soft lRQ 서비스 실행 시작 

     • softirq_exit: Soft IRQ 서비스 실행 완료 

 

▶ ftrace 로그 추출

 

 

./get_ftrace.sh로 셸 스크립트를 실행하면 같은 폴더에 ftrace 로그가 담긴 ftrace log.c 라는 파일이 생성됨

 

★ ftrace는 커널 코드 분석의 안내자이다.

→ ftrace 이벤트의 이름으로 커널 내부의 어떤 소스코드에서 이벤트를 출력하는지 알 수 있기 때문

 

각 이벤트를 출력하는 함수 목록

 

3.5 임베디드 디버거의 전설 TRACE32

 

▶ 주소로 코드 정보 파악

 

 

▶ 전역 변수 확인

 

 

▶ 구조체를 주소로 캐스팅

 

 

▶ 어셈블리 코드 보기

 

 

 

'2021 SUMMER STUDY > LINUX KERNEL' 카테고리의 다른 글

Week05_프로세스3  (0) 2021.08.21
Week04_프로세스2  (0) 2021.08.15
Week03_프로세스  (0) 2021.08.09
Week01_리눅스 소개와 전망  (0) 2021.07.18

+ Recent posts