Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

75261
1501
219117


[리눅스커널] 익명 페이지 메모리 누수(Memory Leak) 디버깅 - TRACE32 [Crash]Troubleshooting!!

많은 리눅스 시스템 개발자들은 자신이 개발하고 있는 리눅스 커널의 버전이 주류였으면 하는 바램이 있다. 하지만 현실은 다르다. 현재 리눅스 커널의 최신 버전인 4.19(LTS 기준)으로 개발하고 있어도 이전 리눅스 커널 버전에서 다뤘던 자료 구조에 대해 알고 있어야 한다.

이번에는 커널 3.10 버전에서 struct anon_vma 구조체와 struct address_space 구조체의 관계에 대해 알아보겠다.

먼저 다음 TRACE32 명령어를 입력해 3번째 프레임의 페이지 디스크립터를 확인하자.

$(TRACE32 명령어)  v.v %d %t %h %i (struct page[0x7FFF])*mem_map
  (static struct page [32767]) (struct page[0x7FFF])*mem_map = (
    [0] = ((long unsigned int) flags = 524392 = 0x00080068, (struct address_space *) mapping = 0xF70
    [1] = ((long unsigned int) flags = 2592 = 0x0A20, (struct address_space *) mapping = 0xF6E90564,
    [2] = ((long unsigned int) flags = 524392 = 0x00080068, (struct address_space *) mapping = 0xF70
    [3] = (
      (long unsigned int) flags = 524392 = 0x00080068,
      (struct address_space *) mapping = 0xF70A6861,
      (long unsigned int) index = 468946 = 0x000727D2,
      (void *) freelist = 0x000727D2,
      (bool) pfmemalloc = -46 = 0xD2,
      (unsigned int) counters = 0 = 0x0,
 
struct address_space 타입인 mapping 필드는 0xF70A6861 이란 주소를 저장하고 있다.

어, 뭔가 이상하다! 왜 주소 끝 부분이 0x1로 끝날까?

그 이유는 익명(ANON) 페이지의 mapping 필드는 다음과 같은 규칙으로 정해지기 때문이다.

   * page->mapping & 0xFFFFFFFE = struct anon_vma 구조체 주소

그렇다면 0xF70A6860 주소를 struct anon_vma 구조체로 캐스팅을 해서 확인해볼까?

$(TRACE32 명령어) v.v %d %t %h %i (struct anon_vma)0xF70A6860

  (static struct anon_vma) (struct anon_vma)0xF70A6860 = (
    (struct anon_vma *) root = 0xF70A6860,
    (struct rw_semaphore) rwsem = (
      (__s32) activity = 0 = 0x0,
      (raw_spinlock_t) wait_lock = (
        (arch_spinlock_t) raw_lock = (
          (u32) slock = 171772477 = 0x0A3D0A3D,
          (struct __raw_tickets) tickets = (
            (u16) owner = 2621 = 0x0A3D,
            (u16) next = 2621 = 0x0A3D))),
      (struct list_head) wait_list = (
        (struct list_head *) next = 0xF70A686C,
        (struct list_head *) prev = 0xF70A686C)),
    (atomic_t) refcount = ((int) counter = 1 = 0x1),
    (struct rb_root) rb_root = ((struct rb_node *) rb_node = 0xF733FCF0))

필드를 보니 제대로된 값을 저장하고 있는 듯 하다.
스핀락의 티켓과 오너의 값이 같으니 말이다.

이번에는 메모리 릭으로 의심이 되는 mm_struct 구조체를 확인해보자.

$(TRACE32 명령어) v.v %i %h %t (struct mm_struct)0xF7F41880 

  (static struct mm_struct) (struct mm_struct)0xF7F41880 = (
    (struct vm_area_struct *) mmap = 0xF706DE70 -> (
      (long unsigned int) vm_start = 0x48400000,
      (long unsigned int) vm_end = 0xB4C00000,
      (struct vm_area_struct *) vm_next = 0xF71F34D0,
      (struct vm_area_struct *) vm_prev = 0x0,
      (struct rb_node) vm_rb = ((long unsigned int) __rb_parent_color = 0xF71F34E1, (struct rb_node
      (long unsigned int) rb_subtree_gap = 0x48400000,
      (struct mm_struct *) vm_mm = 0xF7F41880,
      (pgprot_t) vm_page_prot = 0x079F,
      (long unsigned int) vm_flags = 0x00100073,
      (union) shared = ((struct) linear = ((struct rb_node) rb = ((long unsigned int) __rb_parent_co
      (struct list_head) anon_vma_chain = ((struct list_head *) next = 0xF733FCE8, (struct list_head
      (struct anon_vma *) anon_vma = 0xF70A6860,
      (struct vm_operations_struct *) vm_ops = 0x0,
      (long unsigned int) vm_pgoff = 0x00048400,
      (struct file *) vm_file = 0x0,
      (void *) vm_private_data = 0x0),
 
anon_vma의 구조체인 anon_vma 필드가 0xF70A6860 주소를 저장하고 있다.

그렇다면, 다음과 같은 규칙에 따라 익명 페이지의 페이지 디스크립터 mapping 필드는 
0xF70A6861 일 것이다.  

   * page->mapping & 0xFFFFFFFE = struct anon_vma 구조체 주소

크래시 유틸리티를 사용해 0xF70A6861 주소를 누가 얼만큼 저장하는지 알아볼까?
아래는 'search 0xF70A6861' 명령어를 입력한 출력 결과이다.

crash> search 0xF70A6861
ce600004: f70a6861
ce60004c: f70a6861
ce600070: f70a6861
ce600124: f70a6861
ce600148: f70a6861
ce60016c: f70a6861
ce600190: f70a6861
...

엄청난 결과가 출력이 된다.

전체 페이지 디스크립터 중 0xF70A6861을 얼마나 차지하는지 확인해볼까?
 
crash> search 0xF70A6861 | wc -l
443296

무려 443296 개나 된다.

페이지 한 개당 크기가 4096이니 다음과 같은 계산식으로 익명 페이지가 1,731MB 의 메모리 공간을 차지하고 있다.

443296 * 4096 = 0x6C3A_0000
0x1B0E80KB(0x6C3A_0000/1024) = 1,731MB(0x1B0E80/1024)


   * 결론은 유저 공간의 메모리 릭이다!

참고로, 익명 페이지는 거의 유저 공간에 어떤 파일을 읽거나 쓸 때 사용한다.

위에서 살펴본 덤프는 아래 콜스택과 같이 메모리가 부족해 OOM Killer이 발생해 추출됐다.

-000|out_of_memory()
-001|__alloc_pages_may_oom(inline)
-001|__alloc_pages_slowpath(inline)
-001|__alloc_pages_nodemask()
-002|alloc_pages_node(inline)
-002|__page_cache_alloc(inline)
-002|page_cache_alloc_cold(inline)
-002|page_cache_read(inline)
-002|filemap_fault()
-003|alloc_pages_node(inline)
-003|__do_fault()
-004|handle_pte_fault()
-005|pmd_page_vaddr(inline)
-005|__handle_mm_fault(inline)
-005|handle_mm_fault()
-006|__do_page_fault(inline)
-006|do_page_fault()
-007|do_PrefetchAbort()
-008|__pabt_usr(asm)
 -->|exception
 

덧글

댓글 입력 영역