genassym.sh - emit an assym.h file

FreeBSD 커널이 빌드되는 과정에서 대상 아키텍처에 대응하는 assym.h라는 파일을 생성하는 것이 있다. 이 파일은 각 아키텍처의 어셈블리 파일(.S)에서 포함(include)해 사용되는데, 여기에는 그 아키텍처의 각종 상수들이 정의되어 있다.

확장자가 대문자 S인 어셈블리 파일은 C 컴파일러(정확하게는 C preprocessor)에 의해 선처리(preprocess)되기 때문에 #define 지시자로 정의된 숫자 상수를 어셈블리 파일에서 포함해 사용하는 것은 문제가 없지만, C 컴파일러의 평가(evaluation)가 필요한 상수는 사용할 수 없다. 예를 들면,

#define PC_SIZEOF sizeof(struct pcpu)

이 PC_SIZEOF를 선처리하여 대체된 sizeof(struct pcpu)를 어셈블러가 이해할 수 없기 때문이다. C 코드와 어셈블리 코드가 많은 상수를 공유하는 커널에서는 이 문제를 해결하는 것이 중요하다.

FreeBSD에서 이 문제를 해결하는 방법은, 먼저 C 컴파일러의 평가가 필요한 상수들을 모아 genassym.c 파일에 아래와 같이 기술한다. (/sys/amd64/amd64/genassym.c)

ASSYM(PC_SIZEOF, sizeof(struct pcpu));
ASSYM(PC_PRVSPACE, offsetof(struct pcpu, pc_prvspace));
ASSYM(PC_CURTHREAD, offsetof(struct pcpu, pc_curthread));
...

ASSYM 매크로는 /sys/sys/assym.h에 아래와 같이 정의되어 있다.

#define ASSYM_BIAS       0x10000 /* avoid zero-length arrays */
#define ASSYM_ABS(value) ((value) < 0 ? -((value) + 1) + 1ULL : (value))

#define ASSYM(name, value)                                                    \
char name ## sign[((value) < 0 ? 1 : 0) + ASSYM_BIAS];                        \
char name ## w0[(ASSYM_ABS(value) & 0xFFFFU) + ASSYM_BIAS];                   \
char name ## w1[((ASSYM_ABS(value) & 0xFFFF0000UL) >> 16) + ASSYM_BIAS];      \
char name ## w2[((ASSYM_ABS(value) & 0xFFFF00000000ULL) >> 32) + ASSYM_BIAS]; \
char name ## w3[((ASSYM_ABS(value) & 0xFFFF000000000000ULL) >> 48) + ASSYM_BIAS]

즉, 컴파일러가 평가한 값을 부호(sign)와 4개의 워드(word) 단위로 분리하여 그 값 크기의 배열을 생성한다. 배열 크기가 0이 되는 경우가 없도록 바이어스 값으로 최대 워드 값(0xffff)보다 큰 0x10000을 더한다. 이렇게 컴파일된 genassym.o는 nm1을 통하여 심볼의 이름과 크기를 알 수 있다. (바이어스 값은 뺀다.)

$ nm genassym.o | grep PC_SIZEOF
0000000000010000 C PC_SIZEOFsign
0000000000010280 C PC_SIZEOFw0
0000000000010000 C PC_SIZEOFw1
0000000000010000 C PC_SIZEOFw2
0000000000010000 C PC_SIZEOFw3

/sys/kern/genassym.sh 스크립트23는 genassym.o 파일을 nm과 awk4로 가공하여 아래와 같은 결과물을 출력한다.

#define PC_SIZEOF    0x280
#define PC_PRVSPACE  0x200
#define PC_CURTHREAD 0x0
...

이 내용은 FreeBSD의 커널 빌드 과정에서 assym.h 파일로 저장되어 커널의 어셈블리 파일에서 사용된다.