본문 바로가기

Core BSP 분석/리눅스 커널 핵심 분석

[리눅스커널][디버깅] 슬럽(슬랩) 캐시 오브젝트 T32로 메모리 디버깅하기

리눅스 커널 동적 메모리 할당을 위해 슬럽 캐시를 씁니다.
이번 시간에서는 kmalloc-64 슬럽 캐시 자료 구조를 통해 kmalloc-64 슬럽 오브젝트를 확인하는 방법을 소개합니다.
 
슬럽 캐시 확인하기
 
먼저 kmalloc-size 타입 슬럽 캐시를 관리하는 kmalloc_caches 전역 변수를 보겠습니다.
 
kmalloc_caches 전역 변수의 세부 필드는 다음과 같습니다.
  (static struct kmem_cache * [14]) kmalloc_caches = (
    [0] = 0x0,
    [1] = 0x0,
    [2] = 0xF1401E00, // "kmalloc-192"
    [3] = 0x0,
    [4] = 0x0,
    [5] = 0x0,
    [6] = 0xF1401F00 -> (
      (struct kmem_cache_cpu *) cpu_slab = 0xC19D82E0,
      (long unsigned int) flags = 0,
      (long unsigned int) min_partial = 5,
      (int) size = 64,
      (int) object_size = 64,
      (int) offset = 0,
      (int) cpu_partial = 30,
      (struct kmem_cache_order_objects) oo = ((long unsigned int) x = 64),
      (struct kmem_cache_order_objects) max = ((long unsigned int) x = 64),
      (struct kmem_cache_order_objects) min = ((long unsigned int) x = 64),
      (gfp_t) allocflags = 0,
      (int) refcount = 10,
      (void (*)()) ctor = 0x0,
      (int) inuse = 64,
      (int) align = 64,
      (int) reserved = 0,
      (char *) name = 0xF1402040 -> "kmalloc-64",
      (struct list_head) list = ((struct list_head *) next = 0xF1401FC4, (struct lis
      (int) red_left_pad = 0,
      (struct kobject) kobj = ((char *) name = 0xEC1E1FC0 -> ":t-0000064", (struct l
      (struct kmem_cache_node * [1]) node = ([0] = 0xF1400FC0)),
    [7] = 0xF1401E80,  // "kmalloc-128"
    [8] = 0xF1401D80,  // "kmalloc-256"
    [9] = 0xF1401D00,  // "kmalloc-512"
    [10] = 0xF1401C80, // "kmalloc-1024"
    [11] = 0xF1401C00, // "kmalloc-2048"
    [12] = 0xF1401B80, // "kmalloc-4096"
    [13] = 0xF1401B00) // "kmalloc-8192"
 
kmem_cache[6] 배열 인덱스에는 "kmalloc-64" 슬럽 캐시 자료구조가 있습니다.
    [6] = 0xF1401F00 -> (
      (struct kmem_cache_cpu *) cpu_slab = 0xC19D82E0,
      (long unsigned int) flags = 0,
      (long unsigned int) min_partial = 5,
      (int) size = 64,
      (int) object_size = 64,
      (int) offset = 0,
      (int) cpu_partial = 30,
      (struct kmem_cache_order_objects) oo = ((long unsigned int) x = 64),
      (struct kmem_cache_order_objects) max = ((long unsigned int) x = 64),
      (struct kmem_cache_order_objects) min = ((long unsigned int) x = 64),
      (gfp_t) allocflags = 0,
      (int) refcount = 10,
      (void (*)()) ctor = 0x0,
      (int) inuse = 64,
      (int) align = 64,
      (int) reserved = 0,
      (char *) name = 0xF1402040 -> "kmalloc-64",
 
필드 중 size와 object_size가 같으니 슬럽 오브젝트에 레드 존이 없다는 사실을 유추할 수 있습니다.
      (struct kmem_cache_cpu *) cpu_slab = 0xC19D82E0,
      (long unsigned int) flags = 0,
      (long unsigned int) min_partial = 5,
      (int) size = 64,
      (int) object_size = 64,
 
위 필드 중에 percpu 슬럽 캐시는 0xC19D82E0 주소이며 percpu 타입입니다.
슬럽 캐시에서 슬럽 오브젝트 할당을 관리하는 자료구조입니다.
 
percpu 슬럽 캐시 확인하기
 
따라서 __per_cpu_offset 오프셋을 반영해 percpu1 슬럽 캐시 정보를 봅시다.
v.v %s %t %h  (struct kmem_cache_cpu*)(0xC19D82E0 +__per_cpu_offset[1])
 
01  (struct kmem_cache_cpu *) (struct kmem_cache_cpu*)(0xC19D82E0 +__per_cpu_offset[1]) = 0xF1CC12E0
02    (void * *) freelist = 0xC44EC100,
03    (long unsigned int) tid = 0x104B4349,
04    (struct page *) page = 0xEC89D880 -> (
05      (long unsigned int) flags = 0x0100,
06      (struct address_space *) mapping = 0x0,
07      (void *) s_mem = 0x0,
08      (long unsigned int) index = 0xC44EC200,
09      (void *) freelist = 0xC44EC200,
10      (bool) pfmemalloc = FALSE,
11      (unsigned int) counters = 0x8040003B,
12      (atomic_t) _mapcount = ((int) counter = 0x8040003B),
13      (unsigned int:16) inuse = 0x3B,
14      (unsigned int:15) objects = 0x40,
15      (unsigned int:1) frozen = 0x1,
16      (int) units = 0x8040003B,
17      (atomic_t) _count = ((int) counter = 0x1),
18      (unsigned int) active = 0x8040003B,
19      (struct list_head) lru = ((struct list_head *) next = 0xECF69D00, (struct list_head *) prev =
20      (struct page *) next = 0xECF69D00,
21      (short int) pages = 0x13,
 
09 번째 로그를 보면 슬럽 페이지에서 현재 할당 가능한 Free kmalloc-64 슬럽 오브젝트의 주소가 0xC44EC200 이라고 말해줍니다.   
04    (struct page *) page = 0xEC89D880 -> (
05      (long unsigned int) flags = 0x0100,
06      (struct address_space *) mapping = 0x0,
07      (void *) s_mem = 0x0,
08      (long unsigned int) index = 0xC44EC200,
09      (void *) freelist = 0xC44EC200,
 
페이지 디스크립터로 물리주소 변환하기
 
페이지 디스크립터 주소가 0xEC89D880 이니 다음 공식으로 페이지 프레임 번호를 알 수 있습니다.
0x19D880 = 0xEC89D880 - 0xEC700000
0x19D880 / 0x60 = 0x19D880 / (struct page) 구조체 사이즈 
0x44EC = 0x19D880 / 0x60
 
페이지 디스크립터를 관리하는 mem_map[0..1] 정보는 다음과 같습니다.
v.v %l %t(struct page[100])*mem_map
  (static struct page [100]) [D:0xEC700000] (struct page[100])*mem_map = (
    ((long unsigned int) [D:0xEC700000] flags = 32768, (struct address_space *)  
    ((long unsigned int) [D:0xEC700060] flags = 65536, (struct address_space *) 
 
0xEC700000 시작 주소와 1 번째 인덱스인 0xEC700060를 빼니 0x60입니다. struct page 구조체 사이즈가 0x60입니다.
 
페이지 프레임 번호는 0x44EC, 10진수로는 17644입니다.
 
페이지 프레임 번호를 알면 자연히 물리 주소를 알 수 있습니다. 
페이지 프레임 번호가 0x44EC이니 커널 이미지를 로딩한 물리주소 기준으로 +0x44EC000입니다. 
0x44EC << 12 = 0x44EC000
 
커널 이미지가 실행 중인 물리 주소 시작 주소가 0x8000_0000이니 다음 계산으로 0x844EC000가 됩니다.
0x844EC000 = 0x8000_0000 +0x44E_C000
 
정리하면 다음 페이지 디스크립터에 매핑하는 물리 주소는 0x844EC000입니다.
(struct page *) page = 0xEC89D880
 
왜 물리 주소를 계산했는지는 조금 후에 알게 됩니다.
 
슬럽 오브젝트를 메모리 덤프에서 확인하기
 
다시 본론으로 돌아가서 슬럽 페이지 디스크립터의 freelist 필드를 보면 0xC44EC200 주소가 Free된 슬럽 오브젝트 주소입니다. 
04    (struct page *) page = 0xEC89D880 -> (
05      (long unsigned int) flags = 0x0100,
06      (struct address_space *) mapping = 0x0,
07      (void *) s_mem = 0x0,
08      (long unsigned int) index = 0xC44EC200,
09      (void *) freelist = 0xC44EC200,
 
즉, 0xC44EC200 주소를 바로 할당할 수 있습니다. 그럼 0xC44EC200 주소 메모리 덤프를 보겠습니다.
 
0xC44EC200 주소를 보니 0xC44ECE40 주소가 있습니다. 이는 다음 Free 슬럽 오브젝트 주소입니다.
________address|________0________4________8________C_0123456789ABCDEF
   NSD:C44EC200|>C44ECE40 00000200 00000003 00000001 @.N.............
   NSD:C44EC210| 00000005 0000000A 00000000 00000015 ................
   NSD:C44EC220| 00000001 000002C5 00000000 00000000 ................
   NSD:C44EC230| C44ECE40 00000018 00000001 00000000 @.N.............
 
현재 분석 중인 슬럽 캐시가 kmalloc-64 이니 슬럽 오브젝트 사이즈는 64, 16진수로는 0x40입니다.
그래서 C44EC200~C44EC3FF까지 메모리 덤프를 본 것입니다.
 
그러면 다음 Free 슬럽 오브젝트를 보겠습니다. 주소는 C44ECE40입니다.
________address|________0________4________8________C_0123456789ABCDEF
   NSD:C44ECE40|>C44EC180 C44ECE40 00000000 00020002 ..N.@.N.........
   NSD:C44ECE50| DEAD4EAD FFFFFFFF FFFFFFFF E645B388 .N............E.
   NSD:C44ECE60| 0000001C 00000000 00000000 00000000 ................
   NSD:C44ECE70| 00000000 00000000 00000000 00000000 ................
 
0xC44ECE40 주소에 가보니 0xC44EC180 주소를 가르키고 있습니다.
이는 다음 Free 슬랩 오브젝트 주소를 의미합니다.
 
이 방식으로 계속 꼬리 물듯이 주소를 따라가 보겠습니다.
________address|________0________4________8________C_0123456789ABCDEF
   NSD:C44EC180|>C44ECA00 00000021 00000000 00000000 ..N.!...........
   NSD:C44EC190| E3A99691 00000000 E3A99410 C44ECC1C ..............N.
   NSD:C44EC1A0| 00000000 00000000 00000100 00000200 ................
   NSD:C44EC1B0| C236E400 00000000 00000000 00000000 ..6.............
 
________address|________0________4________8________C_0123456789ABCDEF
   NSD:C44ECA00|>C44EC400 00000200 00000003 00000001 ..N.............
   NSD:C44ECA10| 00000005 0000000A 00000000 00000015 ................
   NSD:C44ECA20| 00000001 000002C5 00000000 00000000 ................
   NSD:C44ECA30| C44EC400 00000018 00000001 00000000 ..N.............
 
이제 정리를 해보겠습니다.
1. kmem_cache 구조체에 있는 percpu 슬랩 오브젝트에 접근    
       [6] = 0xF1401F00 -> (
      (struct kmem_cache_cpu *) cpu_slab = 0xC19D82E0, 
  
2. 0xC19D82E0 주소로 cpu1 percpu 로 캐스팅 
   v.v %s %t %h  (struct kmem_cache_cpu*)(0xC19D82E0 +__per_cpu_offset[1])
   
   01  (struct kmem_cache_cpu *) (struct kmem_cache_cpu*)(0xC19D82E0 +__per_cpu_offset[1]) = 0xF1CC12E0
   02    (void * *) freelist = 0xC44EC100,
   03    (long unsigned int) tid = 0x104B4349,
   04    (struct page *) page = 0xEC89D880 -> (
   05      (long unsigned int) flags = 0x0100,
   06      (struct address_space *) mapping = 0x0,
   07      (void *) s_mem = 0x0,
   08      (long unsigned int) index = 0xC44EC200,
   09      (void *) freelist = 0xC44EC200,
 
   (struct kmem_cache_cpu*).page->freelist가 0xC44EC200임
 
3. page->freelist가 가르키는 0xC44EC200 주소 꼬리 물고 따라다니기  
   0xC44EC200 -> 0xC44ECE40 -> 0xC44EC180 -> 0xC44ECA00 -> 0xC44EC400
 
   ________address|________0________4________8________C_0123456789ABCDEF
      NSD:C44EC200|>C44ECE40 00000200 00000003 00000001 @.N.............
      NSD:C44EC210| 00000005 0000000A 00000000 00000015 ................
      NSD:C44EC220| 00000001 000002C5 00000000 00000000 ................
      NSD:C44EC230| C44ECE40 00000018 00000001 00000000 @.N.............
 
   ________address|________0________4________8________C_0123456789ABCDEF
      NSD:C44ECE40|>C44EC180 C44ECE40 00000000 00020002 ..N.@.N.........
      NSD:C44ECE50| DEAD4EAD FFFFFFFF FFFFFFFF E645B388 .N............E.
      NSD:C44ECE60| 0000001C 00000000 00000000 00000000 ................
      NSD:C44ECE70| 00000000 00000000 00000000 00000000 ................
 
   ________address|________0________4________8________C_0123456789ABCDEF
      NSD:C44EC180|>C44ECA00 00000021 00000000 00000000 ..N.!...........
      NSD:C44EC190| E3A99691 00000000 E3A99410 C44ECC1C ..............N.
      NSD:C44EC1A0| 00000000 00000000 00000100 00000200 ................
      NSD:C44EC1B0| C236E400 00000000 00000000 00000000 ..6.............
 
   ________address|________0________4________8________C_0123456789ABCDEF
      NSD:C44ECA00|>C44EC400 00000200 00000003 00000001 ..N.............
      NSD:C44ECA10| 00000005 0000000A 00000000 00000015 ................
      NSD:C44ECA20| 00000001 000002C5 00000000 00000000 ................
      NSD:C44ECA30| C44EC400 00000018 00000001 00000000 ..N.............
 
여기서 Free 슬럽 오브젝트 주소는 다음과 같습니다.
0xC44EC200 -> 0xC44ECE40 -> 0xC44EC180 -> 0xC44ECA00 -> 0xC44EC400
 
슬럽 오브젝트 주소를 물리 주소로 변환하기
 
흥미로운 패턴을 발견할 수 있습니다. 모든 주소가 다음 주소 범위 내에 있습니다.
0xC44ECxxx
 
그러면 0xC44EC000 가상 주소를 물리 주소로 변환해 볼까요?
0xC44EC000 주소의 [31:19] 가 0xC44입니다.
 
swapper_pg_dir 주소를 보니 TTBR 즉 페이지 테이블 베이스 주소가 0xC0004000입니다. 
(unsigned int *) (unsigned int*)&swapper_pg_dir = 0xC0004000
 
0xC44EC000 가상 주소에 대한 페이지 테이블 엔트리 주소는 다음 공식으로 계산할 수 있습니다.
0xC0004000 + (0xC44 << 2)
0xC0004000 + (0x3110)
0xC0007110
 
0xC0007110 주소가 0xC44EC000 가상 주소에 대한 주소 변환 정보가 있는 페이지 테이블 엔트리 주소입니다.
________address|________0________4________8________C_0123456789ABCDEF
   NSD:C0007100| 8401141E 8411141E 8421141E 8431141E ..........!...1.
   NSD:C0007110|>8441141E 8451141E 8461141E 8471141E ..A...Q...a...q.
   NSD:C0007120| 8481141E 8491141E 84A1141E 84B1141E ................
 
0xC0007110 주소를 보니 페이지 테이블 엔트리 레코드가 0x8441141E 입니다.
0x8441141E 값을 2진수로 바꿔 보겠습니다.
10000100010000010001010000011110
 
하위 비트 [1:0]이 10으로 끝나니 섹션 엔트리입니다. 바로 물리 주소로 변환할 수 있습니다.
0x8441141E 페이지 테이블 엔트리 레코드에서 [31:19] 비트 주소만 남기고 나머지는 0으로 밀겠습니다.
0x8441141E
0x84400000
 
0x84400000 주소가 물리 주소 베이스 주소가 됩니다. 여기에 가상 주소 [18:0] 비트를 더하면 물리 주소가 됩니다.
0x84400000
0x000EC000( where: 0xC44EC000) 
------------------------------
0x844EC000
 
0xC44EC000 가상 주소를 물리 주소로 변환한 결과가 0x844EC000입니다.
 
그런데 0x844EC000 주소는 어디서 본 것 같습니다. 
위에서 다음과 같이 (struct page *) page = 0xEC89D880 페이지 디스크립터로 물리 주소를 변환했습니다.
-----------------------------------------------------------------------------------------------------------
페이지 프레임 번호는 0x44EC, 10진수로는 17644입니다.
 
페이지 프레임 번호를 알면 자연히 물리 주소를 알 수 있습니다. 
페이지 프레임 번호가 0x44EC이니 커널 이미지를 로딩한 물리주소 기준으로 +0x44EC000입니다. 
0x44EC << 12 = 0x44EC000
 
커널 이미지가 실행 중인 물리 주소 시작 주소가 0x8000_0000이니 다음 계산으로 0x844EC000가 됩니다.
0x844EC000 = 0x8000_0000 +0x44E_C000
-----------------------------------------------------------------------------------------------------------
 
메모리 디버깅 정보 요약하기 
 
분석한 디버깅 정보를 요약해 보겠습니다.
- 페이지 디스크립터 주소
(struct page *) page = 0xEC89D880
 
- 해당 가상 주소: 0xC44EC000
- 해당 물리 주소: 0x844EC000
 - Free 슬럽 오브젝트 주소는 다음과 같습니다.
   0xC44EC200 -> 0xC44ECE40 -> 0xC44EC180 -> 0xC44ECA00 -> 0xC44EC400
 
위에서 분석한 결과를 최종 정리하면 다음과 같은 결론을 이끌어 낼 수 있습니다.
1. (struct page *) page = 0xEC89D880는 슬럽(kmalloc-64 슬럽 캐시) 페이지이고 해당 가상 주소는 0xC44EC000입니다.    
2. kmalloc-64 슬럽 오브젝트 시작 주소는 0xC44EC000 이며 64 바이트 단위로 슬럽 오브젝트가 있습니다. 
 
다음 메모리 덤프는 0xC44EC000 주소에 64 바이트 씩 위치한 kmalloc-64 슬랩 오브젝트입니다.
0xC44EC200 주소가 Free된 슬럽 오브젝트 주소이고 나머지는 모두 이미 할당해 쓰고 있습니다.
________address|________0________4________8________C 
   NSD:C44EC000|>02D37147 00000002 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC000)
   NSD:C44EC010| C44ECDD1 00000000 00000000 C42F1D1C  
   NSD:C44EC020| 00000000 00000000 D1F31128 EA38EBA8  
   NSD:C44EC030| E6190000 DA79D700 00000000 00000000 
   NSD:C44EC040| 031AAE0C 0000001E 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC040)
   NSD:C44EC050| E9EFA3D1 E222A910 E9B72310 C535E59D  
   NSD:C44EC060| C44ECF5C C535E25C 00000000 E8C617B0  
   NSD:C44EC070| DD08C800 E8C61780 00000000 00000000  
   NSD:C44EC080| 02D37148 00000003 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC080)
   NSD:C44EC090| C44EC391 C44EC350 C44ECDD0 D36D0D9D  
   NSD:C44EC0A0| C325BE5C D109EA1C D1F31228 EA38EEA8  
   NSD:C44EC0B0| E6190000 DA7F5B00 00000000 00000000  
   NSD:C44EC0C0| 02D37149 00000004 00000001 00000001 //<<--kmalloc-64  (시작주소: C44EC0C0)
   NSD:C44EC0D0| C44EC351 00000000 00000000 C40D6CDD  
   NSD:C44EC0E0| C4A7C79C D0562E5C D1F31868 EA38E7A8  
   NSD:C44EC0F0| E6190000 CEA7F100 00000000 00000000  
   NSD:C44EC100| C44EC480 C44EC100 00000002 00000000 //<<--kmalloc-64 (시작주소: C44EC100)
   NSD:C44EC110| 00000000 00000000 00000000 00000000  
   NSD:C44EC120| 00000000 00000000 00000000 00000000  
   NSD:C44EC130| 00000000 00000000 00000000 00000000  
   NSD:C44EC140| 031AAE7F 000001AE 00000001 00000001 //<<--kmalloc-64 (시작주소: C44EC140)
   NSD:C44EC150| E1826991 C44ECB50 E2391990 DC14639D  
   NSD:C44EC160| DD26F1DC C402DC5C 00000000 E8C61F30  
   NSD:C44EC170| E9B92E00 E8C61F00 00000000 00000000  
   NSD:C44EC180| C44ECA00 00000021 00000000 00000000 //<<--kmalloc-64 (시작주소: C44EC180)
   NSD:C44EC190| E3A99691 00000000 E3A99410 C44ECC1C  
   NSD:C44EC1A0| 00000000 00000000 00000100 00000200  
   NSD:C44EC1B0| C236E400 00000000 00000000 00000000  
   NSD:C44EC1C0| 03169726 0000008E 00000001 00000001 //<<--kmalloc-64  (시작주소: C44EC1C0)
   NSD:C44EC1D0| C44EBF51 00000000 00000000 DD535ADC  
   NSD:C44EC1E0| 00000000 00000000 00000000 C2998CB0  
   NSD:C44EC1F0| D8D7D200 C2998C80 00000000 00000000  
   NSD:C44EC200| C44ECE40 00000200 00000003 00000001  //<<--kmalloc-64(Free) (시작주소: C44EC200)
   NSD:C44EC210| 00000005 0000000A 00000000 00000015  
   NSD:C44EC220| 00000001 000002C5 00000000 00000000  
   
흥미로운 사실은 슬럽 오브젝트 마지막 바이트를 보면 0x0000_0000이 보입니다.
이는 kmalloc(50, GFP_KERNEL); 로 메모리를 할당했을 것이라 생각됩니다.
   NSD:C44EC170| E9B92E00 E8C61F00 00000000 00000000  
   NSD:C44EC180| C44ECA00 00000021 00000000 00000000 //<<--kmalloc-64 
   NSD:C44EC190| E3A99691 00000000 E3A99410 C44ECC1C  
   NSD:C44EC1A0| 00000000 00000000 00000100 00000200  
   NSD:C44EC1B0| C236E400 00000000 00000000 00000000  
   NSD:C44EC1C0| 03169726 0000008E 00000001 00000001 //<<--kmalloc-64  
   NSD:C44EC1D0| C44EBF51 00000000 00000000 DD535ADC  
   
다음과 같이 kmalloc() 함수 첫 번째 인자로 메모리 할당을 요청하면 "kmalloc-64" 슬럽 캐시가 메모리를 할당해 줍니다.
kmalloc(50, GFP_KERNEL);
kmalloc(48, GFP_KERNEL);
kmalloc(46, GFP_KERNEL);