Egloos | Log-in
F/OSS study
F/OSS study
core 파일의 구조 (2)
OS: ubuntu 9.10
Linux: 2.6.31
arch: x86

이전 글 보기: core 파일의 구조 (1)


앞서 말했듯이 coredump를 일으킨 시점의 프로세스 정보는 NOTE 영역에 저장된다.
다음 명령을 실행하여 이를 확인할 수 있다.

$ readelf -n core

Notes at offset 0x00000214 with length 0x0000022c:
  Owner        Data size    Description
  CORE         0x00000090   NT_PRSTATUS (prstatus structure)
  CORE         0x0000007c   NT_PRPSINFO (prpsinfo structure)
  CORE         0x000000a0   NT_AUXV (auxiliary vector)
  LINUX        0x00000030   Unknown note type: (0x00000200)

위에서 볼 수 있듯이 NOTE 영역 자체도 다시 4개의 영역으로 나누어지며
각 영역에 대한 정보는 /usr/include/elf.h 파일에 아래와 같이 정의되어 있다.

#define NT_PRSTATUS    1        /* Contains copy of prstatus struct */
#define NT_FPREGSET    2        /* Contains copy of fpregset struct */
#define NT_PRPSINFO    3        /* Contains copy of prpsinfo struct */
#define NT_PRXREG      4        /* Contains copy of prxregset struct */
#define NT_TASKSTRUCT  4        /* Contains copy of task structure */
#define NT_PLATFORM    5        /* String from sysinfo(SI_PLATFORM) */
#define NT_AUXV        6        /* Contains copy of auxv array */
...
#define NT_386_TLS    0x200        /* i386 TLS slots (struct user_desc) */

NT_PRSTATUS와 NT_PRPSINFO는 프로세스 상태에 대한 일반적인 정보를 저장하는 것으로
각각에 해당하는 elf_prstatus 구조체와 elf_prpsinfo 구조체는
(GDB가 사용하는) /usr/include/sys/procfs.h 파일에 정의되어 있다.
NT_PRSTATUS 영역에서는 프로세스가 받은 signal, 지금까지 실행된 시간, 레지스터 값 등을 볼 수 있고
NT_PRPSINFO 영역에서는 프로그램의 이름, argument 목록, 사용자의 uid/gid 등을 볼 수 있다.

세 번째 NT_AUXV 영역은 커널에서 응용 프로그램으로 전달하는 정보로서
중요한 것으로는 vdso의 로딩 위치, dynamic loader (interpreter)의 로딩 위치 등이 있으며
이러한 정보는 /usr/include/elf.h 파일에 정의되어 있다.
참고로 프로그램 실행 시 LD_SHOW_AUXV 환경 변수를 1로 설정하면 이 값을 볼 수 있다.

$ LD_SHOW_AUXV=1 ./a.out
AT_SYSINFO:      0x42b420
AT_SYSINFO_EHDR: 0x42b000
AT_HWCAP:    fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe
AT_PAGESZ:       4096
AT_CLKTCK:       100
AT_PHDR:         0x8048034
AT_PHENT:        32
AT_PHNUM:        8
AT_BASE:         0x9ea000
AT_FLAGS:        0x0
AT_ENTRY:        0x8048420
AT_UID:          1000
AT_EUID:         1000
AT_GID:          1000
AT_EGID:         1000
AT_SECURE:       0
AT_RANDOM:       0xbff6240b
AT_EXECFN:       ./a.out
AT_PLATFORM:     i686
Segmentation fault (core dumped)

마지막 4번째 영역은 TLS에 관련된 정보이다.
리눅스는 GDT 내에 TLS 용으로 3개의 descriptor를 (6, 7, 8번) 예약해 두는데
그 중에서 첫 번째를 glibc가 사용한다.
strace 명령을 이용해서 시스템 콜 호출을 살펴보면
mmap()으로 1 페이지를 할당하여 set_thread_area()를 호출하는 것을 볼 수 있다.
이 때 사용되는 user_desc 구조체는 /usr/include/asm/ldt.h 파일에 정의되어 있다.

$ strace ./a.out 2>&1 | grep -B1 thread
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb785f000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb785f6c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0

이제 대강의 구성을  확인했으니
실제로 core 파일을 읽어서 NOTE 영역의 정보를 확인하는
간단한 프로그램을 작성해서 결과를 살펴보기로 하자.

그러기 위해선 ELF 파일의 구조를 알아야 하는데 다행히도 대부분의 정보는
/usr/include/elf.h 파일과 /usr/include/sys/procfs.h 파일에서 제공해 준다.
먼저 ELF 파일의 맨 처음에 위치한 ELF 헤더를 읽은 후 program 헤더의 위치를 알아낸 다음
가장 처음에 위치한 NOTE 영역에 대한 program 헤더를 읽어 NOTE 영역의 위치를 알아낸다.

readcore.c:
#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <sys/procfs.h>
#include <asm/ldt.h>

int main(void)
{
     FILE *fp;
     Elf32_Ehdr elf_hdr;
     Elf32_Phdr pgrm_hdr;

     fp = fopen("core", "r");

     /* read ELF header */
     fread(&elf_hdr, sizeof(elf_hdr), 1, fp); 

     /* seek to first program header (for NOTE) */
     fseek(fp, elf_hdr.e_phoff, SEEK_SET);

     /* read program header */
     fread(&pgrm_hdr, sizeof(pgrm_hdr), 1, fp); 

     /* seek to note */
     fseek(fp, pgrm_hdr.p_offset, SEEK_SET);

이제 NOTE 영역의 위치와 크기를 알았으니 전체 NOTE 영역을 읽어들인다.
또한 NOTE 영역의 4개의 부분 영역으로 구성된다는 것도 알고있다.
각 부분 영역은 Elf32_Nhdr 구조체에 대응하는 헤더가 먼저 나오고
그 뒤에 해당 영역의 이름이 나오고 (4 바이트 단위로 정렬됨), 그 뒤에 데이터가 존재한다.
먼저 헤더 정보를 통해 각 영역의 정보를 분석할 수 있는 함수를 작성해 보자.

static void * read_note(void *addr, void **pdata)
{
#define ALIGN(pos, size)  ((((pos) + (size-1)) / size) * size)

     Elf32_Nhdr *note_hdr = addr;

     /* print note name and type */
     printf("note:  %-8s (0x%03x)\n", (char *)(note_hdr+1), note_hdr->n_type);

     /* calculate data position */
     *pdata = (void *) ((unsigned long) (note_hdr+1) + ALIGN(note_hdr->n_namesz, 4));

     /* return pointer of next note header */
     return (void *) ((unsigned long) *pdata + ALIGN(note_hdr->n_descsz, 4));
}

위 함수는 헤더에 해당하는 주소를 받아서 다음 헤더의 주소를 반환한다.
또한 데이터 영역의 위치를 저장할 포인터도 받아서 적절히 설정해 준다.
(이미 전체 영역을 버퍼에 읽어 둔 상태이므로 시작 주소만 알면 된다.)
이제 main() 함수는 다음과 같이 간단하게 작성할 수 있다.

int main(void)
{
     prstatus_t *status;
     prpsinfo_t *psinfo;
     Elf32_auxv_t *auxv;
     struct user_desc *ldt;

     ...

     note = buf;
     note = read_note(note, (void **) &status);
     note = read_note(note, (void **) &psinfo);
     note = read_note(note, (void **) &auxv);
     note = read_note(note, (void **) &ldt);

이제 모든 정보를 다 읽었으니 결과를 출력해보자.
그 전에 이전 글을 먼저 확인해 보면 다음과 같은 결과를 기대할 수 있을 것이다.
  • core 파일을 생성한 프로그램의 이름: a.out
  • core 파일이 생성되는 시점의 레지스터 값: eip = 0x080483bc, eax = 0
  • 프로세스가 받은 signal: 11 (SIGSEGV)
  • dynamic loader의 (/lib/ld-2.10.1.so)의 위치: 0x009ea000 (AT_BASE)
  • vdso의 위치: 0x0042b000 (AT_SYSINFO_EHDR)
  • tls의 위치: 3개의 이름없는 영역 중의 하나 (0x00251000, 0xb7876000, 0xb788f000)
출력은 다음과 같이 하면 된다.

#define EAX    6
#define EIP    12

     printf("program name = %s\n", psinfo->pr_fname);
     printf("signo = %d\n", status->pr_info.si_signo);
     printf("EIP = 0x%08lx\nEAX = 0x%08lx\n", status->pr_reg[EIP], status->pr_reg[EAX]);

     /* print auxiliary vector */
     for ( ; auxv->a_type != AT_NULL; auxv++) {
      if (auxv->a_type == AT_BASE)
           printf("address of dynamic loader: %p\n", (void *) auxv->a_un.a_val);
      if (auxv->a_type == AT_SYSINFO_EHDR)
           printf("address of vdso: %p\n", (void *) auxv->a_un.a_val);
     }

     printf("gdt entry: %u, base: 0x%08x, limit: 0x%08lx\n",
        ldt->entry_number, ldt->base_addr & ~0xfff,
        ldt->limit_in_pages ? ldt->limit * PAGE_SIZE : ldt->limit);

     return 0;
}

예상한대로 다음과 같은 출력을 볼 수 있다.

$ gcc readcore.c
$ ./a.out
note:  CORE     (0x001)
note:  CORE     (0x003)
note:  CORE     (0x006)
note:  LINUX    (0x200)
program name = a.out
signo = 11
EIP = 0x080483bc
EAX = 0x00000000
address of vdso: 0x42b000
address of dynamic loader: 0x9ea000
gdt entry: 6, base: 0xb7876000, limit: 0xfffff000


by namhyung | 2009/11/29 19:05 | System | 트랙백(1) | 덧글(0)
트랙백 주소 : http://studyfoss.egloos.com/tb/5183491
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Tracked from tomato_house at 2010/02/09 17:18

제목 : core 파일의 구조 (2)
core 파일의 구조 (2)...more

:         :

:

비공개 덧글

◀ 이전 페이지 다음 페이지 ▶

카테고리
General
Application
System
Kernel
Book
Tips
태그
computer-architecture vcs elf x86 binutils perf blktrace awk patch C scm glibc block-layer emacs CAaQA3 documentation synchronization gcc SMP CARM algorithm compiler kernel git script bash sed build memory linux
전체보기
이글루 파인더

최근 등록된 덧글
informsi yang bagus dan b..
by agen qnc at 06/22
informasi yang bagus dan b..
by agen qnc at 06/22
informasi yang bagus dan b..
by agen qnc at 06/22
최근 등록된 트랙백
Tod's Ferrari Homme
by Tods Pas Cher,Kodak did ..
Mocassin Femme
by Mocassins Homme, I got so..
natural garcinia cambogia
by
rss

skin by jiinny


X