8086 で FizzBuzz!

さあ!すっかり 8086 のアセンブリプログラミングにハマってしまいました。いや、ハマったというのは過言ですが。

ともかく、8086 で FizzBuzz に挑戦してみました。が、2560 で止まってしまいます…。

fizzbuzz

# vim:set syntax=gas:

/* fizzbuzz.S
 * https://linuxshugendo.wordpress.com/?p=1486
 * 2015-02-16
 */

        .code16
        .global _start
_start:
        xor     %ax, %ax
        movw    %ax, %es
        movw    $0xffff, %sp

        xor     %bx, %bx        # カウンタ

next:
        /* カウンタの更新 */
        inc     %bx

        /* 停止条件 */
        movw    %bx, %ax
        xor     $0xfff, %ax
        jz      end

        xor     %di, %di        # Fizz か Buzz だったことを示すフラグ

        /* Fizz */
        movw    %bx, %ax
        xor     %dx, %dx
        movw    $3, %cx
        div     %cx
        xor     $0, %dx
        jnz     buzz

        movw    $1, %di
        movw    $(fizzstr), %si
        call    puts

buzz:
        /* Buzz */
        movw    %bx, %ax
        xor     %dx, %dx
        movw    $5, %cx
        div     %cx
        xor     $0, %dx
        jnz     else

        movw    $1, %di
        movw    $(buzzstr), %si
        call    puts

else:
        /* フラグチェック */
        xor     $1, %di
        jz      printspc

        /* カウンタを表示します */
        movw    $(string), %di
        call    to_s
        call    puts
printspc:
        movw    $(space), %si
        call    puts

        jmp     next
end:
        hlt
        jmp     end

string: .string "Test!"
space:  .string " "
fizzstr:.string "Fizz"
buzzstr:.string "Buzz"

/* puts
 *
 * 入力
 *   %si ヌル終端文字列の開始アドレス
 */
puts:
        push    %ax
        push    %si
        movb    $0xe, %ah
        cld
sub_puts:
        lodsb
        xor     $0, %al
        jz      end_puts
        int     $0x10
        jmp     sub_puts
end_puts:
        pop     %si
        pop     %ax
        ret

/* to_s
 *
 * 入力
 *   %bx 数値
 *   %di 保存先のアドレス (5 バイト長)
 *
 * 出力
 *   %si 新しいアドレス
 */
to_s:
        push    %ax
        push    %bx
        push    %cx
        push    %di

        movw    %di, %si

        /* 空白文字で埋めます */
        cld
        movb    $0x20, %al
        movw    $4, %cx
        rep
        stosb

        std
        movw    %bx, %ax
        movb    $10, %bl
sub_to_s:
        div     %bl
        addb    $0x30, %ah      # ASCII 文字に変換します
        xchg    %ah, %al
        stosb
        xchg    %ah, %al
        xor     %ah, %ah
        xor     $0, %al
        jnz     sub_to_s

        movw    %si, %cx
        xor     %bx, %bx        # 空白文字数カウンタ
        cld
cnt_spaces:
        inc     %bx
        lodsb
        xor     $0x20, %al      # 空白文字と比較
        jz      cnt_spaces
        dec     %bx
        movw    %cx, %si
        addw    %bx, %si

        pop     %di
        pop     %cx
        pop     %bx
        pop     %ax

        ret

いやー難しい!パズル大好きな人ならハマるかも知れませんね。

(コウヅ)

8086 で遊べ!

今やパソコンの CPU は 64 ビット、メモリは数 GB、HDD は数 TB の時代です。一昔前と比べたら随分な進歩です。

が、現在人気の intel Core i なんとかみたいな最新の 64 ビット CPU でも、電源投入直後は 1978 年に発表されたインテル 8086 と互換性のあるリアルモードで動作します。ここから OS を起動する手続きの中で 32 ビットのプロテクトモード64 ビットモードという風に CPU の動作モードを順番に切り替えていくようです。

プロテクトモードに移行するにはメモリ上にセグメントディスクリプタテーブルだの割り込みディスクリプタテーブルだのを作成するとか何とかややこしいようですが、リアルモードならそんなの関係ありません。

ということで、フリーの仮想マシン環境の VirtualBox でリアルモードを体験してみました。BIOS コールを使って画面に “Hello, world!” と表示するだけです。BIOS 使うので VirtualBox インスタンスの設定で EFI をオンにしないように注意しましょう。

なお、8086 はまず 0xFFFF0 番地からプログラムを実行開始します。IBM PC では 0xA0000 (640 KiB) から 0xFFFFF まではメモリマップト IO で、0xFFFF0 は BIOS の領域です。BIOS はブートデバイスの最初の 1 セクタ (MBR) を 0x07C00 にロードしてここにジャンプするらしいです。(EFI と GPT の時はどうなるんでしょう?)

この記事が面白そうです!

下が GNU Assembler 用の Hello world (hello.S) です:

# vim:ft=gas:
.code16                                 # 以下リアルモード (16 ビットコード)。
.text  
        .globl  _start;
_start:                                 # リンカのために _start シンボルが必要。

        /* 画面に文字を表示するには、ah に 0x0e を、al に ASCII コードを
         * 入れて int 0x10 を実行します。
         * bh はページ番号、bl は色、の指定にそれぞれ使うらしいです。
         */
        movb    $0x0e, %ah
        movw    $(msg_end - msg), %cx   # 文字列の長さを cx に入れます。
        movw    $(msg), %si             # 文字列の開始アドレスを si に入れます。
puts:  
        lodsb                           # %ds:(%si) から1バイト
                                        # 取り出して al に入れます。
        /* lodsb の代わりにこうしても同じです (生成される機械語は違います):
        movb    %ds:(%si), %al
        inc     %si
         */
        int     $0x10                   # BIOS 呼び出し。
        dec     %cx                     # カウンタをデクリメント。
        jnz     puts                    # カウンタがゼロでなければループ。

        hlt                             # CPU を待機状態にします。でないと、
                                        # 下の "Hello, world!" を機械語として
                                        # 実行してしまいます。
msg:   
        .string "Hello, world!"
msg_end:

/* ブートフラグ */
        . = _start + 510                # ここは開始アドレスから 510 バイト目。
        .word   0xaa55                  # これがブートフラグらしいです。

ビルドに使う Rakefile も作ってみました。バイナリを作ったあと、フロッピーイメージを作ります:

task default: "hello.img"

FLOPPY_SIZE = 1440 * 1024
BLOCK_SIZE = 512

file "hello.o" => "hello.S" do |t|
  sh "as #{t.source} -o #{t.name}"
end

file "hello.bin" => "hello.o" do |t|
  # MBR は 0x7c00 にロードされます
  sh "ld -Ttext 0x7c00 --oformat=binary #{t.source} -o #{t.name}"
end     

file "hello.img" => "hello.bin" do |t|  
  # まずゼロフィルした空のフロッピーイメージを用意します
  sh "dd if=/dev/zero \
         of=#{t.name} \
         bs=#{BLOCK_SIZE} \
         count=#{FLOPPY_SIZE/BLOCK_SIZE}"
        
  # フロッピーイメージの先頭に hello.bin を書き込みます
  File.open t.name, "r+b" do |f|        
    f.write File.binread(t.source)
  end   
end                                     
                                        
task :clean do
  rm %w(hello.o hello.bin hello.img)
end

これが VirtulBox での実行結果です:

Hello, world!

すばらしい!

ところで、上のソースコードで “Hello, world!” を機械語として実行する、と書きましたが、実際この文字列は機械語としてどんな意味があるんでしょう? objdump で調べてみました。

対象は以下のソースコードから作られたバイナリです:

.code16
.text
        .globl _start
_start:
        .string "Hello, world!"

下が objdump -m i386 -b binary -D で逆アセンブルした結果です:

string.bin:     file format binary


Disassembly of section .data:

00000000 <.data>:
   0:   48                      dec    %eax
   1:   65                      gs
   2:   6c                      insb   (%dx),%es:(%edi)
   3:   6c                      insb   (%dx),%es:(%edi)
   4:   6f                      outsl  %ds:(%esi),(%dx)
   5:   2c 20                   sub    $0x20,%al
   7:   77 6f                   ja     0x78
   9:   72 6c                   jb     0x77
   b:   64 21 00                and    %eax,%fs:(%eax)

何してるのか全く分かりません…。そしてこの後、どこかのメモリ領域に闖入するわけですね。恐ろしい。

Hello, world! がマスターできたところで、BIOS を使ってディスクの内容を読み込んだり、VGA でお絵かきしたり、ということができたらまた面白いかも知れません。何だか昔のマイコンみたい。PC って OS があって当然、みたいに思い込んでたので、新鮮な気分です。

(コウヅ)

Node.js の process.nextTick でスタックオーバーフロー!

今 JavaScript がアツいですね!Node.js や MongoDB なんかのおかげでしょうか?AWS も CloudFormation だの IAM だののコンフィグを見る限り JSON を活用しているようですし、JavaScript はやらなきゃ損損!

と勢い込んでますが、Node.js というやつは非同期という特徴を押し出しててちょっと取っ付きにくいような気がします。長い処理をする時は、時々 process.nextTick を呼び出して他の処理に制御を移すのがマナーらしいんですが…。

function add(a, b) { return a + b; }

/* Array.reduce の非同期版のようなものです。
 */
Array.prototype.myReduce = function(f, cb, n) {
    if (typeof n == "undefined") {
        n = 1;
    }

    // this (配列) をコピーします。
    ary = JSON.parse(JSON.stringify(this));

    // ary に対して n 回分 f を適用し、残りは process.nextTick で次回に
    // 持ち越します。
    function subMyReduce() {
        for (var i = 0; i < n; i++) {
            if (ary.length == 1) {
                return cb(null, ary[0]);
            }

            var a = ary.shift();
            var b = ary.shift();
            ary.push(f(a, b));
        }

        process.nextTick(subMyReduce);
    }

    subMyReduce();
}

var xs = [];
for (var i = 0; i < 30000; i++) {
    xs.push(Math.floor(Math.random() * 5));
}

xs.slice(0, 1000).myReduce(add, function(err, result) {
    console.log(result);
});

上の myReduce, 内部ループの n が 1 の時、対象の配列の要素数が 1,000 くらいなら問題ないですが、それより多いとスタックオーバーフロー (RangeError: Maximum call stack size exceeded) になってしまいます。

どうしたものかと Google 検索してみると、process.nextTick の代わりに setTimeout を使うといいようです:

        setTimeout(subMyReduce, 0);

が、劇的に遅くなってしまうのが玉に瑕。
前述の条件で process.nextTick を使うと実行時間は 15 ミリ秒くらいですが、setTimeout にすると一気に 2,000 ミリ秒くらいに…。

それならばと process.nextTick と setTimeout を組み合わせてみました:

Array.prototype.myReduce = function(f, cb, n) {
    if (typeof n == "undefined") {
        n = 1;
    }   

    // this (配列) をコピーします。
    ary = JSON.parse(JSON.stringify(this));

    var j = 0;

    // ary に対して n 回分 f を適用し、残りは process.nextTick で次回に
    // 持ち越します。
    function subMyReduce() {
        for (var i = 0; i < n; i++) {
            if (ary.length == 1) {
                return cb(null, ary[0]);
            }

            var a = ary.shift();
            var b = ary.shift();
            ary.push(f(a, b));
        }

        if (++j < 1000) {
            process.nextTick(subMyReduce);
        } else {
            j = 0;
            setTimeout(subMyReduce, 0);
        }
    }

    subMyReduce();
}

これなら要素数 80,000 でも 200 ミリ秒くらいで実行完了しました。
要素数がこれより大きいとハングアップするようですが、また別の問題かもしれません。

ともかく、気配りするのも大変です!

(コウヅ)

脆弱性対策

Heartbleed やら Shellshock やら POODLE やら、最近ビッグな脆弱性がたくさん出てきたように感じますね。おさらいしましょう。

Heartbleed

Heartbleed は OpenSSL の実装上の脆弱性です。OpenSSL は SSL/TLS で通信するためのライブラリで、Apache の mod_ssl を始め、色んなソフトウェアが依存しています。脆弱性の内容は、OpenSSL が malloc で取得したメモリ領域を初期化せずに使うもんだから、無関係なセッションのデータが盗み見できちゃうというもの。ショッピングサイトの Web サーバーに Heartbleed 攻撃すれば、クレジットカード情報とか見れちゃうかもしれません。

Shellshock

Bash の実装上の脆弱性です。環境変数に “() { :;}; <任意のコード>” をセットして bash を起動すると、<任意のコード> が実行されてしまいます。Perl なんかで書いた CGI スクリプトの中で bash を利用したスクリプトを呼び出すとアウトです。例えば HTTP_USER_AGENT 環境変数は自由に設定できるからです。bash をアップデートするか、WAF でフィルタリングしましょう。

POODLE

SSL 3.0 の Cipher Block Chaining というモードに存在する設計上の脆弱性です。何と、暗号を解読されてしまいます。Apache での対策は簡単で、mod_ssl で SSLv3 を無効化するだけです。SSLProtocol ディレクティブを使います (SSLProtocol all -SSLv2 -SSLv3)。すると、TLS 1.0 以上の安全なプロトコルだけが使用されます。SSL が全滅したので、今後は TLS という用語に一本化される…のでしょうか。

(コウヅ)

頭の体操!

cron に 15 分置きに実行するようなジョブを設定する時、毎時 0, 15, 30, 45 分じゃなくて、微妙にずれた時間、例えば 3, 18, 33, 48 分を指定したい時があります。0 分スタートなら */15 という風に指定できますが、3 分スタートなら 3,18,33,48 と書かないといけないので若干面倒です。況して 5 分おきなど。

こういうちょっとした仕事はスクリプトを書いて解決しましょう!Perl (どんなディストリビューションでもデフォルトで入ってる…はず) で素直に書くとこんな感じでしょうか:

#!/usr/bin/perl

use 5.016;

my $step = shift @ARGV;
my $rand = int rand $step;

! $step || 60 % $step and die "60 が割り切れる数を指定してね!";
my $i;
for ($i = $rand; $i < 60 - $step; $i += $step) {
  print "$i,";
}
say $i;

コマンドライン引数に 15 を渡すと、こんなような出力が得られます: 9,24,39,54

何だかプログラミングの教本みたいです。ちょっと考え方を変えて、関数型言語みたいにリストありきで書いてみましょう:

#!/usr/bin/perl

use 5.016;

my $step = shift @ARGV;
my $rand = int rand $step;

! $step || 60 % $step and die "60 が割り切れる数を指定してね!";
say join ',', map {$_ * $step + $rand} (0..(int(60 / $step) - 1));

うまく動くとちょっと感動ものです。
が、Python や PHP は map を使うまでもなく range 関数で一発、という悲しい現実。

(コウヅ)

スイッチも疲れてる

某メーカー製の PoE スイッチから白煙が上がりました。数時間電源アダプタを抜いて休止させ、それから電源アダプタを差し込んだところでした。

どうもレギュレータなる部品が犯人のようです。夜はエアコンを切ってしまう環境だったので、疲れがたまってたんでしょうか。南無阿弥陀仏。

a

※ DC ジャックの右のチップが煙を噴きました。

「サーバーエラーのふり」を見抜く方法?

政治家の橋本聖子さんの事務所のサイトが「アクセス集中」によるサーバー負荷が原因で閲覧できなくなっているという話題を見つけました。

こちらのツイートが発端のようです:https://twitter.com/nyupy/status/502395108151394304

もう指摘された方もきっといらっしゃるとは思いますが、一応。HTTP レスポンスが返って来たから真正のエラーではない、と判断することはできません。エラーかどうかはレスポンスボディではなくてステータスコードで判断することになっています。

Web アプリケーション側でエラーが発生して処理を打ち切ったら、HTTP サーバー側で適切なレスポンスを返すこともあるでしょう (500 Internal Server Error 等)。あるいは (もしあれば) ロードバランサ側で 502 Bad Gateway のようなレスポンスを返すこともあるでしょう。レスポンスボディの内容はもちろんカスタマイズできるので、ステータスコードを見ないことにはエラーか否か判断できません。

じゃあ橋本聖子さんの事務所のサイトの HTTP レスポンスはどうなんだと言うと…

HTTP/1.1 200 OK
Date: Fri, 22 Aug 2014 01:14:59 GMT
Server: Apache/1.3.42 Ben-SSL/1.60 (Unix) PHP/4.4.9 with Suhosin-Patch
Last-Modified: Wed, 20 Aug 2014 14:53:19 GMT
ETag: "3f8f891-245-53f4b65f"
Accept-Ranges: bytes
Content-Length: 581
Connection: close
Content-Type: text/html; charset=shift_jis

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis" />
<title></title>
<style type="text/css">
.center {
	text-align: center;
}
</style>
</head>

<body>
<p class="center">&nbsp;</p>
<p class="center">&nbsp;</p>
<p class="center">只今アクセスが集中しており、ページを表示することができません。
<p class="center">しばらくたってから、アクセスいただきますようお願いいたします。
</body>
</html>

1行目がステータスコードです。200 OK は「何ら問題なく正常にリクエストを処理した」ことを示します。なので、「只今アクセスが集中しており、ページを表示することができません。」というのはちょっと違います。でも、このページを表示しないと本当にサイトが過負荷で火を吹く可能性はあるでしょう (どのようなサイト構成なのかによりますが)。

レスポンスヘッダで他に注目したいのは、Server: Apache/1.3.42 Ben-SSL/1.60 (Unix) PHP/4.4.9 でしょうか。なかなかクラシックなサーバーです。Apache 1.3.42 リリースのアナウンスがポストされたのは Wed, 03 Feb 2010 00:03:34 GMT、php 4.4.9 は 07 Aug 2008 です。

Last-Modified はこの日時に index.html が編集された、ということです。なぜ index.html かというと、デタラメな URI をリクエストすると index.html にリダイレクトされ、上の HTML が返ってくるからです。転送には mod_rewrite (あるいは mod_alias) を使っているんでしょう。なお、Apache 1 系列の mod_rewrite の正規表現は文字クラスの省略記法が使えない等、注意が必要なようです:http://stackoverflow.com/questions/1223754/diff-in-mod-rewrite-on-apache-1-3-and-2-2

さて、これは他人事ではありません。自社の顧客のサイトが炎上して似たような対応を求められることもあるかもしれません。どうすればもっともらしいレスポンスが返せるでしょう?ということで、Perl の PSGI を使ってウソエラー生成器を作ってみました:

# vim: syntax=perl:

use 5.020;
use utf8;
use Encode;

my $app = sub {
  my ($env) = @_;

  [
    503,
    [
      'Server' => 'Apache',
      'Content-Type' => 'text/html; charset=UTF-8',
    ],
    [encode('utf8', <<EOF )],
<!doctype html>
<html>
  <head>
    <title>503 Service Unavailable</title>
  </head>
  <body>
    <h1>503 Service Unavailable</h1>
    <p>ただいまアクセスが集中しているため、サイトが表示できません。</p>
  </body>
</html>
EOF
  ];
};

上のスクリプトを Starman 等の PSGI 対応の HTTP サーバーで動かします (シェルで starman foo.psgi を実行するだけ)。Starman にアクセスするとこうなります:

HTTP/1.1 503 Service Unavailable
Server: Apache
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 22 Aug 2014 02:09:24 GMT
Connection: keep-alive

fb
<!doctype html>
<html>
  <head>
    <title>503 Service Unavailable</title>
  </head>
  <body>
    <h1>503 Service Unavailable</h1>
    <p>ただいまアクセスが集中しているため、サイトが表示できません。</p>
  </body>
</html>

0

どんなリクエスト URI を指定しても上のようなレスポンスが返ります。ウソエラーを返すという目的には好都合です。が、レスポンスヘッダはもうちょっと凝った方が良いかも?あと、POST リクエストには違うエラーを返したり、リクエストヘッダに Accept 等がなければ反応しなかったり、そういう工夫もしないと「本物らしさ」が出ないかもしれません。

ともあれ、こういう事態には巻き込まれたくないものです。しみじみ。

(コウヅ)