Điều gì chính xác xảy ra khi tôi thực thi một tập tin trong shell của tôi?


32

Vì vậy, tôi nghĩ rằng tôi đã hiểu rõ về điều này, nhưng chỉ cần chạy thử nghiệm (để đáp lại cuộc trò chuyện mà tôi không đồng ý với ai đó) và thấy rằng sự hiểu biết của tôi là thiếu sót ...

Càng chi tiết càng tốt những gì chính xác sẽ xảy ra khi tôi thực thi một tệp trong trình bao của mình? Ý tôi là, nếu tôi gõ: ./somefile some argumentsvào shell của mình và nhấn return (và somefiletồn tại trong cwd, và tôi đã đọc + thực thi quyền trên somefile) thì điều gì xảy ra dưới mui xe?

Tôi nghĩ câu trả lời là:

  1. Vỏ làm cho một exectòa nhà cao tầng, đi qua con đường đếnsomefile
  2. Nhân kiểm tra somefilevà xem số ma thuật của tệp để xác định xem đó có phải là định dạng mà bộ xử lý có thể xử lý không
  3. Nếu số ma thuật chỉ ra rằng tệp có định dạng mà bộ xử lý có thể thực thi, thì
    1. một quy trình mới được tạo ra (với một mục trong bảng quy trình)
    2. somefileđược đọc / ánh xạ vào bộ nhớ. Một ngăn xếp được tạo và thực thi nhảy đến điểm vào của mã somefile, với ARGVkhởi tạo thành một mảng các tham số (a char**, ["some","arguments"])
  4. Nếu số ma thuật là một shebang thì exec()sinh ra một quy trình mới như trên, nhưng thực thi được sử dụng là trình thông dịch được tham chiếu bởi shebang (ví dụ /bin/bashhoặc /bin/perl) và somefileđược chuyển đếnSTDIN
  5. Nếu tệp không có số ma thuật hợp lệ, thì sẽ xảy ra lỗi như "tệp không hợp lệ (số ma thuật xấu): Lỗi định dạng Exec" xảy ra

Tuy nhiên, ai đó nói với tôi rằng nếu tệp là văn bản thuần túy, thì trình bao cố gắng thực thi các lệnh (như thể tôi đã gõ bash somefile). Tôi đã không tin điều này, nhưng tôi chỉ thử nó, và nó đã đúng. Vì vậy, tôi rõ ràng có một số quan niệm sai lầm về những gì thực sự xảy ra ở đây, và muốn hiểu cơ học.

Điều gì chính xác xảy ra khi tôi thực thi một tập tin trong shell của tôi? (càng nhiều chi tiết là hợp lý ...)


Không có sự thay thế hoàn hảo nào cho việc xem mã nguồn để hiểu sâu hơn.
tự đại diện

1
@Wildcard đó là những gì tôi đang làm ngay bây giờ, thực sự là :-) Nếu tôi có thể, tôi sẽ trả lời câu hỏi của riêng tôi
Josh

1
source somefile./somefilemặc dù rất khác với một quy trình mới đang bị chặn lại .
thrig

@thrig có, tôi đồng ý. Nhưng tôi không nghĩ rằng điều đó ./somefilesẽ khiến bash thực thi các lệnh somefilenếu tập tin không có số ma thuật. Tôi nghĩ rằng nó sẽ chỉ hiển thị một lỗi và thay vào đó nó có vẻ hiệu quảsource somefile
Josh

Tôi lại nhầm, tôi có thể xác nhận rằng nếu somefilelà tệp văn bản, thì trình bao mới sẽ xuất hiện nếu tôi cố thực thi nó. Một tập tin echo $$hoạt động khác nhau nếu tôi thực hiện so với nguồn nó.
Josh

Câu trả lời:


31

Câu trả lời dứt khoát cho "cách các chương trình được chạy" trên Linux là cặp bài viết trên LWN.net có tiêu đề, đủ ngạc nhiên, Cách chương trình được chạyCách chương trình chạy: nhị phân ELF . Bài viết đầu tiên đề cập đến các kịch bản ngắn gọn. (Nói đúng ra câu trả lời dứt khoát nằm trong mã nguồn, nhưng những bài viết này dễ đọc hơn và cung cấp liên kết đến mã nguồn.)

Một thử nghiệm nhỏ cho thấy rằng bạn đã hiểu đúng và việc thực thi một tệp chứa danh sách các lệnh đơn giản, không có shebang, cần phải được xử lý bởi trình bao. Trang thực thi (2) chứa mã nguồn cho chương trình thử nghiệm, thực thi; chúng ta sẽ sử dụng nó để xem những gì xảy ra mà không có vỏ. Đầu tiên, viết một bản kiểm tra testscr1, có chứa

#!/bin/sh

pstree

và một số khác testscr2, chỉ chứa

pstree

Làm cho cả hai thực thi và xác minh rằng cả hai đều chạy từ trình bao:

chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less

Bây giờ hãy thử lại, bằng cách sử dụng execve(giả sử bạn đã xây dựng nó trong thư mục hiện tại):

./execve ./testscr1
./execve ./testscr2

testscr1vẫn chạy, nhưng testscr2sản xuất

execve: Exec format error

Điều này cho thấy vỏ xử lý testscr2khác nhau. Mặc dù vậy, nó không xử lý tập lệnh, nó vẫn sử dụng /bin/shđể làm điều đó; điều này có thể được xác nhận bằng cách dẫn testscr2đến less:

./testscr2 | less -ppstree

Trên hệ thống của tôi, tôi nhận được

    |-gnome-terminal--+-4*[zsh]
    |                 |-zsh-+-less
    |                 |     `-sh---pstree

Như bạn có thể thấy, có lớp vỏ tôi đang sử dụng, zshđã bắt đầu lessvà lớp vỏ thứ hai, đơn giản sh( dashtrên hệ thống của tôi), để chạy tập lệnh chạy pstree. Trong zshphần này được xử lý bởi zexecvein Src/exec.c: shell sử dụng execve(2)để chạy lệnh và nếu thất bại, nó đọc tệp để xem nó có shebang hay không, xử lý nó cho phù hợp (điều này cũng sẽ được xử lý) không thành công, nó cố chạy tệp sh, miễn là nó không đọc bất kỳ byte nào từ tệp:

        for (t0 = 0; t0 != ct; t0++)
            if (!execvebuf[t0])
                break;
        if (t0 == ct) {
            argv[-1] = "sh";
            winch_unblock();
            execve("/bin/sh", argv - 1, newenvp);
        }

bashcó hành vi tương tự, được thực hiện execute_cmd.cvới một nhận xét hữu ích (như được chỉ ra bởi Taliezin ):

Thực hiện một lệnh đơn giản được hy vọng được xác định trong một tệp đĩa ở đâu đó.

  1. fork ()
  2. nối ống
  3. tra cứu lệnh
  4. làm chuyển hướng
  5. execve ()
  6. Nếu execve thất bại, hãy xem tập tin có cài đặt chế độ thực thi không. Nếu vậy, và nó không phải là một thư mục, thì thực hiện nội dung của nó như là một kịch bản shell.

POSIX định nghĩa một tập hợp các hàm, được gọi là các exec(3)hàm , cũng bao bọc execve(2)và cung cấp chức năng này; xem câu trả lời của muru để biết chi tiết. Trên Linux, ít nhất các chức năng này được thư viện C triển khai chứ không phải bởi kernel.


Điều này thật tuyệt vời và có chi tiết tôi đang tìm kiếm, cảm ơn bạn!
Josh

12

Một phần, điều này phụ thuộc vào execchức năng gia đình cụ thể được sử dụng. execve, như Stephen Kitt đã chỉ ra chi tiết, chỉ chạy các tệp ở định dạng nhị phân hoặc tập lệnh chính xác bắt đầu bằng một shebang thích hợp.

Tuy nhiên , execlpexecvptiến thêm một bước: nếu shebang không chính xác, tệp sẽ được thực thi /bin/shtrên Linux. Từ man 3 exec:

Special semantics for execlp() and execvp()
   The execlp(), execvp(), and execvpe() functions duplicate the actions
   of the shell in searching for an executable file if the specified
   filename does not contain a slash (/) character.
   …

   If the header of a file isn't recognized (the attempted execve(2)
   failed with the error ENOEXEC), these functions will execute the
   shell (/bin/sh) with the path of the file as its first argument.  (If
   this attempt fails, no further searching is done.)

Điều này phần nào được hỗ trợ bởi POSIX (nhấn mạnh của tôi):

Một nguồn gây nhầm lẫn tiềm năng được các nhà phát triển tiêu chuẩn lưu ý là về cách thức nội dung của tệp hình ảnh quá trình ảnh hưởng đến hành vi của họ hàm thực thi. Sau đây là mô tả về các hành động được thực hiện:

  1. Nếu tệp hình ảnh quá trình là tệp thực thi hợp lệ (ở định dạng có thể thực thi được và hợp lệ và có các đặc quyền phù hợp) cho hệ thống này, thì hệ thống sẽ thực thi tệp.

  2. Nếu tệp hình ảnh quá trình có các đặc quyền phù hợp và ở định dạng có thể thực thi được nhưng không hợp lệ đối với hệ thống này (chẳng hạn như tệp nhị phân được nhận dạng cho kiến ​​trúc khác), thì đây là lỗi và errno được đặt thành [EINVAL] (xem sau RATIONALE trên [EINVAL]).

  3. Nếu tệp hình ảnh quá trình có các đặc quyền phù hợp nhưng không được công nhận:

    1. Nếu đây là lệnh gọi execlp () hoặc execvp (), thì họ gọi trình thông dịch lệnh giả định rằng tệp hình ảnh quá trình là tập lệnh shell.

    2. Nếu đây không phải là lệnh gọi execlp () hoặc execvp (), thì sẽ xảy ra lỗi và errno được đặt thành [ENOEXEC].

Điều này không chỉ định cách thu được trình thông dịch lệnh, vì vậy, nhưng không xác định rằng phải đưa ra lỗi. Do đó, tôi đoán rằng các nhà phát triển Linux đã cho phép các tệp như vậy được chạy cùng /bin/sh(hoặc đây đã là một thông lệ phổ biến và họ chỉ tuân theo sự phù hợp).

FWIW, trang quản lý FreeBSDexec(3) cũng đề cập đến hành vi tương tự:

 Some of these functions have special semantics.

 The functions execlp(), execvp(), and execvP() will duplicate the actions
 of the shell in searching for an executable file if the specified file
 name does not contain a slash ``/'' character. 
 …
 If the header of a file is not recognized (the attempted execve()
 returned ENOEXEC), these functions will execute the shell with the path
 of the file as its first argument.  (If this attempt fails, no further
 searching is done.)

AFAICT, tuy nhiên, không sử dụng vỏ thông thường execlphoặc execvptrực tiếp, có lẽ để kiểm soát tốt hơn đối với môi trường. Tất cả đều thực hiện cùng một logic sử dụng execve.


3
Tôi cũng muốn nói thêm rằng ít nhất trên Linux, execl, execlp, execle, execv, execvpexecvpelà tất cả phía trước đích đến execvesyscall; cái trước được cung cấp bởi thư viện C, kernel chỉ biết về execve(và execveatngày nay).
Stephen Kitt

@StephenKitt Điều đó giải thích tại sao tôi không thể tìm thấy một trang dành cho các chức năng đó trên phần 2. của m7.org
muru

6

Đây có thể là một bổ sung cho câu trả lời của Stephen Kitt, như một nhận xét từ bashnguồn trong tệp execute_cmd.c:

Thực hiện một lệnh đơn giản được hy vọng được xác định trong một tệp đĩa ở đâu đó.

1. fork ()
2. connect pipes
3. look up the command
4. do redirections
5. execve ()
6. If the execve failed, see if the file has executable mode set.  

Nếu vậy, và nó không phải là một thư mục, thì thực hiện nội dung của nó như là một kịch bản shell.


0

Nó được thực thi như một kịch bản shell, nó không có nguồn gốc (ví dụ, các biến được đặt trong tệp thực thi không ảnh hưởng đến bên ngoài). Có lẽ là dấu tích từ quá khứ mù sương, khi có một vỏ và một định dạng thực thi. Không phải là một thực thi, nó phải là một kịch bản shell.


2
Bạn đã hiểu nhầm câu hỏi của tôi. Điều gì xảy ra chi tiết? Ở mức tối thiểu, tôi cần hiểu những gì kiểm tra cho một shebang, đó là exec()hay vỏ? Tôi muốn có nhiều nội bộ hơn đáng kể
Josh
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.