SoC : : Architecture/: : Raspberry

printk() 함수

Jay.P Morgan 2024. 10. 10. 23:57

 

 

 

printk printf 유사한 기능입니다. printf 윈도우 콘솔 프로그램이나 리눅스 시스템 프로그램에서 로그를 출력합니다. 마찬가지로 printk() 함수를 호출하면 커널 로그를 있습니다.

 

printk 커널 함수에 적절히 추가하면 유용한 디버깅 정보를 얻을 있습니다. 리눅스 커널 핵심 함수도 printk 이용해 시스템 정보를 출력합니다.

커널 핵심 코드에서 printk 쓰는 예시 코드를 소개합니다.

[https://elixir.bootlin.com/linux/v4.14.70/source/arch/arm/kernel/process.c]

01 void __show_regs(struct pt_regs *regs)

02 {

...

03 printk("pc : [<%08lx>]    lr : [<%08lx>]    psr: %08lx\n",

04        regs->ARM_pc, regs->ARM_lr, regs->ARM_cpsr);

05 printk("sp : %08lx  ip : %08lx  fp : %08lx\n",

06        regs->ARM_sp, regs->ARM_ip, regs->ARM_fp);

07 printk("r10: %08lx  r9 : %08lx  r8 : %08lx\n",

08  regs->ARM_r10, regs->ARM_r9,

09  regs->ARM_r8);

10 printk("r7 : %08lx  r6 : %08lx  r5 : %08lx  r4 : %08lx\n",

11  regs->ARM_r7, regs->ARM_r6,

12  regs->ARM_r5, regs->ARM_r4);

 

__show_regs() 함수가 실행하면 레지스터 세트를 출력합니다. 03~12 번째 줄과 같이 레지스터 정보를 출력합니다.

 

※ 64bit - Raspberry Pi 5 - kernel v.6.6

 

 

 

정수형 포멧

이번에는 printk 전달하는 포멧에 대해 살펴봅시다.

 

정수형 포멧은 int, long, long long 지원합니다. C언어의 printf 같습니다.

변수 타입  타입 % 지정자

Int %d or %x

unsigned int %u or %x

Long %ld or %lx

unsigned long %lu or %lx

long long %lld or %llx

unsigned long long %llu or %llx

size_t %zu or %zx

ssize_t %zd or %zx

s32 %d or %x

u32 %u or %x

s64 %lld or %llx

u64 %llu or %llx

 

 

printk 전달하는 인자와 % 형식과 매칭이 되야 커널 빌드 컴파일 에러가 발생하지 않습니다.

 

 

printk 포인터를 출력하고 싶을 때는  %p 쓰면 됩니다.

커널은 심볼 테이블을 갖고 있으므로 %pS 포맷을 쓰면 함수 주소를 심볼로 출력합니다.

함수 포인터를 디버깅할 자주 쓰는 기법입니다.

 

이번에는 printk 쓰는 방법을 소개합니다. 다음 코드를 리눅스 커널 코드에 입력합시다.

[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/workqueue.c]

01 static void insert_wq_barrier(struct pool_workqueue *pwq,

02         struct wq_barrier *barr,

03         struct work_struct *target, struct worker *worker)

04 {

05 struct list_head *head;

06 unsigned int linked = 0;

07

08 + printk("[+] process: %s \n", current->comm);

09 + printk("[+][debug] message [F: %s, L:%d]: caller:(%pS)\n", 

10 +   __func__, __LINE__, (void *)_builtin_return_address(0));

 

08 번째 코드를 보겠습니다

08 + printk("[+] process: %s \n", current->comm);

프로세스 이름을 출력합니다. current 현재 프로세스 태스크 디스크립터 주소를 가르킵니다

 

다음 09~10 번째 코드를 보겠습니다.

09 + printk("[+][debug] message [F: %s, L:%d]: caller:(%pS)\n", 

10 +   __func__, __LINE__, (void *)_builtin_return_address(0));

 

09~10 코드에서 보이는 아규먼트 내용은 다음과 같습니다.

Ÿ __func__: 현재 실행 중인 함수 이름

Ÿ __LINE__: 현재 실행 중인 코드 라인 

Ÿ _builtin_return_address(0): 자신을 호출한 함수 주소

 

모두 GCC 컴파일러에서 제공하는 매크로입니다. 코드를 입력하면 커널 코드 실행 위치와 누가 자신을 호출하는지 있습니다

 

09 번째 가장 왼쪽 코드를 보면 “%pS” 보입니다. %pS 아규먼트로 지정한 주소를 심볼로 변환해 출력합니다.

 

코드를 커널 함수에 입력한 빌드 커널 이미지를 설치하면 다음과 같은 커널 로그를 있습니다.

1 [+] process: kworker/2:3

2 [+][debug] message [F:insert_wq_barrier, L:2354] caller(workqueue_cpu_down_callback+0x90/0xac)

 

로그에 담긴 디버깅 정보는 다음과 같습니다.

Ÿ printk 로그를 실행한 프로세스 이름은 kworker/2:3입니다.

Ÿ printk 추가한 코드 정보는 insert_wq_barrier() 함수 2354 라인입니다

Ÿ insert_wq_barrier() 함수는 workqueue_cpu_down_callback() 함수가 호출했습니다.

 

위와 같은 방식으로 printk 추가하면 커널 코드 실행 정보를 얻을 있습니다.

 

 

 

printk 주의해야

리눅스 커널에서 1초에 수 백 번 이상 호출하는 함수에 printk를 호출하면 시스템은 오동작할 수 있습니다. 시스템 응답 속도가 매우 느려지거나 심하면 커널 패닉이 발생할 있습니다

printk 실행 시간 관점으로 많은 비용이 드는 함수이기 때문입니다. 파일 시스템의 도움으로 로그를 콘솔 버퍼에 저장하는 세부 동작을 수행합니다.

 

만약 스케줄링 코드 분석 __schedule() 함수에 다음과 같은 코드를 입력했다고 가정합시다.

[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/sched/core.c]

01 static void __sched notrace __schedule(bool preempt)

02 {

03 struct task_struct *prev, *next;

04 unsigned long *switch_count;

05 struct rq_flags rf;

06 struct rq *rq;

07 int cpu;

08

09 cpu = smp_processor_id();

10 rq = cpu_rq(cpu);

11 prev = rq->curr;

12

13 + printk("[+] process: %s \n", current->comm);

14 + printk("[+][debug] message [F: %s, L:%d]: caller:(%pS)\n", 

15 +   __func__, __LINE__, (void *)_builtin_return_address(0));

 

__schedule() 함수는 매우 자주 호출되므로 13~15 번째 줄에 printk() 함수를 호출하면 시스템은 오동작할 것입니다. 성능이 매우 좋은 슈퍼 컴퓨터를 제외하고는 코드를 반영하면 대부분 부팅 조차 못합니다. 그러면 리눅스 커널 함수 코드를 보고 세부 자료구조와 함수 흐름을 파악하고 싶을 어떤 방식을 써야 할까요? 리눅스 커널 제공하는 ftrace 쓰면 됩니다

 

 

 

by Austin Kim. blog

'SoC : : Architecture > : : Raspberry' 카테고리의 다른 글

커널 빌드 / 설치  (0) 2024.10.14
dump_stack() 함수  (0) 2024.10.10
(2) 디버깅과 코드 학습 능력  (0) 2024.10.10
(1) 디버깅은 문제 해결 능력의 지름길  (0) 2024.10.10
Kernel Source 구조  (1) 2024.10.10