为什么我使用libexpect.so在我的简单c ++程序中出现分段错误?

ben*_*eno 8 c++ expect segmentation-fault

我忙于一个项目,我必须在bash或ssh中自动化一些进程,所以我决定使用libexpect.so库.如果您不知道libexpect是什么,它提供了我可以在c ++程序中使用的expect扩展,并且期望只是一个程序,您可以在其中运行ssh等自动脚本.所以我可以执行一个尝试在某处ssh的脚本...当找到密码提示时,我可能已经预期会发送密码.

我的问题是,当我运行一个程序,即使是一个非常简单的程序时,我得到一个分段错误,我用gdb缩小到libexpect.so中的一个函数,名为exp_spawnv.

我知道我已经链接了库,它编译得很好,事实上当我在ubuntu中编译和运行时整个问题都不存在,但是在我的arch linux安装中,我得到了分段错误,我将在后面详述.我之所以在arch上构建它是因为我想最终使项目可以在大多数发行版上构建.

我的想法是,在我的arch安装中,有权限在调用exp_spawnv函数时失败,可能是管道,分支或其他任何东西.

为了证明我没有做一些时髦的事情,这里有一个简单的main.cpp用于说明目的.

#include <tcl8.5/expect.h>

int main()
{
  FILE* file = exp_popen("bash");
}
Run Code Online (Sandbox Code Playgroud)

所以它只是有史以来最简单的期望计划.这是我编译和链接它.

$ 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中有一个seg错误.这是我在gdb中使用最后的回溯做的.

(gdb)运行

启动程序:/ home/user/testlibexpect/mainprog

警告:无法加载linux-vdso.so.1的共享库符号.

你需要"set solib-search-path"或"set sysroot"吗?

程序接收信号SIGSEGV,分段故障.

来自/usr/lib/libexpect.so的exp_spawnv()中的0x00007ffff7bc8836

(gdb)回溯

来自/usr/lib/libexpect.so的exp_spawnv()中的0 0x00007ffff7bc8836

来自/usr/lib/libexpect.so的exp_spawnl()中的1 0x00007ffff7bc8cb4

来自/usr/lib/libexpect.so的exp_popen()中的2 0x00007ffff7bc8d01

Main.cpp中的main()中的3 0x000000000040069:5

有两件事与我有关.

  1. 看看libexpect的联机帮助页,我知道exp_spawnv会分配一个新进程,我将能够通过FILE*进行通信.所以我想收到SIGSEGV信号因为叉子发生了什么不好的事情?

  2. 回溯中的那一行(警告:无法加载linux-vdso.so.1的共享库符号.)看起来很可疑?

总而言之,我的问题是我应该考虑什么来解决这个问题?我已经尝试从源代码构建expect库并通过arch包管理器pacman获取它......问题仍然存在,所以如果你知道我的意思,我认为库构建是不是很糟糕.

编辑:根据我所做的研究,我的顾虑点2不是问题,只是化妆品.

日食的反汇编如下:

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
Run Code Online (Sandbox Code Playgroud)

回答我的答案

这是我最终提出的解决方案,我已经接受了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;
}
Run Code Online (Sandbox Code Playgroud)

我在这里完成的所有内容都展示了如何初始化expect/tcl以防止szx所讨论的问题.然后我只是做了一个典型的期望问题,我几乎说如果shell提示你输入发送它pwd.然后,如果它给你当前目录期望正在工作.这种结构对于像ssh这样的东西非常有用.假如你想在某个地方自动化sshing,做某事然后离开那里.特别是如果你想要这样做几百次,你不想确认每个主机的真实性并且每次都输入密码.

请注意,由于某种原因,我从来不必在ubuntu上执行此操作...可能是因为我没有从源代码构建它而只是使用它apt-get.但是,我的项目要求我从源代码构建,所以我在http://www.linuxfromscratch.org/lfs/view/development/chapter05/tcl.htmlhttp:// www上找到了一个非常好的,简洁的方法..linuxfromscratch.org/lfs/view/development/chapter05/expect.html ...事实上整个网站看起来真的很有用.

再次感谢szx

szx*_*szx 4

Tcl 内部定义的全局变量恰好是在尝试访问定义为该结构的成员时发生的(请参阅 参考资料TclStubs *tclStubsPtr):NULLexp_spawnvTcl_ErrnoMsgtcl.h

#ifndef Tcl_ErrnoMsg
#define Tcl_ErrnoMsg \
    (tclStubsPtr->tcl_ErrnoMsg) /* 128 */
#endif
Run Code Online (Sandbox Code Playgroud)

我对 Expect 和 Tcl 都不熟悉,但上面的内容表明您可能应该调用一些初始化子例程(如果存在)或手动设置它。