How to Write C/C++ Code for Linux using Hyper-V and Visual Studio03 Aug 2017 by 0
Microsoft has definitely been bringing the love for Linux lately! I’ve used Linux more in 2017 than in the entirety of my previous career combined. Microsoft made that happen. Recently, they added support to their premier development product, Visual Studio, so that it can connect, deploy, and debug C/C++ code on a Linux system. I’m going to show you how to use that new functionality in conjunction with Hyper-V to ease development on Linux. I’ll provide a demo program listing that you can use to retrieve the information that Hyper-V provides to Linux guests via KVP exchange.
Why Use Visual Studio for Linux C/C++ Development?
I think it’s natural to wonder why anyone would use a Microsoft product to write code on Linux. This discussion can get very religious very quickly, and I would personally like to stay out of that. I’ve never understood why people get so emotional over their own programming preferences that they feel the need to assault the preferences of others. If using Visual Studio causes you some sort of pain, don’t use Visual Studio. Simple.
For the more open-minded people out there, there are several pragmatic reasons to use Visual Studio for Linux C/C++ development:
- Intellisense: Visual Studio quickly shows me errors, incomplete lines, unmatched braces/parentheses, and more. Lots of other products have something similar, but I haven’t found anything that I like as much as Intellisense.
- Autocomplete: Everyone has autocomplete. But, when it’s combined with Intellisense, you’ve got a real powerhouse. A lot of other products seem to stumble in ways that Visual Studio doesn’t. It seems to know when I want help and when to stay out of the way. It might also be my familiarity with the product, but…
- Extensions and Marketplace: Visual Studio sports a rich extension API. A veritable cottage industry sprang up to provide plug-ins and tools. Many are free-of-charge.
- [CTRL]+[K], [D] (Format Document). This particular key chord keeps Visual Studio right at the top of my list of must-have tools. Disagreements over how to place braces and whether to use tabs or spaces are ridiculous, but frequently cause battles that reach family-splitting levels of vitriol anyway. VS’s Format Document will almost instantly convert whatever style in place when you opened the file into whatever style you’ve configured VS to use. Allman with tabs, OTBS with spaces — it doesn’t matter! I haven’t found any other tool that deals with this as well as Visual Studio.
- Remote debugging. I’ve been using Visual Studio’s remote debugger on Windows for a while and have really liked it. It allows you to write code on one system but run it on another. Since VS won’t run directly on Linux, this feature makes the VS+Linux relationship possible.
- No Linux GUI needed. Practically, this is the same as the previous bullet. I want it separate so that it skimmers don’t miss it. Out of all of my Linux installations, only two have a GUI. I know that some people declare that “real programmers” only use text editors to write code. That’s part of that religious thing that I’m avoiding. I want a good IDE for my coding activities. Visual Studio allows me to have a good IDE and a GUI-less system.
- Use the compiler of your choice. Visual Studio only provides the development environment. It calls upon the target Linux system to compile and debug your code. You can specify what tools it uses.
- Free Community Edition. That’s free as in beer, not open source. But, Community Edition contains most of the best parts of Visual Studio. I would like to see CodeLens extended to the Community Edition, especially since the completely free Visual Studio Code provides it. Most of the rest of the features missing from Community Edition involve the testing suite. You can see a comparison for yourself.
Why Use Hyper-V for Visual Studio and Linux Development?
I don’t know about you, but I like writing code in a virtual machine. Visual Studio 2017 does not modify systems as extensively as its predecessors, but it still uses a very invasive installer. Also, you get a natural sandbox environment when coding in a virtual machine. I feel the same way about target systems. I certainly don’t want to code against a production system, and I don’t want to litter my workspace with a lot of hardware. So, I code in a virtual machine and I test in a virtual machine (several, actually).
I can do all of these things from my Windows 10 desktop. I can also target systems on my test servers. I can mix and match. Since I’m a Hyper-V guy, I can also use this to test code that’s written specifically for a Linux guest of a Hyper-V host. I’ll provide some demo code later in this article specifically for that sort of environment.
Preparing the Linux Environment for Visual Studio->Linux Connections
Visual Studio does all of its work on the Linux environment via SSH (secure shell). So, you’ll need to ensure that you can connect to TCP port 22. I don’t use SELinux, but I believe that it automatically allows the local SSH daemon as long as the default port hasn’t been changed. You’re on your own if you did that.
You need the following components installed:
- SSH server/daemon. In most cases, this will be pre-installed, although you might need to activate it
- The GNU C Collection (GCC) and related C and C++ compilers
- The GNU Debugger
- The GNU Debugger Server
Installation will vary by distribution.
openSUSE (definitely Leap, presumably Tumbleweed and SEL as well): sudo zypper install -y openssh gcc-c++ gdb gdbserver
Ubuntu: sudo apt-get install -y openssh-server g++ gdb gdbserver
CentOS, Fedora: dnf install -y openssh-server gcc-c++ gdb gdb-gdbserver
If you needed to install SSH server, you’ll probably need to start it as well: sudo service sshd start. You may also want to look up how to autostart a service on your distribution.
You’ll need a user account on the Linux system. Visual Studio will log on as that user to transfer source code and to execute compile and debug operations. Visual Studio does not SUDO, so the account that you choose will not run as a superuser. On some of the distributions, it might be possible to just use the root account. I did not spend any time investigating that. If you need to sudo for debugging, I will show you where to do that.
That’s all for the Linux requirements. You may need to generate a private key for your user account, but that’s technically not part of preparing the Linux environment. I’ll show you how to do that as part of the Windows preparation.
Preparing the Windows Environment for Visual Studio->Linux Connections
First, you need a copy of Visual Studio. You must at least use version 2015. 2017 is preferred. You can use any edition. I will be demonstrating with the Community Edition.
Visual Studio Install Options for Linux C/C++ Development
For Visual Studio 2015, acquire the extension: https://aka.ms/vslinuxext.
For Visual Studio 2017, the new installer includes the Linux toolset.
You may choose any other options as necessary, of course.
Connecting Visual Studio to your Linux System(s)
You will instruct Visual Studio to maintain a master list of target Linux systems. You will connect projects to systems from that list. In this section, you’ll set up the master list.
- On the main Visual Studio top menu, click Tools->Options.
- In the Options window, click Cross Platform. You should be taken right to the Connection Manager screen.
- At the right of the window, click Add. You’ll fill in the fields with the relevant information. You have two separate connection options, which I’ll show separately.
Connecting Visual Studio to Linux Using a Password
Depending on the configuration of your SSH server, you might be able to use a simple password connection. By default, Ubuntu and Fedora (and probably CentOS) will allow this; openSUSE Leap will not.
Fill out the fields with the relevant information, using an account that exists on the target Linux system:
When you click Connect, Visual Studio will validate your entries. If successful, you’ll be returned to the Options window. If not, it will highlight whatever it believes the problem to be in red. It does not display any errors. If it highlights the host name and port, then it was unable to connect. If it highlights the user name and password, then the target system rejected your credentials. If you’re certain that you’re entering the correct credentials, read the next section for a solution.
Connect Visual Studio to Linux Using Key Exchange
Potentially, using full key exchange is more secure than using a password. I’m not so sure that it’s true in this case, but we’ll go with it. If you’re using openSUSE and don’t want to reconfigure your SSH server, you’ll need to follow these steps. For the other distributions, you can use the password method above or the key method.
- Connect to/open the Linux system’s console as the user that you will be using in Visual Studio. Do not use sudo! On some distributions, you can use root via SSH; Ubuntu blocks it.
- Run ssh-keygen -t rsa. It may ask you where to create the files. Press [Enter] to accept the defaults (a hidden location in your home directory).
- When prompted, provide a passphrase. Use one that you can remember.
- You should see output similar to the following:Shell123456789101112131415161718192021Generating public/private rsa key pair.Enter file in which to save the key (/home/eric/.ssh/id_rsa):Created directory '/home/eric/.ssh'.Enter passphrase (empty for no passphrase):Enter same passphrase again:Your identification has been saved in /home/eric/.ssh/id_rsa.Your public key has been saved in /home/eric/.ssh/id_rsa.pub.The key fingerprint is:SHA256:aFfLrjJ0yZ7NwOixXDRW+hC7EjEV/JwL4Hjx9sDF86c eric@svlmon01The key's randomart image is:+---[RSA 2048]----+| ooo || = o = || o B O.+ || . +.&o+.. . || .oOSXo. o || .=.O.+ E || + B =. || * o.o || o. |+----[SHA256]-----+
- Next, enter ssh-copy-id firstname.lastname@example.org. For instance, I used ssh-copy-id email@example.com. Remember, you want to use the name of the Linux system, not the remote Windows system running Visual Studio. The system may complain that it can’t verify the authenticity of the system. That’s OK in this case. Type out¬ yes¬†and press [Enter].
- You will be asked to provide a password. Use the password for your user account, not the passphrase that you created for the key.
- Use any tool that you like to copy the file¬ ~/.ssh/id_rsa¬ to your local system. The .ssh folder is hidden. If you’re using WinSCP, go to the Options menu and select Preferences. On the Panels tab, check Show hidden files (CTRL+ALT+H).
- The id_rsa file is a private key. The target Linux system now implicitly trusts that anyone wielding the specified user name (in step 5) and encrypting with this particular private key is perfectly safe to be allowed on to the system. You must take care with this key! In my case, I just dropped into my account’s My Documents folder. That folder already has some NTFS permission locking and I can be reasonably certain that I can trust everyone that has sufficient credentials to override. If not, the passphrase that I chose will serve as my last line of defense.
Now that I have my private key ready, I pick up where step 3 left off in the initial Connecting section above.
- Fill in the target system and port
- Fill in the user name
- Change the Authentication type drop-down to Private Key
- In the Private key file field, enter or browse to the id_rsa file
- In the Passphrase field, enter the passphrase that you generated for this key
When you click Connect, Visual Studio will validate your entries. If successful, you’ll be returned to the Options window. If not, it will highlight whatever it believes the problem to be in red. It does not display any errors. If it highlights the host name and port, then it was unable to connect. If it highlights the user name and key section, then the target system rejected your credentials. If that happens, verify that you entered the ssh-copy-id command correctly.
Note: You can also use this private key with other tools, such as WinSCP.
Once you’ve added hosts, Visual Studio will remember them. Conveniently, it will also identify the distribution and bitness:
Configuring a Visual Studio C/C++ Project to Connect to a Linux System
At this point, you’ve prepared your overall environment. From this point onward, you’re going to be configuring for ongoing operational tasks. The general outlay of a Visual Studio to Linux connection:
- Your project files and code remain on the Visual Studio system. That means the .sln, .vcxproj, etc. files.
- During a build operation, Visual Studio transfers the source files to the target Linux system. It calls on the local compiler to build them.
- During a debug operation, Visual Studio calls on the local gdb installation. It brings the output to your local system.
You’ll find all of the transferred files under ~/projects/. Expanded, that’s /home/userid/projects. The local compiler will create bin and obj folders in that location to hold the respective files.
The following sub-sections walk through creating a project.
Creating a Linux Project in Visual Studio
You must have followed all of the preceding steps or the necessary project templates will not be available in Visual Studio.
- In Visual Studio, use the normal method to create a new solution or a project for an existing solution (File->New->Project).
- In the New Project dialog, expand Installed -> Templates -> Visual C++ -> Cross Platform and click Linux.
- In the center, choose Console Application. If you choose Empty Project, you just won’t get the starter files. If you have your own process for Linux projects, you can choose Makefile Project. I will not be demonstrating that. Fill out the Name, Location, and Solution Name (if applicable) fields as necessary. If you want to connect to a source control system, such as your Github account, you can facilitate that with the Add to Source Control check box.
Your new project will include an introductory home page and a default main.cpp code file. The Getting Started page contains useful information:
Default main.cpp code:
Selecting a Target System and Changing Linux Build Options in Visual Studio
If you’ve followed through directly and gotten this far, you can begin debugging immediately. However, you might dislike the default options, especially if you added multiple target systems.
Access the root location for everything that I’m going to show you by right-clicking on the project and clicking Properties:
I won’t show/discuss all available items because I trust that you can read. I’m going to touch on all of the major configuration points.
General Configuration Options
Start on the General tab. Use this section to change:
- Directories on the remote system, such as the root project folder.
- Project’s name as represented on the remote system.
- Selections when using the Clean option
- The target system to use from the list of configured connections
- The type of build (application, standard library, dynamic library, or makefile)
- Whether to use the Standard Library statically or as a shared resource
Directories (especially for Intellisense)
On the VC++ Directories tab, you can configure the include directories that Visual Studio knows about. This tab does not influence anything that happens on the target Linux system(s). The primary value that you will get out of configuring this section is autocomplete and Intellisense for your Linux code. For example, I have set up WinSCP to synchronize the include files from one of my Linux systems to a named local folder:
It won’t synchronize symbolic links, which means that Intellisense won’t automatically work for some items. Fortunately, you can work around that by adding the relevant targets as separate entries. I’ll show you that in a tip after the following steps.
To have Visual Studio access these include files:
- Start on the aforementioned VC++ Directories tab. Set the Configuration to All Configurations. Click Include Directories to highlight it. That causes the drop-down button at the right of the field to appear. Click that, then click Edit.
- In the Include Directories dialog, click the New Line button. It will create a line. At the end of that line will be an ellipsis (…) button that will allow you to browse for the folder.
- One completed, your dialog should look something like this:
- OK out of the dialog.
Remember, this does not affect anything on the target Linux system.
TIP: Linux uses symbolic links to connect some of the items. Those won’t come across in a synchronization. Add a second include line (or more) for those directories. For instance, in order to get Intellisense for <sys/types.h> and <sys/stat.h> on Ubuntu, I added x86_64-linux-gnu:
Visual Studio’s natural behavior is to compile C code with the C++ compiler. It assumes that you’ll do the same on your Linux system. If you want to override the compiler(s) that it uses, you’ll find that setting on the General tab underneath the C/C++ tree node.
TIP: In Release mode, VC++ sets the Debug Information Format to Minimal Debug Information (-g1). I’m not sure if there’s a reason for that, but I personally don’t look for any debug information in release executables. So, that default setting bloats my executable size with no benefit that I’m aware of. Knock it down to None (-g0) on the C/C++/All Options tab (make sure you select the Release configuration first):
Passing Arguments and Running Additional Commands
You can easily find the Pre- and Post- sections for the linker and build events in their respective sections. However, those only apply during a build cycle. In most cases, I suspect that you’ll be interested in changing things during a debug session. Visual Studio provides many options, but I’m guessing that the two of most interest will be running a command prior to the debug phase and passing arguments into the program. You’ll find both options on the Debugging tab:
If the program needs to run with super user privileges, then you could enter¬ sudo -s into the Pre-Launch Command field. However, by default, you’d also need to supply the password. That password would then be saved into the project’s configuration files in clear text. Even that by itself might not be so bad if the project files live in a secure location. However, if you add the project to your Github account… So, if you need to sudo, I would recommend simply bypassing the need for this account to enter a password at all. It’s ultimately safer to know that you have configured this account that way than to try to keep track of all the places where the password might have traveled. I’ve found two places that guide how to do that: StackExchange and Tecmint. I typically prefer Stack sites but the Tecmint directions are more thorough.
Starting a Debug Process for Linux C/C++ Code from Visual Studio
You’ve completed all configuration work! Now you just need to write code and start debugging.
Let’s start with the sample code since we know it’s a good working program. You can press the green arrow button titled Remote GDB Debugger or press the F5 key when the code window has focus.
You will be prompted to build the project:
If you’ve left the Windows Firewall active, you’ll need to allow Visual Studio to communicate out:
In the Output window, you should see something similar to the following:
If errors occur, you should get a fairly comprehensible error message.
Viewing the Output of a Remote Linux Debug Cycle in Visual Studio
After the build phase, the debug cycle will start. On the Debug output, you may get some errors that aren’t as comprehensible as compile errors:
As far as I can tell these errors (“Cannot find or open the symbol file. Stopped due to shared library event”) occur because the target system uses an older compiler. Changing the default compiler on a Linux distribution can be done, but it is a non-trivial task that may have unforeseen consequences. You have three choices:
- As long as the older compiler can successfully build your application, live with the errors. If your final app will target that distribution, then you can bet that users of that distribution will also be using that older compiler.
- Add a newer version of the compiler and use what you learned above to instruct Visual Studio to call it instead of the default. You’ll need to do some Internet searching to find out what the corrected command line needs to be.
- Change the default compiler on the target. That would be my last choice, as it will affect all future software built on that system in a manner that is inconsistent with the distribution. If you want to do that, you’ll need to look up how.
The consequence of doing nothing is the normal effect of debugging into the code for which you have no symbols. I have not yet taken any serious steps to fix this problem on my own systems. I’m not even certain that I’m correct about the version mismatch. However, these aren’t showstoppers. Assuming that the code compiled, the debug session will start. Assuming that it successfully executed your program, it will have run through to completion and exit. If you remember the first time that you coded a Visual C++ Windows Console Application and didn’t have some way to tell the program to pause so that you could view the results, then you’ll already know what happened: you didn’t get to see any output aside from the return code.
Since you’re working in a remote session, you need to do more than just put some simple input process at the end of your code. In the Debug menu, click Linux Console.
This will open a new always-on-top window for you to view the results of the debug. Debug the default application again, and you should see this:
Of course, the built output will remain until you clean it, so you can always execute the app in a separate terminal window:
LinuxApp is the name that I used for my project. Substitute in the name that you used for your project.
Sample C++ Application: Retrieving KVP data from Hyper-V on a Linux Guest
If we’re going to have an article on Hyper-V, Linux, and C++, it seems only fair that it should include a sample program tying all three together, doesn’t it?
Hyper-V KVP on Linux
A while back, I went through the KVP exchange mechanism for Hyper-V and Windows guests. From the host side, nothing changes for Linux guests. On the Linux side, just about everything changes.
If you followed my guides, the Hyper-V KVP service will already be running on your Linux guest. Check for it: sudo service --status-all | grep kvp. If it’s not there, you can look at the relevant guide on this site for your distribution (I’ve done Ubuntu, Ubuntu, openSUSE Leap, and Kali). You can also check TechNet for your distribution’s instructions. Also, make sure that the service is enabled on the virtual machine’s property page in Hyper-V Manager or Failover Cluster Manager.
Linux/Hyper-V KVP Input/Output Locations
On Windows, the KVP service operates via the local registry. On Linux, the KVP daemon operates via files:
- /var/lib/hyperv/.kvp_pool_0: an inbound file populated by the daemon. This is data that an administrative user can send from the host. Same purpose as the External key on a Windows guest. You only read this file from the Linux side. It does not require special permissions. Ordinarily, it will be empty.
- /var/lib/hyperv/.kvp_pool_1: an outbound file that you can use to send data to the host. Same purpose as the Guest key on a Windows guest.
- /var/lib/hyperv/.kvp_pool_2: an outbound file populated by the daemon using data that it collects from the guest. Same purpose as the Auto key on a Windows guest. This information is read by the host. You cannot do anything useful with it from the client side.
- /var/lib/hyperv/.kvp_pool_3: an inbound file populated by the host. This data contains information about the host. Same purpose as the Guest\Parameter key on a Windows guest. You can only read this file. It does not require special permissions. It should always contain data.
Linux/Hyper-V KVP File Format
All of the files follow a simple, straightforward format. Individual KVP records are simply laid end-to-end. These KVP records are a fixed length of 2,560 bytes. They use a simple format:
- 512 bytes that contain the data’s key (name). Process as char. hyperv.h defines this value as HV_KVP_EXCHANGE_MAX_KEY_SIZE.
- 2,048 bytes that contain the data’s value. By default, you’ll also process this as char, but data is data. hyperv.h defines this as HV_KVP_EXCHANGE_MAX_VALUE_SIZE.
Be aware that this differs from the Windows implementation, which doesn’t appear to use a fixed limit on value length.
Armed with the above knowledge, we’re going to read the inbound information that contains the auto-created host information.
I replaced the default main.cpp with the following code:
using namespace std;
int main(int argc, char **argv)
string KVPFileName = "/var/lib/hyperv/.kvp_pool_3";
cout << "Opening file: " << KVPFileName << endl;
ifstream KVPFile(KVPFileName, ios::binary);
cout << "Reading KVP records." << endl;
while ( // slightly faster, somewhat shorter, much dodgier: while (KVPFile.read((char*)&KvpEntry, sizeof(KvpEntry)))
KVPFile.read(KvpEntry.key, sizeof(KvpEntry.key)) &&
cout << noskipws << " * " << KvpEntry.key << ": " << KvpEntry.value << endl;
cout << "Finished reading KVP records." << endl;
cerr << "Error while opening" << KVPFileName << ": " << strerror(errno) << endl;
Debug this with the Linux Console open, and you should see something like the following:
For More Information
I poached a little bit of the Visual Studio for C/C++ on Linux information from¬†https://blogs.msdn.microsoft.com/vcblog/2016/03/30/visual-c-for-linux-development/.
I got the base information about Hyper-V/Linux KVP from this article: https://technet.microsoft.com/en-us/library/dn798287(v=ws.11).aspx. If you want to write KVP readers/writers using C rather than C++, you’ll find examples there. While I certainly don’t mind using C, I feel that the lock code detracts from the simplicity of reading and writing KVP data.