FromNandの日記

完全自分用のメモでございます。

コンピュータサイエンスについての興味深い記事を並べたい

C++ - ミューテックスとアトミック処理について|teratail
C++ - 変数の型とCPUの関係|teratail
データ型のアラインメントとは何か,なぜ必要なのか?
[C] 意外と知られていないva_copy | ごちゃまぜの音
低レイヤを知りたい人のためのCコンパイラ作成入門
【連載】コンピュータアーキテクチャの話 | マイナビニュース
C++ - オブジェクト指向のメモリ効率|teratail

printf・fprintf・vfprintfについて考察してみた

c言語のstdio.hに似たような関数が3つもあったので違いを考察してみた。


簡単に言うと

  • printfはstdoutにフォーマット付き文字列を出力する関数
  • fprintfは第一引数に指定したストリームにフォーマット付き文字列を出力する関数
  • vfprintfは主に可変長引数を取る関数内で使用され、va_list型の変数をフォーマット付き文字列の引数として代用することで可変長引数をもつ関数を作りやすくする


ということらしい。

標準出力(stdout)と標準エラー出力(stderr)の違いについて

標準出力(stdout)と標準エラー出力(stderr)の違いについてまとめていく。


標準出力と標準エラー出力の主な違いはリダイレクトできるかできないかの違いが大きいらしい。
標準出力はリダイレクトが行われるが、標準エラー出力の方ではリダイレクトが行われない。


他にも標準出力ではバッファがかまされているが、標準エラー出力ではバッファが使用されていないみたいなことも聞いたことがある。(嘘かも)
これの理由については、おそらくエラー出力をする際は緊急時で、そんなときにバッファなぞを利用している暇はないのだろう。


次のコードでは、標準出力に「stdout」、標準エラー出力に「stderr」という文字列を出力している。
一応、このコードは「test.c」というファイルに保存している。

#include<stdio.h>

int main(void){
    fprintf(stdout, "stdout");
    fprintf(stderr, "stderr");
    return 0;
}


そして、コマンドラインで次のようなコマンドを打ち込む。

gcc -o test test.c
./test > stderr.txt


すると、stderr.txtの内容は次のようになる。

stderr


つまり、「fprintf(stdout, "stdout")」の方だけがリダイレクトされている。
また、コマンドラインで次のように記述すると、標準出力と標準エラー出力に分けてファイルに出力することができるらしいのでやってみた。

./test 1>stdout.txt 2>stderr.txt


こうすると、stdout.txtには「stdout」、stderr.txtには「stderr」の文字列が保存されていた。

【x86アセンブラ】PUSH・POPの動作を少し細かく見てみる

今回考えるのは、PUSH・POPにおいてESPの変化と値を取り出すタイミングがどの順番で行われているかということだ。


実験に使用するコードはこれ。

.code32

.globl push_test, pop_test

.text

push_test:
    subl $4, %esp               ## here!
    movl $0x12345678, (%esp)    ## here!
    popl %eax
    ret

pop_test:
    pushl $0x12345678
    movl (%esp), %eax           ## here!
    addl $4, %esp               ## here!
    ret


C言語からは次のように呼び出す。
printというのは自作のprintf関数だ。

unsigned int push_test();
unsigned int pop_test();

int main(int argc, char **argv, char **envp){
    print("push_test() = %x, pop_test() = %x\n", push_test(), pop_test());
    return 0;
}


実行結果は次のようになった。

push_test() = 12345678, pop_test() = 12345678


この結果から、PUSH・POPを行う直前のESPは最後に操作した値を指しているということがわかる。
最後にPUSH・POPを模倣したコードを載せておく。


PUSH

subl $4, %esp
movl 値, (%esp)


POP

movl (%esp), %eax
addl $4, %esp

【x86アセンブラ】レジスタの内容を表示するプログラムを考えてみる

低レイヤの作業をしているとこういった関数が欲しくなる時があるので実装してみた。


まず、これがC言語側から呼び出す際に必要とする構造体と使用する関数のプロトタイプ宣言。

// search_regs.h

#ifndef _SEARCH_REGS_H
#define _SEARCH_REGS_H

typedef struct REGS {
    unsigned int eax, ecx, edx, ebx, esp, ebp, esi, edi, eip;
} REGS;

unsigned int search_eax(void);
unsigned int search_ecx(void);
unsigned int search_edx(void);
unsigned int search_ebx(void);
unsigned int search_esp(void);
unsigned int search_ebp(void);
unsigned int search_esi(void);
unsigned int search_edi(void);
unsigned int search_eip(void);
REGS* search_regs(REGS*);
void print_regs(REGS*);

#endif


次にこれが実際のコード。
search_eax, search_ecx, search_edx, search_ebx, search_esp, search_ebp, search_esi, search_edi, search_eipはそれぞれ戻り値に符号なし整数でレジスタの値を返す。
espとeipは関数を呼び出す直前のものを表示するようにしている。
search_regsは引数に上の構造体へのアドレスを渡し、それに内容を詰めていく感じだ。
espはそんなに参照する機会はないだろし、実装も少しめんどくさかったので、ちょっと妥協した実装になっている。

# espの場合は関数を呼び出した後の値を返す(引数、戻り地などがespを変化させて、厳密にやるのがめんどい)
# eipの場合は関数を呼び出す命令のアドレスを返す

.code32

.globl search_eax, search_ecx, search_edx, search_ebx, search_esp, search_ebp, search_esi, search_edi, search_eip
.globl search_regs

.text

# こいつらは単発でレジスタを調べたいときに使うといい
# 呼び出して戻り値を参照するだけ

search_eax:
    ret

search_ecx:
    movl %ecx, %eax
    ret

search_edx:
    movl %edx, %eax
    ret

search_ebx:
    movl %ebx, %eax
    ret

search_esp:
    leal -0x4(%esp), %eax
    ret

search_ebp:
    movl %ebp, %eax
    ret

search_esi:
    movl %esi, %eax
    ret

search_edi:
    movl %edi, %eax
    ret

search_eip:
    movl (%esp), %eax
    subl $5, %eax
    ret

search_regs:
    pushl %eax
    movl 0x8(%esp), %eax
saerch_regs_eax:
    popl (%eax)
    addl $4, %eax
search_regs_ecx:
    movl %ecx, (%eax)
    addl $4, %eax
search_regs_edx:
    movl %edx, (%eax)
    addl $4, %eax
search_regs_ebx:
    movl %ebx, (%eax)
    addl $4, %eax
search_regs_esp:
    movl %esp, (%eax)
    addl $4, %eax
search_regs_ebp:
    movl %ebp, (%eax)
    addl $4, %eax
search_regs_esi:
    movl %esi, (%eax)
    addl $4, %eax
search_regs_edi:
    movl %edi, (%eax)
    addl $4, %eax
search_regs_eip:
    movl (%esp), %ecx
    subl $5, %ecx
    movl %ecx, (%eax)
search_regs_end:
    movl 0x4(%esp), %eax
    ret


C言語の関数からは次のように呼び出すといい。

int main(void){
    REGS regs;
    search_regs(&regs);
    print_regs(&regs);
    return 0;
}


print_regsというのはREGS構造体を理解して、その内容を表示する関数だ。
この関数の中でprintというのを使っているのだが、これはprntfを自作したもの。

void print_regs(REGS *regs){
    print("eax = %x, ecx = %x, edx = %x, ebx = %x, esp = %x, ebp = %x, esi = %x, edi = %x, eip = %x\n", \
          regs->eax, regs->ecx, regs->edx, regs->ebx, regs->esp, regs->ebp, regs->esi, regs->edi, regs->eip);
}


僕の環境でのプログラムの実行結果は次のようになった。

eax = FFD99DFC, ecx = FFD99E38, edx = 3, ebx = F7F83000, esp = FFD99DDC, ebp = FFD99E28, esi = 1, edi = 8049000, eip = 8049071


おまけとして、アセンブラから呼び出す際のコードも紹介しておく。

# アセンブラから呼び出す場合は36byteの領域を確保して、その領域への先頭ポインタをpushしてから呼び出す。(C言語ではREGS構造体のポインタ)
# search_regsによってその領域に値が詰められて、領域へのポインタがeaxに入って帰ってくるので、push eaxしてからprint_regsを呼び出す。

test:
    pushl %ebp
    movl %esp, %ebp
    subl $36, %esp
    pushl %esp
    call search_regs
    addl $4, %esp
    pushl %eax
    call print_regs
    addl $4, %esp
    addl $36, %esp
    leave
    ret


このtestをmainから呼び出した結果は次のようになった。

eax = 8050008, ecx = FFB63468, edx = 3, ebx = F7F70000, esp = FFB6341C, ebp = FFB63448, esi = 1, edi = 8049000, eip = 80493B8

【x86アセンブラ】PUSHAL・POPALについて調べてみた【順番・レジスタの種類など】

この記事は32bitレジスタを対象に考えておりますのでご了承ください。
参考 : PUSHAD - Push All General-Purpose Registers


x86には一括してレジスタを操作する命令PUSHAL・POPALがあるらしい。
それぞれが行う命令を分解して考えると、こうだ。

PUSHAL (PUSHAD)

PUSH EAX
PUSH ECX
PUSH EDX
PUSH EBX
PUSH ESP
PUSH EBP
PUSH ESI
PUSH EDI

POPAL (POPAD)

POP EDI
POP ESI
POP EBP
# POP ESP will be ignored.
POP EBX
POP EDX
POP ECX
POP EAX


PUSHALではEAX→ECX→EDX→EBX→ESP→EBP→ESI→EDIの順番に操作されるようだ。
POPALの方ではESPの値が復旧されないらしいが、これは当然そうなる。
なぜなら、少しでもおかしな番地からPOPADした場合、ESPは当然変な値になってしまうが、こうなった場合プログラムはかんたんにダウンすることになる。
また、これは憶測だが、POPADを行っている途中もESPの値を参照しているわけで、これを変更してしまうとPOPAD命令にも影響が出るかもしれない。


ここでPUSHALを行った際のメモリレイアウトを確認して締めようと思う。

低アドレス

EDI    ← ESP indicate here!!
ESI
EBP
ESP
EBX
EDX
ECX
EAX

高アドレス