diff --git a/.gitignore b/.gitignore index 7484b75..7f16d65 100644 --- a/.gitignore +++ b/.gitignore @@ -308,3 +308,7 @@ out-temp/ # output from pandoc md -> tex md-out/ + +# compiled programs + +programs/build/ diff --git a/main.tex b/main.tex index 67bdffa..1c321d2 100644 --- a/main.tex +++ b/main.tex @@ -35,7 +35,10 @@ \input{section/appendix.tex} \newpage - \printbibliography + \printbibliography[ + heading=bibintoc, + title={参考文献} + ] \compiledTime -\end{document} \ No newline at end of file +\end{document} diff --git a/nix/compilers.nix b/nix/compilers.nix new file mode 100644 index 0000000..6ab0121 --- /dev/null +++ b/nix/compilers.nix @@ -0,0 +1,2 @@ +with import {}; +pkgsCross.riscv64-embedded.mkShell {} diff --git a/output/main-opt-final.pdf b/output/main-opt-final.pdf index e698cf9..86bbf86 100644 Binary files a/output/main-opt-final.pdf and b/output/main-opt-final.pdf differ diff --git a/output/main.pdf b/output/main.pdf index 125612d..d17872f 100644 Binary files a/output/main.pdf and b/output/main.pdf differ diff --git a/programs/Makefile b/programs/Makefile new file mode 100644 index 0000000..df716af --- /dev/null +++ b/programs/Makefile @@ -0,0 +1,17 @@ +CC=gcc +CFLAGS=-Wall + +demo: + $(CC) $(CFLAGS) -nolibc -nostdlib -O0 demo.c -o build/demo + +prog1: + $(CC) $(CFLAGS) prog1.c -o build/prog1 + +prog2: + $(CC) $(CFLAGS) prog2.c -o build/prog2 + +prog3: + $(CC) $(CFLAGS) prog3.c -o build/prog3 + +prog4: + $(CC) $(CFLAGS) prog4.c -o build/prog4 diff --git a/programs/prog2.c b/programs/prog2.c index 6de36e4..ae3895f 100644 --- a/programs/prog2.c +++ b/programs/prog2.c @@ -1,9 +1,7 @@ #include int main(void) { - printf("C\n"); - printf("言\n"); - printf("語\n"); + printf("C\n言\n語\n"); return 0; } diff --git a/programs/prog3.c b/programs/prog3.c index d3e0001..756a5aa 100644 --- a/programs/prog3.c +++ b/programs/prog3.c @@ -1,10 +1,7 @@ #include int main(void) { - printf("情\n"); - printf(" 報\n"); - printf("  処\n"); - printf("   理\n"); + printf("情\n 報\n  処\n   理\n"); return 0; } diff --git a/references.bib b/references.bib index 5c1597f..208ca96 100644 --- a/references.bib +++ b/references.bib @@ -65,5 +65,5 @@ year = {2021}, month = {01}, date = {2021-01-14}, - pages = {12-13,33,35-37,61-62,76,80-81} + pages = {12-13,29-37,61-62,76,80-81} } diff --git a/section/appendix.tex b/section/appendix.tex index fe2e386..1fcd581 100644 --- a/section/appendix.tex +++ b/section/appendix.tex @@ -3,7 +3,8 @@ \subsection{Deep Dive - 変数の舞台裏} -\ref{var_decl_def}節にてプログラムが値の種類を判別するためにコンパイラが型情報に基づいて読み出すべきビット長を実行ファイルに書き込まれると書いたが、実際はどうであろう。 +\ref{var_decl_def}節にてプログラムが値の種類を判別するためにコンパイラが型情報に基づいて読み出すべき\\ +ビット長を実行ファイルに書き込まれると書いたが、実際はどうであろう。 ここではコンパイラの生成物である実行ファイルをアセンブリに分解して検証していく。 まずは検証用のソースコードを用意する。今回は以下の物を用意した: @@ -15,11 +16,13 @@ このコードをコンパイルするにあたって、解読を容易にするために少しフラグを変更する必要がある。 Linuxでの\texttt{gcc}コンパイラは特に指定されていなければコンパイラの機能を使用するための\texttt{libgcc}と\texttt{libc}というC言語の標準的な関数(\texttt{printf}, etc.)などが宣言・定義されたライブラリをリンクします。 -これによりソフトウェアの日常的な機能の再開発を避けれるが、人間が読めるアセンブリに直すとその部分のコードが析出してしまい、目的のコードが埋もれてしまう。 +これによりソフトウェアの日常的な機能の再開発を避けれるが、人間が読めるアセンブリに直すと\\ +その部分のコードが析出してしまい、目的のコードが埋もれてしまう。 なので、これらライブラリの自動リンクをやめる必要がある。そこで今回は\texttt{-nostdlib}フラグと\texttt{-nolibc}フラグを追加する。\cite{gcc_man} 最近のコンパイラは非常に賢く、無意味なコードを取り除いて最適化しようとする。 -だが今回の検証用コードはただ単に変数を宣言・定義するだけで、実際に使用されないのでそのままではコンパイラに無意味と見なされ、勝手に変数の宣言・定義のコードを削除してしまう。 +だが今回の検証用コードはただ単に変数を宣言・定義するだけで、実際に使用されないのでそのままではコンパイラに\\ +無意味と見なされ、勝手に変数の宣言・定義のコードを削除してしまう。 なのでここにコンパイラによる最適化を無効にするために\texttt{-O0}フラグも追加する。\cite{gcc_man} 最終的なコンパイルコマンドはこのようになる: @@ -30,7 +33,7 @@ Linuxでの\texttt{gcc}コンパイラは特に指定されていなければコ \end{verbatim} \end{center} -上記のコマンドを実行するとリンカからエントリーポイント:プログラムの始まり、が定義されていないとエラーを吐くが解読に問題はないのでそのままでよい。 +上記のコマンドを実行するとリンカからエントリーポイント:プログラムの始まりが定義されていないとエラーを吐くが解読に問題はないのでそのままでよい。 次に生成物である\texttt{demo}をデコンパイル(実行ファイルからアセンブリに戻す作業)を行うためにGNU Binutils\footnote{\url{https://www.gnu.org/software/binutils/}}の\texttt{objdump}を利用する。 @@ -50,17 +53,17 @@ Linuxでの\texttt{gcc}コンパイラは特に指定されていなければコ \begin{center} \begin{verbatim} -demo: ファイル形式 elf64-x86-64 +demo-x86_64: ファイル形式 elf64-x86-64 セクション .text の逆アセンブル: -0000000000401000
: - 401000: 55 push rbp - 401001: 48 89 e5 mov rbp,rsp - 401004: c7 45 fc ff 00 00 00 mov DWORD PTR [rbp-0x4],0xff - 40100b: 90 nop - 40100c: 5d pop rbp - 40100d: c3 ret +0000000000001000
: + 1000: 55 push rbp + 1001: 48 89 e5 mov rbp,rsp + 1004: c7 45 fc ff 00 00 00 mov DWORD PTR [rbp-0x4],0xff + 100b: 90 nop + 100c: 5d pop rbp + 100d: c3 ret \end{verbatim} \end{center} @@ -69,28 +72,29 @@ demo: ファイル形式 elf64-x86-64 このアセンブリの口語訳は以下となる: \begin{itemize} - \item \texttt{rbp}レジスタ(CPU内にあるごく小さな変数)の内容をスタック(メモリ上にある最初に入れたデータは最後に出る仕組みを持つデータ保持の構造)の最上部に積む。 + \item \texttt{rbp}レジスタ(CPU内にあるごく小さな変数)の内容をスタック(メモリ上にある最初に入れた\\データは最後に出る仕組みを持つデータ保持の構造)の最上部に積む。 \item \texttt{rsp}レジスタの内容を\texttt{rbp}レジスタに書き込む。 - \item 16進数値\texttt{0xff}(10進数で255)を\texttt{rbp}レジスタの内容から\texttt{0x4}を引いたアドレス(メモリ上の場所を表す住所のような数値)が指している場所に書き込む。 + \item 16進数値\texttt{0xff}(10進数で255)を\texttt{rbp}レジスタの内容から\texttt{0x4}を引いたアドレス(メモリ上の\\場所を表す住所のような数値)が指している場所から\texttt{DWORD}(4バイト)分の領域に書き込む。 \item なにもしない。 \item スタックの最上部にある値を取り、それを\texttt{rbp}レジスタに読み込む。 \item 呼び出し元の関数に制御を戻す。 \end{itemize} \texttt{rbp}レジスタは主に関数内でのみ有効なローカル変数を格納するレジスタである。 -また、\texttt{rsp}レジスタはスタックポインタといい、現在実行されている関数に関するデータの読み書きに使用される。 +また、\texttt{rsp}\\レジスタはスタックポインタといい、現在実行されている関数に関するデータの読み書きに使用される。 今回の例では、まず\texttt{rbp}レジスタの内容をスタックに退避させ、現在のスタックポインタが指しているアドレスを\texttt{rbp}レジスタにコピーする。 -次に、\texttt{rbp}が指しているアドレスから4つ分移動させる。 -この時の4は単位が1バイトであるため$4*8=32$ビット分の空を確保している。 +次に、\texttt{rbp}が指しているアドレスから4つ分移動させ、\\スタックの4バイト分の領域を確保する。 +1バイトは8ビットであるため$4*8=32$ビット分の空を確保している。 これはまさに宣言そのもので、\texttt{int}型は32ビットのサイズを持つのでちゃんと当てはまる。 -更にこの空いた場所に\texttt{movl}命令を使用して16進数値\texttt{0xff}、10進数の255を移動させている。 -これが定義であり、宣言した場所に値を代入するというC言語の\texttt{a = 255;}と一致する。 +更にこの空いた場所に\texttt{mov}命令を使用して16進数値\texttt{0xff}、10進数の255を移動させている。 +これが\\定義であり、宣言した場所に値を代入するというC言語の\texttt{a = 255;}と一致する。 + \texttt{x86\_64}の様なCISC(複雑命令セットコンピュータ)は宣言と定義を一つの命令で行っている。 \newpage -では、小さく単純な命令セットを持つRISC(縮小命令セットコンピュータ)ではどうだろうか。 +では、小さく単純な命令セットを持つRISC(縮小命令セットコンピュータ)ではどうだろうか。\\ コンパイラをRISC-V 64ビットアーキテクチャ用のツールチェーンに変更して再度検証してみる。 まずは、以下のコマンドでコンパイルする: @@ -149,12 +153,14 @@ demo-riscv: ファイル形式 elf64-littleriscv \end{itemize} やはり、命令の種類が少ないRISCでは命令の数が多くなっている。 -だが最初の4つと最後の4つは関数のスタックフレームに関する命令で無視してよい。 -見るべきものは\texttt{100f0}と\texttt{100f4}である。 +だが最初の4つと最後の4つは\\関数のスタックフレームに関する命令で無視してよい。 +見るべきものは\texttt{100f0}と\texttt{100f4}である。\\ そこでは32ビットの値255を\texttt{a5}レジスタに記憶し、スタックにコピーしている。 ここでは64ビット長のレジスタに32ビット値を代入することで変数の宣言と定義を同時にしている。 しかしレジスタは一時的なデータの保持に使用されるので関数の寿命の間、いつでも参照できるようにスタックに移動させているのである。 +\texttt{x86\_64}との違いは読み出すバイト数が命令引数のキーワードとして明示されているか否かで、\texttt{RISC-V}では読み出すサイズによって命令が分けられている(eq. \texttt{SB}: 1バイト、\texttt{SH}: 2バイト、\\ +\texttt{SW}: 4バイト)\cite{riscv}。 -比べてみると、CISCとRISCではやることは同じである:スタックに変数の場所を作り、値を入れる。 +比べてみると、CISCとRISCではやることは同じである:スタックに変数のサイズ分の場所を作り、値を入れる。 以上より、\ref{var_decl_def}節で示した値の種類の判別にビット長を実行ファイルに埋め込むということがらが実証された。 diff --git a/section/program-4.tex b/section/program-4.tex index 5d29895..5f8cc66 100644 --- a/section/program-4.tex +++ b/section/program-4.tex @@ -2,8 +2,9 @@ 3つのint型整数y,m,dを宣言し,yは誕生年,mは誕生月,dは誕生日で初期化し,y+m+dの値を「○○○○の生年月日の和は****です.」と表示するプログラム +\newpage + \subsection{コードリスティング} -\vspace{-0.5cm} \lstinputlisting[language=C, title={演習課題4}]{../programs/prog4.c} \subsection{実行結果} diff --git a/section/syntax.tex b/section/syntax.tex index 8f3ea69..b71df9d 100644 --- a/section/syntax.tex +++ b/section/syntax.tex @@ -39,13 +39,18 @@ なので\texttt{stdlib.h}で提供される\texttt{exit()}関数や\texttt{EXIT\_SUCCESS, EXIT\_FAILURE}定数を使うのが望ましい。\cite{cppref_mainfunc} \begin{lstlisting}[language=C,title={\texttt{main}関数}] -int main(void) { // プログラム実行時に何も引数を渡す必要がない場合 +int main(void) { + // プログラム実行時に何も引数を渡す必要がない場合 + // 文 return 0; } -int main(int argc, char** argv) { // プログラム実行時に引数を渡す場合、argc は呼び出しプログラム名を含む引数の数、argv は呼び出しプログラム名を含む引数の値(文字列型)の配列である。 +int main(int argc, char** argv) { + // プログラム実行時に引数を渡す場合、argc は呼び出しプログラム名を含む引数の数、 + // argv は呼び出しプログラム名を含む引数の値(文字列型)の配列である。 + // 文 return 0;