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?
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?
Câu trả lời:
Đ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
argc
lớn hơn 0, chuỗi được trỏ tớiargv[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 là tên chương trình. Phần trước đó nói rằng:
Nếu giá trị của
argc
lớn hơn 0, các thành viên mảngargv[0]
thông quaargv[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 exec
nhóm lệnh gọi, cũng phải (và thực hiện) ghi lại nó.
argv[0]
là áp dụng cho lập trình trong thế giới thực.
Dưới *nix
loạ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 argv0
vị 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.
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.
ISO-IEC 9899 nêu rõ:
5.1.2.2.1 Khởi động chương trình
Nếu giá trị của
argc
lớn hơn 0, chuỗi được trỏ tớiargv[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ủaargc
lớn hơn một, các chuỗi được trỏ đếnargv[1]
thông quaargv[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.
/proc/self/path/a.out
kết biểu tượng có thể sử dụng được trên Solaris 10 trở lên.
GetModuleFileNameW
nê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).
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].
argv[0]="-/bin/sh"
? Đó là trường hợp của tất cả các máy tôi đã sử dụng.
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 getty
thê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
và /bin/ls
vớ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
execve
Ví 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.
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.
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ị trongargv[0]
.