ぐだぐだ低レベルプログラミング(245)x86(16/32bit)、DOS4GWセグメント続

Joseph Halfmoon

前回は「プロテクトモード」に忍び込み、古き時代のDOSエクステンダ「DOS4GW」のデフォルトセグメントの属性を観察。しかし至らぬ点が2つあり。プロテクトモードでは「許されねえ」アクセス等ままあり、前回はその辺はパス。もう一つはアクセス権は観察したもののセグメント・リミットは無。今回はその辺をテコ入れっす。

※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら

※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
LAR命令とLSL命令

プロテクトモードのセグメントのあれやこれやを調べるために使える命令が2つあり。

    • LAR命令、セグメントセレクタの値からデスクリプタに記述されているアクセス権をもってくる
    • LSL命令、セグメントセレクタの値からデスクリプタに記述されているセグメント・リミットの値をもってくる

前回はLAR命令だけを使ってみましたが、今回はLSL命令も使ってセグメント・リミットの値も吟味?してみます。

上記2つの命令は「のぞき見」しようとするお惚け老人にありがたい命令なのであります。通常、プロテクトモード下でよからぬことをたくらむコードを走らせると即イクセプションが発生し、制御が召し上げられてしまいます。しかし、上記の2命令に関しては、本来ダメなセレクタに作用させたとしても、不成功になるだけで例外は発生しないようです。なお成功はゼロフラグが立ち、不成功だとゼロフラグはクリアされます。

前回はDOS4GWがデフォルトでCS、DS、SSにセットしてくれているセレクタについてLARで属性を見たので、不成功になる筈が無かったです。しかし、テキトーなセグメント・セレクタを吟味しようとすると不成功になることも当然ありであります。今回はその辺を考慮して場合分けを追加。

また、LAR命令が成功したセグメントについてはそのリミットも読み取れる筈なので、LARにつづいてLSL命令も発行、セグメント・リミット値も吟味できるようにしてみました。

なお、セグメント・リミットにはちょいクセ強なところあり、20ビットまではバイト単位のセグメント・リミットを設定できるものの、グラニュアリティ・ビットを立てた場合、1ページ4Kバイト設定となり、20ビット幅のページ数で32ビット対応ということになっとります。しかしLSL命令でロードできるセグメント・リミット値は常にバイト単位です。つまり4Kバイト・ページ対応の場合は、セグメント・リミット値を12ビット左シフトした上で、下12ビットについてはオール1を立てた値を入れてくれます。

今回実験のOpen Watcom Cプログラム(DOS4GW用)

前回実験のコードのチョイ変っす。ただし、属性やリミットの読み取りが「ダメ」なセグメント・セレクタに対しても使えることを確かめるべく、ヌル・セレクタに対する動作確認を追加してます。

#include <stdio.h>

void printSelector(const char* nam, short sel)
{
    printf("Selector %s 0x%04X ", nam, (sel & 0xFFF)>>3);
    if ((sel & 0x4) == 0) {
        printf("GDT");
    } else {
        printf("LDT");
    }
    printf(" RPL=%d ",(sel & 0x3));
}

void printAR(const char* nam, long ar)
{
    printf("AR %s ", nam);
    if ((ar & 0x800000)!=0) {
        printf("4Kpg");
    } else {
        printf("Byte");
    }
    printf(" DPL=%d ",(ar & 0x6000)>>13);
    switch ((ar & 0xe00)>>9) {
    case 0:
        printf("R/O(U)");
        break;
    case 1:
        printf("R/W(U)");
        break;
    case 2:
        printf("R/O(D)");
        break;
    case 3:
        printf("R/W(D)");
        break;
    case 4:
        printf("E/O   ");
        break;
    case 5:
        printf("E/R   ");
        break;
    case 6:
        printf("E/O(C)");
        break;
    default:
        printf("E/R(C)");
        break;
    }
    if ((ar & 0x100)!=0) {
        printf(" ACCESS\n");
    } else {
        printf("\n");
    }
}

int dumpSelector(const char* nam, short sel) {
    static long _ar;
    static long _limit;
    static int _ok;

    _asm {
        .386p
        mov ax, sel
        lar eax, eax
        jnz  lbl_ng
        mov _ok, 1
        mov _ar, eax
        mov ax, sel
        lsl eax, eax
        mov _limit, eax
        jmp lbl_nxt
lbl_ng: mov _ok, 0
lbl_nxt:
    }

    printSelector(nam, sel);
    if (_ok != 1) {
        printf(", selector NG\n");
    } else {
        printf(", selector OK\n");
        printAR(nam, _ar);
        printf("Limit %s 0x%08X\n", nam, _limit);
    }
}

void main()
{
    short cs_selector, ss_selector, ds_selector, null_selector;

    null_selector = 0;

    _asm {
        .386p
        mov ax, cs
        mov cs_selector, ax
        mov ax, ss
        mov ss_selector, ax
        mov ax, ds
        mov ds_selector, ax
    }

    dumpSelector("null: ", null_selector);
    dumpSelector("CS: ", cs_selector);
    dumpSelector("DS: ", ds_selector);
    dumpSelector("SS: ", ss_selector);
}
実行結果

上記 pmseldmp.c ソースについて、下のコマンドラインを適用すると、WatcomCコンパイラが灰の中から蘇り、DOS/4GW上で実行されるオブジェクト pmseldmp.exe を生成してくれます。

wcl386 /l=dos4g /d2 pmseldmp.c

実行は普通にDOSプロンプトから。

実行結果が以下に(色枠はお惚け老人の書き加え)segmentAR_LIMIT

黄色枠のところ、セレクタ「0」はヌル・セレクタです。使えないセグメントを表すためのものなので、selector NGということで調査を却下してます。今回追加のダメなセレクタをはじくところは動いているみたいね。

そして緑枠のところがセグメント・リミット値です。前回全てのセグメントが4Kページ属性だったので薄々気づいてましたが、オール1、つまり4Gバイトのフラットでリニアな空間を指向しているみたいっす。まあ、メモリ管理のドライバは、セグメンテーションでなく、ページングでメモリ管理しているみたいだし当然か。

大分、手の内が分かってきた。ホントか?

ぐだぐだ低レベルプログラミング(244)x86(16/32bit)、DOS4GWのセグメント へ戻る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です