ハロー“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 まで