Bug 489677 - Error when using remote application launch/debug
Summary: Error when using remote application launch/debug
Status: NEW
Alias: None
Product: CDT
Classification: Tools
Component: cdt-debug (show other bugs)
Version: Next   Edit
Hardware: PC Linux
: P3 normal with 1 vote (vote)
Target Milestone: ---   Edit
Assignee: cdt-debug-inbox@eclipse.org CLA
QA Contact: Jonah Graham CLA
URL:
Whiteboard:
Keywords:
: 510605 (view as bug list)
Depends on:
Blocks:
 
Reported: 2016-03-15 14:06 EDT by Simon Marchi CLA
Modified: 2020-09-04 15:17 EDT (History)
6 users (show)

See Also:


Attachments
Screenshot (62.88 KB, image/png)
2016-03-15 14:22 EDT, Simon Marchi CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Simon Marchi CLA 2016-03-15 14:06:29 EDT
Using CDT, I am trying to debug an application using a small ARM board as the target.  I consistently get this error:

http://nova.polymtl.ca/~simark/ss/fileikFgVT.png

The launch is configured to use an SSH remote connection and to upload the binary to the target.  If I choose to "Skip download to target path" (it sounds like it should say upload... anyway), then it works.  When CDT tries to upload the binary itself, I can see that the binary is correctly uploaded (the md5sum matches), but the file is not executable:

  $ ls -ltr
  ...
  -rw-rw-r-- 1 simark simark   32919 Mar 15 14:02 TestArm
Comment 1 Marc Khouzam CLA 2016-03-15 14:19:19 EDT
Can you attach the screenshot to this bug.  The day you loose your account at polymtl.ca we loose the info for this bug.

Doesn't CDT explicitly set the executable bit for the file?
Comment 2 Simon Marchi CLA 2016-03-15 14:21:39 EDT
I made the following patch:
https://git.eclipse.org/r/#/c/68396/

It makes the remote launch works for my scenario, but I am not 100% that it's the right fix.

AFAIU, the procedure to prepare a remote launch looks like this:

1. Upload the file using sftp
2. Start a remote shell and execute chmod +x
3. Start a remote shell and execute gdbserver

My initial thought was that we don't wait until we have confirmation that #2 is complete before continuing with #3.  So gdbserver would try (and fail) to execute a file that is not executable yet.  I would find it surprising however that gdbserver's shell and gdbserver would start faster than the time required by chmod to do its work.  But it's plausible, since there's nothing that guarantees the order of execution right now, so let's assume this is what's happening.  Even when we get the error, presumably because gdbserver couldn't execute the file, the chmod process should still finish eventually and leave us with an executable file on the filesystem.  However, that's not what I see, the file is still non-executable.

Because of this, I don't think I fully understand the problem, and therefore I am not sure my patch is right.  It might be that it changes the timing in some way and I get lucky, without fixing the real problem...
Comment 3 Simon Marchi CLA 2016-03-15 14:22:56 EDT
Created attachment 260320 [details]
Screenshot
Comment 4 Simon Marchi CLA 2016-03-15 14:24:59 EDT
(In reply to Marc Khouzam from comment #1)
> Can you attach the screenshot to this bug.  The day you loose your account
> at polymtl.ca we loose the info for this bug.
> 
> Doesn't CDT explicitly set the executable bit for the file?

Yes, it runs chmod on the target.  Although it doesn't check if the command worked, so it could fail and we wouldn't know.
Comment 6 Simon Marchi CLA 2016-03-15 18:55:20 EDT
I replaced the call to chmod by a small wrapper that logs its invocation:

  chmod $@
  echo "$(date) chmod $@" >> /tmp/chmod-log

And it appears that when the problem occurs, chmod is not called.  So the problem may revolve around the fact that the second ssh shell execution (to start gdbserver) messes with the first one (that executes chmod).  I tried to dive in the RSE code, but it's not clear to me what happens.

It explains at least why the file ends up non-executable, the chmod command never actually gets executed on the target.
Comment 7 Teodor Madan CLA 2016-03-16 04:02:35 EDT
(In reply to Simon Marchi from comment #6)
> I replaced the call to chmod by a small wrapper that logs its invocation:
> 
>   chmod $@
>   echo "$(date) chmod $@" >> /tmp/chmod-log
> 
> And it appears that when the problem occurs, chmod is not called.  So the
> problem may revolve around the fact that the second ssh shell execution (to
> start gdbserver) messes with the first one (that executes chmod).  I tried
> to dive in the RSE code, but it's not clear to me what happens.
> 
> It explains at least why the file ends up non-executable, the chmod command
> never actually gets executed on the target.

Could it be that ssh command is sent, but not executed on target side? I have seen a similar issue with Bug 467833 where I have ended implementing an workaround on eclipse side.
Comment 8 Teodor Madan CLA 2016-03-16 04:08:49 EDT
(In reply to Simon Marchi from comment #0)
> Using CDT, I am trying to debug an application using a small ARM board as
> the target.  I consistently get this error:
> 
> http://nova.polymtl.ca/~simark/ss/fileikFgVT.png
> 
> The launch is configured to use an SSH remote connection and to upload the
> binary to the target.  If I choose to "Skip download to target path" (it
> sounds like it should say upload... anyway), then it works.  When CDT tries
> to upload the binary itself, I can see that the binary is correctly uploaded
> (the md5sum matches), but the file is not executable:
> 
>   $ ls -ltr
>   ...
>   -rw-rw-r-- 1 simark simark   32919 Mar 15 14:02 TestArm


The setup is close to the setup from Bug 467833.
Comment 9 Simon Marchi CLA 2016-03-16 09:23:44 EDT
> Could it be that ssh command is sent, but not executed on target side? I
> have seen a similar issue with Bug 467833 where I have ended implementing an
> workaround on eclipse side.

It could be, but I don't really know how to debug that.

You seem to have some experience debugging this kind of scenario, do you have a some pointers oh how you do that?  Would sniffing with Wireshark help me at all, or I won't see anything useful because it's encrypted?
Comment 10 Teodor Madan CLA 2016-03-16 09:41:03 EDT
(In reply to Simon Marchi from comment #9)
> > Could it be that ssh command is sent, but not executed on target side? I
> > have seen a similar issue with Bug 467833 where I have ended implementing an
> > workaround on eclipse side.
> 
> It could be, but I don't really know how to debug that.
> 
> You seem to have some experience debugging this kind of scenario, do you
> have a some pointers oh how you do that?  Would sniffing with Wireshark help
> me at all, or I won't see anything useful because it's encrypted?

It did help to start ssh daemon with additional verbose setting to confirm that packets do reach into target applications. 

BTW, do you experience similar issue with CDT 8.8.1? (using RSE ssh capabilities)
Comment 11 Simon Marchi CLA 2016-03-16 10:23:04 EDT
(In reply to Teodor Madan from comment #10)
> It did help to start ssh daemon with additional verbose setting to confirm
> that packets do reach into target applications. 

Some things appear in the log, but I can't really make sense out of it.  Also, I don't know if those are related to the "chmod" connection or "gdbserver" connection (assuming they use different connections).
 
> BTW, do you experience similar issue with CDT 8.8.1? (using RSE ssh
> capabilities)

I just checkout out CDT' 8.8 branch.  The first two launches worked somehow. but now it's constantly failing as well.
Comment 12 Teodor Madan CLA 2016-03-16 11:36:11 EDT
(In reply to Simon Marchi from comment #11)
> (In reply to Teodor Madan from comment #10)
> > BTW, do you experience similar issue with CDT 8.8.1? (using RSE ssh
> > capabilities)
> 
> I just checkout out CDT' 8.8 branch.  The first two launches worked somehow.
> but now it's constantly failing as well.

Make sure that you rebuild RSE for Bug 467833. Also, to activate workaround you have to pass "-vmargs -DRSE_SHELL_READY_PING=200" to eclipse
Comment 13 Teodor Madan CLA 2016-03-16 11:58:38 EDT
(In reply to Simon Marchi from comment #11)
> (In reply to Teodor Madan from comment #10)
> > It did help to start ssh daemon with additional verbose setting to confirm
> > that packets do reach into target applications. 
> 
> Some things appear in the log, but I can't really make sense out of it. 
> Also, I don't know if those are related to the "chmod" connection or
> "gdbserver" connection (assuming they use different connections).

AFAIR, I had to rebuild sshd with debug define to output channel messages as well.
Comment 14 Simon Marchi CLA 2016-03-16 13:44:01 EDT
(In reply to Teodor Madan from comment #12)
> Make sure that you rebuild RSE for Bug 467833. Also, to activate workaround
> you have to pass "-vmargs -DRSE_SHELL_READY_PING=200" to eclipse

I am not sure what you mean by having to rebuild RSE.  The version I use already has the code that handles RSE_SHELL_READY_PING it seems.

So I just tested adding -DRSE_SHELL_READY_PING=200 to my test Eclipse launch, it seems like it does fix the problem.  I can see briefly (before it disappears) the "ping"'s being displayed on the remote shell view.
Comment 15 Teodor Madan CLA 2016-03-17 04:07:36 EDT
(In reply to Simon Marchi from comment #14)
> So I just tested adding -DRSE_SHELL_READY_PING=200 to my test Eclipse
> launch, it seems like it does fix the problem.  I can see briefly (before it
> disappears) the "ping"'s being displayed on the remote shell view.

To me this is a confirmation of the same target SW/HW stack issue for opening a ssh channel. And the proper fix would be in o.e.remote to have a similar ssh opening handshake implementation.
Comment 16 Teodor Madan CLA 2017-01-18 03:14:05 EST
*** Bug 510605 has been marked as a duplicate of this bug. ***
Comment 17 Adrian Oltean CLA 2017-01-18 03:32:19 EST
Can someone explain why is this bug having such a low priority? There was a workaround for RSE but now (eclipse neon.2) there's no way to start a remote debug session...
Comment 18 Mihai Furis CLA 2017-01-30 10:54:55 EST
There are 2 bugs related to launching GDB remotely:
1. The two procedures that are executing remote commands RemoteHelper.execCmdInRemoteShell and
RemoteHelper.remoteShellExec don't wait for shell prompt after the command is executed.
For this reason new commands try to execute before the previous command has finished and the system gets blocked because there is no shell prompt available. To fix this I wrote a new procedure waitForShellPrompt that should be called before the output stream is flushed.

private static void waitForShellPrompt(Channel channel, InputStream is) throws Exception{
int SIZE = 1024;
byte[] buffer = new byte[SIZE];
String line="";
while(true){ 
while (is.available() > 0) {
int i = is.read(buffer, 0, 1024);
if (i < 0)
{ break; }
line = new String(buffer, 0, i);
line= line.trim();
// System.out.println(line);
}
if(line.contains("logout"))
{ break; }
if (channel.isClosed())
{ break;}
if (!line.isEmpty() && (line.endsWith("#") ||
line.endsWith(">") ||
line.endsWith("$") ||
line.endsWith(":")))
{break;}
try
{ Thread.sleep(500); }
catch (InterruptedException e)
{ e.printStackTrace(); }
}
}

insert a call to the above procedure before the output stream is flushed as below:

IRemoteCommandShellService shellService = conn.getService(IRemoteCommandShellService.class);
IRemoteProcess remoteProcess = null;
Process p = null;

try { remoteProcess = shellService.getCommandShell(IRemoteProcessBuilder.ALLOCATE_PTY); 
p = new RemoteProcessAdapter(remoteProcess); 
InputStream is =p.getInputStream();
OutputStream os = remoteProcess.getOutputStream(); os.write(remoteCommand.getBytes()); 
waitForShellPrompt(((JSchProcessBuilder) remoteProcess.getProcessBuilder()).getChannel(), is); 
os.flush(); 
} catch (IOException e) 
{ abort(Messages.RemoteRunLaunchDelegate_7, e, ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR); } catch (Exception e){ e.printStackTrace(); }
monitor.done();
return p;
}
It works for me but I do not know if it is the best solution as various machines can have various shell prompts and I check only for #, $, :, > prompts. This procedure should be called from both remoteShellExec and execCmdInRemoteShell.

2. The second bug is related to the RemoteGdbLaunchDelegate.launch method.
Apparently a listener on the monitoring stream that returns the GDB server output has been attached to the stream AFTER the command that starts GDB server has been executed. For this reason when we try to start GDB for the first time we'll not receive the response from the server because the LISTENER IS NOT triggered. However when we start the GDB server for the second time then the listener will be triggered and we'll receive the response from the server.
I suggest the fix below: 
IStreamsProxy streams = iProcess.getStreamsProxy();
IStreamMonitor monitorStream = streams.getOutputStreamMonitor();
if (monitorStream.getContents().contains("Listening on port")) {
synchronized (lock) {
gdbServerReady[0] = true;
lock.notifyAll();
}};
This code should be inserted before the listener is added to the stream, so we can check if the GDB is started and its output is on the pipe already. Practically, we do what the listener was supposed to do for the first execution of the GDB server.
//below is the original code that must follow after my fix:
monitorStream.addListener(new IStreamListener() {
@Override
public void streamAppended(String text, IStreamMonitor monitor) {
if (text.contains("Listening on port")) { //$NON-NLS-1$
synchronized (lock)
{ gdbServerReady[0] = true; 
lock.notifyAll(); }
monitor.removeListener(this);
}
}
});

Let me know what you think about my observations and how do you propose to solve these bugs. Thanks!
Comment 19 Adrian Oltean CLA 2017-01-31 09:16:30 EST
I'm really glad we have an attempt to fix the remote debugging issue but my opinion is that we might need a better approach to determine if the shell prompt is available and ready to interpret commands. Unix shell prompts are configurable and checking within a set of known patterns guarantees your solution will work only in *some* situations. I'm wondering if probing the console readiness with an 'echo <some_control_text>' command wouldn't be better approach for the implementation of waitForShellPrompt method. That's just a suggestion that will work in all Unix environments. The best approach would be to find if the console is ready by querying some objects states but I have no idea if this is possible.

Mihai, regarding the second problem reported, I suggest you open a bug in bugzilla as well. This a separate problem than the one affecting remote application debug. Let's handle them separately.

Doug/Marc/Simon/Teo, would you please comment on the possible fixes we have for the first problem Mihai tried to fix?

Thanks,
Adrian
Comment 20 Simon Marchi CLA 2017-02-01 14:03:30 EST
(In reply to Mihai Furis from comment #18)
> There are 2 bugs related to launching GDB remotely:

I'll answer to both separately.

> 1. The two procedures that are executing remote commands
> RemoteHelper.execCmdInRemoteShell and
> RemoteHelper.remoteShellExec don't wait for shell prompt after the command
> is executed.
> For this reason new commands try to execute before the previous command has
> finished and the system gets blocked because there is no shell prompt
> available. To fix this I wrote a new procedure waitForShellPrompt that
> should be called before the output stream is flushed.
> 
> private static void waitForShellPrompt(Channel channel, InputStream is)
> throws Exception{
> int SIZE = 1024;
> byte[] buffer = new byte[SIZE];
> String line="";
> while(true){ 
> while (is.available() > 0) {
> int i = is.read(buffer, 0, 1024);
> if (i < 0)
> { break; }
> line = new String(buffer, 0, i);
> line= line.trim();
> // System.out.println(line);
> }
> if(line.contains("logout"))
> { break; }
> if (channel.isClosed())
> { break;}
> if (!line.isEmpty() && (line.endsWith("#") ||
> line.endsWith(">") ||
> line.endsWith("$") ||
> line.endsWith(":")))
> {break;}
> try
> { Thread.sleep(500); }
> catch (InterruptedException e)
> { e.printStackTrace(); }
> }
> }
> 
> insert a call to the above procedure before the output stream is flushed as
> below:
> 
> IRemoteCommandShellService shellService =
> conn.getService(IRemoteCommandShellService.class);
> IRemoteProcess remoteProcess = null;
> Process p = null;
> 
> try { remoteProcess =
> shellService.getCommandShell(IRemoteProcessBuilder.ALLOCATE_PTY); 
> p = new RemoteProcessAdapter(remoteProcess); 
> InputStream is =p.getInputStream();
> OutputStream os = remoteProcess.getOutputStream();
> os.write(remoteCommand.getBytes()); 
> waitForShellPrompt(((JSchProcessBuilder)
> remoteProcess.getProcessBuilder()).getChannel(), is); 
> os.flush(); 

Note that you'd probably want to flush the output stream before waiting for the prompt.  If for some reason the command that you wrote is buffered locally, we'll end up waiting for the prompt forever.

> } catch (IOException e) 
> { abort(Messages.RemoteRunLaunchDelegate_7, e,
> ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR); } catch (Exception e){
> e.printStackTrace(); }
> monitor.done();
> return p;
> }
> It works for me but I do not know if it is the best solution as various
> machines can have various shell prompts and I check only for #, $, :, >
> prompts. This procedure should be called from both remoteShellExec and
> execCmdInRemoteShell.

As you and Adrian have mentioned, this would probably not be acceptable, since the prompts can vary.  Also, some commands can have a trailing (#, $, :, >) in their output, so you'd get a false positive.

I think the only safe way is to have the remote shell exit once the command execution is complete, and have CDT wait for the shell to exit.

The first part could be done by either:

 - appending something like "; exit" to the command (or maybe "; exit $?" if we want the shell to exit with the return value of the command)
 - shells end when their input stream is closed/reaches EOF, so perhaps we can just close our output stream.

Intuitively, I'd say that we could make CDT wait on the remove shell process by calling .waitFor() on the IRemoteProcess object, but I see this comment in IRemoteProcess.java, and I am not sure what to think about it:

	/**
	 * Wait until the process has terminated
	 * 
	 * Note: some implementations (e.g. JSch) will not work correctly if the remote process generates stdout or stderr but the
	 * calling thread does not read the corresponding input or error streams.

That's to be investigated.  But in theory, it should provide us with a safe way to wait for command completion.
Comment 21 Simon Marchi CLA 2017-02-01 14:12:37 EST
(In reply to Mihai Furis from comment #18)
> 2. The second bug is related to the RemoteGdbLaunchDelegate.launch method.
> Apparently a listener on the monitoring stream that returns the GDB server
> output has been attached to the stream AFTER the command that starts GDB
> server has been executed. For this reason when we try to start GDB for the
> first time we'll not receive the response from the server because the
> LISTENER IS NOT triggered. However when we start the GDB server for the
> second time then the listener will be triggered and we'll receive the
> response from the server.

Just to be sure, are you talking about the listener that we install at RemoveGdbLaunchDelegate.java:150, where it says

  // Listen process' output to determine gdbserver is up and running.

?

I am not sure I understand why things would be different for a second invocation of gdbserver.  The same code is executed, and in the second run it's a completely new listener.

> I suggest the fix below: 
> IStreamsProxy streams = iProcess.getStreamsProxy();
> IStreamMonitor monitorStream = streams.getOutputStreamMonitor();
> if (monitorStream.getContents().contains("Listening on port")) {
> synchronized (lock) {
> gdbServerReady[0] = true;
> lock.notifyAll();
> }};
> This code should be inserted before the listener is added to the stream, so
> we can check if the GDB is started and its output is on the pipe already.
> Practically, we do what the listener was supposed to do for the first
> execution of the GDB server.
> //below is the original code that must follow after my fix:
> monitorStream.addListener(new IStreamListener() {
> @Override
> public void streamAppended(String text, IStreamMonitor monitor) {
> if (text.contains("Listening on port")) { //$NON-NLS-1$
> synchronized (lock)
> { gdbServerReady[0] = true; 
> lock.notifyAll(); }
> monitor.removeListener(this);
> }
> }
> });
> 
> Let me know what you think about my observations and how do you propose to
> solve these bugs. Thanks!

I don't really understand the change you are proposing.  May I suggest you create gerrit changes to show code changes?  Or at least patches attached to the bug?  It's very difficult to read code in bugzilla comments, since there's no formatting.

Thanks!

Simon
Comment 22 Eclipse Genie CLA 2017-07-07 04:46:24 EDT
New Gerrit change created: https://git.eclipse.org/r/100894
Comment 23 Mihai Furis CLA 2017-07-07 05:12:22 EDT
Hello,
I created the fix and send it for review. Please let me know what you think about it. I need this fix committed on both Oxygen and Neon (all versions).

Sincerely,
Mihai Furis