티스토리 뷰

VisualStudio

SAL 이해

pu928 2017. 10. 17. 23:08
반응형

여기에서 가져옴

SAL 이해

Visual Studio 2015
 

Visual Studio 2017 에 대한 최신 설명서는 Visual Studio 2017 설명서를 참조하세요.

Microsoft 소스 코드 주석 언어 (SAL)는 함수의 매개 변수를 어떻게 사용하는지 완료 될 때 그것들에 대한 가정을 설명하는 주석의 집합을 제공합니다. 주석을 <sal.h>헤더 파일에 정의되었습니다. C++에 대한 Visual Studio 코드 분석을 SAL 주석에 사용하여 기능 분석을 수정합니다. SAL 2.0에 대한 자세한 내용은 Windows 드라이버에 대 한 SAL 2.0 주석을 참조하십시오.

기본적으로, C 및 C++ 개발자가 의도한 불변을 일관되게 표현하는 제한적인 방법으로만 제공합니다. SAL 주석을 사용하여 소비하는 개발자들이 사용하는 방법을 더 잘 이해할 수 있도록 함수를 자세히 설명할 수 있습니다.

간단히 말하면 SAL는 컴파일러가 코드를 검사할 수 있도록 하는 저렴한 방법입니다.

코드의 품질을 높이는 SAL

SAL은 인간이고 코드 분석 도구에 대한 코드 디자인을 보다 이해 하기 쉽게 만들 수 있습니다. C 런타임 함수 memcpy를 보여 수는 이 예제를 고려하십시오.

  
void * memcpy(  
   void *dest,   
   const void *src,   
   size_t count  
);  
  

이 함수는 어떤일을 말할 수 있습니까? 함수를 호출하거나 구현 프로그램이 올바른지 확인하기 위해 특정 속성을 유지 합니다. 예제와 같은 선언을 보고, 정의에 대해서는 모릅니다. SAL 주석없이 코드 주석 또는 문서를 의존해야 합니다. memcpy 에 대한 MSDN 설명서의 말:

"복사본을 dest는 src의 바이트를 계산합니다.원본 영역과 대상 문자열이 겹치면, 동작이 지정되지 않습니다.Memmove은 겹치는 부분을 처리하기 위해 사용합니다.
보안 정보: 는 원본 버퍼보다 크거나 같은 크기의 대상 버퍼인지 확인합니다.자세한 내용은 버퍼 오버런 방지를 참조하십시오."

설명서 2 프로그램이 올바른지 확인하기 위해 특정 속성을 유지하려면 코드에 있는 제안하는 정보 비트를 포함합니다.

  • memcpy복사본은 count 대상 버퍼를 소스 버퍼에서 바이트.

  • 대상 버퍼를 소스 버퍼 이상이여야 합니다.

그러나 컴파일러 설명서나 비공식 메모를 읽을 수 없습니다. 두 개의 버퍼와 count 간의 관계를가 모르고, 효과적으로 관계에 대한 것도 추측할 수 없습니다. SAL는 다음 그림과 같이 속성 및 함수를 구현하는 방법에 대한 자세한 설명을 제공할 수 있습니다.:

  
void * memcpy(  
   _Out_writes_bytes_all_(count) void *dest,   
   _In_reads_bytes_(count) const void *src,   
   size_t count  
);  

이러한 주석이 MSDN 설명서에 있는 정보와 유사하지만 보다 간결하게 하는 의미 있는 패턴을 따르는 것을 주목합니다. 이 코드를 읽을 때 이 함수의 속성 및 버퍼 오버런 보안 문제를 방지하는 방법을 쉽게 파악할 수 있습니다. 게다가 SAL 제공 의미 무늬 효율성과 효과의 잠재적인 버그를 검색하는 초기의 자동화 된 코드 분석 도구를 향상시킬 수 있습니다. 이 버그가 있는 wmemcpy 구현을 쓰는 사람을 가정합니다.

  
wchar_t * wmemcpy(  
   _Out_writes_all_(count) wchar_t *dest,   
   _In_reads_(count) const wchar_t *src,   
   size_t count)  
{  
   size_t i;  
   for (i = 0; i <= count; i++) { // BUG: off-by-one error  
      dest[i] = src[i];  
   }  
   return dest;  
}  
  

이 구현은 일반적인 해제-하나씩 오류가 포함 되어 있습니다. 다행히 코드 작성자 포함 SAL 버퍼 크기 주석-코드 분석 도구만이 함수를 분석하여 버그를 catch할 수 없습니다.

SAL 기본

SAL 네 가지 기본 유형의 사용 패턴으로 분류 되는 매개 변수를 정의 합니다.

범주매개 변수 주석설명
함수 호출에 대한 입력_In_데이터는 호출된 함수에 전달되고 읽기 전용으로 취급됩니다.
호출된 함수에 대한 입력과 호출자에 출력_Inout_사용 가능한 데이터 함수에 전달되고 잠재적으로 수정 됩니다.
호출자에 출력_Out_호출자만 쓰려고 하는 호출된 함수에 대한 공간을 제공 합니다. 호출된 함수는 해당 공간에 데이터를 씁니다.
호출자에 포인터 출력_Outptr_마찬가지로 호출자에 출력. 호출된 함수에서 반환되는 값이 있습니다.

이러한 네 가지 기본 주석을 명시적으로 더 다양한 방법으로 만들 수 있습니다. 기본적으로 주석이 추가된 포인터 매개 변수는 필요한 것으로 간주 됩니다-함수의 성공에 대해 NULL이 아니어야만 합니다. 자주 사용하는 기본 주석 변동을 포인터 매개 변수가 선택적 임을 나타냅니다-NULL이면 함수 작업 과정에서 여전히 성공할 수 있습니다.

이 표에서 필수 및 선택적 매개 변수를 구분하는 방법을 보여 줍니다.

매개 변수가 요구됩니다.선택적 매개 변수입니다.
호출된 함수에 대한 입력_In__In_opt_
호출된 함수에 대한 입력과 호출자에 출력_Inout__Inout_opt_
호출자에 출력_Out__Out_opt_
호출자에 포인터 출력_Outptr__Outptr_opt_

이러한 주석을 사용할 수 있는 초기화되지 않은 값을 확인하고 공식적이고 정확한 방식으로 잘못된 null 포인터를 사용 합니다. 필수 매개 변수에 NULL을 전달하는 것은 충돌이 발생할 수 있나 "실패" 오류 코드 반환이 발생할 수 있습니다. 어느 쪽이건 함수에서의 작업을 계속할 수 없습니다.

이 여기서 기본 SAL 주석 코드 예제를 보여 줍니다.

Visual Studio 코드 분석 도구를 사용하여 오류 찾기

예제에서는 Visual Studio 코드 분석 도구가 코드 오류를 찾기 위해 SAL 주석과 함께 사용 됩니다. 작업을 수행 하는 방법은 다음과 같습니다.

Visual Studio 코드 분석 도구 및 SAL을 사용하려면
  1. Visual Studio SAL 주석을 포함 하는 C++ 프로젝트를 엽니다.

  2. 선택 메뉴 모음에서 빌드를 선택하고, 솔루션에 대해 코드 분석 실행을 합니다.

    이 섹션에 _In_ 예제를 참조 하십시오. 코드 분석을 실행 하는 경우 이 경고가 표시 됩니다.

    C6387 잘못된 매개 변수 값
    '파인트'는 ()'에 '0' 수: 함수 'InCallee'에 대한 사양을 준수하지 않습니다.

예: _In_ 주석

_In_ 주석을 나타냅니다.:

  • 매개 변수가 유효해야 하고 수정되지 않습니다.

  • 함수는 단일 요소 버퍼에서 읽을 것 입니다.

  • 호출자가 버퍼를 제공하고 초기화해야만 합니다.

  • _In_ "읽기 전용"을 지정합니다. _In_ 을 _Inout_ 주석을 대신에 갖고 있어야만 하는 매개변수에 적용하는 것은 흔히 실수하는 것입니다.

  • _In_는 포인터가 아닌 스칼라에 분석기에서 무시하지만 허용 됩니다.

void InCallee(_In_ int *pInt)  
{  
   int i = *pInt;  
}  
  
void GoodInCaller()  
{  
   int *pInt = new int;  
   *pInt = 5;  
  
   InCallee(pInt);  
   delete pInt;     
}  
  
void BadInCaller()  
{  
   int *pInt = NULL;  
   InCallee(pInt); // pInt should not be NULL  
}  
  

이 예제에서 Visual Studio 코드 분석을 사용하는 경우 호출자는 pInt에 대해 초기화된 버퍼에 Null 포인터 전달을 확인합니다. 이 경우 pInt 는 NULL 일 수 없습니다.

예: _In_opt_ 주석

_In_opt_ 는 _In_과 같은, 입력된 매개 변수는 NULL이 될 수 있기 때문에 따라서 함수는 이것을 확인해야 한다는 점이 다릅니다.

  
void GoodInOptCallee(_In_opt_ int *pInt)  
{  
   if(pInt != NULL) {  
      int i = *pInt;  
   }  
}  
  
void BadInOptCallee(_In_opt_ int *pInt)  
{  
   int i = *pInt; // Dereferencing NULL pointer ‘pInt’  
}  
  
void InOptCaller()  
{  
   int *pInt = NULL;  
   GoodInOptCallee(pInt);  
   BadInOptCallee(pInt);  
}  
  

Visual Studio 코드 분석 함수는 버퍼에 액세스하기 전에 NULL에 대해 함수를 확인합니다.

예: _Out_ 주석

_Out_ 는 요소 버퍼를 가리키는 NULL 포인터에 전달되고 함수는 요소를 초기화 하는 일반적인 시나리오를 지원합니다. 호출자가 호출; 하기 전에 버퍼를 초기화 하지 않아도 호출된 함수가 반환하기 전에 초기화하는 데 약속을 합니다.

  
void GoodOutCallee(_Out_ int *pInt)  
{  
   *pInt = 5;  
}  
  
void BadOutCallee(_Out_ int *pInt)  
{  
   // Did not initialize pInt buffer before returning!  
}  
  
void OutCaller()  
{  
   int *pInt = new int;  
   GoodOutCallee(pInt);  
   BadOutCallee(pInt);  
   delete pInt;  
}  
  

Visual Studio 코드 분석 도구는 호출자가 pInt 에 대한 버퍼에 NULL 포인터를 전달 확인하고 반환하기 전에 해당 함수에 의해 버퍼는 초기화됩니다.

예: _Out_opt_ 주석

_Out_opt_ 는 _Out_과 같은, 입력된 매개 변수는 NULL이 될 수 있기 때문에 따라서 함수는 이것을 확인해야 한다는 점이 다릅니다.

  
void GoodOutOptCallee(_Out_opt_ int *pInt)  
{  
   if (pInt != NULL) {  
      *pInt = 5;  
   }  
}  
  
void BadOutOptCallee(_Out_opt_ int *pInt)  
{  
   *pInt = 5; // Dereferencing NULL pointer ‘pInt’  
}  
  
void OutOptCaller()  
{  
   int *pInt = NULL;  
   GoodOutOptCallee(pInt);  
   BadOutOptCallee(pInt);  
}  
  

Visual Studio 코드 분석은 pInt 이 역참조 되기 전에 NULL에 대해 함수를 검사하고 pInt 이 NULL이 아닐 때, 반환하기 전에 함수에 의해 버퍼는 초기화 됩니다.

예: _Inout_ 주석

_Inout_ 는 함수에 의해 변경되는 포인터 매개 변수는 주석을 추가하는 데 사용됩니다. 포인터를 호출하기 전에 유효한 초기화 데이터를 가리켜야 변경될 경우에 여전히 있어야 올바른 값을 반환 합니다. 주석은 함수를 자유롭게 읽고 쓰는 요소가 하나인 버퍼를 지정합니다. 호출자가 버퍼를 제공하고 초기화해야만 합니다.

System_CAPS_ICON_note.jpg 참고

_Out_ 와 마찬가지로, _Inout_ 는 수정할 수 있는 값을 적용해야만 합니다.

  
void InOutCallee(_Inout_ int *pInt)  
{  
   int i = *pInt;  
   *pInt = 6;  
}  
  
void InOutCaller()  
{  
   int *pInt = new int;  
   *pInt = 5;  
   InOutCallee(pInt);  
   delete pInt;  
}  
  
void BadInOutCaller()  
{  
   int *pInt = NULL;  
   InOutCallee(pInt); // ‘pInt’ should not be NULL  
}  
  

Visual Studio 코드 분석은 호출자가 pInt 에 대한 초기화된 버퍼에 non-NULL 포인터를 전달하는 호출자인지 확인하고 반환하기 전에, pInt 은 여전히 non-NULL 이고 버퍼는 초기화 됩니다.

예: _Inout_opt_ 주석

_Inout_opt_ 은 _Inout_ 과 같고, 입력된 매개 변수가 NULL이 되는 것을 허용하는 것을 제외하므로 함수는 이것을 확인해야만 합니다.

  
void GoodInOutOptCallee(_Inout_opt_ int *pInt)  
{  
   if(pInt != NULL) {  
      int i = *pInt;  
      *pInt = 6;  
   }  
}  
  
void BadInOutOptCallee(_Inout_opt_ int *pInt)  
{  
   int i = *pInt; // Dereferencing NULL pointer ‘pInt’  
   *pInt = 6;  
}  
  
void InOutOptCaller()  
{  
   int *pInt = NULL;  
   GoodInOutOptCallee(pInt);  
   BadInOutOptCallee(pInt);  
}  
  

Visual Studio 코드 분석은 pInt 이 NULL이 아닐 때 버퍼에 접근하기 전에 NULL에 대해 확인하는 함수를 검사하고 반환하기 전에 함수에 의해 버퍼는 초기화 됩니다.

예: _Outptr_ 주석

_Outptr_ 은 포인터 반환으로 간주되는 매개 변수에 주석을 지정하는 데 사용됩니다. NULL이 아니어야만 하는 매개 변수이고, 자체 안에 non-NILL 포인터를 반환하는 호출된 된 함수이고 초기화된 데이터에 그 포인터를 가리킵니다.

  
void GoodOutPtrCallee(_Outptr_ int **pInt)  
{  
   int *pInt2 = new int;  
   *pInt2 = 5;  
  
   *pInt = pInt2;  
}  
  
void BadOutPtrCallee(_Outptr_ int **pInt)  
{  
   int *pInt2 = new int;  
   // Did not initialize pInt buffer before returning!  
   *pInt = pInt2;  
}  
  
void OutPtrCaller()  
{  
   int *pInt = NULL;  
   GoodOutPtrCallee(&pInt);  
   BadOutPtrCallee(&pInt);  
}  
  

Visual Studio 코드 분석 도구는 *pInt에 대해 호출자가 non-NULL 포인터를 전달하는 것을 확인하고 이것을 반환하기 전에 함수에 의해 버퍼가 초기화됩니다.

예: _Outptr_opt_ 주석

_Outptr_opt_ 은 _Outptr_ 와 같고 선택적 매개 변수를 제외합니다-호출자는 매개변수에 대해 NULL 포인터에 전달할 수 있습니다.

  
void GoodOutPtrOptCallee(_Outptr_opt_ int **pInt)  
{  
   int *pInt2 = new int;  
   *pInt2 = 6;  
  
   if(pInt != NULL) {  
      *pInt = pInt2;  
   }  
}  
  
void BadOutPtrOptCallee(_Outptr_opt_ int **pInt)  
{  
   int *pInt2 = new int;  
   *pInt2 = 6;  
   *pInt = pInt2; // Dereferencing NULL pointer ‘pInt’  
}  
  
void OutPtrOptCaller()  
{  
   int **ppInt = NULL;  
   GoodOutPtrOptCallee(ppInt);  
   BadOutPtrOptCallee(ppInt);  
}  
  

Visual Studio 코드 분석은 *pInt 이 역참조 되기 전에 NULL에 대해 함수를 검사하고, 반환하기 전에 함수에 의해 버퍼는 초기화 됩니다.

예: _Out_과 _Success_ 주석의 조합

대부분의 개체에 주석은 적용할 수 있습니다. 특히 전체 함수에 주석을 달 수 있습니다. 함수의 가장 큰 특징 중 하나는 성공 또는 실패할 수 있는지입니다. 하지만 연결 버퍼 크기와 같은 C/C++ 함수 성공 또는 실패를 표현할 수 없습니다. _Success_ 주석의 사용에 의해 함수의 성공처럼 보인다고 말할 수 있습니다. _Success_ 주석에 대한 매개 변수는 함수가 성공 했음을 나타낼 때의 식일 뿐입니다. 식 주석이 파서가 처리할 수 있는 모든 것이 될 수 있습니다. 함수가 반환 된 후 주석의 효과 함수가 성공적으로 실행 될 때만 적용 됩니다. 예제가 어떻게 옳은 것을 하는 _Out_ 와 어떻게 _Success_ 상호 작용을 하는지 보여줍니다. return 키워드를 사용 하면 반환 값을 나타냅니다.

  
_Success_(return != false) // Can also be stated as _Success_(return)  
bool GetValue(_Out_ int *pInt, bool flag)  
{  
   if(flag) {  
      *pInt = 5;  
      return true;  
   } else {  
      return false;  
   }  
}  
  

_Out_ 주석은 Visual Studio 코드 분석이 pInt에 대해 버퍼에 non-NULL 포인터를 전달하는 호출자를 확인하고, 그 버퍼는 반환하기 전에 함수에 의해 초기화됩니다.

기존 코드에 주석 추가

SAL은 보안 및 코드의 안정성을 개선하는 데 도움이 되는 강력한 기술입니다. SAL을 학습 한 후 일상 업무에 새로운 스킬을 적용할 수 있습니다. 새 코드에서 SAL 기반 사양을 전체 디자인하여 사용할 수 있고 이전 코드에서 점차적으로 주석을 추가할 수 있으며 업데이트할 때마다 늘려 혜택을 늘릴 수 있습니다.

Microsoft 공용 헤더들은 이미 주석 처리 됩니다. 따라서 프로젝트에 먼저 리프 노드 기능 및 이점을 최대한 활용 하려면 Win32 Api를 호출 하는 함수를 주석을 다는 것이 좋습니다.

주석을 달아야 하는 경우

예를 들면 다음과 같습니다.

  • 모든 포인터 매개 변수에 주석을 지정 합니다.

  • 코드 분석 및 버퍼 포인터 안전을 보장할 수 있도록 주석을 값 범위에 주석을 답니다.

  • 잠금 규칙과 잠금 부작용에 주석을 답니다. 자세한 내용은 잠금 동작에 주석 지정을 참조하십시오.

  • 드라이버 속성 및 기타 도메인 관련 속성에 주석을 답니다.

또는 모든 주석에 투명한 전체를 의도하고 주석을 단 것을 확인 하려면 쉽게 수행할 수 있도록 모든 매개 변수에 주석을 달 수 있습니다.

코드 분석 팀 블로그


반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함