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
태그
compiler bash scm sed emacs SMP linux documentation vcs script synchronization algorithm x86 CARM awk patch binutils CAaQA3 block-layer glibc build computer-architecture perf kernel memory elf gcc git blktrace C
전체보기
이글루 파인더

최근 등록된 덧글
http://serbaserbiinfoterkini56..
by cakra at 09/22
informsi yang bagus dan b..
by cakra at 09/16
informsi yang bagus dan be..
by pordanaia at 08/05
최근 등록된 트랙백
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