태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.
2017.09.29 08:30


 [Effective Objective-C] #35 좀비를 이용해 메모리 관리 오류를 디버깅하라


출처 : Effective Objective-C

arc, core memory, corefoundation, correct tool, dealloc, Diagnostics, enable zombie objects, Foundation, Free, Instruments, ISA, marker, message sent to deallocated instance, NSZombieEnable, objc_duplicateClass, respondsToSelector, root class, Run configuration, Runtime, xcode, Zombie, [Effective Objective-C] #35 좀비를 이용해 메모리 관리 오류를 디버깅하라, _nszombie, __forwarding__, 교정 도구, 덮어쓰기, 디버깅, 디버깅 도구, 런타임, 런타임 함수, 메모리 관리 오류, 메모리 누수, 메모리 버그, 메모리 일부분 재사용, 메모리 재사용 여부 알 수 있는 방법, 메모리 해제, 새로운 이름 부여, 스위즐링, 앱 스킴 편집, 엑스코드, 예외, 완전 포워드 기술, 좀비, 좀비 메모리, 좀비 메세지, 체크 박스, 최상위 클래스, 코어 메모리, 코코아, 크래시, 클래스 복사, 템플릿 클래스, 할당 해제, 할당 해제된 인스턴스 좀비 객체 전환, 환경 변수


-

메모리 관리 오류를 디버깅하는 것은 매우 어려운 일이다.

할당 해제된 객체에 메시지를 보내는 일은 예상할 수 있듯이 전혀 안전하지 못하다.

그러나 때때로 그것은 동작하기도 하고 동작하지 않기도 하는데 이는 객체가 사용했던 메모리를 덮어 썼느냐에 달려있다.

메모리가 재사용되었는지 알 수 있는 방법은 없다.

그로 인해 크래시가 가끔 일어난다.

메모리의 일부분만 재사용되는 경우도 있는데 이 때 객체의 일부분은 여전히 유효하다.

또한 순전히 운으로 또 다른 유효한 살아 있는 객체가 메모리를 덮어쓸 수도 있다.

이 경우 런타임은 새로운 객체에 메시지를 보낼 것이고 객체는 응답을 할 수도, 하지 않을 수도 있다.

이 경우 크래시는 발생하지 않겠지만, 왜 메시지를 보낸 객체가 여러분의 의도대도 동작하지 않는지 알 수 없을 것이다.

객체가 선택자에 응답하지 않으면 앱은 보통 크래시된다.



-

운 좋게도 코코아에는 손쉽게 사용할 수 있는 ‘좀비(zombie)’라는 기능이 있다.

이 디버깅 기능을 활성화하면 런타임은 할당 해제된 인스턴스를 즉시 제거하지 않고 특별한 좀비 객체로 바꾼다.



-

객체가 사용한 코어 메모리(core memory)는 재사용할 수 없게 한다.

그러므로 그 어느 것이라도 그 자리를 덮어쓸 수 없다.

좀비 객체가 메시지를 받는다면 좀비 객체로 보낸 메시지와 만일 아직 살아있었다면 사용되었을 객체에 대한 정확한 정보를 가진 예외를 던질 것이다.

좀비를 사용하는 것은 메모리 관리 오류를 디버깅하는 최고의 방법이다.



-

이 기능은 NSZombieEnable 환경 변수를 YES 로 설정하면 사용할 수 있다.

$ export NSZombieEnabled = “YES"

$ ./app



-

좀비한테 메시지를 보내면 콘솔로 메시지가 출력되고 앱은 종료될 것이다.

메시지는 다음과 같이 생겼다.

*** -[CFString respondsToSelector:]: message sent to deallocated instance 0x7ff9e9c080e0



-

엑스코드에 옵션을 체크하여 엑스코드로 앱을 실행했을 때 자동으로 환경 변수가 설정되게 하는 것도 가능하다.

이것을 하려면 앱 스킴 편집에서 'Run configuration’ 을 선택한 다음 Diagnostics 탭을 고르고 ‘Enable Zombie Objects’ 체크 박스를 선택하면 된다.



-

좀비 기능은 오브젝티브-C 런타임과 Foundation, CoreFoundation 프레임워크 깊숙한 곳에 구현되어 있다.

객체가 할당 해제될 때 이 기능을 활성화할지를 나타내는 환경 변수가 켜져 있으면

추가적인 단계(extra step)가 실행된다.

이 추가 단계는 객체를 완전히 할당 해제하는게 아니라 좀비로 만들어 준다.



-

메모리 버그는 ARC 를 사용해도 생길 수 있다.



-

객체의 클래스는 EOCClass 에서 _NSZombie_EOCClass 로 변경되었다.

컴파일러가 좀비 기능이 켜져 있을 때 컴파일하는 모든 클래스마다 추가로 클래스(좀비 클래스)를 하나 더 생성하는 것은 매우 비효율적일 것이다.

_NSZombie_EOCClass 가 실행 시간에 처음으로 생성되는 때는 처음으로 EOCClass 의 객체가 좀비로 바뀔 때다.

이것은 클래스 리스트를 조작할 수 있는 강력한 런타임 함수를 사용한다.



-

좀비 클래스는 _NSZombie_ 템플릿 클래스의 복사본이다.

이러한 좀비 클래스는 많은 일을 하지는 않고 간단한 표시자(marker) 로서 행동한다.



-

NSZombieEnabled 환경변수가 설정되어 있으면 런타임이 dealloc 메서드를 위의 코드 버전으로 스위즐링한다.

이 루틴의 끝에는 객체의 클래스가 _NSZomebie_OriginalClass 로 변경된다.

OriginalClass 는 원래 클래스의 이름이다.



-

결정적으로 객체가 거주한(live) 메모리는 해제되지 않는다. (free() 호출을 통해)

그렇기 때문에 그 메모리는 재사용이 불가능할 것이다.

메모리 누수이지만 이것은 디버깅 도구다.

그리고 실제 운영 중인 앱에서는 절대로 켜지지 않을 것이다.

그렇기 때문에 문제가 되지 않는다.



-

왜 클래스마다 좀비로 바뀔 새로운 클래스를 생성하는가?

이는 메시지가 좀비로 보내질 때 원래의 클래스가 결정되기 때문이다.


새로운 클래스는 런타임 함수 objc_duplicateClass() 를 이용해 생성한다.

클래스 전체를 복사하지만 새로운 이름을 부여한다.

상위 클래스, 인스턴스 변수, 메서드는 복사한 클래스와 복사된 클래스가 완전히 똑같다.


이전 클래스 이름을 유지하면서 새로운 클래스를 만드는 또 다른 방법은 복사하는 대신에 _NSZombie_ 로부터 상속받아 새로운 클래스를 만드는 것이다.

그러나 이 일을 하는 함수는 직접 복사하는 것에 비해 덜 효율적이다.



-

_NSZombie_ 클래스(그리고 그것의 모든 복사본)는 아무런 메서드도 구현하지 않는다.

이 클래스는 상위 클래스가 없다.

그리고 모든 최상위 클래스가 가져야 할 isa 라는 인스턴스 변수를 유일하게 가지고 있는 NSObject 와 같은 오브젝티브-C 최상위 클래스(root class)이다.

이 가벼운 클래스는 어떠한 메서드도 구현하지 않는다.

그래서 어떠한 메시지를 보내더라도 모두 완전 포워드 기술로 넘길 것이다.


완전 포워드 기술의 핵심은 __forwarding__ 이다.

이 함수가 하는 첫 번째 일은 메시지를 보낸 객체의 클래스 이름을 확인하는 것이다.

이름이 _NSZombie_ 로 시작하면 좀비가 발견된 것이다.

그리고 뭔가 특별한 일이 일어난다.

앱은 이 지점에서 메시지를 출력하고 죽는다.



-

객체의 원래 클래스를 명확하게 볼 수 있다.

또한 메시지를 보낸 죽은 객체의 포인터까지 볼 수 있다.

이 정보는 필요하다면 디버거에서 좀 더 분석하기 위해 사용할 수 있다.

또한 엑스코드에 있는 Instruments 같은 교정 도구(correct tool)의 유용함을 증명할 때 사용할 수도 있다.




기억할 점


객체가 할당 해제되었을 때 할당 해제되는 대신 좀비로 바꿀 수 있다.

이 기능은 NSZombieEnabled 환경 표식을 이용해 활성화 할 수 있다.


객체의 클래스를 특별한 좀비 클래스로 변경하기 위해 isa 포인터를 조작해서 객체를 좀비로 바꾼다.

좀비 클래스는 어떤 객체에 어떤 메시지를 보냈는지 알리는 메시지를 출력하고 앱을 종료함으로써 모든 선택자에 응답한다.


더보기



댓글을 달아 주세요


Posted by 돼지왕왕돼지