Skip to main content

Analyzing Multi-Language Software Projects with Axivion

Recap: C# P/Invoke and Rust FFI are Unsafe

In the previous post of this series (Pitfalls in Multi-Language Software Projects ), we introduced possible pitfalls and sources for bugs in software projects that use technologies like P/Invoke or Rust’s FFI to interact with C API functions.

For example, mismatches in function signature or type declarations may lead to problems such as:

  • Crashes: the application or appliance stops working and is no longer usable.
  • Loss of data: Values are truncated or misinterpreted, leading to false results.

This time, we will look at an example bug in a software module, that uses P/Invoke to execute a C function from C#. The same general approach and potential issues apply to Rust’s FFI and are also applicable to other target languages than C.

Setting the Stage: The Example Code

We have a system consisting of a control unit written in C# and an embedded device with a communication interface written in C.

The C interface used is as follows:

// exports.h
#pragma pack(push, 1)

typedef struct
{
   short id;

   int flags;
   double value;
} HWCommand;
#pragma pack(pop)

extern "C" __declspec(dllexport) void SendCommand(HWCommand cmd);

// exports.cpp
extern "C" __declspec(dllexport) void SendCommand(HWCommand cmd)
{
   // just print the values
   printf("Received Command - id: %d, flags: %d, value: %f", cmd.id, cmd.flags, cmd.value);
}

It consists of a struct definition HWCommand, which is composed of an id, some flags and some value, and a function SendCommand, which is used to send HWCommand instances to the device. In our case, the command is just printed on the console for presentation. The SendCommand function is marked __declspec(dllexport), which enables it to be used from other libraries.

The C# code that consumes the C interface is as follows:

// Program.cs
SendCommand(new HWCommand() { id = 1, flags = 1, value = 3.14 });

[DllImport("UnmanagedLibrary.dll", EntryPoint = "SendCommand")]
static extern void SendCommand(HWCommand cmd);

public struct HWCommand
{
   public short id;
   public int flags;
   public double value;
}

Here, we see the HWCommand struct definition again, which matches its counterpart defined in C. And also the definition of SendCommand, which imports the exported function from our unmanaged library using the DllImportAttribute.

Note that the C# compiler can and will not verify whether the declarations match or that there even exists a function with the given name.

When running the above code, we would expect the following output to be produced: 

Received Command - id: 1, flags: 1, value: 3.140000

Now, let us run the code and see if our expectation matches the actual program execution:

Received Command - id: 1, flags: 65536, value: 0.000000

Unfortunately, these are not the expected values. The data has not been transferred to the “device” side correctly and thus functionally cannot be ensured! Therefore, it is important to find these issues quickly. However, as discussed in the previous blog, the individual compilers used have no chance of detecting the underlying issue as they only see the two languages separately. How is it then possible to find these bugs early in the development process without having to set up complex hardware testing environments or writing and running any unit or integration tests?

Axivion Static Code Analysis to The Rescue!

Static code analysis, such as the one performed by Axivion is another useful tool in finding issues such as the example shown above.

After setting up an Axivion project with a build configuration for both your C# and C/C++ code and running/enabling the C#-CheckPInvoke rule in your analysis, you can view the analysis results in the Axivion Dashboard:

The analysis found a signature mismatch, and we can now explore it further by looking at the source code directly from within the dashboard:

As can be seen in the panel on the right, the error message provides detailed information about the problem. It tells us that the size and alignment of the structure defined in C/C++ does not match the definition in our C# code, which explains the wrong values, we have seen when executing the code.

Conclusions

In conclusion, Axivion can help finding, understanding and fixing errors on the border between different programming languages. These issues are notoriously hard to spot otherwise, since they cannot properly be detected by tools considering the participating languages individually and separately. With our latest release 7.11, Axivion supports analyzing the overall software system and its architecture across multiple languages simultaneously and consistently. This involves elaborate broad checks such as the Axivion Architecture Verification as well as technically deep analyses of the interfacing between languages. We are working on transporting even more of the information Axivion has on individual language-dependent part of a software project across language borders to provide exhaustive architecture- and language aware quality and safety checks for involved multi-language projects.

Discover More

For additional insights into our architecture verification and static code analysis toolsplease visit our website. You can also take an interactive tour to learn how Axivion Static Code Analysis works.

If you have any questions or wish to schedule a demo, please reach out to us.

To stay up to date with our latest product news and events: Sign Up For Our Newsletter.

 

Comments