什么是`objc_msgSend_fixup`,究竟是什么?

时间:2012-09-01 13:55:48

标签: objective-c assembly segmentation-fault objective-c-runtime

我正在搞乱Objective-C运行时,尝试编译objective-c代码而不将其链接到libobjc,并且我对程序有一些分段错误问题,所以我生成了一个程序集来自它的文件。我认为没有必要显示整个程序集文件。在main函数的某个时刻,我得到了以下行(顺便说一句,这是我得到seg错误之后的行):

callq   *l_objc_msgSend_fixup_alloc

以下是l_objc_msgSend_fixup_alloc的定义:

.hidden l_objc_msgSend_fixup_alloc # @"\01l_objc_msgSend_fixup_alloc"
    .type   l_objc_msgSend_fixup_alloc,@object
    .section    "__DATA, __objc_msgrefs, coalesced","aw",@progbits
    .weak   l_objc_msgSend_fixup_alloc
    .align  16
l_objc_msgSend_fixup_alloc:
    .quad   objc_msgSend_fixup
    .quad   L_OBJC_METH_VAR_NAME_
    .size   l_objc_msgSend_fixup_alloc, 16

我重新实现objc_msgSend_fixup作为函数(id objc_msgSend_fixup(id self, SEL op, ...))返回nil(只是为了看看会发生什么),但是这个函数甚至没有被调用(程序崩溃)在打电话之前)。

所以,我的问题是,callq *l_objc_msgSend_fixup_alloc应该做什么,objc_msgSend_fixupl_objc_msgSend_fixup_alloc:之后)应该是什么(函数或对象)?

修改

为了更好地解释,我没有将我的源文件与objc库链接。我想要做的是实现libray的一些部分,只是为了看它是如何工作的。这是我所做的一种方法:

#include <stdio.h>
#include <objc/runtime.h>

@interface MyClass {

}
+(id) alloc;
@end

@implementation MyClass
+(id) alloc {
  // alloc the object
  return nil;
}
@end

id objc_msgSend_fixup(id self, SEL op, ...) {
  printf("Calling objc_msgSend_fixup()...\n");

  // looks for the method implementation for SEL in self's method list

  return nil;   // Since this is just a test, this function doesn't need to do that
}

int main(int argc, char *argv[]) {
    MyClass *m;
    m = [MyClass alloc];    // At this point, according to the assembly code generated
    // objc_msgSend_fixup should be called. So, the program should, at least, print
    // "Calling objc_msgSend_fixup()..." on the screen, but it crashes before
    // objc_msgSend_fixup() is called...

    return 0;
}

如果运行时需要访问对象的vtable或obect类的方法列表来找到正确的调用方法,那么实际执行此操作的函数是什么?在这种情况下,我认为它是objc_msgSend_fixup。因此,当调用objc_msgSend_fixup时,它会接收一个对象作为其参数之一,如果此对象尚未初始化,则该函数将失败。

所以,我已经实现了我自己的objc_msgSend_fixup版本。根据上面的汇编来源,它应该被称为。如果函数实际上正在寻找作为参数传递的选择器的实现,则无关紧要。我只想要调用objc_msgSend_lookup。但是,它没有被调用,也就是说,查找对象数据的函数甚至没有被调用,而不是被调用并导致错误(因为它返回nil(顺便说一下,它没有没关系))。程序段在调用objc_msgSend_lookup之前失败...

修改2

更完整的装配片段:

.globl  main
    .align  16, 0x90
    .type   main,@function
main:                                   # @main
.Ltmp20:
    .cfi_startproc
# BB#0:
    pushq   %rbp
.Ltmp21:
    .cfi_def_cfa_offset 16
.Ltmp22:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
.Ltmp23:
    .cfi_def_cfa_register %rbp
    subq    $32, %rsp
    movl    $0, %eax
    leaq    l_objc_msgSend_fixup_alloc, %rcx
    movl    $0, -4(%rbp)
    movl    %edi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    movq    L_OBJC_CLASSLIST_REFERENCES_$_, %rsi
    movq    %rsi, %rdi
    movq    %rcx, %rsi
    movl    %eax, -28(%rbp)         # 4-byte Spill
    callq   *l_objc_msgSend_fixup_alloc
    movq    %rax, -24(%rbp)
    movl    -28(%rbp), %eax         # 4-byte Reload
    addq    $32, %rsp
    popq    %rbp
    ret

对于l_objc_msgSend_fixup_alloc,我们有:

.hidden l_objc_msgSend_fixup_alloc # @"\01l_objc_msgSend_fixup_alloc"
    .type   l_objc_msgSend_fixup_alloc,@object
    .section    "__DATA, __objc_msgrefs, coalesced","aw",@progbits
    .weak   l_objc_msgSend_fixup_alloc
    .align  16
l_objc_msgSend_fixup_alloc:
    .quad   objc_msgSend_fixup
    .quad   L_OBJC_METH_VAR_NAME_
    .size   l_objc_msgSend_fixup_alloc, 16

L_OBJC_CLASSLIST_REFERENCES_$_

.type   L_OBJC_CLASSLIST_REFERENCES_$_,@object # @"\01L_OBJC_CLASSLIST_REFERENCES_$_"
    .section    "__DATA, __objc_classrefs, regular, no_dead_strip","aw",@progbits
    .align  8
L_OBJC_CLASSLIST_REFERENCES_$_:
    .quad   OBJC_CLASS_$_MyClass
    .size   L_OBJC_CLASSLIST_REFERENCES_$_, 8

OBJC_CLASS_$_MyClass是指向MyClass结构定义的指针,它也是由编译器生成的,它也出现在汇编代码中。

2 个答案:

答案 0 :(得分:10)

要了解objc_msgSend_fixup是什么以及它做了什么,有必要确切了解如何在Objective-C中执行消息发送。所有ObjC程序员都有一天听说编译器将[obj message]语句转换为objc_msgSend(obj, sel_registerName("message"))个调用。但是,这并不完全准确。

为了更好地说明我的解释,请考虑以下ObjC片段:

[obj mesgA];
[obj mesgB];

[obj mesgA];
[obj mesgB];

在此代码段中,会向obj发送两条消息,每条消息都会发送两次。因此,您可能会想到生成以下代码:

objc_msgSend(obj, sel_registerName("mesgA"));
objc_msgSend(obj, sel_registerName("mesgB"));
objc_msgSend(obj, sel_registerName("mesgA"));
objc_msgSend(obj, sel_registerName("mesgB"));

然而sel_registerName可能成本太高,只要调用特定方法并不是一件明智的事情,就可以调用它。然后,编译器为每个要发送的消息生成这样的结构:

typedef struct message_ref {
    id (*trampoline) (id obj, struct message_ref *ref, ...);
    union {
        const char *str;
        SEL sel;
    };
} message_ref;

所以,在上面的例子中,当程序启动时,我们有类似的东西:

message_ref l_objc_msgSend_fixup_mesgA = { &objc_msgSend_fixup, "mesgA" };
message_ref l_objc_msgSend_fixup_mesgB = { &objc_msgSend_fixup, "mesgB" };

当需要将这些消息发送到obj时,编译器会生成与以下内容等效的代码:

l_objc_msgSend_fixup_mesgA.trampoline(obj, &l_objc_msgSend_fixup_mesgA, ...);   // [obj mesgA];
l_objc_msgSend_fixup_mesgB.trampoline(obj, &l_objc_msgSend_fixup_mesgB, ...);   // [obj mesgB];

在程序启动时,消息引用trampolines是指向objc_msgSend_fixup函数的指针。对于每个message_ref,当第一次调用其trampoline指针时,objc_msgSend_fixup会被调用,接收要将消息发送到的obj和{{1}调用它的结构。因此,message_ref必须做的是获取要调用的消息的选择器。因为,每个消息引用只需执行一次,objc_msgSend_fixup还必须通过指向另一个不修复消息选择器的函数的指针替换ref的objc_msgSend_fixup字段。此函数称为trampoline(选择器已被修复)。既然已经设置了消息选择器并且不必再次执行此操作,objc_msgSend_fixedup只需调用objc_msgSend_fixup,这只会调用objc_msgSend_fixedup。之后,如果再次调用消息ref objc_msgSend,则其选择器已经修复,trampoline是被调用的。

简而言之,我们可以像这样写objc_msgSend_fixedupobjc_msgSend_fixup

objc_msgSend_fixedup

这使得消息发送速度更快,因为只有在第一次调用消息时才会发现相应的选择器(id objc_msgSend_fixup(id obj, struct message_ref *ref, ...) { ref->sel = sel_registerName(ref->str); ref->trampoline = &objc_msgSend_fixedup; objc_msgSend_fixedup(obj, ref, ...); } id objc_msgSend_fixedup(id obj, struct message_ref *ref, ...) { objc_msgSend(obj, ref->sel, ...); } )。在以后的调用中,已找到选择器,并使用objc_msgSend_fixupobjc_msgSend)直接调用该消息。

在问题的汇编代码中,objc_msgSend_fixedupl_objc_msgSend_fixup_alloc方法的alloc结构,并且分段错误可能是由第一个字段中的问题引起的(可能它没有指向{ {1}} ...)

答案 1 :(得分:7)

好的,你的代码是Objective-C,而不是C.

编辑/关于objc_msgSend_fixup

objc_msgSend_fixup是内部Objective-C运行时的东西,用于使用C ++样式方法vtable管理调用。

您可以在这里阅读一些有关此内容的文章:

编辑/结束

现在关于你的段错误。

Objective-C使用运行时进行消息传递,分配等。

消息传递(方法调用)通常由objc_msgSend函数完成 这就是你做的事情:

[ someObject someFunction: someArg ];

它被翻译为:

objc_msgSend( someObject, @selector( someFunction ), someArg );

因此,如果您在这样的运行时函数中有段错误,例如objc_msgSend_fixup_alloc,它肯定意味着您在未初始化的指针(如果不使用ARC)或已释放的对象上调用方法。

类似的东西:

NSObject * o;

[ o retain ]; // Will segfault somewhere in the Obj-C runtime in non ARC, as 'o' may point to anything.

或者:

NSObject * o;

o = [ [ NSObject alloc ] init ];

[ o release ];
[ o retain ]; // Will segfault somewhere in the Obj-C runtime as 'o' is no longer a valid object address.

因此,即使segfault位置在运行时,这肯定是您自己的代码中基本的Objective-C内存管理问题。

尝试启用NSZombie,它应该有帮助。
还可以试试静态分析仪。

修改2

它在运行时崩溃,因为运行时需要访问对象的vtable来找到正确的调用方法。

由于对象无效,vtable查找会导致无效指针的取消引用。

这就是segfault位于此处的原因。

编辑3

你说你没有与objc图书馆联系 你怎么称呼“objc库”?

我问这是因为,正如我们在您的代码中看到的那样,您最终使用的是Objective-C编译器。

例如,您可能无法链接«Foundation»框架,它提供了基础对象,但由于您使用的是Objective-C编译器,因此 libobjc 库(提供运行时)仍将隐含地联系在一起。

你确定不是这样吗?在生成的二进制文件上尝试一个简单的nm

编辑4

如果确实如此,objc_msgSend_fixup不是第一个重新创建运行时的函数。

在定义类时,运行时需要了解它,因此您需要对objc_allocateClassPair和朋友等内容进行编码。

您还需要确保编译器不使用快捷方式。

我在你看到的代码类似于:L_OBJC_CLASSLIST_REFERENCES_$_

此符号是否存在于您自己的版本中?