繁体   English   中英

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

[英]Why do I get a segmentation fault in my simple c++ program using libexpect.so?

我忙于一个项目,在该项目中我必须自动化bash或ssh中的某些进程,因此我决定使用libexpect.so库。 如果您不知道libexpect是什么,它将提供一个我可以在c ++程序中使用的Expect扩展,而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");
}

因此,这只是有史以来最简单的期望程序。 这是我编译并链接它。

$ 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)运行

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

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

您是否需要“设置solib-搜索路径”或“设置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

3 0x000000000040069e在main。)中main.cpp:5

有两件事与我有关。

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

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

因此,总而言之,我的问题是我应该寻找什么来解决此问题? 我尝试过从源代码构建期望库,并通过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在谈论我遇到的问题。 然后,我只是做了一个典型的期望之类的问题,我几乎说过,如果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

exp_spawnv尝试访问Tcl_ErrnoMsg (定义为该结构的成员)时,在Tcl中定义的全局变量TclStubs *tclStubsPtr恰好为NULL (请参阅tcl.h ):

#ifndef Tcl_ErrnoMsg
#define Tcl_ErrnoMsg \
    (tclStubsPtr->tcl_ErrnoMsg) /* 128 */
#endif

我既不期望使用Tcl,也不熟悉Tcl,但是上面的建议建议您应该调用一些初始化子例程(如果存在)或手动进行设置。

编译时,我会最担心警告。 该接口显然要求您传递一个可写的字符串,但是您传递一个字符串常量。 如果确实写入了它,将导致分段错误。 因此,它看起来像是解决您问题的理想人选。

如果您尝试创建可写缓冲区并传递该缓冲区,将会发生什么情况:

char name[] = "bash";
FILE* file = exp_popen(name);

更新:我已经测试了您的程序(进行了上述更改,并在最后加上“ return 0;”),对我来说很好。 也许您的系统有问题,例如半安装的库? 与-static链接时,可以检查它是否也失败。 如果这样做,请确保编译时链接库与运行时使用的库相同(因为它将在编译时包含在可执行文件中)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM