iOS 코드 다이어트 실천

머리말
본 고 는 실행 가능 한 파일 Mach-o 에서 사용 되 지 않 은 클래스,소스 링크 를 찾 는 데 정적 분석 방식 을 제공 할 것 입 니 다.xuezhulian/classunref ( 로 컬 다운로드 )。
Mach-o 파일 중DATA __objc_classrefs 세그먼트 에 인용 류 의 주 소 를 기록 하 였 습 니 다.DATA __objc_classlist 세그먼트 는 모든 종류의 주 소 를 기록 하고 차 집합 을 취하 면 사용 되 지 않 은 종류의 주 소 를 얻 을 수 있 으 며 기호 화 를 하면 인용 되 지 않 은 클래스 정 보 를 얻 을 수 있 습 니 다.
참조 클래스 주소
Mac 자체 가 가지 고 있 는 도구 인 otool 을 통 해 Mach-o 의 세그먼트 정 보 를 인쇄 할 수 있 습 니 다.주의해 야 할 것 은 시 뮬 레이 터 와 실제 컴퓨터 에 대응 하 는 실행 가능 한 파일 입 니 다.데이터 의 저장 방식 이 다 르 기 때문에 구분 해 야 합 니 다.
file 명령 을 통 해 arch 를 가 져 올 수 있 습 니 다.

#binary_file_arch: distinguish Big-Endian and Little-Endian
#file -b output example: Mach-O 64-bit executable arm64
binary_file_arch = os.popen('file -b ' + path).read().split(' ')[-1].strip()
클래스 주 소 를 찾 을 때 x86 구분64 와 arm.

def pointers_from_binary(line, binary_file_arch):
 line = line[16:].strip().split(' ')
 pointers = set()
 if binary_file_arch == 'x86_64':
  #untreated line example:00000001030cec80	d8 75 15 03 01 00 00 00 68 77 15 03 01 00 00 00
  pointers.add(''.join(line[4:8][::-1] + line[0:4][::-1]))
  pointers.add(''.join(line[12:16][::-1] + line[8:12][::-1]))
  return pointers
 #arm64 confirmed,armv7 arm7s unconfirmed
 if binary_file_arch.startswith('arm'):
  #untreated line example:00000001030bcd20	03138580 00000001 03138878 00000001
  pointers.add(line[1] + line[0])
  pointers.add(line[3] + line[2])
  return pointers
 return None
otool-v-s 를 통 해DATA __objc_classrefs 에서 인용 류 의 주 소 를 가 져 옵 니 다.

def class_ref_pointers(path, binary_file_arch):
  ref_pointers = set()
  lines = os.popen('/usr/bin/otool -v -s __DATA __objc_classrefs %s' % path).readlines()
  for line in lines:
    pointers = pointers_from_binary(line, binary_file_arch)
    ref_pointers = ref_pointers.union(pointers)
  return ref_pointers
모든 클래스 주소
otool-v-s 를 통 해DATA __objc_classlist 에서 모든 종류의 주 소 를 가 져 옵 니 다.

def class_list_pointers(path, binary_file_arch):
  list_pointers = set()
  lines = os.popen('/usr/bin/otool -v -s __DATA __objc_classlist %s' % path).readlines()
  for line in lines:
    pointers = pointers_from_binary(line, binary_file_arch)
    list_pointers = list_pointers.union(pointers)
  return list_pointers
차출 집
모든 종류의 정보 로 인용 류 의 정 보 를 빼 면 사용 하지 않 은 종류의 주소 정 보 를 얻 을 수 있 습 니 다.

unref_pointers = class_list_pointers(path, binary_file_arch) - class_ref_pointers(path, binary_file_arch)
기호 화
nm-nm 명령 을 통 해 주소 와 대응 하 는 클래스 이름 을 얻 을 수 있 습 니 다.

def class_symbols(path):
  symbols = {}
  #class symbol format from nm: 0000000103113f68 (__DATA,__objc_data) external _OBJC_CLASS_$_EpisodeStatusDetailItemView
  re_class_name = re.compile('(\w{16}) .* _OBJC_CLASS_\$_(.+)')
  lines = os.popen('nm -nm %s' % path).readlines()
  for line in lines:
    result = re_class_name.findall(line)
    if result:
      (address, symbol) = result[0]
      symbols[address] = symbol
  return symbols
여과
실제 분석 과정 에서 만약 에 한 유형의 하위 클래스 가 실례 화 되 고 부류 가 실례 화 되 지 않 으 면 부류 가 에 나타 나 지 않 는 다 는 것 을 발견 했다.objc_classrefs 이 단락 에 서 는 사용 되 지 않 은 클래스 에서 이 부분 부모 클래스 를 걸 러 내야 합 니 다.otool-oV 를 사용 하면 클래스 의 계승 관 계 를 얻 을 수 있 습 니 다.

def filter_super_class(unref_symbols):
  re_subclass_name = re.compile("\w{16} 0x\w{9} _OBJC_CLASS_\$_(.+)")
  re_superclass_name = re.compile("\s*superclass 0x\w{9} _OBJC_CLASS_\$_(.+)")
  #subclass example: 0000000102bd8070 0x103113f68 _OBJC_CLASS_$_TTEpisodeStatusDetailItemView
  #superclass example: superclass 0x10313bb80 _OBJC_CLASS_$_TTBaseControl
  lines = os.popen("/usr/bin/otool -oV %s" % path).readlines()
  subclass_name = ""
  superclass_name = ""
  for line in lines:
    subclass_match_result = re_subclass_name.findall(line)
    if subclass_match_result:
      subclass_name = subclass_match_result[0]
    superclass_match_result = re_superclass_name.findall(line)
    if superclass_match_result:
      superclass_name = superclass_match_result[0]

    if len(subclass_name) > 0 and len(superclass_name) > 0:
      if superclass_name in unref_symbols and subclass_name not in unref_symbols:
        unref_symbols.remove(superclass_name)
      superclass_name = ""
      subclass_name = ""
  return unref_symbols
일부 3 자 라 이브 러 리 의 오 해 를 방지 하기 위해 서 는 접두사 나 접두사 만 있 는 클래스 를 걸 러 낼 수 있 습 니 다.

 for unref_pointer in unref_pointers:
    if unref_pointer in symbols:
      unref_symbol = symbols[unref_pointer]
      if len(reserved_prefix) > 0 and not unref_symbol.startswith(reserved_prefix):
        continue
      if len(filter_prefix) > 0 and unref_symbol.startswith(filter_prefix):
        continue
      unref_symbols.add(unref_symbol)
최종 결 과 는 스 크 립 트 디 렉 터 리 에 저 장 됩 니 다.

script_path = sys.path[0].strip()
f = open(script_path+"/result.txt","w")
f.write( "unref class number:  %d
" % len(unref_symbles)) f.write("
") for unref_symble in unref_symbles: f.write(unref_symble+"
") f.close()
이 사 고 는 어느 정도 에 코드 의 번 거 로 움 을 줄 이 고 가방 의 부 피 를 줄 일 수 있다.정적 분석 이기 때문에 동적 호출 상황 을 포함 할 수 없고 삭제 해 야 할 클래스 에 대해 더 확인 해 야 합 니 다.
총결산
이상 은 이 글 의 모든 내용 입 니 다.본 고의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가 치 를 가지 기 를 바 랍 니 다.여러분 의 저희 에 대한 지지 에 감 사 드 립 니 다.

좋은 웹페이지 즐겨찾기