パスワードを忘れた? アカウント作成
この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。

むむう」記事へのコメント

  • static char const str[] = "afo";

    str[ 0 ] = 'x';

    とか?
    でも実際に動かしてみると、
    Segmentation fault
    だって。
    • 仰っている意味が(素で)いまいち良く分かりません orz

      けど、とりあえず(ここ大事)ソースに書いてコンパイルしてみたところ
      コンパイラさんは
          警告: 代入が読み込み専用領域で行われました

      で、実行させたら
          セグメンテーション違反です

      ですって。

      でも本当にどうして書き込み禁止領域って分かるんですか?
      いつ頃/どのようにしてそういう知識を身に着けられたの?

      (日記じゃなければなかなか聞けないので質問厨でごめんなさい)
      親コメント
      • by hix (3507) on 2004年05月07日 22時50分 (#542773) 日記
        static char const str[] = "afo";

        char *str = "afo";
        としても結果は同じでした。
        コンパイルで警告が出ないところは違いますが。
        # コードとしてタチが悪いものの今回の現象が解りやすいのは後者かも知れない

        で、この "afo" っていう文字列は「文字列定数」なのです。"afo"は"afo"として使わねばならず、"xfo"とは出来ません。もしそういう使い方をしたいなら、
        static char const str[] = "afo";
        char buff[ 4 ];
        strncpy( buff, str, sizeof( buff ) );
        buff[ 0 ] = 'x';
        とします(書き換え可能な領域は別に用意する必要があります)。

        ところで、メモリに乗っかっているデータは、以下のような分類が出来ます。
        そして、プログラムが動作中は、この分類で示したデータごとに分けてメモリが使われ、各メモリ領域にはファイルシステムのパーミッションのように読み取り専用とか読み書き可能などの属性が付けられます。(処理系によっては区別が曖昧だったり異なったり、そもそも私の説明に抜けがある可能性があったりしますが)。
        • コード(書き換え不可)
          用途:プログラム本体やライブラリ

        • スタック(書き換え可)
          用途:int a;等で宣言したローカル変数や関数の戻り先アドレスなど

        • スタック以外の作業領域(書き換え可)
          用途:static int a; で宣言したスタティック変数や、関数外で宣言したグローバル変数や、malloc()で確保したバッファなど

        • 定数(書き換え不可)
          用途: const 宣言した配列や、文字列定数など

        • よそのプロセスの所有物で「共有で使ってヨシ」と言っていないメモリ領域(当然書き換え不可。読んでもダメ)

        コード書き換え可能なら実行時に乗っ取りし放題だし、int a; と宣言した変数が書き換え出来なければ意味無いしmalloc()で確保したバッファに何も書けなきゃ使い物にならないし。

        というわけで、最後に残った「定数」ですが、プログラム実行中は値が書き換わらないデータです。
        より正確に言うと、コンパイラが暗黙に「定数」と理解したか、ソースで明示的に「定数」である宣言をされたデータです。

        CPUのレジスタに直接代入可能なint型の程度の大きさであれば、実行時に例えば「レジスタEAXに120を代入する」という具合のコードになるのですが、配列となるとそうもいきません(文字列も配列です)。
        ですので、メモリ領域に置いておいて、それをポインタで参照するようなコードとなります。値が書き換わらないのでプログラムから見て読取専用な領域で良いわけです。
        読取専用とすることで、書き換えようとした時に強制終了させることが出来たり(今回の現象)、書き換えを無視して値の不変を保証したり、(コンパイラの最適化によって)同じ内容の定数を一つにまとめたり、ROM化するような時にRAMに移す必要が無いというメリットがあります。
        親コメント
        • 私の中で、C言語の教科書とアセンブラの教科書とマイコン&DSPの教科書の
          内容がつながりそうです!
          (ビミョウな表現)
          #.textとか.bssとかいうアレね

          自分で解ってることを説明するのって難しいです、
          それを日記のコメントでこんなに丁寧に(マニヤックに (笑))
          教えていただけて

          ああ私って幸せだなあ。ありがとうございます。
          親コメント
        • まぢ説明だとそんな感じですね~。一つチャチャを入れさせてもらうなら、
          s/よそのプロセス/アクセスに特権が要るリソース/てなもんでしょうか。
          よそのプロセスはそもそも見えないし。
          # マニヤックな記述で文字列定数使うなら*"" = 'x';とかですかね。
          親コメント
          • by hix (3507) on 2004年05月10日 21時25分 (#544592) 日記
            ご指摘ありがとうございます。
            仰せのとおり、他プロセスのメモリは見えませんね。
            メモリディスクリプタテーブルのエントリが異なるので。
            # 8086→80286→80386の進化ではこのメモリディスクリプタテーブルの辺りが一番面白いと思います。
            親コメント
            • 無理矢理書き込もうとしても、メモリプロテクション様のお怒りを
              買うという意図を達することができそうな気がするんですが
              どうなんでしょう?

              なんとなーくWebを眺めていたら、VxWorksさんがバージョンアップして
              いままで苦手だったメモリプロテクションなどを手がけるようになった
              みたいですね。セミナに行くとサンプルコードが貰えそうな勢いですが、
              会社に転がっている各種の評価用OSで試すことが出来たら面白いかもねー、
              なんて妄想中です。

              日記に書いたように、スレッドを複数生成してお互いに書き込み合い
              って思ったのですが、(弱々しく自信なさげな声で)スレッドってメモリ領域
              共有だから、無茶書きするならプロセス越えてやればいいのかしら?
              って思ってみたり。

              > 8086→(略)→80386の進化ではこのメモリディスクリプタテーブルの辺りが一番面白い

              ・・・そ、そういうものなんですか?
              親コメント
              • というのが、コレ [srad.jp]なわけですわ。

                それでもって、見えない部分に書くというのが、「他プロセスの持ち物だけど、自分から見えていないと思われる場所にあてずっぽうで書く」 というのであれば、絶対に無理です。
                自分のプロセスのメモリ空間(論理アドレス)にマッピングされていない領域なので。

                というわけで「メモリディスクリプタテーブル」が避けて通れなくなりました。
                # さすがに、コレを直接見たりいじったりする用が無いので、正しく説明できるかどうか怪しいのですが…
                # 間違ってたらバシバシ指摘してください>みなさま

                大雑把に言って、どの論理アドレスに何があるか?という一覧(とその属性)が「メモリディスクリプタテーブル」です。
                物理メモリだったり、スワップされたディスク領域だったり、オープン済みのファイルだったり…
                # まぁx86アーキテクチャから言うと、物理メモリかそれ以外かはCPU側の区別で、それ以降はOS側の区別になるのですが
                80286はどうだったか忘れましたが80386以降は「メモリディスクリプタテーブル」はプロセスごとに別のテーブルを持ちます。

                8086だと、「セグメント:オフセット」であくまでも物理メモリのアドレスしか指し示せませんでしたが、 80286以降は「セレクタ:オフセット」でOSが管理しているリソース(物理メモリや、スワップ領域や、ファイルの内容)を指し示します。
                「セレクタ」とは「メモリディスクリプタテーブル」(という配列の)、添え字です。

                C言語風にいうと、8086でポインタでメモリをアクセスしていたのが、80286以降ではポインタのポインタでアクセスする、ということになります。
                プロセスのメモリアクセスでは、最初に必ず「メモリディスクリプタテーブル」を参照するので、 物理メモリの中でも、アクセスすると具合の悪いことが起きそうなエリア(よそ様のプロセスのメモリなど)は、 「メモリディスクリプタテーブル」に書いておかなければ良いのです。

                というわけで、どーやってもよそ様のプロセスの持ち物に手を出す事は出来ないのです。
                # 「メモリディスクリプタテーブル」を書き換えればそれも可能ですが、ほぼ間違いなく特権が必要のハズです。

                で、メモリプロテクションでオチるというのは何か?というと、 「メモリディスクリプタ」を参照した結果、物理メモリがある領域なら、(読み取り専用の所を書こうとしない限り)何の問題も無いのですが、 そうではない領域だと、OSに処理がゆだねられます。
                # CPU的には物理メモリが無い場所へのアクセスとか言う無茶で手におえない要求を、OSに押し付けてしまうわけです。
                OSは“どこのアドレスに何をしようとしたのか”を見て、正常な処理の場合であれば、どこかから空きメモリを探し出してきて割り付けて値を設定したり、 アクセスされた内容をディスクに書き戻したりしますが、OSにも覚えが無いようなアドレスだったりすると、プロセスにシグナルを送りつけるわけです。
                親コメント
              • IA32では、メモリ空間が何種類かあって、
                        1.セグメント内空間
                        2.リニアアドレス
                        3.物理アドレス
                みたいになってます。
                # 286ではページングできないので2と3は一緒です。

                セグメントは1-2間の変換に使います。なのでセレクタ:オフセットの組で指
                定できるのは2のリニアアドレスです。

                2-3の変換にはページテーブルを用います。具体的にはCR3でページテーブル
                の集合であるページディレクトリの物理アドレスを指定してページテーブル群
                を指定します。んで、このページテーブル群がプロセス空間になります。
                # ページテーブルの指定はページディレクトリでページテーブルのリニアア
                # ドレスを指定します。この辺は厄介でおもしろいところです。

                プロセスを切り替える際にはページテーブル群をまるごと切り替えてしまうの
                で、よそのプロセスの持ちものは文字どおり見えなくなります。もっとも、カ
                ーネルから実メモリが全部見えるようにマッピングすることも多いのでカーネ
                ルによっては絶対に見えないわけでもありません。が、そこは当然触るのには
                特権が必要な領域なわけです。

                で、IA32では、
                        a.セグメントの属性/特権
                        b.空ページ/特権
                の2つの違反ができます。変な表現ですが。

                *"" = 'x';みたいに文字列に書き込むというのはaに違反します。
                *(int*)hoge = 0;はhogeの値によりますが、セグメント違反な場所ならa
                セグメントがOKでページが割り当てられてないところならbの違反ができます。
                親コメント
              • by hix (3507) on 2004年05月11日 17時38分 (#545355) 日記
                昔に文献を読んだ記憶で書いたので、抜けがあったり違ってたりしていました。
                補足ありがとうございます。

                80286と80386でアドレス変換の手順が一つ増えたハズだと思っていたのですが、それがページテーブルだったのですね...思い出してきました。
                # 昔本を読んだ時にも、その辺りの関係がちっとも理解できず、苦労した記憶が…
                # 「ディスクリプタ」とか「ディレクトリ」とか、ファイルシステムで同じ名前を持つ物と概念的な部分で混同してしまったり、
                # アプリケーションで使っているアドレスを物理アドレスに変換してみようとした時に、今見ているテーブルが何のテーブルだか解らなくなったり…

                昔はWin95でその辺りの物をいじったので、今度はLinuxで見てみることにします。
                親コメント
              • 今そのあたりをやってるだけだし。
                親コメント
      • by Anonymous Coward
        > いつ頃/どのようにしてそういう知識を身に着けられたの?
        こーゆー知識ってやってるうちに気づいたり、教えてもらったり
        して自然とみにつく気がします。

        あえて書籍で勉強したいなら
        http://www.amazon.co.jp/exec/obidos/ASIN/4274132072/250-0525562-7569831
        プログラミングの力を生み出す本―インテルCPUのGNUユーザへ

        は入門としては読みやすいかも。

Stableって古いって意味だっけ? -- Debian初級

処理中...