今更セグメントにはまる

頭では理解していたつもりでも、実際やってみて、初めて理解出来る物事は多い。
プログラミングなんて、その最たる物だと思う。
それさえ、分かっていたつもりでも、何度となく身に沁みる経験をする。


今回も、正に、そのような経験である。
x86のメモリモデルは、セグメント方式のお陰で、いくつかのパターンが存在する。
一番単純で分かり易いのは、CS=DS=SSとして、全て同一のセグメントに納めるパターン。
多分、このパターンで事足りている間は、それほど悩まないような気がする。
現に、自分は、しばらくの間、何の不便も不満も感じず、プログラミングを進めていた。


ところが、指定されたメモリ領域をダンプしようと、ごにょごにょいじり始めた途端、
今まで問題なく動いていたコードが、突然、おかしな動作をし始めた。
原因が分かってしまえば、当たり前の話だが、
この指定されたメモリ領域を参照する為に、DSに指定されたセグメント値を放り込んだのが悪かった。


オフセット値をBXレジスタにセットし、

MOV     AL, [BX] 

みたいな感じで参照したかったのだが、
データの参照は、暗黙、DSが指定するセグメントに対して行われるわけなので、
単純にDSに指定されたセグメントをセットする方法が自然だと思う。
ところが、ここでDSの値を変更してしまうと、
以降、CS=DS=SSを前提として書かれたコードは、当然であるが、正しく動作しない。


では、どうする?
最初に考えたのは、各サブルーチンで、前提とするDSの値を、明示的に指定する方法。
つまり、

PUSH    DS
MOV     AX, CS
MOV     DS, AX

       :
       :

POP     DS

しかし、このサブルーチンに、例えば、スタック領域のデータを渡したらアウト。
いや、CS=SSだったら、まぁ、問題ないわけだけど、
そういう前提のコードをなくしたいから、どうすべきか考えているわけで、
新しい前提が増えちゃ意味ないわな。
と言うか、出来れば、今まで動いていた部分は、いじりたくないな。


じゃ、今まで動いていた部分は、そのままにして、
参照の間だけ、DSを変更する方法。
つまり、

PUSH    DS
MOV     AX, [指定されたセグメント値]
MOV     DS, AX

MOV     AL, [BX]

POP     DS

動かない事はない。
ただ、どうなんだ?
この参照がループの中とかで処理されていたとしたら、
何回余計なPUSH,POPを実行する事になるんだろう?
なんとなく、美しくないし。


で、結局、こうなった。

; ループ前にセットする
MOV     AX, [指定されたセグメント値]
MOV     ES, AX
        :
        :
; ループの中では
MOV     AL, [ES:BX]

なるほど、セグメントオーバーライドって、こう使うのね。
なんて、本当に今更なんだけど、
試行錯誤して、悩みあぐねて、必要に迫られて、初めて、心底納得なのですよ。
いくら、本やネットで調べても、頭の中だけじゃ、やっぱり、知っているだけ。
いや、僕がおバカなだけかもしれないけれど...


しかし、あちこちで散見する「セグメント方式は、なかなかに面倒である」といった評価は、
自分が思っていた以上に面倒であるという事なのではないかと感じ始めている。
現状、CS=DS=SSとしているから、ハンドアセンブル的な感じでも問題ないけれど、
CS<>DS<>SS(CS<>SS)なんて状況になったら、多分無理だな。
ようやく ASSUME の存在意義も分かってきた気がする。