【问题标题】:Why do I get a segmentation fault in my simple c++ program using libexpect.so?为什么我在使用 libexpect.so 的简单 c++ 程序中出现分段错误?
【发布时间】:2012-12-04 15:05:30
【问题描述】:

我正忙于一个项目,我必须在 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 中存在 seg 错误。这是我在 gdb 中所做的,最后是回溯。

(gdb) 运行

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

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

你需要“set solib-search-path”还是“set sysroot”?

程序收到信号SIGSEGV,分段错误。

0x00007ffff7bc8836 in exp_spawnv () from /usr/lib/libexpect.so

(gdb) 回溯

0 0x00007ffff7bc8836 in exp_spawnv () from /usr/lib/libexpect.so

1 0x00007ffff7bc8cb4 in exp_spawnl () from /usr/lib/libexpect.so

2 0x00007ffff7bc8d01 in exp_popen () from /usr/lib/libexpect.so

3 0x000000000040069e in main() at main.cpp:5

我关心两件事。

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

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

总之,我的问题是我应该如何解决这个问题?我已经尝试从源代码构建 expect 库,并通过 arch 包管理器 pacman 获取它......问题仍然存在,所以如果你明白我的意思,我认为库构建不会损坏。

编辑:根据我所做的研究,我担心的第 2 点不是问题,只是表面问题。

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

【问题讨论】:

  • 我将其编辑到我的问题中
  • 但是里面没有0x00007ffff7bc8836,地址变了吗?
  • 没有运行和回溯后我说过反汇编它仍然是相同的地址
  • 我又编辑了一遍,不过这次我用的是eclipse调试器,在反汇编中复制到需要的地址。
  • 这是一个有用的答案:stackoverflow.com/questions/11046935/…

标签: c++ segmentation-fault expect


【解决方案1】:

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

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

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

【讨论】:

  • 啊,是的,这可能是个问题...我在 tcl.h 中没有找到它,但它在 tclDecls.h 中.到目前为止,我在源目录中使用grep -r "tclStubsPtr" * 来查找指针的任何提及或初始化,我在 tclStubLib.c 中找到了它。此外,greplibtcl8 中找到了指针。 5.so 库。你会注意到我没有链接到那个库。所以我的下一步是链接它,看看是否能解决任何问题……如果没有,我会调查是否需要调用 tcl。
  • 好吧,原来你给了我一个出色的领先优势...tclStubsPtr 指针保持为空的原因可能有很多。但无论出于何种根本原因,最大的问题是 expect/tcl 环境没有正确初始化。经过一番挖掘后,我终于在man libexpect(我开始的地方)找到了答案。 Don Libes,作者,将exp_main_exp.c 作为原型 main 包含在源代码分发中提供的源代码中。它涉及包含 tcl 头和 expect_tcl 联合头。
  • 然后我强制expect/tcl全局实体初始化。不幸的是,这有点像黑客......我不应该初始化 tcl 甚至包括 tcl 标头。无论如何,我接受你的回答,因为我从没想过要查找为什么 tcl 没有正确初始化。对于任何有兴趣的人,我已经编辑了一个示例主程序,它在我的问题中做到了这一点。
【解决方案2】:

我最关心的是编译时的警告。该接口表面上要求您传递一个可写字符串,但您传递一个字符串常量。如果它确实写入它,它将导致分段错误。所以它看起来很适合解决您的问题。

如果您尝试创建一个可写缓冲区并传递它会发生什么:

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

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

【讨论】:

  • 感谢巴斯的快速响应,这确实解决了警告,但同样的问题仍然存在。
  • 抱歉,我的回答很简洁,这是我关于堆栈溢出的第一个问题,我没有意识到在发送它的评论中点击回车。我为我糟糕的编程实践(我的疏忽)道歉,你确实是对的,让我注意到这一点。但可惜它并没有解决主要问题,但可以为我以后省去麻烦。
  • 太糟糕了,它没有修复它。至于这里的评论,你可以随时删除或编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-31
  • 1970-01-01
相关资源
最近更新 更多