Lui*_*BOL 8 assembly objective-c objective-c-runtime segmentation-fault
我正在搞乱Objective-C运行时,尝试编译Objective-c代码而不将其链接起来libobjc,并且我对程序有一些分段错误问题,所以我从中生成了一个汇编文件.我认为没有必要显示整个程序集文件.在我的main功能的某些方面,我有以下一行(顺便说一句,这是我得到seg错误之后的行):
callq *l_objc_msgSend_fixup_alloc
Run Code Online (Sandbox Code Playgroud)
这是以下定义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
Run Code Online (Sandbox Code Playgroud)
我重新实现objc_msgSend_fixup了一个函数(id objc_msgSend_fixup(id self, SEL op, ...))返回nil(只是为了看看会发生什么),但是这个函数甚至没有被调用(程序在调用之前崩溃).
所以,我的问题是,callq *l_objc_msgSend_fixup_alloc应该做什么以及objc_msgSend_fixup(之后l_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;
}
Run Code Online (Sandbox Code Playgroud)
如果运行时需要访问对象的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
Run Code Online (Sandbox Code Playgroud)
因为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
Run Code Online (Sandbox Code Playgroud)
用于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
Run Code Online (Sandbox Code Playgroud)
OBJC_CLASS_$_MyClass是一个指向MyClass结构定义的指针,它也是由编译器生成的,它也出现在汇编代码中.
Lui*_*BOL 10
要了解objc_msgSend_fixup它是什么以及它做了什么,有必要确切地知道如何在Objective-C中执行消息发送.有一天,所有ObjC程序员都听说编译器将[obj message]语句转换为objc_msgSend(obj, sel_registerName("message"))调用.但是,这并不完全准确.
为了更好地说明我的解释,请考虑以下ObjC片段:
[obj mesgA];
[obj mesgB];
[obj mesgA];
[obj mesgB];
Run Code Online (Sandbox Code Playgroud)
在此片段中,将发送两条消息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"));
Run Code Online (Sandbox Code Playgroud)
但是sel_registerName,如果调用特定方法并不是一件明智的事情,那么可能成本太高并且调用它.然后,编译器为每个要发送的消息生成这样的结构:
typedef struct message_ref {
id (*trampoline) (id obj, struct message_ref *ref, ...);
union {
const char *str;
SEL sel;
};
} message_ref;
Run Code Online (Sandbox Code Playgroud)
所以,在上面的例子中,当程序启动时,我们有这样的事情:
message_ref l_objc_msgSend_fixup_mesgA = { &objc_msgSend_fixup, "mesgA" };
message_ref l_objc_msgSend_fixup_mesgB = { &objc_msgSend_fixup, "mesgB" };
Run Code Online (Sandbox Code Playgroud)
当需要将这些消息发送到时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];
Run Code Online (Sandbox Code Playgroud)
在程序启动时,消息引用trampolines是指向该objc_msgSend_fixup函数的指针.对于每一个message_ref,当它trampoline的第一次调用它的指针时,objc_msgSend_fixup会调用它来接收obj要发送消息的消息以及从中调用消息的message_ref结构.因此,objc_msgSend_fixup必须做的是获取要调用的消息的选择器.因为,每个消息引用objc_msgSend_fixup必须只执行一次,所以还必须用trampoline指向另一个不修复消息选择器的函数的指针替换ref 的字段.调用此函数objc_msgSend_fixedup(选择器已被修复).现在已经设置了消息选择器,并且不必再次执行此操作,objc_msgSend_fixup只需调用objc_msgSend_fixedup即可调用objc_msgSend.之后,如果trampoline再次调用消息ref ,则其选择器已经被修复,并且objc_msgSend_fixedup是被调用的选择器.
总之,我们可以写objc_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, ...);
}
Run Code Online (Sandbox Code Playgroud)
这使得消息发送速度更快,因为只有在第一次调用消息时才会发现适当的选择器(by objc_msgSend_fixup).在以后的调用中,已经找到了选择器,并使用objc_msgSend(by objc_msgSend_fixedup)直接调用该消息.
在问题的汇编代码,l_objc_msgSend_fixup_alloc是alloc方法的message_ref结构和分段错误可能已在其第一场的问题引起的(也许这不是指向objc_msgSend_fixup...)
好的,你的代码是Objective-C,而不是C.
编辑/关于objc_msgSend_fixup
objc_msgSend_fixup 是内部Objective-C运行时的东西,用于使用C++样式方法vtable管理调用.
你可以在这里阅读一些关于这个的文章:
编辑/结束
现在关于你的段错误.
Objective-C使用运行时进行消息传递,分配等.
消息传递(方法调用)通常由objc_msgSend函数完成.
这就是你做的时候使用的:
[ someObject someFunction: someArg ];
Run Code Online (Sandbox Code Playgroud)
它被翻译为:
objc_msgSend( someObject, @selector( someFunction ), someArg );
Run Code Online (Sandbox Code Playgroud)
因此,如果您在这样的运行时函数中有段错误,例如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.
Run Code Online (Sandbox Code Playgroud)
要么:
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.
Run Code Online (Sandbox Code Playgroud)
因此,即使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_$_.
此符号是否存在于您自己的版本中?