2015-01-20 4 views
10

여러 플랫폼에서 snprintf와 C++의 매우 이상한 동작이 있음을 알았습니다. 다음 코드 (관찰 된 동작을 일으키는 최소의 작업 예)를 고려하십시오예기치 않은 snprintf 동작

#include <stdio.h> 

char test1[512]; 
char test2[512]; 
char test3[1024]; 
char test4[1024]; 

int main() 
{ 
    snprintf(test1, sizeof(test1), "test1"); 
    snprintf(test2, sizeof(test2), "test2"); 
    snprintf(test3, sizeof(test3), "%s %s", test1, test2); 
    return 0; 
} 

= EXP-sgcheck, 다음과 같은 오류가 (3 현재 snprintf 문에 대해)보고 --tool와 Valgrind의 비록 실행 :

==30302== Invalid read of size 1 
==30302== at 0x568E4EB: vfprintf (in /lib64/libc-2.19.so) 
==30302== by 0x56B7608: vsnprintf (in /lib64/libc-2.19.so) 
==30302== by 0x5695209: snprintf (in /lib64/libc-2.19.so) 
==30302== by 0x4006AD: main (1.cc:12) 
==30302== Address 0x601460 expected vs actual: 
==30302== Expected: global array "test1" of size 1,024 in object with soname "NONE" 
==30302== Actual: global array "test2" of size 512 in object with soname "NONE" 
==30302== Actual: is 0 after Expected 
따라서 첫 번째 % s에 대한 인수로 test1을 전달하면 test1 배열의 끝에서 읽기가 발생합니다.

이 동작으로 인해 Windows 드라이버에서 여러 페이지 폴트가 발생했습니다 (예 : 정적 데이터라는 것을 알고 있습니다 ...). 다행히도이 코드는 이식성이 뛰어나며, valgrind가 리눅스로 이식되었을 때 그 오류를보고했다.

하지만 제 지식에 따르면 snprintf는 6 번째 바이트에서 \ 0으로 test1을 종료해야합니다. 그렇다면 왜 test1 배열의 끝에서 세 번째 snprintf 문을 읽게됩니까? 세 번째 snprintf 문을

snprintf(test3, sizeof(test3), "%.512s %s", test1, test2); 

으로 변경하면 두 플랫폼 모두에서 문제가 해결됩니다. C 코드 (C++이 아님)로 코드를 컴파일하면 오류가 발생하지 않습니다.

업데이트 : 디버그 정보가 포함 된 코드와 최적화가 비활성화되어 있으면 (gcc의 경우 -g -O0) Linux에서만 오류가 발생합니다.

+1

이 경우에는 차이가 있지만, 소스 파일 이름은 valgrind 출력에서'1.cc'로 호출됩니다. C를 작성하고 C++ 컴파일러로 컴파일하는 이유가 있습니까? – Blrfl

+1

'valgrind --tool = exp-sgcheck'로 여러분의 코드를 테스트했지만 오류가보고되지 않았습니까?'valgrind'의 버전과'glibc'의 버전은 무엇입니까? 또한 리눅스 배포판은 무엇입니까? –

+1

조금 더 말씀해 주시겠습니까? 예 : 플랫폼, 컴파일러 버전, 코드 컴파일 방법. – nos

답변

1

전역 개체 (예제의 배열과 같은)는 0으로 초기화되므로 마지막 snprintf는 이전 sprintfs가 종료 0 char를 복사했는지 여부와 관계없이 문자열의 끝 부분을 읽지 않아야합니다. 유일한 설명은 이전의 snprintfs가 제출 된 "test1"보다 훨씬 많은 내용을 대상 test1에 복사하여 모든 0을 0이 아닌 (0이 아닌 임의의 메모리로 덮어 쓰지는 않을 것임) 덮어 씁니다.

매우 드문 일입니다. 명백한 버그는 이전에 발견되었을 것입니다. 드라이버의 오류와 관련하여 나는 메모리가 완전히 무관 한 "프로세스"(일반적인 의미에서, 아마도 다른 드라이버)에 의해 겹쳐 쓰여지고 있다고 의심 할 것입니다. 데스크톱 응용 프로그램의 경우 실패 이유에 대한 설명이 없습니다. gcc 4.8.3을 사용하여 Codingground에서 예제를 시험해보고 끝에 printf()를 추가했을 때 예상되는 문자열을 인쇄했습니다.

Btw, 최적화가 활성화 된 상태에서 원래 코드가 잘 실행되는 것은 놀라운 일이 아닙니다. 관찰 가능한 효과가 없으므로 컴파일러에서 NOP 만 내보낼 수 있습니다.

+0

언젠가 나는 당신의 대답에 완벽하게 동의했을 것입니다.)하지만 지금은 드라이버의 정적 데이터 섹션 바로 뒤에 PAGE_FAULT가있는 snprintf 문을 정확하게 가리키는 크래시 덤프가 있습니다. 그리고 앞에서 언급했듯이 snprintf를 변경하면이 문제가 해결됩니다. 또한 충돌은 드라이버 init 바로 직후에 발생하므로이 버그를 유발할 필요가 없습니다. 하지만 여기서 상황이 매우 복잡하다는 데 동의하고 결국에는 ms C++ 컴파일러에서 버그로 보입니다. 안타깝게도 Windows 용 valgrind가 없습니다. ( – Johannes

+0

gcc 4.9.x, cygwin, VS 2013에서는 예제를 실행할 수 있지만 Windows에서는 valgrind를 사용하지 않으므로 prog를 실행할 수 있습니다.) 일반 사용자 공간 프로그램? 편집 : 나는 C로, 관련이없는 원래 게시물을 다시 읽고 컴파일 - EDIT2 : VS2013 C + +도 잘 실행됩니다. –

+0

사용자 공간에서이 버그를 재현하지 못했습니다 (예 : 충돌이 발생할 수 있음). 그러나 대부분의 경우 페이징 된 메모리가 마지막으로 선언 된 정적 데이터를 따르게 될 가능성이 높기 때문에 충돌이 전혀 발생하지 않습니다. 적절한 테스트 케이스를 만들기 위해 잘못된 읽기 액세스가 발생하는 조건과 정렬되지 않은 메모리 (정렬 및 페이지 크기와 같은 것을 존중하는 방식)에 배치하는 방법에 대해 더 자세히 설명하기 위해 valgrind와 같은 것을 사용하는 것이 매우 편리 할 것입니다. 그러나 상업용 제품이기 때문에 원본 코드를 여기에서 공개 할 수는 없습니다. – Johannes