Linuxアプリを解析するのに使ったコマンドメモです。
環境: Ubuntu 16.04.2 LTS (Bash on Ubuntu on Windows)
コマンド
find
ファイルやディレクトリを探します。
パス指定可能で、以下ではトップディレクトリとしてカレント'.'を指定しています。
- 全てのファイルやディレクトリを列挙する
$ find .
- ファイルのみ列挙する
$ find . -type f
- 拡張子が.hのファイルを列挙する
$ find . -name '*.h'
grep
テキスト内から指定文字列を含む行を検索します。
- テキストファイルから指定文字列を検索する
$ grep '指定文字列' filename.txt
- パイプで渡された文字列から検索する
$ cat filename.txt | grep '指定文字列'
- or 条件で検索する
$ grep -e '検索文字列1' -e '検索文字列2' filename.txt
- and 条件で検索する
$ grep '検索文字列1' filename.txt | grep '検索文字列2'
パイプで繋ぎます。前段の検索結果から後段でさらに絞り込みます。
nm
実行バイナリが公開するシンボルを抽出する
シンボルは、関数名、クラス名、メソッド名、グローバル変数名とかです。
例:
ソースコード
# include <stdio.h>
int main()
{
printf("hello,world!\n");
return 0;
}
コマンド
nm hello
出力
0000000000600e28 d _DYNAMIC
0000000000601000 d _GLOBAL_OFFSET_TABLE_
00000000004005c0 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
00000000004006f8 r __FRAME_END__
00000000004005d4 r __GNU_EH_FRAME_HDR
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
0000000000601038 D __TMC_END__
0000000000601038 B __bss_start
0000000000601028 D __data_start
00000000004004e0 t __do_global_dtors_aux
0000000000600e18 t __do_global_dtors_aux_fini_array_entry
0000000000601030 D __dso_handle
0000000000600e10 t __frame_dummy_init_array_entry
w __gmon_start__
0000000000600e18 t __init_array_end
0000000000600e10 t __init_array_start
00000000004005b0 T __libc_csu_fini
0000000000400540 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000601038 D _edata
0000000000601040 B _end
00000000004005b4 T _fini
00000000004003c8 T _init
0000000000400430 T _start
0000000000601038 b completed.7585
0000000000601028 W data_start
0000000000400460 t deregister_tm_clones
0000000000400500 t frame_dummy
0000000000400526 T main
U puts@@GLIBC_2.2.5
00000000004004a0 t register_tm_clones
2番目のカラムがシンボルの種類を表しています。
T, Wが、この実行モジュールが実体を持っていて公開している関数名のシンボルです。
Uが未定義シンボルで、外部モジュールの公開シンボルを参照しています。
上記例だと、printfが外部モジュールのputsで実際の出力を行っています。
C++のシンボルを読める形式にする
-Cオプションを指定します。
nm -C libmy.so
C++のシンボルは
int func(int, char*);
という定義がモジュールでは、
_Z4funciPc
という名前で埋め込まれています。
これを読める形式に戻します。
xargs
パイプで使います。
例えば、ファイルを列挙して、そのファイルの中身に対してgrepを掛けたい場合。
$ find . -name '*.h' | xargs grep 'printf'
とします。
findで、*.hのファイル名を列挙してパイプでxargsコマンドに受け渡します。
xargsコマンドは、渡ってきたファイル名を以下のように引数の末尾に指定してコマンドを実行します。
grep 'printf' ファイル名
間違い例として、
$ find . -name '*.h' | grep 'printf'
としてしまうと。
findで列挙したファイル名の一覧からprintfを検索してしまいます。
ldd
実行モジュールがロードするライブラリモジュールが出力されます。
$ ldd hello
linux-vdso.so.1 => (0x00007fffef8cc000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f03229b0000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0322e00000)
strace
コマンドが最終的に呼び出すシステムコールを表示します。
printf("hello,world\n");
を実行するだけの実行モジュールの場合。
$ strace ./hello
execve("./hello", ["./hello"], [/* 18 vars */]) = 0
brk(NULL) = 0x69e000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f598bb30000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=66980, ...}) = 0
mmap(NULL, 66980, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f598bb1f000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f598b430000
mprotect(0x7f598b5f0000, 2097152, PROT_NONE) = 0
mmap(0x7f598b7f0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f598b7f0000
mmap(0x7f598b7f6000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f598b7f6000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f598bb10000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f598bb00000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f598baf0000
arch_prctl(ARCH_SET_FS, 0x7f598bb00700) = 0
mprotect(0x7f598b7f0000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7f598ba25000, 4096, PROT_READ) = 0
munmap(0x7f598bb1f000, 66980) = 0
fstat(1, {st_mode=S_IFCHR|0660, st_rdev=makedev(4, 2), ...}) = 0
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
brk(NULL) = 0x69e000
brk(0x6bf000) = 0x6bf000
write(1, "hello, world!\n", 14) = 14
exit_group(0) = ?
+++ exited with 0 +++
printfはロードモジュールのputsを呼んでいますが、
putsから最終的にはシステムコールのwriteを呼んでいるようです。
リダイレクト
コマンド等の出力をファイルとかに出力する場合に使うアレです。
- 標準出力をファイルに出力する
$ hello > hello.txt
- 標準エラー出力をファイルに出力する
$ hello 2> hello.txt
応用
指定シンボルを公開しているモジュールを探す
$ cd /usr/lib
$ find . -type f | xargs nm -AC 2>/dev/null | grep -e ' T ' -e ' W ' | grep '探したいシンボル'
動作として、
- findで、ファイルを列挙
- 列挙されたファイルのシンボルをnmで出力
この時、エラー出力が邪魔なので/dev/nullに捨てている
オプションAで、ファイル名を出力。
オプションCで、C++のシンボルを読める形式に変換 - grepで、実体を公開している、属性がTまたはWの行のみ抽出
4 次のgrepで、目的のシンボルを抽出