ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ
以下,目次です.
目次
- はじめに
 
- 1 ハロー・ワールドに触れてみる
 
- 1.1 ハロー・ワールドのサンプル・プログラム
 
- 1.1.1 サンプル・プログラムのダウンロードについて
 
- 1.1.2 各種ソースコードの入手について
 
- 1.1.3 VMによるCentOS環境の利用について
 
- 1.2 実行ファイルの生成の手順
 
- 1.2.1 実行ファイルを生成する
 
- 1.2.2 コンパイル・オプション
 
- 1.2.3 プログラムの動作を確認する
 
- 1.2.4 逆アセンブル結果を見る
 
- 1.2.5 実行ファイルの解析結果を見る
 
- 1.3 VM環境の利用
 
- 1.3.1 VMイメージのダウンロード
 
- 1.3.2 VMイメージをインポートする
 
- 1.3.3 VMを起動する
 
- 1.3.4 ログインする
 
- 1.3.5 サンプル・プログラムを展開しておく
 
- 1.3.6 各種ソースコードを展開しておく
 
- 1.3.7 VMの終了
 
- 1.4 VM環境を使いやすくする
 
- 1.4.1 GUIを利用する
 
- 1.4.2 SSHでのログインを可能にする
 
- 1.4.3 文字化けを防ぐ
 
- 1.4.4 カーネルの起動オプションを調整する
 
- 1.5 アセンブラを読んでみる
 
- 1.5.1 main()のアセンブラを読む
 
- 1.5.2 レジスタの扱い
 
- 1.5.3 スタックの扱い
 
- 1.5.4 関数呼び出しの手順
 
- 1.5.5 関数からのリターン
 
- 1.6 この章のまとめ
 
- 2 printf()の内部動作を追う
 
- 2.1 デバッガを使ってみよう
 
- 2.1.1 GDBを起動してみよう
 
- 2.1.2 gdbserverで画面崩れを防ぐ
 
- 2.1.3 ブレークポイントを張ってみる
 
- 2.1.4 ステップ実行してみる
 
- 2.2 デバッガで動作を追ってみる
 
- 2.2.1 printf()の中に入っていく
 
- 2.2.2 アセンブラベースで処理を見る
 
- 2.2.3 逆アセンブル結果と比較する
 
- 2.2.4 関数呼び出しの流れを見る
 
- 2.2.5 メッセージの出力箇所を探る
 
- 2.2.6 vfprintf()の中に入る
 
- 2.2.7 ポインタ経由での関数呼び出しを探る
 
- 2.2.8 ブレークポイントを整理する
 
- 2.2.9 さらに深く追っていく
 
- 2.2.10 write()が呼ばれている
 
- 2.2.11 呼ばれる命令が異なる場合
 
- 2.2.12 メッセージが出力される瞬間
 
- 2.3 システムコールの呼び出し
 
- 2.3.1 straceによるトレース
 
- 2.3.2 ptrace()によるトレース
 
- 2.3.3 ptrace()の細かい動作をカーネルソースから知る
 
- 2.3.4 ptrace()で独自トレーサを作る
 
- 2.3.5 コアダンプを解析する
 
- 2.4 バイナリエディタを使ってみる
 
- 2.4.1 バイナリエディタで実行ファイルを開く
 
- 2.4.2 int $0x80 の呼び出し部分を探す
 
- 2.4.3 実行ファイルを書き換えて確認する
 
- 2.5 この章のまとめ
 
- 3 Linuxカーネルの処理を探る
 
- 3.1 Linuxカーネルのソースコードを読んでみよう
 
- 3.1.1 Linuxカーネルのダウンロード
 
- 3.1.2 ディレクトリ構成を見る
 
- 3.1.3 目的の処理を探す
 
- 3.1.4 見るべきファイルを限定していく
 
- 3.1.5 割込みハンドラを見る
 
- 3.1.6 割込みハンドラの登録
 
- 3.2 パラメータの渡しかたを見る
 
- 3.2.1 レジスタの値を確認する
 
- 3.2.2 スタックの状態も確認しておく
 
- 3.2.3 システムコール呼び出し後のレジスタの状態
 
- 3.2.4 システムコール番号
 
- 3.2.5 システムコールの引数
 
- 3.2.6 システムコール・ラッパー
 
- 3.3 戻り値の返しかたを見る
 
- 3.3.1 システムコールの戻り値
 
- 3.3.2 errnoを設定するのは誰か?
 
- 3.3.3 errnoの設定処理
 
- 3.3.4 Linuxカーネルのエラーの返しかた
 
- 3.4 Linuxカーネルの問題点
 
- 3.4.1 引数の個数の制限の問題
 
- 3.4.2 戻り値の範囲の問題
 
- 3.5 この章のまとめ
 
- 4 ライブラリからのシステムコール呼び出し
 
- 4.1 GNU C Library (glibc)
 
- 4.1.1 システムコール・ラッパーの重要性
 
- 4.1.2 glibcのソースコード
 
- 4.1.3 int $0x80 の呼び出しを探す
 
- 4.1.4 システムコール・ラッパーの定義
 
- 4.1.5 システムコール・ラッパーの実体を探す
 
- 4.1.6 lessでキーワードを探す
 
- 4.1.7 システムコール・ラッパーのテンプレート
 
- 4.2 システムコールについて考える
 
- 4.2.1 システムコールのABI
 
- 4.2.2 簡単なシステムコール・ラッパーの例
 
- 4.2.3 アセンブラで書いた関数をC言語から呼び出す
 
- 4.2.4 関数呼び出しのABI
 
- 4.2.5 ABIとAPI
 
- 4.2.6 writeとwrite()の違い
 
- 4.3 glibcをビルドする
 
- 4.3.1 ./configureスクリプトを実行する
 
- 4.3.2 ビルド用のディレクトリを作成する
 
- 4.3.3 makeを実行する
 
- 4.3.4 ./configureからやりなおす
 
- 4.3.5 ライブラリをシステムにインストールする
 
- 4.3.6 ビルドしたglibcで実行ファイルを作成する
 
- 4.3.7 デバッガで追ってみる
 
- 4.4 この章のまとめ
 
- 5 main()関数の前と後
 
- 5.1 デバッガでスタートアップの処理を追う
 
- 5.1.1 とりあえずmain()でブレークしてみる
 
- 5.1.2 main()の呼び出し元を探る
 
- 5.1.3 エントリ・ポイントを見てみる
 
- 5.1.4 main()が呼ばれるまでの処理を追う
 
- 5.1.5 main()の呼び出しの前後を見る
 
- 5.2 スタートアップのソースコードを読む
 
- 5.2.1 スタートアップの役割
 
- 5.2.2 glibcのソースコードを読む
 
- 5.2.3 _startを読む
 
- 5.2.4 __libc_start_main()を読む
 
- 5.3 exit()の処理
 
- 5.3.1 exit()の処理をデバッガで追う
 
- 5.3.2 _exit()の呼び出し
 
- 5.3.3 exit_groupとexitの2つのシステムコール
 
- 5.3.4 _exit()のソースコードを読む
 
- 5.3.5 exit()と_exit()とexit_groupとexit
 
- 5.3.6 manのカテゴリを見てみる
 
- 5.3.7 FreeBSDの場合
 
- 5.3.8 exit()の処理を読む
 
- 5.3.9 atexit()の処理を読む
 
- 5.4 Linuxカーネルの処理を見てみよう
 
- 5.4.1 プログラムの実行はどのようにして行われるのか
 
- 5.4.2 execve()の処理
 
- 5.4.3 ELFフォーマットのロード
 
- 5.4.4 レジスタの設定処理
 
- 5.4.5 argv[]の準備
 
- 5.5 この章のまとめ
 
- 6 標準入出力関数の実装を見る
 
- 6.1 printf()のソースコードを読む
 
- 6.1.1 printf()の本体を探す
 
- 6.1.2 フォーマット文字列の処理を見る
 
- 6.1.3 文字の出力を見る
 
- 6.1.4 ファイルポインタの構造
 
- 6.1.5 ファイル構造体のバッファリング処理
 
- 6.1.6 write()の呼び出し
 
- 6.2 FreeBSDでの実装を見る
 
- 6.2.1 FreeBSDのソースコードを見る
 
- 6.2.2 FreeBSDの標準Cライブラリ
 
- 6.2.3 printf()の先を見る
 
- 6.2.4 GDBで関数呼び出しを確認する
 
- 6.2.5 フォーマット文字列の処理を見る
 
- 6.3 Newlibでの実装を見る
 
- 6.3.1 Newlibのソースコードを見る
 
- 6.3.2 printf()の実装を見る
 
- 6.3.3 FreeBSDでの実装に似ている
 
- 6.3.4 ミニマムな実装が別にある
 
- 6.4 この章のまとめ
 
- 7 コンパイルの手順と仕組み
 
- 7.1 コンパイルの流れ
 
- 7.1.1 コンパイルの広義と狭義の意味
 
- 7.1.2 実行ファイルが生成されるまで
 
- 7.1.3 gccが行っている処理
 
- 7.2 OSとは何なのか
 
- 7.2.1 OSの定義について考える
 
- 7.2.2 汎用システムと組込みシステム
 
- 7.2.3 カーネルとしての「OS」
 
- 7.2.4 システムとしての「OS」
 
- 7.2.5 資源の管理と抽象化のためのOS
 
- 7.2.6 ソフトウェア動作のベース環境としてのOS
 
- 7.2.7 汎用OSと組込みOS
 
- 7.2.8 組込みOSの条件
 
- 7.2.9 UNIXというOS
 
- 7.2.10 「UNIXライク」とはどういう意味か
 
- 7.3 GNU/Linuxディストリビューションとは何なのか
 
- 7.3.1 Linuxとは何なのか
 
- 7.3.2 CentOSとは何なのか
 
- 7.3.3 GNU/Linuxディストリビューション
 
- 7.3.4 「LinuxはUNIX互換」の2つの意味
 
- 7.3.5 標準Cライブラリ「glibc」
 
- 7.3.6 FreeBSDではどうなのか
 
- 7.3.7 FreeBSDのソースコードを見る
 
- 7.3.8 viのソースコードを見る
 
- 7.4 リンクの処理
 
- 7.4.1 ライブラリの場所
 
- 7.4.2 glibcの実体
 
- 7.4.3 ライブラリを逆アセンブルする
 
- 7.5 プリプロセッサの処理
 
- 7.5.1 プリプロセッサの役割
 
- 7.5.2 cppを使ってみる
 
- 7.5.3 ヘッダファイルの場所
 
- 7.5.4 2種類のインクルード方法
 
- 7.5.5 標準ヘッダファイル
 
- 7.5.6 標準ヘッダファイルの開発元
 
- 7.5.7 標準ヘッダファイルの置き場所
 
- 7.5.8 GNU/Linuxディストリビューションではどうなのか
 
- 7.6 この章のまとめ
 
- 8 実行ファイル解析
 
- 8.1 実行ファイルを見てみる
 
- 8.1.1 バイナリエディタで実行ファイルを見る
 
- 8.1.2 実行ファイルを書き換える
 
- 8.2 ELFフォーマット
 
- 8.2.1 readelfとobjdump
 
- 8.2.2 ELFヘッダを見てみる
 
- 8.2.3 ELFヘッダの構造を知る
 
- 8.2.4 いくつものヘッダファイル
 
- 8.2.5 ELFヘッダのバイナリを読む
 
- 8.3 セクションの情報を見る
 
- 8.3.1 セクション・ヘッダの情報を見てみる
 
- 8.3.2 機械語コードを見てみる
 
- 8.3.3 メッセージの配置先アドレスを知る
 
- 8.3.4 メッセージの配置先アドレスを書き換える
 
- 8.4 セグメント情報を見てみる
 
- 8.4.1 領域管理の単位が2つある
 
- 8.4.2 Linuxカーネルでの扱いを見る
 
- 8.4.3 セクションとセグメントの存在意義
 
- 8.5 共有ライブラリの仕組み
 
- 8.5.1 printf()は共有ライブラリ上にある
 
- 8.5.2 動的リンクと共有ライブラリ
 
- 8.5.3 共有ライブラリを調べる
 
- 8.5.4 共有ライブラリの実装を見る
 
- 8.5.5 GOTとPLT
 
- 8.5.6 GOTの初期値
 
- 8.5.7 PLTの全体像
 
- 8.6 この章のまとめ
 
- 9 最適化
 
- 9.1 最適化オプション
 
- 9.1.1 「-O0」の説明を見てみる
 
- 9.1.2 最適化の副作用
 
- 9.1.3 「-O1」の説明を見てみる
 
- 9.1.4 「-O2」の説明を見てみる
 
- 9.1.5 「-Os」の説明を見てみる
 
- 9.2 最適化の効果を見てみる
 
- 9.2.1 実行ファイルのサイズを見る
 
- 9.2.2 デバッグ情報が増加している
 
- 9.2.3 実行時間を見る
 
- 9.2.4 実行命令数をカウントする
 
- 9.3 アセンブラの変化を見る
 
- 9.3.1 -O0のアセンブラを見る
 
- 9.3.2 -O1のアセンブラを見る
 
- 9.3.3 -O2のアセンブラを見る
 
- 9.3.4 -Osのアセンブラを見る
 
- 9.4 シンプルなハロー・ワールドの場合
 
- 9.4.1 printf()のputs()への変換
 
- 9.4.2 改行コードの除去を確認する
 
- 9.5 この章のまとめ
 
- 10 様々な環境と様々なアーキテクチャ
 
- 10.1 FreeBSDでのハロー・ワールド
 
- 10.1.1 FreeBSDのソースコードの場所
 
- 10.1.2 Linux向けの実行ファイルをFreeBSD上で実行できるのか?
 
- 10.1.3 FreeBSDでのハロー・ワールド
 
- 10.1.4 GDBで動作を追う
 
- 10.1.5 FreeBSDのwrite()の処理
 
- 10.2 FreeBSDカーネルの処理を見る
 
- 10.2.1 FreeBSDのカーネル・ソースコード
 
- 10.2.2 C言語によるシステムコール処理
 
- 10.2.3 copyin()による引数の準備
 
- 10.2.4 システムコール番号
 
- 10.2.5 FreeBSDのシステムコール・ラッパー
 
- 10.2.6 エラー処理を見る
 
- 10.2.7 他のアーキテクチャではどうなのか
 
- 10.3 FreeBSDのLinuxエミュレーション機能
 
- 10.3.1 システムコール・テーブルの置き換え
 
- 10.3.2 システムコールごとの対応
 
- 10.3.3 引数の渡しかたの対応
 
- 10.3.4 エラー番号の変換
 
- 10.3.5 LinuxとFreeBSDのABIの比較と考察
 
- 10.4 Linux/x86以外について考える
 
- 10.4.1 ARMのクロスコンパイル環境
 
- 10.4.2 ARMの実行ファイルを作成する
 
- 10.4.3 シミュレータで実行してみる
 
- 10.4.4 GDBで動作を追う
 
- 10.4.5 ARMのシステムコール・ラッパー
 
- 10.4.6 シミュレータ内のシステムコール処理
 
- 10.4.7 システムコール番号を見る
 
- 10.4.8 モニタのシステムコール
 
- 10.4.9 POSIX以外のシステムコール
 
- 10.5 この章のまとめ
 
- 11 可変長引数の扱い
 
- 11.1 可変長引数の関数を作る
 
- 11.1.1 printf()をもう一度見てみる
 
- 11.1.2 可変長引数のサンプル・プログラム
 
- 11.2 可変長引数は,どのようにして渡されているのか
 
- 11.2.1 可変長引数の関数の呼び出し
 
- 11.2.2 va_start()による初期化処理
 
- 11.2.3 va_arg()による引数の取得
 
- 11.3 x86以外のアーキテクチャの場合
 
- 11.3.1 ARMでの関数呼び出しを見てみる
 
- 11.3.2 ARM用の実行ファイルを生成する
 
- 11.3.3 ARMでの可変長引数の関数呼び出し
 
- 11.3.4 ARMでのva_start()による初期化処理
 
- 11.3.5 ARMでのva_arg()による引数の取得
 
- 11.4 この章のまとめ
 
- 12 解析の集大成-システムコールの切替えを見る
 
- 12.1 _dl_sysinfoの設定を探る
 
- 12.1.1 システムコール呼び出しをもう一度見る
 
- 12.1.2 共有ライブラリについて調べる
 
- 12.1.3 システムコールの呼び出し箇所を見る
 
- 12.1.4 _dl_sysinfoには何が設定されているのか?
 
- 12.1.5 ウォッチポイントを利用して調べる
 
- 12.1.6 スタートアップの実装を見る
 
- 12.2 AT_SYSINFOによるパラメータ渡し
 
- 12.2.1 Linuxカーネルからのパラメータ渡し
 
- 12.2.2 AT_SYSINFO をキーワードにして探す
 
- 12.2.3 AT_SYSINFOに渡されるもの
 
- 12.2.4 __kernel_vsyscall の定義
 
- 12.2.5 パラメータはスタック上に格納されている
 
- 12.2.6 渡されたパラメータを確認する
 
- 12.2.7 スタートアップでのパラメータ取得
 
- 12.2.8 /procでパラメータを確認する
 
- 12.3 VDSOとシステムコール
 
- 12.3.1 VDSOとは何か
 
- 12.3.2 vsyscallの設定
 
- 12.3.3 vsyscallの選択
 
- 12.3.4 CPUIDのSEPフラグ
 
- 12.3.5 CPUIDからsysenterの利用可否を判断する
 
- 12.3.6 sysenterを無効化する方法
 
- 12.3.7 VDSOを無効化する方法
 
- 12.3.8 VDSOが無効になった場合の動作
 
- 12.3.9 gettimeofday()の実装
 
- 12.3.10 GDB側の対応
 
- 12.4 この章のまとめ
 
- おわりに
 
- 著者紹介
 
メールは kozos(アットマーク)kozos.jp まで