Windows PE ガイド (基本部分) (1)

ソフトウェア登録コードを探す

こんにちは!


64 ビットのアセンブリとコンパイルのリンク

//Hello.c
#include <stdio.h>
int add(int, int);
int main()
{
    
    
	printf("Hello World %d\n", add(1, 2));
	getchar();
	return 0;
}

//Hello1.c
int add(int a, int b)
{
    
    
	return a + b;
}

vs2019コマンドラインツールを開きますx64 Native Tools Command Prompt for VS 2019

cl /c hello.c
cl /c hello1.c
link hello.obj hello1.obj
  • vs のプロジェクト プロパティを右クリックし、[ビルドの依存関係]、[ビルドのカスタマイズ] をクリックします。
  • masm(.target, .props) を選択します
  • ソースファイル1.asmを追加
//1.asm

; Sample x64 Assembly Program
; Chris Lomont 2009 www.lomont.org

extrn ExitProcess: PROC 	; external functions in system libraries
extrn MessageBoxA: PROC

;64位没有 .model 宏指令,不能指定内存模型和调用约定

.data
	caption db '64-bit hello!', 0
	message db 'Hello World!', 0
.code
	Start PROC
		;函数调用前需要预留影子空间,对其rsp
		sub rsp, 18h	; shadow space, aligns stack	没有这行代码,程序运行就会崩溃,抛出异常:0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF
		mov rcx, 0	; hwnd = HWND_DESKTOP
		lea rdx, message	; LPCSTR lpText
		lea r8, caption	; LPCSTR lpCaption
		mov r9d, 0	; uType = MB_OK
		;函数调用使用fastcall 
		call MessageBoxA	; call MessageBox API function
		mov ecx, eax	; uExitCode = MessageBox(...)
		call ExitProcess	; 执行了这行代码,整个程序就会结束,后面的其实随便填写代码,都不会执行
		add rsp, 28h	; 每个call压入8个字节的返回地址
	Start ENDP	;以后直接end,不用指明符号
End

(コンパイル中に応答がない場合は、1.asm ファイルを右クリックし、プロパティ、一般を構成し、生成から除外をいいえに設定します。アイテム タイプを
カスタム生成ツールとして選択し、順番に [適用] をクリックします
。追加の生成ツールをカスタマイズし、[OK] をクリックします
。1.asm を再度右クリックし、プロパティを設定し、一般的な設定を行います。以下に示すように Microsoft マクロ アセンブラが表示され、[生成から除外] を [いいえ] に設定します)

ここに画像の説明を挿入します

F7 コンパイル、コンパイル エラー メッセージ: LINK: エラー LNK2001: 未解決の外部シンボル mainCRTStartup
ソリューション: プロジェクト プロパティ、リンカー、詳細設定、エントリ ポイントに Start を入力するだけです。

コンパイル中にエラーが表示された場合:
asm エラー A2008: 構文エラー : 命令
エラー A2008: 構文エラー : endp
解決策:
アセンブリ コードを注意深く確認し、カンマとセミコロンが英語で入力されていることを確認し、各改行を削除して再試行してください。もう一度 Enter キーを押します。
エンコードの問題を回避するには、アセンブリ コード全体をメモ帳にコピーし、コピーして貼り付けて戻します。または、それを Notepad++ にコピーしてエンコードを ANSI に変更します。

サブ RSP、18 時間。シャドウスペース、スタックの位置合わせ

次に、上記のアセンブリ コードの機能を見てみましょう。
このコード行がないと、プログラムはクラッシュし、例外 0xC0000005: アクセス違反読み取り位置 0xFFFFFFFFFFFFFF がスローされます。

説明:
関数の先頭のスタックは 16 ビットにアライメントされている必要があります。64 ビット アセンブリで記述された関数は、実際には call 命令を通じてオペレーティング システムによって呼び出されます。関数の戻りアドレスはスタックにプッシュされます。このとき、少なくともこのアドレスを指定する必要があります。8 バイトを埋めてください。つまり、少なくともこのコード行は です。sub, rsp, 8hこの時点では、コンパイルおよび実行に問題はありませんが、一般的には、いくつかのアドレスを指定します。shadow space一部を変更しないように、この場所の と呼ばれるバイト数を増やします。32 ビットでの API 関数に対する「怠惰な」アプローチにより、rcx、rdx、r8、および r9 によって渡されるパラメーターを 64 ビットでのスタックに配置する必要がありました-bit、つまりレジスタパラメータを 32 ビットスタックパラメータに変換するため、安定性を確保するために、64 ビットアセンブラを作成するときは最初にシャドウスペースを割り当てる必要があります。

ml64 /c 1.asm
link 1.obj

接続時の天気错:エラー LNK2019: 関数メインで参照された未解決の外部シンボル ExitProcess
エラー LNK2019: 関数メインで参照された未解決の外部シンボル MessageBoxA
エラー LNK2001: 未解決の外部シンボル mainCRTStartup

解決策: コマンドラインに kernel32.lib と user32.lib を含めることができます。

link 1.obj /defaultlib:user32.lib /defaultlib:kernel32.lib /entry:Start

PE および COFF ファイルの概要

PE および COFF ファイル レイアウトの概要

ソースコードの役割

シンボルが見つからない一般的なリンク エラーは、リンカが関数名に対応する実行可能コードの場所を見つけられないことです。コンパイラとリンカにはソース コード内で十分な情報が与えられていないため、ソース コード内に場所を記述してください。関数名が配置されている lib ファイルの。つまり、この静的ライブラリには、この関数のすべての情報とこの関数の実行可能コードが含まれています。このライブラリが dll に対応するシンボルをインポートする単なるライブラリである場合、このライブラリはには、この関数の実行可能コードがどこにあるかに関する情報があります。このライブラリをソース コードに組み込むと、コンパイラがこの関数名を認識すると、このライブラリを通じてこの関数がどこにあるかがわかります。将来、どの場所にあるかがわかります。この情報は .obj と .exe にマージされるため、コンパイラはエラーを報告しません。

オブジェクトの役割

複数の obj が 1 つの exe にマージされるため、obj 内の情報は、コンパイラが exe ファイルを合成するために使用する何らかの情報をコンパイラに提供する必要があります。obj 内のすべての情報は、obj をマージするためのものです (または、リンクはこの目的を果たすための exe になります。

PEの役割

ほとんどの exe は実行用のプロセスを生成するために使用されるため、PE 形式のすべての情報が将这个exe装到内存里面然后执行この目的に役立ちます。
このexeがどのようにメモリにインストールされ、どのように実行されるのかを考えてみましょう。

COFF 形式ファイルのレイアウトの概要

ここに画像の説明を挿入します

COFF 形式は上記の 4 つの部分から構成されており、最初の部分の _IMAGE_FILE_HEADER は PE 形式の標準の PE ヘッダと同じであり、2 番目の部分のセクション ヘッダ(セクション テーブル
)は可変長配列に相当し、各要素の固定サイズ 各セクションを説明する情報 (.text、.data など)。

ここに画像の説明を挿入します

セクション ヘッダー (セクション テーブル) の後にはシンボル Symbol と文字列 String が続き、シンボル Symbol の後には文字列 String が続きます。

PE 形式ファイルのレイアウトの概要

ここに画像の説明を挿入します

_IMAGE_DOS_HEADER は、DOS システムとの互換性を保つために使用されます。DOS システムでの実行可能ファイルもこの構造を持っています。DOS システムでの実行可能ファイルでは、この構造の後に実行可能コードが続きます。したがって、Windows システムで PE 実行可能ファイルが実行されるとき、 DOS システムでは、プログラム (LINK 中に付加されるデフォルト コード) も実行され、ユーザーにいくつかのエラー メッセージが表示されます。

ここに画像の説明を挿入します

_IMAGE_OPTIONAL_HEADER は、COFF 形式 (または .obj ファイル) では使用できません。

ここに画像の説明を挿入します

_IMAGE_SECTION_HEADER は、後続の各 Section_Data に加え、シンボルと文字列 (存在する場合) を含め、COFF 形式とまったく同じです。

PE 形式ファイルには、COFF 形式よりも DOS ヘッダー (_IMAGE_DOS_HEADER) とオプション ヘッダー (_IMAGE_OPTIONAL_HEADER) が 1 つ多くあり、その他はすべて同じです。

ここに画像の説明を挿入します

_IMAGE_OPTIONAL_HEADER の情報は、COFF 形式 (または .obj ファイル) とは何の関係もありません。.obj は、ImageBase などの情報を提供する必要はありません。この情報は、LINK が EXE 実行可能プログラムを生成するときに自動的に生成できます。これらの情報はすべて、この情報は、EXE ファイルがメモリにロードされて実行される方法に関するものです。

COFF形式とPE形式のレイアウト概要

構造 COFF形式 PEフォーマット
_IMAGE_DOS_HEADER いいえ 持っている
_IMAGE_FILE_HEADER 持っている 持っている
_IMAGE_OPTIONAL_HEADER いいえ 持っている
_IMAGE_SECTION_HEADER 持っている 持っている
セクション_データ 持っている 持っている
シンボル オプション オプション
オプション オプション

COFFヘッダー

COFF ファイルヘッダー

リンク: COFF ファイルヘッダー (オブジェクトと画像)

  • マシン タイプは、
    PE ファイルの実行プラットフォームを示します。
    IMAGE_FILE_MACHINE_AMD64 : 0x8664 : x64
    IMAGE_FILE_MACHINE_I386 : 0x14c : Intel 386 以降のプロセッサおよび互換プロセッサ
    IMAGE_FILE_MACHINE_IA64 : 0x200 : Intel Itanium プロセッサ ファミリー
  • 特性は
    ファイルの属性を表します。
    IMAGE_FILE_LARGE_ADDRESS_ AWARE : 0x0020 : アプリケーションは 2 GB を超えるアドレスを処理できます。
    アプリケーションは 2GB を超えるアドレス空間を処理できます。この機能は 32 ビット アプリケーションでのみ役立ち、64 ビット アプリケーションでは役に立ちません。
    IMAGE_FILE_SYSTEM: 0x1000: イメージ ファイルはシステム ファイルであり、ユーザー プログラムではありません ドライバー
    ファイルはシステム ファイルであり、アプリケーション プログラムに属さないため、この特性を持ちます。

セクションヘッダー

链接:Section Table (Section Headers)

ここに画像の説明を挿入します

_IMAGE_SECTION_HEADER

  • 名前
    8 バイトの null 埋め込み UTF-8 エンコード文字列。文字列の長さがちょうど 8 文字の場合、終端の null はありません。長い名前の場合、このフィールドにはスラッシュ (/) が含まれ、その後に次の ASCII 表現が続きます。文字列テーブルへのオフセットである 10 進数。実行可能イメージは文字列テーブルを使用せず、8 文字を超えるセクション名をサポートしません。オブジェクト ファイル内の長い名前は、実行可能ファイルに出力される場合に切り詰められます。PE 形式は次のとおりです。
    8 バイトを超えるセクション名はサポートされません。実行可能イメージは文字列テーブルを使用せず、8 文字を超えるセクション名をサポートしません。
    COFF 形式では、8 バイトを超える長い名前がサポートされます。このフィールドには、スラッシュ (/) の後に、文字列テーブルへのオフセットである 10 進数の ASCII 表現が含まれます。
    .obj ファイルのセクション名が長い場合、.exe ファイルにマージされるときに切り詰められます。
    名前は 8 バイトのセクション名、UTF-8 文字です。セクション名はゼロで終わる必要はありません。たとえば、セクション名は正確に 8 バイトにすることができます。

  • VirtualSize
    VirtualSize は、メモリにインストールされているこのセクションのサイズです (セクションのサイズ、ファイル アライメントなしの実際のサイズを示します);
    .obj ファイルをメモリ内にロードできないため、COFF では VirtualSize と VirtualAddress は両方とも 0 です、リンカによって PE 形式ファイルにのみリンクできます。

  • SizeOfRawData
    ディスク ファイル内のこのセクションの元のサイズ。
    セクションのサイズを示します (ファイル調整後のサイズに基づきます)。このフィールドの値は、VirtualSize フィールドの値が IMAGE_OPTIONAL_HEADER32.FileAlignment フィールドの値に従って調整された後のサイズと等しくなります。


  • .obj ファイル内のPointerToRawDataセクションのオフセット位置 (セクション領域のファイル オフセット アドレス FOA)。

  • PointerToRelocations
    は再配置シンボルに関連しています。ポインタは配列を指します。基本的に、配列内の各要素はシンボル、つまり .obj ファイルの対応するセクション内のシンボルの再配置情報 (オフセット) に対応します。
    .obj ファイルで使用され、再配置テーブルへのポインター。

  • NumberOfRelocationsは
    、再配置情報の数(再配置エントリの数)である。
    .obj ファイルで、再配置テーブル内の要素の数を示すために使用されます。

  • このセクションの特性
    属性 (IMAGE_SCN_MEM_EXECUTE、IMAGE_SCN_MEM_READ、IMAGE_SCN_MEM_WRITE、IMAGE_SCN_MEM_SHARED、IMAGE_SCN_MEM_NOT_PAGED)。

ここに画像の説明を挿入します
ここに画像の説明を挿入します

ここに画像の説明を挿入します

ここに画像の説明を挿入します

リンク: セクションフラグ

セクション フラグには、IMAGE_SCN_TYPE_NO_PAD (廃止され、IMAGE_SCN_ALIGN_1BYTE フラグに置き換えられました)、IMAGE_SCN_CNT_CODE (このセクションには実行可能コードが含まれます)、IMAGE_SCN_CNT_INITIALIZED_DATA (このセクションには初期化されたデータが含まれます)、IMAGE_SCN_CNT_UNINITIALIZED_DATA (このセクションには初期化されていないデータが含まれます)、IMAGE が含まれます。 _S CN_LNK_INFO(.drectve セクションにはこのタイプがあります) 、IMAGE_SCN_MEM_NOT_CACHED (このセクションはキャッシュに配置できません)、IMAGE_SCN_MEM_NOT_PAGED (このセクションはページング ファイルに配置できません。このページングまたは非ページングは​​セクション全体を対象としています。このセクションにこのフラグがある場合、このセクションのすべてのコードがセクションは非ページ メモリに配置できます)、IMAGE_SCN_MEM_SHARED (このセクションはメモリ内の複数のプロセスで共有できます。共有メモリはセクションを通じてメモリを共有します)、IMAGE_SCN_MEM_EXECUTE (このセクションはコードを実行できるセクションです。セクションには実行可能な属性が含まれます)、IMAGE_SCN_MEM_READ (このセクションは読み取り可能、セクションには読み取り可能な属性が含まれます)、IMAGE_SCN_MEM_WRITE (このセクションは書き込み可能、​​セクションには書き込み可能な属性が含まれます)。

IMAGE_SCN_GPREL フラグ (セクションにはグローバル ポインターを介して参照されるデータが含まれます) などの特定のフラグを持つセクションを検索する場合は、次のようにコードを変更できます。

ここに画像の説明を挿入します


シンボルテーブル 1

链接:ファイルのその他の内容

ここに画像の説明を挿入します

ここに画像の説明を挿入します

メモリ内のシンボル テーブルの位置 (PointerToSymbolTable + p) とシンボル テーブル内の要素の数 (NumberOfSymbols) がわかっているので、シンボル テーブルの直後に文字列テーブルを取得できます。
注: シンボル テーブルの各要素は必ずしもシンボルを表すわけではありません (1 対 1 の関係ではありません)。複数の要素が 1 つのシンボルを表す場合もあります。

リンク: シンボルテーブルの形式

ここに画像の説明を挿入します


上の図は、シンボル テーブル内のシンボルの標準形式です。シンボル テーブル内の要素がこの形式を持つ場合、それはシンボルを表します。いくつかの補助シンボル要素の形式もあります。
リンク: 補助記号レコード
それらの違いは何ですか? 標準フォーマットのサイズは 18 バイトで、補助要素も 18 バイトですが、そのメンバー変数が変更されています。つまり、そのデータの意味が変更されています。

標準シンボル形式

シンボル テーブルはレコードの配列であり、各レコードの長さは 18 バイトです。各レコードは、標準または補助シンボル テーブル レコードです。標準レコードでは、シンボルまたは名前を次の形式で定義します。

  • Name は、グローバル変数、静的変数、関数名などのこのシンボルの名前です。
    シンボル テーブルの ShortName フィールドには、名前自体を含む 8 バイトが含まれます (長さが 8 バイトを超えない場合)。 ShortName フィールドは文字列テーブルによってオフセットが提供されます。指定された名前自体が指定されているか、オフセットが指定されているかを判断するには、最初の 4 バイトがゼロに等しいかどうかをテストします。
    慣例により、これらの名前はゼロで終わる UTF-8 エンコード文字列として扱われます。

ここに画像の説明を挿入します

ShortName: 8 バイトの配列。名前の長さが 8 バイト未満の場合、配列の右側は null 値で埋められます。
Zeores: 名前が 8 バイトより長い場合、このフィールドはすべてゼロに設定されます。
オフセット: 文字列テーブル内のオフセット。

  • 値はシンボルのタイプに応じて異なる意味を持ち、通常はオフセットです。

  • SectionNumberを強調する必要があります。
    構造体 _IMAGE_FILE_HEADER の PointerToSymbolTable メンバーは、配列と見なすことができるシンボル テーブルを指しており、インデックス (数値) は 0 から始まり、構造体 _IMAGE_FILE_HEADER の NumberOfSymbols メンバーは
    、シンボル テーブル内のシンボルの数。
    シンボル テーブルの Name がグローバル変数を表す場合、たとえば、その名前が a を表し、その値 Value が通常はオフセットである場合、SectionNumber はグローバル変数が配置されているセクションの番号を指し、SectionNumber は 1 に基づきます。開始番号、たとえば、SectionNumber の値は 5 です。セクション番号は 0 から始まるため、これは 4 番目のセクションにありますが、この場所の番号は 1 から始まるため、SectionNumber の値は 5 になります。 1 つのインデックスに基づく 1 ベースのインデックス)。
    SectionNumber は、セクション テーブルを「識別」するために 1 から始まるインデックスを使用してセクションを識別する符号付き整数です (セクション テーブル IMAGE_SECTION_TABLE は配列であり、SectionNumber-1 はセクション テーブル配列のインデックスです。セクションのインデックスはtable 配列は 0 から始まるため、SectionNumber から 1 を減算する必要があります)。
    SectionNumber は符号付きの数値で、0 または負の値を指定できます。値が 0 の場合、シンボルがこのセクションにないことを意味します (シンボル レコードにセクションが割り当てられていません)。値 0 は、シンボルが存在することを意味します。他の場所で定義された外部シンボルへの参照。 、このシンボルが外部参照変数であることを示します。 Value の 0 以外の値は、それがユニバーサル シンボルであることを示し、そのサイズは Value によって指定されます。)、および値メンバーも 0 で、外部で定義されたシンボルを参照していることを示します。たとえば、int yyy = 222;このグローバル変数を Source.c で定義した場合、それを別のソース ファイル 1.c でどのように使用すればよいでしょうか? ただそうする必要がありますextern int yyy;この変数は、この方法で参照することで使用できます。これは外部変数です。SectionNumber
    の値が 0 に等しく、Value メンバーの値が 0 に等しくない場合、それはユニバーサル シンボルであることを意味します。この値は、シンボルが占有するメモリ サイズを表します。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

この新しく追加された 1.c ファイルと Source.c ファイルが結合されて、実行可能プログラムが形成されます。Source.c では、主に COFF ファイル 1.obj に注目します。コードでは、すべてのファイルを 1.c に置きます。 obj ファイル。セクション名がすべて出力されます。

ここに画像の説明を挿入します

次に、すべてのシンボル名 (標準シンボル要素と補助シンボル要素)、補助シンボル要素の数 (シンボル テーブルはレコード配列であるため、各レコードの長さは 18 バイトです。各レコードは標準シンボル テーブル レコードまたは補助シンボル テーブル レコードです) を入力します。したがって、この NumberOfAuxSymbols はシンボル テーブル配列全体の要素の数でもあり、それらはすべて出力されます。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

ここに画像の説明を挿入します

1.c で参照した外部変数 yyy がシンボルテーブルに存在することがわかります。これは 62 番目のシンボル (シンボルテーブルのインデックス) であり、下図に示すように、SectionNumber は 0、Value も 0 です。このシンボルは外部参照変数です。

ここに画像の説明を挿入します

  • Type は、変数がどのようなタイプであるかを示します。Type は 2 バイトを占め、2 つの部分で構成されます。MSB の最上位ビットは、シンボルが複合型 (ポインター、関数、配列) であるかどうかを示します (具体的には、その意味以下の基本型の説明にも依存します)、またはそれが複合型ではないのか none:

ここに画像の説明を挿入します

ここに画像の説明を挿入します

LSB の最下位ビット部分は、IMAGE_SYM_TYPE_CHAR、IMAGE_SYM_TYPE_INT、IMAGE_SYM_TYPE_DOUBLE、IMAGE_SYM_TYPE_BYTE、IMAGE_SYM_TYPE_DWORD などの変数の基本型を参照します。これら 2 つの部分を組み合わせると、変数の型を明確にすることができます

LSB と MSB は下位バイトと上位バイトを指すのではなく、有効ビットを指すことに注意してください。たとえば、Type=0x0020 の場合、その LSB は 0、MSB は 2 です (1 つの 16 進数が 4 ビットを占有し、2 の 4 乗 = 16 は基本タイプの数とまったく同じであるため、1 つの 16 進数が表記されます) Microsoft ツールは通常このフィールド
を使用せず、LSB を 0 に設定しますが、基本型には次の値が定義されています。代わりに、Visual C++ デバッグ情報を使用して型を示します。

  • .obj または .exe にある 2 つの一般的なストレージ クラスの種類は、IMAGE_SYM_CLASS_EXTERNAL と IMAGE_SYM_CLASS_STATIC です。他の種類は通常は表示されません。たとえば、上記のコード (62 番目の記号 yyy) では、関数名または変数の場合、(StorageClass= 2 . IMAGE_SYM_CLASS_EXTERNAL: Microsoft ツールで外部シンボルに使用される値。StorageClass が IMAGE_SYM_CLASS_EXTERNAL の場合、セクション番号 SectionNumber が IMAGE_SYM_UNDEFINED (0) の場合、セクションがシンボル レコードに割り当てられていないことを意味し、値はセクションを表します。値のサイズ。このとき、シンボルにセクションが割り当てられていないため、Value の値も 0 になります。これは、シンボルが外部参照変数であることを示します。セクション番号 SectionNumber が 0 でない場合、Value フィールドは次のようになります
    。変数がセクション内にあることを示すために使用されます。このセクションの先頭からの相対的なオフセット)
    ストレージ クラスの値が 3 (つまり IMAGE_SYM_CLASS_STATIC) の場合、Value の値はセクション内のオフセットです。Value の値が 3 の場合、Value の値はセクション内のオフセットです。が0の場合、この記号はセクション名を意味します。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

上の図でわかるように、.debug は 2 つの補助シンボル要素 (i=6 と i=7) を持つセクションであり、観察すると、セクション名の後に 2 つの補助要素が続くことがわかります。
この NumberOfAuxSymbols は、この標準要素の後に複数の補助シンボル テーブル エントリが続くことを意味します。場合によっては、1 つの標準要素ではシンボル情報を説明するのに十分ではないため、その後に 1 つまたは複数の補助シンボル要素 (補助テーブル エントリ) が続きます。シンボル レコード) 、補助シンボル要素はシンボル情報を追加します。
最後の補助シンボル要素は終了標識として 0 に設定されるため、補助シンボル要素の数は 2 未満にはなりません。

コードを変更し、ストレージ クラスを出力して確認してみましょう。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

ストレージ クラスは IMAGE_SYM_CLASS_LABEL で、ソース コードがコンパイラによってアセンブリ コードに展開されるときに自動的に生成されるラベルを参照します。

IMAGE_SYM_CLASS_WEAK_EXTERNAL は、弱参照シンボルを指します。弱参照とは何ですか? たとえば、次の図で選択されたコードには値の割り当てがなく、弱参照です。

ここに画像の説明を挿入します

代入と同様にint yyy=200;、これは強参照です。pFile_Head
変数と yyy 変数は 1.c ソース ファイルで再度定義されます。

ここに画像の説明を挿入します

リンク時にエラーが報告され、yyy 変数が複数回定義されていることを示します。変数に値を代入することは強参照であり、値を代入しないことは弱参照です。上記のコードのように、両方のソース ファイルで定義されています
。同じタイプと名前の弱参照 pFile_Head の場合、コンパイルおよびリンク時に、異なるファイルで定義された同じ名前を持つこれら 2 つの弱参照変数はマージされるため、弱参照変数を複数回定義してもエラーは報告されません。
実際には、異なるソース ファイルで同じ名前の弱い変数を定義できます。また、同じ名前の弱い変数の種類も異なる場合があります。たとえば、1.c で変数を定義した場合、それを定義することもできます。 Source でint y6=0;1 つを c で定義しますchar y6;。名前は同じですが型が異なる 2 つの変数は、リンクしても競合せず、エラーも発生しません。このタイプを弱い変数と呼びます。

補助記号レコード

補助形式 5: セクション定義

まず、下図のセクション名に続く補助セクション定義を見てみましょう (補助シンボル レコードは標準シンボル レコードと同じ 18 バイトを占有しますが、そのメンバー変数が変更されています。つまり、そのデータの意味が変更されています)。 )。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

i = 8 kkkk 2 3 .data上の図の 8 番目のシンボル .data( ) を見てみましょう (2 はこのシンボルが持つ補助シンボルの数 NumberOfAuxSymbols を指し、3 は StorageClass=IMAGE_SYM_CLASS_STATIC を指します)、監視ウィンドウで pSymbol[8] の値を確認してみましょう

ここに画像の説明を挿入します

SectionNumber の値が 3 で、2 番目のセクション テーブル ( 2 = .data) を参照していることがわかります。
9 番目のシンボルは補助シンボル (補助セクション?) です。次のようにコードを変更し、補助シンボル pAux_Symbol を再割り当てし、9 番目の補助シンボルを取得して、補助シンボルの特定の値をデバッグして観察できるようにします。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

ここに画像の説明を挿入します

Length の値がセクション データのサイズを示す 4 であることがわかります。2 番目のセクション pSction_Head[2] のデータ サイズも 4 であるため、その値はセクション テーブルの SizeOfRawData と同じです。

Number と Selection の 2 つのメンバーは、COMDAT などの特別なセクションでのみ役立ちます。
Number は、関連するセクションのセクション テーブルへの 1 から始まるインデックスです。数値は、COMDAT 選択が 5 に設定されている場合に使用されます。
COMDAT 選択は数値です。この選択番号は、セクションが COMDAT セクションの場合に意味を持ちます。

文字列と再配置

COMDATフェスティバル

COMDAT のようなデータセクションがありますが、このデータセクションのデータは C 言語で定義されたグローバル変数ですが、このデータセクションの特徴は何でしょうか?

セクション定義補助形式の選択フィールドは、セクションが COMDAT セクションの場合に適用されます

COMDATセクションは複数のobjファイルで定義できるセクションです。(フラグ IMAGE_SCN_LNK_COMDAT は、セクション ヘッダーの Characteristics フィールドに設定されます。)
補助形式の Selection フィールドは、リンカが COMDAT セクションの複数の定義を解析する方法を決定します (たとえば、Selection 値は IMAGE_COMDAT_SELECT_ANY または IMAGE_COMDAT_SELECT_ASSOCIATIVE)。

-IMAGE_COMDAT_SELECT_ANY
同じ COMDAT シンボルを定義するセクションはリンクできますが、残りは削除されます。

  • IMAGE_COMDAT_SELECT_ASSOCIATIVE
    他の特定の COMDAT セクションがリンクされている場合、そのセクションはリンクされます。
    別のセクションは、セクション定義の補助シンボル レコードの番号フィールドによって示されます。この設定は、複数のセクションにコンポーネントがある定義 (たとえば、あるセクションにコードがあり、別のセクションにデータがある) に便利ですが、すべてのコンポーネントをセットとしてリンクする (つまり、1 つの exe ファイルにマージする) か、破棄する必要があります。このセクションに関連付けられている他のセクションは COMDAT セクションである必要があり、別の関連付けられた COMDAT セクションであってもかまいません。関連する COMDAT セクションのセクション関連付けチェーンはサイクルを形成できません。セクション関連付けチェーンは、最終的に、IMAGE_COMDAT_SELECT_ASSOCIATIVE セットを持たない COMDAT セクションに到達する必要があります。

COMDAT セクションのセクション値を持つ最初のシンボルは、セクション シンボルである必要があります

シンボル (標準シンボル形式 IMAGE_SYMBAL) には、セクションの名前 Name、ゼロに等しい Value フィールド、問題の COMDAT セクションのセクション番号 SectionNumber、および IMAGE_SYM_TYPE_NULL に等しい Type フィールド (型情報なし、または不明な基本型) が含まれています。 Microsoft ツールはこの設定を使用します)、IMAGE_SYM_CLASS_STATIC と等しい StorageClass フィールド、および補助レコードを使用します。
2 番目のシンボルは「COMDAT シンボル」と呼ばれ、リンカによって選択フィールドに基づいて解決されます (補助形式の選択フィールドによって、リンカが COMDAT セクションの複数の定義を解決する方法が決まります)。

つまり、同じ名前の 2 つの変数があります。たとえば、1.obj と 2.obj は、同じ名前の 2 つの割り当てられた変数を定義します。これを強参照と言います。LINKER がレポートするのは当然のことです。エラーですが、「これらの 2 つの変数が__declspec(selectany) int x5 =100; このように定義されている場合」と入力すると、それらは同じ名前を持つことができることを意味します (この時点では、x5 が配置されているセクションには COMDAT データがあります)。同じ名前の場合は、エラーを報告せずに選択するだけで済みます。
プログラムを実行して次のことを確認してみましょう。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

この x5 は 12 番目のシンボルであり、その SectionNumber は 3 であり、2 番目のセクションにあります。

ここに画像の説明を挿入します

2 番目の section.data の IMAGE_SECTION_HEADER.Characteristics 値には、IMAGE_SCN_LNK_COMDAT 属性である値 0x1000 があり、このセクションに COMDAT データが含まれていることを示していることがわかります。

ここに画像の説明を挿入します

各セクション名もシンボルであると言い、このセクション名symbol.dataを探します。

ここに画像の説明を挿入します

9 番目と 13 番目のシンボルが両方ともこの .data セクション名を持っていることがわかります (セクション テーブルには 2 つの .data セクションがあります)。2 つのセクション名は同じなので、x5 は 9 番目のものにある可能性があります。で表されるセクションセクション名シンボルは、13 番目のセクション名シンボルで表されるセクション内にも存在する場合があり、これら 2 つのセクション名の後に 2 つの補助セクションが続きます (最初の補助シンボルに注目してください。最後の補助シンボルはターミネータです)。

ここに画像の説明を挿入します

まず 9 番目のシンボルを見てみましょう。その SectionNumber は 3 で、これも 2 番目のセクションであり、依然として .data セクションです。このセクションの特性は IMAGE_SCN_LNK_COMDAT 属性であるため、10 番目の補助シンボルを確認できます。

ここに画像の説明を挿入します

この補助シンボルがセクション定義を説明するものである場合、補助シンボルの形式は、以下に示すこのタイプのレコードの形式になります。

ここに画像の説明を挿入します

このセクションが COMDAT セクションになると (2 番目のセクション.data の Characteristics 値の値が 0x1000 になります。これは IMAGE_SCN_LNK_COMDAT 属性であり、このセクションに COMDAT データが含まれていることを示します)、このセレクションは意味を持ちます。そうでない場合、セレクションは意味がありません。
このセレクションは、同じ名前を持つ 2 つの変数を見つけたときにそれらをマージする方法を表します。
先ほど、セレクションの値が 2 であることがわかりました。これは、いずれかのセクションに同じシンボルがある場合はリンクでき、シンボルは 1 つだけであることを意味します。リンクされた場合、残りは削除されます。
たとえば、Source.obj と 1.obj の両方に変数 x5 が含まれている場合、変数の 1 つだけが使用されるため、リンク時にエラーは報告されません。

ここに画像の説明を挿入します

この COMDAT セクションは COFF 形式ファイルにのみ表示できます。このセクションは IMAGE (つまり EXE) ファイルには存在しません。EXE ファイルはリンクされているため、このセクションは意味を持ちません。

通常の x6 変数をもう一度見てみましょう。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

これは 16 番目のシンボルであり、その SectionNumber は 4 番目であり、これは 3 番目のセクション.データ セクション (下図を参照) であり、その Characteristics に COMDAT データを表す値 0x1000 がないことがわかります。このセクションには通常のデータが含まれます。

ここに画像の説明を挿入します

セクション name.data の後の補助シンボル (14 番目のシンボル) を見てみましょう。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

ここに画像の説明を挿入します

この中には選択が存在せず (選択フィールドは無意味です)、この中に選択の記述がないことがわかります; したがって、
通常の変数が配置されているセクションには COMDAT ロゴがありません。 2 つの .obj ファイルがあります。2 つの変数が識別された場合、エラーが報告されます。

標準的なシンボル形式は次の形式です。

ここに画像の説明を挿入します

標準シンボルを説明する補助シンボルもあります (下の図は補助フォーマット 5: セクション定義を示しています):
このフォーマットは、定義セクションのシンボル テーブル レコードに従います。このようなレコードには、セクション名 (.text や .drectve など) であるシンボリック名があり、ストレージ クラス STATIC (3) があります。補助レコードは、それが参照するセクションに関する情報を提供します。したがって、セクション テーブルから一部の情報がコピーされます。

ここに画像の説明を挿入します

文字列は文字列の束であり、COFF 文字列テーブルは文字列の束です。文字列テーブルの最初の 4 バイトは、それが占めるスペース (すべての文字列が占めるスペース) を表します (このサイズにはサイズ フィールド自体が含まれるため、文字列が存在しない場合、この位置の値は 4) です。
文字列テーブルの最初の 4 バイトの後に、COFF シンボル テーブル内のシンボルが指すヌル終了文字列があります。

COFF文字列テーブル

COFF シンボル テーブルのすぐ後には、COFF 文字列テーブルがあります。テーブルの場所は、COFF ヘッダーのシンボル テーブル アドレスを取得し、シンボルの数とシンボルのサイズを乗算して加算することによって決定されます。標準シンボル要素と補助シンボル要素のサイズは両方とも 18 バイトであることがわかります。 :

ここに画像の説明を挿入します

上図の pSymbol ポインタ変数が指す IMAGE_SYMBOL 型は標準シンボルの形式であるため、C 言語のポインタの特性によれば、pSymbol+1 は 1 つのシンボル要素をスキップすることを意味し、2 を追加することは 2 つのシンボル要素をスキップすることを意味します。したがって、シンボル要素の数 pFile->NumberOfSections を追加すると、COFF シンボル テーブルの直後にある文字列テーブルの開始アドレスを見つけるのと同じになります。
この文字列テーブルの開始アドレスをメモリ ウィンドウに追加して観察します。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

最初の 4 バイトの値は 0x0000029d であり、その後は 0 で終わるすべての文字列 (上図の右列の文字列の後ろにある小さな黒い点) であることがわかります。すべての文字列が占めるバイト数は 0x29d です.。 (10 進数は 669)、文字列テーブルの開始アドレスに 29d の値を加えた 0x154F14F0DEC (上図の右列の MZ 位置を指すC_Shutdown.rtc$TMZ) を使用します。これが、obj ファイル全体の文字列になります。
では、文字列はいつ使用されるのでしょうか?
標準シンボルの形式をもう一度見てみましょう。

ここに画像の説明を挿入します

最初のメンバーは 8 バイトの名前ですが、プログラムの出力を見ると、一部のシンボル名は非常に長く、8 バイトに収まりきらないことがわかります。たとえば、下の図の 22 番目のシンボルを見てみましょう。 :

ここに画像の説明を挿入します

ここに画像の説明を挿入します

前にも述べたように、ロングネーム LongName の下位 4 バイトが 0 (pSymbol[i].N.Name.Short の値が 0) の場合、それは a であると言います。 4 バイトは 0x22 (pSymbol[i].N.Name.Long の値) で、これはその文字列テーブルの先頭からのアドレス オフセットが 0x22 バイトであり、これが探している名前であることを意味します。
シンボル名の表現

ここに画像の説明を挿入します

上記のコードに示されているように、Short が 0 に等しい場合はpString+pSymbol[i].N.Name.Long長い名前が出力され、Shot が 0 に等しくない場合は短い名前が出力されます。
これはシンボルの名前の表現です。

dumpbin を使用してシンボルの内容を出力する

ここに画像の説明を挿入します

Dumpbin は、obj 内のシンボルを抽出できるだけでなく、補助セクション (Length、NumberOfRelocations、NumberOfLinenumbers、CheckSum) の内容も抽出できます。

ここに画像の説明を挿入します

dumpbin で解析した結果は、作成したプログラムで解析した結果と同じになるはずなので、比較してみましょう。

リセット

COFF 再配置 (オブジェクトのみ)

ここに画像の説明を挿入します

上の図はセクション ヘッダー (セクション テーブル) のセクション名です。各セクションには再配置ポインター (PointerToRelocation) があります。この再配置ポインターは、このセクション内の一部のデータの再配置情報を指します。

ここに画像の説明を挿入します

では、いつ情報を再配置する必要があるのでしょうか?

ここに画像の説明を挿入します

Add 関数のローカル変数 (変数 c など) はわかっていますが、このローカル変数はプログラムの実行中に生成されるため、シンボルに含める必要はありません。

ここに画像の説明を挿入します

変数 yyy は 62 番目のシンボル (シンボル テーブルのインデックス) です。上の図に示すように、SectionNumber は 0 で、Value も 0 で、このシンボルが外部参照変数 (StorageClass=2、つまり IMAGE_SYM_CLASS_EXTERNAL:) であることを示します。外部用 Microsoft ツール シンボルの値。StorageClass が IMAGE_SYM_CLASS_EXTERNAL の場合、セクション番号 SectionNumber が IMAGE _ SYM _ UNDEFINED (0) の場合、セクションがシンボル レコードに割り当てられていないことを意味し、Value はセクションのサイズ。このとき、このシンボルにはセクションが割り当てられていないため、Value の値も 0 になります。これは、このシンボルが外部参照変数であることを示します。セクション番号 SectionNumber が 0 でない場合は、Value フィールドが使用されます
。 (値フィールド:シンボルに関連付けられた値
。このフィールドの解釈は、SectionNumber と StorageClass によって異なります。一般的な意味は、再配置可能なアドレスです) 、指定されたセクション内のオフセットなど)。
変数 yyy を再配置する必要があり、変数 aaa.b も再配置する必要があります。これは、1.obj (外部参照変数、シンボル レコード内の SectionNumber=0) 内の yyy のアドレスがわからないためです。 、IMAGE_SYM_UNDEFINED、これはセクションがシンボル レコードに割り当てられていないことを意味します。値 0 は、このシンボルが他の場所で定義された外部シンボルへの参照であることを意味します (このシンボルが外部参照変数であることを示します)。 Address をリンクするときに yyy を指定し、aaa のアドレスがわかっている場合、それは .data セクションにあります (.data セクションのアドレスはわかっています)。
ただし、リンカーが 1.obj ファイルを Source.obj にリンクすると、.data セクションがマージされるため、マージ後に 1.obj 内の .data セクションの場所を特定することはできません。 `aaa.b = 200; によって参照されるアドレスは、exe ファイル内の aaa 変数の位置に従って修正する必要がありますか? では、どのように修正すればよいでしょうか? 修正情報はどこにありますか? セクション ヘッダー (セクション テーブル) (obj ファイルで使用) の PointerToRelocations ポインターが指す再配置テーブルのすぐ内側。
つまり、Add 関数によって生成されたマシンコード内の yyy と aaa を表すアドレスを修正する必要があります。

0 番目のセクションの PointerToRelocations ポインタの値は 0 であり、NumberOfRelocations の値も 0 であるため、0 番目のセクションには再配置アドレスがないことがわかります。
追加シンボルがどのセクションにあるかを見てみましょう。yyy シンボルが配置されているセクションは、内部のアドレスを変更する
必要がないため、再配置する必要がないことに注意してください。変更する必要があるアドレスはすべてコード内にありますどの実行可能コードがこの変数を参照するか、この変数のアドレスが変更された場合、変更された場合、この実行可能コード内のアドレスを修正する必要があるため、探す必要があるのは Add です

ここに画像の説明を挿入します

ここに画像の説明を挿入します

Add は 60 番目のシンボルであり、そのセクション番号 SectionNumber が 6 番目であることがわかります。これはセクション テーブルの 5 番目のセクションです。

ここに画像の説明を挿入します

再配置情報の場所がわかります。セクション 5 には 4 つの再配置情報 (NumberOfRelocations) があります。コードを変更してデバッグし、再配置情報に含まれる内容を確認します。

ここに画像の説明を挿入します

まず、移転情報の形式を見てみましょう。

ここに画像の説明を挿入します

  • VirtualAddress
    の意味は、たとえば、変数のアドレス (yyy など) を使用するコードがある場合、VirtualAddress は、コードが配置されているセクションの開始アドレスからそのアドレスまでのオフセットであるということです。変数の。
    VirtualAddress は、再配置が適用される項目のアドレスです。
    これは、コードが配置されているセクションからのオフセットに、セクション テーブル (セクション ヘッダー) の RVA/Offset フィールドの値を加えたものです。たとえば、セクションの最初のバイトのアドレスが 0x10 である場合、3 番目のバイトのアドレスは 0x12 (セクション内の再配置された項目のオフセットに、その項目を参照するコード セクションの RVA を加えたもの) になります。

  • SymbolTableIndex
    再配置情報は何を再配置する必要がありますか? シンボルを再配置する必要があるため、これは前に説明したシンボル テーブル配列を指すインデックスです。このインデックスが指すシンボルを再配置します。

  • Type
    は転勤の種類の 1 つですが、どのようにして転居しますか? 転勤にはさまざまな種類があります。

ここに画像の説明を挿入します

ここでは、4 つの再配置情報があることがわかります (最後の再配置情報 pRelocation[4] は終了識別子である必要がありますが、これは意味がありません)、Add 関数内に再配置する必要がある yyy と aaa.b があることがわかります。 , 再配置情報はこれら 2 つより多くなります。たとえば、return a + b; このコードは多くのコードを追加しますが、その一部は再配置する必要があるため、再配置情報は 2 つだけではなく、他の情報も含まれています。は4ですが、この4つのどれがyyyの再配置情報でどれがaaaの再配置情報なのかわかりません。
それで、私たちはどう思いますか?

ここに画像の説明を挿入します

追加のメンバー RelocCount があることがわかります。これはカウントされませんので、心配する必要はありません。VirtualAddress と共用体を形成するため、2 つの値は同じです。

ここに画像の説明を挿入します


pRelocation[0] の SymbolTableIndex 値は 0x29 で、41 番目のシンボルであることを示しています。これは明らかにシステム内の再配置が必要なシンボルです。次の再配置情報を見てみましょう。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

pRelocation[1]のSymbolTableIndexの値は0x40で64番目のシンボルですが、これもシステム内で再配置が必要なシンボルであり、関数になっているようです。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

pRelocation[2]のSymbolTableIndexの値は84番目のシンボルである0x54であり、これがyyy(pRelocation[2)]の再配置情報となる。

ここに画像の説明を挿入します

また、17番目のシンボルであるSymbolTableIndex=0x11は、aaaの再配置情報(pRelocation[3])である。
この時点で、再配置する必要がある 2 つの変数 yyy と aaa に加えて、システムによって生成される 2 つの追加のシンボルも再配置する必要があることがより明確になります。

IDA ツールを使用して obj ファイルを表示する

obj ファイルを調べるには IDA ツールを使用します。これにより、obj ファイルを逆アセンブルできます。

ここに画像の説明を挿入します

yyy は変数であり、IDA 関数ウィンドウには関数情報のみが表示されるため、上の図の左側の IDA 関数ウィンドウには yyy 変数がありません。yyy 自体は 1.obj ファイルにないことに注意してください
。 、これはソース .obj 内にあります (ただし、外部シンボル yyy は 1.obj の Add 関数で使用されます)。

ここに画像の説明を挿入します

まず、yyy の再配置情報 (pRelocation[2]) を見てみましょう。そのオフセット (VirtualAddress) は 0x41 です。

ここに画像の説明を挿入します

ねえ、これはなぜ RDI をプッシュするのですか? ああ、この 0x41 は yyy 変数が配置されているセクションから始まるオフセットなので、どのセクションにあるのでしょうか?

ここに画像の説明を挿入します

ここに画像の説明を挿入します

Add シンボルは 60 番目のシンボルであり (Add 関数では 2 つの変数 yyy と aaa.b が参照されるため、Add シンボルを確認してください)、その SectionNumber は 6 で、5 番目のセクションにあります。

ここに画像の説明を挿入します

ファイル内の 5 番目のセクション .text の FOA は 0x3d45 です。上記の再配置の概念によれば、セクション .text の FOA + 再配置項目のオフセット yyy = 0x3d45 + 0x41 = 0x3d86、IDA で表示しますこのアドレス明らかに間違っています!

ここに画像の説明を挿入します

IDA の Add の開始位置が 0x38 で、変数 yyy が配置されているセクションのオフセット VirtualAddress が 0x41 であることがわかり、0x38+0x41=0x79 になります。これが上図の右側で選択された文です。再配置する必要があります (アセンブリ コードで逆のシンボル yyy が確認できます)。つまり、0x79 バイト目から再配置する必要があります。

ここに画像の説明を挿入します

つまり、コード内の yyy に値 2 を割り当てるコードによって生成されたアセンブリです。
IDA でマシンコードを表示すると、選択した 4 バイト (EF 00 00 00) が yyy に再配置する必要があるアドレスであることがわかります。

ここに画像の説明を挿入します

では、EFこれは何を意味するのでしょうか?
さて、この EF が何なのかはわかりませんが、EF 00 00 00リンクすると、リンカーは yyy 変数の実際のアドレスを見つけて、この場所に実際のアドレスを埋めることができます。
変数 yyy は 62 番目のシンボルです。そのシンボル レコードの SectionNumber は 0、つまり IMAGE_SYM_UNDEFINED です。これは、シンボル レコードにセクションが割り当てられていないことを意味します。値 0 は、このシンボルが外部への参照であることを意味します。他の場所で定義されたシンボル (このシンボルが外部参照変数であることを示します)。ゼロ以外の値は、そのサイズが値で指定されるユニバーサル シンボルであることを示します。
(StorageClass=2、つまり IMAGE_SYM_CLASS_EXTERNAL: Microsoft ツールで外部シンボルに使用される値。StorageClass が IMAGE_SYM_CLASS_EXTERNAL の場合、セクション番号 SectionNumber が IMAGE _ SYM _ UNDEFINED (0) の場合、セクションが割り当てられていないことを意味します。シンボル レコードの場合、Value はセクションのサイズを示します。このとき、このシンボルにはセクションが割り当てられていないため、Value の値も 0 になります。これは、このシンボルが外部参照変数であることを示します。セクション番号 SectionNumber
がゼロではない場合、値フィールドは、この変数がその中にあることを示すために使用されます。セクションの先頭を基準とした、そのセクション内のオフセット) (
値フィールド: シンボルに関連付けられた値。このフィールドの解釈は、シンボルに依存します。 SectionNumber と StorageClass。一般的な意味は、再配置可能なアドレス (指定されたセクション内のオフセットなど) です。

ここに画像の説明を挿入します

4番目の再配置情報(変数aaaが配置されているセクションの再配置情報pRelocation[3])のVirtualAddress値が0x4b、Add関数の開始アドレスが0x38、0x4b+0x38=0x83であることがわかります。は、以下の図の赤い線です。マークされたアドレス 89 FF FF FF:

ここに画像の説明を挿入します

これは、aaa 変数を再配置する必要があるアドレスです。この 89 FF FF FF は、この現在のアドレスです。では、これは何のアドレスですか?
64 ビット アセンブリのアドレスはすべて相対アドレスであり、32 ビット アセンブリとは異なり、64 ビット アセンブリには絶対アドレスがありません。
0x81 命令の下の命令アドレスは 0x8B であることがわかります。FFFFFF89 に 0x8B を加算すると 0x14 になります。なぜ 0x14 なのでしょうか?
yyy は外部参照変数であり、1.obj では定義されていないことがわかっているため、yyy を参照する相対オフセット アドレスは役に立たず、リンク中に正しいアドレスに置き換えられます。また、aaa は内部で定義された 1.obj にあります

ここに画像の説明を挿入します

変数 aaa のアドレスは 0x10 で、構造体 aaa は次のように定義されていることがわかります。

ここに画像の説明を挿入します

AAA 構造体の b メンバーは、b メンバーのアドレスになるように AAA の開始アドレスに 4 を追加する必要があることがわかります。Add 関数では、値を aaa.b に割り当てるため、0x10 を追加する必要があります。これは aaa.b のアドレスなので、ここでは 0x14 として計算されます。

ここに画像の説明を挿入します

上に示したように、64 ビット アセンブリで選択されたアドレスは相対アドレスです。この相対アドレスは、参照する変数 aaa.b のアドレスです。このアドレスはどのように取得されるのでしょうか?
0x14-0x8B=0xFFFFFF89

ここに画像の説明を挿入します

次のステートメントのアドレス 8B に 89 FF FF FF の相対オフセットを加算することによってのみ、aaa.b (0x14) のアドレスを取得できます。64 ビット プログラムのすべての相対アドレスはこの方法で計算されます。
64 ビット相対アドレスにより再配置の作業が軽減され、相対オフセットによりプログラムがメモリにロードされるときにアドレスを修正するプロセスが軽減されます。

それを修正するにはどうすればよいですか?
obj ファイルが exe ファイルにリンクされると、このセクションが再配置され、相対オフセットが不正確になります。そのため、obj を exe にマージした後、新しいレイアウトに従って相対オフセットを調整する必要があります。数量です。
調整後にどのようになるかを見てみましょう。

ここに画像の説明を挿入します

上図で選択されているのは調整後の相対アドレスです 先ほどのobjファイルのセクションの位置変更を基準にしています この相対量がリンカリンク時に調整された相対アドレスです 調整の基準となるのはpRelocationです[2]とpRelocation[3]、この2つの再配置情報、どの位置を調整するか、どのように調整するかはすべてこの再配置情報に含まれます。

Sub 関数シンボルが配置されているセクションを見てみましょう。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

上の図から、Add が 5 番目のセクション (SectionNumber=6) にあり、Sub が 7 番目のセクション (SectionNumber=8) にあることがわかります。下の図から、両方の関数が .text にあることがわかります。 $mn セクション. ですが、これら 2 つのシンボルの開始位置は異なります。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

IDA を見ると、これら 2 つのシンボルの開始位置が異なることがわかります。

移転の種類

再配置レコードの Type フィールドは、どのような種類の再配置を実行する必要があるかを示します。コンピュータの種類ごとに異なる再配置の種類が定義されています。
タイプインジケーター

ここに画像の説明を挿入します

ここに画像の説明を挿入します

たとえば、上図の IMAGE_REL_AMD64_REL32 タイプ (0x0004、再配置後のバイトからの 32 ビット相対アドレスは、再配置後のバイトから始まる 32 ビット相対アドレス、つまり 4 番目のバイトの次のバイトから始まります)再配置する必要があるバイト (32 ビット相対アドレス)
このタイプは何を意味しますか? 下図に示すように、選択した 4 バイトの機械語コードの次のバイト (下図では 0000000000000099) から始まるアドレスを基準とした 32 ビットの相対アドレスです。つまり、この 4 バイトは相対アドレスです。 to 0000000000000099 このアドレスの 32 ビット オフセット (相対アドレス)
したがって、VirtualAddress フィールドはバイト修正を開始する場所を示し、SymbolTableIndex フィールドは修正するシンボルを示し、Type フィールドはバイト修正を開始する場所を示します。バイトを修正します。これらの 4 バイトを修正する方法。

ここに画像の説明を挿入します

yyy で使用される型と aaa で使用される型を見てみましょう。それらの型は同じです。

ここに画像の説明を挿入します

この 8 のタイプは IMAGE_REL_AMD64_REL32_4 (0x0008、再配置からのバイト距離 4 に相対的な 32 ビット アドレス)、開始 4 バイトの 32 ビット相対アドレス)

ここに画像の説明を挿入します

ここに画像の説明を挿入します

これは確かに 32 ビット 4 バイトの相対アドレス (再配置からのバイト距離 4 に対する 32 ビット アドレス、32 ビット相対アドレス、再配置から始まる 4 バイト 32 ビット相対アドレス) であることがわかります。 。
さらに、上図からわかるように、逆アセンブリ コード内の yyy と aaa.b の相対アドレスの差は 0x83-0x79=0xa であり、これら 2 つの変数に関連付けられた再配置レコード内の VirtualAddress の差は次のとおりです。 : 0x4b-0x41=0xa なので、再配置からのバイト距離 4 に対する 32 ビットのアドレスは、アドレスが再配置レコードから始まることを意味します。

再配置レコードの VirtualAddress は、再配置が適用される項目のアドレスです。
これは、コードが配置されているセクションからのオフセットに、セクション テーブル (セクション ヘッダー) の RVA/Offset フィールドの値を加えたものです。たとえば、セクションの最初のバイトのアドレスが 0x10 である場合、3 番目のバイトのアドレスは 0x12 (セクション内の再配置された項目のオフセットに、その項目を参照するコード セクションの RVA を加えたもの) になります。
これが意味するのは、たとえば、変数のアドレス (yyy など) を使用するコードがある場合、VirtualAddress は、コードが配置されているセクションの開始アドレスからそのアドレスまでのオフセットであるということです。変数の。

このタイプはプロセッサのタイプに関連していることに注意してください。プロセッサが異なれば、このタイプも異なり、プロセッサのタイプごとに再配置方法も異なります。

各 obj の各セクションには、このセクションの独自の再配置情報があります。
各セクションの再配置情報は、exe ファイルには存在しません。PE については後で説明します。今、COFF の再配置情報について説明しています。再配置情報。

グループ化されたセクション (オブジェクトのみ)

グループ化されたセクション

セクションの名前を見ると、$ドル記号が表示されますが、このドル記号は何をするのでしょうか?

ここに画像の説明を挿入します

.text A と .text A と .textがあるとします。2 つのセクションA.t e x t Bがあります。リンカーはこれら 2 つのセクションに遭遇すると、これら 2 つのセクションをマージしようとします。なぜマージする必要があるのでしょうか?
リンカは、ドル記号の後の文字 (ドル記号を含む) が実際のセクション名ではないと考えます。このセクションを exe にマージする場合、使用される実際のセクション名は、文字 の前の名前になります次に、その前にある名前と一致します。それでシンボルの前にある名前。では、記号に続く文字は何に使われるのでしょうか?
これはobj ファイル内の.text$A別のセクションである.text$Bため、別のセクションでもあります。リンカはこれら 2 つのセクションを 1 つのセクションにマージしますが、これらを 1 つのセクションにマージするときに問題が発生します。 text B セクション.text にあります Bこのセクションは .text にありますセクションBはセクション.t e x t A の前に配置されるか、セクション .text A は .text 内に配置されますセクションAはセクション テキスト Bありますか? これら 2 つのセクションのどちらが最初でどちらが最後になるかという規則があり、リンカはシンボルの後の文字を
見て、したがって、その文字以降の文字は **アルファベット順**でソートされ、セクションにマージされます。それでによると、文字の後の文字アルファベット順並べ替えて 1 つのセクションに結合します。したがって、このドル記号はセクションを結合するために使用されます。これがグループ化セクションの意味です。

COFF にはセクションの内容もいくつかあり、それを PE ファイルに入れました。


COFFファイルの概要

このセクションでは、COFF についてより深く理解できるように、COFF の全体像について説明し、さらに詳細についても説明します。

COFF ファイル = ファイルヘッダー + コード (Add/Sub) + データ + デバッグ情報 + 再配置情報 + シンボル + 文字列
IMAGE_FILE_HEADER + .text + .data + .debug

ここに画像の説明を挿入します

obj ファイル内のセクション

まず、上記のコードのセクションと各セクションの内容を見てみましょう。詳細を説明するために実際の obj ファイルを例に挙げてみましょう。

ここに画像の説明を挿入します

.drectve セクション

内部のコンテンツはいくつかのリンク コマンドであり、実際には文字列の束です。この文字列の束はいくつかのリンク コマンドです。

ここに画像の説明を挿入します

上の図で選択した位置の右側にある虫眼鏡アイコンをクリックすると、テキスト情報を表示するリスト ボックスが開きます。

ここに画像の説明を挿入します

/DEFAULTLIBリンク コマンドは、リンク時に組み込まれる lib ライブラリ ファイルを意味します。/EDITANDCONTINUEリンク コマンドを使用すると、デバッグ中にブレーク
ポイント以下のコードを変更でき、変更後も続行できます。変更後に新しくコンパイルされたバージョン。
/alternatenameは代替コマンドです。__JustMyCode_Default @ を置き換えるには、__CheckForDebuggerJustMyCode を使用してください。この名前はエイリアスに相当するため、使いにくいです。

したがって、.drectve セクションは、リンカーが 1.obj ファイルと Source.obj を exe ファイルにリンクするのに役立つ補助情報でもあります。

.bss セクション

.bss セクションには、初期化されていない変数 (データ) が含まれています。.bss に配置される初期化されていない変数は、次の図の変数 p1 および y7 など、0 に初期化された変数です。

ここに画像の説明を挿入します

int y6;1.c で代入されていない変数を弱い変数と呼びますが、異なるソース ファイルに同じ名前の弱い変数を定義することもできますし、同じ名前の弱い変数の種類も異なりますchar y6;。同じ名前のこれら 2 つの変数は、リンク時に競合せず、エラーも発生しません。このタイプの変数を弱い変数と呼びます。このタイプの変数はより特殊です
。 , この種の変数は初期化されていませんが、.bss セクションには配置されません。.bss セクションには、0 に初期化した変数が配置されます。

.dataセクション

ここに画像の説明を挿入します

上の図 3 と図 4 の 2 つの .data セクションには、初期化した変数が含まれています。たとえば、int x6=200;この初期化された変数は .data セクションに配置されます。

ここに画像の説明を挿入します

int* p1 = NULL変数 p1 が 2 番目のセクション (SectionNumber=3) に配置されており、p1 は NULL ( ) に初期化されています。つまり、p1 は 0 に初期化されているため、.bss セクションに配置されていることがわかります。

ここに画像の説明を挿入します

y6 の SectionNumber=0 は、変数 y6 がどのセクションにも存在せず、どのセクションにも存在しないことを示します。その StorageClass=2 (IMAGE_SYM_CLASS_EXTERNAL) は、y6 が外部変数であることを示し、その SectionNumber= 0 、値はシンボルのサイズを示すために使用されます。

ここに画像の説明を挿入します

y6 は、シンボル テーブルの 12 番目の変数です。デバッグ観察から、Value=4 であることがわかります。StorageClass=2 で、その SectionNumber が 0 ではない場合、Value は、セクション内のこの変数のオフセットを表すために使用されます。シフト量、このセクションの先頭からの相対的なオフセットです
が、上図に示すように、SectionNumber=0 の場合、変数のサイズを示すために Value が使用されます (y6 は int 型として定義されます)。

明らかに、1.c で y6 変数を定義したとき、それが 1.obj 内の変数であると考えましたが、コンパイルされた 1.obj ファイルでは、実際には外部変数として存在します。 1.obj ファイル内の y6 変数に割り当てられ、外部変数として存在します。
つまり、このメソッドは、先ほど説明した弱い変数の機能を促進します。Source.c で y6 を定義することもできます。このプロジェクトで必要な obj ファイルが多数ある場合、たとえば 10 個のソース コードがある場合、10 個の y6 を で定義できます。これら 10 個のソース ファイル (int、char、struct 構造体、longlong、その他の 64 ビットなど、さまざまな型にすることができます) の場合、これら 10 個の y6 は外部変数であり、これら 10 個のソース ファイルで宣言された y6 は外部変数です。リンカーがそれらを exe ファイルにリンクしたい場合、リンカーはそれをどのように処理しますか?
私たちのリンカは 10 個の外部変数を見ることができます。このとき、リンカはこれら 10 個の y6 をまとめて 1 つの変数として使用します。これらの変数を 1 つの y6 にマージします。リンカはこれらの y6 のどれかを確認します。型の長さは次のとおりです。たとえば、構造体型の y6 が 32 バイトを占める場合、リンカは y6 の長さとして最も長いバイト型を選択します。この場合、32 バイトのセグメント Space は int として使用できます。 char、struct、longlong として使用できます。この場合、コンパイラはそれらをマージします。これは、これまでに見てきた弱い変数の特徴です。

ここに画像の説明を挿入します

y7 は強い変数であるため、その SectionNumber は 3 であり、これは 2 番目の .bss セクションです。y7 は 0 ( int y7 = 0;) に初期化されているため、0 に初期化され、.bss セクションに配置されます。変数は変数です。 0に初期化されます。

ここに画像の説明を挿入します

次に、もう一度 x6 を見てみましょう。その SectionNumber=5 なので、4 番目の .data セクションに配置されます。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

x5 のような変数については、COMDAT セクション データについて説明したときにすでに詳しく説明しました。この種の変数は、初期化済みであることを意味しますが、コンパイル中に同じ名前の変数と競合することは望ましくありません。変数に接頭辞 selectany を追加すると、コンパイラは同じ名前の変数の 1 つをベンチマークとして選択し、同じ名前の他の変数を破棄します。5 x5 を定義した場合、コンパイラは 1 つのみを使用します
。 x5 の場合、エラーは報告されません。この種の変数は ATL でも使用されます。

.msvcjmc セクション

ここに画像の説明を挿入します

.msvcjmc セクションは何に使用されますか? このセクションが何のためのセクションなのか知りたいのですが、Microsoft の公式 Web サイトの説明ではこのセクションについて言及されていません。私たちができるのは、いくつかの手がかりを見つけて自分の目で確認することだけです。
このセクションの SectionNumber は 6 である必要があることがわかり、SectionNumber が 6 であるシンボルを調べます。

ここに画像の説明を挿入します

セクション .msvcjmc で非常に多くの変数を探しています。このセクションの変数はすべて、上の図で選択された場所にあります。これらの変数は何に使用されますか?
これらの変数はデバッグに使用されます。1.obj ファイルはデバッグ バージョン (デバッグ x64) に従ってコンパイルされます。コンパイルされた obj にはこれらの変数が含まれます。これらの変数は、コードのデバッグおよびチェック時に使用されます。

例を見てみましょう:

ここに画像の説明を挿入します

上の図で選択したシンボルのメモリ アドレスを rcx レジスタに置き、最初のパラメータとして __CheckForDebuggerJustMyCode 関数に渡します。呼び出しコードにブレークポイントを置き、F5 と F11 を使用して関数内でこのデバッグ チェックを入力できます。コード。

ここに画像の説明を挿入します

上の図で選択された変数 __B9B818ED_1@c は、次の図で選択されたシンボルであり、その SectionNumber=6 です。これは、この変数が 5 番目の .msvcjmc セクションにあることを意味します。
したがって、.msvcjmc セクションの変数はデバッグ時にすべて使用されます。obj のデバッグ バージョンであるため、この変数のペアが生成されます。

このデバッグ チェック コードの最後まで行って、関数 __CheckForDebuggerJustMyCode が何を行うかを見てみましょう。

ここに画像の説明を挿入します

このデバッグ検査コードを読むと、この関数は最終的には何があっても正常に実行されることがわかります。
rcx は、先ほどのデバッグで生成された変数 __B9B818ED_1@c のメモリ アドレスです。このメモリ アドレスを rsp+8 に置きます。上の図で選択した文が実行されると、rsp レジスタが指すメモリ アドレスは何になります。内部に格納されているのは、関数の戻りアドレスです。

静的解析ツール IDA を使用して COFF 形式ファイル 1.obj を開き、__CheckForDebuggerJustMyCode をダブルクリックして、この関数の逆アセンブリ コードと VC によって生成された逆アセンブリ コードの違いを確認します。

ここに画像の説明を挿入します

この関数には extrn キーワードがあり、これが外部関数であることを示しています。つまり、1.obj にはこの関数のコードがありません。
したがって、1.obj に __CheckForDebuggerJustMyCode 関数のコードがなく、リンカーが 1.obj と Source.obj を exe にリンクすると、VC 逆アセンブリで何が表示されますか。 _CheckForDebuggerJustMyCode 関数の逆アセンブリ コードはどこにありますかから来る?
明らかに、この関数の逆アセンブリ コードは exe ファイルに配置されていますが、この関数のコードはどこから来たのでしょうか?

ここに画像の説明を挿入します

dec をリンカー コマンド ラインと呼びますが、これは、コマンド /DEFAULTLIB、/EDITANDCONTINUE、および /alternatename が 1.obj に配置され、次に link が 1.obj と Source.obj を exe にリンクすることを意味します。これら 2 つの obj だけでは十分ではありません。__CheckForDebuggerJustMyCode 関数のコードもあるからです。明らかに、このコードは 1.obj の外のコードです。この関数のコードは 1.obj にありません。Source.obj の外にある必要はありません。 , 当然ですが、exeをリンクするとこの関数のコードが出てくるのですが、どこに出てくるのでしょうか?
リンカーは、コンパイルおよびリンク コマンド (/DEFAULTLIB およびその他のコマンド) を認識した後、uuid.lib、LIBCCMTD、および OLDNAMES ライブラリもこの exe にリンクします。
lib はライブラリです。C 言語を学ぶと、これは静的ライブラリであり、関数コードが含まれていると必ず言われます。
先ほど述べた __CheckForDebuggerJustMyCode 関数のコードは LIBCCMTD ライブラリにあるはずです。このライブラリのコードを exe に入れると、この関数のコードがそこに存在します。
したがって、これは link コマンドの機能であり、リンカーが obj を実行可能ファイル exe にリンクするのに役立つ補助情報です。この一連のリンク コマンドがなければ、__CheckForDebuggerJustMyCode 関数のコードを見つけることができません。

ここに画像の説明を挿入します

IDA によって開かれた 1.obj の逆アセンブリ コードには、この __CheckForDebuggerJustMyCode 関数のコードが表示されません。この関数のコードは obj ファイルにないため、IDA がそれを静的に逆アセンブルする方法はありません。

もちろん、IDA を使用して、コンパイルとリンクによって生成された exe ファイルを静的に逆アセンブルすることはできますが、ここでは別のツール x64dbg を使用して、生成された exe 実行可能ファイルを開きます。

ここに画像の説明を挿入します

__CheckForDebuggerJustMyCode 関数のコードは exe 内に存在する必要があることがわかっているため、x64dbg のシンボル タブで exe 実行可能ファイル モジュールをクリックし、右側の検索編集ボックスに __CheckForDebuggerJustMyCode 関数名を入力して見つけます。 :

ここに画像の説明を挿入します

検索された関数名をダブルクリックして、関数の逆アセンブリ コードを入力します。次に、関数の先頭にブレークポイントを設定し、F9 キーを 2 回押して関数を実行します。

ここに画像の説明を挿入します

ここで、この関数の逆アセンブルされたコードを確認し、VC の逆アセンブルされたコードと 1 行ずつ比較できます。コードの最初の 2 文は同じで、VC のコードの 3 番目の文の JMC_flag と x64dbg の位置が同じです
。コードが異なります。x64dbg のコードは rsp+40 です。明らかに、VC のコードは x64dbg ではより明確に見ることができません。
つまり、rsp は最初に戻りアドレスを指し、次に rsp が 38h 減らされ、その後 40h が追加されます。これはたまたま戻りアドレスの 1 つ上のパラメータ位置、つまりコードの 3 番目の文が実行された後を指します。 、パラメータ 1 値は rax レジスタに配置されます。
F8 キーを押して実行をステップ実行して、以下を確認できます。

ここに画像の説明を挿入します

コードの 3 番目の文を実行すると、rax=rcx= パラメータ 1 の値であることがわかります。最初のパラメータの値は、シンボル変数 __B9B818ED_1@c が配置されているメモリ アドレス、つまり、次の JMC_flag です。 VC のこの場所
これmov rax, qword ptr [JMC_flag]が何なのか理解できませんが、x64dbg デバッガーでは理解できます。

ここに画像の説明を挿入します

上の図では、このコード行が選択されています。ここでは、call qword ptr ds:[<&GetCurrentThreadId>]現在のスレッド ID を取得するために、このコードに注目する必要があります。この関数はプログラミング時によく使用されます。この関数は、何か小さなことを行うために使用します。ブレークポイントを This に設定します。コード。

ここに画像の説明を挿入します

上の図の cmp コードを実行すると、この文をクリックして下のウィンドウに表示されdword ptr ds:[00007FF784E3A43C <project34.__DebuggerCurrentSteppingThreadId>]=0、次にこの文をダブルクリックして、以下のデータ ウィンドウのアドレス 00007FF784E3A43C の内容が表示されます。

ここに画像の説明を挿入します

逆アセンブリ コードに戻ると、cmp コードの比較条件が true の場合、次の行の je 命令はジャンプを実行し、関数の最後にジャンプできます。

ここに画像の説明を挿入します

次に、呼び出しコードを実行したい場合は、cmp コードの __DebuggerCurrentSteppingThreadId 変数を 0 に等しくないようにする必要があります。
データ ウィンドウでこの変数の内容を変更し (右クリック、バイナリ編集、編集)、任意の値を変更し (たとえば、ASCII 値 = 1)、je コードに対して F7 を実行してもジャンプしません。 、その後、呼び出しを実行できます。
F7 を押してこの呼び出しに従います。
ここに画像の説明を挿入します

これは現在のスレッド ID を取得するための GetCurrentThreadId の関数コードですが、この時点でスレッドの TEB が gs レジスタに置かれているのではないかと推測されます。

32 ビット プログラムでは、TEB は FS レジスタに配置されますが、64 ビット TEB は GS レジスタに配置されます。
Windbg を開き、Windows オペレーティング システムがデバッグ モード ( CMD で実行bcdedit -debug on) である必要があり、ダウンロードした Microsoft シンボルがロードされている必要があります。次のdt _TEBコマンドを実行します。

ここに画像の説明を挿入します

この TEB の構造がわかります。gs レジスタの 30 バイト目が TEB の NtTib にあることがわかります。次のコマンドを実行しますdt _NT_TIB

ここに画像の説明を挿入します

30 バイト目は Self で、自分自身を指すことを意味します。

ここに画像の説明を挿入します

したがって、rax レジスタは TEB の最初のアドレスを保持します。TEB の 48 番目のバイトは _CLIENT_ID 構造内にあります。次のdt _CLIENT_IDコマンドを入力します。

変数が 2 つあり、1 つはプロセスの ID、もう 1 つはスレッドの ID であることがわかります。そのため、このメソッドを使用して、スレッド TEB ブロックを gs レジスタから直接見つけます。これは 48 番目の TEB です。ブロック。バイトはスレッド ID です。

リリースバージョンobj

Debug x64 でコンパイルしない場合、.msvcjmc セクションに大量のシンボルが残るでしょうか?
obj のリリース バージョンをコンパイルして、このシンボルのペアがまだ生成できるかどうかを確認してみましょう (まず VC でリリース コンパイル モードを選択して obj をコンパイルし、CreateFile 関数で obj ファイル パス パラメーターを変更してから、デバッグ コンパイルを選択します)モード。デバッグ 1.obj ファイル):

ここに画像の説明を挿入します

ここに画像の説明を挿入します

デバッグ ステップが上図の 47 行目に到達すると、pFile_Head 変数が観察され、内部のメンバーの値がすべて間違っていることがわかります。たとえば、Machine は 0、NumberOfSections は 0xffff です。どうしてこれほど多くのセクションがあるのでしょうか。 ? これは明らかに間違っています。なぜですか? 何が起こっているのですか?
現時点では、VC のデバッグに問題があると考えられるので、Microsoft のツール dumpbin を使用して 1.obj ファイルを調べます。

ここに画像の説明を挿入します

Microsoft が提供するツールは、リリース バージョン 1.obj の形式を認識できないことがわかりました。つまり、COFF 形式ではないため、Microsoft がリリース バージョンの obj ファイルにどのような形式を提供したかはわかりません。明確ではないので、どうすればよいでしょうか?
コマンド ラインを使用して、1.c ソース ファイルを 1.obj ファイルにコンパイルします。

ここに画像の説明を挿入します

現時点でコンパイルしたのはリリース バージョン 1.obj です。次に、Source.c ソース コード内の CreateFile 関数のパラメータを変更し、コマンド ラインからコンパイルされた 1.obj ファイルを開きます。

ここに画像の説明を挿入します

ここに画像の説明を挿入します

これで、.msvcjmc セクションに乱雑なシンボルは表示されなくなりました。.msvcjmc セクションさえも削除されました。これがリリース バージョンの実際の姿であり、デバッグとチェックのための多くの機能が削除されました。

.text はコード セクションであり、実行可能コードが含まれています。

.xdata セクションと .pdata セクションの概要

2 つのセクション .xdata と .pdata は何に使用されますか? これら 2 つのセクションは関数呼び出しに関連しており、関数呼び出しはスタック メモリの割り当て、関数自体の「開始」と「閉じる」と密接に関連しています。これらの 2 つのセクションは、構造化例外処理にも関連しています。
つまり、これら 2 つのセクションは、関数呼び出し、スタック メモリの割り当て、関数の「開始」と「終了」、および構造化例外に関連しています。これらは接続されているため、処理はすべて関連しています。ここで、これらの内容がこれら 2 つのセクションに関連していることを覚えておいてください。

関数の「開始」と「終了」

ここで、関数の「開始」と「終了」とは何かについて簡単に説明します。

機能の「始まり」

ここに画像の説明を挿入します

Add 関数に進みましょう。この関数では、実際の関数のコードがmov dword ptr [c], 0C8hこの文からデバッグチェックコードを呼び出しますが、なぜ 0xCCCCCCCh で初期化されるのでしょうか? 0xCC は int 3 命令のマシンコードであり、この命令を実行するとコードが中断されるためです。
関数の先頭にある 2 行の逆アセンブリ命令は、関数パラメータを rdx および rcx レジスタからスタック メモリに配置し、次に関数で使用される rbp および rdi レジスタの値をスタックにプッシュし、これら 2 つのレジスタの値を破棄することはできないため、関数を終了するときに、これら 2 つのレジスタの値を復元する必要があります。

しかし、eax および ecx レジスタの値を保存せず、rbp および rdi レジスタの値のみを個別に保存するのはなぜでしょうか? eax および ecx レジスタの値が変更されても問題にならないのはなぜですか?これはなぜですか?
私たちがプログラムを書くとき、ハードウェア要件があるだけではありません。
アセンブリ言語を学ぶとき、どのレジスタとどのレジスタを一致させることができるか、どのレジスタとどのレジスタを一致させることができないか、どのレジスタが何に使用されるかなどです。このリップは、次に実行可能な命令を指すレジスタです。このレジスタの目的は専用です。これは CPU のハードウェア要件です。このハードウェア要件に従ってプログラムしないと、CPU は動作しません。
同時に、私たちがプログラムを書くとき、ハードウェア要件だけでなく、ソフトウェア要件もあります。これらのソフトウェア要件は誰が提案するのでしょうか? ソフトウェア要件は誰が私たちに提供するのでしょうか?
ハードウェア メーカーはハードウェア要件についてのみ言及しますが、ソフトウェア要件はオペレーティング システム メーカー (Microsoft など) によって提示されます。これを ABI (アプリケーション バイナリ インターフェイス) と呼びます。現在、私たちが作成するすべての Windows アプリケーションは、Windows アプリケーション バイナリ インターフェイスの仕様に準拠しています。これは Windows アプリケーションだけでなく、Windows カーネル ドライバーでも同様です。
したがって、Add 関数では、ecx と eax の値に関係なく、2 つのレジスタ rbp と rdi の値のみを保存します。ecx と eax を変更しましたが、値を復元する必要はありません。復元に関しては、Microsoft のソフトウェア要件には、使用する前に元の値を保存する必要があるレジスタに関係なく、どのレジスタを使用できるかが示されています。これは、ソフトウェア要件。
もちろん、Microsoft のコンパイラとリンカを使用しているため、これらのことを考慮する必要はありませんが、アセンブリ言語でコードを記述したい場合は、この種の ABI ソフトウェアの要件を理解する必要があります。これらの Microsoft アプリケーションのバイナリ インターフェイスを知っていれば、Windows プログラムをアセンブリ言語で作成すると、多くの問題が発生することになります。これが、Windows プログラムをアセンブリ言語で作成したくない理由の 1 つです。これらのルールを理解するのは非常に面倒だからです。もちろん、これらのルールについては、今後具体的に説明しますが、結局のところ、私たちの研究は非常に詳細に行われているため、ここで誰もがそれらを最初に理解できます。

関数の「オープン」とは、実際には、レジスタ値の一部の保存、スタック メモリの割り当て、関数パラメータの設定であり、これらを総称して関数の「オープン」と呼びます。

関数の「開始」と「終了」

ここに画像の説明を挿入します

上の図の関数の最後にある選択された文は、関数の「終了の挨拶」であり、レジスタを復元し、関数の先頭に割り当てられたスタック領域を取り戻します。

.chks64 セクション

ここに画像の説明を挿入します

この .chks64 は Microsoft に固有のセクションです。このセクションが何に使用されるかはわかりません。このセクションの SectionNumber は 9 ですが、シンボル テーブルにはこの番号のシンボルがありません。

ここに画像の説明を挿入します

上の図ではセクション名だけが選択されていますが、このセクションには何も表示されておらず、このセクションが何に使われるのかよくわかりません。

セクション名の変更

ここに画像の説明を挿入します

セクションに名前を付けることもできます。たとえば、.bss セクションの y7 変数を、自分たちで名前を付けた .YouData セクションに置くことができます。プログラムの出力を見てみましょう (CreateFile の 1.obj path パラメーターを変更することを忘れないでください)デバッグ バージョンに):

ここに画像の説明を挿入します

ここに画像の説明を挿入します

変数 y7 は、この .YouData セクションに配置されています。変数 y7 を確認できます。

ここに画像の説明を挿入します

y7 の SectionNumber=4 であることがわかり、実際に 3 番目の .YouData セクションにあります。この方法でこのセクションの名前を変更できます。

ここに画像の説明を挿入します

Add 関数のコードを .abc セクションに配置する場合は、コードを次のように変更するだけです。

ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/zhaopeng01zp/article/details/131283557