8

私はObjective-Cランタイムをいじり、Objective-Cコードをリンクせずにコンパイルしようとしてlibobjcいます.プログラムにセグメンテーション違反の問題があるため、そこからアセンブリファイルを生成しました. アセンブリ ファイル全体を表示する必要はないと思います。関数のある時点でmain、次の行を取得しました (ちなみに、これは、seg fault が発生した後の行です)。

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_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 またはオブジェクトのクラスのメソッド リストにアクセスして、呼び出す正しいメソッドを見つける必要がある場合、実際にこれを行う関数は何ですか? objc_msgSend_fixupこの場合は だと思います。したがって、objc_msgSend_fixupが呼び出されると、パラメータの 1 つとしてオブジェクトを受け取り、このオブジェクトが初期化されていない場合、関数は失敗します。

そこで、独自のバージョンの を実装しましたobjc_msgSend_fixup。上記のアセンブリ ソースによると、それを呼び出す必要があります。関数がパラメーターとして渡されたセレクターの実装を実際に探しているかどうかは問題ではありません。objc_msgSend_lookup呼ばれたいだけ。しかし、それは呼び出されていません。つまり、オブジェクトのデータを検索する関数は、呼び出されてエラーを引き起こすのではなく、呼び出されてnilいません (それは a を返すためです (ちなみに、これは問題ではありません))。 . プログラムセグメントが呼び出される前に失敗します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_$_MyClassMyClassこれもコンパイラによって生成された構造体定義へのポインターであり、アセンブリ コードにも存在します。

4

2 に答える 2

11

何が何で、何をするのかを理解するobjc_msgSend_fixupには、Objective-C でメッセージ送信がどのように行われるかを正確に知る必要があります。すべての ObjC プログラマーは、ある日、コンパイラーが[obj message]ステートメントをobjc_msgSend(obj, sel_registerName("message"))呼び出しに変換することを耳にしました。ただし、それは完全に正確ではありません。

私の説明をよりよく説明するために、次の ObjC スニペットを検討してください。

[obj mesgA];
[obj mesgB];

[obj mesgA];
[obj mesgB];

このスニペットでは、2 つのメッセージが に送信されobj、それぞれが 2 回送信されます。したがって、次のコードが生成されると想像できます。

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関数へのポインターです。For eachmessage_refは、そのtrampolineポインターが初めて呼び出されたときに呼び出され、メッセージの送信先と呼び出し元の構造体をobjc_msgSend_fixup受け取ります。そのため、呼び出されるメッセージのセレクターを取得する必要があります。これは、メッセージ参照ごとに 1 回だけ実行する必要があるため、ref のフィールドを、メッセージのセレクターを修正しない別の関数へのポインターに置き換える必要もあります。この関数が呼び出されます(セレクターが修正されました)。メッセージセレクターが設定されたので、これを再度行う必要はありません。呼び出すだけで、これを呼び出すだけですobjmessage_refobjc_msgSend_fixupobjc_msgSend_fixuptrampolineobjc_msgSend_fixedupobjc_msgSend_fixupobjc_msgSend_fixedupobjc_msgSend. その後、メッセージ reftrampolineが再度呼び出されると、そのセレクターは既に固定されており、呼び出されるセレクターに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(by objc_msgSend_fixedup) で直接呼び出されます。

質問のアセンブリコードでl_objc_msgSend_fixup_allocは、allocメソッドのmessage_ref構造であり、セグメンテーション違反は最初のフィールドの問題によって引き起こされた可能性があります(おそらくそれは指していないobjc_msgSend_fixup...)

于 2013-03-09T17:01:18.753 に答える
7

OK、コードは C ではなく Objective-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_$_.

このシンボルは、独自のバージョンに存在しますか?

于 2012-09-01T14:25:24.747 に答える