ELF 형식의.gnu.hash 구역 과 glibc 의 기호 조회 전 과정 을 인 스 턴 스 로 분석 합 니 다.

머리말
ELF 형식의 gnu.hash 절 은 디자인 에 있어 복잡 하고 glibc 소스 코드 에서 직접 분석 하 는 것 도 어렵 습 니 다.오늘 은 마음 을 가 라 앉 히 고 보 았 다이 멋 진 문장드디어 부 릉 필터,산수 연산 을 비트 연산 으로 바 꾸 는 등 일련의 세부 사항 을 알 게 되 었 다.그러나 본인 은 매우 우둔 하고 완전한 워 크 스 루 가 없 으 면 자신 이 정말 무엇 을 알 고 있다 고 생각 할 수 없습니다.따라서 본 고 는 기호의 실제 상황 을 찾 는 것 에서 출발 하여 ELF 형식 이 기 호 를 어떻게 구성 하 는 지,그리고 동적 링크 기 가 이러한 정 보 를 어떻게 읽 고 처리 하여 기호 조 회 를 하 는 전 과정 을 상세 하 게 설명 한다.
본 고 는 독자 가 이미 앞에서 언급 한 블 로 그 를 읽 었 다 고 가정 하고 부 릉 필터,GNU hash 가 사용 하 는 단일 해시 전략 을 이해 하여 모델 링 을 이러한 명사 로 바 꾸 었 다.나중에 시간 이 있 을 때 저 는 그들 에 대해 간단하게 소개 할 수 있 지만 주옥 은 앞에서 추 태 를 보이 고 싶 지 않 았 습 니 다.
본 논문 의 실현 과 so 파일 은 모두 glibc 2.31 을 기준 으로 한다.
기호 해시,기호 표 와 문자 표
하나의 기호 에 대한 정 보 는 ELF 파일 에서 dynamic section 의 세 조각 에 나타 납 니 다.gnu.hash 에 대응 하 는 기호 해시,dynsim 에 대응 하 는 동적 기호 표,dynstr 에 대응 하 는 문자 표 입 니 다.기 호 를 찾 을 때 동적 링크 기 는 먼저.gnu.hash 에서 조회 하여 이 기호 가 동적 기호 표 에서 의 오프셋 을 얻 을 수 있 습 니 다.동적 링크 기 는 이 오프셋 에 따라 기 호 를 읽 고 이 기호의 이름 이 문자 표 에서 의 오프셋 을 찾 습 니 다.문자 표 에서 기호의 이름 을 읽 고 찾 으 려 는 기호 와 일치 하면 이 기 호 를 찾 은 다음 기호 표 에서 기호의 관련 정 보 를 읽 고 되 돌려 줍 니 다.
64 비트 ELF 형식의 기호 정 의 는 다음 과 같 습 니 다.

// in <elf.h>
typedef struct
{
  // 32 bits
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */
  // 8 bit
  unsigned char	st_info;		/* Symbol type and binding */
  // 8 bit
  unsigned char st_other;		/* Symbol visibility */
  // 16 bits
  Elf64_Section	st_shndx;		/* Section index */
  // 64 bits
  Elf64_Addr	st_value;		/* Symbol value */
  // 64 bits
  Elf64_Xword	st_size;		/* Symbol size */
} Elf64_Sym;
이 데이터 구조 가 메모리 의 크기 를 24B 로 차지 하 는 것 도 파일 크기 를 절약 하기 위해 구성원 순 서 를 합 리 적 으로 배정 하 는 예 이다.
gnu.hash 의 구조
glibc 는 다음 함수 로 ELF 파일 에서 기호 해시 에 관 한 정 보 를 읽 습 니 다.

// in elf/dl-lookup.c
void
_dl_setup_hash (struct link_map *map)
{
  Elf_Symndx *hash;

  if (__glibc_likely (map->l_info[ELF_MACHINE_GNU_HASH_ADDRIDX] != NULL))
    {
      //     32       ,          ,  hash32
      Elf32_Word *hash32
	= (void *) D_PTR (map, l_info[ELF_MACHINE_GNU_HASH_ADDRIDX]);
      map->l_nbuckets = *hash32++;
      Elf32_Word symbias = *hash32++;
      Elf32_Word bitmask_nwords = *hash32++;
      /* Must be a power of two.  */
      assert ((bitmask_nwords & (bitmask_nwords - 1)) == 0);
      map->l_gnu_bitmask_idxbits = bitmask_nwords - 1;
      map->l_gnu_shift = *hash32++;

      map->l_gnu_bitmask = (ElfW(Addr) *) hash32;
      hash32 += __ELF_NATIVE_CLASS / 32 * bitmask_nwords;

      map->l_gnu_buckets = hash32;
      hash32 += map->l_nbuckets;
      map->l_gnu_chain_zero = hash32 - symbias;

      /* Initialize MIPS xhash translation table.  */
      ELF_MACHINE_XHASH_SETUP (hash32, symbias, map);

      return;
    }
  //        DT_HASH ,    
  if (!map->l_info[DT_HASH])
    return;
  hash = (void *) D_PTR (map, l_info[DT_HASH]);//Q: what about some non-GNU ELFs

  map->l_nbuckets = *hash++;
  /* Skip nchain.  */
  hash++;
  map->l_buckets = hash;
  hash += map->l_nbuckets;
  map->l_chain = hash;
}
상기 코드 는 관건 적 인 변수 할당 값 을 읽 었 습 니 다:lnbuckets,symbias,bitmask_nwords,l_gnu_shift,l_gnu_buckets,l_gnu_chain_zero。이 중"l"로 시작 하 는 변 수 를 ELF 파일 에 저장 하 는 linkmap 에서 구체 적 인 정 의 는참조.그리고 파일 에서 읽 을 수 없 는 변수 lgnu_bitmask_idxbits,그들의 구체 적 인 의 미 는:
  • l_nbuckets:해시 통 사용 수량
  • 4.567917.symbias:동적 기호 표 에서 외부 에서 접근 할 수 없 는 기호 수량 이지 만 동적 기호 표 항목 을 차지 합 니 다
  • bitmask_nwords:bitmask 사용nwords 개 글 자 는 부 릉 필터 의 벡터 로 서
  • l_gnu_shift:같은 해시 함 수 를 사용 하여 k=2 의 부 릉 여과 기 를 실현 하기 위해 서 는 오른쪽으로 이동 하 는 자릿수 가 필요 합 니 다
  • l_gnu_buckets:해시 통 의 시작 주소
  • l_gnu_chain_zero:기호 해시 값 의 시작 주소
  • l_gnu_bitmask_idxbits:bitmasknwords 취 모 를 취 합 으로 바 꾸 고 bitmasknwords-1 에서 왔 습 니 다.
    이해 하기 편리 하도록 gnu.hash 절의 내용 을 설명도 로 그립 니 다.

    libc 를 예 로 들 면.대응 하 는 필드 의 값 검사:
    
    $ objdump -s /lib/x86_64-linux-gnu/libc.so.6 | grep .gnu.hash -A 5
    
    Contents of section .gnu.hash:
     38a0 (f3030000)        (0c000000)      (00010000)            (0e000000)     ................
          ->l_nbuckets=1011 ->symbias=12    ->bitmask_nwords=256  ->l_gnu_shift=14
     38b0 (00301044 a0200201) (8803e690 c5458c00)  .0.D. .......E..
          ->   bloom word 0x010220a044103000                  
     38c0 c4005800 07840070 c280010d 8a0c4104  ..X....p......A.
     38d0 10008840 32082a40 88543c2d 200e3248  ...@2.*@.T<- .2H
     38e0 2684c08c 04080002 020ea1ac 1a0666c8  &.............f.
    Symbias=12,즉 12 개의 내부 기호 가 있 습 니 다.
    
    $ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | head -n 20
    
    Symbol table '.dynsym' contains 2367 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __libpthread_freeres
         2: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND _rtld_global@GLIBC_PRIVATE (33)
         3: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND __libc_enable_secure@GLIBC_PRIVATE (33)
         4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __tls_get_addr@GLIBC_2.3 (34)
         5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _dl_exception_create@GLIBC_PRIVATE (33)
         6: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND _rtld_global_ro@GLIBC_PRIVATE (33)
         7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __tunable_get_val@GLIBC_PRIVATE (33)
         8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _dl_find_dso_for_object@GLIBC_PRIVATE (33)
         9: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _dl_starting_up
        10: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __libdl_freeres
        11: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND _dl_argv@GLIBC_PRIVATE (33)
        12: 00000000000ab970    33 FUNC    GLOBAL DEFAULT   16 __strspn_c1@GLIBC_2.2.5
        13: 0000000000089260   352 FUNC    GLOBAL DEFAULT   16 putwchar@@GLIBC_2.2.5
        14: 00000000001324f0    20 FUNC    GLOBAL DEFAULT   16 __gethostname_chk@@GLIBC_2.4
        15: 00000000000ab9a0    44 FUNC    GLOBAL DEFAULT   16 __strspn_c2@GLIBC_2.2.5
        16: 000000000014f580   218 FUNC    GLOBAL DEFAULT   16 setrpcent@@GLIBC_2.2.5
    보 이 는 기호 0-11 은 내부 기호 이다.
    찾기 기호
    다음은 기호 printf 를 찾 는 것 을 예 로 들 어 기호 찾기 과정 을 소개 합 니 다.
    우선 아래 의 해시 함 수 를 사용 하여 기 호 를 만 드 는 32 비트 해시:
    
    // in elf/dl-lookup.c
    static uint_fast32_t
    dl_new_hash (const char *s)
    {
      uint_fast32_t h = 5381;
      for (unsigned char c = *s; c != '\0'; c = *++s)
        h = h * 33 + c;
      return h & 0xffffffff;
    }
    printf 를 얻 은 해시 값 은 0x 156 b2bb 8 입 니 다.
    다음 에 부 릉 필터 에 필요 한 두 개의 hashbit 를 계산 합 니 다.
    
    unsigned int hashbit1 = new_hash & (__ELF_NATIVE_CLASS - 1);
        unsigned int hashbit2 = ((new_hash >> l->l_gnu_shift) & (__ELF_NATIVE_CLASS - 1));
    hashbit 1=56,hashbit 2=44 를 얻 었 습 니 다.
    이 hash 에 대응 하 는 Bloom word 를 찾 았 습 니 다:
    
      const Elf64_Addr *bitmask = l->l_gnu_bitmask;
        // l->l_gnu_bitmask_idxbits = bitmask_nwords - 1,       
        // (new_hash / __ELF_NATIVE_CLASS) & l->l_gnu_bitmask_idxbits = 174
        Elf64_Addr bitmask_word = bitmask[(new_hash / __ELF_NATIVE_CLASS) & l->l_gnu_bitmask_idxbits];
    printf 에 대응 하 는 hash 는 174 번 째 Bloom word 에 있 습 니 다.이 값 은 Bloom word 의 시작 주소 0x38b 0+174*8=3e 20 에 있 습 니 다.
    3e 20 곳 에 대응 하 는 값 검사:
    
    $ objdump -s /lib/x86_64-linux-gnu/libc.so.6 | grep " 3e20 "
    
     3e20 d0884a41 c0703429 10ec4303 92003103  ..JA.p4)..C...1.
    그 bloom word 는 0x293470c0414a88d 0 이다.
    오른쪽으로 56 비트 이동:0b 0010 1001
    오른쪽으로 44 위 이동:0b 10 1001 0011 0100 0111
    두 사람의 마지막 자 리 는 모두 1 로 부 릉 필터 가 이 해시 값 을 거절 할 수 없다 는 것 을 의미한다.
    이때 해당 하 는 해시 통 에서 찾기:
    
    Elf32_Word bucket = l->l_gnu_buckets[new_hash % l->l_nbuckets];
    0x 156 b2bb 8%1011=295 로 인해 296 번 째 해시 통 을 찾 아야 합 니 다.
    하 쉬 통 의 시작 주 소 는 lgnu_bitmask + 64 / 32 * bitmask_nwords=0x40b 0,해시 통 에 대응 하 는 주 소 는 0x40b 0+295*4=0x454c 입 니 다.
    0x454c 에 대응 하 는 해시 통 내용 보기:
    
    $ objdump -s /lib/x86_64-linux-gnu/libc.so.6 | grep " 4540 "
    
     4540 77020000 00000000 7a020000 **7c020000**  w.......z...|...
    해시 통 의 내용 은 0x27c 이다.
    그리고 lgnu_chain_zero 의 주 소 는:
    
      l_gnu_chain_zero = l_gnu_buckets + l_nbuckets - symbias;
    계산 가능 lgnu_chain_zero 의 주 소 는 0x504c 이기 때문에 296 번 째 해시 통 에 포 함 된 진정한 하 시 는 0x504c+27c*4=0x5a3c 에 있 습 니 다.
    구체 적 인 해시 내용 보기:
    
    $ objdump -s /lib/x86_64-linux-gnu/libc.so.6 | grep " 5a30 " -A 2
    
     5a30 ade8dbbb 142dcb13 bb86f85f e6952000  .....-....._.. .
     5a40 **b82b6b15** 0a05f1d5 deb6427f 856177fd  .+k.......B..aw.
     5a50 1ae585e7 ec296fa8 1ae585e7 29ce248f  .....)o.....).$.
    0x5a 40 곳 에서 우리 가 계산 한 해시 0x 156 b2bb 8 을 찾 았 습 니 다.
    이때 이 기 호 는'gnu.hash'의 아래 에 표 시 됩 니 다.바로 동적 기호 표 에 있 는(아래 표-symbias)입 니 다.하지만 전에 lgnu_chain_zero 는 이미 전체적으로 symbias 를 줄 였 기 때문에 이 기호의 주소 로 l 을 줄 입 니 다.gnu_chain_zero 는 기호 표 의 아래 표 시 를 직접 얻 을 수 있다.
    0x5a 40-0x504c=0x9f 4=2548,하나의 해시 값 이 4 바이트 이기 때문에 아래 는 2548/4=637 로 표시 합 니 다.
    동적 기호 표 의 시작 주 소 를 찾 습 니 다:
    
    $ objdump -s /lib/x86_64-linux-gnu/libc.so.6 | grep .dynsym -A 1
    
    Contents of section .dynsym:
     07548 00000000 00000000 00000000 00000000  ................
    앞에서 언급 한 바 와 같이 64 비트 ELF 파일 중 하나의 기호의 길 이 는 24 바이트 이 므 로 기호 표 의 시작 주 소 는 0x 7548+24*637=0xb 100 이 어야 한다.
    동적 기호 표 가 대응 하 는 위치의 내용 을 찾 습 니 다:
    
    $ objdump -s /lib/x86_64-linux-gnu/libc.so.6 | grep " 0b0f8 " -A 1
    
     0b0f8 16000000 00000000 **f3040000** 12001000  ................
     0b108 104e0600 00000000 cc000000 00000000  .N..............
    읽 기 기호 가 문자 표 에서 의 오프셋 은 0x4f 3 이다.
    문자 테이블 의 시작 주 소 를 찾 습 니 다:
    
    $ objdump -s /lib/x86_64-linux-gnu/libc.so.6 | grep .dynstr -A 1
    
    Contents of section .dynstr:
     15330 00786472 5f755f6c 6f6e6700 5f5f7763  .xdr_u_long.__wc
    시작 주 소 는 0x 15330 이 므 로 이 기호의 주 소 는 0x 15330+0x4f 3=0x 15823 입 니 다.
    문자 테이블 의 대응 하 는 위치의 값 읽 기:
    
    $ objdump -s /lib/x86_64-linux-gnu/libc.so.6 | grep " 15820 " -A 1
    
     15820 494f5f**70** 72696e74 66007265 67697374  IO_printf.regist
     15830 65725f70 72696e74 665f6675 6e637469  er_printf_functi
    기호 printf 를 찾 았 습 니 다.IO 입 니 다.printf 의 별명 은 문자 표 에서 공간 을 절약 하기 위해 두 사람 을 합 쳤 습 니 다.
    이렇게 해서 기호 조회 의 전 과정 을 완성 했다.
    이상 은 ELF 형식의.gnu.hash 구역 과 glibc 의 기호 에서 찾 은 상세 한 내용 입 니 다.ELF 형식의.gnu.hash 구역 과 glibc 의 기호 에서 찾 은 자 료 는 다른 관련 글 에 주목 하 십시오!
  • 좋은 웹페이지 즐겨찾기