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

2022-01-12 00:00:00 segmentation-fault c++ expect

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

I am busy with a project where I have to automate some processes in bash or ssh so I decided to use the libexpect.so library. If you don't know what libexpect is, it provides an expect extension that I can use in a c++ program, and expect is just a program where you can run automated scripts for things like ssh. So I can execute a script which attempts to ssh somewhere...when the password prompt is found by expect I could have already given expect a password to send.

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

My problem is that when I run a program, even a really simple one, I get a segmentation fault which I narrowed down, with gdb, to a function in libexpect.so called exp_spawnv.

我知道我已经正确链接了库,它编译得很好,事实上,当我在 ubuntu 中编译和运行时,整个问题并不存在,但是在我的 arch linux 安装中,我得到了分段错误,我稍后会详细说明.我在 Arch 上构建它的原因是因为我希望最终使该项目可以在大多数发行版上构建.

I know I've linked the library right, it compiles fine and infact the whole problem doesn't exist when I compile and run in ubuntu, but in my arch linux install I get the segmentation fault which I'll detail later. The reason why I'm building it on arch is because I want to eventually make the project buildable on most distros.

我的想法是,在我的 Arch 安装中,当调用 exp_spawnv 函数(可能是管道、叉子或其他)时,某些权限会失败.

My thoughts are that in my arch installation there are permissions which fail when the exp_spawnv function is called, perhaps a pipe, fork or whatever.

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

To prove that I'm not doing something funky, here is a simple main.cpp for illustration purposes.

#include <tcl8.5/expect.h>

int main()
{
  FILE* file = exp_popen("bash");
}

所以它几乎是有史以来最简单的期望程序.这是我编译和链接它.

So it is just about the most simple expect program ever made. Here is me compiling and linking it.

$ g++ -ggdb -c main.cpp

$ g++ -ggdb -c main.cpp

main.cpp:在函数'int main()'中:

main.cpp: In function ‘int main()’:

main.cpp:5:32: 警告:不推荐将字符串常量转换为‘char*’ [-Wwrite-strings]

main.cpp:5:32: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]

$ g++ main.o -lexpect -o mainprog

$ g++ main.o -lexpect -o mainprog

所以我得到了我的可执行 mainprog...只是运行它会给我一个分段错误,没有别的.

So I got my executable mainprog...just running that will give me a segmentation fault and nothing else.

如果我在 gdb 中运行 mainprog,它会告诉我 exp_spawnv 中存在 seg 错误.这是我在 gdb 中所做的,最后是回溯.

If I run mainprog in gdb it tells me that there is a seg fault in exp_spawnv. Here is what I did in gdb with the backtrace at the end.

(gdb) 运行

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

Starting program: /home/user/testlibexpect/mainprog

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

warning: Could not load shared library symbols for linux-vdso.so.1.

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

Do you need "set solib-search-path" or "set sysroot"?

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

Program received signal SIGSEGV, Segmentation fault.

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

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

(gdb) 回溯

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

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

1 0x00007ffff7bc8cb4 in exp_spawnl () 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

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

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

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

我关心两件事.

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

  1. looking at the manpage for libexpect, I know exp_spawnv forks a new process and I'll be able to communicate through the FILE*. So I guess that SIGSEGV signal is received because something bad has happened with the fork?

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

That line (warning: Could not load shared library symbols for linux-vdso.so.1.) in the backtrace looks fishy?

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

So in summary, my question is what should I look into to fix this problem? I have tried building the expect library from source and by acquiring it with the arch package manager pacman...the problem persists so I don't think the library build is corrupt if you know what I mean.

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

point 2 of my concerns is not a problem according to research I've done, just cosmetic.

eclipse的反汇编如下:

The disassembly from eclipse is below:

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 的回答,因为它引导我走上这条路,一旦我知道自己在寻找什么,这条路就变得微不足道了.

Here is the solution I eventually came up with, I have accepted szx's answer because it lead me down this path which was trivial once I knew what I was looking for.

//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", "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,做某事然后离开那里.尤其是如果你想做几百次,又不想每次都确认每个主机的真实性并输入密码.

All I've done here is shown how to initialize expect/tcl to prevent the problem I had that szx was talking about. Then I just did a typical expect like problem where I pretty much said if the shell prompts you for input send it pwd. Then, if it gives you the current directory expect is working. This kind of structure can be extremely useful for something like ssh. Say if you want to automate sshing somewhere, doing something and then getting out of there. Especially if you want to do it a couple hundred times and you don't want to confirm the authenticity of every host and type a password in every time.

请注意,出于某种原因,我从未在 ubuntu 上执行此操作...可能是因为我没有从源代码构建它,而只是使用了 apt-get.但是,我的项目需要我从源代码构建,所以我在 http://www.linuxfromscratch.org/lfs/view/development/chapter05/tcl.html 和 http://www.linuxfromscratch.org/lfs/view/development/chapter05/expect.html...事实上整个网站看起来真的很有用.

Note that I never had to do this on ubuntu for some reason...possibly because I did not build it from source and just used apt-get. However, my project requires me to build from source so I found a really good, neat way to do it on http://www.linuxfromscratch.org/lfs/view/development/chapter05/tcl.html and http://www.linuxfromscratch.org/lfs/view/development/chapter05/expect.html... infact that whole website looks really useful.

再次感谢 szx

推荐答案

Tcl内部定义的全局变量TclStubs *tclStubsPtrexp_spawnv<时恰好为NULL/code> 尝试访问定义为该结构成员的Tcl_ErrnoMsg(参见tcl.h):

The global variable TclStubs *tclStubsPtr defined inside Tcl happens to be NULL when exp_spawnv tries to accessTcl_ErrnoMsg which is defined as a member of that structure (see tcl.h):

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

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

I'm not familiar with neither expect nor Tcl but the above suggests that you probably should call some initialization subroutine (if there exists one) or set it manually.

相关文章