Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

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.

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.

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.

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

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

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

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

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

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) run

Starting program: /home/user/testlibexpect/mainprog

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

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

Program received signal SIGSEGV, Segmentation fault.

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

(gdb) backtrace

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

Two things concern me.

  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?

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

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.

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

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

THE ANSWER I CAME UP WITH

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\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;
}

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.

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.

Thanks again to szx

like image 282
benzeno Avatar asked Dec 04 '12 15:12

benzeno


People also ask

What causes a segmentation fault in C?

In practice, segfaults are almost always due to trying to read or write a non-existent array element, not properly defining a pointer before using it, or (in C programs) accidentally using a variable's value as an address (see the scanf example below).

What is segmentation fault in C with example?

A segmentation fault occurs when your program attempts to access an area of memory that it is not allowed to access. In other words, when your program tries to access memory that is beyond the limits that the operating system allocated for your program.

How do you handle a segmentation fault?

Troubleshooting the problem: Check EVERY place in your program that uses pointers, subscripts an array, or uses the address operator (&) and the dereferencing operator (*). Each is a candidate for being the cause of a segmentation violation. Make sure that you understand the use of pointers and the related operators.


2 Answers

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

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.

like image 91
szx Avatar answered Sep 28 '22 02:09

szx


I'd be most concerned about the warning when compiling. The interface appearantly requires you to pass a writable string, but you pass a string constant. If it does indeed write to it, it will result in a segmentation fault. So it looks like a good candidate for your problem.

What happens if you instead try to create a writable buffer and pass that:

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

Update: I've tested your program (with the above change, and a "return 0;" at the end), and it works fine for me. Perhaps there's something wrong with your system, like a half-installed library? You can check if it also fails when you link with -static. If you do that, you are sure that the compile-time linked library is the same as the run-time used library (because it will be included in the executable at compile-time).

like image 44
Bas Wijnen Avatar answered Sep 28 '22 02:09

Bas Wijnen