본문 바로가기

Computer Science/Note

[Linux] nm

728x90

nm 명령은 주어진 라이브러리의 심볼 리스트를 보고한다.
라이브러리, 컴파일된 오브젝트 모듈, 공유 오브젝트 파일, 독립 실행 파일 등의 바이너리 파일을 검사해서 그 파일 들에 저장된 내용 또는 메타 정보를 표시한다.

GNU 프로젝트는 높은 기능을 갖춘 nm 프로그램을 GNU Binutils 패키지에 포함시키고 있다.
GNU 툴체인의 다른 부분과 함께 주어진 nm 바이너리는 특정 컴퓨터 아키텍쳐와 바이너리 포멧만을 위해 컴파일 된 것이므로 의심스런 바이너리를 검사하기 위해 nm을 사용하는 보안 전문가들은 보통 여러 타겟 용으로 만들어 놓은 nm 바이너리를 갖고 있다.


사용 예 - 특정한 라이브러리 찾기
특정한 함수를 사용하여 컴파일을 하다 보면 링크 에러가 발생할 때가 있다. 이럴 때는 그 함수를 정의하고 있는 라이브러리를 링크해 주어야 한다.

렇게 헤더 파일에는 프로토타입이 선언되어 있지만 링크시에 에러가 발생하면 쉘에서 다음과 같이 실행한다.
# nm -A /usr/lib/*.a 2>/dev/null | grep [function_name]

nm 명령어로 모든 함수명을 출력한다.
그럼 원하는 함수가 포함되어 있는 라이브러리 파일 이름을 발견할 수 있다.

2>/dev/null은 에러 메시지를 표시 하지 않겠다는 뜻이다.
1은 표준 출력 2는 표준 에러 출력을 의미하며 이것을 null device로 리다이렉트시키면 출력이 되지 않는다.

그럼 이제 컴파일러 옵션에 다음과 같이 추가한다.
-l[라이브러리 이름에서 앞의 lib를 떼고 뒤의 확장자를 제거한 이름]

즉, libpthread.a의 경우에는 -lpthread라고 추가하면 된다.
# gcc -o hello hello.c -lpthread



nm과 C++ filt 활용
출처 : http://kldp.org/node/68410

프로그램 빌드/디버깅 과정에서 쓸모있는 툴 중 nm(1)이 있다. nm(1)은 간단히 오브젝트 파일에 들어있는 심볼들을 보여준다. 예를 들어 다음과 같은 C 코드(파일 이름 "nm-data.c")를 생각해 보자:
static int global_static = 1234;
const int global_const = 5432;
int global;

void global_function()
{
    static int local_static = 2;
    int local;
}

static void static_function()
{
    extern int extern_int;
    void extern_function(void);

    extern_function();
    extern_int = 0;
}

자세히 볼 것도 없이, 아무런 의미가 없는 코드이다. 위 코드를 "cc -c nm-data.c"로 컴파일해서 nm으로 그 내용을 보면 다음과 같다:
$ nm nm-data.o
U extern_function
U extern_int
00000004 C global
00000000 R global_const
00000000 T global_function
00000000 d global_static
00000004 d local_static.0
00000008 t static_function
$ _

nm의 출력은 크게 3개의 열로 되어 있다. 첫 번째 열은 심볼 값을, 두 번째 열은 심볼 타입을, 세 번째 열은 심볼 이름을 나타낸다.
심볼 값은 생략 가능하고, 보통 table offset 값이나 virtual address를 나타낸다. 심볼 타입은 한 글자 알파벳으로 되어 있고, 심볼 이름은 말 그대로 이름을 나타낸다.

이 글에서는 심볼 타입에 대해서 주로 다룰 것이다. 먼저 심볼 타입은 A, B, C, D, G, I, N, R, S, T, U, V, W, -, ?가 있으며, 이 중 알파벳은 대소문자가 모두 가능하다. 사실 다 외워 둘 필요는 없고, 딱 하나만 외워 두면 되는데 그건 바로 U이다. U는 Undefined를 뜻하며, 이 오브젝트 파일이 심볼을 참조하고 있지만 정의가 없을 경우에 U로 표시한다. 
소스를 보면, 함수 extern_function()을 호출하고 있지만, 이 함수에 대한 정의는 존재하지 않는다. 또, 전역 변수 extern_int에 0을 대입하고 있지만 이 변수의 정의도 없다. nm은 이러한 심볼들에 대하여 U 타입으로 나타낸다. T는 이 심볼이 text 섹션에 있다는 것 즉, 함수를 의미한다. R은 read only data section, 상수를 의미한다. C는 common, 초기화되지 않은 데이터를 의미한다.
자세한 것은 man 1 nm 또는 info nm을..

여기까지 알았으면, 경험 많은 프로그래머라면 nm이 어떤 경우에 도움을 줄 수 있는지 바로 알 수 있을 것이다. 바로 이름이 충돌날 때(naming conflict)인데, 예를 들어 다음과 같은 코드(파일 이름: nm-ex.c)를 보자:
void f1(void)
{

}

int main(void)
{
    fl();
    return 0;
}

정의한 함수는 f1(), 즉 "소문자 에프-숫자 일"이다. 그리고 호출한 함수는 "소문자 에프-소문자 엘"이다. 서로 다른 것에 주의 바란다. 그러나 이 코드를 작성한 프로그래머는 두 함수 이름이 서로 같다고 착각하고 있다고 가정하자. 이 경우 컴파일 해 보면, 다음과 같은 에러가 발생한다:
$ cc nm-ex.c
/tmp/ccQQbww3.o: In function `main':
nm-ex.c:(.text+0x16): undefined reference to `fl'
collect2: ld returned 1 exit status
~$ _

"응? 왜 에러가 나지? 분명 함수 fl()을 정의하고 호출했는데, 왜 fl()이 없다고 하지?"라고 생각할 것이다. 이 경우 object 파일을 만들고 nm으로 출력해 보면 그 차이를 알 수 있다:
$ cc -c nm-ex.c
$ nm nm-ex.o
00000000 T f1
U fl
00000005 T main
$ _

즉 정의한 함수는 타입이 T로 출력된 f1이고, 호출한 함수는 정의되지 않은 타입 U로 표시된 fl인 것을 알 수 있다. 즉, 타이핑 실수로 함수 이름을 서로 다르게 만들어 두었기 때문에, 두 심볼로 표시된 것이다. 만약 올바르게 한 함수 이름으로 코드가 만들어졌다면 U 타입 심볼은 만들어지지 않았을 것이다.

또 다음과 같은 C++ 소스를 보자(파일 이름: nm-ex.cc):
class foo
{
public:
    foo();
    ~foo();

    void work(void);
    void work(int i);
};

foo::foo() {}
foo::~foo() {}

void foo::work(void) {}
void foo::work(int i) {}

이것을 컴파일하고 nm으로 출력하면 다음과 같다:
$ c++ -c nm-ex.cc
$ nm nm-ex.o
0000001e T _ZN3foo4workEi
00000018 T _ZN3foo4workEv
00000006 T _ZN3fooC1Ev
00000000 T _ZN3fooC2Ev
00000012 T _ZN3fooD1Ev
0000000c T _ZN3fooD2Ev
$ _

C++ name mangling 때문에, 심볼 이름을 알아보기 힘들 것이다. 이 경우, c++filt(1)라는 툴을 쓰면 쉽게 알아 볼 수 있다:
$ nm nm-ex.o | c++filt
0000001e T foo::work(int)
00000018 T foo::work()
00000006 T foo::foo()
00000000 T foo::foo()
00000012 T foo::~foo()
0000000c T foo::~foo()
$ _

nm(1)은 오브젝트 파일 이외에, 실행 파일, 라이브러리 파일에도 모두 동작한다. 특히, 문서나 헤더 파일이 없는 라이브러리가 어떤 함수들을 제공하는지 훑어볼 때에도 유용하다:
$ nm libwhat_is_this.a

또, nm은 이러한 목적 이외에도, 나중에 다뤄 볼 objcopy와 함께 object 파일을 직접 조작할 때 자주 쓰인다(파일을 처리할 때 ls 명령이 얼마나 자주 쓰이는지 생각해 보면, object 파일을 처리할 때 nm이 얼마나 자주 쓰이는지 쉽게 유추할 수 있다).
-------------------------------------------------------------------------------------------------------

Usage : nm [option(s)] [file(s)]
 List symbols in [file(s)] (a.out by default).

 The options are:
  -a, --debug-syms       Display debugger-only symbols
  -A, --print-file-name  Print name of the input file before every symbol
  -B                     Same as --format=bsd
  -C, --demangle[={none,auto,gnu,lucid,arm,hp,edg,gnu-v3,java,gnat,compaq}]
                         Decode low-level symbol names into user-level names
      --no-demangle      Do not demangle low-level symbol names
      --demangler=<dso:function> Set dso and demangler function
  -D, --dynamic          Display dynamic symbols instead of normal symbols
      --defined-only     Display only defined symbols
  -e                     (ignored)
  -f, --format=FORMAT    Use the output format FORMAT.  FORMAT can be `bsd', `sysv' or `posix'.  The default is `bsd'
  -g, --extern-only      Display only external symbols
  -l, --line-numbers     Use debugging information to find a filename and line number for each symbol
  -n, --numeric-sort     Sort symbols numerically by address
  -o                     Same as -A
  -p, --no-sort          Do not sort the symbols
  -P, --portability      Same as --format=posix
  -r, --reverse-sort     Reverse the sense of the sort
  -S, --print-size       Print size of defined symbols
  -s, --print-armap      Include index for symbols from archive members
      --size-sort        Sort symbols by size
      --special-syms     Include special symbols in the output
      --synthetic        Display synthetic symbols as well
  -t, --radix=RADIX      Use RADIX for printing symbol values
      --target=BFDNAME   Specify the target object format as BFDNAME
  -u, --undefined-only   Display only undefined symbols
  -X 32_64               (ignored)
  -h, --help             Display this information
  -V, --version          Display this program's version number

nm: supported targets: elf32-i386 a.out-i386-linux efi-app-ia32 elf32-little elf32-big srec symbolsrec tekhex binary ihex trad-core

Report bugs to <URL:http://www.sourceware.org/bugzilla/> and hjl@lucon.org.

http://korea.gnu.org/manual/release/binutils/binutils_3.html#SEC5