프로가 되자.

post search result

objc_msgSend()와 관련된 글 1개를 찾았습니다.

  1. 2009/07/06 Objective-C vs C

Objective-C vs C

Objective-C를 처음 접했을 때 클래스 메서드를 호출하였을 때 C의 함수 호출과 다른점이 무엇일까? 라는 생각이 들었는데.. 그냥 wrapping 한건지 아니면 뭐 또 다른게 있을지 말이죠.

그 동안 귀찮아서 미루다가 오늘 disassemble 해 보았습니다.
테스트 환경은 iPhone Simulator입니다. 따라서 레지스터 이름들이 익숙한(?) eax, ebx가 나오는데요..
실제 iPhone은 arm이라 어떻게 disassemble 될 지는 잘 모르겠습니다;;
(제가 disasm 초보라 양해 부탁드립니다~)

* 테스트에 사용 될 클래스 및 함수

@interface MyClass : NSObject
{
	int _member;
}

- (void)printTest;
- (int)get;
- (void)setMember:(int)member;

@end

@implementation MyClass

- (void)printTest
{
	printf("- printTest\n");
}

- (int)get
{
	return 30;
}

- (void)setMember:(int)member
{
	_member = member;
}

- (int)member
{
	return _member;
}

@end

void PrintTest()
{
	printf("PrintTest()\n");
}

int Get()
{
	return 30;
}


* "-[MyClass printTest]" vs "PrintTest()" (void 테스트)

Dump of assembler code for function -[MyClass printTest]:
0x00001d7a <-[MyClass printTest]+0>: nop    
0x00001d7b <-[MyClass printTest]+1>: nop    
0x00001d7c <-[MyClass printTest]+2>: nop    
0x00001d7d <-[MyClass printTest]+3>: nop    
0x00001d7e <-[MyClass printTest]+4>: nop    
0x00001d7f <-[MyClass printTest]+5>: nop    
0x00001d80 <-[MyClass printTest]+6>: push   ebp
0x00001d81 <-[MyClass printTest]+7>: mov    ebp,esp
0x00001d83 <-[MyClass printTest]+9>: push   ebx
0x00001d84 <-[MyClass printTest]+10>: sub    esp,0x14
0x00001d87 <-[MyClass printTest]+13>: call   0x1d8c <-[MyClass printTest]+18>
0x00001d8c <-[MyClass printTest]+18>: pop    ebx
0x00001d8d <-[MyClass printTest]+19>: lea    eax,[ebx+0x1dc]
0x00001d93 <-[MyClass printTest]+25>: mov    DWORD PTR [esp],eax
0x00001d96 <-[MyClass printTest]+28>: call   0x400a 
0x00001d9b <-[MyClass printTest]+33>: add    esp,0x14
0x00001d9e <-[MyClass printTest]+36>: pop    ebx
0x00001d9f <-[MyClass printTest]+37>: leave  
0x00001da0 <-[MyClass printTest]+38>: ret


Dump of assembler code for function PrintTest:
0x00001ebd : nop    
0x00001ebe : nop    
0x00001ebf : nop    
0x00001ec0 : nop    
0x00001ec1 : nop    
0x00001ec2 : nop    
0x00001ec3 : push   ebp
0x00001ec4 : mov    ebp,esp
0x00001ec6 : push   ebx
0x00001ec7 : sub    esp,0x14
0x00001eca : call   0x1ecf 
0x00001ecf : pop    ebx
0x00001ed0 : lea    eax,[ebx+0xa5]
0x00001ed6 : mov    DWORD PTR [esp],eax
0x00001ed9 : call   0x400a 
0x00001ede : add    esp,0x14
0x00001ee1 : pop    ebx
0x00001ee2 : leave  
0x00001ee3 : ret

무슨말인지 잘 모르겠지만(-_-;;) 보시다시피 다른 부분이 없습니다.  둘 다 그냥 printf()를 호출하고 종료합니다.

* "-[MyClass get]" vs "Get()" (int 테스트)

Dump of assembler code for function -[MyClass get]:
0x00001da1 <-[MyClass get]+0>: nop    
0x00001da2 <-[MyClass get]+1>: nop    
0x00001da3 <-[MyClass get]+2>: nop    
0x00001da4 <-[MyClass get]+3>: nop    
0x00001da5 <-[MyClass get]+4>: nop    
0x00001da6 <-[MyClass get]+5>: nop    
0x00001da7 <-[MyClass get]+6>: push   ebp
0x00001da8 <-[MyClass get]+7>: mov    ebp,esp
0x00001daa <-[MyClass get]+9>: sub    esp,0x8
0x00001dad <-[MyClass get]+12>: mov    eax,0x1e
0x00001db2 <-[MyClass get]+17>: leave  
0x00001db3 <-[MyClass get]+18>: ret


Dump of assembler code for function Get:
0x00001ee4 : nop    
0x00001ee5 : nop    
0x00001ee6 : nop    
0x00001ee7 : nop    
0x00001ee8 : nop    
0x00001ee9 : nop    
0x00001eea : push   ebp
0x00001eeb : mov    ebp,esp
0x00001eed : sub    esp,0x8
0x00001ef0 : mov    eax,0x1e
0x00001ef5 : leave  
0x00001ef6 : ret

이것 역시 완전히 동일합니다. mac 개발에서도 eax로 리턴값을 주는군요.



이번에는 메서드를 호출하였을 경우 어떻게 코드가 실행되는지 분석 해 보겠습니다.

1. [myClass setMember:10] 호출

0x00001e67 : mov    edx,DWORD PTR [ebp-0xc] // myClass 주소 ($ebp-0x0c)를 edx에 저장
0x00001e6a : lea    eax,[ebx+0x121b] // selector(setMember:)주소를 가리키고 있는 변수
0x00001e70 : mov    eax,DWORD PTR [eax] // eax가 가리키는 곳의 값을 eax에 넣음
0x00001e72 : mov    DWORD PTR [esp+0x8],0xa // 10을 esp+0x08에 넣음 (3번째 인자)
0x00001e7a : mov    DWORD PTR [esp+0x4],eax // selector 값을 넣음 (2번째 인자)
0x00001e7e : mov    DWORD PTR [esp],edx // 스택에 myClass 주소를 넣음 (1번째 인자)
0x00001e81 : call   0x4005  // dyld_stub_objc_msgSend 호출

objc_msgSend는 많이 들어 보셨을 껍니다. 잘못된 receiver에 메시지를 날릴려고 하면 항상 나오는 EXC_BAD_ACCESS와 같은 오류 상황에서 backtrace (call stack) 해보면 objc_msgSend()가 찍히죠.
여기서 보시면 receiver가 edx에 저장이 되고 eax에는 selector가 저장됩니다.[2] 이러한 값들을 c 코드로 보면 다음과 같이 되겠죠.

dyld_stub_objc_msgSend(myClass, selectorOfSetMember, 10);


이렇게 하면 "4. 멤버 변수 접근 (setter 이용)"에서 보시는 것과 같이 _member 변수에 값이 설정됩니다.

2. objc_msgSend로..

위의 0x00001e81 <TestMe+162> 부분의 코드에서 call 0x4005를 통해 dyld_stub_objc_msgSend를 호출하는 것을 보실 수 있습니다.
따라가보면..

0x00004005 <dyld_stub_objc_msgSend+0>: jmp    0x95da0670 <objc_msgSend>


와 같이 나오는데, 또 다시 objc_msgSend를 거치는 것을 보실 수 있습니다. (dyld_stub_objc_msgSend는 말그대로 stub 코드라서)

3. objc_msgSend 에서는 무슨 일이?

1번에서 넣어준 인자 덕분에 objc_msgSend()는 myClass, selector, 10 이라는 3개의 인자를 받았습니다. 이 인자들을 이용해서 무엇인가를 하겠죠.

objc_msgSend()는 아래와 같이 생겼습니다. 정신건강을 위해 more/less로 처리 했습니다 :)

objc_msgSend() 보기

뭐가 뭔지 잘 모르겠지만 한번 보도록 하죠.
청므에 ecx에 esp+0x08, eax에 esp+0x04를 넣습니다. 각각 selector와 myClass가 되겠죠.
이렇게 넣고 0xfffeb010과 비교를 하는데 이걸 찾아보니 ignore selector라는 군요.[4] 참고 자료를 보시면

-#if WITH_OBJC
-static inline bool
-is_ignored_selector(SEL sel)
-{
-#if defined(__ppc__)
-    return sel == (SEL)0xfffef000;
-#elif defined(__i386__)
-    return sel == (SEL)0xfffeb010;
-#else
-# error Unsupported arch
-#endif
-}
-#endif
-


와 같이 되어 있습니다. i386일때 selector가 0xfffeb010이면 is_ignored_selector가 true가 됩니다. 위의 어셈 코드를 보시면 아시겠지만.. 이 값을 갖게 되면 무시되서 그냥 return 하네요. (왜 이렇게 하는지 용도는 먼지 모르겠습니다??) 참고로 inline으로 선언되어 있어서 is_ignored_selector(selector)와 같이 호출하여도 어셈 코드 상에는 하드 코딩 됩니다.

receiver에게 데이터를 전달하기 위해 계속 진행하다 보면 <objc_msgSend+44>지점에서 eax가 0인지 확인 합니다. 여기서 eax는 selector인데, 만약 0이라면 <objc_msgSend+58>로 이동하겠지만 0이 아니므로 계속 진행합니다. 이렇게 쭉 가다가 "_class_lookupMethodAndLoadCache"라는 곳으로 이동하는데요, 이렇게 이동을 해서 보면 정신 없는 코드로 빠져 버립니다. 이것 역시 정신건강을 위해 less/more로..

_class_lookupMethodAndLoadCache 보기

일반 C++ 메서드 호출과는 비교할 수 없을 정도로 징하게 많이 호출하는군요;
보시면 _class_lookupMethodAndLoadCache, _class_getFreedObjectClass, _class_getNonexistentObjectClass, _class_isInitialized, class_initialize과 같은 메서드 들을 호출합니다. 정확히 분석해 보진 않았지만 receiver가 nil 일 경우 여기서 걸러지겠죠?? 여기서 상위 클래스의 메서드 호출 등을 수행하고 메서드를 cache 하는 동작을 한다고 합니다.[3] 머.. 여기선 특별한게 없는 듯 합니다.

다시 objc_msgSend()로 가서 보면 <objc_msgSend+110>에서 jmp eax 합니다. 여기서 eax는 selector 주소가 계산되어 실제 코드 영역으로 진입하게 됩니다. 이 부분을 통해 setMember:가 호출 되는 것이죠.

4. setMember 안에서..

Dump of assembler code for function -[MyClass setMember:]:
0x00001db4 <-[MyClass setMember:]+0>: nop    
0x00001db5 <-[MyClass setMember:]+1>: nop    
0x00001db6 <-[MyClass setMember:]+2>: nop    
0x00001db7 <-[MyClass setMember:]+3>: nop    
0x00001db8 <-[MyClass setMember:]+4>: nop    
0x00001db9 <-[MyClass setMember:]+5>: nop    
0x00001dba <-[MyClass setMember:]+6>: push   ebp
0x00001dbb <-[MyClass setMember:]+7>: mov    ebp,esp
0x00001dbd <-[MyClass setMember:]+9>: sub    esp,0x8
0x00001dc0 <-[MyClass setMember:]+12>: mov    edx,DWORD PTR [ebp+0x8] // edx에 self주소(ebp + 0x08) 저장
0x00001dc3 <-[MyClass setMember:]+15>: mov    eax,DWORD PTR [ebp+0x10] // eax에 첫번째 인자 값(ebp + 0x10) 저장
0x00001dc6 <-[MyClass setMember:]+18>: mov    DWORD PTR [edx+0x4],eax // _member(edx + 0x04)에 eax값 저장
0x00001dc9 <-[MyClass setMember:]+21>: leave  
0x00001dca <-[MyClass setMember:]+22>: ret

아까부터 느끼는거지만... mac에서는 앞에 nop이 꼭 붙는군요; 저 영역이 어딘가에 사용되서 이겠죠?
특별한건 없습니다. 값 들을 보면 objc_msgSend에서 esp에 myClass 포인터를 넣어주는 것 같고, 2번째 인자로 10을 넣어주는 것 같네요.


전체적으로 보자면..... 한개의 메시지가 전달되기 위해 엄청 많은 명령어를 실행하고 있는데 좀 비효율 적인게 아닌가 생각이 듭니다.
나중에 selector 호출 속도와 일반 함수, C++ 클래스 메서드 호출 속도를 profiling 해봐야 겠네요.

어셈을 잘 몰라 하면서 분석하느라 부족한게 많은 글이네요. 나중에 한번 다시 도전 해봐야겠습니다-_-;

* 참고

[1] So you crashed in objc_msgSend()
http://www.sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html

[2] obj_msgSend()
http://ridiculousfish.com/blog/archives/2005/08/01/objc_msgsend/

[3] The faster objc_msgSend
http://www.mulle-kybernetik.com/artikel/Optimization/opti-9.html

[4] [macruby-changes] [391] MacRuby/branches/lrz_unstable
http://lists.macosforge.org/pipermail/macruby-changes/2008-August/000280.html

크리에이티브 커먼즈 라이센스
Creative Commons License
2009/07/06 17:35 2009/07/06 17:35

top

About this post

이 글에는 아직 트랙백이 없고, 아직 댓글이 없고, , 태그가 달려있으며,
2009/07/06 17:35에 작성되었습니다.

◀ recent : [1] : previous ▶

blog information

프로가 되자.
BLOG main image
빗소리를 먹는 사람.
RSS 2.0Tattertools
최근 글 최근 댓글 최근 트랙백
태그 구름사이트 링크