Using Visual Studio Code for C Programming on an Old Linux Remote Server

Visual Studio Code with C/C++ extension on Windows 7

Visual Studio Code (VSCode) has become the most popular development environment in the world, and for some good reasons:

  • if it doesn’t do the job out of the box, it’s likely an extension will,
  • it’s highly customisable,
  • it’s fast (yes, even if based on Electron, which powers the nice but sluggish Atom),
  • Convenient JSON format settings,
  • it’s good looking, important when I spend half of my life in front of a code editor.

But for me the nicest feature, by far, is the remote development. It’s really as advertised and feels like developing on the remote machine itself. No need to transfer the sources back and forth to your local PC anymore, it works like magic. This is very convenient when developing on Linux from a Windows PC, as is the case in many companies.

To achieve this VSCode installs a (node) deamon on the remote machine which interacts with its filesystem and talks to your local VSCode. It’s pretty much VSCode installing itself on the remote server. This applies for the extensions as well. It is a tad overkill, but that is how you can get all the nice features of VSCode even on non local machines.

All of this means these remote binaries must be compatible with the remote host.

But what happens when the remote host runs an old Linux distribution, say, RedHat 6.10?

Nothing. Because it doesn’t work.

But nothing that can’t be overcome.

Unfriendly Development Environment

At my current job, I am given a PC running Windows 7 to do C programming on Linux platform.

That would be fine a situation but:

  • I don’t have local Windows administration rights to install VSCode,
  • I don’t have root rights on the remote Linux,
  • Remote server runs Linux RedHat 6.10 which was released 10 years ago.

First issue is easy to work around. VSCode also comes in portable version which can be installed without admin rights on Windows.

But the second and third ones need tricks to overcome. Let’s explain.


To have a functional Visual Studio Code for C/C++ development, we’ll look at the following:

  1. how to do SSH key authentication, as required by the remote development functionality in VSCode,
  2. how to install newer versions of libraries on a very old Linux distribution, without admin rights and without breaking anything,
  3. how to force binaries linking to these newer libs rather than the ones from the system (in $LD_LIBRARY_PATH)


Feel free to skip that section if you are familiar with SSH key authentication.

SSH Client Installation on Windows

VSCode doesn’t come with an ssh client. It needs to be installed separately.

Everything is explained on the VSCode Remote Development page. In short:

  • If you are under Windows 10, you have a Windows OpenSSH Client dedicated package,
  • If you use an earlier version of Windows, then you are in for installing either Cygwin, or Git and its Unix utilities (also based on Cygwin). ssh is one of these utilities.

It doesn’t matter how you install it. But just make sure that you can call ssh from the command line before going further in this installation guide.

When ssh is properly installed on Windows 7

SSH Keys Generation

VSCode allows remote development through SSH and only requires that the authentication is done with authentication keys.

ssh-keygen can be used to generate them on the target Linux (PuTTYgen could also be used if the PuTTY suite is installed on the Windows host). I show the process for RSA keys generated with ssh-keygen here.

> ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/export/home/username/.ssh/id_rsa): id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa.
Your public key has been saved in
The key fingerprint is:
11:f9:c3:23:16:fe:08:91:a5:47:d6:07:92:57:fe:72 username@hostname
The key's randomart image is:
+--[ RSA 2048]----+
| o*+.o. |
| o++oo.. |
| .oo= .. |
| ..+.= . |
| oS+ o. E |
| . . o |
| |
| |
| |

Two files are output:

  • id_rsa the private key,
  • the public key.

The id_rsa private key’s content should look like (I show dummy keys):

-----END RSA PRIVATE KEY-----v’s content looks like:

ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6yVemVZtnm16/Fx7tVbOMl73xGuECnNB6zuLjEawy42yK+0F+UphiWbZk9kwmaQH2si3NEcURpr/DuSooV2Dyy/EhKyCZYtjY+sTZ2d+khR0JICFqv1G49PB7UVuQvdp+FzapUsZtpehLvWPTu2gpqE5c3qJQEHn0XsCHMqi2qB1cDHTV44BUCwszAbsAantkio6th3bEp3XXxxe3Ly7CoiN9qY8zODZs57bw/IQ0z5foULOTLr1F7GAPcWcFHpUC4QjJrLs0yTX/C/ANKi3TNWjVQaJGqJJ9XzwsLtyBzqq0agUT0kpKdWnrrf29hmqThmTxZIGhQxo+k1fYUOjhQ== username@hostname

Append the public key to your $HOME/.ssh/authorized_keys file. If the folder and file don’t exist, create them and make sure they have 700 rights for .ssh folder

drwx------  2 username group 4096 May 22 09:23 .ssh

and 600 rights for authorized_keys

-rw------- 1 username group 391 May 22 09:21 authorized_keys

Retrieve the private key on your local PC and put it somewhere safe. Delete it from the remote (our leave it in .ssh/ with 600 rights).

Starting an SSH Session in Visual Studio Code

Start VSCode, and switch to Explorer view (the terminal icon on the left bar). Add an SSH target by clicking the “+” on the SSH TARGETS bar. Then input the following command:

ssh username@hostname

VSCode prompts you to choose a config file and ask to open it. Accept. Since I installed ssh with Git, my ssh config file is:


You’ll see that VSCode has already added the new SSH host:

Host xxxxxxxx
HostName xxxxxxxx
User username

Complete the setup by adding the path to your private key, as shown in bold below:

Host xxxxxxxx
HostName xxxxxxxx
User username
IdentityFile C:\Path\to\your\id_rsa

You can also rename the Host (and not the HostName!) to your liking. I chose dev_server. Restart VSCode if the new name doesn’t update in SSH TARGETS.

Click the add icon on the right. Choose Linux as the target platform and answer “yes” to the fingerprint question.

VSCode tries to start a remote SSH session but miserably fails:

Visual Studio Code cannot start the remote process

What’s happening? VSCode is not very clear about that, so let’s check on the remote server itself. From the remote user home directory, run:

> ls -al | grep vscode
drwxr-xr-x 3 username group 4096 May 25 16:08 .vscode-server

VSCode has put something there. That’s the remote daemon we mentioned, which acts as a delegate to manipulate the remote Linux file system and show them as being “local” on VSCode. We are looking for the executables in ELF format (The output is overwhelming so I have stripped the middle part):

> find .vscode-server/ -exec file {} \; | grep -i elf
.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/node: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped
.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/node_modules/vsda/build/Release/vsda.node: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped

node seems like a good candidate. It’s very likely the daemon our local VSCode talks to. Running it gives:

> cd .vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/
> ./node
./node: /usr/lib64/ version `GLIBCXX_3.4.14' not found (required by ./node)
./node: /usr/lib64/ version `GLIBCXX_3.4.18' not found (required by ./node)
./node: /usr/lib64/ version `CXXABI_1.3.5' not found (required by ./node)
./node: /usr/lib64/ version `GLIBCXX_3.4.15' not found (required by ./node)
./node: /lib64/ version `GLIBC_2.17' not found (required by ./node)
./node: /lib64/ version `GLIBC_2.16' not found (required by ./node)
./node: /lib64/ version `GLIBC_2.14' not found (required by ./node)

No wonder! node doesn’t find the proper version of the C/C++ libs against which it was linked. We can check the C library to confirm:

> strings /lib64/ | grep GLIBC

The C library’s version is 2.12 and node requires at least 2.14. We’re just shy of 0.2.0.

We need to upgrade the C and C++ standard libs. But without admin rights, we’ll need to cheat a little.

Installing Newer Version of Standard Librairies on Linux, Without an Admin

Getting newer version of libraries running on a Linux without administration capability is not difficult but tricky. It is possible to install them from proper RPM packages.

RPM packages can be found on We want these ones:

  • glibc (the GNU C standard library)
  • libstdc++ (C++ standard library)

For our versions and architecture requirements, these are the packages to use:

We can do the installation in an isolated pseudo “root” folder of the remote session, say, $HOME/local.

~/local > ll
total 4036
-rw-r — r — 1 username group 3815032 May 25 18:53 glibc-2.17–307.el7.1.x86_64.rpm
-rw-r — r — 1 username group 312504 May 25 18:52 libstdc++-4.8.5–39.el7.x86_64.rpm
~/local >

Obviously, we cannot use rpm nor yum to install these. All we can do is a straightforward extraction. Which is fine, we don’t need the dependencies checks and we — more or less — know what we do.

The actual files of an RPM files lie in a cpio archive inside. The cpio needs to be extracted first before it can be unarchived:

rpm2cpio glibc-2.17–307.el7.1.x86_64.rpm | cpio -idmv
libstdc++-4.8.5–39.el7.x86_64.rpm | cpio -idmv

This creates a folder structure similar to what you’d get under the root folder of a Linux install, but nested under the current directory, here $HOME/local. Once the packages are installed, we can look for the libraries we are trying to upgrade:

~/local > find . -name "lib*.so.6"

Don’t forget to check the libs versions:

~/local > strings ./lib64/ | grep GLIBC
~/local > strings ./usr/lib64/ | grep GLIBCXX

These are the versions we want.

“Relinking” Linux ELF Binaries

We need to somehow tell the VSCode binaries to hit these new libs we’ve just installed, instead of the standard ones from the distribution.

A naive thought would be to update $LD_LIBRARY_PATH environment variable to hit our “local” folder before the standard ones but this would break all the other links and render the remote system unusable (don’t try!). Instead we “alter” the links in the binaries themselves. That means:

  • Change the dynamic loader (“ELF interpreter”) of executables
  • Change the RPATH of executables and libraries (where it looks for the dynamically linked libs)

A utility does just that: patchelf. We’ll also get it from an rpm package: patchelf-0.9–10.el7.x86_64.rpm.

Contrary to the others, we don’t extract it in the local folder, but in a tmp folder, only to keep the patchelf binary.

~/tmp > rpm2cpio patchelf-0.9–10.el6.x86_64.rpm | cpio -idmv
314 blocks
~/tmp > mkdir -p ~/bin
~/tmp > cp usr/bin/patchelf ~/bin
~/tmp > cd ..
~ > rm -fr tmp/

Running it on node:

~/bin/patchelf --set-interpreter $HOME/local/lib64/ld-linux-x86– --set-rpath $HOME/local/usr/lib64/:$HOME/local/lib64 node

(the command was issued from .vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/ folder). There’s no output, so we assume there’s no error. Let’s check:

> ldd node => (0x00007ffe1c7da000) => /home/username/local/lib64/ (0x00007fbf1d870000) => /home/username/local/lib64/ (0x00007fbf1d667000) => /home/username/local/usr/lib64/ (0x00007fbf1d360000) => /home/username/local/lib64/ (0x00007fbf1d05e000) => /lib64/ (0x0000003e63a00000) => /home/username/local/lib64/ (0x00007fbf1ce37000) => /home/username/local/lib64/ (0x00007fbf1ca69000)
/home/username/local/lib64/ => /lib64/ld-linux-x86– (0x00005639414e6000)

It seems to work. We see that we have missed libgcc but the version on our system is good enough.

If we open a session to dev_server again…

There still is a prominent warning, but it is just a warning and is because VSCode detects old versions of the standard C and C++ libs in the library paths. The thing to look at is the bright orange banner at the bottom left showing we are connected!

We can further confirm by checking the processes on the remote host:

> ps -fu username | grep vscode
10929 13188 13162 0 20:43 ? 00:00:00 sh /home/username/.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/ — host= — enable-remote-auto-shutdown — port=0
10929 13196 13188 0 20:43 ? 00:00:01 /home/username/.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/node /home/username/.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/out/vs/server/main.js — host= — enable-remote-auto-shutdown — port=0
10929 13282 13196 0 20:43 ? 00:00:01 /home/username/.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/node /home/username/.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/out/bootstrap-fork — type=extensionHost — uriTransformerPath=/home/username/.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/out/vs/server/uriTransformer.js
10929 13303 13282 0 20:43 ? 00:00:00 /home/username/.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/node /home/username/.vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/extensions/json-language-features/server/dist/jsonServerMain — node-ipc — clientProcessId=13282
10929 13355 2298 0 20:46 pts/5 00:00:00 grep vscode

The Node JS server is running! It shows many processes because node preforks them upon launch (so this is as many processes you’ll have).

Back to VSCode, try to open a folder and you’ll be navigating on the remote filesystem. A workspace can be chosen from here.

All the other ELF binaries in .vscode-server/bin/ff915844119ce9485abfe8aa9076ec76b5300ddd/ shall be patched the same way. Use the command to find ELF files shown above, and patch them with patchelf. ELF libraries don’t have an interpreter, so there’s no need to specify a replacement and the command line to patch them is:

~/bin/patchelf — set-rpath $HOME/local/usr/lib64/:$HOME/local/lib64 lib_to_patch

Check the results with ldd after each patchelf.

Dealing With Plugins

The C/C++ functionalities of VSCode are found in an extension:

  • C/C++ for Visual Studio Code

Extensions should be carefully chosen, but that one has millions of downloads, good reviews and is backed by Microsoft, so no worries here. Don’t be fooled by the low version number, it is fully functional.

Notice the button says “Install in SSH: dev_server”. That makes sense given all we have said so far. But that also means we are going to run into similar linking problems…and indeed, after the installation, we get:


So we go to the download page and get the latest cpptools-linux.vsix package:

We drop it in the remote $HOME folder, and launch the “Install from VSIX” command from the Command Palette (F1):

The remote $HOME folder shows up and we can see the just added .vsix package:

Pick it. The install completes fairly quickly and then prompts to reload VSCode. This only gets us another error message:

Hopefully the last error message…

Again, VSCode is not clear about the problem but it is the same: wrong versions of librairies linked to the extension binaries. Given that extensions install themselve in $HOME/.vscode-server/extensions on the remote Linux, the following identified ELF binaries must be patched:

~/.vscode-server/extensions/ms-vscode.cpptools-0.28.1 > find . -exec file {} \; | grep -i elf
./bin/cpptools-srv: ELF 64-bit LSB executable, x86–64, version 1 (GNU/Linux), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped
./bin/cpptools: ELF 64-bit LSB executable, x86–64, version 1 (GNU/Linux), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped
./debugAdapters/mono.linux-x86_64: ELF 64-bit LSB executable, x86–64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped
./LLVM/bin/clang-format: ELF 64-bit LSB executable, x86–64, version 1 (GNU/Linux), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped

Here’s a cleaned up version of the output:


These are all executables binaries, so the interpreter must be changed when elfpatching them.

~/bin/patchelf --set-interpreter $HOME/local/lib64/ --set-rpath $HOME/local/usr/lib64/:$HOME/local/lib64 file_to_patch

Note that patchelf only process one file at a time so the command must be issued for each of them.

After a reload of the VSCode window (“Reload Window” from the command palette) you’ll have a fully working Visual Studio Code with C/C++ extension enabled!

It’s finally working! Visual Studio Code and the C/C++ extension in action.

We can put the SSH integration to test. Fire up a terminal (“Open New External Terminal” in the Command Palette) and type

> touch hello.txt

The hello.txt file immediately appears in the explorer, at the left of the screen.

Again, it really feels like developing on a local machine.

Working with the C Debugger

I find it much convenient to use a graphical debugger over the command line. But can we make it work on our patched up environment?

Use this little C program to check:

Compile it and run it:

> gcc test.c -o test
> ./test
Message: It works!

Let’s try to run it with the debugger. Switch to the Run view and create a launch.json file.


  • C++ (GDB/LLDB)
  • gcc — Build and debug active file

VSCode generates a launch.json file automatically. Change it to make the debugger stop at the beginning (stopAtEntry set to “true”):

"version": "0.2.0",
"configurations": [
"name": "gcc - Build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
"preLaunchTask": "gcc build active file",
"miDebuggerPath": "/usr/bin/gdb"

It also generates a tasks.json file, which defines a pre task to run before firing up the debugger. It is typically a compilation.

"tasks": [
"type": "shell",
"label": "gcc build active file",
"command": "/usr/bin/gcc",
"args": [
"options": {
"cwd": "/usr/bin"
"version": "2.0.0"

Note that it is clever enough to compile with the -g flag.

Make sure the test.c file is in focus, and launch the just defined “gcc — Build and debug active file” task.

Graphical gdb

It works! You can see it is possible to inspect variables, to step into functions, to look at the call stack, and to add breakpoints… we have a full fledged graphical debugger.

So yes, we can make the graphical debugger work. But while it seemed like a breeze to have it work, my investigations took a bit longer as I didn’t immediately realise that the ./debugAdapters/mono.linux-x86_64 file of the C/C++ extension needed to be elfpatched as well.


Some drawback of that workaround:

  1. The elfpatching process needs to be repeated each time VSCode or an extension is installed or upgraded,
  2. a remote session fires up a node server (four processes) for each user on the remote server but also a process for each extension component, terminal session… this can potentially use a lot of resources.

But in my view these little shortcomings are easily compensated by how nicer of a development environment this has gotten me.


Software Engineer, hobbyist photographer.

Get the Medium app