パスワードを忘れた? アカウント作成
465796 journal

messier42の日記: argv[0]は何をさしているか? 5

日記 by messier42
C言語のmain関数の引数main(int argc, char argv*[])はプログラムを実行した時の
コマンドライン引数であることは賢明な/.の読者ならご存じだろう。

さて、daemonのプログラムを読んだ・書いた読者ならご存じかと思うがこのargvなので
あるが、上書きする事が出来る。例えばこんなふうにだ。

---
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    strcpy(argv[0], "./b.out");
    sleep(180);
}
---

このプログラムになんの意味があるのだろうか? 引数を上書きする事になんの意味が?

実はあるのだ。「ps x」等で表示されるコマンド名が書きかわる!

% gcc tmp.c
% ./a.out &
% ps x (補足 CentOS5.1では無引数のpsでは駄目なようである)

さて、これは何に使うのかというと、例えばdaemonのプログラムで、現在の動作状態等を
表すのに使っているようである。例えば休止状態の場合は「daemon sleep」等である。
Linuxシステムではargv[0]を書き換えると/proc/PID/cmdlineが書き換わるので確認
してみると良い。また引数を与えた場合はどのような挙動になるか調べてみるのも面白い
だろう。

次に、mainには第三の引数がある事をご存じだろうか?

---
int main(int argc, char *argv[], char *env[])
{
      ....
}
---
第三の引数envは環境変数が格納されている。argvは配列の大きさはargcで渡されるが、
envは最終配列がNULLである。

さて、C言語のこのようなmain関数の仕様に、不安間を覚えた読者は居ないだろうか?
C言語のmain関数は少なくとも3種類のプロトタイプ宣言が存在するのである。

--
int main(void);
int main(int argc, char *argv[]);
int main(int argc, char *argv[], char *env[]);
--

本来3引数の関数であるのに、2引数で宣言したり、無引数で宣言したりすると、引数が正
しく渡されず、メモリエラーになりそうなものである。

でも、メモリエラーにはならないんですよね。さてなぜでしょう? (仕様という答えは不可)
この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
  • エキスパートCプログラミング―知られざるCの深層 (Ascii books) (単行本) [amazon.co.jp]

    いや、実は仕様だから。

    仕様として「引数はスタックアルゴリズムを使って、後ろの引数が省略可能であるような順序で積み上げる事」になっているから。つまり一応 main(argc)も作れるには作れる。意味があるとは思えないが。main()を呼ぶ側は、必ず main(argc,argv,env)のように呼び出す必要がある。

    本当に Stack に積み上げてもいいが、スタックが無いプロセッサと言うものもこの世にはあり、そういう環境用のCコンパイラも存在するので「スタック上にこう積み上げているから」という答は正解ではない。
    # だいたい、それだとレジスタ渡しの場合の説明になっていない。
    # あくまでも「スタックアルゴリズム」に従っているのだ。

    .

    さて、では私から質問。

    unix のシステムコールにはほぼ確実に open(2) が存在する。しかし open(2) の宣言は2種類ある。

    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);

    もちろん、呼ぶ側に両方の宣言があるのはいいとしよう。適切なものを選べばそれでいい。では、「呼ばれた側」は mode の有無をどのようにして判別しているのでしょうか?
    --
    fjの教祖様
    • いやはや正解だと思っていた、スタックという答えは見事に粉砕されました。
      引数のレジスタ渡しというのがありましたね。

      openについてですが、fopenしか使った事が無かったので戸惑いましたが、
      マニュアルを読むと、

      http://www.linux.or.jp/JM/html/LDP_man-pages/man2/open.2.html [linux.or.jp]

      printfとほぼ同じ実装のようですね。可変長引数の関数として定義されて
      いて、第二引数flagでO_CREATが指定された場合のみ、第三引数modeが
      参照される。glibcのソースコードでも確認しましたが、そのようになって
      いました。
      親コメント
      • printfとほぼ同じ実装のようですね。可変長引数の関数として定義されて
        いて、第二引数flagでO_CREATが指定された場合のみ、第三引数modeが
        参照される。

        その通りです。が、ここに問題があるのは見つけましたか?

        glibcは通常の関数コールからシステムコールへと変換するライブラリの所で、O_CREATチェックをして mode を0で渡すか、第3引数のある『はずの』所からデータを取ってくるかを決定しますよね?

        呼び出し側が O_CREATオプションつきで open() を呼んでおきながら、modeを渡し忘れた場合、mode にはゴミが入ってしまいます。

        つまり、「呼ばれた側は、厳密には mode の有無をチェックしておらず、ここに問題が起こる余地がある」って事です。誰だ、こんなのをPOSIX標準とした奴は…。拡張するなら creat(2)の方に「存在した場合の処理」を埋め込めばいいものを…。
        --
        fjの教祖様
        親コメント
        • それでしたら、openよりscanf/printfの方が深刻ではないですか? openは滅多に使わないし。私はopenを使った記憶が無い。いや、ソケットプログラムの習作で使ったかな?

          scanf/printf達はもうちょっとどうにかならんのかというような関数達です。gccなら文字列部分を解析してくれる機能もありますが、文字列定数として与えられている場合だけですしね。
          親コメント
          • scanf(3), printf(3)とopen(2)の違いは…物凄く乱暴に言うとprintf(3)達は「ライブラリ」に過ぎない。悪影響を与える範囲はそのプロセス内にとどまっている。

            ところが、open(2)は「システムコール」。つまり、glibcのopen()関数は、その引数を「カーネルに」引き継ぐ。「creat(2)時のmodeなんて被害はタカが知れている」かもしれないが、その被害は kernel の制御範囲全体に広がってしまう。ここが被害が甚大なポイント。

            .

            open(2)自体は…ようするにFILE構造体がもたらす「副作用」が困る場合に使います。同期書き込みがやりたいとか、ファイル開く程度でいちいちmalloc()して数メガもバッファとるな、どうせこちとら1kbyte程度も書かねぇ、とか…そういう場合。

            あと「排他的にファイルを開きたい」場合。flags に (O_CREAT|O_EXCL) が設定してあると「もし、そのファイルが無かったらファイルを作りなさい。そして排他的に開きなさい」と言うことになります。つまりそのファイルが存在している間は誰もそのファイルを作り直せないし開けない。いちいち共有メモリ等を作らずにプロセス間排他制御をしたいときに使いますが、これは fopen(3)では作れない。

            .

            ちなみに。
            ps の技。全てのunix系で使えるわけではありません。当然非unix系ではもっと使えない可能性が大きいです。

            なによりも argv[0]の先にあるメモリ領域のサイズによっては、buffer overflow を起こしかねません。起動時のファイル名が"abiraunkensovaka"とか長くて、statusが「0001」程度の長さならOKですが、逆の場合は危険なので、一般的な利用はやめましょう。100時間動かしたあとにこいつのせいでSIGSEGVを食らったときの衝撃は、忘れたくても忘れられないものです。
            --
            fjの教祖様
            親コメント
typodupeerror

普通のやつらの下を行け -- バッドノウハウ専門家

読み込み中...