산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Industrial Internet of Things >> 임베디드

기능 안전이라는 외계의 컴파일러

여러 부문에서 기능 안전의 세계는 개발자에게 새로운 요구 사항을 제시합니다. 기능적으로 안전한 코드에는 다양한 원인으로 인해 발생할 수 있는 예기치 않은 이벤트를 방어하기 위한 방어 코드가 포함되어야 합니다. 예를 들어, 코딩 오류 또는 우주선 이벤트로 인한 메모리 손상은 코드 논리에 따라 "불가능한" 코드 경로의 실행으로 이어질 수 있습니다. 고급 언어, 특히 C 및 C++에는 코드가 준수하는 언어 사양에 따라 동작이 규정되지 않는 놀라운 기능이 포함되어 있습니다. 이 정의되지 않은 동작은 기능적으로 안전한 응용 프로그램에서 허용되지 않는 예기치 않은 잠재적으로 재앙적인 결과를 초래할 수 있습니다. 이러한 이유로 표준에서는 방어적인 코딩이 적용되고, 해당 코드가 테스트 가능하며, 적절한 코드 적용 범위를 대조할 수 있고, 시스템이 완전하고 고유하게 구현하도록 보장하기 위해 애플리케이션 코드를 요구 사항에 대해 추적할 수 있어야 합니다.

또한 코드는 높은 수준의 코드 적용 범위를 달성해야 하며 일부 부문(특히 자동차)에서는 설계에 정교한 외부 진단, 교정 및 개발 도구가 필요한 것이 일반적입니다. 발생하는 문제는 방어 코딩 및 외부 데이터 액세스와 같은 관행이 컴파일러가 인식하는 세계의 일부가 아니라는 것입니다. 예를 들어, C와 C++ 모두 메모리 손상을 허용하지 않습니다. 따라서 메모리 손상을 방지하도록 설계된 코드에 손상이 없을 때 액세스할 수 없는 경우 코드가 최적화될 때 단순히 무시될 수 있습니다. 결과적으로 방어 코드는 "최적화"되지 않는 경우 구문 및 의미론적으로 도달할 수 있어야 합니다.

정의되지 않은 동작의 인스턴스도 놀라움을 유발할 수 있습니다. 단순히 피해야 한다고 제안하기는 쉽지만 식별하기가 어려운 경우가 많습니다. 그것이 존재하는 경우 컴파일된 실행 코드의 동작이 개발자의 의도와 일치한다는 보장은 없습니다. 디버깅 도구에서 사용하는 데이터에 대한 "백도어" 액세스는 언어가 허용하지 않는 또 다른 상황을 나타내며, 따라서 예기치 않은 결과를 초래할 수 있습니다.

컴파일러 최적화는 이러한 모든 영역에 중대한 영향을 미칠 수 있습니다. 그 중 어느 것도 컴파일러 공급업체의 책임이 아니기 때문입니다. 최적화는 "실행 불가능성"과 관련된 위치, 즉 가능한 입력 값 세트로 테스트 및 확인할 수 없는 경로에 존재하는 경우 명백히 방어적인 코드가 제거되는 결과를 초래할 수 있습니다. 더욱 놀라운 것은 단위 테스트 중에 존재하는 것으로 보이는 방어 코드가 시스템 실행 파일이 구성될 때 제거될 수 있다는 것입니다. 따라서 단위 테스트 중에 방어 코드의 적용 범위가 달성되었다고 해서 완성된 시스템에 존재한다고 보장할 수는 없습니다.

기능 안전이라는 이상한 나라에서 컴파일러는 요소에서 벗어날 수 있습니다. 그렇기 때문에 OCV(객체 코드 검증)는 실패와 관련된 심각한 결과가 있는 모든 시스템, 그리고 실제로 모범 사례만 있으면 충분한 시스템에 대한 모범 사례를 나타냅니다.

컴파일 전후

IEC 61508, ISO 26262, IEC 62304, MISRA C 및 C++와 같은 기능 안전, 보안 및 코딩 표준에 의해 옹호되는 검증 및 검증 관행은 요구 사항 기반 테스트 중에 애플리케이션 소스 코드가 얼마나 실행되는지 보여주는 데 상당한 중점을 두고 있습니다.

경험에 따르면 코드가 올바르게 수행되는 것으로 표시되면 현장에서 실패할 확률이 상당히 낮아집니다. 그러나 이 칭찬할 만한 노력의 초점이 고급 소스 코드(언어에 관계 없이)에 있기 때문에 이러한 접근 방식은 컴파일러가 개발자가 수행한 내용을 정확하게 재현하는 개체 코드를 생성하는 능력에 큰 믿음을 둡니다. 예정된. 가장 중요한 애플리케이션에서는 이러한 묵시적 가정이 정당화될 수 없습니다.

개체 코드의 제어 및 데이터 흐름은 그것이 파생된 소스 코드의 정확한 미러가 아닐 수 있으며 모든 소스 코드 경로가 안정적으로 실행될 수 있음을 증명하는 것이 개체 코드의 동일한 것을 증명하지 않습니다. . 오브젝트 코드와 어셈블러 사이에 1:1 관계가 있다는 점을 감안할 때 소스 코드와 어셈블리 코드를 비교하면 알 수 있습니다. 오른쪽의 어셈블러 코드가 왼쪽의 소스 코드에서 생성된 그림 1의 예를 생각해 보세요(최적화를 비활성화한 TI 컴파일러 사용).


그림 1:오른쪽의 어셈블러 코드는 왼쪽의 소스 코드에서 생성되었으며 소스 코드와 어셈블리 코드 간의 비교를 보여줍니다. (출처:LDRA)

나중에 설명하는 것처럼 이 소스 코드가 컴파일될 때 결과 어셈블러 코드의 흐름 그래프는 소스의 흐름 그래프와 상당히 다릅니다. 왜냐하면 C 또는 C++ 컴파일러가 따르는 규칙은 바이너리가 제공되는 경우 원하는 방식으로 코드를 수정할 수 있도록 허용하기 때문입니다. 마치 "동일한 것처럼" 행동합니다.

대부분의 상황에서 그 원칙은 전적으로 수용 가능하지만 예외가 있습니다. 컴파일러 최적화는 기본적으로 코드의 내부 표현에 적용되는 수학적 변환입니다. 예를 들어 코드 기반에 정의되지 않은 동작의 인스턴스가 포함된 경우와 같이 이러한 변환은 가정이 유지되지 않으면 "잘못됩니다".

항공 우주 산업에서 사용되는 DO-178C만이 개발자 의도와 실행 가능한 동작 사이의 위험한 불일치 가능성에 초점을 맞춥니다. 그럼에도 불구하고 이러한 불일치를 감지하지 못한 채 남겨둘 가능성이 분명한 해결 방법을 지지하는 사람을 찾는 것은 어렵지 않습니다. 그러나 이러한 접근 방식은 허용되지만 소스 코드와 개체 코드 간의 차이가 모든 중요한 응용 프로그램에서 치명적인 결과를 초래할 수 있다는 사실은 여전히 ​​남아 있습니다.

개발자 의도 대 실행 가능한 행동

소스 코드 흐름과 개체 코드 흐름 간의 명확한 차이점에도 불구하고 주요 관심사는 아닙니다. 컴파일러는 일반적으로 매우 안정적인 응용 프로그램이며 다른 소프트웨어와 마찬가지로 버그가 있을 수 있지만 컴파일러의 구현은 일반적으로 설계 요구 사항을 충족합니다. 문제는 이러한 설계 요구 사항이 항상 기능적으로 안전한 시스템의 요구 사항을 반영하는 것은 아니라는 것입니다.

간단히 말해서 컴파일러는 기능적으로 작성자의 목표에 부합한다고 가정할 수 있습니다. 그러나 아래 그림 2에서 CLANG 컴파일러로 컴파일한 결과로 생성된 예와 같이 이것이 완전히 원하거나 예상한 것은 아닐 수 있습니다.


그림 2는 CLANG 컴파일러(출처:LDRA)를 사용한 컴파일을 보여줍니다.

'error' 함수에 대한 방어 호출이 어셈블러 코드에서 표현되지 않은 것이 분명합니다.

'state' 객체는 초기화되고 'S0' 및 'S1'의 경우에만 수정되므로 컴파일러는 'state'에 지정된 값이 'S0' 및 'S1'뿐이라고 추론할 수 있습니다. 컴파일러 손상이 없다고 가정하고 'state'가 다른 값을 보유하지 않을 것이기 때문에 'default'가 필요하지 않다는 결론을 내립니다. 실제로 컴파일러는 정확히 그 가정을 합니다.

컴파일러는 또한 실제 개체(13 및 23)의 값이 숫자 컨텍스트에서 사용되지 않기 때문에 단순히 0과 1 값을 사용하여 상태 사이를 전환한 다음 배타적 "or"를 사용하여 업데이트하기로 결정했습니다. 상태 값. 바이너리는 "마치" 의무를 준수하며 코드는 빠르고 간결합니다. 참조 조건 내에서 컴파일러는 훌륭한 작업을 수행했습니다.

이 동작은 링커 메모리 맵 파일을 사용하여 개체에 간접적으로 액세스하고 디버거를 통해 메모리에 직접 액세스하는 "교정" 도구에 영향을 미칩니다. 다시 말하지만, 이러한 고려 사항은 컴파일러의 책임이 아니므로 최적화 및/또는 코드 생성 중에 고려되지 않습니다.

이제 코드가 변경되지 않았지만 컴파일러에 제공된 코드의 컨텍스트가 그림 3과 같이 약간 변경되었다고 가정합니다.


그림 3:코드는 변경되지 않은 상태로 유지되지만 컴파일러에 제공되는 코드의 컨텍스트는 약간 변경됩니다. (출처:LDRA)

이제 상태 변수의 값을 정수로 반환하는 추가 함수가 있습니다. 이번에는 절대값 13과 23이 컴파일러에 제출된 코드에서 중요합니다. 그럼에도 불구하고 이러한 값은 업데이트 기능(변경되지 않은 상태로 유지됨) 내에서 조작되지 않으며 새로운 "f" 기능 내에서만 명백합니다.

요컨대, 컴파일러는 13과 23의 값을 어디에 사용해야 하는지에 대해 (올바른) 계속해서 가치 판단을 내리며, 모든 상황에 적용되는 것은 아닙니다.

상태 변수에 대한 포인터를 반환하도록 새 함수가 변경되면 어셈블러 코드가 크게 변경됩니다. 이제 포인터를 통한 별칭 액세스의 가능성이 있기 때문에 컴파일러는 더 이상 상태 개체에 무슨 일이 일어나고 있는지 추론할 수 없습니다. 아래 그림 4와 같이 13과 23의 값이 중요하지 않다고 결론을 내릴 수 없으므로 이제 어셈블러 내에서 명시적으로 표현됩니다.


그림 4:상태 변수에 대한 포인터를 반환하도록 새 함수가 변경되면 어셈블러 코드가 크게 변경됩니다. 13과 23의 값이 중요하지 않다는 결론을 내릴 수 없으므로 이제 어셈블러 내에서 명시적으로 표현됩니다(출처:LDRA).

소스 코드 단위 테스트에 대한 의미

이제 가상 단위 테스트 하니스의 맥락에서 예제를 고려하십시오. 테스트 중인 코드에 액세스하기 위한 하네스가 필요하기 때문에 상태 변수의 값이 조작되고 결과적으로 기본값이 "최적화"되지 않습니다. 이러한 접근 방식은 소스 코드의 나머지 부분과 관련된 컨텍스트가 없고 모든 것에 액세스할 수 있도록 하는 데 필요한 테스트 도구에서 완전히 정당화되지만 부작용으로 컴파일러가 방어 코드를 합법적으로 누락한 것을 위장할 수 있습니다.

컴파일러는 임의의 값이 포인터를 통해 상태 변수에 기록된다는 것을 인식하고 다시 13과 23의 값이 중요하지 않다고 결론을 내릴 수 없습니다. 결과적으로 이제 어셈블러 내에서 명시적으로 표현됩니다. 이 경우 S0 및 S1이 상태 변수에 대해 가능한 유일한 값을 나타낸다고 결론지을 수 없으며, 이는 기본 경로가 실행 가능함을 의미합니다. 그림 5에서 볼 수 있듯이 상태 변수의 조작은 목적을 달성하고 오류 함수에 대한 호출은 이제 어셈블러에서 분명합니다.


그림 5:상태 변수의 조작이 목적을 달성하고 오류 함수에 대한 호출이 이제 어셈블러에서 명확해집니다. (출처:LDRA)

그러나 이 조작은 제품 내에서 배송될 코드에는 존재하지 않으므로 error() 호출은 전체 시스템에서 실제로 존재하지 않습니다.

객체 코드 검증의 중요성

개체 코드 확인이 이 난제를 해결하는 데 어떻게 도움이 되는지 설명하기 위해 그림 6에 표시된 첫 번째 예제 코드 스니펫을 다시 살펴보세요.


그림 6:객체 코드 검증이 전체 시스템에 오류 호출이 없는 문제를 해결하는 데 어떻게 도움이 되는지 보여줍니다. (출처:LDRA)

이 C 코드는 다음과 같이 단일 호출을 통해 100% 소스 코드 적용 범위를 달성하는 것으로 입증될 수 있습니다.

f_while4(0,3);

코드는 라인당 단일 작업으로 재형식화될 수 있고 흐름도에 "기본 블록" 노드의 모음으로 표시될 수 있으며, 각 노드는 일련의 직선 코드입니다. 기본 블록 간의 관계는 노드 간의 방향 모서리를 사용하여 그림 7에 표시됩니다.


그림 7:노드 간의 방향성 에지를 사용하여 기본 블록 간의 관계를 보여줍니다. (출처:LDRA)

코드를 컴파일하면 아래와 같은 결과가 나온다(그림 8). 흐름 그래프의 파란색 요소는 f_while4(0,3) 호출에 의해 실행되지 않은 코드를 나타냅니다.

이 메커니즘은 개체 코드와 어셈블러 코드 간의 일대일 관계를 활용하여 개체 코드의 어떤 부분이 실행되지 않았는지 노출하고 테스터가 추가 테스트를 고안하고 완전한 어셈블러 코드 적용 범위를 달성하여 개체 코드 검증을 달성하도록 합니다.


그림 8:코드를 컴파일했을 때의 결과를 보여줍니다. 흐름 그래프의 파란색 요소는 f_while4(0,3) 호출에 의해 실행되지 않은 코드를 나타냅니다. (출처:LDRA)

분명히 개체 코드 검증은 컴파일러가 설계 규칙을 따르고 부주의하게 개발자의 최선의 의도를 우회하는 것을 방지할 수 없습니다. 그러나 부주의한 사람들의 주의를 끌기 위해 그러한 불일치를 가져올 수 있으며 실제로 그렇게 합니다.

이제 앞의 "오류 호출" 예의 맥락에서 그 원칙을 고려하십시오. 물론 완성된 시스템의 소스 코드는 단위 테스트 수준에서 검증된 것과 동일하므로 이를 비교하면 아무것도 알 수 없습니다. 그러나 완성된 시스템에 대한 개체 코드 검증의 적용은 필수 동작이 개발자가 의도한 대로 표현된다는 보증을 제공하는 데 매우 중요합니다.

세계 최고의 모범 사례

컴파일러가 단위 테스트와 비교하여 테스트 하니스에서 코드를 다르게 처리한다면 소스 코드 단위 테스트 커버리지가 가치가 있습니까? 대답은 자격을 갖춘 "예"입니다. 많은 시스템이 이러한 인공물의 증거에 대해 인증되었으며 서비스에서 안전하고 신뢰할 수 있음이 입증되었습니다. 그러나 모든 부문에서 가장 중요한 시스템의 경우 개발 프로세스가 가장 상세한 조사를 견디고 모범 사례를 준수하려면 OCV로 소스 수준 단위 테스트 범위를 보완해야 합니다. 설계 기준을 충족한다고 가정하는 것이 합리적이지만 해당 기준에는 기능 안전 고려 사항이 포함되어 있지 않습니다. 개체 코드 검증은 현재 컴파일러 동작이 표준을 준수하지만 그럼에도 불구하고 상당한 부정적인 영향을 미칠 수 있는 기능 안전 세계에 대한 가장 확실한 접근 방식을 나타냅니다.


임베디드

  1. 전기 안전의 중요성
  2. 섬유 염료의 세계
  3. 직물 세계의 산성 염료 적용
  4. 염료 세계 살펴보기
  5. 안전 바구니의 다양한 용도
  6. 빠르게 진화하는 시뮬레이션 세계
  7. 세계의 제조 수도
  8. 가장 중요한 크레인 안전 수칙 5가지
  9. 안전 시스템에서 마찰 재료의 중요성
  10. 공장의 안전:지속적인 개선의 원천