-
BPF는 일반적으로 하나의 함수(main 함수)가 하나의 섹션이 되고, 하나의 섹션이 하나의 프로그램이 된다. 그리고 커널에서 해당 프로그램을 실행할 때는 프로그램의 시작 위치가 메인함수의 시작 위치이기 때문에 간단히 처음부터 실행하면 된다.
-
하지만 아래와 같이 메인함수에서 공통함수를 호출하는 경우처럼 두 개 이상의 함수가 필요한 경우에는 프로그램의 시작 위치를 어떻게 보장할까? BPF는 이러한 상황에서 시작 위치 보장을 위해 서브프로그램이라는 기능을 제공한다.
-
아래 코드는 bcc의 filetop 예제코드이다. 해당 예제는
vfs_read
와vfs_write
커널함수에서 각각 사용할 두 개의 BPF 메인함수와 두 개의 함수에서 사용하는 공통함수(probe_entry)로 구성되어 있다. -
이를 컴파일한 결과는 아래와 같다.
-
위의 오브젝트를 보면 메인함수는 각각의 섹션에 위치해있지만 공통함수는
.text
섹션에 위치해있는 것을 볼 수 있다. (함수 선언 앞에 섹션을 지정하지 않으면 해당 함수는 기본적으로.text
섹션에 위치하게 된다.) -
일반적으로 BPF 프로그램을 로딩할 때는 하나의 특정 섹션을 지정해서 사용하는데, 위와 같이 메인함수에서 호출하는 함수가 다른 섹션에 존재할 때는 어떻게 동작하는 것일까?
-
이 질문에 대한 해답은 libbpf를 기준으로 알아보자. 아래 재배치 목록을 살펴보자.
-
위의 재배치 목록 중 두 번째 항목은
kprobe/vfs_write
섹션의 0x18 오프셋에 해당하는(3:)
명령어에서.text
섹션을 참조한다는 의미이다. -
그리고
kprobe/vfs_write
섹션의 (3:
) 명령어의 인자를 보면-1
(0xffffffff
)인 값인데, 이는 해당 섹션(.text
)에서-1
에1
을 더한 위치를 의미한다. -
즉,
(3:)
명령어는.text
섹션의0x0
오프셋을 호출(call)하라는 뜻이다. 이러한 재배치 정보를 이용하여 실제 커널에 전달할 BPF 코드를 작성하는 과정은 다음과 같다. -
일단 심볼 테이블을 이용하여 코드를 포함하고 있는 섹션에 있는 함수들을 모두 프로그램으로 등록한다. 위의 심볼 테이블을 보면,
kprobe/vfs_read
섹션에 있는vfs_read_entry
함수,kprobe/vfs_write
섹션에 있는vfs_write_entry
함수, 그리고.text
섹션에 있는probe_entry
함수를 각각 프로그램으로 등록한다. -
이때
.text
섹션에 있는 함수들은 모두 서브프로그램으로 등록이 되는데, 이는 커널에 직접 로딩되는 프로그램이 아니고, 다른 프로그램에서 호출해서 사용하는 프로그램이라는 의미이다. -
그리고 나머지 커널에 직접 로딩되는 프로그램들은 앞의 재배치 정보(
.text
섹션의 0x0 오프셋)와 프로그램 목록(.text
섹션의0x0
오프셋에 해당하는probe_entry
프로그램)을 이용하여 메인함수에서 호출하는 함수를 해당 프로그램의 뒤쪽에 추가하고, 해당 함수를 호출하는 명령어의 인자를 적절한 값으로 수정한다. -
이 과정은 메인함수에서 호출한 함수에서도 다른 함수를 호출할 수 있기 때문에 재귀적으로 일어난다. 아래는 커널에 로딩된 프로그램(BPF 코드)을 덤프한 것이다.
-
위의 코드를 보면, 맨 앞 부분에 메인함수가 위치해있고, 바로 이어서 공통함수(probe_entry)가 위치해있는 것을 볼 수 있다.
-
그리고 공통함수를 호출하는 (
3:
) 명령어를 보면, 공통함수의 시작 위치가 (6:
) 명령어이기 때문에 다음 명령어(4:
)의 주소값(Program Counter)을 기준으로 2 를 더한 위치를 호출하는 것을 볼 수 있다. -
마지막으로 BPF 코드를 실제 동작 가능한 머신코드(x86)로 JIT(Just-In-Time) 컴파일한 결과물은 아래와 같다.
-
리눅스 커널에서는 앞의 BPF 코드를 한번에 컴파일하지 않고, 메인함수와 공통함수를 서브프로그램으로 나눈 다음 각각 컴파일한다.
-
그리고 메인함수(
vfs_write_entry
)에서 공통함수(probe_entry
)를 호출하는 명령어(18:
)를 보면, 다음 명령어(0x1d:
)의 위치에서 공통함수를 JIT 컴파일한 결과물이 저장된 메모리 위치까지의 거리(오프셋)를 이용하여 호출하는 것을 볼 수 있다.
참고