Apple ldのalias_listオプションの挙動が良く分かりません

本エントリで何がしたいか

Appleのldは、リンカスクリプトを受け取る事が出来ません。(多分…)
ところで、リンカスクリプト中のシンボル同士の代入を用い、シンボルのrenamingをしたいといった事は稀に良くあることだと思うのですが、じゃあリンカスクリプト使えない時にそれをどうしましょう…という話を書いています。

Apple ldにはaliasとalias_listという「それっぽい」オプションがあるのでこれを使えば動くのではないかなーと思うのですが、どうも上手く動かないように思われるケースがあるので以下に記します。

% gcc --version
i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

の元で以下コンパイルしています。

h.h

struct In {
    int i;
};

struct S {
    struct In in;
    int d;
};

lib1.c

#include "h.h"
#include <stdio.h>
#include <assert.h>

extern struct S lib1_s;
extern struct In lib1_in;

void f(int v)
{
    puts("-- f --");
    printf("&lib1_s = %p\n",&lib1_s);
    printf("&lib1_in = %p\n",&lib1_in);

    printf("lib1_s.in.i = %d\n",lib1_s.in.i);
    printf("lib1_in.i = %d\n",lib1_in.i);
    printf("lib1_s.d = %d\n",lib1_s.d);

    assert(v == lib1_s.d);
}

main.c

#include "h.h"
#include <stdio.h>

struct S main_s;
struct In main_in;

extern void f(int);

int main()
{
    main_in.i = 42;
    main_s.in = main_in;
    main_s.d = 84;

    printf("&main_s = %p\n",&main_s);
    printf("&main_in = %p\n",&main_in);

    f(main_s.d);

    return 0;
}

alias_listオプションを使いつつ、実行可能ファイルを作ります

いや、そもそもalias_listオプションて何?という話ですね。

man ld(あと、http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/ld.1.html) から抜粋すると

     -alias symbol_name alternate_symbol_name
                 Create an alias named alternate_symbol_name for the symbol symbol_name.  By default the alias symbol has global visibility.
                 This option was previous the -idef:indir option.

     -alias_list filename
                 The specified filename contains a list of aliases. The symbol name and its alias are on one line, separated by whitespace.
                 Lines starting with # are ignored.

今回は、lib1.cのシンボル、lib1_sをmain1_sへ、lib1_inをmain1_inへ書き換えてみよう(特に意味はありませんが)。少なくともassertで死ぬ事はないでしょう。という考えです。

次のようなファイルを作り、alias_listと共にgccに渡します。

#alias
_main_s _lib1_s
_main_in _lib1_in
% gcc -Wall -c -g -O0 main.c lib1.c
% gcc main.o lib1.o -Wl,-alias_list,alias


% ./a.out 
&main_s = 0x10000107c
&main_in = 0x100001078
-- f --
&lib1_s = 0x100001078
&lib1_in = 0x100001078
lib1_s.in.i = 42
lib1_in.i = 42
lib1_s.d = 42
Assertion failed: (v == lib1_s.d), function f, file lib1.c, line 18.
zsh: abort      ./a.out

ギョエピー!!!!

Linuxリンカスクリプトを使ってやってみる

% gcc --version
gcc 4.4.3
% ld --version
GNU ld 2.20.1-system.20100303

次のようなリンカスクリプトを準備

lib1_s = main_s;
EXTERN(main_s);

lib1_in = main_in;
EXTERN(main_in);

EXTERNが本当にこの例で必要かどうかは分かりませんが…。
さて、今度はldがリンカスクリプトを受け取れるのでこれを使って生成してあげると

% gcc -Wall -c -g -O0 main.c lib1.c
% gcc main.o lib1.o linker.script

% ./a.out

&main_s = 0x804a024
&main_in = 0x804a02c
-- f --
&lib1_s = 0x804a024
&lib1_s.in = 0x804a024
&lib1_in = 0x804a02c
lib1_s.in.i = 42
lib1_in.i = 42
lib1_s.d = 84

とこっちでは上手く動いている事が分かります。