x86アセンブリ言語に関するメモ

x86系マイクロプロセッサの持つ主なレジスタ

レジスタ 呼び名 主な機能
EAX アキュムレータ 算術演算の結果を格納
EBX ベースレジスタ メモリアドレスを格納
ECX カウントレジスタ ループ回数をカウント
EDX データレジスタ データを格納
ESI ソースインデックス データ転送元のメモリアドレスを格納
EDI ディスティネーションインデックス データ転送先のメモリアドレスを格納
EBP ベースポインタ データの格納領域のメモリアドレスを格納
ESP スタックポインタ スタック領域のメモリアドレスを格納

主な命令

命令 使用例 意味    詳細
MOV MOV EAX,ECX EAX = ECX ECXの値をEAXに格納
MOVZX MOVZX_EAX,ECX EAX = ECX MOVのサイズが違うレジスタにコピーするとき用いる版
ADD ADD EAX,ECX EAX += ECX EAXにECXを加算
SUB SUB EAX,ECX EAX -= ECX EAXからECXを減算
MUL MUL EAX,ECX EAX *= ECX EAXにECXを乗算
DIV DIV EAX,ECX EAX =EAX / ECX EAXをECXで割って,商をEAXに,余りをEDXに格納
EDX = EAX % ECX
INC INC EAX EAX++ EAXに1を加算
DEC DEC EAX EAX-- EAXに1を減算
XOR XOR EAX,ECX EAX = EAX ^ ECX EAXとECXの各ビットごとに排他的論理和を取り,結果をEAXに格納
LEA LEA EAX,[ECX+4] EAX = ECX + 4 ECX+4(アドレス値)をEAXに格納
CMP CMP EAX,ECX if(EAX==ECX)_ZF=1 EAXとECXの値を比較してフラグに反映
else ZF = 0 EAXとECXが等しければZF=1,EAXとECXが等しくなければ、ZF=0
TEST TEST EAX,EAX if(EAX==0) ZF = 1 EAXの値を0と比較してフラグに反映
else ZF = 0 EAXが0と等しければZF=1,EAXが0でなければZF=0
JE(JZ) JE 041001000 if(ZF == 1) ZFが1なら041001000にジャンプ
GOTO 041001000
JNE(JNZ) JNE 041001000 if(ZF == 0) ZFが0なら041001000にジャンプ
GOTO 041001000
JMP JMP 041001000 GOTO 041001000 無条件で041001000にジャンプ
CALL CALL lstrcmpW lstrcmpW() lstrcmpw関数の呼び出し
PUSH PUSH 00000001 スタックへ00000001を格納
POP POP EAX スタックからEAXへ値を取得
NOP NOP 何もしない
SAR SAR EAX EAXを右にシフトする,すなわち2で割る
RET RET 処理を呼び出し元に戻す

豆知識

  • XOR EAX,EAX は MOV EAX,0 と同様の働きをする。

  • dword ptr は、指定されたメモリアドレスから4バイトのデータを読み出すことを示す。

例えば、

MOV DWORD PTR SS:[EBP-8],1

の場合ベースポインタのアドレス-8から4バイトまでのデータに整数値1を格納している。

Pythonで16進文字列をintに変換

16進文字列からint

  • 基数を指定してintにパースすればいい。
>>> s = 'abcdef'
>>> print int(s, 16)
11259375
  • ちなみにインタラクティブシェルなら0xからはじめた16進文字列を入力すれば10進数にしたものを返してくれる。
>>> 0xdeadbeef
3735928559

intから16進文字列

  • hex()を使う
>>> a = 111223
>>> print hex(a)
0x1b277
  • 先頭に0xがつくのがいやならformatメソッドを使うとよい
>>> a = 111223
>>> print format(a, 'x')
1b277
  • こうなふうに先頭2文字を削除するという手もある
>>> a = 111223
>>> print hex(a)[2:]
1b277
  • フォーマット指定子(%記法)を使ってもいい
>>> a = 111223
>>> print '%x' % a
1b277

簡単なタイピングゲーム

はじめに

 C言語で簡単なタイピングゲームを作ってみた。
 Visual C++でビルドすることを前提としてる。

コード

/*typing_cui1.c*/
#include <time.h>
#include <conio.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
 
#define Qnumber 20
 
int main(){
 
    int i, stage, miss = 0, alpsum = 0;
    char *japanese[Qnumber] = { 
        "交わした約束忘れないよ",
        "目を閉じ確かめる",
        "押し寄せた闇",
        "振り払って進むよ",
        "いつになったらなくした未来を",
        "私ここでまた見ることできるの?",
        "溢れ出した不安の影を",
        "何度でも裂いて",
        "この世界歩んでこう",
        "とめどなく刻まれた",
        "時は今始まり告げ",
        "変わらない思いをのせ",
        "閉ざされた扉開けよう",
        "目覚めた心は走り出した",
        "未来を描くため",
        "難しい道で立ち止まっても",
        "空はきれいな青さで",
        "いつも待っててくれる",
        "だから怖くない",
        "もう何があっても挫けない"
    };
    char *roman[Qnumber] = {
        "kawasitayakusokuwasurenaiyo",
        "mewotojitasikameru",
        "osiyosetayami",
        "huriharattesusumuyo",
        "ituninattaranakusitamiraiwo",
        "watasikokodematamirukotodekiruno",
        "ahuredasitahuannnokagewo",
        "nandodemosaite",
        "konosekaiayundekou",
        "tomedonakukizamareta",
        "tokihaimahajimarituge",
        "kawaranaiomoiwonose",
        "tozasaretatobiraakeyou",
        "mezametakokorohahasiridasita",
        "miraiwoegakutame",
        "muzukasiimitidetatidomattemo",
        "sorahakireinaaosade",
        "itumomattetekureru",
        "dakarakowakunai",
        "mounanigaattemokujikenai"
    };
    double time;
    clock_t start, end;
 
    for (i = 0; i < Qnumber; i++)
        alpsum += strlen(roman[i]);
 
    printf("スペースキーで開始です。\n");
    while (_getch() != ' ')
        ;
 
    start = clock();
 
    for (stage = 0; stage < Qnumber; stage++){
 
        printf("%s\n", japanese[stage]);
        fflush(stdout);
 
        int len = strlen(roman[stage]);
        for (i = 0; i < len; i++){
            int ch;
            do{
                ch = _getch();
                if (isprint(ch)){
                    _putch(ch);
                    if (ch != roman[stage][i]){
                        miss++;
                        _putch('\b');
                    }
                }
            } while (ch != roman[stage][i]);
        }
        printf("\n");
    }
 
    end = clock();
 
    time = (end - start) / CLOCKS_PER_SEC;
 
    printf("\n%.2ftypes/sec\n%.1ftypes/min\nmiss types:%d\n", alpsum / time, alpsum / time * 60,miss);
 
    return 0;
}

入力されるアルファベットも指定しているのでかなり手抜き。
_getch()や_putch()など標準Cでは定義されてない関数を使っているので例えばgccコンパイルしようとするとエラーになる。

参考

新版 明解C言語 中級編

新版 明解C言語 中級編

C言語でchar型を返す関数。

はじめに

 C言語で文字列が与えられたときそれを逆順したものを返す関数をなぜか作りたくなった。

ポインタを使う

 文字列を返す関数を作るときはintやdoubleなどと同じように何も考えず

  • 間違え
char hoge(char fuga){
//何らかの処理
return fuga;
}

のようにしてはダメ。

C言語の文字列はchar型の配列であるため、その配列の先頭の要素のアドレスを指すポインタを返すようにする。

  • 正解
char *hoge(char *fuga) {
//何らかの処理
return fuga;
}

サンプルコード

ポインタについての理解を助けるためのサンプルコードを添付した。

単に配列変数を指定するとその配列の先頭のアドレスを示すことが確認できる。

scanf("%s", str); などのように int や double と違い変数の先頭に & をつけなくて良いのはそのため。

冗長になるだけであるが scanf("%s", &str[0]); のように書くこともできる。

実装例1

配列のi番目と(文字列の長さ-1-i)番目の要素を(文字列の長さ÷2)回入れ替えてる。

char *reverse(char *s) {
    int i;
    char tmp;
    for (i = 0; i< strlen(s)/2; i++) {
        tmp = s[i];
        s[i] = s[strlen(s)-1-i];
        s[strlen(s)-1-i] = tmp;
    }
    return s;
}

実装例2

文字列の先頭を指すポインタと末尾を指すポインタを使って文字を入れ替えている。

使用例

例えばこの問題の仕様に合わせるのであれば、

#include <stdio.h>
#include <string.h>

char *reverse(char *s) {
    int i;
    char tmp;
    for (i = 0; i < strlen(s)/2; i++) {
        tmp = s[i];
        s[i] = s[strlen(s)-1-i];
        s[strlen(s)-1-i] = tmp;
    }
    return s;
}

int main() {
    char str[21];

    fgets(str, sizeof(str), stdin);
    printf("%s\n", reverse(str));

    return 0;
}

こんな風に使える。

終わりに

 ポインタを全然理解していなかったため、こんな簡単な処理をするだけの関数を書くのにかなり時間がかかってしまった。