“Argv [0] = name-of-execute” là tiêu chuẩn được chấp nhận hay chỉ là quy ước chung?


102

Khi truyền đối số tới main()trong ứng dụng C hoặc C ++, sẽ argv[0]luôn là tên của tệp thực thi? Hay đây chỉ là quy ước chung và không được đảm bảo đúng 100% thời gian?


19
Trên Unix, hãy xem xét: execl("/home/hacker/.hidden/malicious", "/bin/ls", "-s", (char *)0);. Tên của tệp thực thi không liên quan đến giá trị trong argv[0].
Jonathan Leffler

Câu trả lời:


118

Đoán (ngay cả phỏng đoán có giáo dục) là một niềm vui nhưng bạn thực sự cần phải xem các tài liệu tiêu chuẩn để chắc chắn. Ví dụ, ISO C11 nêu (nhấn mạnh của tôi):

Nếu giá trị của argclớn hơn 0, chuỗi được trỏ tới argv[0] biểu thị tên chương trình; argv[0][0]sẽ là ký tự rỗng nếu tên chương trình không có sẵn từ môi trường máy chủ.

Vì vậy, không, nó chỉ là tên chương trình nếu tên đó có sẵn. Và nó "đại diện" cho tên chương trình, không nhất thiết phải tên chương trình. Phần trước đó nói rằng:

Nếu giá trị của argclớn hơn 0, các thành viên mảng argv[0]thông qua argv[argc-1]bao gồm phải chứa các con trỏ đến các chuỗi, được cung cấp các giá trị do môi trường chủ xác định việc triển khai trước khi khởi động chương trình.

Điều này không thay đổi so với C99, tiêu chuẩn trước đó và có nghĩa là ngay cả các giá trị cũng không bị quy định bởi tiêu chuẩn - hoàn toàn phụ thuộc vào việc triển khai.

Điều này có nghĩa rằng tên chương trình thể để trống nếu môi trường máy chủ không cung cấp cho nó, và bất cứ điều gì khác nếu các môi trường máy chủ không cung cấp cho nó, với điều kiện "bất cứ điều gì khác" bằng cách nào đó đại diện cho tên chương trình. Trong những khoảnh khắc tàn bạo hơn của tôi, tôi sẽ cân nhắc dịch nó sang tiếng Swahili, chạy nó qua một mật mã thay thế sau đó lưu trữ nó theo thứ tự byte ngược :-).

Tuy nhiên, thực hiện xác định không có một ý nghĩa cụ thể trong tiêu chuẩn ISO - tài liệu thi phải làm thế nào nó hoạt động. Vì vậy, ngay cả UNIX, có thể đưa bất cứ thứ gì nó thích vào argv[0]trong execnhóm lệnh gọi, cũng phải (và thực hiện) ghi lại nó.


3
Đó có thể là tiêu chuẩn, nhưng đơn giản là unix không thực thi nó và bạn không thể tin tưởng vào nó.
dmckee --- ex-moderator kitten

4
Câu hỏi đặt ra đã không đề cập UNIX ở tất cả . Đó là một câu hỏi C rõ ràng và đơn giản, do đó ISO C là tài liệu tham khảo. Tên chương trình là phần triển khai được định nghĩa trong tiêu chuẩn nên việc triển khai có thể tự do làm những gì nó muốn, bao gồm cả việc cho phép một thứ gì đó trong đó không phải là tên thực - tôi nghĩ tôi đã nói rõ điều đó ở câu áp chót.
paxdiablo

2
Pax, tôi đã không bỏ phiếu cho bạn và không tán thành những người đã làm vì câu trả lời này có thẩm quyền nhất có thể . Nhưng tôi nghĩ rằng sự không đáng tin cậy của giá trị argv[0]là áp dụng cho lập trình trong thế giới thực.
dmckee --- ex-moderator kitten

4
@caf, đúng vậy. Tôi đã thấy nó chứa những thứ đa dạng như đường dẫn đầy đủ của chương trình ('/ progpath / prog'), chỉ tên tệp ('prog'), tên sửa đổi một chút ('-prog'), tên mô tả (' prog - một chương trình dành cho progging ') và không có gì (' '). Việc triển khai phải xác định những gì nó nắm giữ nhưng đó là tất cả những gì tiêu chuẩn yêu cầu.
paxdiablo

3
Cảm ơn mọi người! Cuộc thảo luận tuyệt vời từ một câu hỏi (dường như) đơn giản. Mặc dù câu trả lời của Richard hợp lệ với hệ điều hành * nix, nhưng tôi đã chọn câu trả lời của paxdiablo vì tôi ít quan tâm đến hoạt động của một hệ điều hành cụ thể và chủ yếu quan tâm đến sự tồn tại (hoặc không có) một tiêu chuẩn được chấp nhận. (Nếu bạn tò mò: Trong ngữ cảnh của câu hỏi ban đầu - tôi không có hệ điều hành. Tôi đang viết mã để xây dựng bộ đệm argc / argv thô cho một tệp thực thi được tải vào một thiết bị nhúng và cần biết tôi nên làm gì với argv [0]). +1 cho StackOverflow để trở nên tuyệt vời!
Mike Willekes

48

Dưới *nixloại hệ thống có exec*()cuộc gọi, argv[0]sẽ là bất cứ điều gì người gọi đặt vào argv0vị trí trong exec*()cuộc gọi.

Trình bao sử dụng quy ước rằng đây là tên chương trình, và hầu hết các chương trình khác cũng tuân theo quy ước tương tự, vì vậy argv[0]thường là tên chương trình.

Nhưng một chương trình Unix giả mạo có thể gọi exec()và tạo ra argv[0]bất cứ thứ gì mà nó thích, vì vậy bất kể tiêu chuẩn C nói gì, bạn không thể tin tưởng vào 100% thời gian này.


4
Đây là một câu trả lời tốt hơn paxdiablo ở trên. Tiêu chuẩn chỉ gọi nó là "tên chương trình", nhưng điều này không được thực thi ở bất kỳ đâu theo hiểu biết của tôi. Các hạt nhân Unix chuyển một cách đồng nhất chuỗi được truyền tới execute () không thay đổi đối với tiến trình con.
Andy Ross

4
Tiêu chuẩn C bị hạn chế về những gì nó có thể nói vì nó không biết về 'execute ()', v.v. Tiêu chuẩn POSIX ( opengroup.org/onlinepubs/9699919799/functions/execve.html ) có nhiều điều để nói - làm rõ điều đó rằng những gì trong argv [0] là tùy theo ý thích của quá trình mà thực hiện lệnh gọi hệ thống 'execute ()' (hoặc liên quan).
Jonathan Leffler

1
@Andy, bạn có thể tự do đưa ra ý kiến ​​của mình :-) Nhưng bạn đã sai về việc thực thi. Nếu một triển khai không tuân theo tiêu chuẩn thì nó không phù hợp. Và trên thực tế, vì nó được triển khai định nghĩa theo "tên chương trình" là gì, một hệ điều hành như UNIX sẽ tuân theo miễn là nó chỉ định tên đó là gì. Điều đó bao gồm việc có thể giả mạo trắng trợn tên chương trình bằng cách tải argv [0] với bất kỳ thứ gì bạn muốn trong họ lệnh thực thi.
paxdiablo

Đó là vẻ đẹp của từ "đại diện" trong tiêu chuẩn khi nó đề cập đến argv [0] ("nó đại diện cho tên chương trình") và argv [1..N] ("chúng đại diện cho các đối số của chương trình"). "unladen nuốt" là tên chương trình hợp lệ.
Richard Pennington

8

Theo Tiêu chuẩn C ++, phần 3.6.1:

argv [0] sẽ là con trỏ đến ký tự đầu tiên của một NTMBS đại diện cho tên được sử dụng để gọi chương trình hoặc ""

Vì vậy, không, nó không được đảm bảo, ít nhất là theo Tiêu chuẩn.


5
Tôi giả sử đó là chuỗi nhiều byte bị chấm dứt rỗng?
paxdiablo

5

ISO-IEC 9899 nêu rõ:

5.1.2.2.1 Khởi động chương trình

Nếu giá trị của argclớn hơn 0, chuỗi được trỏ tới argv[0]đại diện cho tên chương trình; argv[0][0]sẽ là ký tự rỗng nếu tên chương trình không có sẵn từ môi trường máy chủ. Nếu giá trị của argclớn hơn một, các chuỗi được trỏ đến argv[1]thông qua argv[argc-1]đại diện cho các tham số chương trình .

Tôi cũng đã sử dụng:

#if defined(_WIN32)
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    return GetModuleFileNameA(NULL, pathName, (DWORD)pathNameCapacity);
  }
#elif defined(__linux__) /* elif of: #if defined(_WIN32) */
  #include <unistd.h>
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    size_t pathNameSize = readlink("/proc/self/exe", pathName, pathNameCapacity - 1);
    pathName[pathNameSize] = '\0';
    return pathNameSize;
  }
#elif defined(__APPLE__) /* elif of: #elif defined(__linux__) */
  #include <mach-o/dyld.h>
  static size_t getExecutablePathName(char* pathName, size_t pathNameCapacity)
  {
    uint32_t pathNameSize = 0;

    _NSGetExecutablePath(NULL, &pathNameSize);

    if (pathNameSize > pathNameCapacity)
      pathNameSize = pathNameCapacity;

    if (!_NSGetExecutablePath(pathName, &pathNameSize))
    {
      char real[PATH_MAX];

      if (realpath(pathName, real) != NULL)
      {
        pathNameSize = strlen(real);
        strncpy(pathName, real, pathNameSize);
      }

      return pathNameSize;
    }

    return 0;
  }
#else /* else of: #elif defined(__APPLE__) */
  #error provide your own implementation
#endif /* end of: #if defined(_WIN32) */

Và sau đó bạn chỉ cần phân tích cú pháp chuỗi để trích xuất tên thực thi từ đường dẫn.


2
Liên /proc/self/path/a.outkết biểu tượng có thể sử dụng được trên Solaris 10 trở lên.
ephemient

Đã ủng hộ cho mã (không nói rằng nó lý tưởng hay chính xác, ví dụ như trên Windows GetModuleFileNameWnên được sử dụng để có thể truy xuất bất kỳ đường dẫn nào, nhưng chỉ sự hiện diện của mã tạo thành hướng dẫn tốt).
Chúc mừng và hth. - Alf

4

Trang này cho biết:

Phần tử argv [0] thường chứa tên của chương trình, nhưng điều này không nên dựa vào - dù sao thì việc chương trình không biết tên của chính nó là điều bất thường!

Tuy nhiên, các trang khác dường như sao lưu thực tế rằng nó luôn là tên của tệp thực thi. Điều này nói rõ:

Bạn sẽ nhận thấy rằng argv [0] là đường dẫn và tên của chính chương trình. Điều này cho phép chương trình khám phá thông tin về chính nó. Nó cũng thêm một đối số nữa vào mảng đối số của chương trình, do đó, một lỗi phổ biến khi tìm nạp đối số dòng lệnh là lấy argv [0] khi bạn muốn argv [1].


11
Một số chương trình lợi dụng thực tế là họ không biết tên đã được sử dụng để gọi chúng. Tôi tin rằng BusyBox ( busybox.net/about.html ) hoạt động theo cách này. Chỉ có một tệp thực thi thực thi nhiều tiện ích dòng lệnh khác nhau. Nó sử dụng một loạt các liên kết tượng trưng và argv [0] để xác định những gì công cụ dòng lệnh nên chạy
Trent

Vâng, tôi nhớ mình đã để ý rằng "gunzip" là một liên kết tượng trưng với "gzip", và tự hỏi trong giây lát nó hoạt động như thế nào.
David Thornley

2
Nhiều chương trình nhìn vào argv [0] để biết thông tin; ví dụ: nếu thành phần cuối cùng của tên bắt đầu bằng dấu gạch ngang (ví dụ: '/ bin / -sh'), thì shell sẽ chạy hồ sơ và các nội dung khác như đối với shell đăng nhập.
Jonathan Leffler

2
@Jon: Tôi nghĩ rằng trình bao đăng nhập được bắt đầu bằng argv[0]="-/bin/sh"? Đó là trường hợp của tất cả các máy tôi đã sử dụng.
ephemient

3

Các ứng dụng có argv[0] !=tên thực thi

  • nhiều shell xác định xem chúng có phải là shell đăng nhập hay không bằng cách kiểm tra argv[0][0] == '-'. Các trình bao đăng nhập có các thuộc tính khác nhau, đáng chú ý là chúng có nguồn gốc một số tệp mặc định như /etc/profile.

    Nó thường là init tự nó hoặc gettythêm phần dẫn đầu -, xem thêm: /unix/299408/how-to-login-automatically-without-typing-the-root-username-or-password -trong-build / 300152 # 300152

  • mã nhị phân đa cuộc gọi, có lẽ đáng chú ý nhất là Busybox . Các liên kết biểu tượng này có nhiều tên, ví dụ như /bin/sh/bin/lsvới một tên có thể giải thích duy nhất /bin/busybox, có thể nhận ra công cụ nào sẽ sử dụng từ argv[0].

    Điều này làm cho nó có thể có một tệp thực thi nhỏ được liên kết tĩnh đại diện cho nhiều công cụ và sẽ hoạt động về cơ bản trên mọi môi trường Linux.

Xem thêm: /unix/315812/why-does-argv-include-the-program-name/315817

execveVí dụ về Runnable POSIX trong đó argv[0] !=tên thực thi

Những người khác đã đề cập exec, nhưng đây là một ví dụ có thể chạy được.

AC

#define _XOPEN_SOURCE 700
#include <unistd.h>

int main(void) {
    char *argv[] = {"yada yada", NULL};
    char *envp[] = {NULL};
    execve("b.out", argv, envp);
}

bc

#include <stdio.h>

int main(int argc, char **argv) {
    puts(argv[0]);
}

Sau đó:

gcc a.c -o a.out
gcc b.c -o b.out
./a.out

Cung cấp:

yada yada

Có, argv[0]cũng có thể là:

Đã thử nghiệm trên Ubuntu 16.10.


2

Tôi không chắc liệu đó là một quy ước gần như toàn cầu hay một tiêu chuẩn, nhưng bạn nên tuân thủ theo cách nào. Mặc dù vậy, tôi chưa bao giờ thấy nó được khai thác bên ngoài các hệ thống giống Unix và Unix. Trong môi trường Unix - và có thể đặc biệt là trong những ngày xưa - các chương trình có thể có các hành vi khác nhau đáng kể tùy thuộc vào tên mà chúng được gọi.

ĐÃ CHỈNH SỬA: Tôi thấy từ các bài đăng khác cùng lúc với của tôi rằng ai đó đã xác định nó đến từ một tiêu chuẩn cụ thể, nhưng tôi chắc rằng quy ước này có trước tiêu chuẩn từ lâu.


1
Tôi chắc chắn ước rằng nếu mọi người sẽ "đánh dấu" phản hồi của tôi, họ sẽ đưa ra một số dấu hiệu họ không thích về nó.
Joe Mabel

0

Nếu bạn bắt đầu một chương trình Amiga bởi Workbench argv [0] sẽ không được thiết lập, chỉ bởi CLI.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.