mapファイルの読み方

「ROMがあふれた」「RAMが足りない」「この関数はどこに配置されている?」。こうした疑問に答えてくれるのがmapファイル(リンカマップ)です。リンカが出力する配置結果の報告書です。テキストエディタで開けますが、読み方がわからないまま放置されがちな資料でもあります。本記事では、GCCツールチェーン(GNU ld)を例に、mapファイルからメモリ使用量を読み取り、肥大化の犯人を特定する手順を解説します。

mapファイルとは:リンカが出す配置報告書

mapファイルは、リンク処理の結果として「どの関数・変数を、どのアドレスに、何バイトで配置したか」を一覧にしたテキストファイルです。GCCではリンカオプションに -Wl,-Map=出力名.map を追加すると生成されます(-Wl, はGCCからGNU ldへオプションを渡すための指定です)。主要なコンパイラにはほぼ同等の機能があり、IDEではプロジェクト設定から有効化できることが多いです。

まず押さえる3つのブロック

  • メモリ構成(Memory Configuration):リンカスクリプトで定義したROM/RAMの開始アドレスとサイズの一覧。設計上の「器」の大きさです。
  • セクション配置:.text(コード)、.rodata(const定数)、.data(初期値ありの変数)、.bss(初期値が0、または初期化子のない変数)などが、どのアドレス範囲に置かれたか。
  • シンボル一覧:関数・変数ひとつひとつの配置アドレスとサイズ、どのオブジェクトファイル由来か。

セクションの意味がつかめると、mapファイルは一気に読めるようになります。リンカスクリプトとセクションの関係は既存記事「リンカスクリプトの役割」「メモリマップの読み方」でも解説しています。

ROMとRAMの使用量を読み取る

以降、この記事ではコード格納先の不揮発メモリ(フラッシュやEEPROMなど)を便宜上ROMと呼びます。使用量の集計は次の対応で考えます。

領域 含まれるもの 補足
ROM(フラッシュ) .text + .rodata + .dataの初期値 コードと定数、変数の初期値イメージ
RAM .data + .bss +(静的に確保されたスタック・ヒープ領域) 実行時に読み書きする領域

ポイントは .data が「ROMとRAMの両方」に計上されることです。一般的な構成では、初期値ありの変数は初期値イメージをROMに持ち、起動時にスタートアップコードがRAMへコピーします(リンカスクリプトのAT>やNOLOAD、起動方式の変更によって例外もあります)(詳しくは既存記事「ブートローダとスタートアップコードの実装」へ)。なお、手早く合計だけ見たいときは、GNU Binutils付属の size コマンドや、GNU ld のオプションである --print-memory-usage を使うと、リンカスクリプトの MEMORY で定義した各リージョンの使用量・総容量・使用率を表形式で確認できます。ただし、size コマンドが表示するのは text/data/bss のサイズであり、この記事で定義した ROM/RAM の内訳と完全には対応しません。厳密な内訳を確認したいときは、map ファイルが最も有効です。なお、表示形式によって計上先は変わります(既定の Berkeley 形式では text 列、SysV 形式ではセクションごとに個別表示されます)。バージョンや設定によって挙動が異なるため、詳細は man size または info binutils を参照してください。

なお、mapファイルで確認できるスタック・ヒープは、リンカスクリプトで静的に確保した領域のサイズであり、実行時の動的な使用量ではありません。予約セクションを設けていない構成では、スタック・ヒープがmapファイル上に出力されないこともあります。追跡したい場合は専用の予約セクションを設けます。

肥大化の犯人探し

  • シンボルをサイズ順に並べる:nm -S --size-sort(-S はサイズそのものを表示する指定)や、mapファイルのシンボル一覧をスクリプトで集計すると、大きい関数・配列が一目でわかります。巨大なconstテーブルや、デバッグ用に置いた配列が見つかることもよくあります。
  • printfと書式処理ライブラリ:printf系を導入した途端に数KB〜十数KB増えるのは典型例です。書式処理ライブラリ一式がリンクされるためであり、mapファイルのライブラリ由来シンボルとして確認できます(「組み込みでprintfを使う方法」参照)。
  • 最適化設定の影響:インライン展開はコードサイズを増やす方向に働きます。サイズ優先(-Os)との比較は既存記事「最適化オプションの使い分け」で解説しています。
  • 身に覚えのないライブラリ関数:mapファイルにはアーカイブ(.a)からどのオブジェクトが「なぜ」リンクされたかの記録も出力されます。除算やmemcpyなどコンパイラが暗黙に呼ぶ関数の出どころもここで追えます。

「あの変数はどこ?」を調べる

シンボル一覧を変数名で検索すれば、配置アドレスとセクションがわかります。見つからない場合は、最適化で削除された可能性が高いです(誰からも参照されない変数・関数はリンカやコンパイラが除去します)。また、特定の変数を特定領域(外部RAMや不揮発領域など)に置きたい場合は、セクション指定属性で専用セクションに割り当て、mapファイルで意図どおり配置されたかを確認します。

実務でのおすすめ運用

  • mapファイルをビルド成果物として保存する:リリースごとに残しておくと、バージョン間のdiffで「何が増えたか」を即座に特定できます。
  • メモリ余裕率をリリース判定に入れる:ROM/RAMとも、量産後の修正余地として余裕(目安として2〜3割)を確認してから出荷します。

まとめ

mapファイルは「メモリ構成・セクション・シンボル」の3階層で読みます。ROM/RAMの内訳が数字で見えるようになると、メモリ不足対応が場当たり的な削減作業から、根拠のある設計改善に変わります。

組み込みソフトの世界 トップへ戻る