[Linux Kernel] Sparse 정적 분석 도구

문연수·7일 전
0

Linux Kernel

목록 보기
3/3
post-thumbnail

 리눅스 커널에는 Sparse 라는 이름의 정적 분석 도구가 존재한다. SparseSemantic parser(구문 파서) 의 준말로 Linus TorvaldsLinux Kernel 개발에 사용하려고 만든 여러 도구들 중 하나이다. (이 중에는 git 도 있다.)

 여기에서 정적 분석 도구 (Static Analysis Tool) 란 프로그램 실행 없이 코드만 보고 뚝딱 문제를 찾아주는 훌륭한 도구들을 일컫는 말이다. 이 친구의 기능은 참으로 다양하고, 복잡하고, 다른 의미에서는 좀 빡이 치는데 필자는 여러 기능들 중 락(lock) 과 관련된 내용을 중심으로 이 툴을 소개하려 한다.

1. SPARSE 사용해서 빌드하기

- 설치

 일단 SPARSE 를 쓰려면 Sparse 를 설치해야 한다. 당연한 얘기이다.

sudo dnf install sparse

독자들이 사용하는 배포판의 패키지 매니저를 통해 설치할 수 있다. 보통 등록되어 있어서 설치가 가능할 것이다. 이 패키지가 없다면 직접 프로젝트clone 하여 설치하길 바란다.

- 빌드하기

 우리는 Sparse 를 사용하기 위해 커널을 빌드해야 한다. 구문 분석만 하면 될 것이지 왜 빌드를 하느냐? 하면 필자도 잘 모르겠다. 다만 유추할 수 있는 것은 커널 빌드 옵션에 따라 코드가 바뀌기도 하고 파일이 추가되고 하니 어찌 보면 당연할 수도 있겠다. (궁금해서 실제로 V=1 옵션으로 확인해봤는데 전달되는 인자가 config 에 영향을 받는다)

# Sparse 를 사용한 빌드
make C=1

# 위에서 사용한 명령어
make O=../build/x86_64 -j$(nproc) W=1 C=2 2>&1 | tee ../build/x86_64/output

makeC (Checker) 변수의 값을 1 로 바꿈으로써 Sparse 를 통한 구문 분석을 수행할 수 있다. 만약 이미 빌드가 완료되었는데 다시 확인하고 싶다면 C=2 옵션을 주어 확인하는 것도 가능하다. 필자의 경우 Sparse 결과를 계속 확인하기 위해서 redirection 한 뒤 파일로 저장했다.

 여기에서 필자가 관심을 가지는 부분은 lock 이다. 182행부터 187행까지가 Sparse 프로그램이 뱉은 경고 문구이다. 이런 부분을 찾아보면 된다.

2. 문제 확인하기

 우선 Sparse 가 대체 어떻게 lock 과 관련한 문제를 추적하는지 설명하는게 좋을 것 같다. 일반적으로 lock 은 걸고 난 뒤에 반드시 unlock 을 해야 한다. 이게 기본이다. Sparse 는 이 단순한 규칙에 따라 lock 이 걸리고 잠기는 횟수를 추적한다. 기본 원칙은 다음과 같다:

함수에 진입했을 때 락을 걸었다면 나가기 전에는 풀어야 한다. 또한 락을 건 횟수 만큼 풀어야 한다.

이 원칙을 지키지 않았을 경우에 아래의 세 가지 중 하나의 경고를 출력한다:

  1. wrong count at exit
  2. unexpected unlock
  3. different lock contexts for basic block

- 너 안 풀었잖아

wrong count at exitlock 을 풀지 않고 함수를 탈출했을 때 발생한다. 실제 코드를 보면서 확인해보자. v6.11icmp.c 코드에서 Sparse 가 뱉은 경고 메세지이다:

warning: context imbalance in 'icmpv6_xmit_lock' - wrong count at exit

실제 코드는 어떨까?

 아하! 락을 걸고 탈출을 안 했구나! if 에 걸리지 않았다면 이하의 코드는 spin_lock 이 걸린 상태이므로 탈출 전에 반드시 락을 해제하는 코드가 있어야 하는데 없어서 경고가 발생한 것이다. 하지만 함수의 이름을 잘 보면 느낌이 올 것이다. 이건 의도된 것이다. 또 다른 경고 메세지를 보면서 더 자세히 확인해보자.

- 뭐야 나 안 잠겼어요

unexpected unlocklock 이 잠기지 않았는데 unlock 을 수행한 경우 발생한다. 위에서

warning: context imbalance in 'icmpv6_xmit_unlock' - unexpected unlock

어디서 많이 본 이름의 함수에서 비슷한 경고가 발생했다. 코드를 보자:

 이번에는 spin_lock 을 푸는 코드만 있고 락을 거는 코드가 없다. 실제로는 icmpv6_xmit_lock()icmpv6_xmit_unlock() 함수가 페어로 사용되기 때문에 이건 의도된 설계이다. 하지만 Sparse 의 관점에서 보자면 문제다. 이 친구는 멍청해서 이게 의도된 설계라는 사실을 인식하지 못하기 때문이다.

3. 문제 해결하기

- 이게 맞는거야

 진짜 락이 잘못 걸리고 풀렸다면 올바르게 고쳐야 하지만 위와 같은 경우는 Sparse 가 멍청해서 발생한 문제이다. 이런 경우에는 어떻게 하면 될까? 이때에는 annotation 을 붙이면 된다:

__releases() annotation 을 붙여서 컴파일러에게 다음의 사실을 알린다:

이 함수(icmpv6_xmit_unlock) 는 이게 맞는거야. 다른 함수가 이 함수를 호출하면 unlock 했다고 생각해.

반대로 락을 획득하고 그냥 탈출하는 함수의 경우 __acquires() 를 붙이면 된다.

static void icmpv6_xmit_unlock(struct sock *sk)
	__releases(&sk->sk_lock.slock)
{
	sock_net_set(sk, &init_net);
	spin_unlock(&sk->sk_lock.slock);
}

이럼 다음과 같이 문제가...

늘어난다! 와우!

- 뭘 맞아? 한대 맞을래?

 확실히 icmpv6_xmit_unlock 과 관련한 경고는 사라졌지만 새로운 경고 2개가 추가되었다. 638 line 을 보자:

 이게 638 line 의 코드인데 보는 바와 같이 함수 끝에서 unlock 을 했다. 그럼 위에서 lock 을 안 걸었나? 다른 말로 하자면 icmpv6_xmit_lock() 을 호출하지 않았나? 이렇게 생각할 수 있다.

 제대로 걸었다. icmpv6_xmit_lock() 함수는 락을 거는데 실패하면 NULL 을 반환하므로 if 이하부터는 spin_lock 이 걸린 상태이다. trylock 이 문제일까?

 막말로 icmpv6_xmit_lock() 함수의 trylocklock 으로 바꾼 뒤에 __acquires() 추가해도 에러는 계속 발생한다.


 또 __must_hold() 도 있는데 이건 함수 진입 전부터 이미 잠긴 락을 풀고 거는 경우, 그러니까 계속 락을 붙들고 있어야 하는(must_hold) 함수에 쓸 수 있는 annotation 이다.

 위 코드를 보면 잠기지도 않은 락을 풀어 버리는 것(6286 line)처럼 보이지만 실제로는 이 함수 호출 전에 이미 락을 걸고 진입한 상태라고 이해하면 된다.

3. Sparse 의 한계

 마지막으로 하나 남은 경고 메세지가 출력되었다:

warning: context imbalance in 'icmp6_send' - different lock contexts for basic block

 이 경고 메세지는 분기에 따라 락이 제대로 풀리거나 잠기지 않는 상황, 혹은! sparse 본인이 락의 추적 관리가 안되는 상황 에서 발생한다. 위의 icmp6_send 함수는 goto 문으로 함수를 탈출하는 구성을 가지는데 이런 경우에 위와 같은 경고가 발생한다.

 또한 sparse 는 데이터를 추적하는 기능도 없기 때문에 다음의 코드에서도 문제가 생긴다:

 우리는 직관적으로 556 linelock 을 잠그는데 실패한 경우에 실행된다는 사실을 쉽게 알 수 있지만 sparse 의 관점에서는 sk 의 값의 추적을 하지 않으므로 실행 여부를 알 수 없다. 이런 경우에는 코드 리팩터링을 통해 Sparse 가 알아 쳐먹을 수 있는 형태로 코드의 구성을 바꿔야 한다.

ㅇㅈ ㅋㅋ


https://patchwork.kernel.org/project/netdevbpf/patch/20240919142149.282175-1-yyyynoom@gmail.com/

패치 올린 뒤에 Simon 한테 개털리고 수정하다가 빡쳐서 분노와 진실로 후려갈긴 장문의 포스팅. 모두에게 도움이 되었으면 합니다.

출처

https://linux-kernel.vger.kernel.narkive.com/AQEc5UkN/sparse-context-checking
https://sparse.docs.kernel.org/en/v0.6.4/
https://lwn.net/Articles/689907/
https://www.kernel.org/doc/html/v4.12/dev-tools/sparse.html
https://groups.google.com/g/linux.kernel/c/d0IoCx0Wwus?pli=1
https://julesbashizi.wordpress.com/2020/02/29/types-context-imbalance-warnings-and-how-to-annotate/

profile
2000.11.30

0개의 댓글