Page List

Search on the blog

ラベル アセンブラ の投稿を表示しています。 すべての投稿を表示
ラベル アセンブラ の投稿を表示しています。 すべての投稿を表示

2012年5月11日金曜日

はじめての自己書き換えコード

アセンブリで自己書き換えコードを書いてみました。windowsのコマンドプロンプトでdebugと打つとでてくる例のあれです。A xxxでセグメントベース + xxxのアドレスに命令を書くことができます。メモリに命令を直で打ち込むのでノイマン型コンピュータの特徴を身をもって体験できます。

U xxxでオフセットアドレスに格納されている機械語を逆アセンブルすることができるので、これを利用して簡単な自己書き換えコードを書いてみました。

まず、メインの処理。

-A 100
1857:0100 MOV AX, 00FF
1857:0103 MOV DX, 0001
1857:0106 CALL 200
1857:0109

アキュミュレータに00FF、データレジスタに0001をロードして、オフセットアドレス200に記述されたサブルーチンを呼び出します。オフセットアドレス200には加算を行う関数を記述します。

-A 200
1857:0200 ADD AX, DX
1857:0202 RET
1857:0203

足算を引算に書き換えます。203番に引算処理を書いてみて対応する機械語を調べてみます。
-A 203
1857:0203 SUB AX, DX
1857:0205

-U 203 203
1857:0203 29D0          SUB     AX,DX

足算の機械語も見ます。
-U 200 200
1857:0200 01D0          ADD     AX,DX

オフセットアドレス200を29に書きかえれば、ADDがSUBになるみたいです。ということで、オフセットアドレス200の処理を以下のように書きなおします。
-A 200
1857:0200 ADD AX, DX
1857:0202 MOV BYTE PTR [200], 29
1857:0207 RET
1857:0208

これで、足算をしたあと自分自身の処理が書かれた命令を書きなおし、引算をする関数へと変わるはずです。

以下実行結果。
-G = 100 109

AX=0100  BX=0000  CX=0000  DX=0001  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=1857  ES=1857  SS=1857  CS=1857  IP=0109   NV UP EI PL NZ AC PE NC
1857:0109 0000          ADD     [BX+SI],AL                         DS:0000=CD

-G = 100 109

AX=00FE  BX=0000  CX=0000  DX=0001  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=1857  ES=1857  SS=1857  CS=1857  IP=0109   NV UP EI PL NZ NA PO NC
1857:0109 0000          ADD     [BX+SI],AL                         DS:0000=CD

一回目の呼び出しでは足算が行われています。二回目の呼び出しでは思惑どおり引算が行われます。

2012年5月9日水曜日

アセンブリでユークリッドの互除法

MS-DOSのアセンブリでユークリッドの互除法を書いてみました。アキュムレータとデータレジスタに正の整数(1バイトに収まる範囲)をロードして実行するとアキュムレータに最大公約数が出力されます。

 -A 100
1857:0100 DIV DL
1857:0102 MOV AL, DL
1857:0104 MOV DL, AH
1857:0106 MOV AH, 0
1857:0108 CMP DL, 0
1857:010B JA 100
1857:010D

2011年11月10日木曜日

アセンブラを出力して遊ぶ

簡単なプログラムをアセンブラで出力して、スタックの動きを追ってみます。簡単なプログラムですが、結構面白いです。環境は、Ubuntu、gcc 4.4.3です。

 まず、Cのソースコード(test.c)。

#include <stdio.h>

main()
{
int i, j, k;

i = 1;
j = 2;
k = i + j;
printf("%d %d %d\n", i, j, k);
}


 そして、アセンブラ(test.s)。
$ gcc -S test.c
でアセンブラのコードを出力できます。

    .file    "test.c"
    .section    .rodata
.LC0:
    .string    "%d %d %d\n"
    .text
.globl main
    .type    main, @function
main:
    pushl    %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    movl    $1, 28(%esp)
    movl    $2, 24(%esp)
    movl    24(%esp), %eax
    movl    28(%esp), %edx
    leal    (%edx,%eax), %eax
    movl    %eax, 20(%esp)
    movl    $.LC0, %eax
    movl    20(%esp), %edx
    movl    %edx, 12(%esp)
    movl    24(%esp), %edx
    movl    %edx, 8(%esp)
    movl    28(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf
    leave
    ret
    .size    main, .-main
    .ident    "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

 いろいろ分かることがあります。
 まず、スタックのアドレスが番地の大きい方から小さいほうへと広がっているところです。これはx86マシンでは共通だと昔聞いたことがあります。
 それから、andl $-16, %esp があるので、16byteのaddress alignmentを使っているようです。FFFFFFF0でマスキングして16の倍数になる次の数をスタックポインタに代入しています。これだとベースポインタとスタックポインタの整合性があわないようにみえるんですけど、結局ベースポインタ使ってないし、リターンする時ベースポインタをスタックポインタに戻す処理も省略されてるので別にいいような気もします。
 意外だったのは、スタック内の変数にスタックポインタからの差分でアクセスしているところです。スタックポインタは実行時に移動するから、ベースポインタからの差分で変数にアクセスするのが普通かと思いましたが、そうでもないみたいです。
 あと、最後にprintfの可変長引数のところの仕組みがおもしろいです。第一引数の%dが3つあるんで、第一引数をpopしたあと、3回popすればいいんですねー。当然といえば当然ですが、実際コード上でみるとちょっとした感動がありました。printfがcallされるときのstackの状態は下のようになっています。