究竟什么是“objc_msgSend_fixup”



我弄乱了Objective-C运行时,试图编译objective-c代码而不将其链接到libobjc,并且我在程序中遇到了一些分段错误问题,所以我从中生成了一个汇编文件。我认为没有必要显示整个程序集文件。在我的main函数的某个时候,我有以下行(顺便说一下,这是我得到 seg 错误的行(:

callq   *l_objc_msgSend_fixup_alloc

这是l_objc_msgSend_fixup_alloc的定义:

.hidden l_objc_msgSend_fixup_alloc # @"1l_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重新实现为一个返回nil的函数(id objc_msgSend_fixup(id self, SEL op, ...)(,但这个函数甚至没有被调用(程序在调用它之前崩溃(。

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

编辑

为了更好地解释,我没有将我的源文件链接到 objc 库。我想做的是实现库的某些部分,只是为了看看它是如何工作的。以下是我所做的事情的方法:

#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(顺便说一下,这无关紧要((。程序 seg 在调用 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 # @"1l_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 # @"1L_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 结构定义的指针,该定义也由编译器生成,并且也存在于程序集代码中。

要了解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];

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

简而言之,我们可以这样写objc_msgSend_fixupobjc_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_fixup (才会发现相应的选择器。在以后的调用中,将已经找到选择器,并且直接使用 objc_msgSend 调用消息(通过 objc_msgSend_fixedup (。

在问题的汇编代码中,l_objc_msgSend_fixup_allocalloc方法的message_ref结构,分割错误可能是由其第一个字段中的问题引起的(也许它没有指向objc_msgSend_fixup......

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

编辑/关于objc_msgSend_fixup

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

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

  • http://cocoasamurai.blogspot.ch/2010/01/understanding-objective-c-runtime.html
  • http://www.sealiesoftware.com/blog/

编辑/结束

现在关于你的段错误。

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.

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

尝试启用NSZombie,它应该会有所帮助.
还可以尝试使用静态分析器。

编辑 2

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

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

这就是段断层位于此处的原因。

编辑 3

说你没有链接到objc库.
您如何称呼"objc 库"?

我问这个是因为,正如我们在你的代码中看到的,你肯定使用的是Objective-C编译器。

例如,您可能不会与提供基本对象的"Foundation"框架链接,但是由于您使用的是Objective-C编译器,因此libobjc库(提供运行时(仍将隐式链接。

你确定不是这样吗?尝试对生成的二进制文件进行简单的nm

编辑 4

如果确实如此,则 objc_msgSend_fixup 不是为了重新创建运行时而要执行的第一个函数。

当你定义一个类时,运行时需要知道它,所以你需要编写像objc_allocateClassPair和朋友这样的东西。

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

我在你身上看到过这样的代码:L_OBJC_CLASSLIST_REFERENCES_$_.

这个符号存在于你自己的版本中吗?

相关内容

  • 没有找到相关文章

最新更新