ローグウェーブソフトウェアのブログ

開発をシンプルに 安全で高品質のコードを 素早くお客様のもとへ

JViews 2017リリース / 浮動小数点数演算の倍精度化

こんにちは、ローグウェーブのセールスエンジニア柄澤(からさわ)です。Java向けの大規模GUIを作成するためのライブラリ、ローグウェーブのJViews最新版2017がリリースされました 。

先日のTotalViewのPythonデバッグに関するエントリではPYPLにおけるPython人気に触れましたが、このランキングでの1位は依然としてJavaが占めており、この言語の人気の根強さを示しています。Kotlinなどの言語が今後どのくらい受容されていくか、ということも含めて目が離せません。

JViewsは、通信ネットワークの管理や航空機の運行管理など、世界中のミッションクリティカルなアプリケーション開発で信頼され利用されています。オンラインデモのページからは、JViewsを利用したダイアグラム操作やプロセスのモニタリング、ダッシュボードやグラフなど様々なアプリケーションをブラウザでインタラクティブに動かすことができます。

JViewsのバージョン番号は近年の他のローグウェーブ製品に合わせて今回から2017となっていますが、内部的にはまだ前回のリリースまでの慣習を引き継いで、9.0という呼び方もなされています。

倍精度化

さて、今回のJViews 2017ではお客様の重要なアプリケーションを改善するために、従来の製品と非互換な改良を加えました。地図アプリケーションなど大規模なアプリケーションでは正確な計算結果を得たり正しい位置にオブジェクトを配置するために倍精度計算を行うのが一般的ですが、従来floatで値を受け取っていたJViewsメソッドのシグニチャが今回のバージョンからdoubleで受け取るように変更されました。

JViews 2017への移行を支援するために移行ツールが提供されます。このツールはお客様のコードを評価し、どのメソッドやコンストラクタで変更が必要なのか教えてくれます。JViews全体のリリースノートに、このツールダウンロードのための手順と移行のための詳細なガイドラインが記されています。また、このリリースノートにはChartsやMaps等各製品のリリースノートへのリンクがあり、個別のメソッドがどのように変更されたか確認することができます。

移行ツールは、どのメソッドを修正すべきなのかを教えてくれます。その後、EclipseのClean upオプションを使ってコードを整理し、JViewsの新しいライブラリをプロジェクトに加え、APIの引数をfloatからdoubleに変更し、浮動小数点変数のリテラルを、例えば0.5fから0.5のように変更し、あるいは不要なキャストを取り除き、Javadocのannotationをチェックしてください。グラフィックデータの永続性に関するI/Oストリームやシリアル化のような操作はバイナリを直接扱うため殊更に注意が必要です。また、精度が高まったことによって等値の判定基準も影響を受けるはずです。リリースノートに書かれた手順を注意深く読み、十分なテストを行なってください。

移行についてご不明点があったりご支援が必要な場合はサポートチームまでご連絡ください。

Javaサポートバージョン

JViews 2017はJava 7(2011年リリース)と8(2014年リリース)をサポートします。今回のリリースがJava 7をサポートする最後のJViewsリリースになります。

ところで、当初2017年3月に予定されていたJava 9のリリースが7月に延期され、今度はさらに9月に延期されるそうです。強力なプレイヤーがひしめくオープンソースコミュニティと緊密に連携を取り合いながら大規模な仕様の更新を行う苦労は並大抵のものではないだろうと思います。

以上注意を促す書き方になってしまいましたが、計算精度が高まることは間違いなくメリットとなります。JViewsの バージョンアップのご依頼をお待ちしております。

セールスエンジニア 柄澤 (からさわ)

Split DWARFを用いてデバッグ時間やメモリを節約 - ホワイトペーパーから

こんにちは、ローグウェーブのセールスエンジニア柄澤(からさわ)です。快適なデバッグ生活を過ごしておられるでしょうか?プロジェクトが巨大になると、コンパイルは並列化できてもリンクやデバッガの起動は単一のプロセスで実行されるためどうしても時間がかかり、開発プロセスの大きなボトルネックになってしまう、という話があります。

今回の投稿は、ローグウェーブの英語ホワイトペーパー Saving time and space with Split DWARFを日本語訳したものです。このホワイトペーパーでは、Linuxなどの開発環境でソフトウェアのデバッグにかかる時間やメモリを大幅に削減する一般的な方法であるSplit DWARFをご紹介します。

Split DWARFを用いてデバッグ時間やメモリを節約

宇宙が拡大しているということはどなたもご存知でしょう。何のために?もしかしたらそれは、より大きいアプリケーションプログラムのための隙間と、デバッグ情報を格納するのに必要なディスクスペースを作るためかもしれません。このホワイトペーパーでは、DWARF Debugging Standardの比較的新しいアプローチについて説明します。

Overview

Brian Kernighan曰く「誰もが知っているようにデバッグは、最初にプログラムを書いたときに比べて2倍大変です。したがって、あなたの賢明さがコーディングの時と同程度だとしたら、一体どうしてデバッグが可能なのでしょうか?」 (“The Elements of Programming Style"「プログラム書法」, Brian W. Kernighan, 第2版、第2章)。 もちろん、明白な答えはデバッガを使用することですが、デバッガを使うには完全なデバッグ情報が必要です。

コンパイラは、C、C ++、Fortranなどコンパイルが必要な言語で書かれたアプリケーションを記述するためにデバッグ情報を生成します。この情報には、関数や変数などのオブジェクトの名前とアドレス、ソースファイル名と行番号、組み込み型、構造体定義、配列、ポインタなどの型情報が含まれます。

Split DWARFは、DWARFデバッグ情報の保存、アプリケーションのリンク時間の高速化、システムメモリとIO要件の削減、およびデバッガの起動の高速化に必要なスペースを大幅に縮小する新しいアプローチです。 Split DWARFは、「オブジェクトファイル」 (.o) のデバッグ情報の大部分を、リンカによって処理されない別の “DWARFオブジェクトファイル"(.dwo)に置きます。

Split DWARFの革新は、時間の経過と共にアプリケーションがより大きなサイズに向かって拡大しているという事実によって推進されました。これは、数年、数十年にわたって開発されてきたアプリケーションに特に当てはまります。この肥大化はアプリケーションに機能を追加することに起因することもありますが、プログラミング言語パラダイムの進化も成長に寄与しています。例えば、米国エネルギー省の国立研究所は、C++11のテンプレートとラムダ関数を大量に使用するRAJAKokkosなどの「パフォーマンス移植レイヤー」を開発しています。このようなフレームワークは、プログラマーの生産性を向上させますが、数多くのテンプレートインスタンス化、インライン関数、および数千のソースモジュールにまたがる複雑なクラスタイプなど、プログラムの記述に必要なデバッグ情報の量を増やしてしまいます。

アプリケーションの肥大化は数十年前に開発され、現在も続行しているFortranプログラムやその他のレガシー・アプリケーションでも発生しています。たとえば、私たちのTotalViewのお客様の中には2GBを超えるデバッグ情報を含むモノリシックな実行可能ファイルとして構造化されたアプリケーションを持っている方がおられます。また、別のお客様はモジュラー技術を使用し、数百の共有ライブラリに分散した総計10GBを超えるデバッグ情報を抱えるアプリケーションを持っています。

Split DWARF以前は、イメージファイル(実行ファイルや共有ライブラリ)にすべてのデバッグ情報が1つのファイル内に格納されていました。通常、DWARFデバッグ情報は、プログラムのコードセグメントとデータセグメントとともに、イメージファイル自体に格納されます。 「dwz」DWARF Compressor Tool圧縮されたデバッグ情報セクションDWARF 4 Compression and Duplicate Eliminationなど、DWARFデバッグ情報のサイズを縮小するさまざまな手法が開発されています。Split DWARFとDWARF Compression技術とを混同しないようにしてください。イメージファイル全体のデバッグ情報を別のファイルに(圧縮または非圧縮で)保存し、コードとデータを含んだイメージファイルをその別のファイルに「リンク」する「separate debug information file」方式がいくつか存在します。 GNU Debug LinkとBuild IDのアプローチは、この「separate debug information file」スキームの例ですが、Oracle Solaris 11の補助オブジェクトなどの他のものも存在します。これらのテクニックにはそれぞれの使い道がありますが、いずれにしてもリンカはすべてのデバッグ情報を処理する必要があり、それにはコストがかかります。

  • リンク時間が遅い。多くのオブジェクトファイルを1つのイメージファイルにリンクすると、リンカはオブジェクトファイル内のデバッグ情報を読み取り、デバッグ情報に関連付けられた再配置情報を適用し、イメージファイルに書き戻します。ここでのコストには、ディスクやネットワークトラフィックなどのI/O、再配置のためのCPU時間、すべてのデバッグ情報を含むイメージファイルを格納するためのディスク領域が含まれます。
  • メモリ不足。物理メモリの量に応じて、リンカはページングが必要なくらいメモリを消費する可能性があります。過度のページングは​​スラッシングにつながり、リンカをさらに遅くする可能性があります。最終的に仮想メモリが不足してリンカが失敗する可能性もあります。TotalViewのあるお客様のアプリケーションは非常に大きいので、アプリケーションを完全なデバッグ情報と共にリンクするための十分な物理/仮想メモリが開発システムにありませんでした。そのお客様は、デバッグ情報を含めるためにソースモジュールのサブセットごとにコンパイルする必要がありましたが、これはデバッグが必要なモジュールセットが頻繁に変化するため、開発者にとって大きな負担になりました。ここでのコストは開発者の生産性です。
  • ディスク容量。伝統的に、コンパイラデバッグ情報をオブジェクトファイルに書き込み、リンカはそれをイメージファイルにコピーします。つまり、デバッガ情報のコピーが2つあり、リンカによって適用された再配置だけが異なります。ここでのコストは、ディスク容量の消費量です。多くの場合、特にC++の場合、デバッグ情報はプログラムのコードとデータの約2倍です。デバッグ情報のコピーを1つだけ削除するだけで、ディスクの消費量を40%以上削減できます。

Split DWARFは、開発者が日常的にアプリケーションを編集、コンパイル、リンク、デバッグするソフトウェア開発ライフサイクルの実装段階で使われるのに最も適しています。また、処理時間が迅速になれば開発者の生産性は向上するでしょう。

しかし、Split DWARFは、デバッグ情報がアプリケーションと共に再配布され、プログラムの完全なデバッグ情報がソースファイルと同じ数のDWARFオブジェクトファイルに分散されているプロダクションビルドには不適切です(大量の.dwoファイルが出力されるため)。これを解決する方法は、 「.dwo」ファイルを配布用の単一の「.dwp」ファイルに結合する dwpというSplit DWARFパッケージャユーティリティを使用することです。

GNU DEBUGFISSIONとDWARF 5 SPLIT DWARF

2011年頃、GNUGNU DebugFission Projectを開始しました。 DebugFission Projectの目標は、Split DWARFアプローチを使用して、リンカが処理するデバッグ情報の大半を排除することによって、時間とディスクスペースを節約することでした。 DebugFissionはDWARF「ベンダー拡張」としてDWARF 4に実装され、最近リリースされたDWARF 5 Standardにほぼそのまま採用されました。

Split DWARF戦略はシンプルなものです。コンパイル時に、コンパイラはDWARFを2つの部分に分割します(「Spits the DWARF」)。一部はオブジェクトファイル(.o)に残り、残りは対応するDWARFオブジェクトファイル(.dwo)に書き込まれます。軽量の 「スケルトン」DWARFデバッグ情報は.oファイルに含まれ、完全なDWARFデバッグ情報は.dwoファイルに含まれています。リンカは.dwoファイルではなく.oファイルのみを処理するため、処理されるオブジェクトファイルのサイズは大幅に縮小されます。

デバッグ情報を分割するというアイディアは、決して新しいものではありません。実際、Split DWARFの概念自体新しい考えではないのです。 20年以上前、Sun MicrosystemsSTABSデバッグ形式でデバッグ情報の分割を実装しました。ほとんどのSTABSはオブジェクトファイル内に残り、軽量の 「STABSインデックス」情報がイメージファイルに書き込まれました。 AppleがSTABSからDWARF形式に切り替えたとき、Mach-Oオブジェクトファイル用に独自のSplit DWARFスキームが実装されました。 HPは、HP-UXで異なる「+ objdebug」Split DWARFのバリアントを使用しています。 Oracle Studioのコンパイラでは、Split DWARFの別のバリアントが元のSplit STABS形式とともに生成されます。

Split DWARFのバリアントであるDebugFissionは、LinuxプラットフォームでGNUIntel、Clangコンパイラを使用して使用できます。残念ながら、Split DWARFのDWARF 5バリアントの実装はありませんが、Red HatGCCのプロトタイプを 「-gdwarf-5」コンパイラオプションの下で開発していると言われています。

ホワイトペーパーの残りの部分で、Split DWARFのDebugFissionバリアントに焦点を当て、Split DWARFを使用してアプリケーションを構築する方法とその動作方法について簡単に説明します。

デバッグ情報の索引付け

Split DWARFなど分割デバッグ方式の重要な側面の1つは、デバッガがシンボルテーブルを高速に読み込むために、コンパイラやリンカが適切なインデックス情報を生成するというものです。その仕組みがなければ、デバッガはすべての.dwo (または.o)オブジェクトファイルを開き、DWARF情報をスキップして、関数、変数、および型が定義されている場所を特定しなければなりません。 何千個にも及ぶ数のオブジェクトファイルからデバッグ情報を除外することは、単一のイメージファイルから情報を除外することよりも高くつきます。

DebugFissionは、 「.gdb_index」という名前のセクションにSplit DWARF情報のインデックスを作成します。現在、GDB自身を除けば、Linuxの 「gold」リンカ (ld.gold) のみが「.gdb_index」セクションを生成します。したがって、Split DWARFをコンパイルして.gdb_indexセクションを生成するにはこのgoldリンカを使用しなければなりません。厳密に言えば、Split DWARFでコンパイルするときに必ずしも.gdb_indexを生成する必要はありませんが、そうすることで大規模アプリケーションでのデバッガの起動を高速化できます。

DebugFission Split DWARFのビルド

図1は、Linux上のGNUIntel、またはClangコンパイラ用のDebugFission Split DWARFの基本構築モデルを示しています。

f:id:RWSJapan:20170619173116p:plain

  • コンパイル: 完全なDWARFデバッグ情報を含む 「.dw」(DWARFオブジェクト)ファイルと、コード、データ、およびスケルトンのDWARFデバッグ情報を含む 「.o」(オブジェクト)ファイルを生成するには、 コンパイラオプション「-gsplit-dwarf」を使用します。なお、以下に示すように、 「.o」ファイル内のDWARFデバッグ情報は 「.dwo」ファイルを指しているため、そのモジュールをデバッグするなら 「.dwo」ファイルを削除してはなりません。
  • リンク: goldリンカ(ld.gold)を使用するには、「-fuse-ld=gold」コンパイラオプションを使用します。gold-linkerに 「–gdb-index」オプションを渡すには、「-Wl、–gdb-index」コンパイラオプションを使用します。生成された実行ファイルや共有ライブラリのイメージファイルには、デバッガが高速起動するのに必要な「.gdb_index」セクションが含まれています。
単純な分割DWARFの例

Split DWARFがどのように動作するかを確認するには、サンプルを見ることが最も簡単です。 図2は、www.cplusplus.comにあるC++コードです。このコードは、コンパイルするとテンプレート展開のためにやや数の多いシンボルを生成します。 図3に示すmakefileは、Split DWARFがある場合とない場合の両方についてサンプルプログラムをビルドします。

map_empty.cxx

// http://www.cplusplus.com/reference/map/map/empty/
// map::empty
#include <iostream>
#include <map>
int main () {
  std::map<char,int> mymap;
  mymap['a']=10;
  mymap['b']=20;
  mymap['c']=30;
  while (!mymap.empty()) {
    std::cout << mymap.begin()->first << " => " << mymap.begin()->second << '\n';
    mymap.erase(mymap.begin());
  }
  return 0;
}

Makefileは、

all: map_empty.normal.exe map_empty.split-dwarf.exe
clean:
    rm *.o *.exe *.dwo

map_empty.normal.o: map_empty.cxx
    g++ -g -c -o $@ $<
map_empty.normal.exe: map_empty.normal.o
    g++ -o $@ $<
map_empty.split-dwarf.o: map_empty.cxx
    g++ -gsplit-dwarf -c -o $@ $<

map_empty.split-dwarf.exe: map_empty.split-dwarf.o
    g++ -o $@ $< -fuse-ld=gold -Wl,--gdb-index

Fedora 25 x86_64システムでのビルドの結果を下の図に示します。次の点に注意してください。

  • 通常のビルドの実行ファイルは、Split DWARFビルドの実行ファイルより約60%大きい。
  • 通常のビルドのオブジェクトファイルは、Split DWARFビルドのオブジェクトファイルよりも約75パーセント大きい。
  • Split DWARFでビルドされたオブジェクトとDWARFオブジェクトファイルの合計サイズは、通常のビルドオブジェクトファイルのサイズよりも小さくなっています。

この簡単なサンプルでも、Split DWARFビルドファイルは通常のビルドファイルよりも小さくなっています。 大規模なプログラムの場合、さらに劇的にサイズが節約されます。以下はFedora 25, x86_64の結果です。

% make
g++ -g -c -o map_empty.normal.o map_empty.cxx
g++ -o map_empty.normal.exe map_empty.normal.o
g++ -gsplit-dwarf -c -o map_empty.split-dwarf.o map_empty.cxx
g++ -o map_empty.split-dwarf.exe map_empty.split-dwarf.o -fuse-ld=gold -Wl,--gdb-index
% ls -l
total 572
-rw-r--r-- 1 jdelsign tss     355 Apr  7 15:20 Makefile
-rw-r--r-- 1 jdelsign tss     360 Apr  7 15:13 map_empty.cxx
-rwxr-xr-x 1 jdelsign tss  109656 Apr 10 13:30 map_empty.normal.exe
-rw-r--r-- 1 jdelsign tss  192512 Apr 10 13:30 map_empty.normal.o
-rw-r--r-- 1 jdelsign tss   70744 Apr 10 13:30 map_empty.split-dwarf.dwo
-rwxr-xr-x 1 jdelsign tss   67609 Apr 10 13:30 map_empty.split-dwarf.exe
-rw-r--r-- 1 jdelsign tss  110560 Apr 10 13:30 map_empty.split-dwarf.o
%

以下にSplit DWARFの実行ファイルとDWARFオブジェクトファイルのデバッグ情報セクションを示しています。 次の点に着目してください。

  • Split DWARF実行ファイル内のDWARFデバッグ情報エントリ(DIEs, debug information entries)を含む「.debug_info」セクションは小さく、0x34バイトしか占有しません。
  • DWARFオブジェクトファイルの 「.debug_info.dwo」セクションには、大量のDIEが含まれ、0x6bbcバイトを占有します。

DWARFオブジェクトファイルのデバッグセクションには、すべて.dwoというサフィックスが付きます

% readelf -SW map_empty.split-dwarf.exe | egrep 'debug_|gdb_index|Nr'
 [Nr] Name Type Address Off Size ES Flg Lk Inf Al
 [29] .debug_addr PROGBITS 0000000000000000 003d81 000378 00 0 0 1
 [30] .debug_info PROGBITS 0000000000000000 0040f9 000034 00 0 0 1
 [31] .debug_abbrev PROGBITS 0000000000000000 00412d 00001d 00 0 0 1
 [32] .debug_ranges PROGBITS 0000000000000000 00414a 000690 00 0 0 1
 [33] .debug_line PROGBITS 0000000000000000 0047da 000ea6 00 0 0 1
 [34] .debug_str PROGBITS 0000000000000000 005680 000035 01 MS 0 0 1
 [35] .gdb_index PROGBITS 0000000000000000 009c90 006b89 00 0 0 4
% readelf -SW map_empty.split-dwarf.dwo | egrep 'debug_|gdb_index|Nr'
 [Nr] Name Type Address Off Size ES Flg Lk Inf Al
 [ 1] .debug_info.dwo PROGBITS 0000000000000000 000040 006bbc 00 E 0 0 1
 [ 2] .debug_abbrev.dwo PROGBITS 0000000000000000 006bfc 000ac8 00 E 0 0 1
 [ 3] .debug_line.dwo PROGBITS 0000000000000000 0076c4 0003d2 00 E 0 0 1
 [ 4] .debug_str_offsets.dwo PROGBITS 0000000000000000 007a96 001074 00 E 0 0 1
 [ 5] .debug_str.dwo PROGBITS 0000000000000000 008b0a 008604 00 E 0 0 1

Split DWARF実行ファイルをダンプした完全なシンボルテーブルは以下の通りです。 これには、DWARFオブジェクトファイルを指す「スケルトン」のコンパイル単位(CU, compilation unit) DIEであるDIEがちょうど1つだけあります。 DT_AT_GNU_ *属性タグでラベル付けされた行は、GNU DWARF 4拡張です。 関連する属性は次のとおりです。

  • DW_AT_GNU_dwo_name: DWARFオブジェクトファイルの名前。ディレクトパスコンポーネントを含むことができます。
  • DW_AT_comp_dir: モジュールがコンパイルされたときのコンパイラの作業ディレクトリパスの標準DWARF属性。 この属性はデバッガが.dwoファイルを検索するのに役立ちます。
  • DW_AT_GNU_dwo_id: 一意の64ビットDWARFオブジェクトID値で、以下に示すように対応する 「.dwo」ファイルの値と一致する必要があります。
% readelf -wi map_empty.split-dwarf.exe
Contents of the .debug_info section:

  Compilation Unit @ offset 0x0:
    Length: 0x30 (32-bit)
    Version: 4
    Abbrev Offset: 0x0
    Pointer Size: 8
 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
      <c> DW_AT_ranges : 0x0
      <10> DW_AT_low_pc : 0x0
      <18> DW_AT_stmt_list : 0x0
      <1c> DW_AT_GNU_dwo_name: (indirect string, offset: 0x0): map_empty.split-dwarf.
dwo
      <20> DW_AT_comp_dir : (indirect string, offset: 0x1a): /home/jdelsign/
split-dwarf
      <24> DW_AT_GNU_pubnames: 1
      <24> DW_AT_GNU_addr_base: 0x0
      <28> DW_AT_GNU_dwo_id : 0xa35fb8a809fe160e
      <30> DW_AT_GNU_ranges_base: 0x0
%

以下に .dwoファイルのシンボルテーブルのダンプを示しています。 最初のコマンドによると、CUに含まれるすべてのシンボルのDWARF情報が14,028行あり、上に示したSplit DWARF実行ファイルよりもかなり大きいことがわかります。2番目のコマンドは、DWARFを生成したコンパイラ、言語、ソースファイル名、コンパイル作業ディレクトリ、および64ビットDWARFオブジェクトIDといったソースファイルの属性を提供する 「完全 split DWARF」 CU DIEを表示します。

% readelf -wi map_empty.split-dwarf.dwo | wc -l
14028
% readelf -wi map_empty.split-dwarf.dwo | head -13
Contents of the .debug_info.dwo section:
 Compilation Unit @ offset 0x0:
 Length: 0x6bb8 (32-bit)
 Version: 4
 Abbrev Offset: 0x0
 Pointer Size: 8
 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
    <c> DW_AT_producer : (indexed string: 0x2b4): GNU C++14 6.3.1 20161221 (Red
Hat 6.3.1-1) -mtune=generic -march=x86-64 -gsplit-dwarf
    <e> DW_AT_language : 4 (C++)
    <f> DW_AT_name : (indexed string: 0x2a2): map_empty.cxx
    <11> DW_AT_comp_dir : (indexed string: 0x236): /home/jdelsign/split-dwarf
    <13> DW_AT_GNU_dwo_id : 0xa35fb8a809fe160e
%

以下に、Split DWARF実行ファイルの 「.gdb_index」セクションのダンプを示しています。前述のように、 「.gdb_index」セクションはSplit DWARFを使用するために必須というわけではありません。大抵のデバッガはインデックスなしでSplit DWARFを処理できるからです。とはいえ、デバッガの起動時間を短縮するなら推奨されます。簡単に説明すると、索引セクションには次のテーブルが含まれています。

  • コンパイル単位のDIEオフセット範囲を示すエントリを含むCUテーブル。 CUがスケルトンCUか完全なDWARF CUかにかかわらず、通常、コンパイル単位ごとに1つのエントリがあります。 CUテーブルのエントリは、CUテーブルインデックスを使用して他の 「.gdb_index」テーブルから参照されます。
  • 上記の「CUテーブル」のような、「タイプCU」を保持するTUテーブル。
  • アドレスによるルックアップ用のアドレステーブル。現在は関数のみに使用されます。エントリには、CUで定義された関数のアドレス範囲とCUテーブルのインデックスが含まれます。デバッガは、このテーブルのアドレスをすばやく検索して、関数が定義されているCUを特定できます。
  • 関数、変数、型、他のオブジェクトの名前によるルックアップのためのシンボルテーブル。このテーブルは、オブジェクトの正規(デマングル)名でハッシュされたハッシュテーブルエントリのセットとして編成されています。各エントリには、オブジェクトの名前、CUテーブルインデックス、オブジェクトがグローバルかスタティックか、型なのか変数なのか関数であるのかを示す情報が含まれています。デバッガは、このテーブルの名前をすばやく検索して、オブジェクトが定義されているCUを特定できます。リンカは時にシンボルテーブルを生成しないことがあります。その場合、インデックス全体がデバッガの役に立たなくなり、デバッガはすべてのDWARFオブジェクトファイルの内容をスキミングしなければなりません。幸いにも、これは小規模なアプリケーションでのみ起こるようです。
% readelf --debug-dump=gdb_index map_empty.split-dwarf.exe
Contents of the .gdb_index section:
Version 7

CU table:
[ 0] 0x0 - 0x33

TU table:

Address table:
0000000000400ca6 0000000000400e4b 0
0000000000400e4b 0000000000400e5d 0
0000000000400e5d 0000000000400e6c 0
…snip…
0000000000402676 0000000000402690 0
0000000000402690 00000000004026aa 0
00000000004026aa 00000000004026e9 0

Symbol table:
[ 2] std::move<char&>: 0 [global, function]
[ 3] __gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<char const, int> > >::~new_
allocator: 0 [global, function]
[ 4] std::_V2: 0 [global, type]
…snip…
[1015] std::allocator_arg: 0 [static, variable]
[1019] __gnu_cxx::__numeric_traits_integer<int>: 0 [global, type]
[1020] __gnu_cxx::__ops: 0 [global, type]
%

最後に、以下のスクリーンショットはTotalViewによるサンプルの分割DWARF実行ファイルのデバッグセッションを示しています。 このデバッグセッションは、TotalViewとCodeDynamicsで実行できます。

f:id:RWSJapan:20170619173234p:plain

TotalView

TotalViewやCodeDynamicsなどのローグウェーブの動的解析製品は、成熟して堅牢、スケーラブルで機能豊富なデバッガで、C、C ++、Fortranプログラミング言語、MPI、OpenMP、pthreadsなどの並列プログラミングパラダイムのハイブリッド混合、CUDA、OpenACC、UPC、CAF、グローバルアレイ、SHMEMなどをサポートします。

これら動的解析製品は、インタラクティブデバッグ用のグラフィカルユーザーインターフェイス(GUI)とコマンドラインインターフェイス(CLI)を提供し、ユーザーは複数の言語と並列プログラミングのパラダイムからなる多数のプロセスとスレッドを単一の画面からコントロールデバッグできます。標準的なデバッガの機能には次のものがあります。スレッド、プロセス、スレッドまたはプロセスのグループの開始と停止、ソースと命令レベルのステップ実行。ブレークポイントと条件付きブレークポイントソースコード、変数、配列、メモリブロック、およびレジスタの表示、プログラムの状態の変更など。CLIプログラマブルなTclインタプリタであり、デバッガの機能を拡張するためのユーザ定義ユーティリティの作成をサポートし、デバッグの自動化を可能にするスクリプト言語を提供します。 スクリプト言語TVScriptとMemScriptは、非インタラクティブなバッチデバッグフレームワークです。 「イベントアクション」モデルを使用して、ユーザは、プログラム内で発生する一連のイベントと、イベントが発生したときにとるアクションとを定義します。バッチジョブが完了すると、レビュー用のデータが一連のテキスト形式のログファイルに記録されます。

TotalViewとCodeDynamicsは、他にも様々な機能があります。C、C ++、FortranのExpression Evaluator、 データのウォッチポイント、配列データの可視化、 map、set、vector、list、stringといったSTLコレクションクラスの要素を表示する機能、並列アプリケーションのデータを集約するディスプレイ、メモリデバッグ、コアファイルのデバッグ、非同期なスレッド制御、リバースデバッグヘテロジニアスなデバッグ、充実したヘルプとドキュメントなどです。

編集後記

いかがだったでしょうか。先日ふとLinuxでブラウザのChromiumをデバッグしたところ、TotalViewの起動が早く、わずか数分で立ち上がったことに驚きました。その後の動きも軽快でした。起動画面には「Split DWARF skeleton CU」というメッセージが大量に現れ、なるほど今回のホワイトペーパーの内容が使われているのか、と合点が行きました。ドキュメントにはDWARF Fissionが利用されている旨が記され、ビルドディレクトリには.oファイルに対応した.dwoファイルが出力されていました。巨大なアプリケーションを開発している方には便利、というより必須の機能と言えるのではないでしょうか。

ローグウェーブの日本語ウェブサイトに記載されているTotalViewの様々な機能をご覧いただき、ご興味があればトライアル申し込みよりご連絡ください。

ローグウェーブ セールスエンジニア 柄澤(からさわ)

分散環境におけるメモリエラー解析 - ホワイトペーパー紹介

こんにちは、ローグウェーブセールスエンジニアの柄澤です。暑くじめじめした季節になってきましたが、開発者の皆様にとってアプリケーションのメモリ関係のエラーもこの上なく鬱陶しい存在として日々悩まされているのではと思います。

この記事は、ローグウェーブの英語ホワイトペーパー Memory error analysis in distributed applications を日本語訳したものです。このホワイトペーパーでは、並列実行環境における様々なメモリ関係の問題の紹介と分類、そしてソリューションとして、ローグウェーブの並列デバッガTotalViewに搭載されたメモリデバッグ機能 MemoryScapeがどのように問題に対処するのか、ご紹介します。

  • 分散環境におけるメモリエラー解析
    • 序文
    • はじめに
    • メモリエラーの分類
      • Mallocエラー
      • ダングリングポインタ
      • メモリ境界違反
      • Read-Before-Writeエラー
    • メモリリークの検出
      • ヒープ境界違反の検出
    • MemoryScape デバッガ
      • MemoryScapeのアーキテクチャ
      • MemoryScapeの並列アーキテクチャ
      • MemoryScapeを使用したメモリ統計の比較
      • MemoryScapeを使用したヒープ状態の表示
      • MemoryScapeを使用したリークの検出
      • MemoryScapeを使用したヒープ境界違反の検出
      • TotalViewの内部でMemoryScapeを使用する
    • 結論
  • 編集後記

分散環境におけるメモリエラー解析

序文

並列アプリケーションはあらゆる場所で使用されています。動画をストリーミング配信したり、常にユーザーのリクエストに応じた様々なコンテンツを表示するWebアプリケーションを提供している企業にとってそれは特に顕著です。そうした業界で使われているアプリケーションは、多くのユーザーからリクエストを受け取り、それぞれがプロセスやスレッドによって処理されます。スレッドやプロセスの数は、任意の時点におけるシステム上のユーザー数に基づいています。アプリケーションによっては何度もスレッドは作成され破棄されますが、時にはメモリが割り当てられたままになることもあります。また、その反対にアプリケーションによっては同じプロセスやスレッドをずっと使い続けることがありますが、長時間実行されるプロセスは、データフィードのサイズに比例した大きなメモリリークを引き起こす可能性があります。メモリのバグ、つまりヒープメモリの管理の失敗により、システムが急速に遅くなったりクラッシュすることにつながります。

メモリバグは一般に追跡が困難です。スタックの上書き、ヒープの解放忘れ、間違ったタイミングでの解放など、さまざまな要因によって生じるからです。現象が顕在化するのに時間がかかるため、原因となったコードの特定の行までトレースするのは簡単でありません。全体として、メモリのバグは、アプリケーションのユーザビリティに大きな損害を与える可能性があります。定期的にサーバーの電源を切って再起動しなければならないようでは、これらのサーバーを信頼することはできません。幸いなことに、解決策があります。このホワイトペーパーでは、根本原因とこれらのバグの解決方法について説明します。

続きを読む