我的问题是,当我运行程序,即使是一个非常简单的程序,我也会收到一个段错误,我用gdb将其缩小到了libexpect.so中的一个名为exp_spawnv的函数。
我知道我已经正确链接了库,它可以编译通过,实际上,在ubuntu中编译和运行时整个问题都不存在,但在我的arch linux安装中,我得到了分段错误,稍后我会详细说明。我在arch上构建它的原因是我希望最终能够在大多数发行版上构建该项目。
我的想法是,在我的arch安装中,当调用exp_spawnv函数时,可能存在权限失败的管道、fork或其他问题。
为了证明我没有做什么奇怪的事情,这里有一个简单的main.cpp以说明目的。
#include <tcl8.5/expect.h>
int main()
{
FILE* file = exp_popen("bash");
}
这只是有史以来最简单的expect程序。以下是我编译和链接它的过程。 $ g++ -ggdb -c main.cpp main.cpp: 在函数‘int main()’中: main.cpp:5:32: 警告:从字符串常量转换为‘char*’已弃用[-Wwrite-strings] $ g++ main.o -lexpect -o mainprog
于是我得到了可执行文件mainprog……但只运行它会导致分段错误,没有其他输出。
如果我在gdb中运行mainprog,它会告诉我exp_spawnv中有一个分段错误。以下是我在gdb中执行的操作及末尾的回溯信息。
(gdb) run
开始运行程序:/home/user/testlibexpect/mainprog
警告:无法加载linux-vdso.so.1的共享库符号。
您需要“set solib-search-path”或“set sysroot”吗?
程序收到SIGSEGV信号,分段错误。
0x00007ffff7bc8836在/usr/lib/libexpect.so中的exp_spawnv()。
(gdb) backtrace
0 0x00007ffff7bc8836在/usr/lib/libexpect.so中的exp_spawnv()。
1 0x00007ffff7bc8cb4在/usr/lib/libexpect.so中的exp_spawnl()。
2 0x00007ffff7bc8d01在/usr/lib/libexpect.so中的exp_popen()。
3 0x000000000040069e在main.cpp的main()中:5
有两件事让我担忧。
查看libexpect的man页面,我知道exp_spawnv会fork一个新进程,并且我可以通过FILE*进行通信。那么我猜测收到SIGSEGV信号是因为fork出了一些问题吗?
回溯中的那行警告信息(warning: Could not load shared library symbols for linux-vdso.so.1.)看起来有问题?
总之,我的问题是我应该查找什么来解决这个问题?我已经尝试从源代码构建expect库,也尝试通过arch软件包管理器pacman获取它...但问题仍然存在,所以我不认为库构建是有问题的,如果你明白我的意思的话。
编辑:根据我所做的研究,我的第二个担忧点并不是问题,只是表面问题。
eclipse中的反汇编如下:
00007ffff7bc87c6: mov 0x20c68b(%rip),%rax # 0x7ffff7dd4e58
00007ffff7bc87cd: mov (%rax),%rax
00007ffff7bc87d0: test %rax,%rax
00007ffff7bc87d3: je 0x7ffff7bc87d7 <exp_spawnv+935>
00007ffff7bc87d5: callq *%rax
00007ffff7bc87d7: mov %r12,%rsi
00007ffff7bc87da: mov %rbp,%rdi
00007ffff7bc87dd: callq 0x7ffff7bb2330 <execvp@plt>
00007ffff7bc87e2: callq 0x7ffff7bb1720 <__errno_location@plt>
00007ffff7bc87e7: mov 0x24(%rsp),%edi
00007ffff7bc87eb: mov %rax,%rsi
00007ffff7bc87ee: mov $0x4,%edx
00007ffff7bc87f3: xor %eax,%eax
00007ffff7bc87f5: callq 0x7ffff7bb1910 <write@plt>
00007ffff7bc87fa: mov $0xffffffff,%edi
00007ffff7bc87ff: callq 0x7ffff7bb23d0 <exit@plt>
00007ffff7bc8804: nopl 0x0(%rax)
00007ffff7bc8808: xor %eax,%eax
00007ffff7bc880a: movl $0x0,0x20dd3c(%rip) # 0x7ffff7dd6550
00007ffff7bc8814: callq 0x7ffff7bb1700 <exp_init_pty@plt>
00007ffff7bc8819: xor %eax,%eax
00007ffff7bc881b: callq 0x7ffff7bb2460 <exp_init_tty@plt>
00007ffff7bc8820: lea -0x1c97(%rip),%rdi # 0x7ffff7bc6b90
00007ffff7bc8827: callq 0x7ffff7bb2540 <expDiagLogPtrSet@plt>
00007ffff7bc882c: mov 0x20c555(%rip),%rax # 0x7ffff7dd4d88
00007ffff7bc8833: mov (%rax),%rax
00007ffff7bc8836: mov 0x410(%rax),%rdi
我想到的答案
这是我最终想到的解决方案,我接受了szx的答案,因为它让我走上了这条路,一旦我知道我在寻找什么,这就变得微不足道了。
//do not use TCL stubs as this is a main
#undef USE_TCL_STUBS
#include <iostream>
using std::cout;
using std::endl;
//headers that must be included when using expectTcl as an extension to c++ program
#include <stdio.h>
#include <stdlib.h>
#include <expectTcl/tcl.h>
#include <expectTcl/expect_tcl.h>
#include <expectTcl/expect.h>
//enums representing cases of what expect found in loop
enum{FOUNDSEARCH, PROMPT};
int main()
{
/* initialise expect and tcl */
Tcl_Interp *interp = Tcl_CreateInterp();
if(Tcl_Init(interp) == TCL_ERROR)
{
cout << "TCL failed to initialize." << endl;
}
if(Expect_Init(interp) == TCL_ERROR)
{
cout << "Expect failed to initialize." << endl;
}
/* end of intialisation procedure */
//open a shell with a pipe
char shellType[] = "sh";
FILE* fp = exp_popen(shellType);
//should we exit from the loop which is studying sh output
bool shouldBreak = false;
//did we find the pwd
bool foundSearch = false;
//does it look like expect is working
bool expectWorking = false;
//did we receive a prompt...therefore we should send a command
bool receivedPrompt = false;
while(shouldBreak == false)
{
switch(exp_fexpectl(fp,
exp_glob, "/tools/test*", FOUNDSEARCH, //different
exp_glob,"# ", PROMPT, //cases are shown here
exp_end)) //that the expect loop could encounter
{
case FOUNDSEARCH:
foundSearch = true;
break;
case PROMPT:
if (receivedPrompt)
{
shouldBreak = true;
expectWorking = true;
}
else
{
receivedPrompt = true;
fprintf(fp, "%s\r", "pwd");
}
break;
case EXP_TIMEOUT:
shouldBreak = true;
break;
case EXP_EOF:
shouldBreak = true;
break;
}
//cout << "exp_match : " << exp_match << endl;
}
cout << endl;
if (foundSearch)
{
cout << "Expect found output of pwd" << endl;
}
else
{
cout << "Expect failed to find output of pwd" << endl;
}
if(expectWorking)
{
cout << "The expect interface is working" << endl;
}
else
{
cout << "The expect interface is not working" << endl;
}
cout << "The test program successfully reached the end" << endl;
}
我在这里所做的只是展示了如何初始化expect/tcl以防止szx所提到的问题。然后,我只是做了一个典型的expect问题,基本上说如果shell提示您输入密码,则发送pwd。然后,如果它给出当前目录,expect就可以工作了。这种结构对于像ssh这样的东西非常有用。比如说,如果你想自动化地ssh到某个地方,做一些事情,然后离开那里。特别是如果你想做几百次,而且你不想确认每个主机的真实性并每次输入密码。
请注意,由于我没有从源代码构建而是只使用了
apt-get
,因此在Ubuntu上我从未不得不这样做。然而,我的项目要求我从源代码构建,所以我找到了一个非常好的、整洁的方法,在http://www.linuxfromscratch.org/lfs/view/development/chapter05/tcl.html和http://www.linuxfromscratch.org/lfs/view/development/chapter05/expect.html上...实际上,整个网站看起来都非常有用。感谢szx的再次帮助。