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

c++, expect, segmentation-fault

Solution

The global variable `TclStubs *tclStubsPtr` defined inside Tcl happens to be `NULL` when `exp_spawnv` tries to access`Tcl_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.

Problem

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. 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? 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

Original source