Win32デバッグ(12)・・・SEH(Structured Exception Handling)

まぁ、どうしようか悩んだが、とりあえず、進める所まで進んでみますsad

ということで今回のお題目は例外。結論から言うとDelphiではtry-except文による例外処理にしろ、try-finally文の終了処理にしろ、WindowsのSEH(Structured Exception Handling)を使って実装されているのだが、そのSEHについて。

SEHはWindowsによって提供されている構造化例外処理のためのメカニズムのことであるが、その流れについて説明する。

まず、0による除算や不正なメモリへのアクセスなどのハードウェア例外にしろ、Win32 APIのRaiseException関数によるソフトウェア例外にしろ、スレッド内で例外が発生するとOSに制御が移る。そして、OSは例外が発生したスレッドのCPUレジスタなどのコンテキスト情報の保存など必要な処理を行った後、例外をハンドルするための例外ハンドラを検索し、呼び出す。

具体的には、まず、OSは例外が発生したスレッドのスレッド環境ブロック(TEB:Thread Environment Block)またはスレッド情報ブロック(TIB:Thread Information Block)と呼ばれる領域の先頭4バイト(at FS:[0])の値を取得する。この4バイトの値は例外ハンドラのリンクリストの先頭をポイントしているので、この値から先頭の例外ハンドラから順に呼び出していくのである(つまり、ここに例外ハンドラを登録すれば例外発生時に例外ハンドラが呼び出されるようになるのだが、通常は、Delphiにしろ、C++にしろ、言語提供の例外処理などを利用すれば、コンパイラによって登録するコードが挿入されるので、普通は自前で登録しないが・・)。

リンクリストのノードは次のようなEXCEPTION_REGISTRATION構造体になっている。

prevメンバは次のノードへのアドレス、handlerメンバーには例外ハンドラのアドレスつまりOSから呼び出されるコールバック関数のアドレスを表す。呼び出されるコールバック関数のプロトタイプは次のようになる。

第1引数はEXCEPTION_RECORD構造体のアドレスである。EXCEPTION_RECORD構造体は例外の種類を表すコードや例外発生時のアドレスなどの発生した例外に関する様々な情報が格納されているので、例外ハンドラはこれらの情報を見て例外を処理するかを決定する。Delphiのtry-except文では例外オブジェクトのクラスとexceptのon句に指定したクラス(on E: ExceptionClass doのExceptionClassの部分)を比較して判定している。

呼び出された例外ハンドラで例外を処理せず、OSに次の例外ハンドラを呼び出させる場合はExceptionSearchException(1)を返して例外ハンドラからOSにリターンする。

また、例外が発生したアドレスから例外の原因を修正(0による除算が発生した場合、除数を0以外の値に修正)するなどして、再実行する場合はExceptionContinueExecute(0)を返してOSにリターンする。Delphiではこれは行われない。

例外ハンドラで例外を処理する場合は、例外ハンドラからOSにリターンせず、通常は、Win32APIのRtlUnwindなどでアンワインド(巻き戻し)したり、スタックフレームを適切に設定し直して、処理を続行する。Delphiではexceptのon句の後に指定した処理から実行が再開される。

RtlUnwindによるアンワインドはMSDNのドキュメントみても何の事だかさっぱり理解できないと思うが、簡単に言うと、例外ハンドラのリンクリストから不必要になった例外ハンドラのノードを削除することである。また、このとき、例外ハンドラの2回目の呼び出しが行われる。この2回目の呼び出しのときに、通常は終了処理を行う。Delphiではtry-finally文のfinally句の後に指定した終了処理がこの2回目の呼び出しのときに、実行される。

とまぁ、おおざっぱに書くとこんなところであろうが、詳細は

すべて、英語で書かれているが、特に一番上の記事にはSEHについて分かりやすく、詳細に書かれているのでお勧めです。自分は頑張って読みましたdash

| | コメント (0) | トラックバック (0)

ODBCヘッダー

また、さぼってたwobbly。なんというかプログラミングに対する情熱?自体が最近なくなってきたwobbly。要するに作りたいものがなくなってきた。

そんな事はさておき、今回は以前やりかけの事がたくさんあると書いたが、その内のとりあえず公開できそうな物を公開してみる。ということで、今回はODBC(Open DataBase Connectivity)のDelphi用ヘッダーファイル。Cのヘッダーファイルを以前に頑張って移植したbearing

まぁ、今更ODBC?みたいな感があるが・・・Unicodeに対応するDelphi2009以前のDelphi用ですのであしからず。また、ODBCの関数を使う時は現状、LoadLibraryで自分でDLLをロードして下さいsad。ダウンロードはSkyDriveから。

本当はODBCのコンポーネントを作っていたのだが、まだ、公開できる状態ではないので、ヘッダーファイルだけでも・・・

ところでDelphiの最新版であるDelphi 2010が発売されたのであるが、Unicodeアプリを作れる環境がほしいので久しぶりに購入しようかなと思ってたり。でも、作りたいものがなくなってきたこともあり悩んでます。

| | コメント (0) | トラックバック (0)

Win32デバッグ(4)・・・デバッグ情報(Borland/CodeGear編)

今回は具体的にデバッグ情報の中身を実際に覗いてみるgood。覗いてみると宣言するように、デバッグ情報の構造やファイルフォーマットなどの詳細まで書くと確実に倒れるbearingので、あくまでイメージを掴むために覗くだけにする。ははは・・。というより、基本的にファイルフォーマットはコンパイラなどの開発環境のメーカーによってその製品のバージョンアップ時に改訂されることがあるので、デバッグ情報にアクセスするためのインターフェースだけを公開し、フォーマットの詳細は公開しないのが現在の流れであるようなので、フォーマットの詳細は知りませんbearing

また、Microsoftの開発ツールによって生成されるデバッグ情報はMicrosoftのよって公開されているDbgHelpライブラリもしくはDIA(Debug Interface Access) SDKを使ってアクセスできるので、詳細はMSDNを参照ということで。DbgHelpライブラリはOSインストール時にインストールされるが、DIA SDKはデフォルトでインストールされていないので、使用する場合には、Visual Studioの統合開発環境をインストールするか、もしくは、再配布可能パッケージvcredist_x86.exeをインストールし、COMサーバーなのでmsdiaXX.dll(XXは80とか90)をregsvr32.exeなどでシステムに登録する必要がある。

ここではタイトルにもあるように自分が使っているBorland/CodeGearの開発ツールiよって生成されるデバッグ情報の中身を覗いてみるのであるが、DelphiのIDEに統合されている統合デバッガが利用しているデバッグ情報にアクセスするためのAPIが公開されていないのである。ガーンcoldsweats02

ということ終了・・って事になるなら、普通は初めからこんなタイトルの記事を書かない事からも分かるように、ここでは、TD32デバッグ情報と呼ばれるデバッグ情報を生成でき、また、アクセスするためのAPIも公開されているのでその中身を見てみる。歴史は詳しくわからないが、TD32デバッグ情報はTurbo Debuggerと呼ばれる社名もまだBorlandの頃の古いデバッガで利用していたデバッグ情報の事だと思う・・・

まず、デフォルトではTD32デバッグ情報は生成されないので、TD32デバッグ情報の生成の仕方から。

適当なプロジェクトを作成して、「プロジェクト」メニューから「オプション」メニューを選択、そして、「リンカ」設定を選択、

Win32debug_4_1_3

上の画像のように「EXEとDLLのオプション」の「TD32デバッグ情報を含める」にチェック。

Win32debug_4_2_2

以上であるが、「コンパイラ」設定の「デバッグ情報」「ローカルシンボル」あたりのオプションも生成されるTD32デバッグ情報の詳細レベルの影響するので・・

で、プロジェクトをコンパイルする。これによってTD32デバッグ情報を含んだ実行可能ファイルが生成される。

TD32デバッグ情報にアクセスするためのAPI(BorDebug.DLLとそのCヘッダ)はCodeGearの以下のサイトからダウンロードできる。

また、BorDebug.DLLの説明とDelphi用のヘッダファイルは以下のサイトからダウンロードできる。

英語の文書を読むのが苦にならない人は上記のサイトからダウンロードできるPDFファイルとCヘッダのコメント読んだ方が手っ取り早いので・・bearing

でいよいよ、実際にTD32デバッグ情報を覗いてみるのだが、そのためのツールTD32Scannerを作ってみた(製品に付属のTDUMP.EXEを使えば、中身をダンプできるのであるが、後でBorDebug.DLLを使ってプログラムからアクセスしたいので、その使い方を覚えるために作ってみたbearing)。実行画面は次の通り。

Win32debug_4_3_2

左側の「SubSection Headers」ウィンドウにはTD32デバッグ情報に含まれるサブセクションのヘッダ一覧が表示されている。最初にデバッグ情報の構造やファイルフォーマットまで詳細に書かないと述べたが、話を進めるために最低限の事は書こうと思う。、まずは、サブセクションから。サブセクションとはTD32デバッグ情報に含まれる論理的な情報のグループのことで、サブセクションに含まれる情報によって、次のタイプに分けられる。

  • Module
  • SrcModule
  • AlignSym
  • GlobalSym
  • GlobalPub
  • Names
  • GlobalTypes
  • Browse

順にこれらのうちのいくつかを見ていくが、その前に今回は、次の簡単なコンソールプログラムのTD32デバッグ情報を例にとり話を進めるので・・

1×1から9×9まで計算するプログラムで、Multiplyは2つの値を積を返す関数である。

まずは、Moduleサブセクション。Moduleサブセクションはモジュールに関する情報が格納されているサブセクションである。Delphiでいうとモジュールは.DCU(Delphi Compiled Unit)ファイルに相当する。上の画像より今回の例ではModuleIndexが1から7までの7つのModuleサブセクションがあることがわかる。「SubSection Headers」ウィンドウのグリッドからModuleIndexが7のModuleサブセクションをダブルクリックしてその詳細を見てみると、次のようになる。

Win32debug_4_5_2

上の画像のNameIndexより、モジュールTD32Target.DCUに関するModuleサブセクションであることが分かる。

また、Segmentsグリッドにはそのモジュールに含まれるコードが最終的な実行可能ファイルのどこに含まれているかの対応関係を表す情報が表示されている。

Windowsにおける実行可能ファイルのフォーマットであるPE(Portable Executale)フォーマットの場合、コンパイラによって生成される機械語命令は最終的にはセクションと呼ばれるブロックに格納されるが、先ほどのSegmentsグリッドのSegment列はそのセクションのインデックスを表す。

この事から、先ほどの画像より、例えば、TD32Target.DCUに含まれるコードがセクションインデックス1の(コード)セクションのオフセット0x00007070からオフセット0x00007213までの420バイトの領域に含まれている事が分かる(ややこしいが、TD32Scannerにおいて、Segmentと表記されているものは、PEファイルにおけるセクションのインデックスを表すので注意・・・・bearing)。

次に、ソースに関する情報が格納されているSrcModuleサブセクション。ここでは、TD32Target.DCUの元になるソースに関する情報を見るとして、「SubSection Headers」ウィンドウのModuleIndexが7のSrcModuleサブセクションをダブルクリックする。

Win32debug_4_6

SourceFilesグリッドより、TD32Target.DCUは1つのソースファイルTD32Target.dprから構成されていることが分かる。

次に、ローカルシンボルに関する情報が格納されているAlignSymサブセクション。SrcModuleの時と同じように、TD32Target.DCUに含まれるシンボルを見るとして、ModuleIndexが7のAlignSymサブセクションをダブルクリックする。

Win32debug_4_8

Symbolsグリッドには含まれるすべてのシンボルが列挙されている。SymbolKindはシンボルの種類を表すがその一覧はここでは省略するとして、ここではローカルプロシージャを表すシンボルであるLPROC32シンボルの詳細を見てみる。例えば、上の画像において、上から2つめのLPROCシンボルを選択してみる。

Win32debug_4_9_2

NameIndexより自分で定義したMultiply関数のシンボルである事が分かる。また、Segment、Offset、Lengthより、この関数の機械語命令のコードがセクションインデックス1のセクションのオフセット0x00007070から0x0000708Bまでの28バイトの領域に含まれていることが分かる。

とまぁ、残りは省略するが、デバッグ情報の内容とはこんな感じなのである。

作ったTD32ScannerはSkyDriveにアップロードしておいた。現在、GlobalTypesサブセクションにはしっかり対応していないのでsad。型に関する情報多すぎbearing

ふぅ。

| | コメント (0) | トラックバック (0)

コンテナ

前回の投稿から随分と日がたってしまったが・・・今回はDelphiネタ。

Delphiでプログラミングをしているとデータやオブジェクトのコレクションを扱う時に希望のコンテナクラスが標準で用意されていなくて、自分で実装するか公開されている既存のクラスをどっかのサイトから落としてくるはめになるのだが、今回は自分で実装してみた。公開されている既存のクラスを利用するのもいいのだが、GPLだのMPLだのライセンス絡みで面倒なので、正直そのようなものは使いたくないので・・・wobbly。ちなみに、DelphiではDelphi6当たり?のContnrs.pasで、色々なコンテナクラスが追加されているので、それらを利用すればだいたい事が足りるのであるが・・

今回必要になったのは、順序付き連想コンテナ順序無し連想コンテナはキーと値のペアのデータ構造で表現され、キーを使って検索できるが、順序付き連想コンテナとは、これに、更に、単純なリストのようにインデックスを使って先頭からシーケンシャルにアクセスできるようにしたもの。つまり、順序無し連想コンテナではデータの登録順序などは意味を持たないが、データの登録順序などの順序を同時に表現したものとでも言えばいいのか?。うーんwobbly。.NETでいうとOrderedDictionaryクラスかな?。

とういうことでDelphiでこのクラスを実装してみた。Delphiでは、前述のContnrs.pasに順序無し連想コンテナとして、TBucketListクラスが用意され、内部ではキーのハッシュを使いデータを登録するバケットを分散させているのでので、キーを使って高速に検索できるだろう?とのことで、これを利用して、TOrderedBucketList、TObjectOrderedBucketList,TIntegerOrderedBucketListを作ってみた。

キーと値のペアでデータを登録するAddメソッド、指定の位置(インデックス)に挿入するInsertメソッド、指定のキーの値を取得するDataプロパティ(デフォルト)、指定の位置(インデックス)のキーや値を取得するDataAt、ItemAtプロパティ(TBucketListクラスでキーの事をItem、値をDataと呼んでいるのでここでもそれを踏襲しました)があります。命名規則やクラス設計のスタイルはなるべくDelphiに合わせてあるであしからず。SkyDriveからもContnrsEx.pasでダウンロードできます。先ほども書いたようにライセンスとか面倒な事は言いません。ご自身の責任でご自由に。300行そこらのコードですがbearing

| | コメント (0) | トラックバック (0)

テスト投稿

テスト投稿です。

自分はココログベーシックを使ってるのだが、遂にdp.SyntaxHighlighterを使ってハイライト表示ができるようになったhappy01

| | コメント (0) | トラックバック (0)