eu-unstripでdebuginfoからデバッグ情報付きバイナリを再作成

eu-unstrip とは

rpm系では解析時によく使う debuginfo パッケージですが、時々不便なことがあります。
例えば objdump -S をしようとしてもデバッグ情報が入っているバイナリとstripされたバイナリに情報が別れてしまっているため、objdump -S で元のコード付きで逆アセンブリ表示をすることができません。
以下の例だと /usr/sbin/zabbix_server_mysql と /usr/lib/debug/usr/sbin/zabbix_server_mysql.debug に分かれてしまっています。

$ rpm -ql zabbix-server-mysql|grep zabbix_server_mysql
/usr/sbin/zabbix_server_mysql
$ rpm -ql zabbix-debuginfo|grep zabbix_server_mysql.debug
/usr/lib/debug/usr/sbin/zabbix_server_mysql.debug
$ objdump -S /usr/sbin/zabbix_server_mysql
・・・逆アセンブルされるが、元のコードは表示されない
$ objdump -S /usr/lib/debug/usr/sbin/zabbix_server_mysql.debug
・・・逆アセンブルされない

そのため、stripとは逆に2つのバイナリを1つにしてくれるコマンドは無いものか、と人に聞いてみたところ elfutils に eu-unstrip というコマンドが存在することを教えてもらいました。

ビルド

どうやらRHEL5系やその互換OSの elfutils のバージョンでは eu-unstrip は存在しないようですが、 https://fedorahosted.org/releases/e/l/elfutils/ から elfutils-0.152.tar.bz2 を持ってきてビルドしたら使用できました。

$ wget https://fedorahosted.org/releases/e/l/elfutils/0.152/elfutils-0.152.tar.bz2
$ wget https://fedorahosted.org/releases/e/l/elfutils/0.152/elfutils-robustify.patch
$ wget https://fedorahosted.org/releases/e/l/elfutils/0.152/elfutils-portability.patch
$ tar xjf elfutils-0.152.tar.bz2
$ cd elfutils-0.152
$ patch -p1 < ../elfutils-robustify.patch
$ patch -p1 < ../elfutils-portability.patch
$ ./configure
$ make

これで ./src/unstrip(パッケージだと eu-unstrip という名前になっているもの)というバイナリが作成されます。
実行時にいくつかライブラリを参照する必要があるので、elfutils-0.152 の直下に以下のような LD_LIBRARY_PATH を指定して実行してくれる unstrip.sh を作っておきましょう。

$ cat unstrip.sh
#!/bin/bash
THIS_DIR=$(dirname $0)
LD_LIBRARY_PATH=$THIS_DIR/libdw:$THIS_DIR/libelf: $THIS_DIR/src/unstrip $@

これで unstrip が実行できるようになりました。

実行してみる

$ ./elfutils-0.152/unstrip.sh --help
Usage: unstrip [OPTION...] STRIPPED-FILE DEBUG-FILE
or: unstrip [OPTION...] [MODULE...]
Combine stripped files with separate symbols and debug information.

Input selection options:
--core=COREFILE Find addresses from signatures found in COREFILE
--debuginfo-path=PATH Search path for separate debuginfo files
-e, --executable=FILE Find addresses in FILE
-k, --kernel Find addresses in the running kernel
-K, --offline-kernel[=RELEASE] Kernel with all modules
-M, --linux-process-map=FILE Find addresses in files mapped as read from
FILE in Linux /proc/PID/maps format
-p, --pid=PID Find addresses in files mapped into process PID

-f, --match-file-names Match MODULE against file names, not module names
-i, --ignore-missing Silently skip unfindable files

Output options:
-a, --all Create output for modules that have no separate
debug information
-d, --output-directory=DIRECTORY
Create multiple output files under DIRECTORY
-m, --module-names Use module rather than file names
-n, --list-only Only list module and file names, build IDs
-o, --output=FILE Place output into FILE
-R, --relocate Apply relocations to section contents in ET_REL
files

-?, --help Give this help list
--usage Give a short usage message
-V, --version Print program version

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.

The first form puts the result in DEBUG-FILE if -o was not given.

MODULE arguments give file name patterns matching modules to process.
With -f these match the file name of the main (stripped) file (slashes are
never special), otherwise they match the simple module names. With no
arguments, process all modules found.

Multiple modules are written to files under OUTPUT-DIRECTORY, creating
subdirectories as needed. With -m these files have simple module names,
otherwise they have the name of the main file complete with directory
underneath OUTPUT-DIRECTORY.

With -n no files are written, but one line to standard output for each module:
START+SIZE BUILDID FILE DEBUGFILE MODULENAME
START and SIZE are hexadecimal giving the address bounds of the module.
BUILDID is hexadecimal for the build ID bits, or - if no ID is known; the
hexadecimal may be followed by @0xADDR giving the address where the ID resides
if that is known. FILE is the file name found for the module, or - if none was
found, or . if an ELF image is available but not from any named file.
DEBUGFILE is the separate debuginfo file name, or - if no debuginfo was found,
or . if FILE contains the debug information.

Report bugs to http://bugzilla.redhat.com/bugzilla/.

使い方は以下の通り。
/usr/sbin/zabbix_server_mysql と /usr/lib/debug/usr/sbin/zabbix_server_mysql.debug を元に、zabbix_server_mysql__unstrip が作成されます。
これで zabbix_server_mysql__unstrip にデバッグ情報と実行バイナリの情報がマージされたバイナリが作成されます。

$ ./elfutils-0.152/unstrip.sh /usr/sbin/zabbix_server_mysql /usr/lib/debug/usr/sbin/zabbix_server_mysql.debug -o zabbix_server_mysql__unstrip

objdump -S を実行する際にはソースコードが存在するパスを「-I」で指定して実行すれば良さそうです。(ちょっと一部がずれてるような気も。)

$ objdump -S zabbix_server_mysql__unstrip $(find /usr/src/debug/zabbix-1.8.3/ -type d|sed s/'^'/'-I'/g|tr '\n' ' ')|less

Debianでは・・・

Debian では stable(squeeze) でも elfutils パッケージに eu-unstrip が入っていたので気軽に使えました。