标题: 自编译GDB执行出错后的排查
创建: 2016-03-01 13:22 更新: 链接: http://scz.617.cn/unix/201603011322.txt
在x64/Solaris 10上出现一个原始需求,调试64-bits样本。
此OS自带两大神器DTrace+MDB,但由于某些不便明说的理由,现在需要64-bits GDB。 网上可以下载gdb-6.8-sol10-x86-local.gz,这是预编译好的32-bits GDB。未能找 到现成的64-bits GDB for Solaris 10。
Google for "OpenCSW Solaris GDB",找到:
https://www.opencsw.org/package/gdb/ https://en.wikipedia.org/wiki/OpenCSW
据说这样可以安装GDB:
pkgadd -d http://get.opencsw.org/now /opt/csw/bin/pkgutil -U /opt/csw/bin/pkgutil -y -i gdb /usr/sbin/pkgchk -L CSWgdb
有兴趣者可以试试。
GDB当前最新版本是7.11,同样由于不便明说的理由,我只关注6.8版。执行自编译好 的64-bits GDB 6.8时,提示错误:
Interpreter `console' unrecognized
针对此问题,做了大量排查工作,最后将问题简化成本文所描述的这样。由于问题本 质与64-bits无关,下面以32-bits为例进行说明。
wget http://ftp.gnu.org/gnu/gdb/gdb-6.8a.tar.bz2 bzip2 -dc gdb-6.8a.tar.bz2 | tar xf - cd gdb-6.8/ rm -rf build mkdir build cd build CC=/usr/sfw/bin/gcc ../configure --program-transform-name='s,(.*),x86-\1-6.8,' -v make
会碰上错误:
../../gdb/remote.c: In function extended_remote_attach_1': ../../gdb/remote.c:2859: warning: unsigned int format, pid_t arg (arg 3) *** Error code 1 make: Fatal error: Command failed for target
remote.o'
参看:
https://sourceware.org/bugzilla/show_bug.cgi?id=12003
这个BUG在GDB 7.0得到修复,临时解决方案是:
vi ../gdb/remote.c
将2859行的
sprintf (rs->buf, "vAttach;%x", pid);
修改成
sprintf (rs->buf, "vAttach;%x", (int)pid);
继续
make ls -l gdb/gdb file gdb/gdb ldd gdb/gdb
看似一切正常,结果执行时碰上错误:
[root@ /export/home/scz/src/gdb-6.8/build]> gdb/gdb Interpreter `console' unrecognized
Google for "GDB Interpreter console unrecognized"
http://gdb.sourceware.narkive.com/Z06KUw8E/error-message-interpreter-console-unrecognized-with-gdb-6-8-pcsolaris https://www.sourceware.org/ml/gdb/2009-11/msg00180.html
2009年有人碰上过同样的问题,他最后在configure阶段去掉--disable-tui,解决了。 但我configure时本来就没有--disable-tui,所以他所谓的解决只是碰巧而已。放狗 搜了很久,没有更多有价值的信息。我又试了7.0/7.6的源码,问题依旧。如果这是 一个很明显的BUG,应该很多人碰上,并为新版本所修复。现在看来,这个BUG的触发 条件很隐蔽。我确实需要自编译GDB(主要是为了64-bits GDB),决定调试解决。
查看GDB帮助
https://sourceware.org/gdb/onlinedocs/gdb/ https://sourceware.org/gdb/onlinedocs/gdb/Concept-Index.html https://sourceware.org/gdb/onlinedocs/gdb/Mode-Options.html gdb -h
有个参数可以指定interpreter:
--interpreter console -i console
在源码中应该有:
Interpreter `%s' unrecognized
用Source Insight找到它:
struct interp *interp = interp_lookup( interpreter_p ); if ( interp == NULL ) { error( _( "Interpreter `%s' unrecognized" ), interpreter_p ); }
这段代码位于main.c的captured_main()中。interpreter_p指向"console"。检查 interp_lookup():
struct interp * interp_lookup ( char *name ) { struct interp *interp;
if ( name == NULL || strlen( name ) == 0 )
{
return( NULL );
}
for ( interp = interp_list; interp != NULL; interp = interp->next )
{
if (strcmp (interp->name, name) == 0)
return interp;
}
return( NULL );
interp_list是条全局链表,如果"console"不在其中,interp_lookup()返回NULL。 检查interp_list如何初始化的:
void interp_add ( struct interp *interp ) { if ( !interpreter_initialized ) { initialize_interps(); } gdb_assert( interp_lookup( interp->name ) == NULL ); interp->next = interp_list; interp_list = interp; }
interp_add()是唯一一个直接写interp_list的函数。谁在调用它?至少有三个:
_initialize_cli_interp() _initialize_mi_interp() _initialize_tui_interp()
第一个对应"console":
/*
- Standard gdb initialization hook. */ extern initialize_file_ftype _initialize_cli_interp;
void _initialize_cli_interp ( void ) { static struct interp_procs procs = { cli_interpreter_init, /* init_proc / cli_interpreter_resume, / resume_proc / cli_interpreter_suspend, / suspend_proc / cli_interpreter_exec, / exec_proc / cli_interpreter_display_prompt_p / prompt_proc_p */ }; struct interp *cli_interp;
/*
* Create a default uiout builder for the CLI.
*/
cli_uiout = cli_out_new( gdb_stdout );
cli_interp = interp_new( INTERP_CONSOLE, NULL, cli_uiout, &procs );
interp_add( cli_interp );
注释中的"Standard gdb initialization hook."引起我的注意,一般碰上这类说法, 表明在调用_initialize_cli_interp()时有奇技淫巧。果然,找不到它的主调函数!
手头有/usr/local/bin/gdb,源自gdb-6.8-sol10-x86-local.gz,这个版本可以正常 执行,没有前述BUG。用DTrace看一下,是否包含对_initialize_cli_interp的调用:
ID PROVIDER MODULE FUNCTION NAME ... 48293 pid114 gdb _initialize_cli_interp entry ...
dtrace -qwn 'pid$target:a.out:_initialize_cli_interp:entry {jstack();stop();}' -c /usr/local/bin/gdb
gdb`_initialize_cli_interp
gdb`initialize_all_files+0x204
gdb`gdb_init+0x48
gdb`captured_main+0x2ef
gdb`catch_errors+0x47
gdb`gdb_main+0x23
gdb`main+0x3d
gdb`_start+0x60
再看gdb/gdb
dtrace: invalid probe specifier ...: failed to lookup '_initialize_cli_interp' in module 'gdb'
错误提示指出gdb/gdb中没有_initialize_cli_interp()。
gdb`initialize_all_files
gdb`gdb_init+0x48
gdb`captured_main+0x30d
gdb`catch_errors+0x47
gdb`gdb_main+0x23
gdb`main+0x3d
gdb`_start+0x80
(Ctrl-C)
gdb/gdb中有对initialize_all_files()的调用。查看源码,这个函数只在call-cmds.h 中有一个函数原型:
extern void initialize_all_files ( void );
找不到它的函数主体。用调试器找找:
root 126 125 0 10:01:46 pts/3 0:00 gdb/gdb
(gdb) symbol-file gdb/gdb Reading symbols from /export/home/scz/src/gdb-6.8/build/gdb/gdb...done. (gdb) bt #0 0x08092539 in initialize_all_files () at init.c:106 #1 0x0808af8c in gdb_init (argv0=0x8047d4c "gdb/gdb") at ../../gdb/top.c:1643 #2 0x0808466d in captured_main (data=0x0) at ../../gdb/main.c:604 #3 0x0810c973 in catch_errors (func=0x8084360 <captured_main>, func_args=0x8047c54, errstring=0x8271935 "", mask=6) at ../../gdb/exceptions.c:513 #4 0x080851f3 in gdb_main (args=0x8307d00) at ../../gdb/main.c:891 #5 0x0808430d in main (argc=137395344, argv=0x8307c90) at ../../gdb/gdb.c:33 (gdb) list init.c:106 101 extern initialize_file_ftype _initialize_xml_support; 102 extern initialize_file_ftype _initialize_target_descriptions; 103 extern initialize_file_ftype _initialize_inflow; 104 void 105 initialize_all_files (void) 106 { 107 _initialize_gdbtypes (); 108 _initialize_i386_tdep (); 109 _initialize_amd64_sol2_tdep (); 110 _initialize_i386_sol2_tdep ();
initialize_all_files()函数主体在init.c中。注意到init.c不在源码目录下,而是 位于二进制gdb所在目录。
/* Do not modify this file. / / It is created automatically by the Makefile. / #include "defs.h" / For initialize_file_ftype. / #include "call-cmds.h" / For initialize_all_files. */ extern initialize_file_ftype _initialize_gdbtypes; ... void initialize_all_files (void) { _initialize_gdbtypes (); _initialize_i386_tdep (); _initialize_amd64_sol2_tdep (); ... _initialize_xml_support (); _initialize_target_descriptions (); _initialize_inflow (); }
注释里写了,这个init.c是Makefile自动生成的,不是原始源代码的一部分,在 Source Insight中找不到,其中果然没有调用_initialize_cli_interp()。这会导致 "Interpreter `console' unrecognized",但这还不是根源。为什么Makefile生成的 init.c没有调用_initialize_cli_interp()?理论上应该调用。
原始源代码的某个文件中应该包含动态生成init.c的操作,全文内容搜索找出:
gdb-6.8/gdb/Makefile.in
其中有这样一段:
INIT_FILES = $(COMMON_OBS) $ (TSOBS) $(CONFIG_SRCS)
init.c: $ (INIT_FILES)
@echo Making init.c
@rm -f init.c-tmp init.l-tmp
@touch init.c-tmp
@echo gdbtypes > init.l-tmp
@-LANG=c ; export LANG ;
LC_ALL=c ; export LC_ALL ;
echo $(INIT_FILES) |
tr ' ' '\012' |
sed
-e '/^gdbtypes.[co]$$/d'
-e '/^init.[co]$$/d'
-e '/xdr_ld.[co]$$/d'
-e '/xdr_ptrace.[co]$$/d'
-e '/xdr_rdb.[co]$$/d'
-e '/udr.[co]$$/d'
-e '/udip2soc.[co]$$/d'
-e '/udi2go32.[co]$$/d'
-e '/version.[co]$$/d'
-e '/^[a-z0-9A-Z_][SU].[co]$$/d'
-e '/[a-z0-9A-Z]-exp.tab.[co]$$/d'
-e 's/.[co]$$/.c/'
-e 's,signals.c,signals/signals.c,'
-e 's|^([^ /][^ ])|$(srcdir)/\1|g' |
while read f; do
sed -n -e 's/^initialize([a-z_0-9A-Z])./\1/p' $$f 2>/dev/null;
done |
while read f; do
case " $$fs " in
" $$f " ) ;;
* ) echo $$f ; fs="$$fs $$f";;
esac;
done >> init.l-tmp
@echo '/ Do not modify this file. /' >>init.c-tmp
@echo '/ It is created automatically by the Makefile. /'>>init.c-tmp
@echo '#include "defs.h" / For initialize_file_ftype. /' >>init.c-tmp
@echo '#include "call-cmds.h" / For initialize_all_files. /' >>init.c-tmp
@sed -e 's/(.)/extern initialize_file_ftype initialize\1;/' <init.l-tmp >>init.c-tmp
@echo 'void' >>init.c-tmp
@echo 'initialize_all_files (void)' >>init.c-tmp
@echo '{' >>init.c-tmp
@sed -e 's/(.*)/ initialize\1 ();/' <init.l-tmp >>init.c-tmp
@echo '}' >>init.c-tmp
@rm init.l-tmp
@mv init.c-tmp init.c
这段内容最终进入:
gdb-6.8/build/gdb/Makefile
调试Makefile,尝试理解上述操作:
[root@ /export/home/scz/src/gdb-6.8/build/gdb]> cp Makefile sczdbg.mk [root@ /export/home/scz/src/gdb-6.8/build/gdb]> vi sczdbg.mk
比如
init.c: $(INIT_FILES)
@echo $ (INIT_FILES) |
tr ' ' '\012' |
sed
-e '/^gdbtypes.[co]$$/d'
-e '/^init.[co]$$/d'
-e '/xdr_ld.[co]$$/d'
-e '/xdr_ptrace.[co]$$/d'
-e '/xdr_rdb.[co]$$/d'
-e '/udr.[co]$$/d'
-e '/udip2soc.[co]$$/d'
-e '/udi2go32.[co]$$/d'
-e '/version.[co]$$/d'
-e '/^[a-z0-9A-Z_][SU].[co]$$/d'
-e '/[a-z0-9A-Z]-exp.tab.[co]$$/d'
[root@ /export/home/scz/src/gdb-6.8/build/gdb]> rm init.c [root@ /export/home/scz/src/gdb-6.8/build/gdb]> make -f sczdbg.mk init.c i386-tdep.o i387-tdep.o amd64-tdep.o ... cli-interp.o ... xml-tdesc.o xml-builtin.o inflow.o ../../gdb/cli/cli-dump.c ../../gdb/cli/cli-decode.c ../../gdb/cli/cli-script.c ... ../../gdb/cli/cli-interp.c ... ../../gdb/tui/tui-wingeneral.c ../../gdb/tui/tui-winsource.c ../../gdb/tui/tui.c
"echo | tr | sed"先生成一份文件列表,所有的*.o都会转成*.c,非绝对路径的还 会在前面追加$(srcdir)。接下来的第一个while从这些文件中析取_initialize_*():
[root@ /export/home/scz/src/gdb-6.8/gdb]> sed -n -e 's/^initialize([a-z_0-9A-Z])./\1/p' cli/cli-interp.c cli_interp
比如原来有个:
_initialize_cli_interp (void)
析出:
cli_interp
第二个while的作用是消重。之后init.l-tmp内容形如:
在init.l-tmp中没有发现:
cli_interp
回头再说这个问题。接下来有2个sed:
sed -e 's/(.)/extern initialize_file_ftype initialize\1;/' <init.l-tmp >>init.c-tmp sed -e 's/(.)/ initialize\1 ();/' <init.l-tmp >>init.c-tmp
负责生成这种代码:
extern initialize_file_ftype _initialize_inflow;
由于init.l-tmp中没有cli_interp,所以没有生成:
extern initialize_file_ftype _initialize_cli_interp;
那么,为什么init.l-tmp中没有cli_interp?前面看到过:
../../gdb/cli/cli-interp.c
调试sczdbg.mk发现$(srcdir)是:
../../gdb
由于
-e 's|^([^ /][^ ]*)|$(srcdir)/\1|g'
导致这样的文件列表
../../gdb/i386-tdep.c ../../gdb/i387-tdep.c ../../gdb/amd64-tdep.c ... ../../gdb/cli-interp.c ... ../../gdb/target-memory.c ../../gdb/xml-tdesc.c ../../gdb/xml-builtin.c ../../gdb/inflow.c ../../gdb/../../gdb/cli/cli-dump.c ../../gdb/../../gdb/cli/cli-decode.c ../../gdb/../../gdb/cli/cli-script.c ... ../../gdb/../../gdb/cli/cli-interp.c ... ../../gdb/../../gdb/tui/tui-wingeneral.c ../../gdb/../../gdb/tui/tui-winsource.c ../../gdb/../../gdb/tui/tui.c
[root@ /export/home/scz/src/gdb-6.8/build/gdb]> ls -l ../../gdb/cli-interp.c ../../gdb/cli-interp.c: No such file or directory [root@ /export/home/scz/src/gdb-6.8/build/gdb]> ls -l ../../gdb/../../gdb/cli/cli-interp.c ../../gdb/../../gdb/cli/cli-interp.c: No such file or directory
通过上述文件列表根本无法正确定位cli-interp.c,从而无法析取cli_interp。
参看:
gdb-6.8/gdb/configure
或者
[root@ /export/home/scz/src/gdb-6.8/build]> ../configure --help
可以指定--srcdir:
[root@ /export/home/scz/src/gdb-6.8/build]> CC=/usr/sfw/bin/gcc ../configure --srcdir="/export/home/scz/src/gdb-6.8" --program-transform-name='s,(.*),x86-\1-6.8,' -v
这里居然不能指定/export/home/scz/src/gdb-6.8/gdb。
[root@ /export/home/scz/src/gdb-6.8/build]> make [root@ /export/home/scz/src/gdb-6.8/build]> gdb/gdb
可以正常执行。检查init.c:
[root@ /export/home/scz/src/gdb-6.8/build]> grep cli_interp gdb/init.c extern initialize_file_ftype _initialize_cli_interp; _initialize_cli_interp ();
之前在Linux上编译4.18至7.6版本GDB,同样创建build子目录,从未指定--srcdir, 不知Solaris上为何有这种古怪。configure、Makefile系统太琐碎复杂,但价值不高, 不再深究。想到一种可能,在Solaris上不创建build子目录,是否不会碰上该BUG?
[root@ /export/home/scz/src/gdb-6.8-test]> vi gdb/remote.c [root@ /export/home/scz/src/gdb-6.8-test]> CC=/usr/sfw/bin/gcc ./configure --program-transform-name='s,(.*),x86-\1-6.8,' -v [root@ /export/home/scz/src/gdb-6.8-test]> make [root@ /export/home/scz/src/gdb-6.8-test]> gdb/gdb
可以正常执行。检查init.c:
[root@ /export/home/scz/src/gdb-6.8-test]> grep cli_interp gdb/init.c extern initialize_file_ftype _initialize_cli_interp; _initialize_cli_interp ();
难怪碰上该BUG的人廖廖无几,绝大多数人不会创建build子目录再编译,只有我们这 种有洁癖的人才会坚持不懈地创建build子目录。而且Linux不存在该BUG。
此时的$(srcdir)是:
.././gdb
拼接$(srcdir)之前的文件列表形如:
i386-tdep.c i387-tdep.c amd64-tdep.c ... cli-interp.c ... xml-tdesc.c xml-builtin.c inflow.c cli/cli-dump.c cli/cli-decode.c cli/cli-script.c ... cli/cli-interp.c ... tui/tui-wingeneral.c tui/tui-winsource.c tui/tui.c
拼接后形如:
.././gdb/cli-interp.c .././gdb/cli/cli-interp.c
[root@ /export/home/scz/src/gdb-6.8-test/gdb]> ls -l .././gdb/cli-interp.c .././gdb/cli-interp.c: No such file or directory [root@ /export/home/scz/src/gdb-6.8-test/gdb]> ls -l .././gdb/cli/cli-interp.c -rw-rw-rw- 1 9176 65490 4486 Jan 2 2008 .././gdb/cli/cli-interp.c
至少有一个可以正确定位cli-interp.c。
比较错误的init.c与正确的init.c,前者缺失:
extern initialize_file_ftype _initialize_cli_dump; extern initialize_file_ftype _initialize_cli_logging; extern initialize_file_ftype _initialize_cli_interp; extern initialize_file_ftype _initialize_mi_out; extern initialize_file_ftype _initialize_mi_cmds; extern initialize_file_ftype _initialize_mi_cmd_env; extern initialize_file_ftype _initialize_mi_interp; extern initialize_file_ftype _initialize_gdb_mi_common; extern initialize_file_ftype _initialize_tui_hooks; extern initialize_file_ftype _initialize_tui_interp; extern initialize_file_ftype _initialize_tui_layout; extern initialize_file_ftype _initialize_tui_out; extern initialize_file_ftype _initialize_tui_regs; extern initialize_file_ftype _initialize_tui_stack; extern initialize_file_ftype _initialize_tui_win;
_initialize_cli_dump (); _initialize_cli_logging (); _initialize_cli_interp (); _initialize_mi_out (); _initialize_mi_cmds (); _initialize_mi_cmd_env (); _initialize_mi_interp (); _initialize_gdb_mi_common (); _initialize_tui_hooks (); _initialize_tui_interp (); _initialize_tui_layout (); _initialize_tui_out (); _initialize_tui_regs (); _initialize_tui_stack (); _initialize_tui_win ();
强调一点,这个BUG不是GDB 6.8特有的,只要在Solaris上,GDB 7.6同样存在该BUG。
不知GDB为什么非要动态生成如此重要的init.c,内里的逻辑是?这又不像yacc/lex 之类的,完全可以静态写好的吧。如果对GDB源码熟悉,应该很容易找到BUG源头。我 不熟,只好一路调试过来。
问题:
自编译GDB执行时提示"Interpreter `console' unrecognized"
临时解决方案:
configure时指定--srcdir参数,或者不建build子目录
回顾一下整个过程:
- 发现BUG,稳定重现
- 根据错误提示放狗搜索,有人碰上过,但没有真正解决
- 研读源码,寻找特征字符串,定位相关函数
- DTrace+GDB调试GDB,其实就是查了个调用栈回溯,以及在GDB中查看源码
- 找到动态生成的init.c,调试相关Makefile,搞清楚BUG根源
- 针对BUG根源configure时指定--srcdir参数,或者不建build子目录,问题解决
经验:
*nix上有些开源软件会动态生成部分源代码,在下载的源码包中较难直接发现这种情 况。如果Source Insight发现缺失某些函数主体,或者某些函数没有主调者,可以考 虑通过动态调试寻找缺失的代码,进而搞清楚来龙去脉。
前面对DTrace的使用不必要,只用GDB就可以达成目标,只不过DTrace很方便,不用 白不用。要知道,不只是Solaris上有DTrace哦。
文中内容属于"Old News",不具有现实意义,但解决过程具有普遍意义,记之备忘。
编译64-bits GDB for Solaris 10与本BUG无本质关联,不在此记录。
说个题外话,现在这些非漏洞、非攻防类的基础CS技术交流,找不到合适的地方了。 过去我们有个APUE小站,还是很不错的。作为CS专业毕业的程序员,我一直很喜欢进 行这类技术交流,没有太多是是非非,只有发现问题、解决问题的简单乐趣。我自己 分享的技术文章当然可以放在此间,但这里终归是个只读区域,无法形成交流。想起 从前的APUE小站,有些淡淡的遗憾。