NAME

dl_iterate_phdr - 공유 오브젝트 목록 순회하기

SYNOPSIS

#define _GNU_SOURCE         /* feature_test_macros(7) 참고 */
#include <link.h>

int dl_iterate_phdr(
          int (*callback)(struct dl_phdr_info *info,
                          size_t size, void *data),
          void *data);

DESCRIPTION

dl_iterate_phdr() 함수를 이용해 응용에서 어떤 공유 오브젝트들을 적재했고 어떤 순서로 적재됐는지 런타임에 질의할 수 있다.

dl_iterate_phdr() 함수는 응용의 공유 오브젝트 목록을 순회하면서 각 오브젝트마다 함수 callback을 호출한다. 모든 공유 오브젝트를 처리했으면, 또는 callback이 0 아닌 값을 반환하면 멈춘다.

callback 호출은 세 가지 인자를 받는다. info는 공유 오브젝트에 대한 정보를 담은 구조체의 포인터고 sizeinfo가 가리키는 구조체의 크기다. data는 호출 프로그램에서 dl_iterate_phdr() 호출 때 (역시 이름이 data인) 두 번째 인자로 준 값의 사본이다.

info 인자는 다음 타입의 구조체다.

struct dl_phdr_info {
    ElfW(Addr)        dlpi_addr;  /* 오브젝트의 기준 주소 */
    const char       *dlpi_name;  /* 오브젝트의 (널 종료) 이름 */
    const ElfW(Phdr) *dlpi_phdr;  /* 이 오브젝트의 ELF 프로그램
                                     헤더 배열의 포인터 */
    ElfW(Half)        dlpi_phnum; /* dlpi_phdr의 항목 개수 */

    /* 다음 필드들은 이 구조체의 첫 번째 버전이 공개된 후에
       glibc 2.4에서 추가된 것들이다. dl_iterate_phdr 콜백에
       전달되는 size 인자를 사용해서 이후 멤버 각각이 사용
       가능한지 여부를 확인해야 한다. */

    unsigned long long dlpi_adds;
                    /* 새 오브젝트가 추가됐을 수 있을 때
                       증가됨 */
    unsigned long long dlpi_subs;
                    /* 오브젝트가 제거됐을 수 있을 때
                       증가됨 */
    size_t dlpi_tls_modid;
                    /* PT_TLS 세그먼트가 있는 경우는 TLS 재배치에
                       쓰인 그 모듈 ID, 아니면 0 */
    void  *dlpi_tls_data;
                    /* 이 모듈의 PT_TLS 세그먼트 인스턴스가
                       하나 있고 호출 스레드에 할당돼 있으면
                       그 주소, 아니면 널 포인터 */
};

(ElfW() 매크로는 인자를 하드웨어 아키텍처에 맞는 ELF 데이터 타입 이름으로 바꿔 준다. 예를 들어 32비트 플랫폼에서 ElfW(Addr)은 데이터 타입 이름 Elf32_Addr을 내놓는다. 그런 타입들에 대한 추가 정보를 헤더 파일 <elf.h><link.h>에서 찾을 수 있다.)

dlpi_addr 필드는 공유 오브젝트의 기준 주소를 나타낸다. (즉 공유 오브젝트의 가상 메모리 주소와 그 오브젝트를 적재한 파일 내 오프셋 간의 차이다.) dlpi_name 필드는 그 공유 오브젝트를 적재한 경로명을 알려 주는 널 종료 문자열이다.

dlpi_phdrdlpi_phnum 필드의 의미를 이해하려면 ELF 공유 오브젝트가 여러 세그먼트로 이뤄져 있고 각 세그먼트를 기술하는 대응하는 프로그램 헤더가 있다는 점을 알아야 한다. dlpi_phdr 필드는 이 공유 오브젝트의 프로그램 헤더들의 배열에 대한 포인터다. dlpi_phnum 필드는 그 배열의 크기를 나타낸다.

그 프로그램 헤더들은 다음 형태의 구조체다.

typedef struct {
    Elf32_Word  p_type;    /* 세그먼트 타입 */
    Elf32_Off   p_offset;  /* 세그먼트 파일 오프셋 */
    Elf32_Addr  p_vaddr;   /* 세그먼트 가상 주소 */
    Elf32_Addr  p_paddr;   /* 세그먼트 물리 주소 */
    Elf32_Word  p_filesz;  /* 파일 내 세그먼트 크기 */
    Elf32_Word  p_memsz;   /* 메모리 내 세그먼트 크기 */
    Elf32_Word  p_flags;   /* 세그먼트 플래그 */
    Elf32_Word  p_align;   /* 세그먼트 정렬 */
} Elf32_Phdr;

참고로 다음 식을 써서 특정 프로그램 헤더 x의 가상 메모리 내 위치를 계산할 수 있다.

addr == info->dlpi_addr + info->dlpi_phdr[x].p_vaddr;

p_type에 가능한 값들로 다음이 있다. (더 자세한 내용은 <elf.h> 참고.)

#define PT_LOAD         1    /* 적재 가능 프로그램 세그먼트 */
#define PT_DYNAMIC      2    /* 동적 링크 정보 */
#define PT_INTERP       3    /* 프로그램 인터프리터 */
#define PT_NOTE         4    /* 보조 정보 */
#define PT_SHLIB        5    /* 예비 */
#define PT_PHDR         6    /* 헤더 테이블 자체를 위한 항목 */
#define PT_TLS          7    /* 스레드 로컬 저장 공간 세그먼트 */
#define PT_GNU_EH_FRAME 0x6474e550 /* GCC .eh_frame_hdr 세그먼트 */
#define PT_GNU_STACK  0x6474e551 /* 스택 실행 가능 여부 표시 */
#define PT_GNU_RELRO  0x6474e552 /* 재배치 후 읽기 전용 */

RETURN VALUE

dl_iterate_phdr() 함수는 마지막 callback 호출이 반환한 값을 그대로 반환한다.

VERSIONS

glibc 버전 2.2.4부터 dl_iterate_phdr()를 지원한다.

ATTRIBUTES

이 절에서 사용하는 용어들에 대한 설명은 attributes(7)를 보라.

인터페이스 속성
dl_iterate_phdr() 스레드 안전성 MT-Safe

CONFORMING TO

dl_iterate_phdr() 함수는 어느 표준에도 명세돼 있지 않다. 다른 여러 시스템에서도 이 함수를 제공하지만 반환되는 dl_phdr_info 구조체의 세부 내용이 다르다. BSD와 솔라리스에서는 구조체에 dlpi_addr, dlpi_name, dlpi_phdr, dlpi_phnum 필드에 더해서 다른 구현별 필드들이 포함돼 있다.

NOTES

C 라이브러리의 이후 버전에서 dl_phdr_info 구조체에 필드를 더 추가할 수도 있다. 그 경우 콜백 함수에서는 size 인자를 통해 추가 필드가 있는 시스템에서 동작 중인지 알아낼 수 있다.

callback이 방문하는 첫 번째 오브젝트는 메인 프로그램이다. 메인 프로그램에서 dlpi_name 필드는 빈 문자열이 된다.

EXAMPLES

다음 프로그램에서는 적재한 공유 오브젝트들의 경로명 목록을 표시한다. 그리고 각 공유 오브젝트에 대해서 각 ELF 세그먼트의 정보(가상 주소, 크기, 플래그, 타입)를 나열한다.

다음 셸 세션은 x86-64 시스템에서 프로그램이 내놓는 출력을 보여 준다. 출력 첫 번째의 (이름이 빈 문자열인) 공유 오브젝트는 메인 프로그램이다.

$ ./a.out
Name: "" (9 segments)
     0: [      0x400040; memsz:    1f8] flags: 0x5; PT_PHDR
     1: [      0x400238; memsz:     1c] flags: 0x4; PT_INTERP
     2: [      0x400000; memsz:    ac4] flags: 0x5; PT_LOAD
     3: [      0x600e10; memsz:    240] flags: 0x6; PT_LOAD
     4: [      0x600e28; memsz:    1d0] flags: 0x6; PT_DYNAMIC
     5: [      0x400254; memsz:     44] flags: 0x4; PT_NOTE
     6: [      0x400970; memsz:     3c] flags: 0x4; PT_GNU_EH_FRAME
     7: [         (nil); memsz:      0] flags: 0x6; PT_GNU_STACK
     8: [      0x600e10; memsz:    1f0] flags: 0x4; PT_GNU_RELRO
Name: "linux-vdso.so.1" (4 segments)
     0: [0x7ffc6edd1000; memsz:    e89] flags: 0x5; PT_LOAD
     1: [0x7ffc6edd1360; memsz:    110] flags: 0x4; PT_DYNAMIC
     2: [0x7ffc6edd17b0; memsz:     3c] flags: 0x4; PT_NOTE
     3: [0x7ffc6edd17ec; memsz:     3c] flags: 0x4; PT_GNU_EH_FRAME
Name: "/lib64/libc.so.6" (10 segments)
     0: [0x7f55712ce040; memsz:    230] flags: 0x5; PT_PHDR
     1: [0x7f557145b980; memsz:     1c] flags: 0x4; PT_INTERP
     2: [0x7f55712ce000; memsz: 1b6a5c] flags: 0x5; PT_LOAD
     3: [0x7f55716857a0; memsz:   9240] flags: 0x6; PT_LOAD
     4: [0x7f5571688b80; memsz:    1f0] flags: 0x6; PT_DYNAMIC
     5: [0x7f55712ce270; memsz:     44] flags: 0x4; PT_NOTE
     6: [0x7f55716857a0; memsz:     78] flags: 0x4; PT_TLS
     7: [0x7f557145b99c; memsz:   544c] flags: 0x4; PT_GNU_EH_FRAME
     8: [0x7f55712ce000; memsz:      0] flags: 0x6; PT_GNU_STACK
     9: [0x7f55716857a0; memsz:   3860] flags: 0x4; PT_GNU_RELRO
Name: "/lib64/ld-linux-x86-64.so.2" (7 segments)
     0: [0x7f557168f000; memsz:  20828] flags: 0x5; PT_LOAD
     1: [0x7f55718afba0; memsz:   15a8] flags: 0x6; PT_LOAD
     2: [0x7f55718afe10; memsz:    190] flags: 0x6; PT_DYNAMIC
     3: [0x7f557168f1c8; memsz:     24] flags: 0x4; PT_NOTE
     4: [0x7f55716acec4; memsz:    604] flags: 0x4; PT_GNU_EH_FRAME
     5: [0x7f557168f000; memsz:      0] flags: 0x6; PT_GNU_STACK
     6: [0x7f55718afba0; memsz:    460] flags: 0x4; PT_GNU_RELRO

프로그램 소스

#define _GNU_SOURCE
#include <link.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

static int
callback(struct dl_phdr_info *info, size_t size, void *data)
{
    char *type;
    int p_type;

    printf("Name: \"%s\" (%d segments)\n", info->dlpi_name,
               info->dlpi_phnum);

    for (int j = 0; j < info->dlpi_phnum; j++) {
        p_type = info->dlpi_phdr[j].p_type;
        type =  (p_type == PT_LOAD) ? "PT_LOAD" :
                (p_type == PT_DYNAMIC) ? "PT_DYNAMIC" :
                (p_type == PT_INTERP) ? "PT_INTERP" :
                (p_type == PT_NOTE) ? "PT_NOTE" :
                (p_type == PT_INTERP) ? "PT_INTERP" :
                (p_type == PT_PHDR) ? "PT_PHDR" :
                (p_type == PT_TLS) ? "PT_TLS" :
                (p_type == PT_GNU_EH_FRAME) ? "PT_GNU_EH_FRAME" :
                (p_type == PT_GNU_STACK) ? "PT_GNU_STACK" :
                (p_type == PT_GNU_RELRO) ? "PT_GNU_RELRO" : NULL;

        printf("    %2d: [%14p; memsz:%7jx] flags: %#jx; ", j,
                (void *) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr),
                (uintmax_t) info->dlpi_phdr[j].p_memsz,
                (uintmax_t) info->dlpi_phdr[j].p_flags);
        if (type != NULL)
            printf("%s\n", type);
        else
            printf("[other (%#x)]\n", p_type);
    }

    return 0;
}

int
main(int argc, char *argv[])
{
    dl_iterate_phdr(callback, NULL);

    exit(EXIT_SUCCESS);
}

SEE ALSO

ldd(1), objdump(1), readelf(1), dladdr(3), dlopen(3), elf(5), ld.so(8)

Executable and Linking Format Specification, 온라인 여러 곳에서 구할 수 있음.


2021-03-22