Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

848
469
422438


[Kernel] 슬랩 페이지(slab page)가 관리하는 슬랩 오브젝트 갯수 [Linux][Kernel] MM


슬랩 페이지 디스크립터가 관리하는 슬랩 오브젝트의 실제 메모리 덤프를 어떻게 접근할까요?

예를 들어 현재 페이지 디스크립터 주소가 0xEC778540 이라고 가정하겠습니다.
그리고 각 멤버는 다음과 같구요. 눈으로 잠깐 봐도 kmalloc-64 타입의 슬랩 페이지임을 알 수 있겠죠.
  (struct page *) (struct page*)0xEC778540 = 0xEC778540 -> (
    (long unsigned int) flags = 0x80,
    (struct address_space *) mapping = 0x0,
    (void *) s_mem = 0x0,
    (long unsigned int) index = 0xC518EC00,
    (void *) freelist = 0xC518EC00,
//...
    (struct slab *) slab_page = 0x0100,
    (struct callback_head) callback_head = ((struct callback_head *) next = 0x0100, (void (*)()) fun
    (long unsigned int) private = 0xEBC01080,
    (spinlock_t *) ptl = 0xEBC01080,
    (struct kmem_cache *) slab_cache = 0xEBC01080 -> (
      (struct kmem_cache_cpu *) cpu_slab = 0xC17D2CE0,
      (long unsigned int) flags = 0x80000D00,
//...
      (int) refcount = 0x1,
      (void (*)()) ctor = 0x0,
      (int) inuse = 0x44,
      (int) align = 0x40,
      (int) reserved = 0x0,
      (char *) name = 0xEBC02F80 -> "kmalloc-64",
 
우선 페이지 디스크립터 주소로 가상 주소를 변환할 필요가 있습니다.
그럼 그 과정을 알아볼까요.

페이지 프레임 번호는 mem_map 기준으로 (struct page*) 구조체 사이즈 크기 기준으로 매길 수 있습니다.
 
그래서 다음 Trace32 명령어를 입력하면 0xEC778540 페이지의 페이지 프레임 번호는 20878(0x518E)입니다.
v.v %all %l (struct page[0x7FFFF])*mem_map
  (static struct page [524287]) [D:0xEC58F000] (struct page[0x7FFFF])*mem_map = (
    [0] = ((long unsigned int) [D:0xEC58F000] flags = 524408 = 0x00080078 = '...x', (struct address_space *) [D:0xEC58F00
    [1] = ((long unsigned int) [D:0xEC58F060] flags = 524408 = 0x00080078 = '...x', (struct address_space *) [D:0xEC58F06
    [2] = ((long unsigned int) [D:0xEC58F0C0] flags = 524408 = 0x00080078 = '...x', (struct address_space *) [D:0xEC58F0C
//...
    [20877] = ((long unsigned int) [D:0xEC7784E0] flags = 2048 = 0x0800 = '....', (struct address_space *) [D:0xEC7784E4]
    [20878] = ((long unsigned int) [D:0xEC778540] flags = 128 = 0x80 = '....', (struct address_space *) [D:0xEC778544] ma

그럼 여기서 0xEC778540 주소의 페이지 디스크립터를 가상 주소로 변환해볼까요?
다음 코드를 참고해서 페이지 디스크립터 주소를 입력 받아 가상 주소로 변환할 수 있습니다.
static __always_inline void *lowmem_page_address(const struct page *page)
{
return __va(PFN_PHYS(page_to_pfn(page)));
}

이 코드를 전처리 파일로 확인하면 다음과 같습니다.
static inline __attribute__((always_inline)) __attribute__((no_instrument_function)) __attribute__((always_inline)) void *lowmem_page_address(const struct page *page)
{
 return ((void *)__phys_to_virt((phys_addr_t)(((phys_addr_t)(((unsigned long)((page) - mem_map) + (__pv_phys_pfn_offset))) << 12))));
}

위 코드를 염두해 놓고 이제 페이지 디스크립터 주소로 가상 주소를 변환해볼까요?
우선 다음 계산식으로 물리 주소를 계산할 수 있습니다.
페이지 프레임 번호는 20878(0x518E)이고 __pv_phys_pfn_offset 는 0x80000입니다.
0x8518E000 = ( 0x8518E << 12 ) = (0x518E + 0x80000) << 12

이번에는 물리 주소에서 가상 주소로 변환하겠습니다. 아래 코드와 계산식으로 가상 주소는 0xC518E000 이군요.
static inline unsigned long __phys_to_virt(phys_addr_t x)
{
return x - PHYS_OFFSET + PAGE_OFFSET;
}
0xC518E000 = 0x8518E000 + 0x40000000(0xC000_0000 - 0x8000_0000)
(where)
PHYS_OFFSET = 0x8000_0000
PAGE_OFFSET = 0xC000_0000
 
자 여기까지 분석한 내용을 정리하면 0xEC778540 주소의 슬랩 페이지 디스크립터의 가상 주소는 0xC518E000임을 알 수 있습니다.
0xC518E000 가상 주소부터 슬랩 오브젝트가 시작됩니다.

그럼 해당 페이지에 등록된 슬랩 캐시 정보를 확인하겠습니다.
  (struct kmem_cache *) (struct kmem_cache *)0xEBC01080 = 0xEBC01080 -> (
    (struct kmem_cache_cpu *) cpu_slab = 0xC17D2CE0,
    (long unsigned int) flags = 2147486976 = 0x80000D00,
    (long unsigned int) min_partial = 5 = 0x5,
    (int) size = 128 = 0x80,
    (int) object_size = 64 = 0x40,
    (int) offset = 68 = 0x44,
    (int) cpu_partial = 0 = 0x0,
    (struct kmem_cache_order_objects) oo = ((long unsigned int) x = 32 = 0x20),
    (struct kmem_cache_order_objects) max = ((long unsigned int) x = 32 = 0x20),
    (struct kmem_cache_order_objects) min = ((long unsigned int) x = 32 = 0x20),
    (gfp_t) allocflags = 0 = 0x0,
    (int) refcount = 1 = 0x1,
    (void (*)()) ctor = 0x0,
    (int) inuse = 68 = 0x44,
    (int) align = 64 = 0x40,
    (int) reserved = 0 = 0x0,
    (char *) name = 0xEBC02F80 -> "kmalloc-64",
    (struct list_head) list = ((struct list_head *) next = 0xEBC01FC4, (struct list_head *) prev = 0
    (struct kobject) kobj = ((char *) name = 0xE9C89300 -> "kmalloc-64", (struct list_head) entry =
    (struct kmem_cache_node * [1]) node = (0xEBC00FC0))
 
위 멤버 중에 가장 중요한 정보를 담고 있는 디버깅 정보는 다음과 같습니다.
    (int) size = 128 = 0x80,
    (int) object_size = 64 = 0x40,
    (int) offset = 68 = 0x44,
 
size가 0x80입니다. 모든 해더 정보를 포함한 슬랩 오브젝트 크기입니다.
object_size는 0x40 이군요. 슬램 오브젝트가 해더 없이 실제 쓰는 메모리 사이즈입니다.
offset는 0x44입니다. 다음 슬랩 오브젝트 주소 정보를 담고 있는 오프셋입니다.

이 정보를 염두해두고 다음 실제 슬랩 오브젝트 메모리 덤프를 확인할께요.
_____address|_data________|value_____________|symbol
NSD:C518E000| 6B 6B 6B 6B  0x6B6B6B6B  //<<--[1]
NSD:C518E004| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E008| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E00C| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E010| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E014| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E018| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E01C| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E020| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E024| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E028| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E02C| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E030| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E034| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E038| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E03C| 6B 6B 6B A5  0xA56B6B6B //<<--[2]
NSD:C518E040| BB BB BB BB  0xBBBBBBBB  //<<--[3]
NSD:C518E044| 80 E6 18 C5  0xC518E680 //<<--[4]
NSD:C518E048| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E04C| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E050| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E054| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E058| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E05C| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E060| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E064| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E068| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E06C| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E070| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E074| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E078| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E07C| 5A 5A 5A 5A  0x5A5A5A5A  //<<--[5]
NSD:C518E080| 6B 6B 6B 6B  0x6B6B6B6B
NSD:C518E084| 6B 6B 6B 6B  0x6B6B6B6B

[1]--[5] 슬랩 전체 사이즈
다음 메모리 구간이므로 0x80입니다.
0xC518E080 - 0xC518E000

[1]--[3] 슬랩 오브젝트 실제 메모리 덤프 사이즈
다음 메모리 구간이므로 0x40입니다. kmalloc 함수를 호출해서 실제 커널이나 디바이스 드라이버가 쓰는 메모리 공간입니다.
0xC518E040 - 0xC518E000 = 0x40

[3]: 메모리 포이즌 값입니다. 0xBBBBBBBB 이므로 이 슬랩 오브젝트는 이미 free된 상태입니다.

[4]: 다음 슬랩 오브젝트가 위치한 메모리 주소입니다.
이 오프셋은 다음 계산식으로 0x44입니다.
0xC518E044 - 0xC518E000 = 0x44


0xEC778540 주소의 슬랩 페이지 디스크립터의 가상 주소는 0xC518E000라고 했죠.
그런데 페이지 하나 당 0x1000(4096) 바이트 만큼 가상 메모리를 관리하므로 kmalloc-64 슬랩은
0xC518E000 -- 0xC518F000 구간에 위치했습니다.

그럼 각각 슬랩 오브젝트 메모리 덤프를 다음과 같이 표현할 수 있습니다.
_____address|_data________|value_____________|symbol
NSD:C518E000| 6B 6B 6B 6B  0x6B6B6B6B   //<<<< 1st slab
NSD:C518E004| 6B 6B 6B 6B  0x6B6B6B6B
//...
NSD:C518E044| 80 E6 18 C5  0xC518E680
//...
NSD:C518E07C| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E080| 6B 6B 6B 6B  0x6B6B6B6B  //<<<< 2nd slab
NSD:C518E084| 6B 6B 6B 6B  0x6B6B6B6B
//...
NSD:C518E0C4| 80 EC 18 C5  0xC518EC80
//...
NSD:C518E0FC| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E100| 6B 6B 6B 6B  0x6B6B6B6B  //<<<< 3rd slab
NSD:C518E104| 6B 6B 6B 6B  0x6B6B6B6B
//...
NSD:C518E144| 00 ED 18 C5  0xC518ED00
//...
NSD:C518E17C| 5A 5A 5A 5A  0x5A5A5A5A
NSD:C518E180| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 4th slab
NSD:C518E184| 6B 6B 6B 6B  0x6B6B6B6B

그럼 0xC518E000 -- 0xC518F000 구간에 있는 전체 슬랩 오브젝트를 알아볼까요?
조금 무식하긴 하지만 잊어먹지 않기 위해 다음과 같이 표현할게요.
_____address|_data________|value_____________|symbol
NSD:C518E000| 6B 6B 6B 6B  0x6B6B6B6B   //<<<< 1  
NSD:C518E080| 6B 6B 6B 6B  0x6B6B6B6B  //<<<< 2 
NSD:C518E100| 6B 6B 6B 6B  0x6B6B6B6B  //<<<< 3 
NSD:C518E180| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 4 
NSD:C518E200| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 5
NSD:C518E280| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 6
NSD:C518E300| 00 00 00 00  0x0       //<<<< 7
NSD:C518E380| 00 00 00 00  0x0             //<<<< 8
NSD:C518E400| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 9
NSD:C518E480| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 10
NSD:C518E500| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 11
NSD:C518E580| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 12
NSD:C518E600| A0 AF 4A E6  0xE64AAFA0 //<<<< 13
NSD:C518E680| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 14
NSD:C518E700| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 15
NSD:C518E780| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 16
NSD:C518E800| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 17
NSD:C518E880| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 18
NSD:C518E900| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 19
NSD:C518E980| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 20
NSD:C518EA00| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 21 
NSD:C518EA80| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 22 
NSD:C518EB00| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 23 
NSD:C518EB80| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 24
NSD:C518EC00| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 25 
NSD:C518EC80| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 26 
NSD:C518ED00| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 27 
NSD:C518ED80| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 28 
NSD:C518EE00| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 29 
NSD:C518EE80| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 30
NSD:C518EF00| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 31
NSD:C518EF80| 6B 6B 6B 6B  0x6B6B6B6B //<<<< 32
 
정리하면..
아래 (struct page*).objects 멤버가 가르키는 바와 같이 32입니다.
0xEC778540 주소의 페이지 디스크립터가 관리하는 0xC518E000 -- 0xC518F000 가상 메모리 구간에 32개의 슬랩 오브젝트가 있다는 점입니다.
  (struct page *) (struct page*)0xEC778540 = 0xEC778540 -> (
    (long unsigned int) flags = 128 = 0x80,
    (struct address_space *) mapping = 0x0,
    (void *) s_mem = 0x0,
    (long unsigned int) index = 3306744832 = 0xC518EC00,
    (void *) freelist = 0xC518EC00,
    (bool) pfmemalloc = FALSE,
    (unsigned int) counters = 2149580809 = 0x80200009,
    (atomic_t) _mapcount = ((int) counter = -2145386487 = 0x80200009),
    (unsigned int:16) inuse = 9 = 0x9,
    (unsigned int:15) objects = 32 = 0x20,


# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2





핑백

덧글

댓글 입력 영역