C에서 Segfault를 생성하는 가장 간단한 표준 준수 방법은 무엇입니까?
나는 질문이 모든 것을 말한다고 생각한다. C89에서 C11까지 대부분의 표준을 다루는 예가 도움이 될 것입니다. 나는 이것이 있지만 정의되지 않은 행동이라고 생각합니다.
#include <stdio.h>
int main( int argc, char* argv[] )
{
const char *s = NULL;
printf( "%c\n", s[0] );
return 0;
}
편집하다:
일부 투표에서 설명을 요청함에 따라 일반적인 프로그래밍 오류 (내가 생각할 수있는 가장 간단한 것은 세그 폴트)가있는 프로그램을 원했습니다 . 이는 표준에 따라 중단되도록 보장 됩니다. 이것은이 보험에 대해 신경 쓰지 않는 최소 세그 폴트 질문과 약간 다릅니다.
분할 오류는 구현 정의 동작 입니다. 표준은 구현이 정의되지 않은 동작을 처리하는 방법을 정의하지 않으며 실제로 구현이 정의되지 않은 동작을 최적화 하고 여전히 준수 할 수 있습니다. 명확하게 말하면, 구현 정의 동작 은 표준에 의해 지정 되지 않았지만 구현이 문서화해야하는 동작입니다. 정의되지 않은 동작 은 이식 할 수 없거나 오류가 있으며 동작을 예측할 수 없어 신뢰할 수없는 코드입니다.
단락 1 의 용어, 정의 및 기호 섹션에 있는 C99 초안 표준 §3.4.3 정의되지 않은 동작 을 살펴보면 다음과 같이 말합니다 ( 앞으로 강조 표시 ).
이 국제 표준이 요구 사항을 부과하지 않는 비 이식 적이거나 오류가있는 프로그램 구성 또는 잘못된 데이터를 사용할 때의 행동
그리고 단락 2에서 말한다 :
참고 가능한 정의되지 않은 동작은 예측할 수없는 결과로 상황을 완전히 무시하는 것, 번역 또는 프로그램 실행 중 환경 특성 (진단 메시지 발행 여부에 관계없이)의 문서화 된 방식으로 동작하는 것, 번역 또는 실행 종료 ( 진단 메시지 발행).
반면에 대부분의 유닉스 계열 시스템 에서 세분화 오류를 유발하는 표준에 정의 된 메서드를 원할 경우 raise(SIGSEGV)
해당 목표를 달성해야합니다. 엄밀히 말하면 SIGSEGV
다음과 같이 정의됩니다.
SIGSEGV는 스토리지에 대한 잘못된 액세스입니다.
및 §7.14 신호 처리<signal.h>
는 다음 과 같이 말합니다.
구현은 raise 함수에 대한 명시 적 호출의 결과를 제외하고는 이러한 신호를 생성 할 필요가 없습니다 . 선언 할 수없는 함수에 대한 추가 신호 및 포인터 (각각 SIG 문자와 대문자 또는 SIG_ 및 대문자, 219)로 시작하는 매크로 정의가 구현에 의해 지정 될 수 있습니다. 신호의 전체 세트, 의미론 및 기본 처리는 구현에 따라 정의됩니다 . 모든 신호 번호는 양수 여야합니다.
raise()
segfault를 올리는 데 사용할 수 있습니다.
raise(SIGSEGV);
표준은 정의되지 않은 동작만을 언급합니다. 메모리 분할에 대해 아무것도 모릅니다. 또한 오류를 생성하는 코드는 표준을 준수하지 않습니다. 코드는 정의되지 않은 동작을 호출하고 동시에 표준을 준수 할 수 없습니다.
그럼에도 불구하고, 가장 짧은 방법은 아키텍처에 세그먼트 오류 생산하는 수행 등의 결함이 될 것 생성을 :
int main()
{
*(int*)0 = 0;
}
이것이 세그 폴트를 생성하는 이유는 무엇입니까? 메모리 주소 0에 대한 액세스는 항상 시스템에 의해 트랩되기 때문입니다. 유효한 액세스가 될 수 없습니다 (적어도 사용자 공간 코드가 아님).
물론 모든 아키텍처가 동일한 방식으로 작동하는 것은 아닙니다. 그들 중 일부에서는 위의 내용이 전혀 충돌하지 않고 다른 종류의 오류가 발생합니다. 또는 명령문이 완벽하게 괜찮을 수 있으며 메모리 위치 0에 액세스 할 수 있습니다. 이것이 표준이 실제로 무슨 일이 일어나는지 정의하지 않는 이유 중 하나입니다.
올바른 프로그램은 segfault를 생성하지 않습니다. 그리고 잘못된 프로그램의 결정 론적 동작을 설명 할 수 없습니다.
"분할 오류"는 x86 CPU가 수행하는 작업입니다. 잘못된 방법으로 메모리를 참조하려고 시도하면 얻을 수 있습니다. 또한 메모리 액세스로 인해 페이지 오류가 발생하고 (즉, 페이지 테이블에로드되지 않은 메모리에 액세스하려는 경우) OS가 해당 메모리를 요청할 권한이 없다고 결정하는 상황을 나타낼 수도 있습니다. 이러한 조건을 트리거하려면 OS 및 하드웨어에 대해 직접 프로그래밍해야합니다. C 언어로 지정된 것은 아닙니다.
를 호출하는 신호를 발생시키지 않는다고 가정하면 raise
세분화 오류는 정의되지 않은 동작에서 발생할 수 있습니다. 정의되지 않은 동작은 정의되지 않으며 컴파일러는 자유롭게 번역을 거부 할 수 있으므로 정의되지 않은 응답은 모든 구현에서 실패하지 않습니다. 또한 정의되지 않은 동작을 호출하는 프로그램은 잘못된 프로그램입니다.
그러나 이것은 내 시스템에서 segfault를 얻을 수있는 가장 짧은 것입니다 .
main(){main();}
(나는 gcc
및로 컴파일합니다 -std=c89 -O0
).
그런데이 프로그램은 정말로 정의되지 않은 bevahior를 호출합니까?
일부 플랫폼에서 표준을 준수하는 C 프로그램은 시스템에서 너무 많은 리소스를 요청하면 세그먼트 오류로 인해 실패 할 수 있습니다. 예를 들어 큰 개체를으로 할당하면 malloc
성공한 것처럼 보일 수 있지만 나중에 개체에 액세스하면 충돌이 발생합니다.
그러한 프로그램은 엄격하게 준수 하지 않습니다 . 해당 정의를 충족하는 프로그램은 각 최소 구현 한도 내에서 유지되어야합니다.
표준을 준수하는 C 프로그램은 그렇지 않으면 분할 오류를 생성 할 수 없습니다. 다른 유일한 방법은 정의되지 않은 동작을 통하기 때문입니다.
SIGSEGV
신호는 명시 적으로 제기 될 수 있지만이없는 SIGSEGV
표준 C 라이브러리의 기호.
(이 답변에서 "표준 준수"는 "특정 버전의 ISO C 표준에 설명 된 기능 만 사용하여 지정되지 않은 구현 정의 또는 정의되지 않은 동작을 방지하지만 반드시 최소 구현 제한에 국한되지는 않습니다.")
이 질문에 대한 대부분의 답변은 다음과 같은 핵심 요점을 중심으로 논의됩니다 . C 표준에는 세분화 오류의 개념이 포함되어 있지 않습니다. (C99 이후에는 신호 번호가 포함되어 SIGSEGV
있지만 raise(SIGSEGV)
다른 답변에서 논의한 것처럼 해당 신호가 전달되지 않는 상황을 정의 하지 않습니다.)
따라서 세분화 오류를 유발할 수있는 "엄격하게 준수하는"프로그램 (즉, C 표준에 의해 완전히 정의 된 동작 만 사용하는 프로그램)은 없습니다.
분할 오류는 다른 표준 인 POSIX에 의해 정의됩니다 . 이 프로그램은 SIGBUS
메모리 보호 및 고급 실시간 옵션을 포함하여 POSIX.1-2008을 완전히 준수하는 모든 시스템 에서 세분화 오류 또는 기능적으로 동등한 "버스 오류"( ) 를 유발하도록 보장 됩니다 sysconf
. posix_memalign
그리고 mprotect
성공. C99에 대한 필자는이 프로그램이 해당 표준만을 고려하여 구현 정의 된 ( 정의 되지 않은) 동작을 가지고 있으므로 준수 하지만 엄격하게 준수 하지는 않는다는 것입니다 .
#define _XOPEN_SOURCE 700
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(void)
{
size_t pagesize = sysconf(_SC_PAGESIZE);
if (pagesize == (size_t)-1) {
fprintf(stderr, "sysconf: %s\n", strerror(errno));
return 1;
}
void *page;
int err = posix_memalign(&page, pagesize, pagesize);
if (err || !page) {
fprintf(stderr, "posix_memalign: %s\n", strerror(err));
return 1;
}
if (mprotect(page, pagesize, PROT_NONE)) {
fprintf(stderr, "mprotect: %s\n", strerror(errno));
return 1;
}
*(long *)page = 0xDEADBEEF;
return 0;
}
정의 되지 않은 플랫폼에서 프로그램 을 분할 하는 방법을 정의하는 것은 어렵습니다 . 세그먼트 오류는 모든 플랫폼 (예. 간단한 소형 컴퓨터)에 대한 정의되지 않은 느슨한 용어입니다.
프로세스 를 지원하는 운영 체제 만 고려하면 프로세스는 세분화 오류가 발생했다는 알림을받을 수 있습니다.
또한 운영 체제를 '유닉스 계열'OS로 제한하는 것은 프로세스가 SIGSEGV 신호를 수신하는 신뢰할 수있는 방법입니다. kill(getpid(),SIGSEGV)
대부분의 교차 플랫폼 문제에서와 같이 각 플랫폼은 일반적으로 세그 오류에 대해 다른 정의를 가질 수 있습니다.
그러나 실용적으로 현재의 Mac, Lin 및 Win OS는
*(int*)0 = 0;
Further, it's not bad behaviour to cause a segfault. Some implementations of assert()
cause a SIGSEGV signal which might produce a core file. Very useful when you need to autopsy.
What's worse than causing a segfault is hiding it:
try
{
anyfunc();
}
catch (...)
{
printf("?\n");
}
which hides the origin of an error and all you've got to go on is:
?
.
main;
That's it.
Really.
Essentially, what this does is it defines main
as a variable. In C, variables and functions are both symbols -- pointers in memory, so the compiler does not distinguish them, and this code does not throw an error.
However, the problem rests in how the system runs executables. In a nutshell, the C standard requires that all C executables have an environment-preparing entrypoint built into them, which basically boils down to "call main
".
In this particular case, however, main
is a variable, so it is placed in a non-executable section of memory called .bss
, intended for variables (as opposed to .text
for the code). Trying to execute code in .bss
violates its specific segmentation, so the system throws a segmentation fault.
To illustrate, here's (part of) an objdump
of the resulting file:
# (unimportant)
Disassembly of section .text:
0000000000001020 <_start>:
1020: f3 0f 1e fa endbr64
1024: 31 ed xor %ebp,%ebp
1026: 49 89 d1 mov %rdx,%r9
1029: 5e pop %rsi
102a: 48 89 e2 mov %rsp,%rdx
102d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1031: 50 push %rax
1032: 54 push %rsp
1033: 4c 8d 05 56 01 00 00 lea 0x156(%rip),%r8 # 1190 <__libc_csu_fini>
103a: 48 8d 0d df 00 00 00 lea 0xdf(%rip),%rcx # 1120 <__libc_csu_init>
# This is where the program should call main
1041: 48 8d 3d e4 2f 00 00 lea 0x2fe4(%rip),%rdi # 402c <main>
1048: ff 15 92 2f 00 00 callq *0x2f92(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>
104e: f4 hlt
104f: 90 nop
# (nice things we still don't care about)
Disassembly of section .data:
0000000000004018 <__data_start>:
...
0000000000004020 <__dso_handle>:
4020: 20 40 00 and %al,0x0(%rax)
4023: 00 00 add %al,(%rax)
4025: 00 00 add %al,(%rax)
...
Disassembly of section .bss:
0000000000004028 <__bss_start>:
4028: 00 00 add %al,(%rax)
...
# main is in .bss (variables) instead of .text (code)
000000000000402c <main>:
402c: 00 00 add %al,(%rax)
...
# aaand that's it!
PS: This won't work if you compile to a flat executable. Instead, you will cause undefined behaviour.
The simplest form considering the smallest number of characters is:
++*(int*)0;
'Program Tip' 카테고리의 다른 글
하나의 문에 이름이있는 숫자 형 벡터를 만드시겠습니까? (0) | 2020.12.13 |
---|---|
SQL Server Management Studio에서 저장 프로 시저 코드를 보는 방법 (0) | 2020.12.13 |
Python에서 ROC 곡선을 그리는 방법 (0) | 2020.12.13 |
파이썬, 왜 elif 키워드일까요? (0) | 2020.12.13 |
기계적 인조 인간. (0) | 2020.12.13 |