std::vector と例外と+αを使ってる状態で実機用のバイナリを作る方法

とりあえず実機でも一通り動くような感じになってきたので、RVCT3.0 で実機用のバイナリを作る方法をまとめてみようと思います。

事前知識

ずっと勘違いしていたのですが、BREW ではもはやグローバル変数は使えます
elf2mod というツールが Qualcomm から提供されていて、fromelf.exe を使う代わりにこれを使うと、グローバル変数を使っていても正常に動作するようになります。ただしグローバル変数のコンストラクタは呼ばれません。
また、RVCT3.0 ではこのツールが無いと仮想関数が使えません
これは RVCT3.0 が仮想関数の実体をグローバル領域(RW 領域)に作るためで、つまり仮想関数を使うとグローバル変数を使ってると見なされてしまうのです。

ということで、RVCT3.0 ではグローバル変数を扱ったりすることが出来ます。

準備するもの

Visual Studio は必要無いです。VC のカスタムビルドを使って実機用のバイナリを作る方法はまた今度書きます。
 →書きました

ARM コンパイラ用の定義

どこかの cpp ファイルに以下のような定義を書いておきます。


まずは operator new, operator delete。

void* operator new(std::size_t size) throw(std::bad_alloc)
{
    if (size == 0) size = 1;
    void* p = MALLOC(size | ALLOC_NO_ZMEM);
    // 標準に従うなら例外ハンドラを呼び出す必要があるけど、
    // どうせ意味無いだろうからさっさと例外を投げる
    if (p == nullptr) throw std::bad_alloc();
    return p;
}
void* operator new[](std::size_t size) throw(std::bad_alloc)
{
    if (size == 0) size = 1;
    void* p = MALLOC(size | ALLOC_NO_ZMEM);
    if (p == nullptr) throw std::bad_alloc();
    return p;
}
void operator delete(void* p) throw()
{
    if (p != nullptr) FREE(p);
}
void operator delete[](void* p) throw()
{
    if (p != nullptr) FREE(p);
}

void* operator new(std::size_t size, const std::nothrow_t&) throw()
{
    if (size == 0) size = 1;
    return MALLOC(size | ALLOC_NO_ZMEM);
}
void* operator new[](std::size_t size, const std::nothrow_t&) throw()
{
    if (size == 0) size = 1;
    return MALLOC(size | ALLOC_NO_ZMEM);
}
void operator delete(void* p, const std::nothrow_t&) throw()
{
    if (p != nullptr) FREE(p);
}
void operator delete[](void* p, const std::nothrow_t&) throw()
{
    if (p != nullptr) FREE(p);
}

グローバル変数が使えるといっても標準の malloc や free を使うわけにはいかないので、これを定義しておきます。
ただ、↓で malloc や free を再定義してるので、多分これらが無くても動作する気はします。
ただし、その場合は恐らく new に失敗した場合の処理として例外ハンドラを呼び出したりする処理があるので、そこら辺で無駄なバイナリが作られてしまいそうです。


で、次は malloc と free、それから __rw 系の再定義

#ifndef AEE_SIMULATOR

extern "C" void* malloc(unsigned int size)
{
    return MALLOC(size | ALLOC_NO_ZMEM);
}
extern "C" void free(void* p)
{
    FREE(p);
}

#include <rw/_defs.h>
namespace __rw
{
    void* __rw_throw(int, ...) { return 0; } // これはどこからも呼ばれていない
    void* __rw_allocate(_RWSTD_C::size_t size, int) { return ::operator new(size); }
    void  __rw_deallocate(void* p, _RWSTD_C::size_t, int) { ::operator delete(p); }
}

std::__rw_exception& std::__rw_exception::
_C_assign (const char *whatstr, size_t len) throw()
{
    return *this;
}
#endif

最初の2つは std::vector と例外を使ってる状態でコンパイルを通す方法 - melpon日記 - HaskellもC++もまともに扱えないへたれのページ に書いたものなので割愛。
最後の std::__rw_exception::_C_assign の定義は、std::runtime_exception を継承したクラスを作るとこれが定義されてないって怒られました。でも call graph を見てみるとどこからも呼ばれていないので何もせずに返すことにします。


最後に以下のコードを定義します。

#ifndef AEE_SIMULATOR

#include <typeinfo>
#include <exception>
#include <new>

typedef unsigned int uint32_t;

typedef enum {
    _URC_OK = 0,       /* operation completed successfully */
    _URC_FOREIGN_EXCEPTION_CAUGHT = 1,
    _URC_HANDLER_FOUND = 6,
    _URC_INSTALL_CONTEXT = 7,
    _URC_CONTINUE_UNWIND = 8,
    _URC_FAILURE = 9   /* unspecified failure of some kind */
} _Unwind_Reason_Code;

typedef uint32_t _Unwind_State;
typedef uint32_t _Unwind_EHT_Header;

struct _Unwind_Control_Block {
    char exception_class[8];
    void (*exception_cleanup)(_Unwind_Reason_Code, _Unwind_Control_Block *);
    /* Unwinder cache, private fields for the unwinder's use */
    struct {
        uint32_t reserved1;     /* init reserved1 to 0, then don't touch */
        uint32_t reserved2;
        uint32_t reserved3;
        uint32_t reserved4;
        uint32_t reserved5;
    } unwinder_cache;
    /* Propagation barrier cache (valid after phase 1): */
    struct {
        uint32_t sp;
        uint32_t bitpattern[5];
    } barrier_cache;
    /* Cleanup cache (preserved over cleanup): */
    struct {
        uint32_t bitpattern[4];
    } cleanup_cache;
    /* Pr cache (for pr's benefit): */
    struct {
        uint32_t fnstart;             /* function start address */
        _Unwind_EHT_Header *ehtp;     /* pointer to EHT entry header word */
        uint32_t additional;          /* additional data */
        uint32_t reserved1;
    } pr_cache;
    long long int :0;               /* Force alignment of next item to 8-byte boundary */
};

struct __cxa_exception {
    const std::type_info *exceptionType;       // RTTI object describing the type of the exception
    void *(*exceptionDestructor)(void *);      // Destructor for the exception object (may be NULL)
    std::unexpected_handler unexpectedHandler; // Handler in force after evaluating throw expr
    std::terminate_handler terminateHandler;   // Handler in force after evaluating throw expr
    __cxa_exception *nextCaughtException;      // Chain of "currently caught" c++ exception objects
    uint32_t handlerCount;                     // Count of how many handlers this EO is "caught" in
    __cxa_exception *nextPropagatingException; // Chain of objects saved over cleanup
    uint32_t propagationCount;                 // Count of live propagations (throws) of this EO
    _Unwind_Control_Block ucb;                 // Forces alignment of next item to 8-byte boundary
};

typedef void (*handler)(void);

struct __cxa_eh_globals {
    uint32_t uncaughtExceptions;               // counter
    std::unexpected_handler unexpectedHandler; // per-thread handler
    std::terminate_handler terminateHandler;   // per-thread handler
    bool implementation_ever_called_terminate; // true if it ever did
    handler call_hook;     // transient field to tell terminate/unexpected which hook to call
    __cxa_exception *caughtExceptions;         // chain of "caught" exceptions
    __cxa_exception *propagatingExceptions;    // chain of "propagating" (in cleanup) exceptions
    void *emergency_buffer;                    // emergency buffer for when rest of heap full
} __eh_globals;
bool __eh_globals_initialized = false;

static void __default_unexpected_handler()
{
    std::terminate();
}
static void __default_terminate_handler(void)
{
    std::abort();
}

struct emergency_eco {
  __cxa_exception ep;
  std::bad_alloc b;
};

struct emergency_buffer {
  bool inuse;
  emergency_eco eco;
} __emergency_buffer;

extern "C" __cxa_eh_globals* __cxa_get_globals(void)
{
    if (!__eh_globals_initialized)
    {
        __eh_globals.uncaughtExceptions = 0;
        __eh_globals.unexpectedHandler = __default_unexpected_handler;
        __eh_globals.terminateHandler = __default_terminate_handler;
        __eh_globals.implementation_ever_called_terminate = false;
        __eh_globals.call_hook = NULL;
        __eh_globals.caughtExceptions = NULL;
        __eh_globals.propagatingExceptions = NULL;
        __emergency_buffer.inuse = false;
        __eh_globals.emergency_buffer = &__emergency_buffer;
        __eh_globals_initialized = true;
    }
    return &__eh_globals;
}
#endif

__cxa_get_globals を再実装しています。なぜかというと、デフォルトの実装では初回呼び出しの際に malloc でこの構造体の領域を勝手に確保するくせに終了時に free してくれません。なので自前でグローバルに用意して、それを返してやることにします。
あと __eh_globals.emergency_buffer に emergency_buffer 構造体を渡していますが、これもデフォルトでは __ARM_exceptions_buffer_init という関数を使って領域を確保して渡してくれるのですが、最初に発生した例外が std::bad_alloc だったりして、ここで __cxa_get_globals が初めて呼ばれて緊急用バッファを確保しようとして失敗する、なんてことがあったりしそうなので、自前のグローバルなバッファを用意して格納しています。
まあでも、起動時に __cxa_get_globals とかやっちゃって初期化だけしておくのもありかなとは思います。
ちなみにこの辺の実装は Exception handling components, example implementations から持ってきました。


他に ARM の ABI についての情報は、ABI base standard 辺りからいろいろ辿れたりするので調べてみるといいかも。


とりあえずこれで ARM コンパイラ用の定義は終わりです。次は脳みそこねこねします。

コンパイル

自分の場合は以下のフラグを設定してコンパイルしました。

--show_cmdline
-I"C:\Program Files\BREW 3.1.2 Ja\sdk\inc"
-DDYNAMIC_APP
--exceptions
--no_rtti
-c
--cpu=ARM7TDMI
--littleend
--feedback feedback.txt
-O3
-Ospace
--no_alternative_tokens
--multibyte_chars
--locale japanese
--message_locale ja_JP
--split_sections
--diag_style ide
--apcs /interwork/ropi/norwpi

それぞれのフラグの細かいことは ARM からドキュメントを入手してコンパイラ/ライブラリガイドを読めば分かるかと。とりあえず適当に抜き出して説明。
--exceptions は必須です。これが無いと例外使えません。
--no_rtti を指定しちゃってますが、自分は RTTI を使ったら負けだと思ってるだけで、別に使いたい人は使っちゃっていいと思います。
--feedback は効果を実感したことは無いのですが、とりあえず指定してるだけという感じ。リンカから得られた情報をコンパイル時に反映させるためにあるらしいです。まあ大して効果は無いでしょうし、消してもいい気がします。
--no_alternative_tokens は代替トークンを使用不可にするフラグ。まず間違いなく使いません。
--message_locale ja_JP と --diag_style ideRVCT3.0 のエラー形式 - melpon日記 - HaskellもC++もまともに扱えないへたれのページ で書いたとおり。

    • split_sections は必須です。これが無いとリンク時に --first AEEMod_Load とやっても AEEMod_Load が先頭に来なくなってしまいます。


--apcs の /adsabi でデフォルトで付いてくる(http://brewforums.qualcomm.com/ja/showthread.php?t=1127)ので設定する必要は無いです。
ただし、/rwpi を指定すると例外が発生した場合におかしくなっちゃうので、/norwpi は必須です。

リンク

自分の場合は以下のフラグを設定してリンクしました。

--show_cmdline
--reloc
--split
--rw_base 0x208000
--ro-base 0
--exceptions
--entry AEEMod_Load
--first AEEMod_Load
--remove
--vfemode=force
--no_branchnop
--inline
--tailreorder
--callgraph
--feedback feedback.txt
--diag_style ide

この辺もドキュメントを入手して読めば分かるのですが、同様に適当に抜き出して解説。
--reloc と --split は elf2mod に通せる形式にするためのおまじないです。こうしないと通らないらしいのですが、何でなのか自分は分かりません。--split じゃないと elf2mod でいろいろ付与して作るのが難しくなるからなのかなぁとか思ったりしてますが、まあ分からなくてもいいやという感じでとりあえず指定。
--rw-base と --ro-base は BREW スレ9

926 :デフォルトの名無しさん  :2007/11/06(火) 13:48:44
    RVCT3.0になってから、armlinkが吐くelfの配置順が変わったのかな?
    ADS1.2の時は、プログラムヘッダが「RO>RW>DY」で並んでたのが
    RVCT3.0では「RW>RO>DY」で並んでるっぽい。
    elf2modはプログラムヘッダの先頭を決め打ちで見てるから、変換できないのか?

927 :デフォルトの名無しさん :2007/11/06(火) 15:17:18
    920=926です。
    従来のコードであれば動くようになりました。
    armlink --rw_base=0x208000
    などでRWのアドレスをROより後ろにずらせば、Elfのプログラムヘッダも
    RO>RWの順に並び、elf2modで変換でき、実機で動作しました。

    現在例外と格闘中… 

というのがあったので、その通り書いてます。確か --ro-base 0 --rw_base 0 でも動いたような記憶はあるのだけれども、面倒なので試してないです。
--remove --vfemode=force --no_branchnop --inline --tailreorder は容量削ったり最適化するためのフラグ……だとおもふ。どれぐらい効果が出てるのかは確認したことないです。
--callgraph は結構便利。ある関数がどこから呼び出されてどこを呼び出しているのかを、リンクしたライブラリの中身も含めて出力してくれるので、今回みたいな「例外機構が malloc を使ってるからこれを再定義しよう」とか「どこからも呼び出されていないので適当に返しておこう」みたいなことができるようになります。


これでリンクができて .elf ファイルが作られるはず。

elf2mod

あとは実機用のバイナリに変換するだけ。
これは単純に

elf2mod.exe -output hogehoge.mod hogehoge.elf

と書くだけ。正しくリンクできてるなら成功するはず。



自分はこれで実機用のバイナリができました。
今のところちゃんと動作してるので問題ないとは思いますが、保証はできません。