前回紹介したように、
ざっと考えて、
- ①システム上の全てバイナリファイルの依存性情報を調べる
- ②その情報をデータベースに登録する
- ③バイナリファイルや共有ライブラリの名前からデータベースを検索する
このうち、
もっとも、
依存性情報データベースの設計
前回紹介したように、
$ ldd /usr/bin/sed linux-vdso.so.1 => (0x00007fff3a35b000) libacl.so.1 => /usr/lib64/libacl.so.1 (0x00007f526a0c6000) libc.so.6 => /lib64/libc.so.6 (0x00007f5269d5c000) libattr.so.1 => /usr/lib64/libattr.so.1 (0x00007f5269b58000) /lib64/ld-linux-x86-64.so.2 (0x00007f526a2cd000)
一方、
さて、
バイナリファイル | 共有ライブラリ1 | 共有ライブラリ2 | 共有ライブラリ3 | 共有ライブラリ4 | ... |
---|---|---|---|---|---|
/usr/ | linux-vdso. | libacl. | libc. | libattr. | .... |
こういう風に記録するとバイナリファイルと共有ライブラリの対応関係は明確になりますが、
行ごとに欄の数が異なっていると、
バイナリファイル | 共有ライブラリ |
---|---|
/usr/ | linux-vdso. |
/usr/ | libacl. |
/usr/ | libc. |
/usr/ | libattr. |
.... | .... |
こういう形で整理するならば、
コマンド名 | ファイル名 | 共有ライブラリ名 | ライブラリへのパス |
---|---|---|---|
sed | /usr/ | linux-vdso. | |
sed | /usr/ | libacl. | /usr/ |
sed | /usr/ | libc. | /lib64/ |
sed | /usr/ | libattr. | /usr/ |
sed | /usr/ | /lib64/ |
この例のうち、
データベース作成スクリプト
前節で示した表を実現するために、
1: #! /usr/bin/python
2:
3: import sqlite3
4:
5: def init_db(dbname):
6: conn = sqlite3.connect(dbname)
7: conn.execute('''CREATE TABLE depends
8: (base TEXT, path TEXT, soname TEXT, realname TEXT)''')
9: conn.close
10:
11: def insert_db(dbname, t):
12: conn = sqlite3.connect(dbname)
13: try:
14: print "inserting ", t
15: conn.execute('INSERT INTO depends VALUES(?, ?, ?, ?)', t)
16: conn.commit()
17: except sqlite3.Error, e:
18: print "An error occurred:", e.args[0]
19: conn.rollback()
20:
21: def main():
22: dbname = './depends.sql3'
23: init_db(dbname)
24: data = (('sed', '/usr/bin/sed', 'linux-vdso.so.1', ''),
25: ('sed', '/usr/bin/sed', 'libacl.so.1', '/usr/lib64/libacl.so.1'),
26: ('sed', '/usr/bin/sed', 'libc.so.6', '/lib64/libc.so.6'),
27: ('sed', '/usr/bin/sed', '', '/lib64/ld-linux-x86-64.so.2'))
28:
29: for i in data:
30: insert_db(dbname, i)
31:
32: if __name__ == "__main__":
33: main()
1行目はこのスクリプトを解釈するためのコマンドで、
5行目から9行目がSQLiteのデータベースにdependsという表を作る処理で、
各欄の名称を先の例と対応させるとこういう形になります。
コマンド名 | ファイル名 | 共有ライブラリ名 | ライブラリへのパス |
---|---|---|---|
base | path | soname | realname |
11行目から19行目が、
この関数では、
21行目から30行目までがこのスクリプトのmain()部で、
32行目と33行目はPython的なお約束ですが、
このスクリプトを script01.
$ python script01.py inserting ('sed', '/usr/bin/sed', 'linux-vdso.so.1', '') inserting ('sed', '/usr/bin/sed', 'libacl.so.1', '/usr/lib64/libacl.so.1') inserting ('sed', '/usr/bin/sed', 'libc.so.6', '/lib64/libc.so.6') inserting ('sed', '/usr/bin/sed', '', '/lib64/ld-linux-x86-64.so.2')
depends.
$ sqlite3 depends.sql3 SQLite version 3.7.10 2012-01-16 13:28:40 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> select * from depends; sed|/usr/bin/sed|linux-vdso.so.1| sed|/usr/bin/sed|libacl.so.1|/usr/lib64/libacl.so.1 sed|/usr/bin/sed|libc.so.6|/lib64/libc.so.6 sed|/usr/bin/sed||/lib64/ld-linux-x86-64.so.2 sqlite> select * from depends where soname like "%acl%"; sed|/usr/bin/sed|libacl.so.1|/usr/lib64/libacl.so.1
これでデータベースに登録する部分ができました。
依存関係情報収集スクリプト
依存関係を記録する側の処理は目処がついたので、
バイナリファイルの依存関係情報は、
ディレクトリ構造をたどる処理は、
また、
このように考えて次のようなスクリプトを書いてみました。このスクリプトは少し長めなので、
1: #! /usr/bin/python
2:
3: import os, subprocess
4:
5: def get_elfs(path):
6: test = os.walk(path,followlinks=False)
7: elfs = []
8: for root, dirs, files in test:
9: for i in files:
10: path = os.path.join(root,i)
11: if os.path.islink(path) == False:
12: if check_elf(path) :
13: print("{0} is ELF".format(path))
14: elfs.append(path)
15: return elfs
16:
17: def check_elf(testfile):
18: res = subprocess.check_output(['file', testfile])
19: if res.find('ELF') > 0 and res.find('dynamically linked') > 0 :
20: return True
21: else:
22: return False
23:
1行目はこのスクリプトをコマンドとして使うための設定、
5行目から15行目が指定したディレクトリ以下のバイナリファイルを調べる関数です。この関数では先にimportしたosモジュールの提供するos.
バイナリファイルであれば、
17行目から22行目がバイナリファイルかどうかを判断するcheck_
これらの文字列があれば共有ライブラリを参照しているバイナリファイルであると判断できるのでTrueを返し、
24: def get_depends(file):
25: depends = []
26: try:
27: res = subprocess.check_output(['ldd', file])
28: tmp = res.splitlines()
29: for i in tmp:
30: depends.append(i.lstrip())
31:
32: except subprocess.CalledProcessError:
33: print("error occured to ldd {0}. maybe different archs?".format(file))
34:
35: return depends
36:
37: def split_parts(l):
38: (soname, sep, last) = l.partition(' => ')
39: if soname == 'linux-vdso.so.1' or soname == 'linux-gate.so.1' :
40: realname = ''
41: elif soname.find('ld-linux') > 0:
42: (t1, t2, t3) = soname.partition(' (')
43: soname = ''
44: realname = t1
45: else:
46: (realname, sep, last2) = last.partition(' (')
47: return (soname, realname)
48:
24行目から35行目がバイナリファイルの依存関係を調べるget_
get_
37行目から47行目はlddコマンドの出力を整形するためのsplit_
lddの結果の中にはカーネルの提供する仮想的な共有ライブラリ
49: def main():
50: search_dirs = ['/bin', '/lib', '/lib64', '/sbin', '/usr', '/opt']
51: for dir in search_dirs:
52: print("searching {0}".format(dir))
53: files = get_elfs(dir)
54: list = []
55: for path in files:
56: base = os.path.basename(path)
57: tmp = get_depends(path)
58: for i in tmp:
59: (soname, realname) = split_parts(i)
60: t = (base, path, soname, realname)
61: list.append(t)
62:
63: for i in list:
64: print i
65:
66: if __name__ == "__main__":
67: main()
49行目から64行目がmain()の処理で、
次に、
こうして作成した依存関係情報のタプルを、
このスクリプトをscript02.
$ python script02.py searching /bin /bin/bash is ELF /bin/bzip2 is ELF /bin/dd is ELF /bin/cp is ELF /bin/df is ELF .... ('bash', '/bin/bash', 'linux-vdso.so.1', '') ('bash', '/bin/bash', 'libreadline.so.6', '/lib64/libreadline.so.6') ('bash', '/bin/bash', 'libncursesw.so.5', '/lib64/libncursesw.so.5') ('bash', '/bin/bash', 'libdl.so.2', '/lib64/libdl.so.2') ('bash', '/bin/bash', 'libc.so.6', '/lib64/libc.so.6') ('bash', '/bin/bash', '', '/lib64/ld-linux-x86-64.so.2') ('bzip2', '/bin/bzip2', 'linux-vdso.so.1', '') ('bzip2', '/bin/bzip2', 'libbz2.so.1.0', '/lib64/libbz2.so.1.0') ....
ざっと見た限りでは、
この2つのスクリプトで最初にあげた3つの機能のうち1と2は実現できたので、