Ctags Architecture with Kernel - KAT(2-1)


6 분 소요

Ctags Architecture

이 게시글의 목적은 Kernel이 사용하는 ctags를 살펴보는 것이 목적이기 때문에 완전히 Kernel에서 사용하는 내용과 관련된 부분만 다루는 점을 미리 알린다. 또한 이 블로그는 ctags가 무엇인지에 대해 어느정도 아는 것을 가정하고 작성되었으며, 빠르게 적은 글이므로 친절하지 않으므로 다른 reference할 포스팅과 함께 보길 바란다. 나도 그러고 있다.

지금부터 분석할 Ctags의 정확한 명칭은 Exuberant CTAGS이다. 옛날에 존재하던 ctags에서 부족한 기능을 추가한 개정판인 듯 하다.

What is the Ctags

Ctags

Ctags는 많은 c언어 프로젝트에서 코드를 분석하기 위해 Symbol들이 정의된 위치를 쉽게 찾기 위한 프로그램이다.

대표적으로 Vim이나 Emacs에서 기본적으로 지원하는 듯 하다.

Ctags는 근본적으로 문자열 탐색을 기반으로 작동하는 프로그램이다. (뇌피셜이다. 공식 사이트에 가보면 설명이 되어있겠지만 영어라 패스)

  • 심볼 이름
  • 심볼의 위치(path)
  • 해당 symbol을 찾을 수 있는 정규표현식
  • 주석

한 Symbol은 위에 해당하는 4가지로 구성된 한 row를 가지며, 자세한 것은 뒤에 설명한다.

Usage with Shell

Install ctags

자세히 설명하려다가 구글에 약 100개의 언어로 각각 100명 이상의 블로거들이 포스팅 해놓았음이 틀림없음을 깨닫고 그만두었다.

Make kernel ctags file

(https://github.com/torvalds/linux) 에서 kernel repo를 다운 받은 뒤 해당 프로젝트 최상위 디렉토리에서 다음을 수행한다.

$ make tags

특정 아키텍처를 지원하고 싶다면 다음과 같이 한다.

$ make ARCH=arm64 tags

그렇게 하면 최상위 디렉토리에 tags라는 파일이 생성된다. 약 500MB 전후 크기의 용량을 가지는 것으로 확인된다.

Makefile에서 ARCH에 해당하는 target source들을 linux/scripts/tags.sh로 넘겨서 처리한다.

tags file

많이 혼란스럽겠지만 읽으려면 읽을 수 있는 text string을 기반으로 한다. ctags의 철학이란다.

공식 메뉴얼로 나와있는 format에 대해 설명하겠다.

공식 문서

linux kernel에는 /linux/scripts/tags.sh에 존재한다.

Format of the tags

한 Symbol은 4개의 필드를 가지며 한 행으로 이루어진다. 4번째 필드는 vi처럼 ‘"’로 주석을 시작하는데, ‘tagfield’라는 용어로 특정 규칙에 의해서 사용하는 것으로 제안해오고 있다.

** {tagname}{tagfile}{tagaddress}[;"[comment] **

혹은

** {tagname}{tagfile}{tagaddress}[;"{tagfield}..] **

  • tagname: Symbol 이름이며 공백을 포함할 수 없다.
  • TAB: \t에 해당하는 탭문자를 의미한다.
  • tagfile: 해당 tag가 존재하는 경로(path)를 의미한다.
  • tagaddress: 해당 tag가 tagfile의 어디에 위치하는지를 의미한다. 실제 vi의 명령행에서 검색하는 정규표현식을 의미하며, 한 개의 숫자만 적혀 있을 경우 Line number를 의미한다. 두 개이상의 정규 표현식을 거쳐야 한다면 ‘;’으로 구분한다. Line number는 맨 처음 tagaddress 필드에만 올 수 있다.
  • ;”: tagaddress를 끊고 뒤에 tagfield를 추가 하기 위함이다. tagfield가 없으면 생략 가능하다.
  • comment / tagfield: Exuberant 버전이 되면서 생긴 필드. 추가적인 정보를 제공하기 위함이다.

image

다음은 임시로 아무거나 쳐서 만든 c 코드에 대해 만들어진 tags 파일이다. 참고바란다.

Goals of the comment text

목표가 아니라 철학에 가까운 이야기이다.

  1. 본문은 짧게 유지한다.
    • vi가 처리할 수 있는 라인의 길이는 512자 (vim이 아니라 vi겠지)
    • 텍스트가 많으면 검색 속도가 느려진다.
  2. 텍스트를 읽을 수 있도록 유지해라.
    • 새로운 ctags 프로그램의 출력을 확인할 필요가 있다.
    • 손으로 파일을 편집할 수 있다.
    • 파일을 만들거나 구문 분석할 수 있는 프로그램을 작성하기 용이하다.
  3. 특수문자를 사용하지 말아라.
    • 태그 파일을 일반 텍스트 파일처럼 처리할 수 있어야 한다.

Proposal of the comment text (feat. tagfield)

tagfield는 name, colon, value로 구성되며 “name:value”의 형태를 취한다.

  • name: 알파벳 문자로만 구성된다. 대소문자를 구분한다. 소문자 이름을 권장한다.
  • value: 값은 비어있을 수 있다. 탭문자를 사용하면 안 된다.

Proposed tagfiled names:

FIELD_NAME DESCRIPTION
enum 이 태그가 속한 열거형의 이름
file static(local)의미의 태그. value가 비어있으면 tagfile 필드에 해당하는 파일을 의미
kind 심볼의 종류. name과 ‘:’을 생략하고 value만 사용할 수 있음.
struct 이 태그가 속한 구조체의 이름
union 이 태그가 속한 공용체의 이름
typeref 이 태그가 typedef한 자료형(구조)의 이름

kind if tag:

VALUE DESCRIPTION
d 매크로 (#define)
e 열거형 멤버
f 함수 이름
F 파일 이름
g 열거형 이름
m 구조체 멤버
p 함수 프로토타입
x external 변수 선언
s 구조체 이름
t typedef
u 공용체 이름
v 변수 (전역 변수 등)

라고 공식문서에 나와 있으나, 사용자가 커스터마이징 가능. 위는 C에 대한 내용.

kind:m 이런식으로 작성해야 하는데, kind: 를 통채로 생략가능. tags파일을 vi로 열어서 /\tf[\t\n]을 입력하면 함수 이름에 해당하는 tag가 뜬다.

Psuedo-tag

’!’로 시작하는 태그를 의미. tags 파일의 최상단에 위치.

!_TAG_FILE_FORMAT {version-number} /optional comment/

!_TAG_FILE_SORTED {0 1} /0=unsorted, 1=sorted/

!_TAG_PROGRAM_AUTHOR {author-name} /{email-address}/

!_TAG_PROGRAM_NAME {program-name} /optional comment/

!_TAG_PROGRAM_URL {URL} /optional comment/

!_TAG_PROGRAM_VERSION {version-id} /optional comment/

How to Search a tag

  1. tags 파일에서 Symbol들은 정렬이 되어있어야 한다. 정렬이 되어있지 않으면 경고 메세지를 띄워준다.

  2. vi의 명령행에서 :tj {symbol_name}을 입력하거나 symbol_name 문자열 위에서 Ctrl + ] 혹은 g + ] 등을 입력하게되면 vi에 등록된 tags 파일 (vi 명령행 set tags={path}에 등록된)에서 symbol_name을 이진 탐색하여 찾는다.

  3. tag가 발견되면 태그 목록을 띄운다. (Ctrl + ]의 경우 3번 수행 X)

image

tags 파일에는 다음과 같이 작성되어 있었다.

image

각 텍스트들이 어디 위치하게 되는지는 직접 확인하도록 하자.

  1. 목록에서 특정 tag를 선택하게 되면 해당 태그의 tagfile로 이동해서 명령행에 tagaddress를 입력하여 해당 태그 위치로 커서가 이동하게 된다.

  2. 끝.

Ctags options

Ctags의 옵션에 대해서 알아보자 (Kernel에서 쓰는 옵션만)

다음은 linux/scripts/tags.sh 에 작성된 ctags 명령 수행 부분이다.

      setup_regex exuberant asm c
      all_target_sources | xargs $1 -a                        \
      -I __initdata,__exitdata,__initconst,__ro_after_init  \
      -I __initdata_memblock                          \
      -I __refdata,__attribute,__maybe_unused,__always_unused \
      -I __acquires,__releases,__deprecated,__always_inline \
      -I __read_mostly,__aligned,____cacheline_aligned        \
      -I ____cacheline_aligned_in_smp                         \
      -I __cacheline_aligned,__cacheline_aligned_in_smp     \
      -I ____cacheline_internodealigned_in_smp                \
      -I __used,__packed,__packed2__,__must_check,__must_hold     \
      -I EXPORT_SYMBOL,EXPORT_SYMBOL_GPL,ACPI_EXPORT_SYMBOL   \
      -I DEFINE_TRACE,EXPORT_TRACEPOINT_SYMBOL,EXPORT_TRACEPOINT_SYMBOL_GPL \
      -I static,const                                 \
      --extra=+fq --c-kinds=+px --fields=+iaS --langmap=c:+.h \
      "${regex[@]}"

      setup_regex exuberant kconfig
      all_kconfigs | xargs $1 -a                              \
      --langdef=kconfig --language-force=kconfig "${regex[@]}"

그래서 kernel은 다음 옵션을 사용함을 알았다.

  • -a: --append와 동일.
  • --append: 이미 존재하는 태그에 추가
  • -I: 특수처리할 tag list를 작성. 이것 저것 옵션이 있지만 설명하지 않겠음. 단순 나열일 경우, Ctags파일에서 해당 identifier를 제외. elf의 section 지정자나 EXPORT 심볼, 그리고 static, const 등의 keyword를 제거하는 모습을 볼 수 있음.

  • --extra
    • f: include된 모든 source file의 이름을 tag로 추가. 행번호 1
    • q: 구조체 멤버인 경우 Namespace처럼 struct::field 형태로 tag 추가. 특정 태그에 대한 한정자가 되어 더 쉽게 위치를 지정할 수 있는 대신 같은 태그가 2개 생기는 방식. (파일 크기 증가)
  • --c-kinds: c 는 –list-language에서 지원하는 파일 형식중 하나인 c언어를 의미 tagfield에서 name이 kind인 field에서 추가/제거/지정할 kind를 flag 설정.
    • p: prototype (기본으로 생성 안됨.)
    • x: external variable declaration (기본으로 생성 안됨.)
  • --fields: 태그 파일의 항목에 포함할 확장 필드 지정.
    • i: 상속 정보 (c++이 아니라서 상속 개념이 없어서 아무 효과 없는 듯)
    • a: 클래스 멤버 access or export (구조체에도 생기나 c++이 아니라서 전부 다 access:public이다. export는 역시 없다.)
    • S: signature:(함수인자) 로 구성됨. 당연히 함수에만 생김. kernel에서는 그렇다. 다른 언어는 시도해보지 않았으니 모름.
  • --langmap: 특정 확장자의 파일을 특정 언어에 매핑하는 방법 제어.
    • c:+.h: .h 파일을 c언어로 매핑.
  • --langdef: 정규표현식으로 구문 분석할 새 사용자 정의언어. 먼저 정의되면, 다른 옵션에서 해당 이름을 사용할 수 있다. 일반적으로 --langmap을 사용하여 파일 이름을 매핑한 다음 --regex-<LANG>을 사용하여 정규식을 지정한다.

  • --language-force: ctags는 언어를 결정할 수 없는 파일을 무시하고, 확장자를 기반으로 언어를 자동으로 결정하기 때문에, 그 대신 모든 제공된 파일에 사용하도록 강제한다.

  • --regex-<LANG>=/regexp/replacement/[kind-spec/][flags]: tag 생성 규칙을 정규표현식을 이용하여 만드는 것이다. 대게 ctags에서 지원하며, 지원하는 버전을 확인해야 한다.
    • {regexp} 정규표현식에 파싱되는 구문을
    • {replacement} 문자열로 변환한 것을 tag 이름으로 삼는다.
    • [kind-spec]에 tagfield의 kind에 대한 값을 지정할 수 있다. 생략하면 ‘regex’라는 의미의 ‘r’이 기본값으로 사용된다.
    • [flags]:
      • b: Posix basic regular expression
      • e: Posix extended regular expression (default)
      • i: 대소 문자 구분 없이 적용

위 옵션들을 걸고 tags를 만들어 보았다.

image

명령어는 다음과 같다

$ ctags -R --extra=+fq --fields=+iaS --c-kinds=+px --langmap=c:+.h

-R은 현재 디렉토리에서 recursive를 의미

abc.c
#include <stdio.h>

#define DEF 1
#define DEFFUNC(x) x*x

int abcfunc(int a)
{
        int i;
        return 1;
}


enum abcenum{
	A,
	C,
	D, E, F,
	G, H, I=100,
	J,
};

struct abcstruct {
	int i;
	double b;
	union A{
		int a;
		char c;
	} u;
};

typedef struct abcstruct abctype;

abctype a;
def.c
#include <stdio.h>
#include "zzz.h"

#define DEF 0
#define DEFFUNC(x) x*x+x

#if DEF == 1
int deffunc(int a)
{
	int i;
	return 1;
}
#elif DEF == 0
int deffunc(int a)
{
	int j;
	return 2;
}
#else
int deffunc(int a, int b)
{
	int j;
	return 2;
}
#endif
zzz.h
int deffunc(int);

P.S.

한 파일(c언어) 내에 함수 정의 부의 텍스트가 완전히 동일한 경우 태그는 중복되지 않고 하나만 생성된다. (def.c 파일의 deffunc() 처럼) -n 옵션을 줘서 정규표현식을 사용하지 않는 경우는 모두 생성된다. (정규표현식으로 같은 값이 나오면 중복시키지 않는 것으로 보임. Why?)

관련 내용에 대한 질문이나 태클을 환영합니다. 댓글 남겨주세요.



태그: ,

카테고리: ,

작성:

업데이트:

댓글남기기