Làm thế nào để một phân đoạn lỗi hoạt động dưới mui xe?


266

Tôi dường như không thể tìm thấy bất kỳ thông tin nào về điều này ngoài "MMU của CPU gửi tín hiệu" và "hạt nhân hướng nó đến chương trình vi phạm, chấm dứt nó".

Tôi giả định rằng nó có thể gửi tín hiệu đến vỏ và vỏ xử lý nó bằng cách chấm dứt quá trình vi phạm và in "Segmentation fault". Vì vậy, tôi đã kiểm tra giả định đó bằng cách viết một shell cực kỳ nhỏ mà tôi gọi là crsh (crap shell). Shell này không làm gì cả ngoại trừ lấy đầu vào của người dùng và đưa nó vào system()phương thức.

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

int main(){
    char cmdbuf[1000];
    while (1){
        printf("Crap Shell> ");
        fgets(cmdbuf, 1000, stdin);
        system(cmdbuf);
    }
}

Vì vậy, tôi đã chạy lớp vỏ này trong một thiết bị đầu cuối trần (không bashchạy bên dưới). Sau đó, tôi đã tiến hành chạy một chương trình tạo ra một segfault. Nếu các giả định của tôi là chính xác, thì đây sẽ là một) sự cố crsh, đóng xterm, b) không in "Segmentation fault"hoặc c) cả hai.

braden@system ~/code/crsh/ $ xterm -e ./crsh
Crap Shell> ./segfault
Segmentation fault
Crap Shell> [still running]

Quay lại vuông một, tôi đoán. Tôi đã chứng minh rằng nó không phải là cái vỏ làm điều này, mà là hệ thống bên dưới. Làm thế nào để "phân đoạn lỗi" thậm chí được in? "Ai" đang làm điều đó? Hạt nhân? Thứ gì khác? Làm thế nào để tín hiệu và tất cả các tác dụng phụ của nó truyền từ phần cứng đến sự chấm dứt cuối cùng của chương trình?


43
crshlà một ý tưởng tuyệt vời cho loại thử nghiệm này. Cảm ơn đã cho tất cả chúng ta biết về nó và ý tưởng đằng sau nó.
Bruce Ediger

30
Khi tôi lần đầu tiên nhìn thấy crsh, tôi nghĩ rằng nó sẽ được phát âm là "sự cố". Tôi không chắc đó có phải là một cái tên phù hợp như nhau không.
jpmc26

56
Đây là một thử nghiệm hay ... nhưng bạn nên biết những gì system()dưới mui xe. Nó chỉ ra rằng system()sẽ sinh ra một quá trình vỏ! Vì vậy, quy trình shell của bạn sinh ra một quy trình shell khác và quy trình shell đó (có thể /bin/shhoặc một cái gì đó tương tự) là quy trình chạy chương trình. Cách /bin/shhoặc bashcông việc là bằng cách sử dụng fork()exec()(hoặc một chức năng khác trong execve()gia đình).
Dietrich Epp

4
@BradenBest: Chính xác. Đọc trang hướng dẫn man 2 wait, nó sẽ bao gồm các macro WIFSIGNALED()WTERMSIG().
Dietrich Epp

4
@DietrichEpp Đúng như bạn nói! Tôi đã thử thêm một kiểm tra (WIFSIGNALED(status) && WTERMSIG(status) == 11)để có nó in một cái gì đó ngớ ngẩn ( "YOU DUN GOOFED AND TRIGGERED A SEGFAULT"). Khi tôi chạy segfaultchương trình từ bên trong crsh, nó đã in chính xác điều đó. Trong khi đó, các lệnh thoát thường không tạo ra thông báo lỗi.
Braden hay nhất

Câu trả lời:


248

Tất cả các CPU hiện đại có khả năng làm gián đoạn hướng dẫn máy hiện đang thực thi. Họ lưu đủ trạng thái (thông thường, nhưng không phải luôn luôn, trên ngăn xếp) để có thể tiếp tục thực hiện sau đó, như thể không có gì xảy ra (thông thường lệnh bị gián đoạn sẽ được khởi động lại từ đầu). Sau đó, họ bắt đầu thực hiện một trình xử lý ngắt , chỉ là mã máy nhiều hơn, nhưng được đặt tại một vị trí đặc biệt để CPU biết vị trí của nó trước. Trình xử lý ngắt luôn là một phần của hạt nhân của hệ điều hành: thành phần chạy với đặc quyền lớn nhất và chịu trách nhiệm giám sát việc thực hiện tất cả các thành phần khác. 1,2

Các ngắt có thể là đồng bộ , có nghĩa là chúng được kích hoạt bởi chính CPU như là một phản ứng trực tiếp với một thứ mà lệnh hiện đang thực hiện, hoặc không đồng bộ , nghĩa là chúng xảy ra vào thời điểm không thể đoán trước do sự kiện bên ngoài, như dữ liệu đến trên mạng Hải cảng. Một số người bảo lưu thuật ngữ "ngắt" cho các ngắt không đồng bộ và gọi các ngắt đồng bộ là "bẫy", "lỗi" hoặc "ngoại lệ", nhưng những từ đó đều có nghĩa khác nên tôi sẽ gắn với "ngắt đồng bộ".

Bây giờ, hầu hết các hệ điều hành hiện đại đều có khái niệm về các quy trình . Về cơ bản nhất, đây là một cơ chế trong đó máy tính có thể chạy nhiều chương trình cùng một lúc, nhưng nó cũng là một khía cạnh quan trọng trong cách các hệ điều hành cấu hình bảo vệ bộ nhớ , là một tính năng của hầu hết (nhưng, than ôi, vẫn chưa phải là tất cả ) CPU hiện đại. Nó đi cùng với bộ nhớ ảo, đó là khả năng thay đổi ánh xạ giữa các địa chỉ bộ nhớ và vị trí thực tế trong RAM. Bảo vệ bộ nhớ cho phép hệ điều hành cung cấp cho mỗi tiến trình một đoạn RAM riêng, chỉ có nó mới có thể truy cập. Nó cũng cho phép hệ điều hành (thay mặt một số tiến trình) chỉ định các vùng RAM là chỉ đọc, thực thi, được chia sẻ giữa một nhóm các quy trình hợp tác, v.v. Cũng sẽ có một đoạn bộ nhớ chỉ có thể truy cập được bởi nhân. 3

Miễn là mỗi tiến trình chỉ truy cập bộ nhớ theo cách mà CPU được cấu hình để cho phép, bảo vệ bộ nhớ là vô hình. Khi một quá trình phá vỡ các quy tắc, CPU sẽ tạo ra một ngắt đồng bộ, yêu cầu kernel sắp xếp mọi thứ. Nó thường xảy ra rằng quá trình không thực sự phá vỡ các quy tắc, chỉ có kernel cần thực hiện một số công việc trước khi quá trình có thể được phép tiếp tục. Chẳng hạn, nếu một trang trong bộ nhớ của một tiến trình cần được "đuổi" sang tệp hoán đổi để giải phóng không gian trong RAM cho một thứ khác, thì kernel sẽ đánh dấu trang đó không thể truy cập được. Lần tiếp theo quá trình cố gắng sử dụng nó, CPU sẽ tạo ra một ngắt bảo vệ bộ nhớ; hạt nhân sẽ lấy lại trang từ trao đổi, đặt nó trở lại vị trí cũ, đánh dấu nó có thể truy cập lại và tiếp tục thực hiện.

Nhưng giả sử rằng quá trình thực sự đã phá vỡ các quy tắc. Nó đã cố gắng truy cập một trang chưa bao giờ có bất kỳ RAM nào được ánh xạ tới nó hoặc nó đã cố thực thi một trang được đánh dấu là không chứa mã máy hoặc bất cứ thứ gì. Họ các hệ điều hành thường được gọi là "Unix" đều sử dụng tín hiệu để xử lý tình huống này. 4 Tín hiệu tương tự như các ngắt, nhưng chúng được tạo bởi kernel và được xử lý bởi các tiến trình, thay vì được tạo bởi phần cứng và được tạo bởi kernel. Quá trình có thể xác định xử lý tín hiệutrong mã riêng của họ và cho biết kernel đang ở đâu. Những bộ xử lý tín hiệu sau đó sẽ thực thi, làm gián đoạn dòng điều khiển thông thường, khi cần thiết. Tất cả các tín hiệu đều có một số và hai tên, một trong số đó là từ viết tắt khó hiểu và tên còn lại là một cụm từ ít khó hiểu hơn. Tín hiệu được tạo ra khi một quá trình phá vỡ các quy tắc bảo vệ bộ nhớ là (theo quy ước) số 11, và tên của nó là SIGSEGVvà "Lỗi phân đoạn". 5,6

Một sự khác biệt quan trọng giữa tín hiệu và ngắt là có một hành vi mặc định cho mọi tín hiệu. Nếu hệ điều hành không xác định trình xử lý cho tất cả các ngắt, đó là lỗi trong HĐH và toàn bộ máy tính sẽ gặp sự cố khi CPU cố gắng gọi trình xử lý bị thiếu. Nhưng các quy trình không có nghĩa vụ xác định các bộ xử lý tín hiệu cho tất cả các tín hiệu. Nếu kernel tạo tín hiệu cho một tiến trình và tín hiệu đó bị bỏ lại ở hành vi mặc định của nó, kernel sẽ tiếp tục và làm bất cứ điều gì mặc định và không làm phiền quá trình. Hầu hết các hành vi mặc định của tín hiệu là "không làm gì" hoặc "chấm dứt quá trình này và cũng có thể tạo ra kết xuất lõi". SIGSEGVlà một trong những cái sau

Vì vậy, để tóm tắt lại, chúng ta có một quy trình phá vỡ các quy tắc bảo vệ bộ nhớ. CPU đã đình chỉ quá trình và tạo ra một ngắt đồng bộ. Hạt nhân đã ngắt trường và tạo SIGSEGVtín hiệu cho quá trình. Giả sử quy trình không thiết lập trình xử lý tín hiệu SIGSEGV, vì vậy kernel thực hiện hành vi mặc định, đó là chấm dứt quá trình. Điều này có tất cả các hiệu ứng giống như _exitcuộc gọi hệ thống: các tệp đang mở bị đóng, bộ nhớ bị giải phóng, v.v.

Cho đến thời điểm này, không có gì có thể in ra bất kỳ thông điệp nào mà con người có thể nhìn thấy, và vỏ (hay nói chung hơn là quá trình cha mẹ của quá trình vừa bị chấm dứt) hoàn toàn không được tham gia. SIGSEGVđi đến quá trình phá vỡ các quy tắc, không phải cha mẹ của nó. Tuy nhiên, bước tiếp theo trong chuỗi là thông báo cho quá trình cha mẹ rằng con của nó đã bị chấm dứt. Điều này có thể xảy ra theo nhiều cách khác nhau, trong đó đơn giản nhất là khi phụ huynh đang đợi thông báo này, bằng một trong những waitcuộc gọi hệ thống ( wait, waitpid, wait4, vv). Trong trường hợp đó, kernel sẽ chỉ khiến cuộc gọi hệ thống đó trả về và cung cấp cho tiến trình cha một số mã được gọi là trạng thái thoát. 7 Trạng thái thoát thông báo cho cha mẹ tại sao quá trình con bị chấm dứt; trong trường hợp này, nó sẽ biết rằng đứa trẻ đã bị chấm dứt do hành vi mặc định của SIGSEGVtín hiệu.

Quá trình cha mẹ sau đó có thể báo cáo sự kiện cho con người bằng cách in một tin nhắn; chương trình shell hầu như luôn luôn làm điều này. Mã của bạn crshkhông bao gồm mã để làm điều đó, nhưng dù sao thì nó cũng xảy ra, bởi vì thói quen của thư viện C systemchạy một trình bao đầy đủ tính năng /bin/sh, "dưới mui xe". crshông bà trong kịch bản này; thông báo tiến trình cha mẹ được điền vào /bin/sh, trong đó in thông báo thông thường của nó. Sau đó, /bin/shnó tự thoát, vì nó không còn gì để làm và việc triển khai thư viện C systemnhận được thông báo thoát đó . Bạn có thể thấy thông báo thoát đó trong mã của mình, bằng cách kiểm tra giá trị trả về củasystem; nhưng nó sẽ không cho bạn biết rằng quá trình cháu đã chết trên một segfault, bởi vì nó được tiêu thụ bởi quá trình vỏ trung gian.


Chú thích

  1. Một số hệ điều hành không triển khai trình điều khiển thiết bị như một phần của kernel; tuy nhiên, tất cả các trình xử lý ngắt vẫn phải là một phần của kernel và mã cũng cấu hình bảo vệ bộ nhớ, vì phần cứng không cho phép bất cứ thứ gì ngoại trừ kernel làm những việc này.

  2. Có thể có một chương trình gọi là "trình ảo hóa" hoặc "trình quản lý máy ảo" thậm chí còn đặc quyền hơn kernel, nhưng với mục đích của câu trả lời này, nó có thể được coi là một phần của phần cứng .

  3. Nhân là một chương trình , nhưng nó không phải là một quá trình; nó giống như một thư viện Tất cả các quy trình thực hiện các phần của mã của hạt nhân, theo thời gian, ngoài mã riêng của họ. Có thể có một số "chuỗi nhân" chỉ thực thi mã hạt nhân, nhưng chúng không liên quan đến chúng tôi ở đây.

  4. Hệ điều hành một và duy nhất mà bạn có khả năng sẽ phải đối phó nữa mà không thể coi là một triển khai của Unix, tất nhiên, là Windows. Nó không sử dụng tín hiệu trong tình huống này. (Thật vậy, nó không tín hiệu; trên Windows <signal.h>giao diện hoàn toàn bị giả mạo bởi thư viện C.) Thay vào đó, nó sử dụng một cái gì đó gọi là " xử lý ngoại lệ có cấu trúc ".

  5. Một số vi phạm bảo vệ bộ nhớ tạo ra SIGBUS("Lỗi xe buýt") thay vì SIGSEGV. Dòng giữa hai là không xác định và thay đổi từ hệ thống để hệ thống. Nếu bạn đã viết một chương trình xác định trình xử lý cho SIGSEGVthì có lẽ nên xác định trình xử lý tương tự SIGBUS.

  6. "Lỗi phân đoạn" là tên của ngắt được tạo ra do vi phạm bảo vệ bộ nhớ bởi một trong những máy tính chạy Unix gốc , có thể là PDP-11 . " Phân đoạn " là một loại bảo vệ bộ nhớ, nhưng ngày nay thuật ngữ " lỗi phân đoạn " nói chung cho bất kỳ loại vi phạm bảo vệ bộ nhớ nào.

  7. Tất cả các cách khác mà quá trình cha mẹ có thể được thông báo về một đứa trẻ đã chấm dứt, kết thúc bằng việc cha mẹ gọi waitvà nhận được trạng thái thoát. Chỉ là một cái gì đó khác xảy ra đầu tiên.


@zvol: quảng cáo 2) Tôi không nghĩ rằng CPU nói bất cứ điều gì về các quy trình. Bạn nên nói rằng nó gọi một trình xử lý ngắt, giúp chuyển điều khiển.
dùng323094

9
@ user323094 CPU đa lõi hiện đại thực sự biết khá nhiều về các quy trình; đủ để trong tình huống này, họ chỉ có thể tạm dừng luồng xử lý đã kích hoạt lỗi bảo vệ bộ nhớ. Ngoài ra, tôi đã cố gắng không đi vào chi tiết cấp thấp. Từ quan điểm của lập trình viên không gian người dùng, điều quan trọng nhất cần hiểu về bước 2 là phần cứng phát hiện vi phạm bảo vệ bộ nhớ; ít hơn sự phân công lao động chính xác giữa phần cứng, phần sụn và hệ điều hành khi xác định "quy trình vi phạm".
zwol

Một sự tinh tế khác có thể gây nhầm lẫn cho một người đọc ngây thơ là "Hạt nhân gửi quá trình vi phạm tín hiệu SIGSEGV." trong đó sử dụng thuật ngữ thông thường, nhưng thực tế có nghĩa là hạt nhân tự bảo mình xử lý tín hiệu foo trên thanh quá trình (tức là mã vùng người dùng không tham gia trừ khi có trình xử lý tín hiệu được cài đặt, một câu hỏi được giải quyết bởi kernel). Đôi khi tôi thích "tăng tín hiệu SIGSEGV trong quá trình" vì lý do đó.
dmckee

2
Sự khác biệt đáng kể giữa SIGBUS (lỗi bus) và SIGSEGV (lỗi phân đoạn) là: SIGSEGV xảy ra khi CPU biết bạn không nên truy cập địa chỉ (và do đó nó không thực hiện bất kỳ yêu cầu bus bộ nhớ ngoài nào). SIGBUS xảy ra khi CPU chỉ phát hiện ra vấn đề về địa chỉ một khi nó đã đưa yêu cầu của bạn lên bus địa chỉ bên ngoài. Ví dụ: yêu cầu một địa chỉ vật lý mà không có gì trên xe buýt phản hồi hoặc yêu cầu đọc dữ liệu trên ranh giới được căn chỉnh sai (sẽ yêu cầu hai yêu cầu vật lý để nhận thay vì một)
Stuart Caie

2
@StuartCaie Bạn đang mô tả hành vi của các ngắt ; thật vậy, nhiều CPU tạo ra sự khác biệt mà bạn phác thảo (mặc dù một số thì không, và ranh giới giữa hai loại này khác nhau). Các tín hiệu SIGSEGV và SIGBUS, tuy nhiên, được không đáng tin cậy ánh xạ tới những hai điều kiện CPU cấp. Điều kiện duy nhất mà POSIX yêu cầu SIGBUS chứ không phải SIGSEGV là khi bạn mmaptập tin vào vùng nhớ lớn hơn tập tin và sau đó truy cập "toàn bộ trang" bên ngoài cuối tập tin. (POSIX nếu không thì khá mơ hồ về thời điểm SIGSEGV / SIGBUS / SIGILL / etc xảy ra.)
zwol

42

Shell thực sự có liên quan đến thông điệp đó và crshgián tiếp gọi shell, có lẽ là vậy bash.

Tôi đã viết một chương trình C nhỏ luôn phân tách lỗi:

#include <stdio.h>

int
main(int ac, char **av)
{
        int *i = NULL;

        *i = 12;

        return 0;
}

Khi tôi chạy nó từ shell mặc định của mình zsh, tôi nhận được điều này:

4 % ./segv
zsh: 13512 segmentation fault  ./segv

Khi tôi chạy nó từ bash, tôi nhận được những gì bạn lưu ý trong câu hỏi của bạn:

bediger@flq123:csrc % ./segv
Segmentation fault

Tôi sẽ viết một trình xử lý tín hiệu trong mã của mình, sau đó tôi nhận ra rằng system()cuộc gọi thư viện được sử dụng bởi crshexec's shell, /bin/shtheo man 3 system. Điều đó /bin/shgần như chắc chắn in ra "Lỗi phân đoạn", vì crshchắc chắn là không.

Nếu bạn viết crshlại để sử dụng lệnh execve()gọi hệ thống để chạy chương trình, bạn sẽ không thấy chuỗi "Lỗi phân đoạn". Nó đến từ vỏ được gọi bởi system().


5
Tôi vừa mới thảo luận điều này với Dietrich Epp. Tôi đã hack cùng một phiên bản của crsh sử dụng execvpvà thực hiện kiểm tra một lần nữa để thấy rằng trong khi trình bao vẫn không gặp sự cố (có nghĩa là SIGSEGV không bao giờ được gửi đến trình bao), nó không in "Lỗi phân đoạn". Không có gì được in cả. Điều này dường như chỉ ra rằng lớp vỏ phát hiện khi các tiến trình con của nó bị giết và chịu trách nhiệm in "Lỗi phân đoạn" (hoặc một số biến thể của nó).
Braden hay nhất

2
@BradenBest - Tôi đã làm điều tương tự, mã của tôi chậm hơn mã của bạn. Tôi không có tin nhắn nào cả, và cái vỏ thậm chí của tôi không in một thứ gì. Tôi đã sử dụng waitpid()trên mỗi fork / exec và nó trả về một giá trị khác nhau cho các quy trình có lỗi phân đoạn, hơn các quy trình thoát với trạng thái 0.
Bruce Ediger

21

Tôi dường như không thể tìm thấy bất kỳ thông tin nào về điều này ngoài "MMU của CPU gửi tín hiệu" và "hạt nhân hướng nó đến chương trình vi phạm, chấm dứt nó".

Đây là một chút tóm tắt bị cắt xén. Cơ chế tín hiệu Unix hoàn toàn khác với các sự kiện dành riêng cho CPU bắt đầu quá trình.

Nói chung, khi một địa chỉ xấu được truy cập (hoặc được ghi vào vùng chỉ đọc, cố gắng thực thi một phần không thể thực thi, v.v.), CPU sẽ tạo ra một số sự kiện dành riêng cho CPU (trên các kiến ​​trúc không VM truyền thống, đây là được gọi là vi phạm phân đoạn, vì mỗi "phân đoạn" (theo truyền thống, "văn bản" có thể thực thi chỉ đọc, "dữ liệu" có thể ghi và có độ dài thay đổi và ngăn xếp theo truyền thống ở đầu đối diện của bộ nhớ) có một dải địa chỉ cố định - trên một kiến ​​trúc hiện đại, nhiều khả năng đó là lỗi trang [đối với bộ nhớ chưa được xử lý] hoặc vi phạm quyền truy cập [để đọc, viết và thực thi các vấn đề về quyền], và tôi sẽ tập trung vào vấn đề này cho phần còn lại của câu trả lời).

Bây giờ, tại thời điểm này, kernel có thể làm một số điều. Lỗi trang cũng được tạo cho bộ nhớ hợp lệ nhưng không được tải (ví dụ: bị tráo đổi hoặc trong tệp bị khóa, v.v.) và trong trường hợp này, kernel sẽ ánh xạ bộ nhớ và sau đó khởi động lại chương trình người dùng từ hướng dẫn gây ra lỗi. Nếu không, nó sẽ gửi một tín hiệu. Điều này không chính xác "trực tiếp [sự kiện ban đầu] với chương trình vi phạm", vì quá trình cài đặt trình xử lý tín hiệu là khác nhau và chủ yếu là độc lập với kiến ​​trúc, so với nếu chương trình được dự kiến ​​mô phỏng cài đặt trình xử lý ngắt.

Nếu chương trình người dùng đã cài đặt trình xử lý tín hiệu, điều này có nghĩa là tạo khung ngăn xếp và đặt vị trí thực thi của chương trình người dùng thành trình xử lý tín hiệu. Điều tương tự cũng được thực hiện cho tất cả các tín hiệu, nhưng trong trường hợp vi phạm phân đoạn, mọi thứ thường được sắp xếp sao cho nếu trình xử lý tín hiệu trả về, nó sẽ khởi động lại lệnh gây ra lỗi. Chương trình người dùng có thể đã sửa lỗi, ví dụ: bằng cách ánh xạ bộ nhớ đến địa chỉ vi phạm - nó phụ thuộc vào kiến ​​trúc cho dù điều này là có thể). Trình xử lý tín hiệu cũng có thể nhảy đến một vị trí khác trong chương trình (thường thông qua longjmp hoặc bằng cách ném ngoại lệ), để hủy bỏ mọi thao tác gây ra truy cập bộ nhớ xấu.

Nếu chương trình người dùng không cài đặt bộ xử lý tín hiệu, nó sẽ bị ngắt. Trên một số kiến ​​trúc, nếu tín hiệu bị bỏ qua, nó có thể khởi động lại lệnh lặp đi lặp lại, gây ra một vòng lặp vô hạn.


+1, chỉ trả lời thêm bất cứ điều gì cho người được chấp nhận. Mô tả hay về lịch sử "phân khúc". Sự thật thú vị: x86 thực sự vẫn có giới hạn phân đoạn ở chế độ được bảo vệ 32 bit (có bật hoặc không phân trang (bộ nhớ ảo), do đó, các hướng dẫn mà bộ nhớ truy cập có thể tạo ra #PF(fault-code)(lỗi trang) hoặc #GP(0)("Nếu địa chỉ hiệu quả của toán hạng bộ nhớ nằm ngoài CS, Giới hạn phân đoạn DS, ES, FS hoặc GS. "). Chế độ 64 bit giảm kiểm tra giới hạn phân khúc, vì các hệ điều hành chỉ sử dụng phân trang thay thế và mô hình bộ nhớ phẳng cho không gian người dùng.
Peter Cordes

Trên thực tế, tôi tin rằng hầu hết các hệ điều hành trên x86 đều sử dụng phân trang: một loạt các phân đoạn lớn bên trong một không gian địa chỉ phẳng, được phân trang. Đây là cách bạn bảo vệ và ánh xạ bộ nhớ kernel vào từng không gian địa chỉ: vòng (mức bảo vệ) được liên kết với các phân đoạn, không phải trang
Lorenzo Dematté

Ngoài ra, trên NT (nhưng tôi rất muốn biết liệu trên hầu hết các Unix có giống nhau không!) "Lỗi phân đoạn" có thể xảy ra khá thường xuyên: có một phân đoạn được bảo vệ 64k ở đầu không gian người dùng, do đó, việc hủy bỏ con trỏ NULL sẽ tăng (đúng không?) lỗi phân khúc
Lorenzo Dematté

1
@ LorenzoDematté Có, tất cả hoặc gần như tất cả các Unix hiện đại sẽ để lại một khối địa chỉ không bị chặn vĩnh viễn ở đầu không gian địa chỉ để bắt các cuộc hội thảo của NULL. Nó có thể khá lớn - trên các hệ thống 64 bit, trên thực tế, nó có thể là bốn gigabyte , do đó việc cắt ngẫu nhiên các con trỏ thành 32 bit sẽ được bắt kịp thời. Tuy nhiên, phân khúc theo nghĩa x86 nghiêm ngặt hầu như không được sử dụng; có một phân đoạn phẳng cho không gian người dùng và một phân đoạn cho kernel, và có thể là một vài cho các thủ thuật đặc biệt như sử dụng FS và GS.
zwol

1
@ LorenzoDematté NT sử dụng ngoại lệ thay vì tín hiệu; trong trường hợp này STATUS_ACCESS_VIOLATION.
Random832

18

Lỗi phân đoạn là quyền truy cập vào địa chỉ bộ nhớ không được phép (không phải là một phần của quy trình hoặc cố gắng ghi dữ liệu chỉ đọc hoặc thực thi dữ liệu không thể thực thi, ...). Điều này bị MMU (Bộ quản lý bộ nhớ, ngày nay là một phần của CPU), gây ra sự gián đoạn. Ngắt được xử lý bởi kernel, sẽ gửi SIGSEGFAULTtín hiệu (xem signal(2)ví dụ) đến quá trình vi phạm. Trình xử lý mặc định cho tín hiệu này bỏ lõi (xem core(5)) và chấm dứt quá trình.

Vỏ hoàn toàn không có tay trong này.


3
Vậy thư viện C của bạn, như glibc trên Desktop, định nghĩa chuỗi?
drewbenn

7
Cũng đáng lưu ý rằng SIGSEGV có thể được xử lý / bỏ qua. Vì vậy, có thể viết một chương trình không bị chấm dứt bởi nó. Máy ảo Java là một ví dụ đáng chú ý sử dụng SIGSEGV trong nội bộ cho các mục đích khác nhau, như được đề cập ở đây: stackoverflow.com/questions/3731784/ phỏng
Karol Nowak

2
Tương tự như vậy, trên Windows, .NET không bận tâm đến việc thêm kiểm tra con trỏ null trong hầu hết các trường hợp - nó chỉ bắt các vi phạm truy cập (tương đương với segfaults).
Immibis
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.