C での execve の使用

Abdul Mateen 2023年10月12日
  1. C の exec システム コール
  2. C の execve システム コール
C での execve の使用

このチュートリアルでは、execve を使用して、Linux 標準コマンドと C で実行可能ファイルの両方を実行する方法について説明します。

最初に、exec システム コールと exec のファミリについて説明します。 次に、C で実行可能ファイルを呼び出し、最後に、標準の Linux コマンドと実行可能ファイルの両方について説明します。

C の exec システム コール

exec システム コールは、実行中のプロセスを他の実行可能なプロセスに置き換えます。 実行中のプロセスのアドレス空間は、新しいプロセスのアドレス空間に置き換えられます。

新しいプログラムが同じアドレス空間にロードされることに注意することが重要です。 プロセス ID は同じままです。

新しいプログラムは独立して実行されます。 つまり、開始点は新しいプログラムのエントリ ポイントになります。 exec システム コールには多くのバリエーションがあります。

  1. execl
  2. execle
  3. execlp
  4. execv
  5. execve
  6. execvp

これらの関数は、同じベース exec の後に 1つまたは複数の文字が続きます。 余分な文字の詳細は以下のとおりです。

  1. e - ここで、e は環境変数用です。 この関数には、環境変数を指すポインターの配列があります。 環境変数のリストは、新しくロードされたプログラムに明示的に渡されます。
  2. l - ここで、l はコマンド ライン引数用です。 コマンドライン引数のリストを関数に与えることができます。
  3. p - ここで、p は環境変数のパスです。 この関数では、path 変数がファイルの検索に役立ち、新しくロードされたプロセスに引数として渡されます。
  4. v - ここで、v はコマンドライン引数にも使用されます。 ただし、この関数では、コマンド ライン引数がポインターの配列として新しく読み込まれたプロセスに渡されます。

基本的な exec システム コールでは、現在のプロセスのアドレス空間が新しくロードされたプログラムのアドレス空間に置き換えられます。 その結果、現在実行中のプロセスは終了します。 新たにロードされたプロセスは、このシステム コールの引数として渡されます。

新しくロードされたプロセスは、同じプロセス ID、同じ環境変数、および同じファイル記述子のセットを持ちます。 ただし、CPU 統計と仮想メモリは影響を受けます。

exec 呼び出しの構文

ここでは、基本的な exec システム コールの変種である 6つのシステム コールの構文があります。

int execl(const char* path, const char* arg, ...) int execle(
    const char* path, const char* arg, ...,
    char* const envp
        []) int execlp(const char* file, const char* arg,
                       ...) int execv(const char* path,
                                      const char* argv
                                          []) int execve(const char* path,
                                                         const char* argv[],
                                                         char* const envp
                                                             []) int execvp(const char*
                                                                                file,
                                                                            const char* argv
                                                                                []) int execvpe(const char*
                                                                                                    file,
                                                                                                const char* argv
                                                                                                    [],
                                                                                                char* const envp
                                                                                                    [])

まず、すべての関数の戻り値の型は int です。 ただし、操作が成功した場合 (つまり、新しいプログラムがロードされて置き換えられた場合) は、現在のプログラムが戻り値を受け取ることができないため、何も返されません。

何らかのエラーで失敗した場合、新しいプログラムはロードされず、既存のプログラムに -1 が返されます。

最初の引数では、パスとファイルに違いがあります。 pexeclpexecvp、および execvpe には、パスの代わりにファイルがあります。

path は、実行/ロードするファイルのフル パスを指定します。 file は、新しいプログラムのファイルを見つけるのに役立つパス名を指定します。

2 番目の引数の違いは、v を持つ関数は、複数の文字列 (ファイル名を含む) を持つ char 型の 2 次元配列を持つことです。

対照的に、他のプロシージャには char 型の 1つ以上の 1 次元配列があり、このリストの最初の要素には file 名が含まれ、2 番目の要素にはいくつかのパラメータが含まれる場合があります。

最後に、e を持つ関数の場合、3 番目/最後のパラメーターはポインターの配列として環境変数を持ちます。

exec システムコールのコーディング例

さらに議論する前に、例を見たほうがよいでしょう。 この例では、プログラムのソース コードをプログラムの入力として使用しています。

ここでは、このプログラムを実行可能コードのディレクトリーに (名前 execl0.c で) 保存しました。 これは、ソース コードと実行可能コードの両方が同じディレクトリに存在することを意味します。

#include <stdio.h>
#include <unistd.h>
int main(void) {
  char binaryPath[] = "/bin/wc";
  char arg1[] = "wc";
  char arg2[] = "-w";
  char arg3[] = "execl0.c";
  printf("First line of current program\n");
  execl(binaryPath, arg1, arg2, arg3, NULL);
  printf("Last line of current program\n");
  return 1;
}

上記のコードは、char* 型のいくつかの単純な変数のみを持つ execl システム コールを使用します。 最初の変数には (実行される) 新しいプログラムのパスと名前が含まれ、2 番目の変数にはパラメーター wc (再びプログラムの名前) が含まれます。

3 番目の変数にはパラメーター -w があり、コマンドを wc -w として実行して、ソース ファイル内の単語をカウントします。

2つの追加の print ステートメントに注意することも重要です。1つはシステム コールの前、もう 1つはプログラムの最後です。

出力:

First line of current program
32 execl0.c

出力は、新しいプログラムが正常にロードされて実行されたことを示しています。 ただし、最初の print ステートメントが実行されることに注意してください (出力の最初の行を参照してください ('First line of current program')。

新しいプログラムが正常にロードされると現在のプログラムが自動的に終了するため、最後の print ステートメントは実行されません。

出力の 2 行目は、ファイル execl0.c の単語数を示しています。

C の execve システム コール

ここで、execve 呼び出しについて詳しく説明します。

構文:

int execve(const char* path, const char* argv[], char* const envp[])

ここで、最初の引数は path です。 すでに説明したように、path 環境変数は、新しいプログラムとして実行するプログラムを見つけるのに役立ちます。

2 番目の引数は、文字の 2 次元配列、またはコマンド ライン引数のリストを持つ文字列の 1 次元配列です。

3 番目の引数は、文字の 2 次元配列または環境変数のリストを持つ文字列の 1 次元配列です。

exec ファミリでは、execve は 3つの引数パス、コマンド ライン引数のリスト、および環境変数のリストを持つ強力なコマンドです。 プログラムから echo コマンドを実行するコードを見てみましょう。

#include <stdio.h>
#include <unistd.h>
int main(void) {
  char *binaryPath = "/bin/bash";
  char *args[] = {binaryPath, "-c",
                  "echo visit $HOSTNAME:Fun with your browser", "", NULL};
  char *const env[] = {"HOSTNAME=www.delftstack.com", "port=8080", NULL};
  execve(binaryPath, args, env);
  return 1;
}

基本関数の最初の行で、/bin/bash はコマンドが存在するパスです。 2 行目のコマンド ライン引数のリストには、引数を終了する NULL の前に 3つのパラメーターが含まれています。

ここでも、最初の引数はパスで、2 番目のパラメーター -ccmd を表し、コードを文字列として渡すことができます。

3 番目のパラメーターはコマンドです。 コードのように、echo はコマンドです。

コードの 3 行目には、2つの環境変数 HOSTNAMEport を持つ 2つの文字列があります。 最後に、コードの出力は次のとおりです。

visit www.delftstack.com : Fun with your browser

このコードでは、プログラムから Linux コマンドを実行しました。 次に、現在のプログラム内で外部実行可能プログラムを実行します。

まず、次のプログラムをご覧ください。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *args[]) {
  int i;
  int count = atoi(args[1]);
  for (i = 1; i <= count; i++) printf("[%d]", i);
  printf("\n");
  return 0;
}

このプログラムはコマンドライン引数を取ります。 コマンド ライン引数 (文字列として渡される) は、main 関数の 2 行目で整数に変換されます。

次に、1 から count までのループを実行し、角括弧内にカウントを出力します。 このコードの出力を参照してください。

$./ test 4 [1][2][3][4]

test という名前の実行可能ファイルを作成しました。 コマンドプロンプトからパラメータ4testファイルを実行しました。

出力では、角かっこで 1 から 4 を数えていることがわかります。

次に、このプログラム test を別のプログラムからの外部コマンドとして実行する必要があります。 このために、実行可能な test プログラムのパスを指定する必要があります。

ここで、完全なパスは /home/mateen/Documents/test です。 したがって、次のプログラムでこのパスを指定して、実行可能ファイルを見つけます。

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

int main(int argc, char *ar[]) {
  printf("This is the first line\n");
  char *binaryPath = "/bin/bash";
  char name[80] = "/home/mateen/Documents/test ";
  strcat(name, ar[1]);
  char *args[] = {binaryPath, "-c", name, NULL};
  char *env_args[] = {"/bin/bash", (char *)0};
  execve(binaryPath, args, env_args);
  printf("This is the last line\n");
  return 1;
}

関数を使用して文字列を連結する別のライブラリを含めました。 main 関数の 3 行目には、完全なパスとファイル名があります。これは Linux コマンドではないためです。 代わりに、これはユーザー定義の実行可能プログラムです (詳細については既に説明しました)。

次の行では、現在のプログラムに渡されたコマンド ライン引数と新しいプログラムの名前を連結しています。 5 行目には、パス -c を持つコマンド ライン引数があります。

3 番目のパラメーターは、パス + 実行可能ファイルの名前 + 現在のプログラムに渡される引数を持つ変数名です。

出力:

$ ./a.out 5
This is the first line
[1][2][3][4][5]

コマンドラインパラメータ5で現在のプログラムを実行しています。 出力の最初の行には、最初の print ステートメントがあります。

次に、テスト プログラムが実行されていることがわかります。 1 から 5 までのカウントは、角括弧内に書かれています。

最後に、execve を使用して、Linux コマンドと実行可能プログラムの両方を実行できるという結論に達しました。 Linux コマンドの場合、パスを渡して Linux プログラムを見つけることができます。

他の/外部実行可能ファイルの場合、ファイル名を含む完全なパスを指定できます。 この場合、プログラムは指定されたパスに自動的に配置されます。 この場合、コマンドは main 関数の 3 行目のパス変数を無視します。