Linux Kernel(4.9) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[0410] Slab Memory Corruption Case Study#2 - 디버깅 [Linux][Kernel] MM

난이도: 하

이제 커널 로그부터 볼게요. 이전과 비슷한 패턴인데 약간 다른 것 같네요.
1  [ 2551.510956 / 04-07 15:43:12.844][3] =============================================================================
2  [ 2551.510994 / 04-07 15:43:12.844][3] BUG kmalloc-256 (Tainted: G        W     ): Invalid object pointer 0xe216ce10
3  [ 2551.511015 / 04-07 15:43:12.844][3] -----------------------------------------------------------------------------
4  [ 2551.511015 / 04-07 15:43:12.844][3] 
5  [ 2551.511047 / 04-07 15:43:12.844][3] INFO: Slab 0xec69a600 objects=36 used=20 fp=0xe216d500 flags=0x4081
6  [ 2551.511550 / 04-07 15:43:12.844][3] Kernel panic - not syncing: slab error
7  [ 2551.511550 / 04-07 15:43:12.844][3] 
8  [ 2551.511631 / 04-07 15:43:12.844][3] CPU: 3 PID: 585 Comm: surfaceflinger Tainted: G    B   W      3.18.31-g6258e9b-dirty #35
9  [ 2551.511699 / 04-07 15:43:12.844][3] [<c0016fb8>] (unwind_backtrace) from [<c0013760>] (show_stack+0x20/0x24)
10 [ 2551.511761 / 04-07 15:43:12.844][3] [<c0013760>] (show_stack) from [<c0f6408c>] (dump_stack+0x9c/0xd4)
11 [ 2551.511796 / 04-07 15:43:12.844][3] [<c0f6408c>] (dump_stack) from [<c0f5f7cc>] (panic+0x120/0x388)
12 [ 2551.511847 / 04-07 15:43:12.844][3] [<c0f5f7cc>] (panic) from [<c014d950>] (slab_err+0x90/0xa4)
13 [ 2551.511915 / 04-07 15:43:12.844][3] [<c014d950>] (slab_err) from [<c0f61fec>] (free_debug_processing+0x1d8/0x300)
14 [ 2551.511981 / 04-07 15:43:12.844][3] [<c0f61fec>] (free_debug_processing) from [<c0f62158>] (__slab_free+0x44/0x3a8)
15 [ 2551.512047 / 04-07 15:43:12.844][3] [<c0f62158>] (__slab_free) from [<c014fa40>] (kfree+0x238/0x28c)
16 [ 2551.512112 / 04-07 15:43:12.844][3] [<c014fa40>] (kfree) from [<c0372810>] (mdss_rotator_ioctl+0x8d4/0x1b70)
18 [ 2551.512213 / 04-07 15:43:12.844][3] [<c0372810>] (mdss_rotator_ioctl) from [<c016b5fc>] (do_vfs_ioctl+0x500/0x5bc)
19 [ 2551.512240 / 04-07 15:43:12.844][3] [<c016b5fc>] (do_vfs_ioctl) from [<c016b714>] (SyS_ioctl+0x5c/0x84)
20 [ 2551.512268 / 04-07 15:43:12.844][3] [<c016b714>] (SyS_ioctl) from [<c000f300>] (ret_fast_syscall+0x0/0x50)

위 로그를 보면 메모리를 해제하는 콜스택을 볼 수 있습니다. mdss_rotator_ioctl -> kfree 순서로 말이죠.
1  [ 2551.510956 / 04-07 15:43:12.844][3] ================================================ 
2  [ 2551.510994 / 04-07 15:43:12.844][3] BUG kmalloc-256 (Tainted: G        W     ): Invalid object pointer 0xe216ce10
3  [ 2551.511015 / 04-07 15:43:12.844][3] -----------------------------------------------------------------------------
 
그런데 0xe216ce10란 오브젝트 주소에 뭔가 문제가 있다고 알려주는 것 같습니다.
참고로 위 로그는 다음 free_debug_processing 함수에서 slab_err 함수를 출력할 때 뿌려줍니다. 이 점 참고하세요.
static noinline struct kmem_cache_node *free_debug_processing(
struct kmem_cache *s, struct page *page, void *object,
unsigned long addr, unsigned long *flags)
{
struct kmem_cache_node *n = get_node(s, page_to_nid(page));

spin_lock_irqsave(&n->list_lock, *flags);
slab_lock(page);

if (!check_slab(s, page))
goto fail;

if (!check_valid_pointer(s, page, object)) {
slab_err(s, page, "Invalid object pointer 0x%p", object);
goto fail;
}
 
그럼 다시 디버깅으로 돌아갈께요.  이번에는 0xe216ce10 주소가 어떤 타입인지 알아볼 차례군요..
crash> kmem 0xe216ce10
CACHE    NAME                 OBJSIZE  ALLOCATED     TOTAL  SLABS  SSIZE
eac03b80 kmalloc-256              256       2017      2556     71    16k
  SLAB      MEMORY    NODE  TOTAL  ALLOCATED  FREE
  ec69a600  e216c000     0     36         20    16
  FREE / [ALLOCATED]
  [e216ce00]

위 명령어로 확인하니 e216ce00 주소에서 시작하는 kmalloc-256 타입 슬랩입니다.
그럼 e216ce00 메모리 주소 근처 메모리 덤프를 볼까요?
d.v %y.l 0xE216CE00
_____address|_data________|value_____________|symbol
NSD:E216CDF8| 5A 5A 5A 5A  0x5A5A5A5A
NSD:E216CDFC| 5A 5A 5A 5A  0x5A5A5A5A
NSD:E216CE00| 40 A1 E2 E4  0xE4E2A140
NSD:E216CE04| 00 8A 35 D6  0xD6358A00                                                   
NSD:E216CE08| 38 A3 4E C0  0xC04EA338         \\vmlinux\base/devres\devm_kmalloc_release
NSD:E216CE0C| 00 00 00 00  0x0                                                   
NSD:E216CE10| 08 00 00 00  0x8                               
NSD:E216CE14| 00 00 00 00  0x0  

E216CE00 주소에서 0x4만큼 떨어진 E216CDFC 주소를 보면 0x5A5A5A5A 값을 담고 있습니다.
슬랩 오브젝트 패딩 값이군요. 슬랩 오브젝트 경계 주소에 0x5A5A5A5A 이란 패딩 매직값을 써준 다는 점 기억하세요.

여기까지 확인한 정보로 다음과 같이 정리할 수 있습니다.
1. 슬랩 오브젝트를 해제하는 과정
2. 0xe216ce10이란 메모리를 해제하려 하는데 이 메모리는 슬랩 오브젝트 시작 주소가 아님
 
그럼 e216ce00 메모리 주소 근처 메모리 덤프를 볼까요?
d.v %y.l 0xE216CE00
_____address|_data________|value_____________|symbol
NSD:E216CDF8| 5A 5A 5A 5A  0x5A5A5A5A
NSD:E216CDFC| 5A 5A 5A 5A  0x5A5A5A5A
NSD:E216CE00| 40 A1 E2 E4  0xE4E2A140
NSD:E216CE04| 00 8A 35 D6  0xD6358A00                                                   
NSD:E216CE08| 38 A3 4E C0  0xC04EA338         \\vmlinux\base/devres\devm_kmalloc_release
NSD:E216CE0C| 00 00 00 00  0x0                                                   
NSD:E216CE10| 08 00 00 00  0x8                               
NSD:E216CE14| 00 00 00 00  0x0                                                   
NSD:E216CE18| 00 00 00 00  0x0                                                   
NSD:E216CE1C| 38 04 00 00  0x438              
NSD:E216CE20| 38 04 00 00  0x438                   
NSD:E216CE24| 00 00 00 00  0x0                                                   
NSD:E216CE28| 00 00 00 00  0x0
//...
NSD:E216CEFC|0x0
NSD:E216CF00|0xCCCCCCCC
NSD:E216CF04|0xE216DC00
NSD:E216CF08|0xC03721AC         \\vmlinux\mdss_rotator\mdss_rotator_ioctl+0x270
NSD:E216CF0C|0xC0150C8C         \\vmlinux\slub\__kmalloc_track_caller+0xE8
NSD:E216CF10|0xC04EA748         \\vmlinux\base/devres\devm_kmalloc+0x28
NSD:E216CF14|0xC03721AC         \\vmlinux\mdss_rotator\mdss_rotator_ioctl+0x270
NSD:E216CF18|0xC016B5FC         \\vmlinux\fs/ioctl\do_vfs_ioctl+0x500
NSD:E216CF1C|0xC016B714         \\vmlinux\fs/ioctl\sys_ioctl+0x5C
NSD:E216CF20|0xC000F300         \\vmlinux\Global\ret_fast_syscall
NSD:E216CF24|0x0
NSD:E216CF28|0x0
NSD:E216CF2C|0x0
NSD:E216CF30|0x0
NSD:E216CF34|0x0
NSD:E216CF38|0x0
NSD:E216CF3C|0x0
NSD:E216CF40|0x0
NSD:E216CF44|0x0
NSD:E216CF48|0x0
 
위 메모리 덤프에서 매우 중요한 정보만 모아서 보여드릴게요. 
NSD:E216CE00| 40 A1 E2 E4  0xE4E2A140
NSD:E216CE04| 00 8A 35 D6  0xD6358A00                                                   
NSD:E216CE08| 38 A3 4E C0  0xC04EA338         \\vmlinux\base/devres\devm_kmalloc_release
NSD:E216CE0C| 00 00 00 00  0x0   
NSD:E216CE10| 08 00 00 00  0x8                               
NSD:E216CE14| 00 00 00 00  0x0                                                   
NSD:E216CE18| 00 00 00 00  0x0                                                  
//...
NSD:E216CF08|0xC03721AC         \\vmlinux\mdss_rotator\mdss_rotator_ioctl+0x270
NSD:E216CF0C|0xC0150C8C         \\vmlinux\slub\__kmalloc_track_caller+0xE8
NSD:E216CF10|0xC04EA748         \\vmlinux\base/devres\devm_kmalloc+0x28
NSD:E216CF14|0xC03721AC         \\vmlinux\mdss_rotator\mdss_rotator_ioctl+0x270

이 슬랩 메모리는 devm_kmalloc로 할당했습니다. 
왜냐면 슬랩 오브젝트에서 0x8 만큼 떨어진 주소에 devm_kmalloc_release 심볼이 있기 때문이죠.
또한 0xE216CF10 주소에도 devm_kmalloc+0x28 심볼이 있습니다.

그래서 devm_kmalloc로 슬랩 메모리를 할당했을 경우 반드시 아래 형식으로 메모리를 해제해야 합니다.
devm_kfree(0xE94AF6D0, 0xE216CE10)

그런데 아래 코드로 메모리를 해제하니 리눅스 커널에선 0xE216CE10 메모리 주소는 슬랩 시작 주소가 아니라고 식별하는 겁니다. 
kfree(0xE216CE10)

실제 코드를 열어보니 kfree로 메모리를 해제하는군요.
static int mdss_rotator_handle_request(struct mdss_rot_mgr *mgr,
        struct mdss_rot_file_private *private, unsigned long arg)
{
        struct mdp_rotation_request user_req;
        struct mdp_rotation_item *items = NULL;
//..

 handle_request_err1:
         mutex_unlock(&mgr->lock);
 handle_request_err:
         kfree(items);  //<<--

커널 크래시 발생할 때도 mdss_rotator_ioctl 심볼에서 kfree 함수를 호출하는 디버깅 정보를 담고 있습니다. 
참고로 위 mdss_rotator_handle_request 함수는 static 타입으로 선언해서 커널이 심볼을 식별하지 못하고 있다는 점 기억하세요.
1 [ 2551.512047 / 04-07 15:43:12.844][3] [<c0f62158>] (__slab_free) from [<c014fa40>] (kfree+0x238/0x28c)
2 [ 2551.512112 / 04-07 15:43:12.844][3] [<c014fa40>] (kfree) from [<c0372810>] (mdss_rotator_ioctl+0x8d4/0x1b70)
3 [ 2551.512213 / 04-07 15:43:12.844][3] [<c0372810>] (mdss_rotator_ioctl) from [<c016b5fc>] (do_vfs_ioctl+0x500/0x5bc)
4 [ 2551.512240 / 04-07 15:43:12.844][3] [<c016b5fc>] (do_vfs_ioctl) from [<c016b714>] (SyS_ioctl+0x5c/0x84)
5 [ 2551.512268 / 04-07 15:43:12.844][3] [<c016b714>] (SyS_ioctl) from [<c000f300>] (ret_fast_syscall+0x0/0x50)

다음과 같이 코드를 수정했더니 이런 문제는 발생하지 않았습니다.
diff --git a/drivers/video/msm/mdss/mdss_rotator.c b/drivers/video/msm/mdss/mdss_rotator.c
index 019621b..4f2262b 100644
--- a/drivers/video/msm/mdss/mdss_rotator.c
+++ b/drivers/video/msm/mdss/mdss_rotator.c
@@ -2244,7 +2244,7 @@ static int mdss_rotator_handle_request(struct mdss_rot_mgr *mgr,
 handle_request_err1:
        mutex_unlock(&mgr->lock);
 handle_request_err:
-       kfree(items);
+       devm_kfree(&mgr->pdev->dev, items);
        devm_kfree(&mgr->pdev->dev, req);
        return ret;
 }

[Appendix 1] 디버깅 
mdss_rotator_handle_request 함수에서 devm_kfree(&mgr->pdev->dev, items); 함수를 호출할 때 어떤 방식으로 파라미터를 전달하는지 확인했습니다.
static int mdss_rotator_handle_request(struct mdss_rot_mgr *mgr,
struct mdss_rot_file_private *private, unsigned long arg)
{
struct mdp_rotation_request user_req;
struct mdp_rotation_item *items = NULL;
struct mdss_rot_entry_container *req = NULL;
//...
handle_request_err:
devm_kfree(&mgr->pdev->dev, items);  //<<--

R0 (struct device*): 0xE94AF6D0 =  &mgr->pdev->dev
R1 (struct mdp_rotation_item *): 0xE216CE10 where "ldr     r1,[r11,#-0x9C]" r1 = 0xDD095E60

devm_kfree 함수 호출 직전 어셈블리 코드를 열어서 어떤 파라미터를 전달했는지 확인하니 다음과 같군요.
1 NSR:C0372800|E5950034  ldr     r0,[r5,#0x34]  // r0 = 0xE94AF6C0 =  *(0xE81E2110 + 0x34) =  *(r5 + 0x34) 
2 NSR:C0372804|E51B109C  ldr     r1,[r11,#-0x9C]
3 NSR:C0372808|E2800010  add     r0,r0,#0x10      ; r0,r0,#16  // r0 = 0xE94AF6D0 = 0xE94AF6C0 + 0x10 = R0 + 0x10
4 NSR:C037280C|EB05E0FD  bl      0xC04EAC08       ; devm_kfree
d.dump 0xDD095EFC-0x9c
_____address|________0________4________8________C
NSD:DD095E60|>E216CE10 C0047703 DD095E84 DD095E78

[Appendix 2] 다음과 같이 코드를 반영해도 비슷한 유형의 커널 크래시를 유발시킬 수 있습니다.
diff --git a/drivers/base/devres.c b/drivers/base/devres.c
index 8754646..6ba967d 100644
--- a/drivers/base/devres.c
+++ b/drivers/base/devres.c
@@ -872,6 +872,8 @@ char *devm_kasprintf(struct device *dev, gfp_t gfp, const char *fmt, ...)
 }
 EXPORT_SYMBOL_GPL(devm_kasprintf);

+extern uint32_t kernel_bsp_debug_level;
+
 /**
  * devm_kfree - Resource-managed kfree
  * @dev: Device this memory belongs to
@@ -883,8 +885,13 @@ void devm_kfree(struct device *dev, void *p)
 {
        int rc;

-       rc = devres_destroy(dev, devm_kmalloc_release, devm_kmalloc_match, p);
-       WARN_ON(rc);
+       if (kernel_bsp_debug_level == 4066) {
+               kfree(p);
+       }
+        else {
+               rc = devres_destroy(dev, devm_kmalloc_release, devm_kmalloc_match, p);
+               WARN_ON(rc);
+       }
 }
 EXPORT_SYMBOL_GPL(devm_kfree);


[0410] Slab Memory Corruption Case Study#1 - 디버깅(2) [Linux][Kernel] MM

[0410] Slab Memory Corruption Case Study#1 - 로그 분석(1)에서 
계속..


여기까지 커널 로그의 의미를 알아봤으니 이제는 슬랩 오브젝트가 오염돼서 커널 크래시 디버깅을 할 차례입니다.
[701.043491][7] BUG kmalloc-512 (Tainted: G        W     ): Poison overwritten
[701.043515][7] -----------------------------------------------------------------------------
[701.043515][7] 
[701.043550][7] INFO: 0xe411ec00-0xe411ec92. First byte 0x87 instead of 0x6b
 
위와 같이 커널 크래시 발생 전 로그를 보면 가장 먼저 슬랩 오브젝트 종류(kmalloc-512)이 오브젝트 메모리 시작 주소를 알려줍니다. 시작 주소는 0xe411ec00인데 이 메모리 주소에 0x6b이란 포이즌 값이 있어야 하는데 0x87이라는 게 문제였죠.

그럼 0xe411ec00 메모리 공간 전 후로 어떤 메모리 덤프 값인지 조금 더 상세히 볼까요?
희한한 점은 할당하려는 슬랩 오브젝트 메모리 주소가 0xE411EC00 인데 이 메모리 주소 전 후로 계속 0x87878787 값이네요. 뭔가 이상합니다.
_____address|_data________|value______|symbol
1  NSD:E411EBE8| 87 87 87 87  0x87878787
2  NSD:E411EBEC| 87 87 87 87  0x87878787
3  NSD:E411EBF0| 87 87 87 87  0x87878787
4  NSD:E411EBF4| 87 87 87 87  0x87878787
5  NSD:E411EBF8| 87 87 87 87  0x87878787
6  NSD:E411EBFC| 87 87 87 87  0x87878787
7  NSD:E411EC00| 87 87 87 87  0x87878787
8  NSD:E411EC04| 87 87 87 87  0x87878787
9  NSD:E411EC08| 87 87 87 87  0x87878787
10 NSD:E411EC0C| 87 87 87 87  0x87878787
11 NSD:E411EC10| 87 87 87 87  0x87878787
12//..
13 NSD:E411EC7C| 87 87 87 87  0x87878787
14 NSD:E411EC80| 87 87 87 87  0x87878787
15 NSD:E411EC84| 87 87 87 87  0x87878787
16 NSD:E411EC88| 87 87 87 87  0x87878787
17 NSD:E411EC8C| 87 87 87 87  0x87878787
18 NSD:E411EC90| 87 87 87 6B  0x6B878787
19 NSD:E411EC94| 6B 6B 6B 6B  0x6B6B6B6B
20 NSD:E411EC98| 6B 6B 6B 6B  0x6B6B6B6B
21 NSD:E411EC9C| 6B 6B 6B 6B  0x6B6B6B6B
22 NSD:E411ECA0| 6B 6B 6B 6B  0x6B6B6B6B
23 NSD:E411ECA4| 6B 6B 6B 6B  0x6B6B6B6B
24 NSD:E411ECA8| 6B 6B 6B 6B  0x6B6B6B6B
25 NSD:E411ECAC| 6B 6B 6B 6B  0x6B6B6B6B
26 NSD:E411ECB0| 6B 6B 6B 6B  0x6B6B6B6B
27 NSD:E411ECB4| 6B 6B 6B 6B  0x6B6B6B6B
28 NSD:E411ECB8| 6B 6B 6B 6B  0x6B6B6B6B
29 NSD:E411ECBC| 6B 6B 6B 6B  0x6B6B6B6B
30 NSD:E411ECC0| 6B 6B 6B 6B  0x6B6B6B6B
31 NSD:E411ECC4| 6B 6B 6B 6B  0x6B6B6B6B
 
이제부터 크래시 유틸리티 프로그램으로 슬랩 디버깅을 할 차례입니다.
크래시 유틸리티 프로그램의 가장 강력한 기능 중 하나가 슬랩 오브젝트 디버깅이거든요.

그럼 0xe411ec00 메모리 속성을 알아볼까요? 
[701.043491][7] BUG kmalloc-512 (Tainted: G        W     ): Poison overwritten
[701.043515][7] -----------------------------------------------------------------------------
[701.043515][7] 
[701.043550][7] INFO: 0xe411ec00-0xe411ec92. First byte 0x87 instead of 0x6b  

이를 알아보기 위해 "kmem [메모리 주소]" 명령어를 입력합니다. 
1 crash> kmem 0xe411ec00
2 CACHE    NAME                 OBJSIZE  ALLOCATED     TOTAL  SLABS  SSIZE
3 eac023c0 kmalloc-512              512       2914      3772    164    16k
4  SLAB      MEMORY    NODE  TOTAL  ALLOCATED  FREE
5  ec797e00  e411c000     0     23         23     0
6  FREE / [ALLOCATED]
7  [e411ec00]

8  PAGE    PHYSICAL   MAPPING    INDEX CNT FLAGS
9 ec797f00  a411e000         0         0  0 8000 tail

세번째 줄 메시지에서 이 메모리는 "kmalloc-512" 타입의 슬랩이라고 알려주네요. 
3 eac023c0 kmalloc-512              512       2914      3772    164    16k
 
그 다음 6번과 7번째 줄 디버깅 정보를 유심히 봐야 합니다.
6  FREE / [ALLOCATED]
7  [e411ec00]
 
이 슬랩 오브젝트는 메모리를 할당한 상태([ALLOCATED])이며 해당 오브젝트가 시작하는 주소는 e411ec00란 의미입니다. 처음 커널 로그에서 봤던 디버깅 정보와 일치하죠. "INFO: 0xe411ec00-0xe411ec92"
[701.043491][7] BUG kmalloc-512 (Tainted: G        W     ): Poison overwritten
[701.043515][7] -----------------------------------------------------------------------------
[701.043515][7] 
[701.043550][7] INFO: 0xe411ec00-0xe411ec92. First byte 0x87 instead of 0x6b  
 
그런데 0xe411ec00 메모리 공간 이전 주소인 E411EBFC 메모리 주소에도 0x87878787란 값이 담겨져 있습니다.
1  NSD:E411EBE8| 87 87 87 87  0x87878787
2  NSD:E411EBEC| 87 87 87 87  0x87878787
3  NSD:E411EBF0| 87 87 87 87  0x87878787
4  NSD:E411EBF4| 87 87 87 87  0x87878787
5  NSD:E411EBF8| 87 87 87 87  0x87878787
6  NSD:E411EBFC| 87 87 87 87  0x87878787
7  NSD:E411EC00| 87 87 87 87  0x87878787
8  NSD:E411EC04| 87 87 87 87  0x87878787
 
뭔가 0xe411ebfc 이전 메모리 주소에서 0xe411ec00 메모리를 오염시키는 것 같은데요.
그럼 0xe411ebfc 메모리 속성을 볼까요? 그럼 다음 명령어를 입력해서 알아볼께요.
crash> kmem 0xe411ebfc
CACHE    NAME                 OBJSIZE  ALLOCATED     TOTAL  SLABS  SSIZE
eac023c0 kmalloc-512              512       2914      3772    164    16k
  SLAB      MEMORY    NODE  TOTAL  ALLOCATED  FREE
  ec797e00  e411c000     0     23         23     0
  FREE / [ALLOCATED]
  [e411e940]

  PAGE    PHYSICAL   MAPPING    INDEX CNT FLAGS
ec797f00  a411e000         0         0  0 8000 tail
 
여기서 아주 유용한 디버깅 정보가 보입니다. "kmalloc-512" 이란 슬랩인데 e411e940 메모리 공간부터 시작하군요.
또한 이 슬랩은 이미 할당한 슬랩 메모리 속성입니다.

그럼 0xe411e940 메모리 덤프가 어떤 값인지 알아볼까요?
_____address|_data________|value_____________|symbol
NSD:E411E938| 87 87 87 87  0x87878787
NSD:E411E93C| 87 87 87 87  0x87878787
NSD:E411E940| 87 87 87 87  0x87878787
NSD:E411E944| 87 87 87 87  0x87878787
NSD:E411E948| 87 87 87 87  0x87878787
NSD:E411E94C| 87 87 87 87  0x87878787
NSD:E411E950| 87 87 87 87  0x87878787
NSD:E411E954| 87 87 87 87  0x87878787
NSD:E411E958| 87 87 87 87  0x87878787
NSD:E411E95C| 87 87 87 87  0x87878787
NSD:E411E960| 87 87 87 87  0x87878787
NSD:E411E964| 87 87 87 87  0x87878787
NSD:E411E968| 87 87 87 87  0x87878787
NSD:E411E96C| 87 87 87 87  0x87878787
NSD:E411E970| 87 87 87 87  0x87878787
NSD:E411E974| 87 87 87 87  0x87878787
NSD:E411E978| 87 87 87 87  0x87878787
NSD:E411E97C| 87 87 87 87  0x87878787
NSD:E411E980| 87 87 87 87  0x87878787
NSD:E411E984| 87 87 87 87  0x87878787
NSD:E411E988| 87 87 87 87  0x87878787
NSD:E411E98C| 87 87 87 87  0x87878787
NSD:E411E990| 87 87 87 87  0x87878787
NSD:E411E994| 87 87 87 87  0x87878787
NSD:E411E998| 87 87 87 87  0x87878787
NSD:E411E99C| 87 87 87 87  0x87878787
NSD:E411E9A0| 87 87 87 87  0x87878787
NSD:E411E9A4| 87 87 87 87  0x87878787
NSD:E411E9A8| 87 87 87 87  0x87878787
NSD:E411E9AC| 87 87 87 87  0x87878787
NSD:E411E9B0| 87 87 87 87  0x87878787
NSD:E411E9B4| 87 87 87 87  0x87878787
NSD:E411E9B8| 87 87 87 87  0x87878787
NSD:E411E9BC| 87 87 87 87  0x87878787
NSD:E411E9C0| 87 87 87 87  0x87878787
NSD:E411E9C4| 87 87 87 87  0x87878787
NSD:E411E9C8| 87 87 87 87  0x87878787
NSD:E411E9CC| 87 87 87 87  0x87878787
NSD:E411E9D0| 87 87 87 87  0x87878787
NSD:E411E9D4| 87 87 87 87  0x87878787
NSD:E411E9D8| 87 87 87 87  0x87878787
NSD:E411E9DC| 87 87 87 87  0x87878787
NSD:E411E9E0| 87 87 87 87  0x87878787
NSD:E411E9E4| 87 87 87 87  0x87878787
NSD:E411E9E8| 87 87 87 87  0x87878787
NSD:E411E9EC| 87 87 87 87  0x87878787
NSD:E411E9F0| 87 87 87 87  0x87878787
NSD:E411E9F4| 87 87 87 87  0x87878787
NSD:E411E9F8| 87 87 87 87  0x87878787
NSD:E411E9FC| 87 87 87 87  0x87878787
NSD:E411EA00| 87 87 87 87  0x87878787
NSD:E411EA04| 87 87 87 87  0x87878787
NSD:E411EBF8| 87 87 87 87  0x87878787
NSD:E411EBFC| 87 87 87 87  0x87878787
NSD:E411EC00| 87 87 87 87  0x87878787
NSD:E411EC04| 87 87 87 87  0x87878787

위 메모리 덤프에서 다음과 같은 정보를 얻어 낼 수 있습니다.
1. 이 슬랩 오브젝트가 위치한 E411E940--E411EBF8 메모리 공간은 0x87878787 값으로 덮혀 있습니다.
2. 이 슬랩 오브젝트는 이미 할당 됐습니다. 그런데 이 메모리 덤프 안에 (struct track*)에 대응하는 메모리 할당 정보도 없습니다. 
3. E411E940 이전 메모리 공간인 E411E938/E411E93C 에 있는 덤프 값을 봐도 0x87878787으로 돼있군요.
 
정리하면 E411E940 메모리 이전에 누군가가 0x87878787 값으로 이 메모리를 오염시킨 것 같군요.

그럼 E411E93C 메모리 속성을 알아볼까요?
crash> kmem E411E93C
CACHE    NAME                 OBJSIZE  ALLOCATED     TOTAL  SLABS  SSIZE
eac023c0 kmalloc-512              512       2914      3772    164    16k
  SLAB      MEMORY    NODE  TOTAL  ALLOCATED  FREE
  ec797e00  e411c000     0     23         23     0
  FREE / [ALLOCATED]
  [e411e680]

  PAGE    PHYSICAL   MAPPING    INDEX CNT FLAGS
ec797f00  a411e000         0         0  0 8000 tail

이번에도 "kmalloc-512" 타입의 슬랩인데 e411e680 메모리 주소가 시작점이라는 군요. 
물론 이미 할당된 슬랩 오브젝트라고 합니다. 
_____address|value_____________|symbol
NSD:E411E618|0xC0D55730         \\vmlinux\skbuff\skb_free_head+0x68
NSD:E411E61C|0xC014FA40         \\vmlinux\slub\kfree+0x238
NSD:E411E620|0xC0D55730         \\vmlinux\skbuff\skb_free_head+0x68
NSD:E411E624|0xC0D55964         \\vmlinux\skbuff\skb_release_data+0xCC
NSD:E411E628|0xC0D5599C         \\vmlinux\skbuff\skb_release_all+0x30
NSD:E411E62C|0xC0D559BC         \\vmlinux\skbuff\__kfree_skb+0x1C
NSD:E411E630|0xC0D55B58         \\vmlinux\skbuff\consume_skb+0xE8
NSD:E411E634|0xC0D5BF0C         \\vmlinux\core/datagram\skb_free_datagram+0x20
NSD:E411E638|0xC0E58B08         \\vmlinux\af_unix\unix_dgram_recvmsg\out_unlock
NSD:E411E63C|0xC0E58B60         \\vmlinux\af_unix\unix_seqpacket_recvmsg+0x38
NSD:E411E640|0xC0D4DBD0         \\vmlinux\socket\sock_recvmsg+0xA8
NSD:E411E644|0xC0D4FEC8         \\vmlinux\socket\sys_recvfrom+0xD4
NSD:E411E648|0xC000F300         \\vmlinux\Global\ret_fast_syscall
NSD:E411E64C|0x0
NSD:E411E650|0x0
NSD:E411E654|0x0
NSD:E411E658|0x0
NSD:E411E65C|0x6                 
NSD:E411E660|0x18EA
NSD:E411E664|0x3B3F
NSD:E411E668|0x5A5A5A5A
NSD:E411E66C|0x5A5A5A5A
NSD:E411E670|0x5A5A5A5A
NSD:E411E674|0x5A5A5A5A
NSD:E411E678|0x5A5A5A5A
NSD:E411E67C|0x5A5A5A5A
NSD:E411E680|0x87878787
NSD:E411E684|0x87878787
NSD:E411E688|0x87878787
NSD:E411E68C|0x87878787
NSD:E411E690|0x87878787
 
이번에는 뭔가 다른 메모리 덤프가 보입니다. E411E680 메모리 주소부터 0x87878787란 값이 보이네요.
E411E680 메모리 주소 이전 주소부터 0x5A5A5A5A란 슬랩 오브젝트 경계를 알려주는 패딩 값이 제대로 보이는군요.
또한 슬랩 메모리 프로파일링 정보도 E411E61C 주소부터 저장됐구요.

여기까지 얻은 디버깅 정보로 정리를 해볼게요.
0xe411e680 주소부터 써진 0x87878787 이란 값이 다음 슬랩 오브젝트 메모리 공간까지 밀려서 오염시킨 정보를 확인할 수 있습니다.  
0xe411e680 -> 0xe411e940 -> 0xe411ec00 
[701.043491][7] BUG kmalloc-512 (Tainted: G        W     ): Poison overwritten
[701.043515][7] -----------------------------------------------------------------------------
[701.043515][7] 
[701.043550][7] INFO: 0xe411ec00-0xe411ec92. First byte 0x87 instead of 0x6b 
 
이런 메모리 패턴을 리눅스 커널 커뮤니티에서는 out-of-bound copy라고도 합니다.

그럼 0xe411e680 슬랩 오브젝트가 메모리를 오염시킨 정보를 확인했습니다.
이제는 어떤 코드가 이런 문제를 일으켰는지 알아볼 시간입니다.

슬랩 오브젝트는 대부분 포인터 변수가 가르키므로, 0xe411e680 슬랩 오브젝트를 누가 가르키고 있는지 알아봐야 합니다. 이를 위해 크래시 유틸리티 프로그램에서 제공하는 search란 기능을 활용하려 합니다.

다음 명령어를 입력하면 커널 메모리 공간에서 e411e680 주소를 담고 있는 주소를 출력합니다.
crash> search e411e680
c1c7d2bc: e411e680
d56cdc2c: e411e680
d56cdccc: e411e680
e411fbc4: e411e680
 
커맨드 입력 결과 4군데에서 e411e680 주소를 가르키고 있군요.

이 4군데 주소 중 c1c7d2bc 주소가 어떤 유형인지 알아보기 위해 다음 kmem 명령어를 입력합니다.
crash> kmem c1c7d2bc
c1c7d2bc (B) austin_debug_mem_data

  PAGE    PHYSICAL   MAPPING    INDEX CNT FLAGS
eb672e80  81c7d000         0         0  1 400 reserved

crash> p austin_debug_mem_data
austin_debug_mem_data = $1 = (u32 *) 0xe411e680
 
오라, austin_debug_mem_data이란 포인터 타입의 전역 변수가 0xe411e680를 가르키고 있군요.

해당 코드를 열어 보고 슬랩 메모리를 오염시키 주범을 찾았습니다. 
512 바이트 만큼 메모리를 할당하고 1555 바이트 만큼 메모리를 0x87 복사하고 있습니다. 
u32 *austin_debug_data;

109        austin_debug_mem_data = kmalloc(512, GFP_KERNEL);
110        memset(austin_debug_mem_data, 0x87, 1555);

말 그대로 out-of-bound 동작 오류로 슬랩 오브젝트 오염을 시켰군요.

[0410] Slab Memory Corruption Case Study#1 - 로그 분석(1) [Linux][Kernel] MM

슬랩 메모리 Corruption Cast Study: 난이도 최하

이번에는 슬랩 오브젝트가 메모리를 깨면 어떤 방식으로 커널 크래시가 발생하는지 알아볼게요.
이로 슬랩 오브젝트의 실제 자료 구조를 알 수 있습니다.

아 그럼 우선 커널 로그 부터 볼게요. 음 평소에는 볼 수 없는 요상한 로그를 출력하고 있군요.
그럼 각각 로그가 어떤 의미인지 천천히 살펴볼까요?
[701.043443][7] =============================================================================
[701.043491][7] BUG kmalloc-512 (Tainted: G        W     ): Poison overwritten
[701.043515][7] -----------------------------------------------------------------------------
[701.043515][7] 
[701.043550][7] INFO: 0xe411ec00-0xe411ec92. First byte 0x87 instead of 0x6b
[701.043588][7] INFO: Allocated in alloc_buffer+0x28/0x14c age=31044 cpu=4 pid=103
[701.043617][7]  __kmalloc+0xe8/0x2b8
[701.043644][7]  alloc_buffer+0x28/0x14c
[701.043669][7]  __bufio_new+0x74/0x24c
[701.043693][7]  dm_bufio_prefetch+0x94/0x140
[701.043720][7]  verity_prefetch_io+0x140/0x158
[701.043747][7]  process_one_work+0x260/0x478
[701.043772][7]  worker_thread+0x2c4/0x408
[701.043798][7]  kthread+0xf8/0x10c
[701.043825][7]  ret_from_fork+0x14/0x20
[701.043854][7] INFO: Freed in free_buffer+0xa4/0xb0 age=24255 cpu=6 pid=80
[701.043880][7]  kfree+0x238/0x28c
[701.043906][7]  free_buffer+0xa4/0xb0
[701.043930][7]  __free_buffer_wake+0x28/0x60
[701.043954][7]  __cleanup_old_buffer+0x80/0x9c
[701.043979][7]  work_fn+0x80/0xcc
[701.044004][7]  process_one_work+0x260/0x478
[701.044029][7]  worker_thread+0x2c4/0x408
[701.044054][7]  kthread+0xf8/0x10c
[701.044079][7]  ret_from_fork+0x14/0x20
[701.044104][7] INFO: Slab 0xec797e00 objects=23 used=23 fp=0x  (null) flags=0x4080
[701.044129][7] INFO: Object 0xe411ec00 @offset=11264 fp=0xe411c580
[701.044129][7] 
[701.044165][7] Bytes b4 e411ebf0: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044190][7] Object e411ec00: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044215][7] Object e411ec10: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044239][7] Object e411ec20: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044264][7] Object e411ec30: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044288][7] Object e411ec40: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044313][7] Object e411ec50: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044338][7] Object e411ec60: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044363][7] Object e411ec70: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044388][7] Object e411ec80: 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87 87  ................
[701.044412][7] Object e411ec90: 87 87 87 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  ...kkkkkkkkkkkkk
[701.044437][7] Object e411eca0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044462][7] Object e411ecb0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044487][7] Object e411ecc0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044511][7] Object e411ecd0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044536][7] Object e411ece0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044561][7] Object e411ecf0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044585][7] Object e411ed00: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044610][7] Object e411ed10: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044634][7] Object e411ed20: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044659][7] Object e411ed30: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044684][7] Object e411ed40: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044709][7] Object e411ed50: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044733][7] Object e411ed60: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044758][7] Object e411ed70: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044783][7] Object e411ed80: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044807][7] Object e411ed90: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044831][7] Object e411eda0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044856][7] Object e411edb0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044881][7] Object e411edc0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044906][7] Object e411edd0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044930][7] Object e411ede0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[701.044955][7] Object e411edf0: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5  kkkkkkkkkkkkkkk.
[701.044980][7] Redzone e411ee00: bb bb bb bb                                      ....
[701.045005][7] Padding e411eea8: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[701.045030][7] Padding e411eeb8: 5a 5a 5a 5a 5a 5a 5a 5a                          ZZZZZZZZ
[701.045505][7] Kernel panic - not syncing: object poison overwritten
[701.045505][7] 
[701.045540][7] CPU: 7 PID: 6578 Comm: sh Tainted: G    B   W      3.18.31-g6258e9b-dirty #34
[701.045572][7] [<c0016fb8>] (unwind_backtrace) from [<c0013760>] (show_stack+0x20/0x24)
[701.045603][7] [<c0013760>] (show_stack) from [<c0f64068>] (dump_stack+0x9c/0xd4)
[701.045635][7] [<c0f64068>] (dump_stack) from [<c0f5f7a8>] (panic+0x120/0x388)
[701.045668][7] [<c0f5f7a8>] (panic) from [<c014d410>] (check_bytes_and_report+0x98/0xc8)
[701.045700][7] [<c014d410>] (check_bytes_and_report) from [<c014dc88>] (check_object+0x134/0x218)
[701.045733][7] [<c014dc88>] (check_object) from [<c0f618c8>] (alloc_debug_processing+0x8c/0x15c)
[701.045765][7] [<c0f618c8>] (alloc_debug_processing) from [<c0f61d94>] (__slab_alloc.constprop.8+0x3fc/0x458)
[701.045797][7] [<c0f61d94>] (__slab_alloc.constprop.8) from [<c014e968>] (__kmalloc+0xe8/0x2b8)
[701.045829][7] [<c014e968>] (__kmalloc) from [<c01a305c>] (load_elf_binary+0xf8/0x101c)
[701.045860][7] [<c01a305c>] (load_elf_binary) from [<c01603d0>] (search_binary_handler+0x84/0x1cc)
[701.045892][7] [<c01603d0>] (search_binary_handler) from [<c0160c14>] (do_execve+0x360/0x5bc)
[701.045922][7] [<c0160c14>] (do_execve) from [<c0161080>] (SyS_execve+0x2c/0x30)
[701.045953][7] [<c0161080>] (SyS_execve) from [<c000f300>] (ret_fast_syscall+0x0/0x50)
 
우선 가장 먼저 찍히는 로그부터 볼께요. 첫번째 로그 메시지는 "kmalloc-512" 슬랩 오브젝트가 오염됐다는 표시입니다.
1 [701.043491][7] BUG kmalloc-512 (Tainted: G        W     ): Poison overwritten
2 [701.043515][7] -----------------------------------------------------------------------------
3 [701.043515][7] 
4 [701.043550][7] INFO: 0xe411ec00-0xe411ec92. First byte 0x87 instead of 0x6b

4번째 메시지는 현재 할당하려는 슬랩 오브젝트 메모리 공간이 0xe411ec00-0xe411ec92인데,
몇 바이트가 0x6b 이어야 하는데, 0x87이란 메시지입니다. 

슬랩 오브젝트 디버그 옵션을 키면 메모리 속성에 따라 메모리 포이즌 값을 써줍니다. 그런데
슬랩 오브젝트 메모리를 해제할 때 슬랩 오브젝트 패이로드 주소에 0x6b이란 포이즌 값을 써 줍니다. 메모리를 해제한다는 뜻이죠.

이미 해제한 메모리 슬랩 오브젝트 공간이라 0x6b 값일 줄 알았는데 0x87이라고 말하는군요.
0x87 덤프가 이 메모리 공간을 오염시키고 있다는 의미입니다.

그럼 다음 에러 메시지를 볼까요?
1  [701.043588][7] INFO: Allocated in alloc_buffer+0x28/0x14c age=31044 cpu=4 pid=103
2  [701.043617][7]  __kmalloc+0xe8/0x2b8
3  [701.043644][7]  alloc_buffer+0x28/0x14c
4  [701.043669][7]  __bufio_new+0x74/0x24c
5  [701.043693][7]  dm_bufio_prefetch+0x94/0x140
6  [701.043720][7]  verity_prefetch_io+0x140/0x158
7  [701.043747][7]  process_one_work+0x260/0x478
8  [701.043772][7]  worker_thread+0x2c4/0x408
9  [701.043798][7]  kthread+0xf8/0x10c
10 [701.043825][7]  ret_from_fork+0x14/0x20
11 [701.043854][7] INFO: Freed in free_buffer+0xa4/0xb0 age=24255 cpu=6 pid=80
12 [701.043880][7]  kfree+0x238/0x28c
13 [701.043906][7]  free_buffer+0xa4/0xb0
14 [701.043930][7]  __free_buffer_wake+0x28/0x60
15 [701.043954][7]  __cleanup_old_buffer+0x80/0x9c
16 [701.043979][7]  work_fn+0x80/0xcc
17 [701.044004][7]  process_one_work+0x260/0x478
18 [701.044029][7]  worker_thread+0x2c4/0x408
19 [701.044054][7]  kthread+0xf8/0x10c
20 [701.044079][7]  ret_from_fork+0x14/0x20

1번부터 10번째 줄 메시지는 이 슬랩 오브젝트를 할당했을 때 프로파일 정보입니다.
슬랩 오브젝트를 할당한 주소: alloc_buffer+0x28/0x14c
CPU번호: CPU4에서 돌던 프로세스
pid: 103
age: 슬랩 오브젝트를 할당한 시간 31044

그리고 콜스택을 뿌려주는 군요.

이 디버깅 정보는 슬랩 오브젝트 메모리 0xE411EE08 공간에서 출력하는데요.
다음과 같이 똑같은 콜스택 정보를 볼 수 있습니다.
d.v %y.l 0xE411EE08
_____address|_data________|value______|symbol
NSD:E411EE04| 80 C5 11 E4  0xE411C580
NSD:E411EE08| 90 0D 8A C0  0xC08A0D90  \\vmlinux\dm-bufio\alloc_buffer+0x28
NSD:E411EE0C| 68 E9 14 C0  0xC014E968  \\vmlinux\slub\__kmalloc+0xE8
NSD:E411EE10| 90 0D 8A C0  0xC08A0D90  \\vmlinux\dm-bufio\alloc_buffer+0x28
NSD:E411EE14| EC 21 8A C0  0xC08A21EC  \\vmlinux\dm-bufio\__bufio_new+0x74
NSD:E411EE18| 7C 25 8A C0  0xC08A257C  \\vmlinux\dm-bufio\dm_bufio_prefetch+0x94
NSD:E411EE1C| 80 77 8A C0  0xC08A7780  \\vmlinux\dm-verity-target\verity_prefetch_io\no_prefetch_cluster+0x20
NSD:E411EE20| 48 5A 04 C0  0xC0045A48  \\vmlinux\workqueue\process_one_work+0x260
NSD:E411EE24| 98 68 04 C0  0xC0046898  \\vmlinux\workqueue\worker_thread\recheck+0x284
NSD:E411EE28| 48 AA 04 C0  0xC004AA48  \\vmlinux\kernel/kthread\kthread+0xF8
NSD:E411EE2C| D0 F3 00 C0  0xC000F3D0  \\vmlinux\Global\ret_from_fork+0x14

0xE411EE08 주소부터 슬랩 오브젝트를 프로파일링 구조체인 (struct track *)이 시작한다는 것 아시죠?
저번 세미나 때 슬랩 오브젝트 자료 구조에 대해서 설명을 드렸잖아요.

Trace32 프로그램으로 확인해도 똑같은 디버깅 정보를 볼 수 있습니다.
v.v %t %d %h %i (struct track*)0xE411EE08
  (struct track *) (struct track*)0xE411EE08 = 0xE411EE08 -> (
    (long unsigned int) addr = 3230272912 = 0xC08A0D90,
    (long unsigned int [16]) addrs = (
      [0] = 3222595944 = 0xC014E968,
      [1] = 3230272912 = 0xC08A0D90,
      [2] = 3230278124 = 0xC08A21EC,
      [3] = 3230279036 = 0xC08A257C,
      [4] = 3230300032 = 0xC08A7780,
      [5] = 3221510728 = 0xC0045A48,
      [6] = 3221514392 = 0xC0046898,
      [7] = 3221531208 = 0xC004AA48,
      [8] = 3221287888 = 0xC000F3D0,
      [9] = 0 = 0x0,
      [10] = 0 = 0x0,
      [11] = 0 = 0x0,
      [12] = 0 = 0x0,
      [13] = 0 = 0x0,
      [14] = 0 = 0x0,
      [15] = 0 = 0x0),
    (int) cpu = 4 = 0x4,
    (int) pid = 103 = 0x67,
    (long unsigned int) when = 9060 = 0x2364)

그럼 다음 커널 로그를 참고해서 이 슬랩 오브젝트를 할당한 0xC08A0D90 주소를 한번 가볼까요?
[701.043588][7] INFO: Allocated in alloc_buffer+0x28/0x14c age=31044 cpu=4 pid=103
[701.043617][7]  __kmalloc+0xe8/0x2b8
[701.043644][7]  alloc_buffer+0x28/0x14c

다음 명령어를 입력하니 drivers\md\dm-bufio.c 파일에 397 라인에 alloc_buffer이란 함수가 있다는 군요.
y.l.line  0xC08A0D90 
_____address________|source_______________________|line_______________|offset____|
P:C08A0D90--C08A0D97|kernel\drivers\md\dm-bufio.c|\397--0  dm-bufio\alloc_buffer+0x28
P:C08A0D98--C08A0D9B|kernel\drivers\md\dm-bufio.c|\400--0  dm-bufio\alloc_buffer+0x30

해당 코드를 열어보니 이 슬랩 오브젝트는 (struct dm_buffer *) 란 구조체로 쓰고 있었습니다.
https://elixir.bootlin.com/linux/v3.18.104/source/drivers/md/dm-bufio.c#L393
393 static struct dm_buffer *alloc_buffer(struct dm_bufio_client *c, gfp_t gfp_mask)
394 {
395 struct dm_buffer *b = kmalloc(sizeof(struct dm_buffer) + c->aux_size,
396       gfp_mask);

여기서 주의 깊게 봐야할 점은 이 슬랩 오브젝트를 할당했던 정보는 별 의미가 없다는 겁니다.
왜냐면 이 슬랩 오브젝트는 이미 해제됐던 메모리이기 때문이죠.

11번부터 20번째 줄 메시지는 이 슬랩 오브젝트를 해제했을 때 프로파일 정보입니다.
이 프로파일 정보가 그래도 주의 깊게 봐야할 정보입니다.

자 다시 로그를 함께 볼까요?
11 [701.043854][7] INFO: Freed in free_buffer+0xa4/0xb0 age=24255 cpu=6 pid=80
12 [701.043880][7]  kfree+0x238/0x28c
13 [701.043906][7]  free_buffer+0xa4/0xb0
14 [701.043930][7]  __free_buffer_wake+0x28/0x60
15 [701.043954][7]  __cleanup_old_buffer+0x80/0x9c
16 [701.043979][7]  work_fn+0x80/0xcc
17 [701.044004][7]  process_one_work+0x260/0x478
18 [701.044029][7]  worker_thread+0x2c4/0x408
19 [701.044054][7]  kthread+0xf8/0x10c
20 [701.044079][7]  ret_from_fork+0x14/0x20

해당 슬랩 오브젝트를 해제한 프로파일 정보는 다음과 같습니다.
CPU번호: CPU6에서 돌던 프로세스
pid: 80
age: 슬랩 오브젝트를 할당한 시간 24255

해당 정보는 E411EE58 메모리 공간에서 확인할 수 있군요.
________address|_data________|value______|symbol
NSD:E411EE58| F8 0F 8A C0  0xC08A0FF8  \\vmlinux\dm-bufio\free_buffer+0xA4
NSD:E411EE5C| 40 FA 14 C0  0xC014FA40  \\vmlinux\slub\kfree+0x238
NSD:E411EE60| F8 0F 8A C0  0xC08A0FF8  \\vmlinux\dm-bufio\free_buffer+0xA4
NSD:E411EE64| 2C 10 8A C0  0xC08A102C  \\vmlinux\dm-bufio\__free_buffer_wake+0x28
NSD:E411EE68| 20 1F 8A C0  0xC08A1F20  \\vmlinux\dm-bufio\__cleanup_old_buffer+0x80
NSD:E411EE6C| 8C 20 8A C0  0xC08A208C  \\vmlinux\dm-bufio\work_fn+0x80
NSD:E411EE70| 48 5A 04 C0  0xC0045A48  \\vmlinux\workqueue\process_one_work+0x260
NSD:E411EE74| 98 68 04 C0  0xC0046898  \\vmlinux\workqueue\worker_thread\recheck+0x284
NSD:E411EE78| 48 AA 04 C0  0xC004AA48  \\vmlinux\kernel/kthread\kthread+0xF8
NSD:E411EE7C| D0 F3 00 C0  0xC000F3D0  \\vmlinux\Global\ret_from_fork+0x14
NSD:E411EE80| 00 00 00 00  0x0
NSD:E411EE84| 00 00 00 00  0x0
NSD:E411EE88| 00 00 00 00  0x0
NSD:E411EE8C| 00 00 00 00  0x0
NSD:E411EE90| 00 00 00 00  0x0
NSD:E411EE94| 00 00 00 00  0x0
NSD:E411EE98| 00 00 00 00  0x0
NSD:E411EE9C| 06 00 00 00  0x6         
NSD:E411EEA0| 50 00 00 00  0x50        
NSD:E411EEA4| E9 3D 00 00  0x3DE9
NSD:E411EEA8| 5A 5A 5A 5A  0x5A5A5A5A
NSD:E411EEAC| 5A 5A 5A 5A  0x5A5A5A5A
NSD:E411EEB0| 5A 5A 5A 5A  0x5A5A5A5A
NSD:E411EEB4| 5A 5A 5A 5A  0x5A5A5A5A

Trace32 프로그램으로 확인해도 똑같은 디버깅 정보를 볼 수 있습니다.
v.v %t %d %h %i (struct track*)0xE411EE58
  (struct track *) (struct track*)0xE411EE58 = 0xE411EE58 -> (
    (long unsigned int) addr = 3230273528 = 0xC08A0FF8,
    (long unsigned int [16]) addrs = (
      [0] = 3222600256 = 0xC014FA40,
      [1] = 3230273528 = 0xC08A0FF8,
      [2] = 3230273580 = 0xC08A102C,
      [3] = 3230277408 = 0xC08A1F20,
      [4] = 3230277772 = 0xC08A208C,
      [5] = 3221510728 = 0xC0045A48,
      [6] = 3221514392 = 0xC0046898,
      [7] = 3221531208 = 0xC004AA48,
      [8] = 3221287888 = 0xC000F3D0,
      [9] = 0 = 0x0,
      [10] = 0 = 0x0,
      [11] = 0 = 0x0,
      [12] = 0 = 0x0,
      [13] = 0 = 0x0,
      [14] = 0 = 0x0,
      [15] = 0 = 0x0),
    (int) cpu = 6 = 0x6,
    (int) pid = 80 = 0x50,
    (long unsigned int) when = 15849 = 0x3DE9)

그럼 다음 커널 로그를 참고해서 이 슬랩 오브젝트를 할당한 0xC08A0D90 주소를 한번 가볼까요?
11 [701.043854][7] INFO: Freed in free_buffer+0xa4/0xb0 age=24255 cpu=6 pid=80
12 [701.043880][7]  kfree+0x238/0x28c
13 [701.043906][7]  free_buffer+0xa4/0xb0
y.l.line 0xC08A0FF8
_____address________|module____________|source_____________________|line_______|offset____|
P:C08A0FF0--C08A1003|\\vmlinux\dm-bufio|kernel\drivers\md\dm-bufio.c|\385--0 dm-bufio\free_buffer+0x9C

이번에는 0xC08A0FF8 주소를 어셈블리 코드로 보니 kfree로 메모리를 해제하는군요.
___addr/line|code_____|mnemonic________________|comment______|
NSR:C08A0FE0|E1A01003  cpy     r1,r3
NSR:C08A0FE4|E59F0014  ldr     r0,0xC08A1000
NSR:C08A0FE8|EB1AFC24  bl      0xC0F60080       ; printk
NSP:C08A0FEC|E7F001F2  dcd     0xE7F001F2
NSR:C08A0FF0|E1A00004  cpy     r0,r4            ; r0,b
NSR:C08A0FF4|EBE2BA03  bl      0xC014F808       ; kfree

다음 에러 메시지는 커널 크래시가 발생한 콜스택 정보입니다. 별 의미 있는 디버깅 정보는 보이지 않는군요.
[701.045668][7] [<c0f5f7a8>] (panic) from [<c014d410>] (check_bytes_and_report+0x98/0xc8)
[701.045700][7] [<c014d410>] (check_bytes_and_report) from [<c014dc88>] (check_object+0x134/0x218)
[701.045733][7] [<c014dc88>] (check_object) from [<c0f618c8>] (alloc_debug_processing+0x8c/0x15c)
[701.045765][7] [<c0f618c8>] (alloc_debug_processing) from [<c0f61d94>] (__slab_alloc.constprop.8+0x3fc/0x458)
[701.045797][7] [<c0f61d94>] (__slab_alloc.constprop.8) from [<c014e968>] (__kmalloc+0xe8/0x2b8)
[701.045829][7] [<c014e968>] (__kmalloc) from [<c01a305c>] (load_elf_binary+0xf8/0x101c)
[701.045860][7] [<c01a305c>] (load_elf_binary) from [<c01603d0>] (search_binary_handler+0x84/0x1cc)
[701.045892][7] [<c01603d0>] (search_binary_handler) from [<c0160c14>] (do_execve+0x360/0x5bc)
[701.045922][7] [<c0160c14>] (do_execve) from [<c0161080>] (SyS_execve+0x2c/0x30)
[701.045953][7] [<c0161080>] (SyS_execve) from [<c000f300>] (ret_fast_syscall+0x0/0x50)

대신 슬랩 오브젝트에 대한 상세 커널 로그를 출력해준 함수가 check_bytes_and_report이라고 알려주네요.
이 함수는 꼭 분석하기를 바래요.

여기까지 커널 로그의 의미를 알아봤으니 이제는 슬랩 오브젝트가 오염돼서 커널 크래시 디버깅을 할 차례입니다.
[701.043491][7] BUG kmalloc-512 (Tainted: G        W     ): Poison overwritten
[701.043515][7] -----------------------------------------------------------------------------
[701.043515][7] 
[701.043550][7] INFO: 0xe411ec00-0xe411ec92. First byte 0x87 instead of 0x6b

위와 같이 커널 크래시 발생 전 로그를 보면 가장 먼저 슬랩 오브젝트 종류(kmalloc-512)이 오브젝트 메모리 시작 주소를 알려줍니다. 시작 주소는 0xe411ec00인데 이 메모리 주소에 0x6b이란 포이즌 값이 있어야 하는데 0x87이라는 게 문제였죠.

여기까지 커널 로그를 분석했습니다. 다음에는 슬랩 메모리를 누가 오염 시켰는지 알아볼께요.

[Kernel][Crash] 워크큐(workqueue) 락업(3) at [0321] [Crash]Troubleshooting!!

[Kernel][Crash] 워크큐(workqueue) 락업(1) at [0321] 
[Kernel][Crash] 워크큐(workqueue) 락업(2) at [0321] 

http://rousalome.egloos.com/9974388
http://rousalome.egloos.com/9974638
코어 덤프에서 계속...

worker_pool_idr 전역 변수에 접근하면 (*(worker_pool_idr.top)).ary 각 CPU별 워커풀 인스턴스가 있다고 했죠.
  (static struct idr) worker_pool_idr = (
    (struct idr_layer *) hint = 0xFFFFFFC6307B9308,
    (struct idr_layer *) top = 0xFFFFFFC6307B9308 -> (
      (int) prefix = 0x0,
      (int) layer = 0x0,
      (struct idr_layer * [256]) ary = (
        [0x0] = 0xFFFFFFC63A30B000,  //<<--CPU0, node0
        [0x1] = 0xFFFFFFC63A30B400,  //<<--CPU0, node1 
        [0x2] = 0xFFFFFFC63A322000,  //<<--CPU1, node0
        [0x3] = 0xFFFFFFC63A322400,  //<<--CPU1, node1 
        [0x4] = 0xFFFFFFC63A339000,  //<<--CPU2, node0
        [0x5] = 0xFFFFFFC63A339400,  //<<--CPU2, node1 
        [0x6] = 0xFFFFFFC63A350000,  //<<--CPU3, node0
        [0x7] = 0xFFFFFFC63A350400,  //<<--CPU3, node1
        [0x8] = 0xFFFFFFC63A367000,
        [0x9] = 0xFFFFFFC63A367400,
        [0x0A] = 0xFFFFFFC63A37E000,
        [0x0B] = 0xFFFFFFC63A37E400,
        [0x0C] = 0xFFFFFFC63A395000

이 워커 풀을 하나 하나 씩 열어서 아직 실행 안된 배리어 워크가 있는 지 알아봐야 합니다.
각각 워커풀을 다 확인했는데, CPU1 워커풀에서 뭔가 특이한 디버깅 정보가 보이네요.

아래는 CPU1에 대한 워커풀입니다.
  (struct worker_pool *) (struct worker_pool*)0xFFFFFFC63A322000 = 0xFFFFFFC63A322000 -> (
    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner =
    (int) cpu = 1 = 0x1,
    (int) node = 0 = 0x0,
    (int) id = 2 = 0x2,
    (unsigned int) flags = 0 = 0x0,
    (long unsigned int) watchdog_ts = 4302303488 = 0x00000001006FF100,
    (struct list_head) worklist = ((struct list_head *) next = 0xFFFFFFC63A322030, (struct list_head
    (int) nr_workers = 4 = 0x4,
    (int) nr_idle = 2 = 0x2,
    (struct list_head) idle_list = ((struct list_head *) next = 0xFFFFFFC582B43380, (struct list_hea
    (struct timer_list) idle_timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0xDE
    (struct timer_list) mayday_timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0x
    (struct hlist_head [64]) busy_hash = (
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0xFFFFFFC4DEB55880),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0x0),
      ((struct hlist_node *) first = 0xFFFFFFC5956E1E80),
      ((struct hlist_node *) first = 0x0),
//...
    (struct worker *) manager = 0x0,
    (struct mutex) attach_mutex = ((atomic_t) count = ((int) counter = 1 = 0x1), (spinlock_t) wait_l
    (struct list_head) workers = (
      (struct list_head *) next = 0xFFFFFFC4DEB558D0 -> (
        (struct list_head *) next = 0xFFFFFFC5956E1ED0 -> (
          (struct list_head *) next = 0xFFFFFFC5F3942AD0 -> (
            (struct list_head *) next = 0xFFFFFFC582B433D0,

struct worker_pool->workers.next 링크드 리스트와 struct worker_pool->busy_hash를 보니 
아래 주소로 등록된 워커를 살펴봐야 할 것 같습니다. 
(struct list_head) workers = (
  (struct list_head *) next = 0xFFFFFFC4DEB558D0 -> (
    (struct list_head *) next = 0xFFFFFFC5956E1ED0 -> (
v.v %h %t %d container_of(0xFFFFFFC4DEB558D0,struct worker,node)         
v.v %h %t %d container_of(0xFFFFFFC5956E1ED0,struct worker,node)   

배리어 워크는 struct worker->scheduled 링크드 리스트에 등록합니다. 그래서 struct worker->scheduled 멤버를 확인해야 하는데요. 아래 워크는 특이 정보가 없습니다. 왜냐면 struct worker->scheduled에 등록된 링크드 리스트가 비어 있기 때문이죠. 
  (struct worker *) container_of(0xFFFFFFC4DEB558D0,struct worker,node) = 0xFFFFFFC4DEB55880  
    (struct list_head) entry = ((struct list_head *) next = 0x0  
    (struct hlist_node) hentry = ((struct hlist_node *) next = 0x0  
    (struct work_struct *) current_work = 0xFFFFFFC6237C15A8  
    (work_func_t) current_func = 0xFFFFFF97D7172254 = pm_runtime_work,
    (struct pool_workqueue *) current_pwq = 0xFFFFFFC63A329600 
    (bool) desc_valid = FALSE,
    (struct list_head) scheduled = (
      (struct list_head *) next = 0xFFFFFFC4DEB558B0  
        (struct list_head *) next = 0xFFFFFFC4DEB558B0  
        (struct list_head *) prev = 0xFFFFFFC4DEB558B0 
      (struct list_head *) prev = 0xFFFFFFC4DEB558B0  
    (struct task_struct *) task = 0xFFFFFFC51CDC4480  
    (struct worker_pool *) pool = 0xFFFFFFC63A322000  
    (struct list_head) node = ((struct list_head *) next = 0xFFFFFFC5956E1ED0 
    (long unsigned int) last_active = 4302072889 = 0x00000001006C6C39,
    (unsigned int) flags = 0 = 0x0,
    (int) id = 0 = 0x0,
    (char [24]) desc = (0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 =
    (struct workqueue_struct *) rescue_wq = 0x0 

배리어 워크를 struct worker->scheduled 링크드 리스트에 등록하는 코드는 아래 흐름으로 호출되는 insert_wq_barrier 함수를 참고하세요.

flush_work -> start_flush_work -> insert_wq_barrier
static void insert_wq_barrier(struct pool_workqueue *pwq,
      struct wq_barrier *barr,
      struct work_struct *target, struct worker *worker)
{
struct list_head *head;
//..
if (worker)
head = worker->scheduled.next;  //<<--
else {
unsigned long *bits = work_data_bits(target);

head = target->entry.next;
/* there can already be other linked works, inherit and set */
linked = *bits & WORK_STRUCT_LINKED;
__set_bit(WORK_STRUCT_LINKED_BIT, bits);
}

이번에는 다음 워커를 살펴볼 차례입니다.

드디어 처리 안된 배리어 워크를 찾았습니다.
0xFFFFFFC55731E680 주소 태스크 디스크립터인 워커 쓰래드에서 처리 안된 배리어 워크가 있군요.
(struct worker *) container_of(0xFFFFFFC5956E1ED0,struct worker,node) = 0xFFFFFFC5956E1E80 
    (struct list_head) entry = ((struct list_head *) next = 0x0  
    (struct hlist_node) hentry = ((struct hlist_node *) next = 0x0  
    (struct work_struct *) current_work = 0xFFFFFFC623C0AD48 
    (work_func_t) current_func = 0xFFFFFF97D72263A8 = ufshcd_exception_event_handler,
    (struct pool_workqueue *) current_pwq = 0xFFFFFFC63A328800  
    (bool) desc_valid = FALSE,
    (struct list_head) scheduled = (
      (struct list_head *) next = 0xFFFFFFC5F4707B18  
        (struct list_head *) next = 0xFFFFFFC5956E1EB0  
          (struct list_head *) next = 0xFFFFFFC5F4707B18  
          (struct list_head *) prev = 0xFFFFFFC5F4707B18  
        (struct list_head *) prev = 0xFFFFFFC5956E1EB0  
      (struct list_head *) prev = 0xFFFFFFC5F4707B18  
    (struct task_struct *) task = 0xFFFFFFC55731E680  
    (struct worker_pool *) pool = 0xFFFFFFC63A322000  
    (struct list_head) node = ((struct list_head *) next = 0xFFFFFFC5F3942AD0  
    (long unsigned int) last_active = 4302072800 = 0x00000001006C6BE0,
    (unsigned int) flags = 0 = 0x0,
    (int) id = 1 = 0x1,
    (char [24]) desc = (0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 = 0x0, 0 =
    (struct workqueue_struct *) rescue_wq = 0x0  

그럼 다음 struct worker->scheduled 멤버가 가르키는 주소를 참고해서 배리어 워크의 정체를 알아볼 차례입니다...
(struct list_head) scheduled = (
  (struct list_head *) next = 0xFFFFFFC5F4707B18  
    (struct list_head *) next = 0xFFFFFFC5956E1EB0  

배리어 워크의 구조체는 다음과 같고, worker->scheduled.next에는 struct work_struct.entry 주소를 등록했습니다.
struct wq_barrier {
struct work_struct work;
struct completion done;
};

따라서 다음 명령어로 배리어 워크 정보를 확인할 수 있습니다.  
v.v %all container_of(0xFFFFFFC5F4707B18,struct work_struct,entry)

  (struct work_struct *) container_of(0xFFFFFFC5F4707B18,struct work_struct,entry) = 0xFFFFFFC5F4707B10 =
    (atomic_long_t) data = ((long int) counter = -248131712539 = 0xFFFFFFC63A3289E5 = '....:2..'),
    (struct list_head) entry = ((struct list_head *) next = 0xFFFFFFC5956E1EB0 
    (work_func_t) func = 0xFFFFFF97D6AC58F0 = wq_barrier_func -> )

또한 struct work_struct work 멤버는 struct wq_barrier 구조체의 첫 멤버 변수이므로 같은 주소로 다음과 같이 캐스팅 할 수 있습니다.  
v.v %all (struct wq_barrier*)0xFFFFFFC5F4707B10

struct wq_barrier *) (struct wq_barrier*)0xFFFFFFC5F4707B10 = 0xFFFFFFC5F4707B10  
 (struct work_struct) work = ((atomic_long_t) data = ((long int) counter = -248131712539 = 0xFFFFFFC63A3289E5 
 (struct completion) done = (
   (unsigned int) done = 0 = 0x0 = '....',
   (wait_queue_head_t) wait = (
     (spinlock_t) lock = ((struct raw_spinlock) rlock = 
     (struct list_head) task_list = (
       (struct list_head *) next = 0xFFFFFFC5F4707A88 
         (struct list_head *) next = 0xFFFFFFC5F4707B50  
         (struct list_head *) prev = 0xFFFFFFC5F4707B50  
       (struct list_head *) prev = 0xFFFFFFC5F4707A88  
 (struct task_struct *) task = 0xFFFFFFC541030080 

위에서 보이는 주소가 눈에 좀 익숙하지 않나요?

CPU2 워커풀 에서 배리어 워크를 기다리는 콜스택이 있었죠? 다음 디버깅 정보를 보면 웨이트 큐에 등록된 프로세스 주소가 같음을 알 수 있습니다.
(struct completion *) (struct completion *)0xFFFFFFC5F4707B30 = 0xFFFFFFC5F4707B30 -> (
    (unsigned int) done = 0x0,
    (wait_queue_head_t) wait = (
      (spinlock_t) lock = (
        (struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner = 0x1, (u16) next =
      (struct list_head) task_list = (
        (struct list_head *) next = 0xFFFFFFC5F4707A88 -> (
          (struct list_head *) next = 0xFFFFFFC5F4707B50 -> (  //<<--

(where: CPU2에서 배리어 워크가 수행되기를 기다리다가 슬립에 빠진 프로세스 콜스택)
-000|__switch_to(prev = 0xFFFFFFC541030080, next = 0xFFFFFFC60E561180)
-001|context_switch(inline)
-001|__schedule(?)
-002|schedule()
-003|schedule_timeout(timeout = 9223372036854775807)
-004|do_wait_for_common(inline)
-004|__wait_for_common(inline)
-004|wait_for_common(x = 0xFFFFFFC5F4707B30, timeout = 9223372036854775807, ?)
-005|wait_for_completion(x = 0xFFFFFFC5F4707B30)
-006|destroy_work_on_stack(inline)
-006|flush_work(?)
-007|ufshcd_suspend(hba = 0xFFFFFFC623C0AB70, pm_op = UFS_RUNTIME_PM)
-008|ufshcd_runtime_suspend(hba = 0xFFFFFFC623C0AB70)
-009|ufshcd_pltfrm_runtime_suspend(dev = 0xFFFFFFC62CFE6690)
-010|pm_generic_runtime_suspend(dev = 0xFFFFFFC62CFE6690)
//...
-014|pm_runtime_work(work = 0xFFFFFFC62CFE67F8)
 -015|trace_workqueue_execute_end(inline)
-015|process_one_work(worker = 0xFFFFFFC4DE83A780, work = 0xFFFFFFC62CFE67F8)
-016|__read_once_size(inline)
-016|list_empty(inline)
-016|worker_thread(__worker = 0xFFFFFFC4DE83A780)
-017|kthread(_create = 0xFFFFFFC4DA494D00)
-018|ret_from_fork(asm)

이제 다시 이전 디버깅 정보로 돌아가서, 
위에서 찾은 CPU1 워커풀에 등록된 워커 중 현재 처리 중인 배리어 워크가 있는 워커는 다음과 같습니다.
  (struct worker *) container_of(0xFFFFFFC5956E1ED0,struct worker,node) = 0xFFFFFFC5956E1E80  
    (struct list_head) entry = ((struct list_head *) next = 0x0  
    (struct hlist_node) hentry = ((struct hlist_node *) next = 0x0  
    (struct work_struct *) current_work = 0xFFFFFFC623C0AD48 
    (work_func_t) current_func = 0xFFFFFF97D72263A8 = ufshcd_exception_event_handler,
    (struct pool_workqueue *) current_pwq = 0xFFFFFFC63A328800  
    (bool) desc_valid = FALSE,
    (struct list_head) scheduled = (
      (struct list_head *) next = 0xFFFFFFC5F4707B18  
        (struct list_head *) next = 0xFFFFFFC5956E1EB0  
          (struct list_head *) next = 0xFFFFFFC5F4707B18  
          (struct list_head *) prev = 0xFFFFFFC5F4707B18  
        (struct list_head *) prev = 0xFFFFFFC5956E1EB0  
      (struct list_head *) prev = 0xFFFFFFC5F4707B18  
    (struct task_struct *) task = 0xFFFFFFC55731E680 //<<--

그럼 이 프로세스의 이름과 콜스택을 알아볼까요?
"kworker/1:1" 프로세스가 아래 콜스택으로 동작 중이었습니다.
-000|__switch_to(prev = 0xFFFFFFC55731E680, next = 0xFFFFFFC4C4B98080)
-001|context_switch(inline)
-001|__schedule(?)
-002|schedule()
-003|spin_lock_irq(inline)
-003|rpm_resume(dev = 0xFFFFFFC62CFE6690, rpmflags = 4)
-004|__pm_runtime_resume(dev = 0xFFFFFFC62CFE6690, rpmflags = 4)
-005|ufshcd_scsi_block_requests(inline)
-005|ufshcd_exception_event_handler(work = 0xFFFFFFC623C0AD48)
-006|process_one_work(worker = 0xFFFFFFC5956E1E80, work = 0xFFFFFFC623C0AD48)
-007|__read_once_size(inline)
-007|list_empty(inline)
-007|worker_thread(__worker = 0xFFFFFFC5956E1E80)
-008|kthread(_create = 0xFFFFFFC540CA7C80)
-009|ret_from_fork(asm)

그럼 여기까지 분석한 디버깅 정보로 이제 문제 발생 원인을 다음과 같이 좁힐 수 있습니다.
하나 하나 퍼즐을 맞히니 그림이 그려지는 것 같군요.
     CPU1                                    CPU2                                                                            
process_one_work                          process_one_work
ufshcd_exception_event_handler     pm_runtime_work     
__pm_runtime_resume                  pm_generic_runtime_suspend
  flush_work                                  ufshcd_runtime_suspend
                                                    ufshcd_suspend
                                                    flush_work
                                                    ; 배리어 워크가 처리되기를 기다림

1. CPU1에서 flush_work 함수 호출로 배리어 워크를 추가했는데, 뭔가 문제가 생겨서 배리어 워크 처리를 못했습니다.
2. CPU2에서는 배리어 워크가 실행 되기를 기다리고 있습니다.
3. 115초 후 워크큐 락업으로 감지되어 커널 크래시가 발생했습니다.

이제 문제를 유발시킨 코드를 조금 깊히 살펴볼 차례입니다.

[Kernel][Crash] 워크큐(workqueue) 락업(2) at [0321] [Crash]Troubleshooting!!

[Kernel][Crash] 워크큐(workqueue) 락업(1) at [0321] 
http://rousalome.egloos.com/9974388
코어 덤프에서 계속...

자 여기까지 커널 크래시가 발행한 원인까지 확인했습니다.
그럼 CPU2에 워커풀에서 등록된 워커쓰레드를 살펴봐야 겠습니다.

워커쓰레드들은 워커 풀에 등록할 때 struct worker_pool.workers 멤버 링크드 리스트로 등록합니다.
1  (struct worker_pool *) (struct worker_pool*)0xFFFFFFC63A339000 = 0xFFFFFFC63A339000 -> (
2    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner =
3    (int) cpu = 2,
4    (int) node = 0,
5    (int) id = 4,
6    (unsigned int) flags = 1,
// ....
7    (struct mutex) attach_mutex = ((atomic_t) count = ((int) counter = 1), (spinlock_t) wait_lock =
8    (struct list_head) workers = (
9      (struct list_head *) next = 0xFFFFFFC4DE83A7D0 -> (
10        (struct list_head *) next = 0xFFFFFFC50C460CD0 -> (
11          (struct list_head *) next = 0xFFFFFFC4E26A30D0 -> (
12            (struct list_head *) next = 0xFFFFFFC63A339300 -> (
13              (struct list_head *) next = 0xFFFFFFC4DE83A7D0,

자 위에서 7번째부터 13번째까지 링크트 리스트로 연결된 주소가 보이죠?
struct worker_pool.workers.next에 등록된 리스트 주소는 해당 워크의 struct worker.node 와 같습니다.
struct worker {
/* on idle list while idle, on busy hash table while busy */
union {
struct list_head entry; /* L: while idle */
struct hlist_node hentry; /* L: while busy */
};

struct work_struct *current_work; /* L: work being processed */
// ..

struct task_struct *task; /* I: worker task */
struct worker_pool *pool; /* I: the associated pool */
/* L: for rescuers */
struct list_head node; /* A: anchored at pool->workers */

container_of 매크로를 써서 이 자료구조의 상관 관계를 다음과 같이 표시할 수 있습니다.
container_of(struct worker_pool.workers.next, struct worker, node)

참고로, Trace32로 다음과 같이 offsetof와 container_of 명령어를 매크로로 등록해서 쓸 수 있습니다.
sYmbol.NEW.MACRO offsetof(type,member) ((int)(&((type*)0)->member))
sYmbol.NEW.MACRO container_of(ptr,type,member) ((type *)((char *)(ptr)-offsetof(type,member)))

이 명령어를 활용해서 워커풀에 등록된 워커 쓰레드 정보를 확인할께요. 다음 자료 구조 주소를 참고하고요.
9      (struct list_head *) next = 0xFFFFFFC4DE83A7D0 -> (
10        (struct list_head *) next = 0xFFFFFFC50C460CD0 -> (
11          (struct list_head *) next = 0xFFFFFFC4E26A30D0 -> (
12            (struct list_head *) next = 0xFFFFFFC63A339300 -> (
13              (struct list_head *) next = 0xFFFFFFC4DE83A7D0,

9줄에 있는 주소부터 확인할게요. 
v.v %t %h %d %s container_of(0xFFFFFFC4DE83A7D0,struct worker,node)
1  (struct worker *) container_of(0xFFFFFFC4DE83A7D0,struct worker,node) = 0xFFFFF
2    (struct list_head) entry = ((struct list_head *) next = 0x0 = , (struct list_
3    (struct hlist_node) hentry = ((struct hlist_node *) next = 0x0 = , (struct hl
4    (struct work_struct *) current_work = 0xFFFFFFC62CFE67F8 = __efistub__end+0x2
5    (work_func_t) current_func = 0xFFFFFF97D7172254 = pm_runtime_work,
6    (struct pool_workqueue *) current_pwq = 0xFFFFFFC63A340600 = __efistub__end+0
7    (bool) desc_valid = FALSE,
8    (struct list_head) scheduled = ((struct list_head *) next = 0xFFFFFFC4DE83A7B
9    (struct task_struct *) task = 0xFFFFFFC541030080  
10    (struct worker_pool *) pool = 0xFFFFFFC63A339000 
11    (struct list_head) node = ((struct list_head *)  
12    (long unsigned int) last_active = 4302072797 = 0x00000001006C6BDD,
13    (unsigned int) flags = 0 = 0x0,
14    (int) id = 2 = 0x2,
15    (char [24]) desc = "",
16    (struct workqueue_struct *) rescue_wq = 0x0 = )

가장 중요한 정보부터 확인하면 해당 워커 쓰레드의 태스크 디스크립터는 0xFFFFFFC541030080이고, 
가장 마지막에 실행된 시간은 last_active은 4302072797입니다. 이 값은 jiffies 이겠죠. 
9    (struct task_struct *) task = 0xFFFFFFC541030080 
12    (long unsigned int) last_active = 4302072797 = 0x00000001006C6BDD,

태스크 디스크립터는 0xFFFFFFC541030080 정보로 이 프로세스 이름과 콜스택을 알아볼까요? 
  (struct task_struct *) (struct task_struct*)0xFFFFFFC541030080 = 0xFFFFFFC541030080 -> (
    (struct thread_info) thread_info = ((long unsigned int) flags = 0, (mm_segment_t) addr_limit = 1
    (long int) state = 2,
    (void *) stack = 0xFFFFFFC5F4704000,
    (atomic_t) usage = ((int) counter = 2),
//...
    (char [16]) comm = "kworker/2:2",
    (struct nameidata *) nameidata = 0x0,

"kworker/2:2" 프로세스 콜스택은 다음과 같군요. 
-000|__switch_to(prev = 0xFFFFFFC541030080, next = 0xFFFFFFC60E561180)
-001|context_switch(inline)
-001|__schedule(?)
-002|schedule()
-003|schedule_timeout(timeout = 9223372036854775807)
-004|do_wait_for_common(inline)
-004|__wait_for_common(inline)
-004|wait_for_common(x = 0xFFFFFFC5F4707B30, timeout = 9223372036854775807, ?)
-005|wait_for_completion(x = 0xFFFFFFC5F4707B30)
-006|destroy_work_on_stack(inline)
-006|flush_work(?)
-007|ufshcd_suspend(hba = 0xFFFFFFC623C0AB70, pm_op = UFS_RUNTIME_PM)
-008|ufshcd_runtime_suspend(hba = 0xFFFFFFC623C0AB70)
-009|ufshcd_pltfrm_runtime_suspend(dev = 0xFFFFFFC62CFE6690)
-010|pm_generic_runtime_suspend(dev = 0xFFFFFFC62CFE6690)
-011|__rpm_callback(inline)
-011|rpm_callback(cb = 0xFFFFFF97D716DCE0, dev = 0xFFFFFFC62CFE6690)
-012|rpm_suspend(dev = 0xFFFFFFC62CFE6690, rpmflags = 10)
-013|rpm_idle(dev = 0xFFFFFFC62CFE6690, rpmflags = 2)
-014|spin_unlock_irq(inline)
-014|pm_runtime_work(work = 0xFFFFFFC62CFE67F8)
-015|__read_once_size(inline)
-015|static_key_count(inline)
-015|static_key_false(inline)
-015|trace_workqueue_execute_end(inline)
-015|process_one_work(worker = 0xFFFFFFC4DE83A780, work = 0xFFFFFFC62CFE67F8)
-016|__read_once_size(inline)
-016|list_empty(inline)
-016|worker_thread(__worker = 0xFFFFFFC4DE83A780)
-017|kthread(_create = 0xFFFFFFC4DA494D00)
-018|ret_from_fork(asm)

위 콜스택을 보면 process_one_work -> pm_runtime_work 흐름으로 워크가 실행된 후 
디바이스 드라이버에 등록된 suspend 콜백 함수를 호출합니다. 이후 flush_work 함수를 호출합니다. 
1 bool flush_work(struct work_struct *work)
2 {
3 struct wq_barrier barr;
4
5 lock_map_acquire(&work->lockdep_map);
6 lock_map_release(&work->lockdep_map);
7
8 if (start_flush_work(work, &barr)) {
9 wait_for_completion(&barr.done);
10 destroy_work_on_stack(&barr.work);
11 return true;

그런데 세번째 줄 코드에 다음과 같은 변수 선언문을 볼 수 있습니다. 이 코드는 뭘 의미할까요? 
3 struct wq_barrier barr;

이 구조체를 배리어 워크라고 합니다. 현재 워크(struct work_struct)가 워커 쓰레드에서 실행 중일 때,
flush_work 함수를 호출하면 지금 처리 중인 워크 실행을 마무리 할 수 있습니다. 참고하시고요.

이번에는 가장 중요한 코드 중 하나인 8번과 9번 줄 코드입니다. 
8 if (start_flush_work(work, &barr)) {
9 wait_for_completion(&barr.done);
 
해당 코드인 flush_work 함수를 9번줄 코드에서 wait_for_completion(&barr.done) 함수를 호출합니다. 
이 함수를 호출하면 complete(&barr.done) 가 실행될 때 까지 기다립니다.
static void wq_barrier_func(struct work_struct *work)
{
struct wq_barrier *barr = container_of(work, struct wq_barrier, work);
complete(&barr->done);
}

달리 설명드리면 배리어 워크가 워커 쓰레드에서 실행될 때 까지 기다리는 루틴입니다.
워크큐에서 상당히 많이 쓰는 배리어 워크는 다음 시간에 다룰 예정이니 기다려 주세요.

그럼 위 콜스택에서 &barr.done 정체가 뭔지 볼까요? 
-005|wait_for_completion(x = 0xFFFFFFC5F4707B30)

wait_for_completion 함수 선언부를 보면 해당 파라미터 x = 0xFFFFFFC5F4707B30 구조체는 struct completion입니다. 그래서 이 구조체로 디버깅 정보를 확인하면 다음과 같습니다. 
  (struct completion *) (struct completion *)0xFFFFFFC5F4707B30 = 0xFFFFFFC5F4707B30 -> (
    (unsigned int) done = 0x0,
    (wait_queue_head_t) wait = (
      (spinlock_t) lock = (
        (struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner = 0x1, (u16) next =
      (struct list_head) task_list = (
        (struct list_head *) next = 0xFFFFFFC5F4707A88 -> (
          (struct list_head *) next = 0xFFFFFFC5F4707B50 -> (
            (struct list_head *) next = 0xFFFFFFC5F4707A88,

struct completion->wait.task_list에 2개의 링크드 리스트가 보이는군요.
여기에 등록된 링크드 리스트는 struct __wait_queue.task_list에 등록된 링크드 리스트와 같습니다.
그래서 container_of(struct completion->wait.task_list.next,struct __wait_queue, task_list) 조건을 만족합니다.
(where)
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};

typedef struct __wait_queue wait_queue_t;

이 조건에 따라 다음 명령어를 입력하겠습니다.
v.v %all container_of(0xFFFFFFC5F4707A88,struct __wait_queue,task_list)
v.v %all container_of(0xFFFFFFC5F4707B50,struct __wait_queue,task_list)

다음 정보를 태스크 디스크립터가 0xFFFFFFC541030080인 프로세스가 종료되기를 기다리고 있습니다.
v.v %h %t container_of(0xFFFFFFC5F4707A88,struct __wait_queue,task_list) 

  (struct __wait_queue *) container_of(0xFFFFFFC5F4707A88,struct __wait_queue,task_list) = 0xFFFFFFC
    (unsigned int) flags = 1 = 0x1 = '....',
    (void *) private = 0xFFFFFFC541030080  
    (wait_queue_func_t) func = 0xFFFFFF97D6AE571C = default_wake_function -> ,
    (struct list_head) task_list = ((struct list_head *) next = 0xFFFFFFC5F4707B50 

0xFFFFFFC541030080 태스크 디스크립터는 "kworker/2:2" 프로세스 것이고요. 문제는 다음 wait queue에 등록된 인스턴스 입니다. 0xFFFFFFC5F4707B50 주소를 눈여겨 보세요.
  (struct __wait_queue *) container_of(0xFFFFFFC5F4707B50,struct __wait_queue,task_list) = 0xFFFFFFC
    (unsigned int) flags = 65537 = 0x00010001 
    (void *) private = 0xFFFFFFC6FFFFFFFF  
    (wait_queue_func_t) func = 0xFFFFFFFFFFFFFFFF  
    (struct list_head) task_list = ((struct list_head *) next = 0xFFFFFFC5F4707A88 

확인해보니 안타깝게도 쓰레기 값이군요. 
(where)
  (struct completion *) (struct completion *)0xFFFFFFC5F4707B30 = 0xFFFFFFC5F4707B30 -> (
    (unsigned int) done = 0x0,
    (wait_queue_head_t) wait = (
      (spinlock_t) lock = (
        (struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner = 0x1, (u16) next =
      (struct list_head) task_list = (
        (struct list_head *) next = 0xFFFFFFC5F4707A88 -> (
          (struct list_head *) next = 0xFFFFFFC5F4707B50 -> (  //<<--

여기까지 디버깅 정보를 종합하면 8번 줄에 이미 wait_queue에 등록된 프로세스 때문에 0xFFFFFFC5F4707A88에 해당하는 wait queue가 처리되지 못하고 있습니다. 
1  (struct completion *) (struct completion *)0xFFFFFFC5F4707B30 = 0xFFFFFFC5F4707B30 -> (
2    (unsigned int) done = 0x0,
3    (wait_queue_head_t) wait = (
4      (spinlock_t) lock = (
5        (struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner = 0x1, (u16) next =
6      (struct list_head) task_list = (
7        (struct list_head *) next = 0xFFFFFFC5F4707A88 -> (
8          (struct list_head *) next = 0xFFFFFFC5F4707B50 -> (

여기까지 모은 디버깅 정보를 정리하면 다른 CPU 워커 풀에 있는 배리어 워크가 실행되지 않았습니다.
그래서 CPU2의 워커 풀이 Stuck 되어 워크큐 락업으로 감지된 거죠.
-000|__switch_to(prev = 0xFFFFFFC541030080, next = 0xFFFFFFC60E561180)
-001|context_switch(inline)
-001|__schedule(?)
-002|schedule()
-003|schedule_timeout(timeout = 9223372036854775807)
-004|do_wait_for_common(inline)
-004|__wait_for_common(inline)
-004|wait_for_common(x = 0xFFFFFFC5F4707B30, timeout = 9223372036854775807, ?)
-005|wait_for_completion(x = 0xFFFFFFC5F4707B30)
-006|destroy_work_on_stack(inline)
-006|flush_work(?)
-007|ufshcd_suspend(hba = 0xFFFFFFC623C0AB70, pm_op = UFS_RUNTIME_PM)
-008|ufshcd_runtime_suspend(hba = 0xFFFFFFC623C0AB70)
-009|ufshcd_pltfrm_runtime_suspend(dev = 0xFFFFFFC62CFE6690)
-010|pm_generic_runtime_suspend(dev = 0xFFFFFFC62CFE6690)
-011|__rpm_callback(inline)
-011|rpm_callback(cb = 0xFFFFFF97D716DCE0, dev = 0xFFFFFFC62CFE6690)
-012|rpm_suspend(dev = 0xFFFFFFC62CFE6690, rpmflags = 10)
-013|rpm_idle(dev = 0xFFFFFFC62CFE6690, rpmflags = 2)
-014|spin_unlock_irq(inline)
-014|pm_runtime_work(work = 0xFFFFFFC62CFE67F8)
-015|__read_once_size(inline)
-015|static_key_count(inline)
-015|static_key_false(inline)
-015|trace_workqueue_execute_end(inline)
-015|process_one_work(worker = 0xFFFFFFC4DE83A780, work = 0xFFFFFFC62CFE67F8)
-016|__read_once_size(inline)
-016|list_empty(inline)
-016|worker_thread(__worker = 0xFFFFFFC4DE83A780)
-017|kthread(_create = 0xFFFFFFC4DA494D00)
-018|ret_from_fork(asm)

이번에는 다른 워커 풀에서 Pending된 배리어 워크를 찾아야 합니다.


[Kernel][Crash][0320] 워크큐(workqueue) 락업(1) [Crash]Troubleshooting!!

커널 크래시가 발생했습니다. 우선 커널 크래시 발생 직전 커널 로그부터 천천히 볼까요?
1 [73669.590105 / 12-01 13:26:44.379][0] BUG: workqueue lockup - pool cpus=2 node=0 flags=0x1 nice=0 stuck for 115s!
2 [73669.592865 / 12-01 13:26:44.379][2] kernel BUG at home001/austindh.kim/src/kernel/workqueue.c:5381!
3 [73669.592905 / 12-01 13:26:44.379][2] Internal error: Oops - BUG: 0 [#1] PREEMPT SMP
4 [73669.592922 / 12-01 13:26:44.379][0] Modules linked in: texfat(PO) snd_soc_sdm845 
5 [73669.593061 / 12-01 13:26:44.379][2] CPU: 2 PID: 1416 Comm: lowi-server Tainted: P        W  O    4.9.60+ #1
6 [73669.593080 / 12-01 13:26:44.379][2] Hardware name: Qualcomm Technologies, Inc. SDM845 V2 MTP (DT)
7 [73669.593094 / 12-01 13:26:44.379][2] task: ffffffc5e9db5580 task.stack: ffffffc5e9d60000
8 [73669.593132 / 12-01 13:26:44.379][2] PC is at wq_watchdog_timer_fn+0x188/0x18c
9 [73669.593145 / 12-01 13:26:44.379][2] LR is at wq_watchdog_timer_fn+0x188/0x18c

첫 번째 줄 로그는 매우 중요한 정보를 담고 있는데요. workerpool(CPU2, node=0)에 등록된 워커 쓰레드가 115초 동안 실행되지 않았다고 알려줍니다.
workpool은 struct *worker_pool이란 자료 구조이며 각 CPU마다 node 2개를 갖고 있습니다. 
BUG: workqueue lockup - pool cpus=2 node=0 flags=0x1 nice=0 stuck for 115s!

이번에는 2번째와 8번째 줄 로그입니다.
2 [73669.592865 / 12-01 13:26:44.379][2] kernel BUG at home001/src/kernel/workqueue.c:5381!
8 [73669.593132 / 12-01 13:26:44.379][2] PC is at wq_watchdog_timer_fn+0x188/0x18c

이제 BUG가 실행된 코드를 분석해야 할 시간입니다. 왜냐면 정확히 커널 크래시가 발생한 원인을 알기 위해서죠.

그럼 wq_watchdog_timer_fn 함수를 분석해볼까요? 
분석하기 전에 커널 크래시 발생 전 아주 유익한 힌트를 이미 커널 로그가 말해 줬습니다. 
CPU2에 해당 워커풀에서 타임 아웃이 발생했다는 점입니다. 이 점을 유념하면서 함수 리뷰를 해야 겠습니다.
1 static void wq_watchdog_timer_fn(unsigned long data)
2 {
3 unsigned long thresh = READ_ONCE(wq_watchdog_thresh) * HZ;
4 bool lockup_detected = false;
5 struct worker_pool *pool;
6 int pi;
7
8 if (!thresh)
9 return;
10
11 rcu_read_lock();
12
13 for_each_pool(pool, pi) {
14 unsigned long pool_ts, touched, ts;
15
16 if (list_empty(&pool->worklist))
17 continue;
18
19 /* get the latest of pool and touched timestamps */
20 pool_ts = READ_ONCE(pool->watchdog_ts);
21 touched = READ_ONCE(wq_watchdog_touched);
22
23 if (time_after(pool_ts, touched))
24 ts = pool_ts;
25 else
26 ts = touched;
27
28 if (pool->cpu >= 0) {
29 unsigned long cpu_touched =
30 READ_ONCE(per_cpu(wq_watchdog_touched_cpu,
31   pool->cpu));
32 if (time_after(cpu_touched, ts))
33 ts = cpu_touched;
34 }
35
36 /* did we stall? */
37 if (time_after(jiffies, ts + thresh)) {
38 lockup_detected = true;
39 pr_emerg("BUG: workqueue lockup - pool");
40 pr_cont_pool_info(pool);
41 pr_cont(" stuck for %us!\n",
42 jiffies_to_msecs(jiffies - pool_ts) / 1000);
43 }
44 }
45
46 rcu_read_unlock();
47
48 if (lockup_detected) {
49 show_workqueue_state();
50 BUG();
51 }

13번째와 14번째 줄 코드부터 봐야 겠습니다. for_each_pool는 각 워커풀 갯수만큼 접근하는 for loop입니다.
13 for_each_pool(pool, pi) {
14 unsigned long pool_ts, touched, ts;

커널 로그에서 CPU2에 워커풀에 문제가 있다고 했으니 CPU2에 해당하는 워커풀을 봐야 겠습니다.
그럼 코어 덤프에서 CPU2에 해당하는 워커풀은 어떻게 가져올까요?

for_each_pool 매크로를 보면 worker_pool_idr이란 IDR변수에서 워커풀을 가져옴을 알 수 있습니다.
#define for_each_pool(pool, pi) \
idr_for_each_entry(&worker_pool_idr, pool, pi) \
if (({ assert_rcu_or_pool_mutex(); false; })) { } \
else

worker_pool_idr 전역 변수에 접근하면 (*(worker_pool_idr.top)).ary[4]에 CPU2에 대한 워커풀 인스턴스가 있네요.
  (static struct idr) worker_pool_idr = (
    (struct idr_layer *) hint = 0xFFFFFFC6307B9308,
    (struct idr_layer *) top = 0xFFFFFFC6307B9308 -> (
      (int) prefix = 0x0,
      (int) layer = 0x0,
      (struct idr_layer * [256]) ary = (
        [0x0] = 0xFFFFFFC63A30B000,  //<<--CPU0, node0
        [0x1] = 0xFFFFFFC63A30B400,  //<<--CPU0, node1 
        [0x2] = 0xFFFFFFC63A322000,  //<<--CPU1, node0
        [0x3] = 0xFFFFFFC63A322400,  //<<--CPU1, node1 
        [0x4] = 0xFFFFFFC63A339000,  //<<--CPU2, node0
        [0x5] = 0xFFFFFFC63A339400,  //<<--CPU2, node1 
        [0x6] = 0xFFFFFFC63A350000,
        [0x7] = 0xFFFFFFC63A350400,
        [0x8] = 0xFFFFFFC63A367000,
        [0x9] = 0xFFFFFFC63A367400,
        [0x0A] = 0xFFFFFFC63A37E000,
        [0x0B] = 0xFFFFFFC63A37E400,
        [0x0C] = 0xFFFFFFC63A395000,
 
0xFFFFFFC63A339000 주소를 (struct worker_pool *) 구조체로 캐스팅하니 다음과 같은 멤버 변수를 볼 수 있습니다.
1  (struct worker_pool *) (struct worker_pool*)0xFFFFFFC63A339000 = 0xFFFFFFC63A339000 -> (
2    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner =
3    (int) cpu = 2 = 0x2,
4    (int) node = 0 = 0x0,
5    (int) id = 4 = 0x4,
6    (unsigned int) flags = 1 = 0x1,
7    (long unsigned int) watchdog_ts = 4302292746 = 0x00000001006FC70A,  //<<--
8    (struct list_head) worklist = (
9      (struct list_head *) next = 0xFFFFFFC62B7DB6E8,
10      (struct list_head *) prev = 0xFFFFFF97D9308360),
11    (int) nr_workers = 3 = 0x3,
12    (int) nr_idle = 0 = 0x0,
13    (struct list_head) idle_list = ((struct list_head *) next = 0xFFFFFFC63A339048, (struct list_hea
14    (struct timer_list) idle_timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0xDE
15    (struct timer_list) mayday_timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0x
16    (struct hlist_head [64]) busy_hash = ([0] = ((struct hlist_node *) first = 0x0), [1] = ((struct
17    (struct worker *) manager = 0xFFFFFFC50C460C80,
18    (struct mutex) attach_mutex = ((atomic_t) count = ((int) counter = 1 = 0x1), (spinlock_t) wait_l
19    (struct list_head) workers = (
20      (struct list_head *) next = 0xFFFFFFC4DE83A7D0,
21      (struct list_head *) prev = 0xFFFFFFC4E26A30D0),
22    (struct completion *) detach_completion = 0x0,

위에서 3번째, 4번째 그리고 6번째 멤버를 보면 cpu2에 대한 워커풀에 node가 0이고 flags가 1이란 점을 알 수 있습니다. 
3    (int) cpu = 2 = 0x2,
4    (int) node = 0 = 0x0,
6    (unsigned int) flags = 1 = 0x1,

커널 크래시 발생 전 로그와 일치하죠.
BUG: workqueue lockup - pool cpus=2 node=0 flags=0x1 nice=0 stuck for 115s!

다음은 워크큐 락업을 감지하는 37번째부터 42번째 코드를 봐야 할 차례입니다.
이 부분이 커널 크래시가 발생한 이유를 말해주는 핵심 코드라 봐야 합니다.
37 if (time_after(jiffies, ts + thresh)) {
38 lockup_detected = true;
39 pr_emerg("BUG: workqueue lockup - pool");
40 pr_cont_pool_info(pool);
41 pr_cont(" stuck for %us!\n",
42 jiffies_to_msecs(jiffies - pool_ts) / 1000);
43 }

그런데 코드에서 시간을 처리하는 코드가 눈이 띄는데요. 이를 분석하려면 우선 이 시스템이 구동되고 있는 조건에 대해서 잠시 살펴봐야 하는데요.
1. HZ
HZ은 CONFIG_HZ이 100이므로 100입니다. HZ이 100이란 의미는 1초에 jiffies가 100번 씩 증분된다고 봐야겠죠.

2. jiffies
이 시스템은 ARM64 아키텍처입니다. 그래서 jiffies 값은 전처리 파일로 보면 jiffies_64로 대응합니다..

이제 커널 코드와 디버깅 정보를 함께 볼 시간입니다..
37 if (time_after(jiffies, ts + thresh)) {

time_after이란 매크로가 등장했는데요. 함수 이름만 봐도 주눅이 드는 것 같군요.
리눅스 커널의 아주 난해한 time을 처리하는 함수로 보이지만, 아주 간단한 매크로입니다.

time_after은 다음 조건을 만족하면 true를 리턴하는 함수입니다.
jiffies > (ts + thresh)
 
그럼 jiffies 값을 알아 봐야 겠습니다. jiffies_64 변수를 확인하니 4302304310이군요. 
jiffies_64 = 4302304310

그럼 (ts + thresh)이 결괏값이 jiffies 4302304310 값 보다 작았군요. 그래야 if문 내 코드가 실행되어
다음 커널 로그를 출력할 수 있잖아요.
BUG: workqueue lockup - pool cpus=2 node=0 flags=0x1 nice=0 stuck for 115s!
 
그럼 ts값을 알아봐야 겠습니다. 다음 코드를 잠깐 보면 ts는 pool->watchdog_ts 멤버 변수 값이란 걸 알 수 있습니다. 
20 pool_ts = READ_ONCE(pool->watchdog_ts);
21 touched = READ_ONCE(wq_watchdog_touched);
22
23 if (time_after(pool_ts, touched))
24 ts = pool_ts;
25 else
26 ts = touched;

watchdog_ts는 struct worker_pool 구조체의 멤버 중 하나입니다. 
그럼 잠깐 이 변수의 의미를 알아볼게요.

디바이스 드라이버에서 워크(strut work_struct)을 큐잉할 때와 워커 쓰레드가 실행되어
워크가 실행될 때 struct worker_pool.watchdog_ts 멤버에 그 당시 시간 정보인 jiffies값을 저장합니다. 워커풀이 가장 마지막에 실행된 시간 정보를 담고 있다고 봐야죠. 
해당 함수는 __queue_work와 worker_thread이니 시간되면 코드를 한번 열어보세요.

그럼 코어 덤프에서 해당 멤버 변수를 확인하니 4302292746입니다. 
  (struct worker_pool *) (struct worker_pool*)0xFFFFFFC63A339000 = 0xFFFFFFC63A339000 -> (
    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u16) owner =
    (int) cpu = 2,
    (int) node = 0,
    (int) id = 4,
    (unsigned int) flags = 1,
    (long unsigned int) watchdog_ts = 4302292746,
    (struct list_head) worklist = ((struct list_head *) next = 0xFFFFFFC62B7DB6E8, (struct list_head

여기까지 다음 37번째 줄 코드에서 jiffies와 ts 값을 확인했으니, thresh 변수를 알아볼 차례입니다.
37 if (time_after(jiffies, ts + thresh)) {
38 lockup_detected = true; 

thresh 변수는 지역 변수로 선언됐는데, 다음 3번째 줄 코드와 같이 선언됐습니다.
3 unsigned long thresh = READ_ONCE(wq_watchdog_thresh) * HZ;
4 bool lockup_detected = false;

확인해보니 코어 덤프에서는 wq_watchdog_thresh이 60입니다. 
wq_watchdog_thresh = 60
 
그럼 다음 계산식으로 thresh는 6000이라는 걸 알 수 있습니다.
6000 = 60 * 100 = wq_watchdog_thresh * HZ

여기서 wq_watchdog_thresh * HZ 코드가 뭘 의미하는지 생각해볼까요?
HZ이 100이면 1초에 jiffies가 100번 증분된다고 알고 있습니다. 그런데 여기에 60을 곱하고 있어요.

thresh는 1분 동안의 jiffies 값이라고 할 수 있습니다. 
허벌라게 어렵죠? 이렇게 커널 크래시를 제대로 분석하려면 리눅스 커널 전반에 대해 깊히 알아야 합니다.      

이제 이제까지 진행했던 커널 함수 코드 리뷰로 돌아갈께요.
37 if (time_after(jiffies, ts + thresh)) {
38 lockup_detected = true;
39 pr_emerg("BUG: workqueue lockup - pool");
40 pr_cont_pool_info(pool);
41 pr_cont(" stuck for %us!\n",
42 jiffies_to_msecs(jiffies - pool_ts) / 1000);
43 }

그럼 그 동안 모은 디버깅 정보로 jiffies > (ts + thresh) 조건을 만족하는군요. 그래서 if문이 실행된 것 겠죠.
4302304310(jiffies) > 4302298746(ts + thresh = 4302292746 + 6000)

(where: 디버깅 정보)
jiffies = 4302304310
ts = 4302292746 = struct worker_pool.watchdog_ts
thresh = 6000 = 60 * 100 = wq_watchdog_thresh * HZ

다음 코드를 볼게요. "stuck for %us!"란 에러 메시지를 커널 로그로 출력합니다.
41 pr_cont(" stuck for %us!\n",
42 jiffies_to_msecs(jiffies - pool_ts) / 1000);
 
그 동안 반복했던 아래 커널 로그에 대응합니다. 115s 동안 stuck됐다는 군요.
workqueue lockup - pool cpus=2 node=0 flags=0x1 nice=0 stuck for 115s!   
 
그럼 115초가 어떻게 계산됐는지 알아볼게요. 115초는 아래 jiffies_to_msecs 함수를 실행해서 얻은 결괏값인데요.
41 pr_cont(" stuck for %us!\n",
42 jiffies_to_msecs(jiffies - pool_ts) / 1000);

jiffies_to_msecs 함수는 jiffies값을 밀리초로 변환합니다. 여기서 jiffies, pool_ts값은 다음과 같죠.
jiffies = 4302304310
pool_ts = 4302292746

이 값을 밀리초 단위로 계산하면 어떤 값일까요? 결과는 다음과 같습니다.
jiffies(밀리초) = 73370140
pool_ts(밀리초) = 73254500

밀리초로 변환한 값을 (jiffies - pool_ts) 식으로 계산하니 정말 115초가 나오는군요.
115640 = 73370140 - 73254500 = jiffies(밀리초) - pool_ts(밀리초)

그럼 여기까지 모든 디버깅 정보를 모아 이 문제가 왜 발생했는지 정리해볼게요.
아래는 워크(struct work_struct)을 처리하는 과정입니다.

1. 워크(&host->work, struct work_struct 타입)을 해당 워크큐에 큐잉합니다.
2. schedule_work -> queue_work -> queue_work_on -> __queue_work 순서 함수 호출
3. schedule_work() 함수가 실행된 CPU번호에 대한 워커풀을 찾아서, pool->watchdog_ts에 jiffies을 업데이트
   즉, __queue_work 함수를 통해 해당 워커풀에 접근했을 때 시간 정보를 저장
   이후 pool->worklist에 &host->work을 등록함 
4. 워커풀에 등록된 워커 쓰레드를 깨움
5. 워커 쓰레드가 깨어나 실행할 때 worker_thread 함수에서 pool->watchdog_ts에 jiffies 즉 시간 정보 업데이트
6. 등록된 워크(pool->worklist)를 실행 
7. wq_watchdog_timer_fn 함수에서 워크 큐 락업을 감지하여 커널 크래시를 유발합니다.
  즉, 위 schedule_work -> queue_work -> queue_work_on 함수 흐름으로 워크를 등록했는데, 
  해당 워커 쓰레드가 1분 동안 실행되지 않았는지 점검한다는 의미죠. 즉 4/5/6 번 동작에 문제가 있어 제대로 실행안된 것입니다.
  자 여기서, thresh가 1분을 의미한다고 했죠? 60초 동안 워커풀이 실행됐는지 점검하는 thresh hold라고 봐야 합니다.

sdmmc_request 코드를 예를 들까요?  워크를 큐잉할 때 schedule_work 란 함수를 호출합니다. schedule() 함수에 전달하는 &host->work 변수의 구조체는 struct work_struct임을 기억하세요. 
static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct realtek_pci_sdmmc *host = mmc_priv(mmc);
struct mmc_data *data = mrq->data;

mutex_lock(&host->host_mutex);
host->mrq = mrq;
mutex_unlock(&host->host_mutex);

if (sd_rw_cmd(mrq->cmd) || sdio_extblock_cmd(mrq->cmd, data))
host->using_cookie = sd_pre_dma_transfer(host, data, false);

schedule_work(&host->work);
}

여기까지 1차로 워크큐 락업으로 커널 크래시가 발생한 원인을 알아 봤습니다. 그럼 다음 시간에는 조금 더 구체적으로 왜 커널 크래시가 발생했는지 분석을 해야 겠죠? 다음 세미나 시간을 기대해주세요.


[Linux][Kernel] 슬랩 메모리: kfree 소개 [Linux][Kernel] MM

이번에는 kfree에 대해서 알아봅니다. 이번에도 패치 코드를 하나 작성할게요.


이번에는 kmalloc 함수로 메모리를 할당 kfree 함수를 호출해서 메모리를 해제합니다. 그리고 바로 커널 크래시를 유발하죠.

 

커널 크래시가 발생하면 코어 덤프(vmcore) 생깁니다. 파일을 Trace32 올려서 메모리 해제 어떤 동작을 하는 상세히 알아 보려고 하거든요.

 

우선 할당된 메모리 주소는 0xE7AE1300입니다.

(static u32 *) austin_debug_data = 0xE7AE1300


0xE7AE1300 메모리 공간은 어떤 값들로 구성됐는지 알아볼까요?

_____address|_data________|value_____________|symbol

NSD:E7AE1300|6B 6B 6B 6B  0x6B6B6B6B

NSD:E7AE1304|6B 6B 6B 6B  0x6B6B6B6B

NSD:E7AE1308|6B 6B 6B 6B  0x6B6B6B6B

NSD:E7AE130C|6B 6B 6B 6B  0x6B6B6B6B

NSD:E7AE1310|6B 6B 6B 6B  0x6B6B6B6B

NSD:E7AE1314|6B 6B 6B 6B  0x6B6B6B6B

NSD:E7AE1318|6B 6B 6B 6B  0x6B6B6B6B

//...

NSD:E7AE16F8|6B 6B 6B 6B  0x6B6B6B6B

NSD:E7AE16FC|6B 6B 6B A5  0xA56B6B6B

NSD:E7AE1700|BB BB BB BB  0xBBBBBBBB

NSD:E7AE1704|40 0E AE E7  0xE7AE0E40

NSD:E7AE1708|AC 68 43 C0  0xC04368AC       \\kernel_bsp_debug_stat_set+0x134

NSD:E7AE170C|F0 EB 14 C0  0xC014EBF0       \\kmem_cache_alloc_trace+0xB8

NSD:E7AE1710|AC 68 43 C0  0xC04368AC         \\kernel_bsp_debug_stat_set+0x134

NSD:E7AE1714|10 EF 17 C0  0xC017EF10         \\vmlinux\libfs\simple_attr_write+0xD4

NSD:E7AE1718|60 A7 15 C0  0xC015A760         \\vmlinux\fs/read_write\vfs_write+0xC8

NSD:E7AE171C|EC AB 15 C0  0xC015ABEC         \\vmlinux\fs/read_write\sys_write+0x4C

NSD:E7AE1720|00 F3 00 C0  0xC000F300         \\vmlinux\Global\ret_fast_syscall

NSD:E7AE1724|00 00 00 00  0x0

NSD:E7AE1728|00 00 00 00  0x0

NSD:E7AE172C|00 00 00 00  0x0

NSD:E7AE1730|00 00 00 00  0x0

NSD:E7AE1734|00 00 00 00  0x0

NSD:E7AE1738|00 00 00 00  0x0

NSD:E7AE173C|00 00 00 00  0x0

NSD:E7AE1740|00 00 00 00  0x0

NSD:E7AE1744|00 00 00 00  0x0

NSD:E7AE1748|00 00 00 00  0x0

NSD:E7AE174C|07 00 00 00  0x7              

NSD:E7AE1750|85 16 00 00  0x1685

NSD:E7AE1754|96 20 00 00  0x2096

NSD:E7AE1758|98 F3 3C C0  0xC03CF398         \\perform_memory_free+0x50

NSD:E7AE175C|40 FA 14 C0  0xC014FA40         \\vmlinux\slub\kfree+0x238

NSD:E7AE1760|98 F3 3C C0  0xC03CF398         \\perform_memory_free+0x50

NSD:E7AE1764|2C C2 F5 C0  0xC0F5C22C         \watchdog_kthread+0xF4

NSD:E7AE1768|48 AA 04 C0  0xC004AA48         \\kthread+0xF8

NSD:E7AE176C|D0 F3 00 C0  0xC000F3D0         \\ ret_from_fork+0x14

NSD:E7AE1770|00 00 00 00  0x0

NSD:E7AE1774|00 00 00 00  0x0

NSD:E7AE1778|00 00 00 00  0x0

NSD:E7AE177C|00 00 00 00  0x0

NSD:E7AE1780|00 00 00 00  0x0

NSD:E7AE1784|00 00 00 00  0x0

NSD:E7AE1788|00 00 00 00  0x0

NSD:E7AE178C|00 00 00 00  0x0

NSD:E7AE1790|00 00 00 00  0x0

NSD:E7AE1794|00 00 00 00  0x0

NSD:E7AE1798|00 00 00 00  0x0

NSD:E7AE179C|07 00 00 00  0x7            

NSD:E7AE17A0|42 00 00 00  0x42            

NSD:E7AE17A4|36 28 00 00  0x2836

NSD:E7AE17A8|5A 5A 5A 5A  0x5A5A5A5A

NSD:E7AE17AC|5A 5A 5A 5A  0x5A5A5A5A

NSD:E7AE17B0|5A 5A 5A 5A  0x5A5A5A5A

NSD:E7AE17B4|5A 5A 5A 5A  0x5A5A5A5A

NSD:E7AE17B8|5A 5A 5A 5A  0x5A5A5A5A

NSD:E7AE17BC|5A 5A 5A 5A  0x5A5A5A5A


그런데 한 가지 희한한 점이 있어요. 메모리 공간에0x6B란 값이 꽉 채워져 있잖아요.

kmalloc으로 할당한 메모리를 해제하면 해당 메모리 공간을 0x6b로 채워 넣습니다.

 

이 헥사 값은 POISON_FREE 매크로로 정의됐는데 이미 해제한 메모리를 뜻합니다.

[include/linux/poison.h]

#define POISON_FREE 0x6b


그럼 이런 헥사 값을 왜 채워 넣을까요조금 후 다른 프로세스에서 1024 크기 만큼 메모리 할당을 요청하면 이 메모리 공간을 다음에 할당합니다.

그 때 이 메모리 공간에는 당연히 0x6b 값으로 채워져 있겠죠. 그럼 만약0x6b가 아니라 다른 값이 있으면 어떻게 해석해야 할까요?

 

리눅스 커널은 이 때 메모리가 오염됐다고 판단하고 커널 크래시를 유발합니다. 메모리 오염이라. 좀 애매한 소리죠.

좀 더 구체적으로 설명하면 메모리를 할당하지 않고 메모리를 접근하는 경우를 예를 들 수 있습니다..

그럼 0xE7AE1300 메모리 공간부터 0xE7AE16FC 까지 0x6b로 덮혀 있는 구간까지 살펴볼게요.
_____address|_data________|value_____________|symbol
NSD:E7AE1300| 6B 6B 6B 6B  0x6B6B6B6B
NSD:E7AE1304| 6B 6B 6B 6B  0x6B6B6B6B
NSD:E7AE1308| 6B 6B 6B 6B  0x6B6B6B6B
NSD:E7AE130C| 6B 6B 6B 6B  0x6B6B6B6B
NSD:E7AE1310| 6B 6B 6B 6B  0x6B6B6B6B
NSD:E7AE1314| 6B 6B 6B 6B  0x6B6B6B6B
NSD:E7AE1318| 6B 6B 6B 6B  0x6B6B6B6B
//...
NSD:E7AE16F8| 6B 6B 6B 6B  0x6B6B6B6B
NSD:E7AE16FC| 6B 6B 6B A5  0xA56B6B6B

정확히 메모리를 할당한 사이즈 1024 = 0x400(0xE7AE1700 - 0xE7AE1300) 만큼 0x6B를 채우고 있습니다.
그런데 E7AE16FC 메모리에 0xA56B6B6B 와 같이 첫번째 바이트에 0xA5가 나타났습니다.

이는 아래 코드 같이 메모리 공간에 채운 포이즌 값의 끝을 알려주는 포이즌 값입니다.
[include/linux/poison.h]
#define POISON_END 0xa5 /* end-byte of poisoning */

// To be continued...

[Linux][Kernel] 슬랩 메모리: kmalloc 소개 [Linux][Kernel] MM

리눅스 커널에서 메모리란 소리만 나와도 공포에 질리는 분들이 있습니다. 예전에 저도 그랬죠. 스타크래프트에서 테란 조이기를 당할 때와 비슷한 느낌이었죠. 정말 갑갑했죠. 메모리풀, vmalloc, 슬랩 메모리, 하이 메모리 등등 용어를 듣다 보면 뇌정지가 올 때가 있습니다. 그런데 나중에 깨닫게 된 사실은 리눅스 커널에서 메모리를 제대로 이해하는 개발자가 매우 적다는 것이었습니다. 그래서 조금 위안을 가졌죠.

리눅스 커널 메모리 시스템을 이해하려고 리눅스 커널 코드를 열어 보는 분들이 있습니다. 이것은 정말 옵져버 없이 럴커 밭에 달려드는 질럿과 같습니다. 절대 이렇게 분석하면 포기할 수 밖에 없습니다. 그 이유는 리눅스 커널 메모리 관련 코드가 정말 어렵거든요. 그럼 어떻게 리눅스 커널 메모리 시스템을 분석해야 할까요? 제 생각에 직접 눈에 보이는 현상부터 부딛쳐 보는게 좋다고 생각합니다. 무슨 개소리냐구요? 메모리를 할당하면 실제 메모리 덤프를 떠서 실제 어떤 값인지 확인하는게 낫다는 소리죠.

그럼 리눅스 커널에서 메모리 할당하면 kmalloc이 떠 오르죠. 우선 이 함수에 대해서 좀 알아볼게요.

리눅스 커널 많은 코드에서 kmalloc 이란 함수를 써서 메모리를 할당합니다. 그럼 이 함수를 쓰면 실제 커널은 어떻게 메모리를 관리할까요? 이를 알기 위해 아주 간단한 패치 코드를 우선 만들어서 확인해볼게요.
1 u32 *austin_debug_data;
3 static int kernel_bsp_debug_stat_set(void *data, u64 val)
4 {
5      austin_debug_data = kmalloc(1024, GFP_KERNEL);
6      memset(austin_debug_data, 0x78, 1024);
8      BUG();
9 }

다섯 번 째 줄 코드를 보면 1024와 GFP_KERNEL 옵션으로 kmalloc을 호출합니다. 1024는 쓰고 싶은 메모리 크기고 GFP_KERNEL은 현재 커널 공간에서 메모리를 할당한다는 의미입니다. 인터럽트 컨택스트에서 kmalloc으로 메모리를 할당 하려면 GFP_ATOMIC을 쓰는 게 좋다는 것을 잊지 말아주세요.

여섯 번 째 줄 코드는 할당 받은 메모리에 0x78값을 메모리 복사합니다. memset이란 API는 C언어에서도 많이 봤던 라이브러리 함수죠.

8번째 줄에서는 강제로 커널 크래시를 유발합니다. 커널 공간에서 어떻게 메모리를 할당하는지 보고 싶어서요.

위 코드를 실행시킨 다음 커널 크래시를 유발 시켰습니다. 코어 덤프를 받기 위해서죠.
Trace32로 코어 덤프를 로딩했더니 austin_debug_data 이란 포인터 변수는 0xC4EBDF00 메모리 공간을 가르키고 있습니다. 다른 말로 kmalloc으로 할당한 메모리 주소가 0xC4EBDF00 이란 말이죠.
(static u32 *) austin_debug_data = 0xC4EBDF00

자 그럼 이제 0xC4EBDF00 메모리 덤프를 확인해볼까요? 참고로 아래 메모리 덤프는 모두 16진수 기준으로 표현합니다.
_____address|_data________|value_____________|symbol
1 NSD:C4EBDF00|_78_78_78_78__0x78787878
2 NSD:C4EBDF04| 78 78 78 78  0x78787878
3 NSD:C4EBDF08| 78 78 78 78  0x78787878
4 NSD:C4EBDF0C| 78 78 78 78  0x78787878
5 NSD:C4EBDF10| 78 78 78 78  0x78787878
6 NSD:C4EBDF14| 78 78 78 78  0x78787878
7 NSD:C4EBDF18| 78 78 78 78  0x78787878
8 NSD:C4EBDF1C| 78 78 78 78  0x78787878
9 NSD:C4EBDF20| 78 78 78 78  0x78787878
10 NSD:C4EBDF24| 78 78 78 78  0x78787878
11 NSD:C4EBDF28| 78 78 78 78  0x78787878
12 NSD:C4EBDF2C| 78 78 78 78  0x78787878
//..
13 NSD:C4EBE2F8| 78 78 78 78  0x78787878
14 NSD:C4EBE2FC| 78 78 78 78  0x78787878
15 NSD:C4EBE300| CC CC CC CC  0xCCCCCCCC
16 NSD:C4EBE304| C0 D0 EB C4  0xC4EBD0C0
17 NSD:C4EBE308| 74 68 43 C0  0xC0436874         \\vmlinux\kernel_bsp_debug_stat_set+0xFC
18 NSD:C4EBE30C| F0 EB 14 C0  0xC014EBF0         \\vmlinux\slub\kmem_cache_alloc_trace+0xB8
19 NSD:C4EBE310| 74 68 43 C0  0xC0436874         \\vmlinux\kernel_bsp_debug_stat_set+0xFC
20 NSD:C4EBE314| 10 EF 17 C0  0xC017EF10         \\vmlinux\libfs\simple_attr_write+0xD4
21 NSD:C4EBE318| 60 A7 15 C0  0xC015A760         \\vmlinux\fs/read_write\vfs_write+0xC8
22 NSD:C4EBE31C| EC AB 15 C0  0xC015ABEC         \\vmlinux\fs/read_write\sys_write+0x4C
23 NSD:C4EBE320| 00 F3 00 C0  0xC000F300         \\vmlinux\Global\ret_fast_syscall
24 NSD:C4EBE324| 00 00 00 00  0x0
25 NSD:C4EBE328| 00 00 00 00  0x0
26 NSD:C4EBE32C| 00 00 00 00  0x0
27 NSD:C4EBE330| 00 00 00 00  0x0
28 NSD:C4EBE334| 00 00 00 00  0x0
29 NSD:C4EBE338| 00 00 00 00  0x0
30 NSD:C4EBE33C| 00 00 00 00  0x0
31 NSD:C4EBE340| 00 00 00 00  0x0
32 NSD:C4EBE344| 00 00 00 00  0x0
33 NSD:C4EBE348| 00 00 00 00  0x0
34 NSD:C4EBE34C| 05 00 00 00  0x5                
35 NSD:C4EBE350| 54 18 00 00  0x1854
36 NSD:C4EBE354| DE 7F 00 00  0x7FDE
37 NSD:C4EBE358| 38 3D 08 C0  0xC0083D38         \\vmlinux\printk\do_syslog\__out+0x1C4
38 NSD:C4EBE35C| 40 FA 14 C0  0xC014FA40         \\vmlinux\slub\kfree+0x238
39 NSD:C4EBE360| 38 3D 08 C0  0xC0083D38         \\vmlinux\printk\do_syslog\__out+0x1C4
40 NSD:C4EBE364| 30 3F 08 C0  0xC0083F30         \\vmlinux\printk\sys_syslog+0x1C
41 NSD:C4EBE368| 00 F3 00 C0  0xC000F300         \\vmlinux\Global\ret_fast_syscall
42 NSD:C4EBE36C| 00 00 00 00  0x0
43 NSD:C4EBE370| 00 00 00 00  0x0
44 NSD:C4EBE374| 00 00 00 00  0x0
45 NSD:C4EBE378| 00 00 00 00  0x0
24 NSD:C4EBE37C| 00 00 00 00  0x0
24 NSD:C4EBE380| 00 00 00 00  0x0
24 NSD:C4EBE384| 00 00 00 00  0x0
24 NSD:C4EBE388| 00 00 00 00  0x0
24 NSD:C4EBE38C| 00 00 00 00  0x0
24 NSD:C4EBE390| 00 00 00 00  0x0
24 NSD:C4EBE394| 00 00 00 00  0x0
24 NSD:C4EBE398| 00 00 00 00  0x0
24 NSD:C4EBE39C| 05 00 00 00  0x5                 
24 NSD:C4EBE3A0| 5D 04 00 00  0x45D               
24 NSD:C4EBE3A4| 15 7F 00 00  0x7F15
24 NSD:C4EBE3A8| 5A 5A 5A 5A  0x5A5A5A5A
24 NSD:C4EBE3AC| 5A 5A 5A 5A  0x5A5A5A5A

그럼 차근 차근 메모리 덤프를 분석해볼까요? 이 코드는 1024만큼 메모리를 할당했죠. 1024는 16진수로는 0x400인데요. C4EBDF00에서 0x400만큼 더한 C4EBE2FC 메모리 공간을 씁니다.
1 NSD:C4EBDF00|_78_78_78_78__0x78787878
2 NSD:C4EBDF04| 78 78 78 78  0x78787878
3 NSD:C4EBDF08| 78 78 78 78  0x78787878
4 NSD:C4EBDF0C| 78 78 78 78  0x78787878
5 NSD:C4EBDF10| 78 78 78 78  0x78787878
6 NSD:C4EBDF14| 78 78 78 78  0x78787878
7 NSD:C4EBDF18| 78 78 78 78  0x78787878
8 NSD:C4EBDF1C| 78 78 78 78  0x78787878
9 NSD:C4EBDF20| 78 78 78 78  0x78787878
10 NSD:C4EBDF24| 78 78 78 78  0x78787878
11 NSD:C4EBDF28| 78 78 78 78  0x78787878
12 NSD:C4EBDF2C| 78 78 78 78  0x78787878
//..
13 NSD:C4EBE2F8| 78 78 78 78  0x78787878
14 NSD:C4EBE2FC| 78 78 78 78  0x78787878
15 NSD:C4EBE300| CC CC CC CC  0xCCCCCCCC
16 NSD:C4EBE304| C0 D0 EB C4  0xC4EBD0C0

그런데 1번째 줄 덤프부터 14번째 줄 덤프까지 0x78로 도배하고 있네요. 음, 왜 그럴까요?
그 이유는 위에서 memset으로 0x78을 메모리 복사를 했기 때문이에요. 
6      memset(austin_debug_data, 0x78, 1024);

이번엔 15번째 줄 덤프입니다. 0xCC란 값이 있군요. 그런데 왜 갑자기 이런 값을 저장할까요?
15 NSD:C4EBE300| CC CC CC CC  0xCCCCCCCC

리눅스 커널 메모리 시스템에선 메모리 속성을 나타내기 위해 여러 핵사 값을 지정했는데요.
0xcc란 지금 메모리를 할당해서 쓰고 있다는 의미입니다.
[include/linux/poison.h]
#define SLUB_RED_ACTIVE 0xcc

그럼 16번째 덤프를 볼 차례입니다.
16 NSD:C4EBE304| C0 D0 EB C4  0xC4EBD0C0

0xC4EBD0C0란 메모리 덤프를 저장하고 있는데요. 이 메모리 주소는 다음에 할당할 메모리 주소를 가르키고 있습니다. 이 내용은 슬랩 메모리에 대해 설명할 때 다룰 예정이니 조금만 기다리세요. 

이제 17번줄부터 메모리 덤프를 볼 시간입니다.
17 NSD:C4EBE308| 74 68 43 C0  0xC0436874         \\vmlinux\kernel_bsp_debug_stat_set+0xFC
18 NSD:C4EBE30C| F0 EB 14 C0  0xC014EBF0         \\vmlinux\slub\kmem_cache_alloc_trace+0xB8
19 NSD:C4EBE310| 74 68 43 C0  0xC0436874         \\vmlinux\kernel_bsp_debug_stat_set+0xFC
20 NSD:C4EBE314| 10 EF 17 C0  0xC017EF10         \\vmlinux\libfs\simple_attr_write+0xD4
21 NSD:C4EBE318| 60 A7 15 C0  0xC015A760         \\vmlinux\fs/read_write\vfs_write+0xC8
22 NSD:C4EBE31C| EC AB 15 C0  0xC015ABEC         \\vmlinux\fs/read_write\sys_write+0x4C
23 NSD:C4EBE320| 00 F3 00 C0  0xC000F300         \\vmlinux\Global\ret_fast_syscall
24 NSD:C4EBE324| 00 00 00 00  0x0
25 NSD:C4EBE328| 00 00 00 00  0x0
26 NSD:C4EBE32C| 00 00 00 00  0x0
27 NSD:C4EBE330| 00 00 00 00  0x0
28 NSD:C4EBE334| 00 00 00 00  0x0
29 NSD:C4EBE338| 00 00 00 00  0x0
30 NSD:C4EBE33C| 00 00 00 00  0x0
31 NSD:C4EBE340| 00 00 00 00  0x0
32 NSD:C4EBE344| 00 00 00 00  0x0
33 NSD:C4EBE348| 00 00 00 00  0x0
34 NSD:C4EBE34C| 05 00 00 00  0x5                
35 NSD:C4EBE350| 54 18 00 00  0x1854
36 NSD:C4EBE354| DE 7F 00 00  0x7FDE

17번째부터 32번째 줄 코드까지 이 메모리를 할당한 콜스택 정보를 담고 있습니다. 이렇게 리눅스 커널 메모리 시스템에서 메모리를 어떻게 할당했는지 친절하게 저장하는 코드가 많습니다. 그 자료 구조 중에 (struct track *)이란 구조체가 있습니다. 이 자료구조는 슬랩 메모리 속성을 표현합니다. 그럼 이 구조체를 같이 볼까요?
[mm/slub.c]
1 #define TRACK_ADDRS_COUNT 16
2 struct track {
3 unsigned long addr; /* Called from address */  //<<-[1]
4 #ifdef CONFIG_STACKTRACE
5 unsigned long addrs[TRACK_ADDRS_COUNT]; /* Called from address */ //<-[2]
6 #endif
7 int cpu; /* Was running on cpu */ //<-[3]
8 int pid; /* Pid context */ //<-[4]
9 unsigned long when; /* When did the operation occur */ //<-[5]
10 };

[1] unsigned long addr : 메모리를 할당한 함수 주소 정보입니다.

[2] unsigned long addrs : 메모리를 할당할 때 콜스택를 저장하는 배열입니다. TRACK_ADDRS_COUNT 매크로가 16이니 16개 함수를 콜스택으로 저장하는군요. 

[3] int cpu: 메모리를 할당할 때 구동 중이던 CPU 번호입니다.

[4] int pid: 프로세스의 pid 정보입니다. 어떤 프로세스가 이 메모리를 할당했는지 알고 싶어서 추가한 멤버로 보입니다.

[5] unsigned long when: 메모리를 언제 할당했는지 알려주는 시간입니다. 메모리를 할당할 때 jiffies값을 저장합니다.

그럼 위 정보는 어느 코드에서 설정하냐고 어떤 분이 질문을 할 것 같군요. 다음 set_track 함수에서 설정합니다. 7번째부터 10번째 줄 코드까지 눈여겨 보세요.
1 static void set_track(struct kmem_cache *s, void *object,
2 enum track_item alloc, unsigned long addr)
3 {
4 struct track *p = get_track(s, object, alloc);
5
6 if (addr) {
//...
7 p->addr = addr;
8 p->cpu = smp_processor_id();
9 p->pid = current->pid;
10 p->when = jiffies;
11 } else
12 memset(p, 0, sizeof(struct track));
13 }

그럼 다시 메모리 덤프 분석으로 돌아갈께요.
17 NSD:C4EBE308| 74 68 43 C0  0xC0436874         \\vmlinux\kernel_bsp_debug_stat_set+0xFC

17번째 줄 덤프를 보면 0xC4EBE308 메모리 공간에서 (struct track*) 멤버가 위치한다고 했죠. 그럼 이 주소를 (struct track*)으로 캐스팅해서 볼까요? 결과는 다음과 같아요.
(struct track *) (struct track*)0xC4EBE304 = 0xC4EBE304 -> (
  (long unsigned int) addr = 3303788736 = 0xC4EBD0C0,
  (long unsigned int [16]) addrs = (
    [0] = 3225643124 = 0xC0436874,  // kernel_bsp_debug_stat_set+0xFC
    [1] = 3222596592 = 0xC014EBF0,  // kmem_cache_alloc_trace+0xB8
    [2] = 3225643124 = 0xC0436874,  // kernel_bsp_debug_stat_set+0xFC
    [3] = 3222794000 = 0xC017EF10,  // simple_attr_write+0xD4
    [4] = 3222644576 = 0xC015A760,  // vfs_write+0xC8
    [5] = 3222645740 = 0xC015ABEC, // sys_write+0x4C
    [6] = 3221287680 = 0xC000F300, // ret_fast_syscall
    [7] = 0 = 0x0,
    [8] = 0 = 0x0,
    [9] = 0 = 0x0,
    [10] = 0 = 0x0,
    [11] = 0 = 0x0,
    [12] = 0 = 0x0,
    [13] = 0 = 0x0,
    [14] = 0 = 0x0,
    [15] = 0 = 0x0),
  (int) cpu = 5,  //<<- CPU번호
  (int) pid = 6228,  //<<- pid 번호
  (long unsigned int) when = 21346) //<<- 메모리 할당할 때의 시간  

다음은 커널 크래시가 발생할 때의 프로세스 정보입니다. 아래 정보를 보면 CPU5에서 돌던 “sh” 프로세스의 pid가 6228이군요.
crash> runq -m
 CPU 0: [0 00:10:25.295]  PID: 0      TASK: c19553e8  COMMAND: "swapper/0"
 CPU 1: [0 00:08:46.071]  PID: 0      TASK: ead1c5c0  COMMAND: "swapper/1"
 CPU 2: [0 00:10:21.620]  PID: 0      TASK: ead1b640  COMMAND: "swapper/2"
 CPU 3: [0 00:05:45.008]  PID: 0      TASK: ead1be00  COMMAND: "swapper/3"
 CPU 4: [0 00:10:27.343]  PID: 0      TASK: eaf60000  COMMAND: "swapper/4"
 CPU 5: [0 00:00:00.096]  PID: 6228   TASK: e34a6c80  COMMAND: "sh"
 CPU 6: [0 00:10:27.442]  PID: 0      TASK: eaf607c0  COMMAND: "swapper/6"
 CPU 7: [0 00:10:27.443]  PID: 0      TASK: eaf66c80  COMMAND: "swapper/7"

자 여기까지 kmalloc으로 메모리를 할당하면 실제 메모리 덤프으로 메모리를 어떻게 쓰는지 확인했습니다.
다음에는 kfree를 쓸 때 일어나는 일에 대해서 살펴볼 예정입니다.

[Linux][Kernel] IPI call - call_single_queue [Kernel] Data Structure

IPI call을 수행할 때의 queue이 call_single_queue 분석
.

[Linux][Kernel] 라덱스 트리 radix tree [Kernel] Data Structure

인터럽트 디스크립터를 관리하는 라덱스 트리를 분석했습니다.

리눅스 커널 버전: 3.18 ~ 4.4까지 구조

리눅스 커널 버전: 4.9 구조


[Kernel][Timer] jiffies & jiffies_to_msecs Linux Kernel - Core Analysis

이번 시간에는 jiffies 값에 대해 다음과 같이 알아볼게요. 
1. jiffies 변수의 의미
2. 각 아키텍쳐별로 jiffies에 접근하는 방법
3. jiffies을 밀리 초로 변환하는 방법

jiffies 변수의 의미
jiffies 를 알려면 HZ에 대해 배워야 합니다. 그럼 HZ는 뭘 의미하죠?
HZ는 1초당 타이머 인터럽트를 처리하는 횟수를 의미합니다. 만약 HZ가 500이면 1초당 HZ가 500번 업데이트됩니다.

이번엔 만약 HZ가 300이라고 가정하고 현재 jiffies가 1000이면, jiffies 값이 1300이 되면 1초가 지났음을 알 수 있습니다.

현재 jiffies가 1000이면 초당 다음 값으로 업데이트 됩니다.
1초 후: 1300
2초 후: 1600
3초 후: 1900 

그럼 이번에는 jiffies란 값을 어떻게 가져오는지 알아볼게요. 

<<ARM64 아키텍처>>

jiffies란
우선 샘플 코드를 같이 볼게요.
아래 wq_watchdog_touch 함수는 워크큐에서 wq_watchdog_touched_cpu 란 per-cpu 타입의 변수에 jiffies 값을 저장합니다. 참고로 wq_watchdog_touch  함수는 리눅스 커널 4.9 버젼에서 확인했다는 점 기억해주세요.
void wq_watchdog_touch(int cpu)
{
if (cpu >= 0)
per_cpu(wq_watchdog_touched_cpu, cpu) = jiffies;
else
wq_watchdog_touched = jiffies;
}

C 코드로 구현된 wq_watchdog_touch 함수는 다음과 같이 어셈블리 코드로 구현돼 있습니다.
아래 코드에서 jiffies값을 어떻게 접근하는지 알아볼게요.
1 NSX:FFFFFF97D6ACC170|wq_watchdog_touch: stp x29,x30,[SP,#-0x20]!   ; x29,x30,[SP,#-32]!
2 NSX:FFFFFF97D6ACC174|    mov     x29,SP
3 NSX:FFFFFF97D6ACC178|    str     x19,[SP,#0x10]   ; x19,[SP,#16]
4 NSX:FFFFFF97D6ACC17C|    mov     w19,w0
5 NSX:FFFFFF97D6ACC180|    mov     x0,x30
6 NSX:FFFFFF97D6ACC184|    nop
7 NSX:FFFFFF97D6ACC188| tbnz x19,#0x1F,0xFFFFFF97D6ACC1B8  ; x19,#31,0xFFFFFF97D6ACC1B8
8 NSX:FFFFFF97D6ACC18C|    adrp    x1,0xFFFFFF97D91F1000
9 NSX:FFFFFF97D6ACC190|    adrp    x0,0xFFFFFF97D91E7000
10 NSX:FFFFFF97D6ACC194|   add     x1,x1,#0xA0      ; x1,x1,#160
11 NSX:FFFFFF97D6ACC198|   ldr     x2,[x0,#0xA80]   ; x2,[x0,#2688]
12 NSX:FFFFFF97D6ACC19C|                      adrp    x0,0xFFFFFF97D8BC5000
13 NSX:FFFFFF97D6ACC1A0|                      ldr     x1,[x1,w19,sxtw #0x3]   ; x1,[x1,w19,sxtw #3]
14 NSX:FFFFFF97D6ACC1A4|                      add     x0,x0,#0xB10     ; x0,x0,#2832
15 NSX:FFFFFF97D6ACC1A8|                      str     x2,[x0,x1,lsl #0x0]   ; x2,[x0,x1,lsl #0]

우선 다음 코드를 볼게요. 아래 코드가 실행되면 x0은 0xFFFFFF97D91E7000로 업데이트됩니다.
adrp 명령어는 주소를 복사하는 역할을 수행합니다.
9 NSX:FFFFFF97D6ACC190|   adrp    x0,0xFFFFFF97D91E7000

이제 다음 코드에서 jiffies값을 가져 옵니다.  
0xFFFFFF97D91E7000 주소에서 0xA80만큼 떨어진 메모리에 담긴 값을 읽어 옵니다. 즉, 0xFFFFFF97D91E7A80에 있는 메모리 값을 읽어오는 것이죠.
11 NSX:FFFFFF97D6ACC198|                      ldr     x2,[x0,#0xA80]   ; x2,[x0,#2688]

0xFFFFFF97D91E7A80 주소에 가보면 0x1006FF436 값이 있고 이 값을 10진수로 변환하면 4302304310이 되는거죠.
_________address|_data____________________|value_____________|symbol
NSD:FFFFFF97D91E7A80| 36 F4 6F 00 01 00 00 00  0x1006FF436

여기서 jiffies 변수는 0xFFFFFF97D91E7A80 주소에 있습니다.
그런데 실제 0xFFFFFF97D91E7A80 메모리에는 jiffies_64란 변수가 있습니다.
v.v %all %l &jiffies_64
  (long unsigned int *) [-] &jiffies_64 = 0xFFFFFF97D91E7A80 = jiffies_64 -> 4302304310 = 0x00000001006FF436

여기까지 분석한 내용을 유추하면 ARM64 비트 아키텍처에선 jiffies을 jiffies_64 변수로 변환한다는 점을 알 수 있습니다.

jiffies에서 밀리 초 변환
jiffies 변수를 밀리초 단위로 변환해서 보고 싶을 때 jiffies_to_msecs 함수를 호출하면 됩니다.
해당 함수 코드 구현부는 아래와 같은데요. 그런데 참 코드 보기가 어렵죠. 
여러 매크로에 따라 코드가 다르게 구현됩니다.
unsigned int jiffies_to_msecs(const unsigned long j)
{
#if HZ <= MSEC_PER_SEC && !(MSEC_PER_SEC % HZ)
return (MSEC_PER_SEC / HZ) * j;
#elif HZ > MSEC_PER_SEC && !(HZ % MSEC_PER_SEC)
return (j + (HZ / MSEC_PER_SEC) - 1)/(HZ / MSEC_PER_SEC);
#else
# if BITS_PER_LONG == 32
return (HZ_TO_MSEC_MUL32 * j) >> HZ_TO_MSEC_SHR32;
# else
return (j * HZ_TO_MSEC_NUM) / HZ_TO_MSEC_DEN;
# endif
#endif
}
EXPORT_SYMBOL(jiffies_to_msecs);

이럴 땐 차라리 jiffies_to_msecs 함수를 어셈블리 코드로 보는게 더 편합니다.

코드 좀 분석해볼까요? 우선 이 함수에 전달되는 파라미터는 jiffies 값이라고 알고 있죠?
ARM64 함수 호출 규약에 따라 파라미터는 x0 레지스터로 전달되니 아래 코드 0xFFFFFF97D6B3FA24 주소에선 
x0가 jiffies 값을 담고 있다고 머리 속으로 그려주세요. 
그리고 이번엔 jiffies를 0x1006FF436(4302304310: 10진수)라고 가정할께요.
1 NSX:FFFFFF97D6B3FA24|jiffies_to_msecs:    stp     x29,x30,[SP,#-0x20]!   ; x29,x30,[SP,#-32]!
2 NSX:FFFFFF97D6B3FA28|                     mov     x29,SP
3 NSX:FFFFFF97D6B3FA2C|                     str     x19,[SP,#0x10]   ; x19,[SP,#16]
4 NSX:FFFFFF97D6B3FA30|                     mov     x19,x0
5 NSX:FFFFFF97D6B3FA34|                     mov     x0,x30
6 NSX:FFFFFF97D6B3FA38|                     nop
7 NSX:FFFFFF97D6B3FA3C|                     lsl     w0,w19,#0x3      ; w0,w19,#3
8 NSX:FFFFFF97D6B3FA40|                     add     w0,w0,w19,lsl #0x1   ; w0,w0,w19,lsl #1
9 NSX:FFFFFF97D6B3FA44|                     ldr     x19,[SP,#0x10]   ; x19,[SP,#16]
10 NSX:FFFFFF97D6B3FA48|                     ldp     x29,x30,[SP],#0x20   ; x29,x30,[SP],#32
11 NSX:FFFFFF97D6B3FA4C|                     ret

4번 째 줄 코드 부터 볼게요. 파라미터인 jiffies 값은 x0으로 전달된다고 했죠.
jiffies을 x19 레지스터에 저장합니다. x19은 0x1006FF436으로 업데이트됩니다.
4 NSX:FFFFFF97D6B3FA30|     mov     x19,x0

이번엔 7번째 줄 코드입니다.
7 NSX:FFFFFF97D6B3FA3C|   lsl     w0,w19,#0x3   ; w0,w19,#3

lsl 명령어는 왼쪽으로 비트 시프트 연산을 수행하는 동작입니다.
w19 주소인 0x1006FF436을 (0x1006FF436 << 3)로 연산하는 거죠.

0x1006FF436 값을 2진수로 변환하면 다음과 같은데요. 
0x1006FF436 << 3 연산 과정으로 아래 결과를 나옵니다.
100000000011011111111010000110110       // 0x1006FF436
100000000011011111111010000110110000 // 위 비트를 왼쪽으로 3비트 쉬프트한 비트    

11011111111010000110110000 이진수를 16진수로 변환하면 0x37FA1B0 값으로 되겠죠?
결국 x0에 0x37FA1B0 값이 업데이트됩니다.


이번에는 마지막 코드입니다.
8 NSX:FFFFFF97D6B3FA40|   add     w0,w0,w19,lsl #0x1   ; w0,w0,w19,lsl #1
 
이 코드는 w19 레지스터를 왼쪽으로 1비트 쉬프트한 값을 w0에 더해서 w0에 저장하는 동작입니다.
w0 = w0 + (w19,lsl #0x1)  

그럼 우선 (w19,lsl #0x1) 코드가 어떻게 실행되는지 알아볼게요.
x19에는 이 함수에 전달된 jiffies 값 0x1006FF436이 있었죠.

0x1006FF436를 왼쪽으로 1비트 쉬프트시키면 결괏값은 0xDFE86C 입니다.
0xDFE86C = 0x1006FF436 << 0x1

(2진수 쉬프트 값)
100000000011011111111010000110110
1000000000110111111110100001101100

이제까지 계산했던 정보를 모으면 다름 명령어의 결괏값을 알 수 있습니다.
8 NSX:FFFFFF97D6B3FA40|   add     w0,w0,w19,lsl #0x1   ; w0,w0,w19,lsl #1

아래 계산식으로 0x45F8A1C(73370140: 10진수)이 됩니다.
0x37FA1B0 + 0xDFE86C = w0 + (0x1006FF436 << 0x1) = w0 + (w19 << 0x1) = w0 + (w19,lsl #0x1)


<< ARM32 아키텍처 >>
jiffies란
ARM32 아키텍처에선 jiffies 변수를 확인하면 바로 jiffies 값을 확인할 수 있습니다.
그런데 long long 타입으로 jiffies_64이란 변수도 같이 선언돼 있습니다. 

jiffies는 6905112으로 처리합니다.
(long unsigned int *) &jiffies = 0xC1A02100 = jiffies_64 -> 6905112 = 0x00695D18 = '.i].'
(u64 *) &jiffies_64 = 0xC1A02100 = jiffies_64 -> 4301872408 = 0x0000000100695D18

jiffies에 접근하는 jiffy_sched_clock_read 함수를 어셈블리 코드로 볼까요? 실제 코드에서jiffies을 어떻게 접근하는지 알아보기 위해서죠.
static u64 notrace jiffy_sched_clock_read(void)
{
return (u64)(jiffies - INITIAL_JIFFIES);
}

0xC019A75C 메모리 공간엔 jiffies 변수의 위치인 0xC1A02100 메모리 값을 담고 있습니다.
NSR:C019A744|E59F3010  jiffy_sched_clock_read:    ldr     r3,  0xC019A75C
NSR:C019A748|E3A01000      mov     r1,#0x0          ; r1,#0
(long unsigned int *) &jiffies = 0xC1A02100 = jiffies_64 -> 6905112 = 0x00695D18  
 (u64 *) &jiffies_64 = 0xC1A02100 = jiffies_64 -> 4301872408
_____address|________0________4________8________C 
NSD:C1A02100|>00695D18 00000001 00000000 00000000

jiffies_to_msecs
jiffies 값에서 0xA를 곱하면 밀리 초로 변환됩니다. 이 함수에 전달된 파라미터는 r0에 담겨 있다는 점 기억하세요.
NSR:C0188C78|E3A0300A  jiffies_to_msecs:  mov     r3,#0x0A         ; r3,#10
NSR:C0188C7C|E0000093                     mul     r0,r3,r0         ; j,r3,j
NSR:C0188C80|E12FFF1E                     bx      r14

jiffies값이 0x695D18(6905112)이면 0x41DA2F0(69051120) 밀리초로 변환되는군요.
0x41DA2F0(69051120) = 0x695D18 * 0xA

jiffies 값을 보면 밀리 초 단위로 바로 변환해서 확인하거나, jiffies 값 차이를 보면 얼만큼 몇 밀리 초 만큼 차이가 있는지 바로 확인해야 합니다.

[Kernel] wakelock debug patch [Crash] Debugging Patch

안드로이드에서 wake lock이란 기능이 있습니다. 그런데 대부분 wake lock을 어떤 모듈이 잡고 있어서 슬립에 못 들어가는 문제가 생기죠. 이럴 때 프로파일링하면 좋은 디버그 패치를 소개합니다.
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
old mode 100644
new mode 100755
index 7dbfe1a..2ca0964
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -54,6 +54,7 @@ static DEFINE_SPINLOCK(events_lock);
 static void pm_wakeup_timer_fn(unsigned long data);
 
 static LIST_HEAD(wakeup_sources);
+static LIST_HEAD(wakeup_sources_shadow);
 
 static DECLARE_WAIT_QUEUE_HEAD(wakeup_count_wait_queue);
 
@@ -134,6 +135,17 @@ static void wakeup_source_destroy_cb(struct rcu_head *head)
  wakeup_source_destroy(container_of(head, struct wakeup_source, rcu));
 }
 
+struct wakeup_source_shadow {
+ struct list_head entry;
+ const char *name;
+ struct task_struct *p;
+ struct wakeup_source *ws;
+ ktime_t add_ts;
+ ktime_t rmv_ts;
+ int ws_event; /* 1 for add, 2 for remove */
+}; 
+
+
 /**
  * wakeup_source_add - Add given object to the list of wakeup sources.
  * @ws: Wakeup source object to add to the list.
@@ -141,7 +153,8 @@ static void wakeup_source_destroy_cb(struct rcu_head *head)
 void wakeup_source_add(struct wakeup_source *ws)
 {
  unsigned long flags;
-
+ struct wakeup_source_shadow *ws_sh;
+
  if (WARN_ON(!ws))
  return;
 
@@ -152,6 +165,19 @@ void wakeup_source_add(struct wakeup_source *ws)
 
  spin_lock_irqsave(&events_lock, flags);
  list_add_rcu(&ws->entry, &wakeup_sources);
+ if (!strncmp(ws->name, "wlan", strlen("wlan"))) {
+ pr_err("[%s] add hit : %p, %s\n", __func__, ws, ws->name);
+ ws_sh = kmalloc(sizeof(*ws_sh), GFP_ATOMIC);
+ if (ws_sh) {
+ ws_sh->ws = ws;
+ ws_sh->p = current;
+ ws_sh->name = ws->name;
+ ws_sh->add_ts = ws_sh->rmv_ts = ws->last_time;
+ ws_sh->ws_event = 1;
+ list_add_rcu(&ws_sh->entry, &wakeup_sources_shadow);
+ dump_stack();
+ }
+ }
  spin_unlock_irqrestore(&events_lock, flags);
 }
 EXPORT_SYMBOL_GPL(wakeup_source_add);
@@ -163,12 +189,25 @@ EXPORT_SYMBOL_GPL(wakeup_source_add);
 void wakeup_source_remove(struct wakeup_source *ws)
 {
  unsigned long flags;
-
+ struct wakeup_source_shadow *ws_sh;
+
  if (WARN_ON(!ws))
  return;
 
  spin_lock_irqsave(&events_lock, flags);
  list_del_rcu(&ws->entry);
+ if (!strncmp(ws->name, "wlan", strlen("wlan"))) {
+ pr_err("[%s] rmv hit : %p, %s\n", __func__, ws, ws->name);
+ list_for_each_entry(ws_sh, &wakeup_sources_shadow, entry) {
+ if (ws_sh->ws == ws) {
+ ws_sh->rmv_ts = ktime_get();
+ ws_sh->ws_event = 2;
+ ws_sh->p = current;
+ dump_stack();
+ break;
+ }
+ }
+ }
  spin_unlock_irqrestore(&events_lock, flags);
  synchronize_rcu();
 }



[Linux][Kernel] __init 매크로 (라즈베리파이) Raspberry_Kernel_Macro

코드 리뷰를 하다 보면 함수 앞에 __init 코드가 붙은 것이 보입니다. 음, 이 __init이란 코드는 어떤 동작을 할까요?
같이 살펴볼게요. 

그럼 다음 코드를 한번 같이 볼까요? 위에서 말씀드린대로 init_workqueues 함수 앞에 __init가 붙어 있습니다.  
참고로, init_workqueues 함수는 이름과 같이 워크큐를 초기화하는 역할을 수행합니다.
[kernel/workqueue.c]
static int __init init_workqueues(void)
{
int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
int i, cpu;

이전 장에서 C 코드를 보다가 조금 이라도 의문이 생기면 전처리 파일을 열어보는게 좋다고 했죠?
이번에도 해당 파일을 컴파일하면 생성되는 전처리 파일을 함께 볼까요?

kernel/workqueue.c 파일을 컴파일하면 kernel/.tmp_workqueue.i 이름으로 전처리 파일이 생성됩니다.
이점 유념하시고 이 파일을 열여보면 다음과 같은 init_workqueues 함수가 보일 겁니다.
static int __attribute__ ((__section__(".init.text"))) __attribute__((__cold__)) __attribute__((no_instrument_function)) init_workqueues(void)
{
 int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
 int i, cpu;

위 전처리 코드를 보면 __init이란 매크로가 다음과 같다는 사실을 알 수 있습니다. 
평소 C 코드에서 볼 수 없는 이상한 코드가 붙네요.
__init = __attribute__ ((__section__(".init.text"))) __attribute__((__cold__)) __attribute__((no_instrument_function))

그럼 어느 코드에서 __init이란 매크로를 설정하는지 알아볼까요?
__init이란 매크로는 [include/linux/init.h] 파일에 다음과 같이 정의돼 있습니다.
#define __init          __section(.init.text) __cold notrace __latent_entropy

그런데 매크로에서 매크로를 다시 호출하고 있는 구조이네요. 아 그런데 여기부터 머리가 아파지는 군요.
이상한 어셈블리 명령어 같은 문자가 보이네요. 그런데 여기서 분석을 멈추면 안됩니다. 남는 게 아무것도 없거든요.
조금 더 어렵더라고 더 집중하면서 코드의 의미를 살펴볼까요?
 
복잡해 보이지만 코드 한줄 한줄 씩 차근 차근 알아가면 그리 어렵지도 않아요.

코드 리뷰
1. __section(.init.text) 분석
include/linux/compiler.h 파일을 열어보면 다음과 같이 # 매크로를 써서 __section(S)을 구현했는데요. 
# define __section(S) __attribute__ ((__section__(#S)))

__section(.init.text) 이 코드에서 입력이 .init.text이므로 #S 대신 .init.text을 입력하면 다음 코드가 되겠죠?
잘 이해가 안가는 분은 이전 장에서 # 매크로 기법에 대한 내용을 다시 읽어주세요.
 __attribute__ ((__section__(.init.text)))

그럼, __attribute__ ((__section__(.init.text))) 은 어떤 의미일까요? 이는 .init.text란 섹션에 이 코드를 위치시키라는 의미입니다.
섹션이란 비슷한 역할을 수행하는 코드 묶음인데요. 특정 메모리 구간 내에 섹션이 위치합니다.
그럼 __init이란 매크로가 붙는 함수들은 모두  __attribute__ ((__section__(.init.text))) 속성이으로
.init.text 섹션에 위치합니다. 그럼 init_workqueues 함수가 정말 .init.text 섹션에 있는지 확인해야겠죠.

이 동작은 조금 더 알아볼게요. 

2. __cold 분석
__cold 매크로는 [include/linux/compiler-gcc.h] 파일에 다음과 같이 선언 됐습니다.
__cold는 __attribute__((__cold__))로 치환하는 매크로이군요. 
#define __cold                 __attribute__((__cold__))

자주 호출될 가능성이 낮은 함수에 붙는 속성입니다. 보통 부팅이나 리부팅 될 때만 호출되는 함수에 붙는 속성이죠.
이런 속성을 지정하면 GCC 컴파일러는 이 함수의 처리 속도보다 사이즈에 조금 더 초점을 맞춰서 최적화를 합니다.

__init 매크로가 붙는 함수들은 커널이 부팅할 때 한번만 호출된다는 점 유념해주세요.

3. notrace 분석 
include/linux/compiler.h 파일에 notrace 매크로가 선언돼 있습니다.
#define notrace __attribute__((no_instrument_function))
__attribute__((no_instrument_function)) 속성은 함수를 컴파일이 할 때 프로파링 정보를 붙히지 말하는 의미입니다. 왜냐면 __init 매크로가 붙은 함수들은 부팅할 때 딱 한번 호출되거든요. 그래서 해당 함수가 얼마나 빨리 실행하는지 프로파일 할 필요는 없습니다.

여기까지 코드 리뷰를 마무리하고 실제 해당 코드가 어떻게 동작하는지 같이 알아볼게요.

검증 및 테스트해보기
이번에는 위에서 코드 리뷰한 내용을 확인하는 시간을 갖도록 할게요.
코드를 분석하고 이해해도 실제 그렇게 동작하는지 확인하지 않으면 머리 속에 배운 내용이 오래 남기 않거든요.

1. .init.data 섹션 정보
이번에는 .init.data 섹션 정보를 확인하고 정말 init_workqueues 함수가 .init.data 섹션에 있는지 확인 합니다.

이를 위해 바이너리 유틸리티로 init_workqueues 함수 위치를 알아볼 필요가 있는데요.
어떻게 바이너리 유틸리티로 유용한 디버깅 정보를 얻어오는 지 간단히 설명드리죠.

다음 명령어를 입력하면 크로스 컴파일 툴 체인 프로그램을 다운로드 받을 수 있습니다.
git clone https://github.com/raspberrypi/tools

위 명령어로 파일을 받으면 바이너리 유틸리티도 쓸 수 있어요. 파일 위치는 다음과 같습니다.
[./arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-objdump]

라즈베리안을 컴파일하면 디버깅 정보가 포함된 vmlinux가 생성됩니다.
이 vmlinux를 arm-linux-gnueabihf-objdump 바이너리 유틸리티를 써서 여러 유용한 어셈블리 코드 정보를 알 수 있거든요.

arm-linux-gnueabihf-objdump 파일과 vmlinux을 같은 폴더에 위치시키고 다음 명령어를 입력하면,
라즈베리안 리눅스 커널 코드를 어셈블리 형태로 볼 수 있어요.
./arm-linux-gnueabihf-objdump -S -d vmlinux  > rasberian_kernel_code.c

이 출력 파일에서 init_workqueues란 함수를 찾아보니 0xc18321cc 주소에 위치 해 있습니다.
c18321cc <init_workqueues>:
       wq_numa_possible_cpumask = tbl;
       wq_numa_enabled = true;
}

static int __init init_workqueues(void)
{
c18321cc:      e1a0c00d        mov     ip, sp
c18321d0:      e92ddff0        push    {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
c18321d4:      e24cb004        sub     fp, ip, #4
c18321d8:      e24dd024        sub     sp, sp, #36     ; 0x24

아까 __init 매크로를 함수 앞에 붙히면 .init.data 섹션에 해당 코드가 위치한다고 했죠.

그럼 .init.data 섹션 정보에 대해서 조금 알아볼까요?
arm-linux-gnueabihf-objdump 바이너리 유틸리티를 다음 명령어로 쓰면 섹션 정보를 볼 수 있습니다.
./arm-linux-gnueabihf-objdump -x vmlinux  | more

다음은 섹션 정보 조각들인데요. 
25번째 섹션인 .init.data은  0xc18a0000부터 위치 해있고 그 사이즈는 018a8000라는 걸 알 수 있습니다.
당연히 .init.data 섹션은 0xc18a0000--0xc3148000 메모리 공간에 위치했다고 볼 수 있죠.
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .head.text    00000264  c0008000  c0008000  00008000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .text         00f6de70  c0008280  c0008280  00008280  2**6
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .rodata       0074305e  c0f77000  c0f77000  00f77000  2**6
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
//...
 24 .init.data    00093a4c  c18a0000  c18a0000  018a8000  2**12
                  CONTENTS, ALLOC, LOAD, DATA
 25 .data..percpu 00003e00  c1934000  c1934000  0193c000  2**6
                  CONTENTS, ALLOC, LOAD, DATA

.init.data 섹션은 0xc18a0000--0xc3148000 내에 init_workqueues 란 함수(0x80b092bc)가 위치해 있음을 알 수 있죠.

그럼 init_workqueues 근처에 어떤 함수가 위치해있는지 살펴볼게요.
이 함수 전후로 pidhash_init와 wq_sysfs_init 함수가 위치해 있음을 알 수 있습니다. 

wq_sysfs_init 함수 위치
c183203c <wq_sysfs_init>:
       .name                           = "workqueue",
       .dev_groups                     = wq_sysfs_groups,
};

static int __init wq_sysfs_init(void)
{
c183203c:      e1a0c00d        mov     ip, sp

pidhash_init 함수 위치
c1832590 <pidhash_init>:
 * The pid hash table is scaled according to the amount of memory in the
 * machine.  From a minimum of 16 slots up to 4096 slots at one gigabyte or
 * more.
 */
void __init pidhash_init(void)
{
c1832590:      e1a0c00d        mov     ip, sp
c1832594:      e92dd830        push    {r4, r5, fp, ip, lr, pc}
c1832598:      e24cb004        sub     fp, ip, #4

위 코드를 정리하면 아래 순서대로 각각 함수들이 위치해 있다는 걸 알 수 있습니다.
c183203c <wq_sysfs_init>:
c18321cc <init_workqueues>:
c1832590 <pidhash_init>:

여기까지 확인한 정보를 정리하면 __init를 앞에 붙힌 함수는 .init.data 섹션에 위치한다는 점을 확인할 수 있군요.

2. notrace
no_instrument_function 속성이 뭔지 조금 더 알아볼게요. notrace 매크로는 다음 코드로 치환된다고 알고 있습니다.
#define notrace __attribute__((no_instrument_function))

이 매크로를 함수에 추가하면 컴파일러에서 -finstrument-functions 컴파일 옵션을 쓸때 해당 함수에 대한 profiling을 비활성합니다. 그럼 -finstrument-functions 컴파일 옵션을 키면 코드가 어떻게 바뀌는지 알아볼까요?

우리는 __init이란 매크로가 다음 코드로 치환한다는 걸 배웠죠.
#define __init          __section(.init.text) __cold notrace __latent_entropy

그럼 다음과 같이 코드를 작성하면  __attribute__((no_instrument_function)) 매크로를 빼고 아래 함수를 컴파일합니다.
#define __init_test          __section(.init.text) __cold  __latent_entropy

1 diff --git a/kernel/workqueue.c b/kernel/workqueue.c
2 index ebfea5f..0a03444 100644
3 --- a/kernel/workqueue.c
4 +++ b/kernel/workqueue.c
5 @@ -5489,7 +5489,9 @@ static void __init wq_numa_init(void)
6        wq_numa_enabled = true;
7 }
8
9 -static int __init init_workqueues(void)
10 +#define __init_test          __section(.init.text) __cold  __latent_entropy
11 +
12 +static int __init_test init_workqueues(void)
13  {
14       int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
15        int i, cpu;

위 패치 코드를 잠깐 같이 볼까요?
12번 째 줄 코드를 보면 __init 대신 __init_test 매크로로 init_workqueues 함수를 선언합니다.
원래 선언된 코드는 9번째 줄 코드를 보세요.

다음 코드는 init_workqueues 함수에  __attribute__((no_instrument_function)) 속성을 빼고 컴파일한 후 얻은
vmlinux로 확인한 어셈블리 코드입니다. 원래 코드는 __attribute__((no_instrument_function)) 속성이 포함돼 있었다는 점 기억하시고요.
1 80b092bc <init_workqueues>:
2 80b092bc: e1a0c00d        mov     ip, sp
3 80b092c0: e92ddff0        push    {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
4 80b092c4: e24cb004        sub     fp, ip, #4
5 80b092c8: e24dd01c        sub     sp, sp, #28
6 80b092cc: e52de004        push    {lr}            ; (str lr, [sp, #-4]!)
7 80b092d0: ebd814a9        bl      8010e57c <__gnu_mcount_nc>
8 80b092d4: e3a0c000        mov     ip, #0
9 80b092d8: e3a01c01        mov     r1, #256        ; 0x100
10 80b092dc: e3e0e013        mvn     lr, #19

11 c18321cc <init_workqueues>:
12 c18321cc:      e1a0c00d        mov     ip, sp
13 c18321d0:      e92ddff0        push    {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
14 c18321d4:      e24cb004        sub     fp, ip, #4
15 c18321d8:      e24dd024        sub     sp, sp, #36     ; 0x24
16c18321dc:      e3a01c01        mov     r1, #256        ; 0x100
17 c18321e0:      e3a03000        mov     r3, #0
18 c18321e4:      e3e02013        mvn     r2, #19
19 c18321e8:      e50b3034        str     r3, [fp, #-52]  ; 0x34
20 c18321ec:      e50b2030        str     r2, [fp, #-48]  ; 0x30

한 가지 특이한 점은 __attribute__((no_instrument_function)) 속성을 뺀 함수에서 __gnu_mcount_nc란 코드가 추가됐다는 점입니다. 아, 참 신기하네요.
6 80b092cc: e52de004        push    {lr}            ; (str lr, [sp, #-4]!)
7 80b092d0: ebd814a9        bl      8010e57c <__gnu_mcount_nc>

이번에는 __gnu_mcount_nc 함수와 이 함수를 호출한 init_workqueues 함수 코드를 볼게요.
8010e57c <__gnu_mcount_nc>:
8010e57c:       e1a0c00e        mov     ip, lr //<<--[2]
8010e580:       e8bd4000        ldmfd   sp!, {lr} //<<--[3]
8010e584:       e1a0f00c        mov     pc, ip //<<--[4] 

1 80b092bc <init_workqueues>:
//...   
6 80b092cc: e52de004        push    {lr}            ; (str lr, [sp, #-4]!) //<<--[1]
7 80b092d0: ebd814a9        bl      8010e57c <__gnu_mcount_nc>
8 80b092d4: e3a0c000        mov     ip, #0   

어셈블리 코드라도 어려워하지 마시고, 천천히 읽어볼까요? 조금만 지나면 익숙해집니다.

[1]: __gnu_mcount_nc 함수를 호출하기 전부터 코드 부터 확인해야 겠어요.
일단 이 때 init_workqueues 함수를 호출한 함수는 r14에 저장돼 있겠죠.
r14를 스택에 푸시합니다.

[2]: 이제 드디어 __gnu_mcount_nc 함수가 호출됐습니다 바로 linked register(r14)를 r12복사합니다. 
만약 init_workqueues함수에서 __gnu_mcount_nc 함수를 호출했을 경우  linked register는 어디를 가르킬까요?
__gnu_mcount_nc 함수를 호출한 init_workqueues 함수 내 80b092d0 주소를 가르키고 있겠죠.

[3]: 스택에 r14을 푸시합니다. 자 여기서 r14가 init_workqueues 함수 내 80b092d0 주소을 가르킨다는 점 잊지 마세요.

[4]: 프로그램 카운터에 __gnu_mcount_nc 함수를 호출하기 직전 init_workqueues 함수 내 80b092d0 주소를 저장합니다. 달리 이 동작을 설명드리면 __gnu_mcount_nc 함수에서 빠져 나가는 동작입니다.

정리하면 이 __attribute__((no_instrument_function)) 속성을 빼면 추가로 어셈블리 코드가 붙습니다.
프로세스가 돌고 있는 스택이란 운동장에 해당 함수를 호출한 함수와 해당 함수 정보를 저장합니다.

조금 더 구체적으로 이 어셈블리 코드가 수행되면서 init_workqueues 함수를 호출한 함수와 init_workqueues 함수 앞 부분 주소를 스택에 저장하죠. 스택에 init_workqueues 앞 부분 코드를 푸시하는 동작을 하기 때문이죠.

이 동작은 gcc 컴파일러에서 제공하는 프로파일링 기능을 지원할 때 추가되는 코드입니다.

그런데 init_workqueues란 코드는 부팅할 때 딱 한번 호출하는 함수입니다. 구지 성능에 영향을 줄 수 없는 코드라고 할 수 있죠. 그럼 이 함수는 GCC의 prof 기능을 써서 프로파일링을 할 필요가 없죠. 그래서 이 매크로를 붙힌 것입니다.

여기까지 __init 매크로에 대해서 알아봤습니다. 앞으로 __init이란 매크로가 추가된 함수를 보면 위와 같은 규칙으로 코드를 생성하고 해당 코드는 .init.data 섹션에 위치한다는 점 머리에 그려주세요.

[Linux][Kernel] 매크로 ##uname 파라미터 전달 기법 Raspberry_Kernel_Macro

여러분 리눅스 커널 코드를 자주 보시나요? 그런데 커널 소스를 보다가 막힐 때가 종종 있지 않나요? 함수를 따라 가다 보면 존재하지 않는 함수를 호출해서 길을 잃기도 합니다. 그리고 매크로가 너무 많아서 분석 시간이 너무 오래 걸리는 경우도 있습니다.

이번 장에서는 리눅스 커널 코드를 읽다가 만나는 매크로를 모두 풀어보겠습니다. 이 매크로만 풀어도 리눅스 커널 소스 코드를 아주 효율적으로 읽을 수 있어요.


자 그럼 한 가지 예를 들게요. 아래 코드를 보면 PageReclaim와 ClearPageReclaim 함수가 보입니다.
[mm/filemap.c]
void end_page_writeback(struct page *page)
{
if (PageReclaim(page)) {
ClearPageReclaim(page);
rotate_reclaimable_page(page);
}

PageReclaim 함수는 해당 페이지가 Reclaim 속성인지를 확인하고 ClearPageReclaim는 페이지 내 Reclaim 속성을 해제하는 동작인 것 같은데요. 
함수 이름으로 그 동작을 예상하는 것도 좋지만 실제 PageReclaim와 ClearPageReclaim 함수 코드를 열어서 봐야 겠죠?
 
아, 그런데 문제가 생겼습니다. 커널 코드 어디에도 PageReclaim와 ClearPageReclaim 함수를 찾아볼 수가 없습니다.
이거 난감하네요. 여기서 분석을 멈춰야 할까요? 이전에도 말씀드렸지만 이렇게 C 코드로 리눅스 커널 코드를 보다가 조금 이상하면 전처리 파일을 열어보는게 좋습니다.

이전에 소스 코드는 전처리 파일과 함께 보는게 효율적이라고 했죠. 전처리 파일은 해더 파일과 인라인 함수을 담아서 생성되기 때문이죠.
자, 이제 전처리 파일을 찾아서 PageReclaim와 ClearPageReclaim함수가 어디에 있는지 확인해볼까요?

[mm/.tmp_filemap.i] 파일이 [mm/filemap.c]에 대한 전처리 파일이라는 점 기억하시고요.
참고로 전처리 파일은 tmp란 접두사가 붙는다는 점 잊지 마세요.

해당 전처리 파일에서 열어본 end_page_writeback 함수는 다음과 같습니다.
뭔가 기대하고 전처리 코드를 열었는데 안타깝게도 C 코드와 완전히 같네요.
1 void end_page_writeback(struct page *page)
2 {
3 if (PageReclaim(page)) {
4  ClearPageReclaim(page);
5  rotate_reclaimable_page(page);
6  }

그런데 3번과 4번째 줄 코드를 보면 PageReclaim와 ClearPageReclaim 함수가 그대로 있습니다. 아 참 이거 난감하네요. PageReclaim과 ClearPageReclaim 함수를 함수를 검색해볼까요? 
만약 PageReclaim과 ClearPageReclaim 함수가 만약 인라인 타입 함수면 전처리 파일에 그대로 있겠죠.

그럼 [mm/.tmp_filemap.i] 파일에서 PageReclaim, ClearPageReclaim 함수를 검색을 해 볼까요?
어 그런데, 이번엔 다음과 같이 구현부가 보입니다. 
1 static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) __attribute__((always_inline)) int PageReclaim(struct page *page) { return test_bit(PG_reclaim, &({ ((void)(sizeof(( long)(0 && PageTail(page))))); compound_head(page);})->flags); } 
2 static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) __attribute__((always_inline)) void SetPageReclaim(struct page *page) { _set_bit(PG_reclaim,&({ ((void)(sizeof(( long)(1 && PageTail(page))))); compound_head(page);})->flags); } static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) __attribute__((always_inline)) 
3 void ClearPageReclaim(struct page *page) { _clear_bit(PG_reclaim,&({ ((void)(sizeof(( long)(1 && PageTail(page))))); compound_head(page);})->flags); }
4 # 313 "/home001/austindh.kim/src/raspberry_kernel/linux/include/linux/page-flags.h"

자, 그럼 C 코드로는 함수 구현부가 없는데 전처리 코드에는 있습니다. 왜 이럴까요?
그럼 코드에 대한 구현부는 어떻게 찾죠? 비밀은 위 전처리 코드에서 4번째 줄에 해더 파일이 보이죠?
이 해더 파일에 비밀이 있습니다. 그럼 이 해더 파일 근처 코드를 좀 볼까요?
# 313 "/home001/austindh.kim/src/raspberry_kernel/linux/include/linux/page-flags.h"

[linux/include/linux/page-flags.h] 해더 파일을 열어보면 다음 코드가 있습니다.
결론부터 말씀드리면 이 코드가 PageReclaim, ClearPageReclaim 함수의 구현부입니다.
아 참 이해가 안가죠. 이런 매크로가 PageReclaim, ClearPageReclaim 함수 구현부라니? 조금 이상하진 않나요?
 PAGEFLAG(Reclaim, reclaim, PF_NO_TAIL)
         TESTCLEARFLAG(Reclaim, reclaim, PF_NO_TAIL)
 PAGEFLAG(Readahead, reclaim, PF_NO_COMPOUND)
         TESTCLEARFLAG(Readahead, reclaim, PF_NO_COMPOUND)

그럼 이제 어떻게 위 매크로로 PageReclaim, ClearPageReclaim 함수를 구현하는지 차근 차근 배워 볼게요.

이번엔 PAGEFLAG 매크로가 어떻게 구현됐는지 알아볼 시간입니다.
같은 해더 파일 [linux/include/linux/page-flags.h]에서 PAGEFLAG란 매크로를 검색하니 다음 코드가 보이네요.
PAGEFLAG는 또 다시 다른 매크로를 부르는군요. 조금 더 구체적으로 볼까요?
PAGEFLAG 매크로는 uname, lname, policy란 입력을 받아서, TESTPAGEFLAG, SETPAGEFLAG, CLEARPAGEFLAG 매크로를 그대로 호출합니다.
 #define PAGEFLAG(uname, lname, policy)                                  \
         TESTPAGEFLAG(uname, lname, policy)                              \
         SETPAGEFLAG(uname, lname, policy)                               \
         CLEARPAGEFLAG(uname, lname, policy)

그럼 각각 매크로 구현부를 살펴볼게요.

우선 먼저, TESTPAGEFLAG 매크로를 볼게요. 다행히 TESTPAGEFLAG 매크로는 다른 매크로를 호출하지는 않네요. 이제 분석의 종착역에 온 듯 합니다.
1 #define TESTPAGEFLAG(uname, lname, policy)                              \
2 static __always_inline int Page##uname(struct page *page)               \
3         { return test_bit(PG_##lname, &policy(page, 0)->flags); }

이제 코드 한줄 한줄 씩 볼까요?
TESTPAGEFLAG 매크로는 uname 입력을 받아서 "Page##uname"란 인라인 형태의 함수를 선언합니다.
그리고 이 함수의 파라미터는 struct page *page와 같이 페이지이군요.

다음은 3번째 줄 코드인데요. 두번째 파라미터인 lname로 "PG_##lname"란 코드를 만듭니다.
3 test_bit(PG_##lname, &policy(page, 0)->flags);

자 그럼 이제 다시 구현부로 돌아갈게요. 이 PAGEFLAG란 매크로에 전달된 파라미터가 어느 과정으로 TESTPAGEFLAG 까지 전달됐는지 점검해야겠죠?
PAGEFLAG(Reclaim, reclaim, PF_NO_TAIL)

PAGEFLAG 매크로에 입력한 각각 파라미터들을 위 매크로 입력에 대입하면 다음과 같습니다.
uname: Reclaim
lname: reclaim
policy: PF_NO_TAIL

그럼 이해를 돕기 위해 TESTPAGEFLAG 부터 각각 매크로 입력 코드를 위 파라미터로 바꿔서 표현하면 다음과 같습니다.
    #define TESTPAGEFLAG(uname, lname, policy)                              \
-> #define TESTPAGEFLAG(Reclaim, reclaim, PF_NO_TAIL)                              \ 
    static __always_inline int Page##uname(struct page *page)               \
         { return test_bit(PG_##lname, &policy(page, 0)->flags); }
-> static __always_inline int PageReclaim(struct page *page)               \
         { return test_bit(PG_reclaim, &policy(page, 0)->flags); }

이렇게 C 코드를 전처리하는 과정에서 매크로를 인라인 함수 형태로 치환하니 C 코드에서 PageReclaim, ClearPageReclaim 함수를 볼 수 없었던 겁니다.          

그런데 ##uname을 입력으로 PageReclaim, ClearPageReclaim 코드를 생성하는 과정이 좀 낯설 수도 있는데요. 
##을 이용하면, Define으로 선언된 매크로에 파라미터로 Argument로 전달할 수 있습니다.

그럼 다음과 같은 규칙으로 전역 변수를 생성하는 매크로를 생성했다고 가정할게요.
#define CRASH_STATUS_OUT (CURRENT, PREVIOUS) \
int raspberry_crash_##CURRENT; \
int raspberry_crash_##PREVIOUS; \

CRASH_STATUS_OUT(now, prev)

위 코드를 컴파일 하면 전처리 과정에서 다음과 같은 전역 변수를 생성합니다. 물론 이 전역 변수는 C 코드 상으로 존재하진 않죠.
int raspberry_crash_now;
int raspberry_crash_prev;

그럼 다음과 같이 코드를 수정한 후 컴파일을 해볼게요.
다음 패치를 참고해서 코드를 입력하세요. 혹시 오타로 컴파일 에러를 만나면 에러 메시지를 차근 차근 읽으면서 수정하시길 바래요.
diff --git a/mm/filemap.c b/mm/filemap.c
index edfb90e..da9d40a 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -47,6 +47,12 @@

 #include <asm/mman.h>

+#define CRASH_STATUS_OUT(CURRENT, PREVIOUS) \
+int raspberry_crash_##CURRENT; \
+int raspberry_crash_##PREVIOUS;
+
+CRASH_STATUS_OUT(now, prev)
+
*
  * Shared mappings implemented 30.11.1994. It's not fully working yet,
  * though.

역시 전처리 파일을 열어 보면 다음과 같이 int raspberry_crash_now; int raspberry_crash_prev 변수가 생성된 것을 확인할 수 있습니다. 위 전처리 코드가 생성되면서 전역 변수가 생성되는거죠.
int raspberry_crash_now; int raspberry_crash_prev;
44293 # 119 "/home001/austindh.kim/src/raspberry_kernel/linux/mm/filemap.c"
44294 static int page_cache_tree_insert(struct address_space *mapping,

이 기능 좀 신기하지 않나요? 리눅스 커널에는 이런 기법으로 함수나 변수를 생성하는 경우가 종종 있습니다.
그래서 C 소스 코드를 분석하다가 조금 막히는 부분이 있으면 해당 파일에 대응하는 전처리 파일을 열어서 보세요.
앞으로 각 장에서 이런 구문으로 함수나 전역 변수를 생성하는 예제 코드를 만나면 이 내용을 떠올려 주세요.

이런 기능을 리눅스 커널에서 만들었다고 생각할 수도 있는데요.
사실 ## 매크로 기능 C 언어와 컴파일러가 이미 제공하는 기능입니다.
이미 예전부터 RTOS 기반 드라이버 코드에서 매우 자주 활용하는 기법입니다.

참 이거 리눅스 커널 코드 읽는게 쉽지는 않네요.


arm instruction(명령어) - ldr [ARM] Instruction

그럼 ldr 명령어의 정의에 대해서 같이 배워볼까요? LDR 명령어는 메모리에서 워드를 레지스터로 읽어 드리는 동작입니다. 자 그럼 아래 명령어를 예를 들어 같이 볼까요? 참고로 R1은 0xD2FB0000라고 하겠습니다.
ldr r0, [r1]

그런데 0xD2FB0000메모리 주소에는 00000001란 값이 있다고 가정할께요.
메모리주소        값
NSD:D2FB0000|>00000001 C50F6000 00000004 40400040

“ldr r0, [r1]” 명령어가 수행되면 r0은 0000000으로 업데이트 됩니다. r1(0xD2FB0000)이 갖고 있는 메모리 값을 r0에 로딩하는 동작이죠. 그럼 아래와 같은 명령어가 실행되면 어떻게 업데이트 될까요?
ldr r0, [r1,#0x4]

r1에서 0x4만큼 더한 주소는 0xD2FB0004입니다. 그런데 이 메모리 공간에 0xC50F6000 값이 있습니다. 따라서 r0은 0xC50F6000으로 업데이트 됩니다.
r0 = 0xC50F6000 = *(0xD2FB0000+0x4) = *(r1+0x4)

그런데 ldr 명령어를 이런 방식으로 익히면 바로 머리 속에서 사라질 확률이 높습니다. ldr 명령어는 반드시 어셈블리 코드에 대응하는 C 코드를 함께 보면서 익혀야 오래 남거든요.
그럼 한 걸음 더 들어가서 ldr 명령어를 배워볼까요?우선 ldr이란 명령어를 만나면 C코드로 2가지 패턴을 그리면 좀 더 이해가 빠릅니다. 그럼 첫 번째 유형부터 살펴볼까요?


ldr r0, [주소]
아래와 같은 유형의 ldr 명령어를 보면 전역 변수에 접근하고 있다고 보면 됩니다.
ldr r0, [주소]

그럼 C코드와 어셈블리 코드를 함께 살펴볼까요? 이전 장에서 다룬 do_DataAbort 함수의 4번째 줄을 볼게요.
1  NSR:C010036C|do_DataAbort:     push    {r4-r8,r14}
2  NSR:C0100370|                  cpy     r5,r1            ; r5,fsr
3  NSR:C0100374|                  and     r12,r5,#0x400    ; r12,r5,#1024
4  NSR:C0100378|                  ldr     r1,0xC01004D0

4번째 줄 코드를 보면 "ldr r1,0xC01004D0"란 명령어가 보이죠? 이런 명령어가 전역 변수에 접근합니다. 그럼 정말로 이 명령어가 전역 변수에 접근하는지 살펴볼까요?
0xC01004D0 메모리에 어떤 값이 있는지 확인하면 0xC1A195A4이 있네요.
메모리 주소   값
NSD:C01004D0|>C1A195A4 C1B8BAD4 C1001E24 C14517E4 

"sym 0xC1A195A4" 크래시 유틸리티 명령어로 0xC1A195A4 주소가 어떤 심볼인지 확인하니 fsr_info란  전역 변수라고 출력하네요.
crash> sym 0xC1A195A4
c1a195a4 (d) fsr_info

다음 크래시 유틸리티 명령어로 fsr_info 전역 변수가 위치한 메모리를 확인해도 같은 정보(0xc1a195a4)를 확인을 할 수 있습니다.
crash> p &fsr_info
$1 = (struct fsr_info (*)[32]) 0xc1a195a4 <fsr_info>

그럼 이제 실제 do_DataAbort 함수의 C코드 구현부를 볼까요? 4번째 줄 코드를 보면 "fsr_info" 란 전역 변수에 접근하고 있죠.
1 asmlinkage void __exception
2 do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
3 {
4 const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
5 struct siginfo info;

ldr    r0, [r1, 오프셋] 
ldr 명령어를 아래 형식으로 쓰면, 포인터 연산을 하고 있는 경우가 많습니다.
ldr    r0, [r1, 오프셋]

가령 아래와 같이 어떤 포인터 멤버 변수에 접근하는 코드죠. current는 현재 구동 중인 프로세스의 태스크 디스크립터의 포인터를 가져오는데요. 구조체는 struct task_struct입니다.  struct task_struct 구조체 멤버 중에 stack이 있는데요. 프로세스에 할당된 스택 주소를 담고 있습니다.
void *stack_ptr = current->stack;

그럼 실제 리눅스 커널 코드를 열어보면서 살펴볼게요. 그럼 잠깐 아래 코드를 볼까요?
1 NSR:C015BFEC|put_prev_entity:  push    {r4-r11,r14}
2 NSR:C015BFF0|                  cpy     r5,r0            ; r5,cfs_rq
3 NSR:C015BFF4|                  ldr     r3,[r1,#0x1C]

첫번째와 두 번째 줄 코드를 볼까요? 스택에 r4, r5, r6, r7, r8, r9, r10, r11, r14 레지스터를 푸시하고 r0 레지스터를 r5에 저장합니다. 참고로 r0는 함수에 전달되는 파라미터를 저장합니다. 
이번에는 세번째 줄 코드를 볼까요. 드디어 ldr이란 명령어가 보이네요.
3 NSR:C015BFF4|                  ldr     r3,[r1,#0x1C]

이 명령어는 r1에서 0x1c만큼 떨어진 메모리에 있는 값을 r3에 로딩하는 동작입니다. 이를 이해하기 쉽게 수식으로 표현하면 다음과 같아요.
r3 = *(r1+0x1c)

자 여기서 잠깐, ARM 함수 호출 규약에서 파라미터를 어떻게 처리하는지 잠깐 알아볼 필요가 있어요.  
함수에 전달하는 파라미터는 r0에서 r5에 저장해서 전달합니다. 그럼 아래 C 코드를 함께 보면서 살펴볼까요?
1 static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)
2 {
3 if (prev->on_rq)
4 update_curr(cfs_rq); 

위 코드 첫번 째 줄 함수 선언부를 보면 put_prev_entity 함수는 2개 파라미터를 받아 처리합니다.  각각 파라미터의 구조체는 다음과 같죠.
첫번째 파라미터: struct cfs_rq *cfs_rq 
두번째 파라미터: struct sched_entity *prev

그런데 put_prev_entity 함수가 호출되는 순간에는 위 파라미터들은 다음 레지스터로 전달됩니다. r0은 struct cfs_rq *cfs_rq, r1는 struct sched_entity *prev 구조체 포인터 변수를 담고 있습니다.
r0: struct cfs_rq *cfs_rq 
r1: struct sched_entity *prev

그럼 원래 봤던 코드로 돌아가면, 아래 함수 두 번째 줄 코드는 r0(struct cfs_rq *cfs_rq 구조체)를 r5에 저장하고, 세번째 코드의 r1는 struct sched_entity *prev 구조체라는 걸 알 수 있죠.
1 NSR:C015BFEC|put_prev_entity:  push    {r4-r11,r14}
2 NSR:C015BFF0|                  cpy     r5,r0            ; r5,cfs_rq
3 NSR:C015BFF4|                  ldr     r3,[r1,#0x1C]

그럼 여기서 한 가지 의문이 생겼습니다. 왜 "ldr r3,[r1,#0x1C]" 명령어에서  r1에서 0x1C만큼 떨어진 메모리 주소에 접근할까요?.
[r1,#0x1C]

그 이유는 struct sched_entity 구조체 내 on_rq 멤버가 0x1c 오프셋에 위치해 있기 때문입니다. 
조금 더 쉽게 설명드리면, struct sched_entity->on_rq에 접근하기 위해서죠. 이 정보는 아래 크래시 유틸리티 명령어로 확인할 수 있습니다. 다음 5번과 10번째 줄 메시지를 눈여겨 보세요.
crash> struct -o  sched_entity
1 struct sched_entity {
2   [0x0] struct load_weight load;
3    [0x8] struct rb_node run_node;
4   [0x14] struct list_head group_node;
 [0x1c] unsigned int on_rq;
6   [0x20] u64 exec_start;
7   [0x28] u64 sum_exec_runtime;

8 crash> struct -o  sched_entity.on_rq
9 struct sched_entity {
10   [0x1c] unsigned int on_rq;
11 }

그럼 만약 struct sched_entity->exec_start 멤버에 접근하면 ldr 명령어는 어떻게 될까요? 
struct sched_entity 구조체 내 exec_start 멤버가 0x20에 위치하니까, 다음 명령어가 쓰이겠죠.
ldr     r3,[r1,#0x20]

이렇게 ARM 어셈블리 명령어 중 ldr를 만나면 C 코드로 어떻게 구현됐는지 머리 속으로 그리면서 분석하면 조금 더 오랫동안 이 명령어가 머리 속에 남습니다. 이 점 참고하시고요.
ldr이란 명령어는 어느 함수나 볼 수 있습니다. 그래서 이 명령어를 제대로 이해 못하면 어셈블리 코드 자체를 볼 수 없습니다. 그러니 조금 낯설더라도 꾸준히 이 명령어를 익히시길 바래요.


arm instruction(명령어) - push & 스택 푸쉬 [ARM] Instruction

push & 스택 푸쉬
리눅스 커널 함수를 어셈블리 코드로 열어보면 바로 push란 명령어가 눈에 보입니다. 그럼 정말 맞는지 샘플 코드를 볼까요? 아래 코드는 리눅스 커널 핵심 함수입니다. 각 함수에서 가장 먼저 실행되는 명령어가 push죠.
NSR:C0FF413C|__schedule:   push    {r4-r11,r14}
NSR:C0FF4140|              add     r11,r13,#0x20    ; r11,r13,#32
NSR:C0FF4144|              ldr     r3,0xC0FF4948

NSR:C017B0C4|handle_irq_event_percpu:  push    {r0-r2,r4-r11,r14}
NSR:C017B0C8|                          cpy     r3,r13           ; r3,sp
NSR:C017B0CC|                          ldr     r6,[r0,#0x4]

그럼 이 push란 명령어가 실행되면 어떤 동작을 할까요? 당연히 스택에 레지스터를 저장하죠. 이 때 스택 주소가 업데이트 됩니다. 그런데 이 동작은 ARM 프로세스의 함수 호출 규약과 연관돼 있습니다. 그래서 두 코드 “push {r4-r11,r14}”, “push {r0-r2,r4-r11,r14}”가 실행되면 스택 메모리에 정확히 어떤 값들이 업데이트되는지 확인이 필요합니다.

자 이제 시작하기 전에 한 가지 조건을 말씀드릴께요. 여기서 각각 레지스터가 담고 있는 값들은 레지스터 번호와 같다고 가정할께요. 실제 코어 덤프에선 이런 패턴의 레지스터를 볼 수는 없는데요. 좀 더 이해를 돕기 위해서 이렇게 레지스터 번호와 레지스터가 담고 있는 값을 통일 시켰습니다.
r0: 0, r1: 1, r2: 2, r3: 3, r4: 4, r5: 5, r6: 6, … ,r14: start_kernel, 스택 주소: 0xD000D000

push {r4-r11,r14}
아래 함수가 실행되기 전에 스택 주소는 0xD000D000입니다.
NSR:C0FF413C|__schedule:   push    {r4-r11,r14}

"push {r4-r11,r14}" 명령어가 실행되기 전에 0xD000D000 스택주소 근처 메모리 들은 아래 값을 갖고 있습니다.
메모리주소       값
1 NSD:D000CFD8|0x0
2 NSD:D000CFDC|0x0   
3 NSD:D000CFE0|0x0 
4 NSD:D000CFE4|0x0    
5 NSD:D000CFE8|0x0  
6 NSD:D000CFEC|0x0   
7 NSD:D000CFF0|0x0     
8 NSD:D000CFF4|0x0     
9 NSD:D000CFF8|0x0      
10 NSD:D000CFFC|0x0
11 NSD:D000D000|0x0  // <<-- 스택주소

"push {r4-r11,r14}" 명령어가 실행되면 2가지 동작을 한번에 수행합니다. 
1. 스택 주소가 업데이트 됩니다. 스택에 푸쉬되는 레지스터 갯수만큼이죠. {r4-r11,r14} 명령어의 의미는 {r4, r5, r6, r7, r8, r9, r10, r11, r14} 인데요. 레지스터 갯수가 9개이고 32 비트 아키텍처이므로 4 x 9 = 36, 즉 36 바이트만큼 스택 공간을 확보합니다.
0xD000D000 - 0x24(36: 10진수) = 0xD000CFDC, // 10진수 36은 16진수로는 0x24 값이죠.

0x24 바이트 만큼 스택 공간 확보 후 새로운 스택 주소는  0xD000CFDC 가 됐습니다.

2. 레지스터 스택 푸쉬 
이전 동작에서 0x24 크기만큼 스택 공간을 확보했습니다. 이제는 레지스터들을 스택에 푸쉬할 차례입니다. {r4, r5, r6, r7, r8, r9, r10, r11, r14} 9개 레지스터들을 스택에 저장합니다.
메모리 주소     값
12 NSD:D000CFD8|0x0
13 NSD:D000CFDC|0x4   // <<-- 스택주소 
14 NSD:D000CFE0|0x5 
15 NSD:D000CFE4|0x6    
16 NSD:D000CFE8|0x7   
17 NSD:D000CFEC|0x8   
18 NSD:D000CFF0|0x9     
19 NSD:D000CFF4|0x10     
20 NSD:D000CFF8|0x11      
21 NSD:D000CFFC|0xC19008D4 \\vmlinux\init/main\start_kernel
22 NSD:D000D000|0x0

21번째줄 덤프에서 r14을 저장하고, 0xD000CFDC 부터 0xD000CFF8 메모리 주소에 레지스터를 저장합니다. 0xD000CFDC 메모리에는 r4,  0xD000CFE0 메모리에는 r5가 푸시된 거죠. 쭉 이런 방식으로 0xD000CFF8 메모리에는 r11을 저장합니다. 메모리 주소에 어떤 순서로 레지스터를 푸시하는지잘 기억하세요.

이번에는 handle_irq_event_percpu 함수 내 “push {r0-r2,r4-r11,r14}” 명령어가 수행되면 어떤 동작을 하는지 알아볼까요?


push {r0-r2,r4-r11,r14}
역시 이번에도 각각 레지스터가 담고 있는 값들은 레지스터 번호와 같다고 가정할게요. 
r0: 0, r1: 1, r2: 2, r3: 3, r4: 4, r5: 5, r6: 6, … ,r14: start_kernel, 스택 주소: 0xD000D000 

아래 함수가 실행되기 전에 스택 주소는 0xD000D000입니다.
NSR:C017B0C4|handle_irq_event_percpu:  push    {r0-r2,r4-r11,r14}

push 명령어가 실행하기 전 0xD000D000 스택주소 근처 메모리 들은 아래와 값을 갖고 있습니다.
메모리주소           값
1  NSD:D000CFD0|0x0
2  NSD:D000CFD4|0x0
3  NSD:D000CFD8|0x0
4  NSD:D000CFDC|0x0
5  NSD:D000CFE0|0x0
6  NSD:D000CFE4|0x0
7  NSD:D000CFE8|0x0
8  NSD:D000CFEC|0x0
9  NSD:D000CFF0|0x0
10 NSD:D000CFF4|0x0
11 NSD:D000CFF8|0x0
12 NSD:D000CFFC|0x0
13 NSD:D000D000|0x0
14 NSD:D000CFEC|0x0   
15 NSD:D000CFF0|0x0     
16 NSD:D000CFF4|0x0     
17 NSD:D000CFF8|0x0      
18 NSD:D000CFFC|0x0
19 NSD:D000D000(스택주소)|0x0

"push {r0-r2,r4-r11,r14}" 명령어가 실행되면 이번에도 다음과 같이 2가지 동작을 한번에 수행합니다. 

1. 스택 주소가 업데이트 됩니다. 스택에 푸쉬되는 레지스터 갯수만큼이죠. 
{r4-r11,r14} 명령어의 의미는 {r0, r1, r2, r4, r5, r6, r7, r8, r9, r10, r11, r14} 인데요. 
레지스터 갯수가 12개이고 32 비트 아키텍처이므로 4 x 12 = 48, 즉 48 바이트만큼 스택 공간을 확보합니다.
0xD000D000 - 0x30(48: 10진수) = 0xD000CFDC, // 10진수 48은 16진수로는 0x24 값이죠.

0x30 바이트 만큼 스택 공간 확보 후 새로운 스택 주소는   0xD000CFDC 가 됐습니다.


2. 레지스터 스택 푸쉬 
0x30 크기만큼 스택 공간을 확보했습니다. 이제는 레지스터들을 스택에 푸쉬할 차례입니다. 
{r0, r1, r2, r4, r5, r6, r7, r8, r9, r10, r11, r14} 12 개 레지스터들을 스택에 저장합니다.
메모리주소     값
NSD:D000CFD0(새로운 스택)|0x0   
NSD:D000CFD4|0x1        
NSD:D000CFD8|0x2       
NSD:D000CFDC|0x4      
NSD:D000CFE0|0x5        
NSD:D000CFE4|0x6      
NSD:D000CFE8|0x7   
NSD:D000CFEC|0x8      
NSD:D000CFF0|0x9     
NSD:D000CFF4|0x10     
NSD:D000CFF8|0x11      
NSD:D000CFFC|0xC19008D4 \\vmlinux\init/main\start_kernel
NSD:D000D000|0x0

push란 어셈블리 명령어에 대해서 배웠습니다. 실제 메모리에 push 명령어가 실행되면 어떤 값들이 저장되는지 알아봤구요. 다음에는 ldr이란 명령어를 살펴볼께요.

.viminfo 설정 파일 dev utility

   1 " All system-wide defaults are set in $VIMRUNTIME/debian.vim (usually just
  2 " /usr/share/vim/vimcurrent/debian.vim) and sourced by the call to :runtime
  3 " you can find below.  If you wish to change any of those settings, you should
  4 " do it in this file (/etc/vim/vimrc), since debian.vim will be overwritten
  5 " everytime an upgrade of the vim packages is performed.  It is recommended to
  6 " make changes after sourcing debian.vim since it alters the value of the
  7 " 'compatible' option.
  8
  9 " This line should not be removed as it ensures that various options are
 10 " properly set to work with the Vim-related packages available in Debian.
 11 runtime! debian.vim
 12
 13 " Uncomment the next line to make Vim more Vi-compatible
 14 " NOTE: debian.vim sets 'nocompatible'.  Setting 'compatible' changes numerous
 15 " options, so any other options should be set AFTER setting 'compatible'.
 16 "set compatible
 17 set nocompatible
 18
 19 " Vim5 and later versions support syntax highlighting. Uncommenting the next
 20 " line enables syntax highlighting by default.
 21 syntax on
 22
 23 " If using a dark background within the editing area and syntax highlighting
 24 " turn on this option as well
 25 "set background=dark
 26
 27 " Uncomment the following to have Vim jump to the last position when
 28 " reopening a file
 29 if has("autocmd")
 30   au BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$")
 31     \| exe "normal! g'\"" | endif
 32 endif
 33
 34 " Uncomment the following to have Vim load indentation rules according to the
 35 " detected filetype. Per default Debian Vim only load filetype specific
 36 " plugins.
 37 if has("autocmd")
 38   filetype indent on
 39 endif
 40
 41 " The following are commented out as they cause vim to behave a lot
 42 " differently from regular Vi. They are highly recommended though.
 43 set showcmd             " Show (partial) command in status line.
 44 set showmatch           " Show matching brackets.
 45 set ignorecase          " Do case insensitive matching
 46 set smartcase           " Do smart case matching
 47 set incsearch           " Incremental search
 48 set autowrite           " Automatically save before commands like :next and :make
 49 set hidden             " Hide buffers when they are abandoned
 50 "set mouse=a            " Enable mouse usage (all modes) in terminals
 51
 52 " Source a global configuration file if available
 53 " XXX Deprecated, please move your changes here in /etc/vim/vimrc
 54 if filereadable("/etc/vim/vimrc.local")
 55   source /etc/vim/vimrc.local
 56 endif
 57
 58 " Add by colike -- start
 59 colorscheme torte
 60 set bg=dark
 61 set statusline=%<%1*===\ %5*%f%1*%(\ ===\ %4*%h%1*%)%(\ ===\ %4*%m%1*%)%(\ ===\ %4*%r%1*%)\ ===%====\ %2*%b(0x%B)%1*\ ===\ %3*%l,%c%V%1*\ ===\ %5*%P%1*\ ===%0* laststatus=2
 62
 63 set tabstop=8
 64 set shiftwidth=8
 65 "set expandtab
 66 set nu
 67 set nowrap
 68 set hlsearch
 69
 70 set tags=tags,../tags,../../tags,../../../tags,../../../../tags,../../../../../tags,../../../../../../tags
 71
 72 " set tags+=/home001/austin.kim/src/mh_n_mr1_b_src/android/kernel/c_tags
 73 " set tags+=/home001/austin.kim/src/mh_n_mr1_b_src/android/kernel/asm_tags
 74
 75 " mapping function key
 76 map <F10> :NERDTree <CR>
 77 map <F11> :BufExplorer <CR>
 78


[race][PATCH 4.15 20/64] RDMA/uverbs: Fix circular locking dependency [Linux][Kernel] LKML

commit 1ff5325c3ca1843228a86549318bbd3b414b9207 upstream.

Avoid circular locking dependency by calling
to uobj_alloc_commit() outside of xrcd_tree_mutex lock.

======================================================
WARNING: possible circular locking dependency detected
4.15.0+ #87 Not tainted
------------------------------------------------------
syzkaller401056/269 is trying to acquire lock:
 (&uverbs_dev->xrcd_tree_mutex){+.+.}, at: [<000000006c12d2cd>] uverbs_free_xrcd+0xd2/0x360

but task is already holding lock:
 (&ucontext->uobjects_lock){+.+.}, at: [<00000000da010f09>] uverbs_cleanup_ucontext+0x168/0x730

which lock already depends on the new lock.

the existing dependency chain (in reverse order) is:

-> #1 (&ucontext->uobjects_lock){+.+.}:
       __mutex_lock+0x111/0x1720
       rdma_alloc_commit_uobject+0x22c/0x600
       ib_uverbs_open_xrcd+0x61a/0xdd0
       ib_uverbs_write+0x7f9/0xef0
       __vfs_write+0x10d/0x700
       vfs_write+0x1b0/0x550
       SyS_write+0xc7/0x1a0
       entry_SYSCALL_64_fastpath+0x1e/0x8b

/////////////////////////////////////////////////
       __mutex_lock+0x111/0x1720
//    uverbs_uobject_add
//    uobj->type->type_class->alloc_commit(uobj); ->  alloc_commit_idr_uobject -> uverbs_uobject_add(uobj);      
       rdma_alloc_commit_uobject+0x22c/0x600
//     uobj_alloc_commit(&obj->uobject); -> rdma_alloc_commit_uobject(uobj);       
       ib_uverbs_open_xrcd+0x61a/0xdd0
       ib_uverbs_write+0x7f9/0xef0
       __vfs_write+0x10d/0x700
       vfs_write+0x1b0/0x550
       SyS_write+0xc7/0x1a0
       entry_SYSCALL_64_fastpath+0x1e/0x8b
/////////////////////////////////////////////////

-> #0 (&uverbs_dev->xrcd_tree_mutex){+.+.}:
       lock_acquire+0x19d/0x440
       __mutex_lock+0x111/0x1720
       uverbs_free_xrcd+0xd2/0x360
       remove_commit_idr_uobject+0x6d/0x110
       uverbs_cleanup_ucontext+0x2f0/0x730
       ib_uverbs_cleanup_ucontext.constprop.3+0x52/0x120
       ib_uverbs_close+0xf2/0x570
       __fput+0x2cd/0x8d0
       task_work_run+0xec/0x1d0
       do_exit+0x6a1/0x1520
       do_group_exit+0xe8/0x380
       SyS_exit_group+0x1e/0x20
       entry_SYSCALL_64_fastpath+0x1e/0x8b

other info that might help us debug this:

 Possible unsafe locking scenario:

       CPU0                    CPU1
       ----                    ----
  lock(&ucontext->uobjects_lock);
                               lock(&uverbs_dev->xrcd_tree_mutex);
                               lock(&ucontext->uobjects_lock);
  lock(&uverbs_dev->xrcd_tree_mutex);

 *** DEADLOCK ***

3 locks held by syzkaller401056/269:
 #0:  (&file->cleanup_mutex){+.+.}, at: [<00000000c9f0c252>] ib_uverbs_close+0xac/0x570
 #1:  (&ucontext->cleanup_rwsem){++++}, at: [<00000000b6994d49>] uverbs_cleanup_ucontext+0xf6/0x730
 #2:  (&ucontext->uobjects_lock){+.+.}, at: [<00000000da010f09>] uverbs_cleanup_ucontext+0x168/0x730

stack backtrace:
CPU: 0 PID: 269 Comm: syzkaller401056 Not tainted 4.15.0+ #87
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.7.5-0-ge51488c-20140602_164612-nilsson.home.kraxel.org 04/01/2014
Call Trace:
 dump_stack+0xde/0x164
 ? dma_virt_map_sg+0x22c/0x22c
 ? uverbs_cleanup_ucontext+0x168/0x730
 ? console_unlock+0x502/0xbd0
 print_circular_bug.isra.24+0x35e/0x396
 ? print_circular_bug_header+0x12e/0x12e
 ? find_usage_backwards+0x30/0x30
 ? entry_SYSCALL_64_fastpath+0x1e/0x8b
 validate_chain.isra.28+0x25d1/0x40c0
 ? check_usage+0xb70/0xb70
 ? graph_lock+0x160/0x160
 ? find_usage_backwards+0x30/0x30
 ? cyc2ns_read_end+0x10/0x10
 ? print_irqtrace_events+0x280/0x280
 ? __lock_acquire+0x93d/0x1630
 __lock_acquire+0x93d/0x1630
 lock_acquire+0x19d/0x440
 ? uverbs_free_xrcd+0xd2/0x360
 __mutex_lock+0x111/0x1720
 ? uverbs_free_xrcd+0xd2/0x360
 ? uverbs_free_xrcd+0xd2/0x360
 ? __mutex_lock+0x828/0x1720
 ? mutex_lock_io_nested+0x1550/0x1550
 ? uverbs_cleanup_ucontext+0x168/0x730
 ? __lock_acquire+0x9a9/0x1630
 ? mutex_lock_io_nested+0x1550/0x1550
 ? uverbs_cleanup_ucontext+0xf6/0x730
 ? lock_contended+0x11a0/0x11a0
 ? uverbs_free_xrcd+0xd2/0x360
 uverbs_free_xrcd+0xd2/0x360
 remove_commit_idr_uobject+0x6d/0x110
 uverbs_cleanup_ucontext+0x2f0/0x730
 ? sched_clock_cpu+0x18/0x200
 ? uverbs_close_fd+0x1c0/0x1c0
 ib_uverbs_cleanup_ucontext.constprop.3+0x52/0x120
 ib_uverbs_close+0xf2/0x570
 ? ib_uverbs_remove_one+0xb50/0xb50
 ? ib_uverbs_remove_one+0xb50/0xb50
 __fput+0x2cd/0x8d0
 task_work_run+0xec/0x1d0
 do_exit+0x6a1/0x1520
 ? fsnotify_first_mark+0x220/0x220
 ? exit_notify+0x9f0/0x9f0
 ? entry_SYSCALL_64_fastpath+0x5/0x8b
 ? entry_SYSCALL_64_fastpath+0x5/0x8b
 ? trace_hardirqs_on_thunk+0x1a/0x1c
 ? time_hardirqs_on+0x27/0x670
 ? time_hardirqs_off+0x27/0x490
 ? syscall_return_slowpath+0x6c/0x460
 ? entry_SYSCALL_64_fastpath+0x5/0x8b
 do_group_exit+0xe8/0x380
 SyS_exit_group+0x1e/0x20
 entry_SYSCALL_64_fastpath+0x1e/0x8b
RIP: 0033:0x431ce9

Cc: syzkaller <syzkaller@googlegroups.com>
Cc: <stable@vger.kernel.org> # 4.11
Fixes: fd3c7904db6e ("IB/core: Change idr objects to use the new schema")
Reported-by: Noa Osherovich <noaos@mellanox.com>
Signed-off-by: Leon Romanovsky <leonro@mellanox.com>
Signed-off-by: Jason Gunthorpe <jgg@mellanox.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

---
 drivers/infiniband/core/uverbs_cmd.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

--- a/drivers/infiniband/core/uverbs_cmd.c
+++ b/drivers/infiniband/core/uverbs_cmd.c
@@ -560,9 +560,10 @@ ssize_t ib_uverbs_open_xrcd(struct ib_uv
        if (f.file)
                fdput(f);

+       mutex_unlock(&file->device->xrcd_tree_mutex);
+
        uobj_alloc_commit(&obj->uobject);

-       mutex_unlock(&file->device->xrcd_tree_mutex);
        return in_len;

 err_copy:

[라즈베리파이][리눅스커널]인터럽트 컨택스트란 (1) #CS 라즈베리_리눅스커널_인터럽트

인터럽트 컨택스트란
인터럽트 컨택스트란 용어가 좀 낯설진 않나요? 그래서 인터럽트 컨택스트를 배우기 전에 우선 컨택스트란 용어를 배울 필요가 있습니다. 

혹시 컨텍스트 스위칭이란 단어 들어보신 적이 있나요? 리눅스 커널에서 많이 쓰는 용어인데요. 어렵게 설명하면 컨텍스트란 현재 실행하고 있는 레지스터 묶음을 의미합니다. ARM 프로세스에서는 15개의 레지스터(r0부터 pc)가 있다고 알고 있죠? 이 레지스터 중에 현재 실행 중인 코드 주소를 담고 있는 pc(Program Counter)란 레지스터가 있는데요. 만약 현재 커널이 start_kernel이란 함수를 실행하고 있는데 이 함수의 주소가 0xC000D000이면 pc란 레지스터는 0xC000D000 주소를 담고 있습니다. 이렇게 PC 레지스터를 포함한 여러 레지스터 세트로 현재 실행 중인 프로그램 상태를 저장할 수 있어요.

그럼 커널에서 컨택스트 관련 코드를 잠깐 살펴볼까요? 보통 프로세스가 스케줄링 즉 휴면할 때는 현재 실행 중인 레지스터들을 특정 공간(자신의 스택 Top주소)에 저장합니다. 그 이유는 정해진 시간 후 스케줄러가 이 프로세스를 다시 깨우면 이전에 동작했던 순간부터 실행해야 하거든요. 그럼 프로세스는 다시 실행하기 우선 어떤 동작을 할까요? 프로세스는 휴면할 때 저장했던 레지스터를 로딩합니다. 그 이유는 휴면할 때 저장했던 레지스터가 바로 프로세스 실행 정보를 담고 있기 때문이죠.

그럼 어떤 프로세스가 스케줄링 될 때 현재 실행 중인 레지스터를 어느 자료 구조로 저장할까요? 이렇게 컨택스트 즉 현재 실행 중인 레지스터 정보를 표현하는 자료구조는 struct thread_info.cpu_context입니다. struct thread_info 구조체 선언 부에 cpu_context란 멤버가 보이죠? 변수 이름도 context입니다.
struct thread_info {
unsigned long flags; /* low level flags */
// .. 생략 ..
struct cpu_context_save cpu_context; /* cpu context */

이 멤버 선언 부를 눈여겨 보면 역시나 레지스터값들입니다. 이 멤버 변수에 현재 동작 중인 프로세스의 레지스터를 저장합니다.
struct cpu_context_save {
__u32 r4;
__u32 r5;
__u32 r6;
__u32 r7;
__u32 r8;
__u32 r9;
__u32 sl;
__u32 fp;
__u32 sp;
__u32 pc;
__u32 extra[2]; /* Xscale 'acc' register, etc */
};

그럼 쉽게 설명해서 컨택스트란 뭘까요? 정답은 “실행 중” 이란 뜻입니다. 레지스터 세트로 현재 실행 중인 상태를 저장하기 때문이죠. 

이제 컨택스트란 용어의 의미가 조금 덜 낯설지 않나요? 그럼 이제 되돌아가서 인터럽트 컨택스트의 의미를 짚어 보겠습니다. 그럼 인터럽트 컨택스트란 용어는 무슨 뜻일까요? “인터럽트 처리 중”이란 뜻입니다. 인터럽트가 발생하면 인터럽트 벡터 주소부터 인터럽트 핸들러까지 코드 흐름으로 인터럽트를 처리합니다. 다르게 말하자면 인터럽트 컨택스트란 이 코드 흐름 중 하나라고 볼 수 있습니다.

흠, 그래도 인터럽트 컨택스트란 의미는 좀 파악하기 어렵죠? 커널을 이론으로 아무리 자세하게 읽어도 이해가 안 되고 결국 “머릿속 지우개처럼” 머릿속에 잘 남지 않는 것 같습니다. 그렇다고 실망하지 마세요. 우리에겐 커널 코드와 ftrace란 강력한 도구가 있거든요. 이 로그들을 열어서 실제 어느 로그가 인터럽트 컨택스트인지 알아보면 더 오래 기억할 겁니다. 

그럼, 인터럽트 컨택스트란 용어는 왜 배워야 할까요? 그 이유는 리눅스 커널 전반에 이 용어를 많이 쓰고 이 개념을 적용한 커널 코드가 많기 때문입니다. 그래서 이 공학적인 의미를 이해하지 못하면 다른 코드를 이해하기 어려워요.

ftrace와 커널 로그로 인터럽트 컨택스트 확인하기
리눅스 커널에서 커널 동작을 가장 정밀하게 담고 있는 로그는 뭘까요? 아마 많은 리눅스 전문가들은 ftrace라고 대답할 것입니다.  ftrace는 리눅스 커널에서 제공하는 가장 강력한 디버그 로그입니다. 여러분들도 ftrace 로그를 자주 활용해서 리눅스 커널을 익히기를 바래요. 자 이제 ftrace로그를 분석하면서 인터럽트가 어떻게 처리되는지 확인해볼까요? 

ftrace로 인터럽트를 처리하는 인터럽트 핸들러 함수에 필터를 걸고 콜 스택 로그를 받아 보겠습니다. 아래는 터치 인터럽트 핸들러인 touch_irq_handler 함수에 필터를 걸고 받은 ftrace 로그입니다. 잠깐 아래 ftrace로그를 해석하는 방법부터 살펴볼까요? 뭔가 함수들이 늘어서 있는 것 같은데요. ret_fast_syscall가 가장 먼저 실행되는 함수고 이후 함수들이 계속 호출되는 겁니다.
=> touch_irq_handler
=> handle_irq_event_percpu
=> handle_irq_event
=> handle_edge_irq
=> generic_handle_irq
=> msm_gpio_irq_handler
=> generic_handle_irq
=> __handle_domain_irq
=> gic_handle_irq
=> __irq_svc
=> __slab_alloc
=> kmem_cache_alloc
=> jbd2__journal_start
=> __ext4_journal_start_sb
=> ext4_da_write_begin
=> generic_perform_write
=> __generic_file_write_iter
=> ext4_file_write_iter
=> new_sync_write
=> vfs_write
=> SyS_write
=> ret_fast_syscall

그럼 위 로그를 받기 위해 어떻게 설정해야 할까요. touch_irq_handler 함수로 ftrace 스택 트레이서를 설정하기 위해서 아래와 같이 설정하면 됩니다. 아래 설정 코드에서 가장 중요한 점은 "echo 0 > d/tracing/tracing_on" 로 ftrace로그를 잠깐 끄고 설정을 해야 합니다. "sleep 1" 코드로 시스템에 적절히 슬립도 줘야 하고요. 안 그러면 시스템이 오 동작 할 수 있어요. 
아래 리눅스 커널 메일링 리스트에 비슷한 문제가 있었으니 링크를 참고하세요. 
(https://lkml.org/lkml/2017/8/6/153) 
"echo 5000 > /d/tracing/buffer_size_kb"
"echo function > d/tracing/current_tracer"
"sleep 1"

"echo touch_irq_handler  > d/tracing/set_ftrace_filter"
"sleep 1"

"echo 1 > d/tracing/options/func_stack_trace"
"sleep 1"

"echo 1 > d/tracing"

그럼 다시 차근차근 로그를 분석해 볼까요? ret_fast_syscall 함수부터 __slab_alloc 함수 구간까지 프로세스 컨택스트이고, __irq_svc 함수부터 touch_irq_handler 함수 구간까지가 인터럽트 컨택스트입니다. 즉, 인터럽트가 발생하고 난 다음 서브 루틴 동작 구간을 인터럽트 컨택스트라고 말할 수 있습니다. 그 이외에는 프로세스 컨택스트라고 볼 수 있습니다.

함수 흐름을 보면 터치 인터럽트가 발생해서 메모리 할당하는 동작(__slab_alloc)을 멈추고 인터럽트 벡터가 실행된 후 인터럽트 서브 루틴 (__irq_svc->gic_handle_irq)이 실행됐네요. 이렇게 메모리를 할당하는 중요한 동작에서 인터럽트 발생으로 실행을 멈추는군요. 그래서 인터럽트 서비스 루틴에서는 메모리 할당할 때는 GFP_ATOMIC 옵션을 줘야 합니다.
=> touch_irq_handler
=> handle_irq_event_percpu
=> handle_irq_event
=> handle_edge_irq
=> generic_handle_irq
=> msm_gpio_irq_handler
=> generic_handle_irq
=> __handle_domain_irq
=> gic_handle_irq
=> __irq_svc
=> __slab_alloc
=> kmem_cache_alloc
=> jbd2__journal_start
=> __ext4_journal_start_sb
=> ext4_da_write_begin
=> generic_perform_write
=> __generic_file_write_iter
=> ext4_file_write_iter
=> new_sync_write
=> vfs_write
=> SyS_write
=> ret_fast_syscall

위 로그를 제대로 이해하기 위해 알아야 하는 내용이 조금 많습니다. 이제 천천히 살펴볼까요? 우선 프로세스는 높은 주소에서 낮은 주소 방향으로 스택을 씁니다. 왜 이렇게 쓰냐 구요? 사실 꼭 높은 주소에서 낮은 주소 방향으로 스택을 사용하도록 설정할 필요는 없고 반대로 낮은 주소에서 높은 주소 방향으로 스택을 쓰게 설정할 수 있습니다. 그런데 커널 고수들이 프로세스를 높은 주소에서 낮은 주소 방향으로 설정하다 보니 업계의 사실상 표준이 된 것 같습니다. 

그럼 프로세스가 돌고 있는 스택 공간에 대해서 배워 볼까요? 우선 프로세스가 생성될 때는 커널은 프로세스에게 0x2000만큼 스택 공간을 할당합니다. 구내식당에서 식판 주듯이요. 프로세스 입장에서 스택 공간은 운동장으로 볼 수 있습니다. 프로세스는 스택 메모리 공간 내에서만 실행되기 때문이죠. 함수를 호출하거나 로컬 변수 사용할 때 자신에게 부여된 고유 스택 메모리를 사용합니다. 그럼 프로세스별로 할당된 스택 주소는 어떻게 확인할 수 있나요? 이 내용을 알기 전에 잠깐 태스크 디스크립터에 대해 배워야 합니다. 

여러분 혹시 TCB(Task Control Block)란 용어 들어본 적 있나요? 임베디드 시스템에서 태스크 혹은 프로세스 정보를 담고 있는 자료구조로 알고 있죠. 그럼 리눅스 커널의 프로세스 정보를 담고 있는 자료 구조는 뭘까요? 여기서 임베디드 시스템에서 말하는 태스크와 리눅스 커널의 프로세스는 같은 개념으로 봐야 합니다. 정답은 struct task_struct입니다. 이 자료 구조에서 프로세스의 속성과 상태 정보를 확인할 수 있습니다.

그럼 프로세스별로 할당된 스택 주소는 태스크 디스크립터 멤버 struct task_struct.stack로 확인할 수 있습니다. 그런데 이 주소는 스택 Top 주소입니다. 그럼 프로세스에서 함수들을 실행할 때는 스택 Bottom 주소인 struct task_struct.stack + 0x2000(스택 사이즈)에서 스택 Top 주소인 struct task_struct.stack 방향으로 즉 높은 주소에서 낮은 주소로 스택을 사용합니다. 

이렇게 프로세스가 스택 메모리 공간에서 실행 중 인터럽트가 발생하면 인터럽트 벡터와 인터럽트 서비스 루틴은 어느 공간에서 실행할까요? 정답은 프로세스 스택 공간에서 실행됩니다. 전세 살 듯이 잠시 프로세스 스택 공간을 활용하는 거죠. 조금 더 구체적으로 인터럽트 벡터인 __irq_svc 함수부터 인터럽트 핸들러인 touch_irq_handler 함수까지 현재 실행 중인 프로세스의 스택 공간에서 실행됩니다.

그런데 모든 아키텍처에서 인터럽트 발생 시 프로세스 스택 공간을 써서 인터럽트 서비스 루틴을 실행하지는 않습니다. 참고로 ARM64 아키텍처에서는 인터럽트 전용 스택인 IRQ Stack을 할당해서 인터럽트 벡터부터 이 스택 공간에서 처리되도록 합니다. ARM 32비트 아키텍처인 라즈베리파이는 프로세스가 쓰는 스택 공간을 쓴다는 점 기억하세요.

전 리눅스 커뮤니티에서 여러 리눅스 커널 초고수를 만났는데요. 이분들의 공통점은 어떤 로그이든 많이 상상하고 고민하면서 분석한다는 점입니다. 이렇게 함수 흐름 하나에도 조금만 깊이 생각하면 그 뒷면에 여러 가지 공학적 의미가 있거든요. 여러분도 혹시 고수가 되기 바라면 이런 방식으로 코드나 로그를 보시길 바래요.

이번에는 커널 로그로 인터럽트 컨택스트 구간을 확인하겠습니다. 아래 커널 로그는 __irq_svc(asm)--unwind_backtrace() 함수들은 인터텁트 컨택스트에서 수행되며, start_kernel() --arch_cpu_idle() 함수 구간은 프로세스 컨택스트라고 볼 수 있습니다. 참고로 아래 콜스택은 WARN() 매크로가 수행되면 출력되는 커널 로그인데 보통 콜스택을 찍어줍니다.
[출처: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/commit/?id=bbe097f092b0d13e9736bd2794d0ab24547d0e5d]
WARNING: CPU: 0 PID: 0 at include/linux/usb/gadget.h:405
 ecm_do_notify+0x188/0x1a0
 Modules linked in:
 CPU: 0 PID: 0 Comm: swapper Not tainted 4.7.0+ #15
 Hardware name: Atmel SAMA5
 [<c010ccfc>] (unwind_backtrace) from [<c010a7ec>] (show_stack+0x10/0x14)
 [<c010a7ec>] (show_stack) from [<c0115c10>] (__warn+0xe4/0xfc)
 [<c0115c10>] (__warn) from [<c0115cd8>] (warn_slowpath_null+0x20/0x28)
 [<c0115cd8>] (warn_slowpath_null) from [<c04377ac>] (ecm_do_notify+0x188/0x1a0)
 [<c04377ac>] (ecm_do_notify) from [<c04379a4>] (ecm_set_alt+0x74/0x1ac)
 [<c04379a4>] (ecm_set_alt) from [<c042f74c>] (composite_setup+0xfc0/0x19f8)
 [<c042f74c>] (composite_setup) from [<c04356e8>] (usba_udc_irq+0x8f4/0xd9c)
 [<c04356e8>] (usba_udc_irq) from [<c013ec9c>] (handle_irq_event_percpu+0x9c/0x158)
 [<c013ec9c>] (handle_irq_event_percpu) from [<c013ed80>] (handle_irq_event+0x28/0x3c)
 [<c013ed80>] (handle_irq_event) from [<c01416d4>] (handle_fasteoi_irq+0xa0/0x168)
 [<c01416d4>] (handle_fasteoi_irq) from [<c013e3f8>] (generic_handle_irq+0x24/0x34)
 [<c013e3f8>] (generic_handle_irq) from [<c013e640>] (__handle_domain_irq+0x54/0xa8)
 [<c013e640>] (__handle_domain_irq) from [<c010b214>] (__irq_svc+0x54/0x70)
 [<c010b214>] (__irq_svc) from [<c0107eb0>] (arch_cpu_idle+0x38/0x3c)
 [<c0107eb0>] (arch_cpu_idle) from [<c0137300>] (cpu_startup_entry+0x9c/0xdc)
 [<c0137300>] (cpu_startup_entry) from [<c0900c40>] (start_kernel+0x354/0x360)
 [<c0900c40>] (start_kernel) from [<20008078>] (0x20008078)
 ---[ end trace e7cf9dcebf4815a6 ]---J6

이렇게 어떤 커널 로그이던 위와 패턴은 굉장히 자주 확인할 수 있어요. 위와 같은 콜스택에서 인터럽트 벡터 함수인 __irq_svc 로그가 보이면 “아, 인터럽트가 발생해서 인터럽트를 처리 중이구나”라고 해석하면 됩니다. 

이번엔 인터럽트 컨택스트에 대해서 알아봤습니다. 정리하면 현재 수행 중인 코드가 인터럽트를 처리하는 중이란 의미죠. 그럼 인터럽트 컨택스트를 알려주는 함수가 있을 것 같지 않나요? 

 in_interrupt 함수란
리눅스 커널이 실행할 때 수많은 함수가 각자 서로를 호출합니다. 어떤 함수는 프로세스 컨택스트에서 실행(커널 쓰레드)되는데 대부분 함수는 인터럽트 컨택스트, 즉 인터럽트 처리하는 도중 호출될 수 있습니다. 그럼 현재 코드가 인터럽트를 처리 중이면 더 빨리 처리해야겠죠? 인터럽트 서비스 루틴은 실행 중인 프로세스를 멈추고 동작하므로 인터럽트 컨택스트 조건에서만 신속하게 코드를 실행시키고 싶을 때가 있습니다. 그럼 현재 실행 중인 코드가 인터럽트 컨택스트 구간인지 어떻게 알 수 있을까요? in_interrupt()란 함수가 이 정보를 알려줍니다. 

그럼 다음 패치를 함께 살펴볼까요. 아래 패치는__rh_alloc() 함수가 인터럽트 컨택스트 조건에서 다른 동작을 하도록 작성됐습니다. [1]번 코드를 보면 메모리 설정 플래그를 저장하는 지역 변수 gfp_flag를 GFP_KERNEL로 초기화합니다. 다음 [2]번 코드에서 in_interrupt가 true이면 지금 인터럽트 처리 중이므로 gfp_flag를 GFP_ATOMIC로 설정하고 메모리를 할당합니다.  GFP_ATOMIC옵션으로 빨리 메모리 할당을 시도하는 거죠. 반대로 in_interrupt가 false로 리턴하면 이는 현재 코드가 프로세스 컨택스트에서 수행 중이므로 GFP_KERNEL 옵션으로 메모리를 할당합니다. 참고로 GFP_ATOMIC 옵션으로 메모리를 할당하면 프로세스는 휴면하지 않고 메모리를 할당하고, GFP_KERNEL 옵션인 경우 메모리 할당 실패 시 휴면할 수 있습니다. 

요약하면 인터럽트 처리 도중에는 빨리 메모리를 할당하는 코드입니다.
diff --git a/drivers/md/dm-region-hash.c b/drivers/md/dm-region-hash.c
index b929fd5..1325a8a 100644
--- a/drivers/md/dm-region-hash.c
+++ b/drivers/md/dm-region-hash.c
@@ -289,7 +289,12 @@ static struct dm_region *__rh_alloc(struct dm_region_hash *rh, region_t region)
  {
         struct dm_region *reg, *nreg;
 
 -       nreg = mempool_alloc(rh->region_pool, GFP_ATOMIC);
 +       gfp_t gfp_flag = GFP_KERNEL; // <--  [1]
 +       if (in_interrupt()) {  // <-- [2]
 +               gfp_flag = GFP_ATOMIC;
 +       }
 +       nreg = mempool_alloc(flush_entry_pool, gfp_flag);
 +
         if (unlikely(!nreg))
                 nreg = kmalloc(sizeof(*nreg), GFP_NOIO | __GFP_NOFAIL);

이제 in_interrupt 코드를 살펴볼까요. 아래 코드를 보면 실제 irq_count() 매크로로 매핑되어 preempt_count() 매크로로 처리하는 값과 HARDIRQ_MASK | SOFTIRQ_MASK 비트 마스크와 OR 비트 연산을 수행합니다.
#define in_interrupt()  (irq_count())
    
 #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
      | NMI_MASK))  
 
 SOFTIRQ_MASK: 0xff00, SOFTIRQ_OFFSET: 0x100 
 HARDIRQ_MASK: 0xf0000, HARDIRQ_OFFSET: 0x10000

여기서 HARDIRQ_OFFSET(0x10000)란 매크로는 인터럽트를 처리 중임을 나타내고 HARDIRQ_MASK 매크로는 이 HARDIRQ_OFFSET(0x10000)이란 비트 가져오기 위한 비트 마스크입니다.

만약 어떤 값이 0x210200인데 이 값에 HARDIRQ_MASK(0x10000)와 AND 비트 연산을 실행하면 결괏값은 0x10000이 됩니다.  

이렇게 리눅스 커널에서 여러 동작을 비트 연산으로 처리합니다. 그 이유는 비트 연산자는 실행 속도가 빠르기 때문입니다. 

그럼 preempt_count 코드를 좀 더 살펴볼까요. 아래 함수를 보면 current_thread_info()->preempt_count 값을 리턴하는데 이 코드를 이해하려면 current_thread_info() 코드를 살펴봐야 합니다.
static __always_inline int preempt_count(void)
{
  return current_thread_info()->preempt_count;  
}

 [kernel/arch/arm/include/asm/thread_info.h]
static inline struct thread_info *current_thread_info(void)
{
  register unsigned long sp asm ("sp"); // <-- [1]
  return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); <-- [2]
}

current_thread_info 함수를 눈여겨보면, [1]스택 주소를 sp 지역 변수로 읽어 옵니다. 여기서 참 낯선 코드 “asm(“sp”)”가 보이죠? C 문법에 없는 코드인데요. 리눅스 커널에선 아주 자주 호출되는 코드는 인라인 어셈블리를 씁니다. C 코드에서 어셈블리 코드를 쓰는 거죠. 이 코드도 마찬가지 방식으로 구현됐습니다. 그리고 지역 변수를 register로 선언했는데요. 이는 sp란 지역 변수를 다른 지역 변수를 처리하듯이 스택 공간 말고 ARM 레지스터 공간에서 처리하라는 의미입니다. 그럼 다른 지역 변수와 이렇게 register 타입으로 선언된 지역 변수의 차이점은 뭘까요? 그 이유는 register 타입으로 지역 변수를 설정하면 더 적은 어셈블리 코드로 해당 코드를 수행할 수 있습니다. 결국 이렇게 코드를 짠 이유는 이 current_thread_info 란 인라인 함수가 아주 많이 호출되기 때문입니다. 

다음 [2]코드에선 스택 가장 낮은 주소 계산합니다. 이 주소에 프로세스 실행 정보가 담긴 struct thread_info 구조체의 멤버가 저장돼 있습니다.
[kernel/arch/arm/include/asm/thread_info.h]
static inline struct thread_info *current_thread_info(void)
{
  register unsigned long sp asm ("sp"); // <-- [1]
  return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); <-- [2]
}

정리하면 현재 구동 중인 함수 내에서 확인되는 스택 주소를 통해 스택 상단 Top 주소를 얻어 온 후 struct thread_info 구조체의 preempt_count 멤버에서 얻어오는 값입니다. 

위 그림 상단 낮은 주소(Top주소)로 옆에 struct thread_info란 구조체가 보이지요? 커널 프로세스마다 스택 Top 주소에 이 구조체 데이터를 저장합니다. 정리하면 in_interrupt()란 매크로는 현재 프로세스의 스택에 저장된 struct thread_info 구조체의 preempt_count 멤버를 읽습니다. 

이제까지 설명드린 내용을 실제 디바이스에서 확인해볼까요?
아래는 코어 덤프를 로딩해서 Trace32 프로그램으로 확인한 struct thread_info 자료 구조입니다. 참고로, 리눅스 커널에서는 커널 패닉이 발생하면 그 시점의 메모리 덤프를 받을 수 있는 기능을 지원합니다. 이 메모리 덤프를 보통 vmcore 혹은 코어 덤프라고 합니다. 이제 코어 덤프 분석으로 돌아가면, preempt_count값은 0x00010002로 현재 프로세스가 인터럽트 컨택스트에서 실행 중임을 알 수 있습니다.
(struct thread_info*)(0xE359B908 & ~0x1fff) = 0xE359A000 -> (
     flags = 0x2,
     preempt_count = 0x00010002, // <--  HARDIRQ_OFFSET
     addr_limit = 0xBF000000,
     task = 0xD0B5EA40,  // <--  태스크 디스크립터
     exec_domain = 0xC1A1AF1C,
     cpu = 0x0,
     cpu_domain = 0x15,

여기까지 잘 이해되시나요? 조금 낯선 용어들은 이장을 끝까지 읽으면 조금 친숙해지니 끝까지 읽어주세요. 다시 반복하면 HARDIRQ_OFFSET 란 비트는 “현재 인터럽트 처리 중”임을 나타냅니다.
 
그럼 인터럽트 처리 시작을 설정하는 HARDIRQ_OFFSET 란 비트는 어느 함수에서 설정할까요? 
코드를 조금 더 살펴보면 __irq_enter 매크로에서 HARDIRQ_OFFSET 비트를 설정하고 있습니다.
__irq_svc
-gic_handle_irq
--_handle_domain_irq
---irq_enter
----__irq_enter

__handle_domain_irq 함수에서 인터럽트 핸들러를 호출하기 전 irq_enter란 함수를 호출합니다. 함수 이름대로 “인터럽트 처리 시작”을 나타내는 표시로 보입니다. 제 생각에 irq_enter보다 irq_context_enter로 함수 이름을 바꾸면 조금 더 함수를 쉽게 이해할 것 같습니다. 해당 코드는 아래 화살표가 가르키고 있으니 잘 살펴보세요.
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
    bool lookup, struct pt_regs *regs)
 {
  struct pt_regs *old_regs = set_irq_regs(regs);
  unsigned int irq = hwirq;
  int ret = 0;
 
  irq_enter();  //<<-- 
 
 //skip
  if (unlikely(!irq || irq >= nr_irqs)) {
   ack_bad_irq(irq);
   ret = -EINVAL;
  } else {
   generic_handle_irq(irq);
  }
 
  irq_exit();  //<<-- 
  set_irq_regs(old_regs);
  return ret;
 }

irq_enter 함수는 결국 __irq_enter 함수를 호출하는데 __irq_enter 매크로 함수를 자세히 살펴보면 current_thread_info()->preempt_count란 멤버에 HARDIRQ_OFFSET 비트를 더합니다. 이제 “인터럽트 처리 시작”이란 의미의 비트를 설정하는 겁니다.
#define __irq_enter()     \
  do {      \
   account_irq_enter_time(current); \
   preempt_count_add(HARDIRQ_OFFSET); \ // <<--  
   trace_hardirq_enter();   \
  } while (0)

이후 해당 인터럽트 핸들러가 호출되어 인터럽트에 대한 처리를 마친 후에 다음 함수가 호출됩니다. 자, 이제 아래 코드 흐름에서 __irq_exit란 함수가 보이죠? 이 함수에서 struct thread_info.preempt_count 멤버에서 HARDIRQ_OFFSET 비트를 해제합니다.
__handle_domain_irq
-irq 처리 완료
--irq_exit
---__irq_exit

이제 코드를 살펴볼까요? current_thread_info()->preempt_count멤버에서 HARDIRQ_OFFSET 비트를 뺍니다.
#define __irq_exit()     \
  do {      \
   trace_hardirq_exit();   \
   account_irq_exit_time(current);  \
   preempt_count_sub(HARDIRQ_OFFSET); \
  } while (0)

여기까지 HARDIRQ_OFFSET 비트를 설정하고 해제하는 흐름을 알아봤는데, 이 비트는 뭘 의미할까요? HARDIRQ_OFFSET 비트(0x10000)은 현재 코드가 인터럽트를 처리하고 있는지를 여부를 나타냅니다. 이 비트를 설정하면 “현재는 인터럽트 처리 중” 해제하면 “현재 인터럽트 처리 중은 아님”을 알려주는 거죠. 

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

그럼 이제 패치를 작성해서 위에서 설명한 값이 커널 로그로 이제까지 설정한 값이 어떻게 찍히는지 실제 시스템에서 확인하겠습니다.

인터럽트 핸들러에 아래 코드를 반영하고 로그를 받아보면 in_interrupt() 함수 리턴값을 볼 수 있습니다.

이제 그럼 패치 코드를 찬찬히 리뷰해볼까요?
[1]코드: 현재 실행 중인 코드에서 태스크 디스크립터 정보를 어떻게 얻어올까요? 이 때 current란 매크로를 쓰면 됩니다. 이는 커널에서 제공하는 매크로인데 커널에서 자주 사용합니다. (이 세부 동작은 3장 프로세스에서 다루니 참고하세요.) struct task_struct 구조체에 stack이란 멤버에 스택 Top 주소가 저장돼 있거든요. 이 스택 Top주소로 preempt_count 멤버 변수에 접근할 수 있습니다.
[2]코드: 스택 탑(Top) 주소에 struct thread_info 정보가 있다고 배웠죠? 이 구조체로 캐스팅합니다.
[3]코드: in_interrupt와 struct thread_info.preempt_count 값을 출력합니다.
diff --git a/drivers/input/touchscreen/touch_core.c b/drivers/input/touchscreen/touch_core.c
index 9cbf6ad..f148cd5 100644
--- a/drivers/input/touchscreen/touch_core.c
+++ b/drivers/input/touchscreen/touch_core.c
@@ -170,10 +170,20 @@ static void touch_core_initialize(struct touch_core_data *ts)

 irqreturn_t touch_irq_handler(int irq, void *dev_id)
 {
+       void *stack;
+       struct thread_info *current_thread;
+
        struct touch_core_data *ts = (struct touch_core_data *) dev_id;
+
+       stack = current->stack;  <<--[1]
+       current_thread = (struct thread_info *)stack; // <<--[2]
+
+       printk("[+] in_interrupt: 0x%08x, preempt_count = 0x%08x \n", (unsigned int)in_interrupt(), (unsigned int)current_thread->preempt_count);

        TOUCH_TRACE();

위 로그를 작성하고 컴파일하면 문제없이 커널 빌드가 될 겁니다. 이후 커널 이미지를 다운로드 하고 커널 로그를 확인하면 됩니다.

커널 로그를 받아보면, in_interrupt() 함수는 현재 실행 중인 프로세스 스택에 저장된 struct thread_info->preempt_count 값에서 HARDIRQ_OFFSET 비트만 출력하기 위해 HARDIRQ_MASK로 마스킹한 결과를 리턴 합니다.
<6>[  129.931951 / 01-27 17:32:40.057][1] CPU1: Booted secondary processor
<6>[  129.935636 / 01-27 17:32:40.057][0] CPU0: Booted secondary processor
<6>[130.452743][2] [+] in_interrupt: 0x00010000, preempt_count = 0x00010001 
<6>[130.458822][7] [Touch] 1 finger pressed:<1>(xxxx,xxxx,xxxx)
<6>[130.469673][2] [+] in_interrupt: 0x00010000, preempt_count = 0x00010001 
<6>[130.486154][2] [+] in_interrupt: 0x00010000, preempt_count = 0x00010001 
<6>[130.502913][2] [+] in_interrupt: 0x00010000, preempt_count = 0x00010001

위 로그에서 preempt_count 값은 0x00010001이고 in_interrupt 값은 0x00010000이죠? 0x00010001 값에서 HARDIRQ_OFFSET 비트 값만 뽑아서 출력한 값이 0x00010000값 인데요. “in_interrupt: 0x00010000” 이 로그의 의미는 “현재 인터럽트 처리 중”이라는 것이며 현재 코드가 인터럽트 컨택스트라는 사실을 알려줍니다. 

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

인터럽트 컨택스트에서 스케쥴링을 하면?
이제 그 동안 설명했던 용어를 그대로 쓰겠습니다. 만약 인터럽트 컨택스트에서 프로세스가 휴면하면 어떤 일이 벌어질까요? 정답은 커널은 이를 감지하고 커널 패닉을 유발시킵니다.

인터럽트 컨택스트에서 스케쥴링을 하면 안 됩니다. 왜냐면, 짧은 시간에 인터럽트 핸들러를 실행하고 인터럽트 벡터로 다시 돌아가 이미 중단시킨 프로세스를 다시 동작시켜야 하기 때문이죠. 그런데 인터럽트 컨택스트에서 스케쥴링을 하면 커널 입장에서 많은 일을 해야 합니다. 당연히 시간이 오래 걸리죠.
 
그럼 인터럽트 컨택스트에서 스케쥴링 할 때 어떤 흐름으로 커널 패닉이 발생하는지 살펴보겠습니다. 프로세스가 스케쥴링 즉 휴면할 때 __schedule() 함수를 호출합니다. 이 함수를 열어보면 앞 단에 schedule_debug()란 함수를 호출해서 현재 프로세스가 휴면할 수 있는 조건인지 점검합니다. 
__schedule
-schedule_debug
--__schedule_bug

혹시 세니티 체크(Sanity Check)이란 용어를 들어보신 적이 있나요? 어떤 함수에 전달되는 파라미터가 예상했던 값인지 점검하는 에러 체크 루틴이죠. 커널 핵심 함수 구현 부 앞 단에 함수 파라미터들이 정상값인지 점검하는 코드가 많습니다. 위 코드 흐름에서는 schedule_debug() 함수가 이 임무를 수행합니다.

인터럽트 컨택스트에서 아래 함수가 호출되면 in_atomic_preempt_off [1]조건에 걸려 커널 패닉으로 리셋됩니다.
static inline void schedule_debug(struct task_struct *prev)
{
#ifdef CONFIG_SCHED_STACK_END_CHECK
if (unlikely(task_stack_end_corrupted(prev)))
panic("corrupted stack end detected inside scheduler\n");
#endif
/*
* Test if we are atomic. Since do_exit() needs to call into
* schedule() atomically, we ignore that path. Otherwise whine
* if we are scheduling when we should not.
*/
if (unlikely(in_atomic_preempt_off() && prev->state != TASK_DEAD)) // <<--[1]
__schedule_bug(prev); //<< --
rcu_sleep_check();

profile_hit(SCHED_PROFILING, __builtin_return_address(0));

schedstat_inc(this_rq(), sched_count);
}

#define in_atomic_preempt_off() \
((preempt_count() & ~PREEMPT_ACTIVE) != PREEMPT_CHECK_OFFSET)

이렇게 인터럽트 컨택스트에서 스케쥴링을 하다가 발생하는 커널 패닉은 리눅스 커널 커뮤니티에서 자주 볼 수 있는 버그입니다. 
이와 관련된 기존 이슈 하나를 소개하겠습니다. (https://patchwork.kernel.org/patch/4864261/) 아래 커널 로그는 인터럽트 컨택스트에서 뮤텍스락을 걸다가 커널 패닉이 발생했다고 말해줍니다. 참고로 뮤텍스락은 뮤텍스락을 다른 프로세스가 잡고 있으면 바로 휴면에 들어가기 때문에 인터럽트 컨택스트에서 쓰면 안 되는 함수입니다. 
BUG: sleeping function called from invalid context at ../kernel/locking/mutex.c:583
in_atomic(): 1, irqs_disabled(): 128, pid: 0, name: swapper/0
------------[ cut here ]------------
WARNING: CPU: 2 PID: 4828 at ../kernel/locking/mutex.c:479 mutex_lock_nested+0x3a0/0x3e8()
DEBUG_LOCKS_WARN_ON(in_interrupt()) //<<--[3]
Modules linked in:
CPU: 2 PID: 4828 Comm: Xorg.bin Tainted: G        W      3.17.0-rc3-00234-gd535c45-dirty #819
[<c0216690>] (unwind_backtrace) from [<c0212174>] (show_stack+0x10/0x14)
[<c0212174>] (show_stack) from [<c0867cc0>] (dump_stack+0x98/0xb8)
[<c0867cc0>] (dump_stack) from [<c02492a4>] (warn_slowpath_common+0x70/0x8c)
[<c02492a4>] (warn_slowpath_common) from [<c02492f0>] (warn_slowpath_fmt+0x30/0x40)
[<c02492f0>] (warn_slowpath_fmt) from [<c086a3f8>] (mutex_lock_nested+0x3a0/0x3e8)
[<c086a3f8>] (mutex_lock_nested) from [<c0294d08>](irq_find_host+0x20/0x9c)  //<<--[2]
[<c0294d08>] (irq_find_host) from [<c0769d50>] (of_irq_get+0x28/0x48)
[<c0769d50>] (of_irq_get) from [<c057d104>] (platform_get_irq+0x1c/0x8c)
[<c057d104>] (platform_get_irq) from [<c021a06c>] (cpu_pmu_enable_percpu_irq+0x14/0x38)
[<c021a06c>] (cpu_pmu_enable_percpu_irq) from [<c02b1634>] (flush_smp_call_function_queue+0x88/0x178)
[<c02b1634>] (flush_smp_call_function_queue) from [<c0214dc0>] (handle_IPI+0x88/0x160)
[<c0214dc0>] (handle_IPI) from [<c0208930>] (gic_handle_irq+0x64/0x68)
[<c0208930>] (gic_handle_irq) from [<c0212d04>] (__irq_svc+0x44/0x5c)
[<c0212d04>] (__irq_svc) from [<c02a2e30>] (ktime_get_ts64+0x1c8/0x200) // <<--[1]
[<c02a2e30>] (ktime_get_ts64) from [<c032d4a0>] (poll_select_set_timeout+0x60/0xa8)
[<c032d4a0>] (poll_select_set_timeout) from [<c032df64>] (SyS_select+0xa8/0x118)
[<c032df64>] (SyS_select) from [<c020e8e0>] (ret_fast_syscall+0x0/0x48)

이제까지 배운 내용을 활용하면 위 로그를 분석해볼까요? [1]로그에서 보이는 __irq_svc는 인터럽트 벡터 함수죠. 이 정보로 인터럽트가 발생했다고 판단할 수 있습니다. 이후  gic_handle_irq와handle_IPI 순서로 인터럽트를 처리하는 함수가 호출합니다. 자 그럼 [1]번 코드 흐름을 우리는 어떻게 해석할 수 있죠? 인터럽트가 발생해서 인터럽트 벡터가 실행됐으므로 당연히 인터럽트 컨택스트라 할 수 있습니다. 그런데 [2]로그에서 뮤텍스락을 획득합니다. [3] 로그에서 현재 인터럽트 컨택스트인지 확인하여 커널 패닉을 유발합니다. 다음 [3]로그와 같이 in_interrupt() 함수가 true를 리턴하니 DEBUG_LOCKS_WARN_ON 란 WARN() 함수가 실행됩니다. 참고로 WARN() 매크로 함수가 실행하면 그 시점의 콜스택을 뿌려줍니다.
DEBUG_LOCKS_WARN_ON(in_interrupt()) <<--[3]


[라즈베리파이][리눅스커널] 인터럽트 소개(0) #CS 라즈베리_리눅스커널_인터럽트

인터럽트란
인터럽트란 단어가 여러분들은 생소하신가요? 낯설게 들리는 분도 있고 귀에 익은 분도 있을 텐데요. 평소 인터럽트를 뭐라고 하죠? 일상생활에서 인터럽트는 보통 갑자기 생긴 일이나 하던 일을 멈춘다는 의미죠. 예를 들면 책을 읽다가 전화가 와서 읽던 책을 덮어 놓고 전화를 받는 상황이죠. 

임베디드 시스템 관점으로 인터럽트는 뭘 의미하죠? 우선 하드웨어 관점으로는 하드웨어 변화를 감지해서 외부 입력으로 전달되는 전기 신호로 볼 수 있습니다. 예를 들면 키보드를 손으로 입력하면 하드웨어적으로 키보드 하드웨어의 변화를 감지하고 신호를 유발하죠. 보통 하드웨어 개발자들은 종종 오실로스코프란 장비로 인터럽트가 제대로 올라오는지 측정을 하죠. 인터럽트 신호를 측정하면 아래와 같은 파형을 볼 수 있는데요. 참고로 인터럽트 신호는 인터럽트를 식별하는 구간에 일정하게 5V(Voltage) 유지하거나 0V에서 5V로 변화하는 두 가지 종류로 분류합니다.
              
이제 인터럽트란 단어가 조금 덜 낯설지 않나요? 그럼 이제 소프트웨어 관점으로 인터럽트를 어떻게 처리하는지 살펴 볼게요. 인터럽트가 발생하면 프로세스는 하던 일을 멈추고 이미 정해진 코드를 실행해서 하드웨어 변화에 대해 처리하는 방식으로 동작합니다. 여기서 “이미 정해진 코드 흐름”은 인터럽트 서비스 루틴(Interrupt Service Routine)이라고 부르기도 하죠.

그럼 CPU(ARM) 관점으로 인터럽트는 어떻게 처리할까요? 인터럽트는 x86, ARM 아키텍처별로 처리하는 방식이 다른데요. 라즈베리파이가 ARM을 탑재하고 있으므로 ARM 프로세서 기준으로 살펴볼께요. ARM 프로세서에서 인터럽트는 익셉션(Exception)의 한 종류로 처리하므로 우선 익셉션 대해서 잠깐 배울 게요. ARM 프로세서는 외부 하드웨어 입력이나 오류에 대한 사건이 발생하면 익셉션 모드로 진입합니다. 좀 어려운 개념인데 순간 이동과 비슷한 개념으로 생각해도 좋습니다. 인터럽트나 소프트웨어적인 심각한 오류가 발생하면 ARM 프로세스가 특정 주소를 실행시켜 버리거든요. 이럴 때 각 익센션의 종류에 따라 이미 정해진 주소의 코드가 실행됩니다. 여기서 이미 정해진 주소를 익셉션 벡터(Exception Vector)라 하며 ARM 프로세서는 인터럽트를 익셉션 벡터 중 하나의 모드로 처리합니다. 

핵심은 "우리가 보고 있는 어떤 커널 코드도 인터럽트가 발생하면 실행이 멈춰서 인터럽트 벡터로 점프할 수 있다” 입니다. 이 점을 이해하면 드라이버 코드 한 줄 한 줄이 달리 보일 겁니다. 

인터럽트를 잘 알아야 하는 이유
그럼 커널이 인터럽트를 처리하는 코드 흐름과 동작을 왜 잘 알아야 하죠? 인터럽트는 시스템 전반 큰 영향을 끼치기 때문이죠. 또한, 리눅스 커널 시스템 전반을 잘 이해하기 위해서도 커널이 인터럽트를 어떻게 처리하는지 잘 알고 있어야 합니다. 그 이유는 크게 아래와 같습니다.
1. 대부분 리눅스 드라이버는 인터럽트를 통해 하드웨어 디바이스와 통신합니다. 그래서 디바이스 드라이버 코드를 처음 분석할 때 인터럽트를 처리 방식을 가장 먼저 확인합니다. 자연스럽게 디바이스 드라이버 코드를 빨리 이해할 수 있습니다.
2. 인터럽트가 발생했을 때 프로세스는 이미 정해진 동작을 수행합니다. 인터럽트 처리 과정을 잘 알게 되면 자연스럽게 프로세스가 스택 메모리 공간에서 어떻게 실행되는지 알게 됩니다. 
3. 아키텍처(x86, ARM)에 따라 인터럽트 벡터를 다르게 구현합니다. 인터럽트 벡터가 어떻게 동작하는지 잘 알면 자연히 ARM 아키텍처에 대해 더 많이 알게 됩니다.

무엇보다 리눅스 커널을 새로운 보드에 포팅하거나 시스템 전반을 설계하는 개발자는 인터럽트가 어떻게 처리되는지 잘 알아야 합니다. 커널 패닉이나 시스템이 느려지는 성능 문제가 인터럽트 동작과 연관된 경우가 많기 때문입니다.
 
리눅스 커널에서 인터럽트 처리 흐름
그럼 인터럽트가 발생했을 때 커널은 이를 어떻게 처리할까요? 이 동작은 크게 3단계로 나눌 수 있습니다.
1. 인터럽트가 발생하면 인터럽트 벡터가 실행되어 프로세스 정보를 저장하고 관련 커널 함수를 호출합니다.
2. 커널에서는 발생한 인터럽트에 대응하는 인터럽트 디스크립터를 읽고 인터럽트 핸들러를 호출합니다.
3. 인터럽트 핸들러에서 하드웨어를 직접 제어하고 유저 공간에 이 변화를 알립니다.

좀 더 이해를 돕기 위해 리눅스 커널을 쓰는 안드로이드 모바일 디바이스를 예로 들게요. 사용자가 손으로 화면을 만지면 내부에서 어떤 동작을 하죠? 조금만 상상해보세요. 하드웨어적인 터치 모듈이 변화를 감지하고 인터럽트 신호를 유발합니다. 그러면 인터럽트 벡터가 실행되고 커널은 터치 인터럽트에 해당하는 번호로 인터럽트 디스크립터를 읽어 옵니다. 이후 이 인터럽트 디스크립터로 터치 디바이스 드라이버에서 등록한 인터럽트 핸들러를 호출합니다. 결국 터치 인터럽트 핸들러는 해당 터치 인터럽트에 대해 화면을 업데이트하거나 하드웨어 터치 디바이스에 인터럽트를 처리했음을 알리죠. “인터럽트 디스크립트”, “인터럽트 벡터”와 같은 낯선 용어로 설명해 드렸는데, 이 단어의 공학적인 의미들은 하나하나 다음 시간에 배울 테니 조금만 기다리세요. 

잠깐 여기서 인터럽트 관련 용어를 잠깐 소개합니다.
인터럽트 디스크립터: struct irq_desc란 구조체로 커널이 관리하는 인터럽트 속성과 세부 동작을 포함합니다. 인터럽트 번호, 인터럽트 핸들러, 인터럽터 실행 횟수 정보가 여기에 있어요.
인터럽트 컨택스트: 인터럽트를 처리 중이란 뜻입니다.
인터럽트 벡터: 인터럽트가 발생하면 ARM이 실행 시키는 함수입니다. 어셈블리로 구현되어 있습니다.

[Kernel][Panic] panic@ttwu_do_activate (메모리 불량 보드) [Crash]Troubleshooting!!

CPU: 3 PID: 435 Comm: kworker/u17:2 프로세스에서 dm-verity를 처리하는 워크 함수 verity_prefetch_io 서브 루틴에서 커널 데이터 어보트가 발생했습니다.
-000|do_DataAbort(addr = 0, fsr = 0, regs = 0x0)
-001|__dabt_svc(asm)
 -->|exception
-002|ttwu_activate(inline)
-002|ttwu_do_activate.constprop.52(rq = 0xEB95AB80, p = 0xFFFFF69B)
-003|ttwu_queue(inline)
-003|try_to_wake_up(p = 0xEB95AB80, ?, wake_flags = -237815936)
-004|arch_spin_unlock(inline)
-004|__mutex_unlock_common_slowpath(inline)
-004|__mutex_unlock_slowpath(lock_count = 0xEB5BCB40)
-005|dm_bufio_prefetch(c = 0xEB5BCB40, block = 903261, ?)
-006|verity_prefetch_io(work = 0xF1D33780)
-007|static_key_count(inline)
-007|static_key_false(inline)
-007|trace_workqueue_execute_end(inline)
-007|process_one_work(worker = 0xEB063500, work = 0xD36AF980)
-008|worker_thread(__worker = 0xF1D33780)
-009|kthread(_create = 0xEB06EEC0)
-010|ret_from_fork(asm)
-011|ret_fast_syscall(asm)

dm_bufio_prefetch 코드에서 mutex_unlock하는 동작 중 struct mutex.wait_list에 등록된 프로세스를 깨우는 동작이었습니다.
해당 mutex는 아래 변수이며, struct mutex.wait_list에 가장 먼저 등록한 struct mutex.wait_list.next 프로세스를 깨웁니다.
  (struct mutex *) (struct mutex*)0xEB5BCB40 = 0xEB5BCB40 = __key+0x295D6EA4 -> (
    (atomic_t) count = ((int) counter = 1 = 0x1 = '....'),
    (spinlock_t) wait_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u32) slo
    (struct list_head) wait_list = (
      (struct list_head *) next = 0xE9F91D78  //<<--
        (struct list_head *) next = 0xE90ADD78
          (struct list_head *) next = 0xEAFFFE08
            (struct list_head *) next = 0xE5FD5E18
              (struct list_head *) next = 0xE90F7D78

struct mutex.wait_list.next 프로세스의 정체는 struct task_struct 가 0xEB95AB80 인 "kworker/u17:4" 프로세스입니다.
v.v %t %h %s container_of(0xE9F91D78,struct mutex,wait_list)
  (struct mutex *) container_of(0xE9F91D78,struct mutex,wait_list) = 0xE9F91D64 -> (
    (atomic_t) count = ((int) counter = 0xEB5BCB54),
    (spinlock_t) wait_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u32) slo
    (struct list_head) wait_list = ((struct list_head *) next = 0xE90ADD78, (struct list_head *) pre
    (struct task_struct *) owner = 0xEB95AB80 -> ( //<<--
      (long int) state = 0x0100,
      (void *) stack = 0xE9F90000,
      (atomic_t) usage = ((int) counter = 0x2),
// .. 생략 ..
      (struct cred *) cred = 0xEB063C00,
      (char [16]) comm = "kworker/u17:4",

"kworker/u17:4" 프로세스의 콜스택을 보면 역시 뮤텍스락을 얻기 위해 휴면에 진입한 상태였습니다.
v.f /task 0xEB95AB80 

-000|context_switch(inline)
-000|__schedule()
-001|schedule_preempt_disabled()
-002|__mutex_lock_common(inline)
-002|__mutex_lock_slowpath(lock_count = 0xEB5BCB40)
-003|current_thread_info(inline)
-003|mutex_set_owner(inline)
-003|mutex_lock(lock = 0xEB5BCB40)
-004|new_read(c = 0xEB5BCB40, block = 903235, nf = NF_READ, bp = 0xE9F91E1C)
-005|dm_bufio_read(?, ?, ?)
-006|verity_verify_level(v = 0xEB140400, io = 0xE9ED5A00, ?, ?, skip_unverified = FALSE, want_digest = 0x
-007|verity_hash_for_block(v = 0xEB140400, io = 0xE9ED5A00, block = 847125, digest = 0xE9ED5B10, is_zero
-008|verity_verify_io(inline)
-008|verity_work(w = 0x0)
-009|static_key_count(inline)
-009|static_key_false(inline)
-009|trace_workqueue_execute_end(inline)
-009|process_one_work(worker = 0xEB1F2A80, work = 0xE9ED5A38)
-010|worker_thread(__worker = 0x0)
-011|kthread(_create = 0xEB7D7E00)
-012|ret_from_fork(asm)
-013|ret_fast_syscall(asm)

다음 5줄 어셈블리 코드에서 데이터 어보트가 발생했는데, 이유는 R4가 0xFFFFF69B란 값을 담고 있었기 때문입니다.
MMU가 0xFFFFF69B란 가상 메모리를 처리하지 못해 ARM이 데이터 어보트를 유발한 것입니다.
1______addr/line|code_____|label____|mnemonic________________|comment
2   NSR:C014E914|158034D0            strne   r3,[r0,#0x4D0]
3   NSR:C014E918|EBFFFECB            bl      0xC014E44C       ; activate_task
4   NSR:C014E91C|E3A03001            mov     r3,#0x1          ; r3,#1
5___NSR:C014E920|E584302C____________str_____r3,[r4,#0x2C]

그럼 "str_____r3,r4,#0x2C" 이 어셈블리 코드의 정체는 뭘까요? struct task_struct 자료구조에서 on_rq에 멤버에 접근하는 코드입니다.
  (int) offsetof(struct task_struct,on_rq) = 44 = 0x2C = '...,'

이렇게 암흑의 어셈블리 코드 세상에서 벗어나 C 코드로 이동해보면 아래 4785 줄 에서 데이터 어보트가 발생했음을 알 수 있습니다.
4782static inline void ttwu_activate(struct rq *rq, struct task_struct *p, int en_flags)
4783{
4784 activate_task(rq, p, en_flags);
4785 p->on_rq = TASK_ON_RQ_QUEUED;
4786
4787 /* if a worker is waking up, notify workqueue */
4788 if (p->flags & PF_WQ_WORKER)
4789 wq_worker_waking_up(p, cpu_of(rq));
4790}

ttwu_activate 함수를 눈 여겨 보면 두 번째 파라미터가 struct task_struct *p이고 이 파라미터가 0xFFFFF69B란 주소를 담고 있는게 문제입니다.
다음 5줄 어셈블리 코드에서 데이터 어보트가 발생했는데, 이유는 R4가 0xFFFFF69B란 값을 담고 있었기 때문입니다.
MMU가 0xFFFFF69B란 가상 메모리를 처리하지 못해 ARM이 데이터 어보트를 유발한 것입니다.
1______addr/line|code_____|label____|mnemonic________________|comment
2   NSR:C014E914|158034D0            strne   r3,[r0,#0x4D0]
3   NSR:C014E918|EBFFFECB            bl      0xC014E44C       ; activate_task
4   NSR:C014E91C|E3A03001            mov     r3,#0x1          ; r3,#1
5___NSR:C014E920|E584302C____________str_____r3,[r4,#0x2C]

그럼 ttwu_activate 함수는 어느 함수가 호출했을까요?

코드를 조금 살펴보면 아래 4981줄에서 호출했음을 알 수 있습니다.
두번 째 파라미터로 전달하고 있으니 어셈블리 코드로 보면 R1으로 전달하겠죠.
4968static void ttwu_queue(struct task_struct *p, int cpu)
4969{
4970 struct rq *rq = cpu_rq(cpu);
4971
4972#if defined(CONFIG_SMP)
4973 if (sched_feat(TTWU_QUEUE) && !cpus_share_cache(smp_processor_id(), cpu)) {
4974 sched_clock_cpu(cpu); /* sync clocks x-cpu */
4975 ttwu_queue_remote(p, cpu);
4976 return;
4977 }
4978#endif
4979
4980 raw_spin_lock(&rq->lock);
4981 ttwu_do_activate(rq, p, 0);  //<<--
4982 raw_spin_unlock(&rq->lock);
4983}

예상했다 싶히, R10에서 R1으로 struct task_struct *p 값을 전달하네요. 
그럼 Trace32 프로그램에서는 R10이 0xEB95AB80 주소를 담고 있습니다.
1   NSR:C015597C|E1A00008            cpy     r0,r8
2   NSR:C0155980|EB3ADE16            bl      0xC100D1E0       ; _raw_spin_lock
3   NSR:C0155984|E1A00008            cpy     r0,r8
4   NSR:C0155988|E1A0100A            cpy     r1,r10           ; r1,p
5   NSR:C015598C|EBFFE3D7            bl      0xC014E8F0       ; ttwu_do_activate.constprop.52

0xEB95AB80 주소를 struct task_struct 구조체로 캐스팅해볼까요?
아래 명령어로 확인해보니 정말 태스크 디스크립터는 정상 멤버 값들을 담고 있군요.
  (struct task_struct*)0xEB95AB80 = 0xEB95AB80 -> (
    state = 256 = 0x0100,
    stack = 0xE9F90000,
    usage = (counter = 2 = 0x2),
    flags = 69238880 = 0x04208060,
// .. 생략..
    real_cred = 0xEB063C00,
    cred = 0xEB063C00,
    comm = "kworker/u17:4",
    link_count = 0 = 0x0,

아까 뮤텍스락 디버깅을 했었죠? 그 때 정보를 되살려 보면
0xEB95AB80 struct task_struct 구조체 주소는 뮤텍스락 wait_list.next에 등록된 프로세스의 태스크 디스크립터입니다.
v.v %t %h %s container_of(0xE9F91D78,struct mutex,wait_list)
  (struct mutex *) container_of(0xE9F91D78,struct mutex,wait_list) = 0xE9F91D64 -> (
    (atomic_t) count = ((int) counter = 0xEB5BCB54),
    (spinlock_t) wait_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u32) slo
    (struct list_head) wait_list = ((struct list_head *) next = 0xE90ADD78, (struct list_head *) pre
    (struct task_struct *) owner = 0xEB95AB80 -> ( //<<--
      (long int) state = 0x0100,
      (void *) stack = 0xE9F90000,

다시 원래 했던 디버깅 흐름으로 돌아와서.
그럼 두 번째 파라미터 전달했던 struct task_struct *p에 해당하는 0xEB95AB80 주소가 0xFFFFF69B 주소로 왜 갑자기 바뀌었을까요?

아래 5번 째 줄 코드에서 ttwu_do_activate 함수를 호출하기 직전 스택 주소는 0xEAD9DDE0이고.
1   NSR:C015597C|E1A00008            cpy     r0,r8
2   NSR:C0155980|EB3ADE16            bl      0xC100D1E0       ; _raw_spin_lock
3   NSR:C0155984|E1A00008            cpy     r0,r8
4   NSR:C0155988|E1A0100A            cpy     r1,r10           ; r1,p
5   NSR:C015598C|EBFFE3D7            bl      0xC014E8F0       ; ttwu_do_activate.constprop.52

ttwu_do_activate 함수의 어셈블리 코드를 보면 아래와 같이 r4, r5, r11, r14을 스택에 푸쉬합니다.
NSR:C014E8F0|E92D4830  ttwu_do_activate.constprop.52:  push    {r4-r5,r11,r14}

아래 스택 주소 0xEAD9DDE0 근처 메모리 덤프를 보면 7번, 8번, 9번 줄의 메모리 덤프와 같이 스택에 레지스터 값을 저장합니다. 그런데 5번 째 줄을 보면 R10이 0x0입니다. 원래 0xEB95AB80 이어야 하는데요.
1   NSD:EAD9DDC4| FF FF FF FF  0xFFFFFFFF         \\vmlinux\mdss_mdp_pipe\mdss_mdp_pipe_assign\__out
2   NSD:EAD9DDC8| 51 00 00 00  0x51               \\vmlinux\sch_mqprio\__exitcall_mqprio_module_exit+0x1
3   NSD:EAD9DDCC| 1C E9 14 C0  0xC014E91C         \\vmlinux\sched/core\ttwu_do_activate.constprop.52+0x2C
4   NSD:EAD9DDD0| 07 00 00 00  0x7                \\vmlinux\aesbs-glue\__exitcall_aesbs_mod_exit+0x3
5   NSD:EAD9DDD4| 00 00 00 00  0x0                // <<-- R10
6   NSD:EAD9DDD8| 3C DE D9 EA  0xEAD9DE3C   // <<-- R11
7   NSD:EAD9DDDC| 90 59 15 C0 0xC0155990 \\vmlinux\sched/core\try_to_wake_up+0x394 //<<-- R14
8   NSD:EAD9DDE0| 40 2B A8 5F  0x5FA82B40

결국 아래 코드에서 스택 푸쉬를 이상하게 해서 발생한 문제로 보입니다.
NSR:C014E8F0|E92D4830  ttwu_do_activate.constprop.52:  push    {r4-r5,r11,r14}



[프로세스] 실행 중인 cpu 정보 업데이트(커널 4.9.65 ) [Linux][Kernel] Process

ARM64 비트 리눅스 커널 4.9.65 버전에서 아래 구조체가 변경됐습니다.
특히 해당 프로세스가 돌고 있던 CPU 정보를 담고 있는 int cpu 멤버가 사라졌는데요.
crash64> struct thread_info
struct thread_info {
    unsigned long flags;
    mm_segment_t addr_limit;
    int preempt_count;
}

기존 Linux version 4.6.0 버전
crash64> p linux_banner
linux_banner = $1 = "Linux version 4.6.0 (sudhakar.koppiset@ubuntu) (gcc version 5.2.1 20151005 (Linaro GCC 5.2-2015.11-2) ) #1 SMP Tue Apr 4 17:38:35 KST 2017\n"
crash64> struct thread_info
struct thread_info {
    unsigned long flags;
    mm_segment_t addr_limit;
    struct task_struct *task;
    int preempt_count;
    int cpu;
}

기존 리눅스 커널 코드를 잠깐 살펴봐도 알 수 있습니다.
현재 프로세스가 돌고 있는 CPU는 smp_processor_id 함수로 얻어오는데 다음 코드 흐름을 보면 current_thread_info()->cpu로 얻어옵니다. 
void smp_call_function_many(const struct cpumask *mask,
    smp_call_func_t func, void *info, bool wait)
{
struct call_function_data *cfd;
int cpu, next_cpu, this_cpu = smp_processor_id();

# define smp_processor_id() raw_smp_processor_id()
#define raw_smp_processor_id() (current_thread_info()->cpu)

그런데 커널 4.9 버젼은 cpu_number 변수로 cpu 번호를 읽어옵니다.
# define smp_processor_id() raw_smp_processor_id()
#define raw_smp_processor_id() (*raw_cpu_ptr(&cpu_number))

코드와 함께 크래시 유틸리티 프로그램으로 확인하니 역시 per-cpu 타입 변수이네요.
DECLARE_PER_CPU_READ_MOSTLY(int, cpu_number);

crash64> p cpu_number
PER-CPU DATA TYPE:
  int cpu_number;
PER-CPU ADDRESSES:
  [0]: ffffffd27b2ce000
  [1]: ffffffd27b2e5000
  [2]: ffffffd27b2fc000
  [3]: ffffffd27b313000
  [4]: ffffffd27b32a000
  [5]: ffffffd27b341000
  [6]: ffffffd27b358000
  [7]: ffffffd27b36f000

태스크 디스크립터에 cpu란 멤버 변수가 있네요.
crash64> struct task_struct.cpu
struct task_struct {
    [68] unsigned int cpu;
}

크래시 유틸리티로 검증해도 같은 결과값를 확인할 수 있군요.
crash64> runq -m
 CPU 0: [0 00:00:03.122]  PID: 442    TASK: ffffffd267a26680  COMMAND: "mmcqd/0"
 CPU 1: [6 04:54:56.161]  PID: 0      TASK: ffffffd2741a6680  COMMAND: "swapper/1"
 CPU 2: [0 00:00:12.009]  PID: 83     TASK: ffffffd27358c480  COMMAND: "msm_watchdog"
 CPU 3: [0 00:00:00.050]  PID: 435    TASK: ffffffd267cce680  COMMAND: "irq/85-8804000."
 CPU 4: [6 04:54:46.752]  PID: 0      TASK: ffffffd2741a2280  COMMAND: "swapper/4"
 CPU 5: [6 04:54:46.752]  PID: 0      TASK: ffffffd2741a4480  COMMAND: "swapper/5"
 CPU 6: [0 00:00:13.364]  PID: 5359   TASK: ffffffd2685e0080  COMMAND: "kworker/u16:7"
 CPU 7: [6 04:54:56.161]  PID: 0      TASK: ffffffd2741d0080  COMMAND: "swapper/7"

crash64> struct task_struct.cpu ffffffd27358c480
  cpu = 2
crash64> struct task_struct.cpu ffffffd2685e0080
  cpu = 6

이렇게 리눅스 커널 4.9 버젼부터 ARM64 비트 아키텍처에서 CPU번호를 per-cpu 타입으로 관리하는데, 
왜 코드를 바꿨을까요? 여러 의견이 있는데 Security 때문이라는 주장에 저는 점수를 주고 싶습니다.


[라즈베리파이] 커널 살펴보기 - 섹션 정보 dev utility

아래 명령어로 라즈베리파이 소스 다운로드를 하고,
git clone --depth=1 https://github.com/raspberrypi/linux.git

크로스 컴파일러 툴을 설치한 다음에,
git clone https://github.com/raspberrypi/tools

커널 빌드를 하면 vmlinux을 추출할 수 있습니다.
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs  -j12

아래 명령어로 라즈베리파이 섹션 정보를 파악할 수 있는데요. 조금 더 짚어 보겠습니다.
./objdump -x vmlinux  | more

섹션이란 용어에 대해서 설명을 하겠습니다. 섹션은 임베디드 소스 빌드 시 쓰이는 개념인데, 비슷한 속성의 코드들을 묶어서 처리하면 용이하기 때문에 리눅스 커널서도 활용합니다. 각 섹션 정보를 리눅스 lds 파일에서 정의합니다만, 일단 이 페이지에서는 라즈베리파이 리눅스 커널의 섹션 정보 리뷰를 하겠습니다.

[1]: 스타트업 코드: .head.text 섹션에 위치하고 실행 시작 주소는 0x80008000입니다.
                        리눅스 커널 코드를 정해진 메모리 공간에 로딩해 놓으면 0x80008000 주소부터 실행됩니다.  
[2]: 커널 함수: 우리가 볼 수 있는 대부분 커널 함수들은 .text 영역에 위치해 있습니다.

[3]: __init 이란 매크로를 드라이버 실행 함수에 많이 붙힙니다. 이 함수들이 위치한 섹션이 .init.data입니다.
     커널이 부팅할 때 do_initcall 함수를 호출할 때 .init.data 섹션에 있는 함수들을 차례 차례 실행합니다.

[4]: per-cpu 타입 데이터들은 모두 data..percpu 섹션에 위치합니다.
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
0 .head.text    0000026c  80008000  80008000  00008000  2**2  //<<--[1]
                CONTENTS, ALLOC, LOAD, READONLY, CODE
.text         00607618  80100000  80100000  00010000  2**6  //<<--[2]
                CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .fixup        0000001c  80707618  80707618  00617618  2**2
                CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .rodata       001b8c84  80800000  80800000  00618000  2**12
                CONTENTS, ALLOC, LOAD, DATA
4 __bug_table   00006534  809b8c88  809b8c88  007d0c88  2**2
                CONTENTS, ALLOC, LOAD, DATA
5 __ksymtab     00007918  809bf1bc  809bf1bc  007d71bc  2**2
                CONTENTS, ALLOC, LOAD, READONLY, DATA
6 __ksymtab_gpl 00006738  809c6ad4  809c6ad4  007dead4  2**2
                CONTENTS, ALLOC, LOAD, READONLY, DATA
7 __kcrctab     00003c8c  809cd20c  809cd20c  007e520c  2**2
                CONTENTS, ALLOC, LOAD, READONLY, DATA
8 __kcrctab_gpl 0000339c  809d0e98  809d0e98  007e8e98  2**2
                CONTENTS, ALLOC, LOAD, READONLY, DATA
9 __ksymtab_strings 0002120e  809d4234  809d4234  007ec234  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
10 __param       00001374  809f5444  809f5444  0080d444  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
11 __modver      00000848  809f67b8  809f67b8  0080e7b8  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
12 __ex_table    00000f20  809f7000  809f7000  0080f000  2**3
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
13 .ARM.unwind_idx 00034f70  809f7f20  809f7f20  0080ff20  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
14 .ARM.unwind_tab 0004f58c  80a2ce90  80a2ce90  00844e90  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
15 .notes        00000024  80a7c41c  80a7c41c  0089441c  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, CODE
16 .vectors      00000020  ffff0000  80b00000  00898000  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, CODE
17 .stubs        000002c0  ffff1000  80b00020  00899000  2**5
                 CONTENTS, ALLOC, LOAD, READONLY, CODE
18 .init.text    0004274c  80b002e0  80b002e0  008a02e0  2**5 //<<--[3]
                 CONTENTS, ALLOC, LOAD, READONLY, CODE
19 .exit.text    000014bc  80b42a2c  80b42a2c  008e2a2c  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, CODE
20 .init.proc.info 0000023c  80b43ee8  80b43ee8  008e3ee8  2**0
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
21 .init.arch.info 000000d0  80b44124  80b44124  008e4124  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
22 .init.tagtable 00000010  80b441f4  80b441f4  008e41f4  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
23 .init.smpalt  0000ede8  80b44204  80b44204  008e4204  2**2
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
24 .init.pv_table 000008a8  80b52fec  80b52fec  008f2fec  2**0
                 CONTENTS, ALLOC, LOAD, READONLY, DATA
25 .init.data    00022b74  80b54000  80b54000  008f4000  2**12
                 CONTENTS, ALLOC, LOAD, DATA
26 .data..percpu 00006400  80b77000  80b77000  00917000  2**6 //<<--[4]
                 CONTENTS, ALLOC, LOAD, DATA
27 .data         000798e4  80c00000  80c00000  00920000  2**6
                 CONTENTS, ALLOC, LOAD, DATA
28 .data..page_aligned 00001000  80c7a000  80c7a000  0099a000  2**12
                 CONTENTS, ALLOC, LOAD, DATA
29 .bss          000c09a4  80c7b000  80c7b000  0099b000  2**6
                 ALLOC
30 .comment      0000005b  00000000  00000000  0099b000  2**0
                 CONTENTS, READONLY
31 .ARM.attributes 0000002f  00000000  00000000  0099b05b  2**0
                 CONTENTS, READONLY

[라즈베리파이] 인터럽트 백터 - 스택 푸쉬 라즈베리_리눅스커널_인터럽트

인터럽트가 발생했을 때 구동중인 프로세스의 레지스터가 스택 메모리 공간에 푸쉬된다고 설명했는데요. 실제 Trace32 프로그램으로 스택 메모리 덤프를 확인해 보겠습니다.

 

아래 콜스택은 리눅스 커널에서자주 볼 수 함수로 구성되어 있는데 ext4 파일 시스템에서 특정 파일을 동기화(Synch)하는 동작 중에 인터럽트가 발생했습니다..
-000|account_group_exec_runtime(inline)
-000|update_curr()
-001|check_spread(inline)
-001|put_prev_entity()
-002|put_prev_task_fair()
-003|pick_next_task_rt(inline)
-003|pick_next_task_rt()
-004|pick_next_task(inline)
-004|__schedule()
-005|arch_local_irq_disable(inline)
-005|preempt_schedule_irq()
-006|svc_preempt(asm)
-007|__irq_svc(asm)
 -->|exception
-008|blk_flush_plug_list()
-009|current_thread_info(inline)
-009|blk_finish_plug()
-010|ext4_writepages()
-011|__filemap_fdatawrite_range()
-012|filemap_write_and_wait_range()
-013|ext4_sync_file()
-014|vfs_fsync()
-015|fdput(inline)
-015|do_fsync()
-016|ret_fast_syscall(asm)

그럼 위 콜스택에서 인터럽트 벡터인 __irq_svc 함수가 호출된 시점의 스택 덤프를 확인하면
아래 화살표와 같이 스택에 푸쉬된 레지스터를 확인할 수 있습니다. 
________address||value_______|symbol
NSD:CE4F9D80| 0x20070013
NSD:CE4F9D84| 0xFFFFFFFF   
NSD:CE4F9D88| 0xCE4F9DE4
NSD:CE4F9D8C| 0xC0FF97B4   \\vmlinux\Global\__irq_svc+0x74
NSD:CE4F9D90| 0xCE4F8000
NSD:CE4F9D94| 0xCE4F8000
NSD:CE4F9D98| 0xCE4F9DAC
NSD:CE4F9D9C| 0xC0FF4E2C   \\vmlinux\sched/core\preempt_schedule_irq+0x50
NSD:CE4F9DA0| 0xC039B9DC   \\vmlinux\blk-core\blk_flush_plug_list+0x1A4
NSD:CE4F9DA4| 0xC039B9E0   \\vmlinux\blk-core\blk_flush_plug_list+0x1A8
NSD:CE4F9DA8| 0x1           
NSD:CE4F9DAC| 0xC0FF97D8   \\vmlinux\Global\svc_preempt+0x8
NSD:CE4F9DB0| 0x0           // <--[1] R0
NSD:CE4F9DB4| 0xCECC72B4   // <--[2] R1
NSD:CE4F9DB8| 0x1           // <--[3] R2
NSD:CE4F9DBC| 0x0           // <--[4] R3
NSD:CE4F9DC0| 0xCE4F9E00   // <--[5] R4
NSD:CE4F9DC4| 0xEAD60A60   // <--[6] R5
NSD:CE4F9DC8| 0x1           // <--[7] R6
NSD:CE4F9DCC| 0xCE4F9E00   // <--[8] R7 
NSD:CE4F9DD0| 0x0            // <--[9] R8
NSD:CE4F9DD4| 0x60070013   // <--[10] R9
NSD:CE4F9DD8| 0xCE4F8000   // <--[11] R10
NSD:CE4F9DDC| 0x1           // <--[12] R11
NSD:CE4F9DE0| 0x60070093   // <--[13] R12
NSD:CE4F9DE4| 0xCE4F9E00  // <--[14] R13, 스택 주소
NSD:CE4F9DE8| 0xC039B9DC \\vmlinux\blk-core\blk_flush_plug_list+0x1A4//<--[15]R14
NSD:CE4F9DEC| 0xC039B9E0   \\vmlinux\blk-core\blk_flush_plug_list+0x1A8 //<--[16]PC
NSD:CE4F9DF0| 0x20070013
NSD:CE4F9DF4| 0xFFFFFFFF

리눅스 커널 이론서를 보면 “인터럽트가 발생하면 프로세스에서 돌고 있는 정보를 저장한다”는 내용을 수없이 읽었는데 전 머리에 잘 남지 않더라고요. 실제 메모리 덤프를 확인하고 나서 정확히 이해하게 되었습니다.


[라즈베리파이] 인터럽트(6) - 디버깅 라즈베리_리눅스커널_인터럽트

ftrace
라즈베리안 리눅스 커널은 아래 ftrace 로그를 볼 수 있는 컨피그가 기본 설정돼 있습니다.
CONFIG_FUNCTION_TRACER
CONFIG_DYNAMIC_FTRACE
CONFIG_FUNCTION_GRAPH_TRACER

ftrace드라이버에서 리눅스 커널 동작을 확인할 수 있는 기본 이벤트들을 정의했습니다. 그 중 인터럽트는 리눅스 커널 동작에 가장 중요한 동작 중 하나 이므로 /d/tracing/events/irq 폴더에 이벤트를 확인할 수 있습니다. 

라즈베리파이 보드에서 아래와 같이 설정하면 인터럽트를 ftrace 로그에서 볼 수 있습니다.
참고로 sched_switch 이벤트를 추가한 이유는 어떤 프로세스에서 인터럽트를 처리하는지 확인할 수 있기 때문입니다.
"echo 0 > /d/tracing/tracing_on"
"echo nop > /d/tracing/current_tracer
"echo 0 > /d/tracing/events/enable"
"echo 1 > /d/tracing/events/sched/sched_switch/enable"
"echo 1 > /d/tracing/events/irq/irq_handler_entry/enable"
"echo 1 > /d/tracing/events/irq/irq_handler_exit/enable"

" echo 1 > /d/tracing/tracing_on"

위 명령어는 irq_debug.sh란 스크립트 파일로 저장해서 사용하면 손 쉽게 ftrace 이벤트를 설정할 수 있습니다.

아래 명령어로 ftrace 로그를 열어볼 수 있습니다.
"tail -300 /d/tracing/trace"

         rcuop/2-25    [004] d.H1   217.151739: irq_handler_entry: irq=55 name=mmc0
         rcuop/2-25    [004] d.H1   217.151759: irq_handler_exit: irq=55 ret=handled
         rcuop/2-25    [004] d..3   217.151980: sched_switch: prev_comm=rcuop/2 prev_pid=25 prev_prio=120 prev_state=R+ ==> next_comm=kworker/4:1H next_pid=392 next_prio=100
    kworker/4:1H-392   [004] d..3   217.152006: sched_switch: prev_comm=kworker/4:1H prev_pid=392 prev_prio=100 prev_state=S ==> next_comm=rcuop/2 next_pid=25 next_prio=120
         rcuop/2-25    [004] d..3   217.152105: sched_switch: prev_comm=rcuop/2 prev_pid=25 prev_prio=120 prev_state=S ==> next_comm=kernel_logger next_pid=1364 next_prio=120
           <...>-1364  [004] d..3   217.152150: sched_switch: prev_comm=kernel_logger prev_pid=1364 prev_prio=120 prev_state=D ==> next_comm=jbd2/dm-1-8 next_pid=841 next_prio=120
           <...>-841   [004] d..3   217.152455: sched_switch: prev_comm=jbd2/dm-1-8 prev_pid=841 prev_prio=120 prev_state=D ==> next_comm=mmc-cmdqd/0 next_pid=340 next_prio=120
     mmc-cmdqd/0-340   [004] d..3   217.152529: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=340 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120
          <idle>-0     [004] d.h2   217.152549: irq_handler_entry: irq=55 name=mmc0  // [1]
          <idle>-0     [004] d.h2   217.152563: irq_handler_exit: irq=55 ret=handled
          <idle>-0     [004] d..3   217.152664: sched_switch: prev_comm=swapper/4 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/4 next_pid=36 next_prio=120
     ksoftirqd/4-36    [004] d..3   217.152733: sched_switch: prev_comm=ksoftirqd/4 prev_pid=36 prev_prio=120 prev_state=R+ ==> next_comm=kworker/4:1H next_pid=392 next_prio=100
    kworker/4:1H-392   [004] d..3   217.152754: sched_switch: prev_comm=kworker/4:1H prev_pid=392 prev_prio=100 prev_state=S ==> next_comm=ksoftirqd/4 next_pid=36 next_prio=120
     ksoftirqd/4-36    [004] d..3   217.152768: sched_switch: prev_comm=ksoftirqd/4 prev_pid=36 prev_prio=120 prev_state=S ==> next_comm=jbd2/dm-1-8 next_pid=841 next_prio=120
           <...>-841   [004] d..3   217.152949: sched_switch: prev_comm=jbd2/dm-1-8 prev_pid=841 prev_prio=120 prev_state=D ==> next_comm=mmc-cmdqd/0 next_pid=340 next_prio=120
     mmc-cmdqd/0-340   [004] d..3   217.153000: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=340 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120
          <idle>-0     [004] d.h2   217.153583: irq_handler_entry: irq=55 name=mmc0
          <idle>-0     [004] d.h2   217.153598: irq_handler_exit: irq=55 ret=handled
          <idle>-0     [004] d..3   217.153649: sched_switch: prev_comm=swapper/4 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/4 next_pid=36 next_prio=120
     ksoftirqd/4-36    [004] d..3   217.153702: sched_switch: prev_comm=ksoftirqd/4 prev_pid=36 prev_prio=120 prev_state=R+ ==> next_comm=kworker/4:1H next_pid=392 next_prio=100
    kworker/4:1H-392   [004] d..3   217.153763: sched_switch: prev_comm=kworker/4:1H prev_pid=392 prev_prio=100 prev_state=S ==> next_comm=ksoftirqd/4 next_pid=36 next_prio=120
     ksoftirqd/4-36    [004] d..3   217.153776: sched_switch: prev_comm=ksoftirqd/4 prev_pid=36 prev_prio=120 prev_state=S ==> next_comm=mmc-cmdqd/0 next_pid=340 next_prio=120
     mmc-cmdqd/0-340   [004] d..3   217.153837: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=340 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120
          <idle>-0     [004] d.h2   217.154177: irq_handler_entry: irq=55 name=mmc0
          <idle>-0     [004] d.h2   217.154189: irq_handler_exit: irq=55 ret=handled

위 로그에서 [1]로 마킹한 부분을 보면 0.00004초 동안 인터럽트가 처리됐음을 알 수 있습니다.
<idle>-0     [004] d.h2   217.152549: irq_handler_entry: irq=55 name=mmc0  // <<--[1]
          <idle>-0     [004] d.h2   217.152563: irq_handler_exit: irq=55 ret=handled

인터럽트 핸들러는 빨리 실행돼야 한다는 소리는 마르고 닳도록 듣지 않았나요?
그래서 저는 실제 보드에서 인터럽트 핸들러가 얼마나 빨리 처리되는지 측정해봤습니다.

ftrace graph tracer 기능을 활용하면 함수 실행 시간을 측정할 수 있는데요.
아래와 같이 설정하면 됩니다.
echo  > d/tracing/set_event"
adb shell "sleep 1"

"echo 0 > d/tracing/tracing_on"
"echo function_graph > d/tracing/current_tracer"
"echo  > d/tracing/set_event"

"echo touch_irq_handler > d/tracing/set_ftrace_filter"
"echo 1 > d/tracing/tracing_on"

아래 명령어로 ftrace 로그를 열어보니 10us 이내로 실행되는군요.
"tail -300 /d/tracing/trace"
------------------------------------------
 5)  core_ct-356   =>  AudioTr-4407 
 ------------------------------------------

 5)               |  /* + comm : AudioTrack, prio : 97, pid : 4407, nr_thread : 88 */
 5)               |  /* - comm : AudioTrack, prio : 97, pid : 4407 */
 0)   2.135 us    |  touch_irq_handler();
 0)   2.343 us    |  touch_irq_handler();
 0)   2.239 us    |  touch_irq_handler();
 0)   3.802 us    |  touch_irq_handler();
 0)   2.448 us    |  touch_irq_handler();
 0)   1.667 us    |  touch_irq_handler();
 0)   1.875 us    |  touch_irq_handler();
 0)   9.427 us    |  touch_irq_handler();
 0)   2.396 us    |  touch_irq_handler();
 0)   2.187 us    |  touch_irq_handler();
 0)   2.136 us    |  touch_irq_handler();
 0)   1.667 us    |  touch_irq_handler();


[라즈베리파이]인터럽트(5) - 인터럽트 디스크립터란 라즈베리_리눅스커널_인터럽트

인터럽트 디스크립터란
프로세스마다 프로세스를 관리하는 태스크 디스크립터(struct task_struct)가 있습니다. 페이지도 페이지를 관리하는 페이지 디스크립터(struct page)가 있죠. 마찬가지로 리눅스 커널에서 발생하는 인터럽트 종류만큼 인터럽트 디스크립터가 있습니다. 만약 인터럽트 개수가 34개면 이를 관리하는 34개의 인터럽트 디스크립터가 있습니다.

이미 설명했듯이 request_irq 함수를 써서 인터럽트 번호와 인터럽트 핸들러를 등록합니다. 
그럼 커널은 해당 인터럽트가 발생하면 어떤 정보를 참고해서 이미 등록한 인터럽트 핸들러를 호출할 수 있을까요. 정답 인터럽트 디스크립터입니다. 

각 디바이스 드라이버에서 인터럽트 번호, dev_id 그리고 인터럽트 핸들러 정보를 채워 request_irq 함수를 호출합니다.
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

이 함수 내에서 바로 호출되는 request_threaded_irq 함수에서 커널은 이 파라미터들 이외에 다른 정보를 채워서 인터럽트 디스크립터를 생성합니다. 이 후 인터럽트 디스크립터는 인터럽트 디스크립터 테이블에 등록합니다.

인터럽트 디스크립터 자료 구조는 아래와 같습니다.
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;

가장 중요한 인터럽트 정보는 struct irq_desc.irq_data 멤버에 저장돼 있습니다. 해당 정보는 아래와 같습니다.
struct irqaction {
irq_handler_t handler;           //<<-- [1] 
void *dev_id;           //<<--  [2]
void __percpu *percpu_dev_id;  
struct irqaction *next;
irq_handler_t thread_fn;        //<<--  [3]
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq;               //<<--  [4]
unsigned int flags;               //<<--  [5]
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;

[1]:  irq_handler_t handler: 인터럽트 핸들러를 저장합니다.
[2]: void *dev_id: 디바이스 드라이버에서 지정한 데이터가 저장됩니다. 다양한 자료 구조를 지원해야 하므로 void 타입으로 선언했습니다.
[3]: thread_fn: 많은 드라이버가 Soft IRQ로 커널 쓰레드 레벨로 디바이스 데이터를 처리합니다. 이 경우 설정됩니다.
[4]: 인터럽트 번호입니다.
[5]: 설정값입니다. 아래 값 중 하나죠.
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008

이렇게 인터럽트 디스크립터는 해당 인터럽트에 대한 명세서라고 볼 수 있습니다. 
추가로 여러 중요한 정보를 저장하는데, 이 값들이 어떻게 처리되는지 살펴보겠습니다.
1. 각 CPU 별로 발생한 인터럽트 발생 개수
2. 인터럽트 처리 상태

인터럽트 발생 횟수
대부분 디바이스 드라이버는 인터럽트 핸들러로 하드웨어 디바이스가 발생시킨 인터럽트와 통신합니다. 예를 들면 터치 디바이스에서 사용자가 터치를 입력하면 터치 디바이스에서 올려주는 인터럽트로 터치 인풋이 발생했다고 알려줍니다. 그럼 디바이스 드라이버에서 인터럽트 핸들러를 등록한 후 해당 디바이스가 제대로 인터럽트를 발생시키는지 어떻게 알 수 있을까요. 이럴 때는 인터럽트 발생 횟수를 점검하면 됩니다. 이번에는 어떤 흐름으로 인터럽트 발생 횟수를 인터럽트 디스크립터가 관리하는지 점검하겠습니다.

인터럽트 발생 횟수는 인터럽트 디스크립터 내struct irq_desc->kstat_irqs란 per-cpu 타입 멤버 변수에 저장됩니다.
struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;

인터럽트가 발생하면 아래 흐름으로 handle_fastoei_irq 함수가 호출됩니다.
이후 해당 인터럽트 번호로 인터럽트 디스크립터를 가져와서, kstat_incr_irq_this_cpu 함수를 호출합니다.
void
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;

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);  //<<-- 
// 생략
handle_irq_event(desc); 

아래 코드를 보면 struct irq_desc->kstat_irqs 값을 1만큼 증가시킵니다.
static inline void kstat_incr_irqs_this_cpu(struct irq_desc *desc)
{
__this_cpu_inc(*desc->kstat_irqs);
__this_cpu_inc(kstat.irqs_sum);
}

void kstat_incr_irq_this_cpu(unsigned int irq)
{
kstat_incr_irqs_this_cpu(irq, irq_to_desc(irq));
}

라즈베리 커널 코드에서 아래 패치를 적용하면 framebuffer driver 인터럽트 발생 횟수를 확인할 수 있습니다.
diff --git a/drivers/video/fbdev/bcm2708_fb.c b/drivers/video/fbdev/bcm2708_fb.c
index 612293c..afc91ea 100644
--- a/drivers/video/fbdev/bcm2708_fb.c
+++ b/drivers/video/fbdev/bcm2708_fb.c
@@ -804,6 +804,46 @@ static int bcm2708_fb_register(struct bcm2708_fb *fb)
    return ret;
 }
 
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel_stat.h>
+
+static void interrupt_debug_irq_times(int irq_num)
+{
+   struct irqaction *action;
+   struct irq_desc *desc;
+
+   int cpu_num = 0;
+   unsigned long all_count = 0;
+
+   desc = irq_to_desc(irq_num);
+   
+   action = desc->action;
+
+   if (!action ) {
+       pr_err("invalid action at %s line: %d \n", __func__, __LINE__);
+       return;
+   }
+
+
+   for_each_online_cpu(cpu_num) {
+      all_count |= kstat_irqs_cpu(i, j);
+      printk("%10u ", kstat_irqs_cpu(action->irq, cpu_num));
+      printk("all_count: %10u ", all_count); 
+}
+
 static int bcm2708_fb_probe(struct platform_device *dev)
 {
    struct device_node *fw_np;
@@ -859,6 +899,7 @@ static int bcm2708_fb_probe(struct platform_device *dev)
        goto free_dma_chan;
    }

+   interrupt_debug_irq_times(fb->dma_irq);

    pr_info("BCM2708FB: allocated DMA channel %d @ %p\n",
           fb->dma_chan, fb->dma_chan_base);

위 코드는 추가한 다음에 커널 로그를 확인하면 아래와 같이 인터럽트 발생횟수를 확인할 수 있습니다. 
<4>[    2.700485 / 01-01 00:00:02.699][1]          12          0          0          0          
<4>[    2.700485 / 01-01 00:00:02.699][1] all_count: 12

인터럽트 처리 상태
현재 인터럽트가 어느 단계로 처리되는지 알고 싶으면 어떤 변수를 참고하면 좋을까요?
이럴 때 인터럽트 디스크립터 struct irq_desc.irq_data.state_use_accessors 멤버를 확인하면 됩니다.

아래 enum 타입 선언부만 봐도 어떤 의미인지 알 수 있을 것 같은데요. 
이 중에서 IRQD_IRQ_INPROGRESS 값이 어떻게 업데이트되는지 점검해보겠습니다.
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),
};

우선 struct irq_desc.irq_data.state_use_accessors 멤버는 irqd_set 함수로 업데이트됩니다.

아래 코드를 살펴보면, 인터럽트 핸들러를 처리하기 전후로 irqd_set 함수가 호출되어struct irq_desc.irq_data.state_use_accessors 멤버 변수 비트를 설정하고 해제합니다.
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;

desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); // //<<-- 
raw_spin_unlock(&desc->lock);

ret = handle_irq_event_percpu(desc, action);

raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); ////<<-- 
return ret;
}

그럼 실제 코어 덤프에서 이 값을 확인해볼까요?
아래는 Trace32로 인터럽트 디스크립터를 올려본 결과인데, 328번 인터럽트 번호에 struct irq_desc.irq_data.state_use_accessors는 0x4002입니다.
 (struct irq_desc *) (struct irq_desc *)0xDF4533C0 = 0xDF4533C0 = __bss_stop+0x1DD59510 -> (
    (struct irq_data) irq_data = (
      (unsigned int) irq = 328 = 0x0148, 
      (long unsigned int) hwirq = 13 = 0x0D, 
      (unsigned int) node = 0 = 0x0,
      (unsigned int) state_use_accessors = 16386 = 0x4002,

위 디버깅 정보를 해석하면 현재 인터럽트 핸들러 내부에서 어떤 코드가 실행되고 있다고 분석할 수 있습니다.

위에서 설명한 정보들은 실제 리눅스 코어 덤프에서 크래시 유틸리티(Crash-Utililty) 프로그램으로 인터럽트 디스크립터 정보를 볼 수 있습니다. 아래는 인터럽트 디스크립터 주소 eb4f3000를 통해 확인해본 각 멤버 변수들 값입니다.

[1]: 인터럽트 번호는 248
[2]: 인터럽트 핸들러는 gpio_keys_gpio_isr
[3]: dev_id는 0xe9505198입니다.
struct irq_desc {
  irq_data = {
    mask = 0,
    irq = 248,  //<<--[1]
    hwirq = 91,
    node = 4294967295,
    state_use_accessors = 3,
    chip = 0xc193646c 
    domain = 0xeb471440,
    parent_data = 0x0,
    handler_data = 0x0,
    chip_data = 0xebbb6a58,
    msi_desc = 0x0,
    affinity = {{
        bits = {255}
      }}
  },
  kstat_irqs = 0xc18d83c4,
  handle_irq = 0xc017b714 <handle_edge_irq>,
  action = 0xe956e400,

crash> struct irqaction 0xe956e400
struct irqaction {
  handler = 0xc07903ac <gpio_keys_gpio_isr>,  //<<--[2]
  dev_id = 0xe9505198, //<<--[3]
  percpu_dev_id = 0x0,
  next = 0x0,
  thread_fn = 0x0,
  thread = 0x0,
  irq = 248,
  flags = 131,
  thread_flags = 0,
  thread_mask = 0,
  name = 0xc342c00c "volume_up",
  dir = 0xe9579540
}

dev_id 주소가 0xe9505198인데, 이 void 타입 주소는 아래 코드에서 struct gpio_button_data 구조체로 캐스팅합니다.
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;

BUG_ON(irq != bdata->irq);

이 값을 크래시 유틸리티로 확인하면 아래와 같습니다.
crash> struct gpio_button_data 0xe9505198
struct gpio_button_data {
  button = 0xe9555418,
  input = 0xe9502840,
  timer = {
    entry = {
      next = 0x0,
      prev = 0x200
    },
    expires = 4294938339,
    base = 0xec0d0040,
    function = 0xc0790394 <gpio_keys_gpio_timer>,
    data = 3914355096,
    slack = -1,
    start_pid = -1,
    start_site = 0x0,
    start_comm = "000000000000000000000000000000"
  },
  work = {
    data = {
      counter = 64
    },
    entry = {
      next = 0xe95051d8,
      prev = 0xe95051d8
    },
    func = 0xc0790d0c <gpio_keys_gpio_work_func>



[라즈베리파이] 인터럽트 백터 어셈블리 코드 및 동작 분석 라즈베리_리눅스커널_인터럽트

인터럽트가 발생하면 __irq_svc 벡터로 점프합니다. 물론 해당 프로세스는 하던 일을 멈출 수 밖에 없는데요.
그럼 인터럽트 벡터에서 어떤 동작을 하는 지 어셈블리 코드를 분석하겠습니다.

#__irq_svc 코드 리뷰
[1]: 스택 공간을 0x4C 바이트만큼 확보합니다.

[2]--[3]: 현재 실행 중인 레지스터 R0부터 R14, PC까지 스택에 푸쉬합니다.

[4]: 0x80705398 메모리 공간에 있는 메모리 덤프 0x80c089ac를 로딩합니다.
    0x80c089ac는 handle_arch_irq란 함수 포인터 역할을 하는 변수인데 이 변수에는 gic_handle_irq가 지정되어 있습니다.
crash> p -x handle_arch_irq
handle_arch_irq = $3 = 0xc0100680
crash> sym 0xc0100680
c0100680 (t) gic_handle_irq /home001/austin/src/raspberry_kernel/drivers/irqchip/irq-gic.c: 439

[5]: 라즈베리 파이 커널은 ARM 프로세서 이므로 gic_handle_irq 함수를 호출합니다.
80705320 <__irq_svc>:
80705320: e24dd04c sub sp, sp, #76 ; 0x4c  //<< --[1]
80705324: e31d0004 tst sp, #4
80705328: 024dd004 subeq sp, sp, #4
8070532c: e88d1ffe stm sp, {r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip} //<< --[2]
80705330: e8900038 ldm r0, {r3, r4, r5}
80705334: e28d7030 add r7, sp, #48 ; 0x30
80705338: e3e06000 mvn r6, #0
8070533c: e28d204c add r2, sp, #76 ; 0x4c
80705340: 02822004 addeq r2, r2, #4
80705344: e52d3004 push {r3} ; (str r3, [sp, #-4]!)
80705348: e1a0300e mov r3, lr
8070534c: e887007c stm r7, {r2, r3, r4, r5, r6}
80705350: e1a096ad lsr r9, sp, #13
80705354: e1a09689 lsl r9, r9, #13
80705358: e5990008 ldr r0, [r9, #8]  //<< --[3]
8070535c: e3a0147f mov r1, #2130706432 ; 0x7f000000
80705360: e5891008 str r1, [r9, #8]
80705364: e58d004c str r0, [sp, #76] ; 0x4c
80705368: ebeb60e3 bl 801dd6fc <trace_hardirqs_off>
8070536c: e59f1024 ldr r1, [pc, #36] ; 80705398 <__irq_svc+0x78>  //<< --[4]
80705370: e1a0000d mov r0, sp
80705374: e28fe000 add lr, pc, #0
80705378: e591f000 ldr pc, [r1]   //<< --[5]
8070537c: ebeb6091 bl 801dd5c8 <trace_hardirqs_on>
80705380: e59d104c ldr r1, [sp, #76] ; 0x4c
80705384: e5891008 str r1, [r9, #8]
80705388: e16ff005 msr SPSR_fsxc, r5
8070538c: e24d0004 sub r0, sp, #4
80705390: e1801f92 strex r1, r2, [r0]
80705394: e8ddffff ldm sp, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, sp, lr, pc}^
80705398: 80c089ac .word 0x80c089ac

#handle_arch_irq 설정  코드 리뷰
handle_arch_irq 주소는 아래 함수에서 확인할 수 있습니다. 코드를 보아 set_handle_irq 함수 호출로 handle_arch_irq 전역 변수에 함수 포인터를 지정합니다.
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
80b036d4: e1a0c00d mov ip, sp
80b036d8: e92dd800 push {fp, ip, lr, pc}
80b036dc: e24cb004 sub fp, ip, #4
if (handle_arch_irq)
80b036e0: e59f300c ldr r3, [pc, #12] ; 80b036f4 <set_handle_irq+0x20>
80b036e4: e5932000 ldr r2, [r3]
80b036e8: e3520000 cmp r2, #0
return;

handle_arch_irq = handle_irq;
80b036ec: 05830000 streq r0, [r3]
80b036f0: e89da800 ldm sp, {fp, sp, pc}
80b036f4: 80c089ac .word 0x80c089ac

gic_handle_irq 함수 포인터 지정은 아래 __gic_init_bases 함수 내 set_handle_irq(gic_handle_irq); 구문에서 수행됩니다.
[linux/drivers/irqchip/irq-gic.c]
 static int __init __gic_init_bases(struct gic_chip_data *gic,
                                    int irq_start,
                                    struct fwnode_handle *handle)
{
// .. 생략 
         if (gic == &gic_data[0]) {
                 /*
                  * Initialize the CPU interface map to all CPUs.
                  * It will be refined as each CPU probes its ID.
                  * This is only necessary for the primary GIC.
                  */
                 for (i = 0; i < NR_GIC_CPU_IF; i++)
                         gic_cpu_map[i] = 0xff;
 #ifdef CONFIG_SMP
                 set_smp_cross_call(gic_raise_softirq);
 #endif
                 cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
                                           "AP_IRQ_GIC_STARTING",
                                           gic_starting_cpu, NULL);
                 set_handle_irq(gic_handle_irq);

#CONFIG_PREEMPT 코드 리뷰
__irq_svc 어셈블리 코드에서 아래 C코드에서 보이는 CONFIG_PREEMPT 컨피그로 묶여 있는 코드가 안 보입니다. 
__irq_svc:
        svc_entry
        irq_handler

#ifdef CONFIG_PREEMPT
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        ldr     r0, [tsk, #TI_FLAGS]            @ get flags
        teq     r8, #0                          @ if preempt count != 0
        movne   r0, #0                          @ force flags to 0
        tst     r0, #_TIF_NEED_RESCHED
        blne    svc_preempt
#endif

여기서 CONFIG_PREEMPT 컨피그가 켜져 있는 지 간단히 확인하는 유용한 팁을 공유 드리겠습니다. 
#error "CONFIG_PREEMPT" 란 매크로 코드를 추가하고 컴파일을 하면 컴파일 에러가 발생하지 않습니다.
즉 이 컨피그는 꺼져 있다는 말입니다.
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S
index 9f157e7..19f3115 100644
--- a/arch/arm/kernel/entry-armv.S
+++ b/arch/arm/kernel/entry-armv.S
@@ -219,6 +219,7 @@ __irq_svc:
        irq_handler

 #ifdef CONFIG_PREEMPT
+#error "CONFIG_PREEMPT"
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        ldr     r0, [tsk, #TI_FLAGS]            @ get flags
        teq     r8, #0                          @ if preempt count != 0

다른 플렛폼에서는 CONFIG_PREEMPT 컨피그가 켜져 있는데 라즈베리파이는 리눅스 커널 교육용 SoC라
안정성(Stability)을 위해 이 피쳐를 끈 것 같습니다.

[라즈베리파이] 인터럽트 핸들러에서 스택 덤프 저장 라즈베리_리눅스커널_인터럽트

아래 패치를 적용하면 해당 프로세스의 스택 메모리 덤프를 stack_dump 배열에 저장합니다.
인터럽트 처리가 마무리 된 후 프로세스 레벨에서 stack_dump 배열에 저장된 메모리 덤프를 커널 로그로 출력하면,
인터럽트 벡터 심볼 __irq_svc과 스택에 푸쉬한 레지스터 정보를 모두 볼 수 있습니다.
diff --git a/drivers/video/fbdev/bcm2708_fb.c b/drivers/video/fbdev/bcm2708_fb.c
index 612293c..2623e0d 100644
--- a/drivers/video/fbdev/bcm2708_fb.c
+++ b/drivers/video/fbdev/bcm2708_fb.c
@@ -713,9 +713,28 @@ static void bcm2708_fb_imageblit(struct fb_info *info,
        cfb_imageblit(info, image);
 }
 
+#define _DEBUG_ARM_STACK_SIZE 0x2000
+static unsigned int stack_dump[_DEBUG_ARM_STACK_SIZE];
+
+#define _DEBUG_ADDRESS_OFFSET 4
+
 static irqreturn_t bcm2708_fb_dma_irq(int irq, void *cxt)
 {
        struct bcm2708_fb *fb = cxt;
+       
+       register unsigned long current_sp asm ("sp");
+       unsigned long temp_stack_addr = 0x0;
+       unsigned int stack_bottom_addr = 0x0;
+       int proc_times = 0;
+       
+       stack_bottom_addr = (unsigned int)current->stack + _DEBUG_ARM_STACK_SIZE;  //<<--[1]
+
+       temp_stack_addr = current_sp;  //<<--[2]
+       for (proc_times = 0; temp_stack_addr < stack_bottom_addr; proc_times++)
+       {
+               stack_dump[temp_stack_addr] = *((unsigned long*)(temp_stack_addr));  //<<--[3] 
+               temp_stack_addr += _DEBUG_ADDRESS_OFFSET; 
+       }
 
        /* FIXME: should read status register to check if this is
         * actually interrupting us or not, in case this interrupt

[1]: 현재 구동 중인 프로세스의 태스크 디스크립터는 current란 매크로로 가져옵니다.
   struct task_struct->stack 멤버로 스택 Bottom 주소를 가져와서 프로세스 스택 사이즈를 더해 스택 Top 주소를 구합니다

[2]: "register unsigned long current_sp asm ("sp");" 명령어로 현재 실행 중인 코드의 스택 주소를 current_sp 변수에 저장했습니다.
     이 값을 temp_stack_addr 로컬 변수에 저장합니다.

[3]: temp_stack_addr 현재 스택 주소에서 4바이트 씩 계속 더하면서 스택 Top 주소까지 스택 메모리 덤프를 stack_dump 배열에 저장합니다.

스택 덤프를 어떤 방식으로 표현할 지 고민 중입니다.

[라즈베리파이] 커널 정보 확인 - Trace32 라즈베리_리눅스커널_인터럽트

아래 Trace32 스크립트를 실행하면 라즈베리파이 커널 vmlinux를 Trace32에 올릴 수 있습니다.
sys.cpu cortexa7
sys.u

d.load.elf vmlinux

라즈베리파이 리눅스 커널 정보를 확인하겠습니다.
각 섹션 정보는 아래와 같습니다.
y.l.sec
_____address________|path\section___________________________|acc|init|physical
P:80008000--8000826B|\\vmlinux\.head.text                   |R-X|L-  |
P:80100000--80707797|\\vmlinux\.text                        |R-X|L-  |
P:80707798--807077B3|\\vmlinux\.fixup                       |R-X|L-  |
D:80800000--809C2C83|\\vmlinux\.rodata                      |RW-|L-  |
D:809C2C88--809C91AF|\\vmlinux\__bug_table                  |RW-|L-  |
D:809C91B0--809D0AC7|\\vmlinux\__ksymtab                    |R--|L-  |
D:809D0AC8--809D71FF|\\vmlinux\__ksymtab_gpl                |R--|L-  |
D:809D7200--809DAE8B|\\vmlinux\__kcrctab                    |R--|L-  |
D:809DAE8C--809DE227|\\vmlinux\__kcrctab_gpl                |R--|L-  |
D:809DE228--809FF435|\\vmlinux\__ksymtab_strings            |R--|L-  |
D:809FF438--80A007AB|\\vmlinux\__param                      |R--|L-  |
D:80A007AC--80A00FFF|\\vmlinux\__modver                     |R--|L-  |
D:80A01000--80A01F1F|\\vmlinux\__ex_table                   |R--|L-  |
D:80A01F20--80A36E9F|\\vmlinux\.ARM.unwind_idx              |R--|L-  |
D:80A36EA0--80A86443|\\vmlinux\.ARM.unwind_tab              |R--|L-  |
P:80A86444--80A86467|\\vmlinux\.notes                       |R-X|L-  |
P:80B002E0--80B42A3B|\\vmlinux\.init.text                   |R-X|L-  |
P:80B42A3C--80B43EF7|\\vmlinux\.exit.text                   |R-X|L-  |
D:80B43EF8--80B44133|\\vmlinux\.init.proc.info              |R--|L-  |
D:80B44134--80B44203|\\vmlinux\.init.arch.info              |R--|L-  |
D:80B44204--80B44213|\\vmlinux\.init.tagtable               |R--|L-  |
D:80B44214--80B52FFB|\\vmlinux\.init.smpalt                 |R--|L-  |
D:80B52FFC--80B538A3|\\vmlinux\.init.pv_table               |R--|L-  |
D:80B54000--80B76B93|\\vmlinux\.init.data                   |RW-|L-  |
D:80B77000--80B7D3FF|\\vmlinux\.data..percpu                |RW-|L-  |
D:80C00000--80C798E3|\\vmlinux\.data                        |RW-|L-  |
D:80C7A000--80C7AFFF|\\vmlinux\.data..page_aligned          |RW-|L-  |
D:80C7B000--80D3B9A3|\\vmlinux\.bss                         |RW-|--  |
P:FFFF0000--FFFF001F|\\vmlinux\.vectors                     |R-X|L-  |
P:FFFF1000--FFFF12BF|\\vmlinux\.stubs                       |R-X|L-  |

리눅스 버젼은 4.9.80-v7입니다.
v.v % linux_banner 
  (static char [188]) linux_banner = "Linux version 4.9.80-v7+ (austin.kim@LGEARND7B16) (gcc version 4.8.3 20140303 (prerelease) (crosstool-NG linaro-1.13.1+bzr2650 - Linaro GCC 2014.03) ) #1 S

_text 즉 전원을 키면 처음 실행되는 스타트업 코드 주소는 0x80008000입니다.
NSR:80008000|EB042DB6___text:____bl______0x801136E0_______;___hyp_stub_install
NSR:80008004|E10F9000            mrs     r9,cpsr
NSR:80008008|E229901A            eor     r9,r9,#0x1A      ; r9,r9,#26
NSR:8000800C|E319001F            tst     r9,#0x1F         ; r9,#31
NSR:80008010|E3C9901F            bic     r9,r9,#0x1F      ; r9,r9,#31
NSR:80008014|E38990D3            orr     r9,r9,#0xD3      ; r9,r9,#211
NSR:80008018|1A000004            bne     0x80008030
NSR:8000801C|E3899C01            orr     r9,r9,#0x100     ; r9,r9,#256
NSR:80008020|E28FE00C            adr     r14,0x80008034
NSR:80008024|E16FF009            msr     spsr_cxsf,r9
NSP:80008028|E12EF30E            dcd     0xE12EF30E
NSP:8000802C|E160006E            dcd     0xE160006E
NSR:80008030|E121F009            msr     cpsr_c,r9

Calling Convention
1. start_kernel 함수 호출 전 스택 주소가 0xD000C000라고 가정합니다. 또한 강제로 R14 링크드 레지스터를 __irq_svc 심볼로 설정합니다.

[1]: 스택에 {r4-r12,r14,pc} 레지스터를 푸쉬하며 이 동작 스택 주소는 0xD000BFD4로 변경됩니다.
[2]: 스택 주소가 D000BFB8 = (0xD000BFD4 - 0x1C) 로 업데이트됩니다.
NSR:80B00970|E1A0C00D  start_kernel:    cpy     r12,r13
NSR:80B00974|E92DDFF0                   push    {r4-r12,r14,pc} // <<--[1]
NSR:80B00978|E24CB004                   sub     r11,r12,#0x4     ; r11,r12,#4
NSR:80B0097C|E24DD01C                   sub     r13,r13,#0x1C    ; r13,r13,#28  // <<--[2]

스택 덤프
_____address|_data________|value_____________|symbol
NSD:D000BFB0| 00 00 00 00  0x0
NSD:D000BFB4| 00 00 00 00  0x0
NSD:D000BFB8| 00 00 00 00  0x0        // <<--[2]     
NSD:D000BFBC| 00 00 00 00  0x0
NSD:D000BFC0| 00 00 00 00  0x0
NSD:D000BFC4| 00 00 00 00  0x0
NSD:D000BFC8| 00 00 00 00  0x0
NSD:D000BFCC| 00 00 00 00  0x0
NSD:D000BFD0| 00 00 00 00  0x0
NSD:D000BFD4| 04 00 00 00  0x4      // <<--[1]           
NSD:D000BFD8| 05 00 00 00  0x5                
NSD:D000BFDC| 06 00 00 00  0x6                
NSD:D000BFE0| 07 00 00 00  0x7                
NSD:D000BFE4| 08 00 00 00  0x8                
NSD:D000BFE8| 09 00 00 00  0x9                
NSD:D000BFEC| 10 00 00 00  0x10               
NSD:D000BFF0| 11 00 00 00  0x11               
NSD:D000BFF4| 00 C0 00 D0  0xD000C000
NSD:D000BFF8| 20 53 70 80  0x80705320         \\vmlinux\Global\__irq_svc
NSD:D000BFFC| 78 09 B0 80  0x80B00978         \\vmlinux\init/main\start_kernel+0x8
NSD:D000C000| 00 00 00 00  0x0
NSD:D000C004| 00 00 00 00  0x0

start_kernel에서 set_task_stack_end_magic를 호출할 시 스택에 레지스터를 어떻게 푸쉬하는지 점검합니다.
[1]: 스택 주소는 0xD000BFA8로 업데이트됩니다.
[2]: R12 즉 현재 스택 주소이서 0x4만큼 뻬고 R11에 저장합니다.
NSR:80119F00|E1A0C00D__set_task_stack_end_magic:_____cpy_____r12,r13
NSR:80119F04|E92DD800                                push    {r11-r12,r14,pc}  //<<--[1]
NSR:80119F08|E24CB004                                sub     r11,r12,#0x4     ; r11,r12,#4  //<<--[2]
NSR:80119F0C|E52DE004                                str     r14,[r13,#-0x4]!

스택 덤프
_____address|_data________|value_____________|symbol
NSD:D000BF94| 00 00 00 00  0x0
NSD:D000BF98| 00 00 00 00  0x0
NSD:D000BF9C| 00 00 00 00  0x0
NSD:D000BFA0| 00 00 00 00  0x0
NSD:D000BFA4| 00 00 00 00  0x0
NSD:D000BFA8| FC BF 00 D0  0xD000BFFC       // <<--  R11
NSD:D000BFAC| B8 BF 00 D0  0xD000BFB8       //  <<-- R12
NSD:D000BFB0| 88 09 B0 80  0x80B00988         \\vmlinux\init/main\start_kernel+0x18  //<<-- LR
NSD:D000BFB4| 08 9F 11 80  0x80119F08         \\vmlinux\fork\set_task_stack_end_magic+0x8  //<<-- PC
NSD:D000BFB8| 00 00 00 00  0x0
NSD:D000BFBC| 00 00 00 00  0x0
NSD:D000BFC0| 00 00 00 00  0x0
NSD:D000BFC4| 00 00 00 00  0x0
NSD:D000BFC8| 00 00 00 00  0x0
NSD:D000BFCC| 00 00 00 00  0x0
NSD:D000BFD0| 00 00 00 00  0x0
NSD:D000BFD4| 04 00 00 00  0x4            
NSD:D000BFD8| 05 00 00 00  0x5            
NSD:D000BFDC| 06 00 00 00  0x6            
NSD:D000BFE0| 07 00 00 00  0x7            
NSD:D000BFE4| 08 00 00 00  0x8            
NSD:D000BFE8| 09 00 00 00  0x9            
NSD:D000BFEC| 10 00 00 00  0x10           
NSD:D000BFF0| 11 00 00 00  0x11           
NSD:D000BFF4| 00 C0 00 D0  0xD000C000
NSD:D000BFF8| 20 53 70 80  0x80705320         \\vmlinux\Global\__irq_svc
NSD:D000BFFC| 78 09 B0 80  0x80B00978         \\vmlinux\init/main\start_kernel+0x8
NSD:D000C000| 00 00 00 00  0x0

위 정보를 종합하면 함수 호출 시 아래 규칙으로 스택에 푸쉬합니다.
[1]: 스택을 푸쉬할 때의 함수 프로그램 카운터
[2]: 호출한 함수(Linked Register) 주소
[3]: 업데이트하기 직전의 스택 주소
[4]: 프레임 포인터 레지스터: 이 주소에 -4를 빼면 바로 이전에 수행됐던 함수 심볼 정보가 보임      
   예를 들면 0xD000BFF8 =  0xD000BFFC - 0x4

#스택 덤프
NSD:D000BFA8| FC BF 00 D0  0xD000BFFC       // <<--  [4]
NSD:D000BFAC| B8 BF 00 D0  0xD000BFB8       //  <<-- [3]
NSD:D000BFB0| 88 09 B0 80  0x80B00988         \\vmlinux\init/main\start_kernel+0x18  //<<-- [2]
NSD:D000BFB4| 08 9F 11 80  0x80119F08         \\vmlinux\fork\set_task_stack_end_magic+0x8  //<<-- [1]
NSD:D000BFB8| 00 00 00 00  0x0  //<<-- 스택 주소
// .. 생략..
NSD:D000BFF4| 00 C0 00 D0  0xD000C000
NSD:D000BFF8| 20 53 70 80  0x80705320         \\vmlinux\Global\__irq_svc
NSD:D000BFFC| 78 09 B0 80  0x80B00978         \\vmlinux\init/main\start_kernel+0x8
NSD:D000C000| 00 00 00 00  0x0



[라즈베리파이] 커널 빌드 & 컴파일 환경 설정 라즈베리_리눅스커널_인터럽트

라즈베리파이 리눅스 커널 컴파일 명령어 출처
https://wikidocs.net/3243
https://www.raspberrypi.org/documentation/linux/kernel/building.md

소스 코드 다운로드 및 빌드 스크립트 작성
아래와 같이 폴더를 하나 생성합니다.
/home001/austin.kim/src/raspberry_kernel

소스 코드는 아래 명령어로 다운로드 받습니다.
git clone --depth=1 https://github.com/raspberrypi/linux

소스 코드를 다 받으면 linux란 폴더가 생깁니다.
austin.kim@LGEARND7B16:~/src/raspberry_kernel$ ls -l
total 4
drwxr-xr-x 25 austin.kim home001 4096 Feb 12 08:35 linux

크로스컴파일 환경 설정
아래 명령어로 라즈베리 파이 크로스컴파일 툴 체인을 설치합니다. 제가 주로 자주 쓰는 프로그램은 ~/bin/ 폴더에 관리하므로 ~/bin/raspberry_tools에 설치합니다.
git clone https://github.com/raspberrypi/tools ~/bin/raspberry_tools

이제 리눅스 빌드 서버에서 해당 크로스 컴파일 프로그램이 실행되도록 설정을 해줘야 합니다.
유저 루트 디렉토리로 이동해서 .bashrc를 수정하면 컴파일 하기 전에 귀찮게 PATH 설정할 필요가 없습니다.
.bashrc 파일을 열고 가장 하단에 아래 코드 두 줄을 추가합니다.
austin.kim@LGEARND7B16:~$ vi .bashrc
RASPBERRY_CROSS_TOOLS=~/bin/raspberry_tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin
PATH=$PATH:$RASPBERRY_CROSS_TOOLS

리눅스 커널 코드를 밥 먹듯이 수정할텐데, 일일히 커널 빌드 명령어 치기 참 귀찮죠.
그래서 아래와 같은 스크립트를 작성해서 build_kernel.sh 파일을 linux 폴더에 저장합니다.
물론 라즈베리 리눅스 커널 컴파일 명령어는 https://wikidocs.net/3243 을 참고했습니다.
#!/bin/sh
# This file is desinged to build Raspberry pi Linux Kernel
#

OUTPUT="/home001/austin.kim/src/raspberry_kernel/out"

KERNEL=kernel7

#make config
make ARCH=arm O=$OUTPUT CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig

#compile
make ARCH=arm O=$OUTPUT CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs  -j3

컴파일
이미 작성한 빌드 스크립트만 실행하면 바로 커널 빌드를 시작합니다.
austin.kim@LGEARND7B16:~/src/raspberry_kernel/linux$ ./build_kernel.sh
make[1]: Entering directory `/home001/austin.kim/src/raspberry_kernel/out'
  GEN     ./Makefile
#
# configuration written to .config
#
make[1]: Leaving directory `/home001/austin.kim/src/raspberry_kernel/out'
make[1]: Entering directory `/home001/austin.kim/src/raspberry_kernel/out'
  GEN     ./Makefile
scripts/kconfig/conf  --silentoldconfig Kconfig
make[1]: Leaving directory `/home001/austin.kim/src/raspberry_kernel/out'
make[1]: Entering directory `/home001/austin.kim/src/raspberry_kernel/out'
  CHK     include/config/kernel.release
  GEN     ./Makefile
  CHK     include/generated/uapi/linux/version.h
  Using /home001/austin.kim/src/raspberry_kernel/linux as source for kernel
  CHK     include/generated/utsrelease.h
  CHK     include/generated/timeconst.h
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    /home001/austin.kim/src/raspberry_kernel/linux/scripts/checksyscalls.sh
  CHK     include/generated/compile.h
  CC      arch/arm/mm/dma-mapping.o
  CC      arch/arm/common/firmware.o
.. 생략 ..
  H16TOFW firmware/edgeport/boot2.fw
  H16TOFW firmware/edgeport/down.fw
  H16TOFW firmware/edgeport/down2.fw
  IHEX2FW firmware/whiteheat_loader.fw
  IHEX2FW firmware/keyspan_pda/keyspan_pda.fw
  IHEX2FW firmware/whiteheat.fw
  IHEX2FW firmware/keyspan_pda/xircom_pgs.fw
make[1]: Leaving directory `/home001/austin.kim/src/raspberry_kernel/out'
austin.kim@LGEARND7B16:~/src/raspberry_kernel/linux$

아래 폴더에 가면 제대로 컴파일이 됐음을 확인할 수 있습니다.
austin.kim@LGEARND7B16:~/src/raspberry_kernel/out$ ls
arch   crypto    fs       ipc     Makefile         modules.order   scripts   source      virt
block  drivers   include  kernel  mm               Module.symvers  security  System.map  vmlinux
certs  firmware  init     lib     modules.builtin  net             sound     usr         vmlinux.o

전처리 파일 추출
리눅스 커널 소스를 좀 더 상세히 분석하려면 전처리 파일이 필요합니다. 아래 패치를 적용합니다.
austin.kim@LGEARND7B16:~/src/raspberry_kernel/linux$ git diff
diff --git a/Makefile b/Makefile
index 9550b69..b9e53f3 100644
--- a/Makefile
+++ b/Makefile
@@ -395,6 +395,7 @@ KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
                   -fno-strict-aliasing -fno-common \
                   -Werror-implicit-function-declaration \
                   -Wno-format-security \
+                  -save-temps=obj \
                   -std=gnu89
 KBUILD_CPPFLAGS := -D__KERNEL__
 KBUILD_AFLAGS_KERNEL :=

그럼 제대로 전처리 파일이 추출됐는지 확인해볼까요.
아래 work_on_cpu 함수의 INIT_WORK_ONSTACK 매크로를 전처리 파일에서 확인하겠습니다.
[kernel/workqueue.c]
long work_on_cpu(int cpu, long (*fn)(void *), void *arg)
{
        struct work_for_cpu wfc = { .fn = fn, .arg = arg };

        INIT_WORK_ONSTACK(&wfc.work, work_for_cpu_fn);
        schedule_work_on(cpu, &wfc.work);
        flush_work(&wfc.work);
        destroy_work_on_stack(&wfc.work);
        return wfc.ret;
}


workqueue.c 에 대한 전처리 파일을 out 파일에서 찾아보니 아래와 같이 보이네요.
austin.kim@LGEARND7B16:~/src/raspberry_kernel/out$ find . -name *workqueue*.i
./kernel/.tmp_workqueue.i

전처리 파일에서 work_on_cpu 함수는 아래와 같습니다.
long work_on_cpu(int cpu, long (*fn)(void *), void *arg)
{
 struct work_for_cpu wfc = { .fn = fn, .arg = arg };

 do { __init_work(((&wfc.work)), 1); ((&wfc.work))->data = (atomic_long_t) { (WORK_STRUCT_NO_POOL) }; INIT_LIST_HEAD(&((&wfc.work)
 schedule_work_on(cpu, &wfc.work);
 flush_work(&wfc.work);
 destroy_work_on_stack(&wfc.work);
 return wfc.ret;
}

INIT_WORK_ONSTACK 매크로의 실제 구현부를 확인할 수 있어 좋습니다.
INIT_WORK_ONSTACK(&wfc.work, work_for_cpu_fn);
->
do { __init_work(((&wfc.work)), 1); ((&wfc.work))->data = (atomic_long_t) { (WORK_STRUCT_NO_POOL) }; INIT_LIST_HEAD(&((&wfc.work)

vmlinux 소스 코드 정보 추가
리눅스 커널 코드를 빌드하면 vmlinux란 파일이 추출됩니다. 
바이너리 유틸리티를 활용하면 vmlinux에서 여러 디버깅 정보 확인을 할 수 있는데요.
아래 컨피그를 추가하면 vmlinux 심볼 테이블에 소스 코드 정보가 추가됩니다. 
diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig
index 96fbcc6..961829b 100644
--- a/arch/arm/configs/bcm2709_defconfig
+++ b/arch/arm/configs/bcm2709_defconfig
@@ -31,6 +31,7 @@ CONFIG_KPROBES=y
 CONFIG_JUMP_LABEL=y
 CONFIG_MODULES=y
 CONFIG_MODULE_UNLOAD=y
+CONFIG_DEBUG_INFO=y
 CONFIG_MODVERSIONS=y
 CONFIG_MODULE_SRCVERSION_ALL=y
 CONFIG_BLK_DEV_THROTTLING=y

위와 같이 컨피그 코드 수정 후 build_kernel.sh 파일을 실행시켜 생긴 .config 파일을 열어보면 CONFIG_DEBUG_INFO=y 컨피그가 실제 켜져 있네요. 
austin.kim@LGEARND7B16:~/src/raspberry_kernel/out$ vi .config
5459 # Compile-time checks and compiler options
5460 #
5461 CONFIG_DEBUG_INFO=y
5462 # CONFIG_DEBUG_INFO_REDUCED is not set


그런데 컴파일 도중 아래와 같은 에러 메세지와 함께 빌드가 중지됩니다.
lib/raid6/neon8.c: In function ‘raid6_neon8_gen_syndrome_real’:
lib/raid6/neon8.c:158:1: internal compiler error: in dwarf2out_frame_debug_adjust_cfa, at dwarf2cfi.c:1078
 }
 ^
Please submit a full bug report,
with preprocessed source if appropriate.
See <https://bugs.launchpad.net/gcc-linaro> for instructions.
make[3]: *** [lib/raid6/neon8.o] Error 1
make[3]: *** Waiting for unfinished jobs....
lib/raid6/neon4.c: In function ‘raid6_neon4_gen_syndrome_real’:
lib/raid6/neon4.c:114:1: internal compiler error: in dwarf2out_frame_debug_adjust_cfa, at dwarf2cfi.c:1078
 }
 ^

소스 정보가 포함된 vmlinux 파일을 얻는 게 목적이므로 아래 패치를 적용하고 다시 빌드합니다.
diff --git a/lib/raid6/Makefile b/lib/raid6/Makefile
index 3057011..69f563e 100644
--- a/lib/raid6/Makefile
+++ b/lib/raid6/Makefile
@@ -5,7 +5,7 @@ raid6_pq-y      += algos.o recov.o tables.o int1.o int2.o int4.o \

 raid6_pq-$(CONFIG_X86) += recov_ssse3.o recov_avx2.o mmx.o sse1.o sse2.o avx2.o avx512.o recov_avx512.o
 raid6_pq-$(CONFIG_ALTIVEC) += altivec1.o altivec2.o altivec4.o altivec8.o
-raid6_pq-$(CONFIG_KERNEL_MODE_NEON) += neon.o neon1.o neon2.o neon4.o neon8.o
+raid6_pq-$(CONFIG_KERNEL_MODE_NEON) += neon.o neon1.o neon2.o
 raid6_pq-$(CONFIG_TILEGX) += tilegx8.o
 raid6_pq-$(CONFIG_S390) += s390vx8.o recov_s390xc.o

위 패치를 적용하고 난 후 out 폴더에 vmlinux가 제대로 생성됩니다. 

바이너리 유틸리티 사용
raspberry_tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-objdump 폴더에 위치한 arm-linux-gnueabihf-objdump 파일을 out 폴더에 복사합니다.
austin.kim@LGEARND7B16:~/src/raspberry_kernel/out$ cp ~/bin/raspberry_tools/arm-bcm2708/arm-rpi-4.9.3-linux-gnueabihf/bin/arm-linux-gnueabihf-objdump .
austin.kim@LGEARND7B16:~/src/raspberry_kernel/out$

아래 명령어를 치면 arm-linux-gnueabihf-objdump 바이너리 유틸리티 옵션을 확인할 수 있습니다.
austin.kim@LGEARND7B16:~/src/raspberry_kernel/out$ ./arm-linux-gnueabihf-objdump

Usage: ./arm-linux-gnueabihf-objdump <option(s)> <file(s)>
 Display information from object <file(s)>.
 At least one of the following switches must be given:
  -a, --archive-headers    Display archive header information
  -f, --file-headers       Display the contents of the overall file header
  -p, --private-headers    Display object format specific file header contents
  -P, --private=OPT,OPT... Display object format specific contents
  -h, --[section-]headers  Display the contents of the section headers
  -x, --all-headers        Display the contents of all headers
  -d, --disassemble        Display assembler contents of executable sections
  -D, --disassemble-all    Display assembler contents of all sections
  -S, --source             Intermix source code with disassembly
  -s, --full-contents      Display the full contents of all sections requested
  -g, --debugging          Display debug information in object file
  -e, --debugging-tags     Display debug information using ctags style
  -G, --stabs              Display (in raw form) any STABS info in the file
  -W[lLiaprmfFsoRt] or
  --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
          =frames-interp,=str,=loc,=Ranges,=pubtypes,
          =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
          =addr,=cu_index]
                           Display DWARF info in the file
  -t, --syms               Display the contents of the symbol table(s)
  -T, --dynamic-syms       Display the contents of the dynamic symbol table
  -r, --reloc              Display the relocation entries in the file
  -R, --dynamic-reloc      Display the dynamic relocation entries in the file
  @<file>                  Read options from <file>
  -v, --version            Display this program's version number
  -i, --info               List object formats and architectures supported
  -H, --help               Display this information

아래 리다이렉트 명령어로 결과 파일을 dump_kernel_code.c에 저장합니다. 
austin.kim@LGEARND7B16:~/src/raspberry_kernel/out$ ./arm-linux-gnueabihf-objdump -d -S vmlinux > dump_kernel_code.c

이제 dump_kernel_code.c 파일을 열어서 start_kernel 코드를 검색하면 어셈블리와 소스코드를 함께 볼 수 있습니다.
 80b00970 <start_kernel>:
         ioremap_huge_init();
         kaiser_init();
 }

 asmlinkage __visible void __init start_kernel(void)
 {
 80b00970:       e1a0c00d        mov     ip, sp
 80b00974:       e92ddff0        .word   0xe92ddff0
 80b00978:       e24cb004        sub     fp, ip, #4
 80b0097c:       e24dd01c        sub     sp, sp, #28
         char *command_line;
         char *after_dashes;

         set_task_stack_end_magic(&init_task);
 80b00980:       e59f0344        .word   0xe59f0344
 80b00984:       ebd8655d        bl      80119f00 <set_task_stack_end_magic>
         smp_setup_processor_id();
 80b00988:       eb000c3b        .word   0xeb000c3b
         /*
          * Set up the the initial canary ASAP:
          */
         boot_init_stack_canary();

         cgroup_init_early();





1 2 3 4 5 6 7 8 9 10