Friday, December 29, 2023

Windows batch file to run an exe and check its result

Windows batch file to run an exe and check its result. If you get the strange ") was unexpected at this time" error when you run it, try adding a comment line before that ")", it usually fixes it:

Thursday, December 28, 2023

The unbearable lightness of C

We have a Simulink project from which I generate C code to use in a Visual Studio C++ project. The Simulink project works fine, I can build the C code without any errors, but when I ran the C executable, I got an access violation error due to trying to write to address 0x0. The C project was working fine for previous versions. 

I initially identified the revision where this error first appeared. I reviewed the changed code and couldn't find anything wrong.

After a week of debugging I found out that it was due to an off-by-one error; An array was defined with size 47 using Simulink function ssSetNumDiscState(S, 47) in mdlInitializeSizes(...), but later in function mdlInitializeConditions(...), a for loop with upper bound of 48 was executed which resulted in writing to the memory adjacent to the allocated section for that array. 
static void mdlInitializeConditions(SimStruct *S) {
    real_T *states = ssGetRealDiscStates(S);
    for (int i=0; i < 48; i++) {
    	*(states + i) = 0;
    }
}
It has nothing to do with the latest change in the sense that it was not related to the logic of the change. Instead, that change altered the memory mapping of the build, putting another array (TUBufferPtr) after the first and the overflow caused the value at TUBufferPtr[0] to become 0x0 (NULL). When the program tried to write to the address represented by TUBufferPtr[0], it naturally caused an access violation because writing to address 0x0 is not allowed.
When I looked at the repository history, I saw that this error was introduced 3 years ago and for all these years, it did not become visible! The access violation only occurred when other unrelated code updates caused the compiler to arrange memory slightly differently.

This is also one of the reasons why sometimes C programs behave differently between debug and release builds or on different versions (service packs) of the same operating system. That discrepancy is an indication of an error hiding somewhere in your program. Another way such an error can become visible is when you have it in your C++ DLL that you call from your Java program. One day you update your JDK and your program crashes because DLL and JVM share the same memory space. Naturally, your first inclination is to blame the JDK update but in reality there is a buffer overflow in your DLL code.

You can never say for sure that modifying a code in module A won't have an effect on an unrelated module B because as long as these modules are in the same process, i.e. use the same memory, the compiler might put them side by side and an error in A can overflow to B. Functionally distant modules can become "close relatives" in memory address space.

In a way, I was fortunate that the overwrite contained zero values. If it had used some value that was a valid address for the application to write to, it would cause seemingly random behavior and would have been a lot more fun (!)

Note that a typical static code analyzer would not able to catch this problem because we are defining the data structure with Simulink specific ssSetNumDiscState and getting it with ssGetRealDiscStates functions.

I solved the problem by adding #define NUMBER_OF_DISCRETE_STATES (47) and using that define in both ssSetNumDiscState() and mdlInitializeConditions(). This case study also serves as a cautionary tale illustrating why you should use defined constants rather than magic numbers.

In my nightly automated tests I was only checking if exe was generated which only proved that it compiled. I added running the exe and checking if it finished successfully because an access violation can only occur at runtime.

Wednesday, December 13, 2023

Downloading DTED from the internet

You can downlad DTED 2 files (1 arc-second ~ 30m) from USGS EarthExplorer by first creating an account, clicking on the link in verification email and then following these steps:


Monday, December 11, 2023

Avoiding catastrophic cancellation

Catastrophic cancellation occurs when subtracting two nearly equal numbers, leading to a significant loss of precision due to limited precision of floating-point numbers in a computer. Below are some examples and their better versions (reference):
log(a)−log(b) = log(a/b)

Computing cos(x)−1 for small values of x --> cos(x) ≈ 1−(x^2)/2 

Monday, December 4, 2023

Sharing files between Windows and Ubuntu virtual machine

I have a Windows 10 PC with Ubuntu 22.04 installed as a VirtualBox virtual machine. There are other ways to share files between Windows and Ubuntu, but the following is the most general way I know:

  1. On Windows, share a folder (e.g. "temp") with your own windows user name.
  2. Find the IP address of VirtualBox ethernet adapter:

  3. On Ubuntu make sure you can ping that IP address.
  4. Open a new Files window, at the bottom left, click on other locations. Then, at the bottom enter smb://<VirtualbBox ethernet adapter IP address>/<Windows folder name>

  5. After clicking Connect button, you should see the temp folder:

Installing Eclipse CDT and build-essentials to offline Ubuntu

On my offline Ubuntu I use Eclipse C++ (CDT) and build-essentials to build C++ projects. To install Eclipse C++ (CDT) and build-essentials to offline Ubuntu:
  1. Download Linux version of Eclipse C++, copy to offline Ubuntu and extract. You can directly run eclipse without any further installation, but you need to finish the following steps to build a C++ project.
  2. On your Windows PC that is connected to the internet, install a virtualizer like VirtualBox  and install Ubuntu Desktop 22.04 as virtual machine.
  3. Enable Windows - Ubuntu file sharing.
  4. Use the following shell script to download build_essentials on your online PCs Ubuntu virtual machine and its dependencies. You can copy these downloads to your offline Ubuntu and install them following the steps written as comments down below:

Friday, November 3, 2023

Handling left over carriage return

Lines in text files created in Windows end with '\r\n'. If you read that text file in Linux with C++ getline(), your line will have a '\r' at the end because in Linux, getline() only gets rid of '\n'. If you have code that reads a certain number of characters and converts it to floating point using std::stod(), you might get std::invalid_argument exception when trying to read multiple values. You can use the following to take care of this problem:

Simulink signal

After generating code from a Simulink model, if you want to read the value of a parameter inside the model which is not an output port, you can use signals. In Matlab R2022b, signal definition is different from R2019a. The following R2022b method (assuming you have Embedded Coder) is only valid if you generate C code, I have not been able to find a way to generate signal code for C++:
  1. Open Simulink model
  2. Open Configuration Parameter window, go to Code Generation
  3. System target file: ert.tlc (Embedded Coder)
  4. Language: C
  5. In Simulink APPS menu, click on Embedded Coder, A C CODE tabs appears to the right of APPS menu
  6. Find the signal line you want the generate code for and double click on it, give it a name, e.g. "mySignal"
  7. Single click on signal line, a pop up will appear, click on the left-most item, "Add selected signals to code mappings"
  8. Under C CODE tab, click on Code Interface and then click Default Code Mappings
  9. A Code Mappings panel will appear at the bottom of model
  10. Click on right most tab, Signals/States
  11. Under Signals, find mySignal
  12. Click to the right of mySignal (Store Class column), change Auto to Model Default.
  13. Generate code by pressing ctrl + b
  14. If your model name was MyModel, the generated code will have a file called MyModel_ert_rtw/MyModel.h containing the data structure MyModel_B which has mySignal as a field that you can access from your own C/C++ code. 
  15. If your code is C++, dont forget to use include in C wrapper: extern "C" {#include "MyModel_ert_rtw/MyModel.h"}

Friday, October 13, 2023

Sanity checks

If your software component is taking data from other components or sensors, that data should go through at least one sanity check. The world outside of your component is full of surprising errors, most of whom you won't be able to guess beforehand. A correctly working external component might get buggy after an update, don't assume that newer versions don't break existing functionality. Sanity checks prevent the simple ones from crashing your software, protecting you from embarrassment to even loss of life.

Wednesday, August 16, 2023

Compile s-function with C++17

To compile a Simulink s-function file with C++17 (assuming you have Visual Studio 2019 or higher) , use:
        mex COMPFLAGS='$COMPFLAGS /std:c++17' file.cpp

Note that this does NOT work: mex CXXFLAGS='$CXXFLAGS /std:c++17' file.cpp

Thursday, March 16, 2023

Effective usage of locks in multi-threading

In an application with multiple threads and shared resources, make sure that you lock sections that have the minimal time impact, because other threads have to wait for the lock to be released. Let's say you have a function that saves a buffer to a file:

function saveData(newData) {
    addToBuffer(newData)
    saveBufferToFile()
}
To make this function thread safe, you might haphazardly lock at the beginning and unlock at the end of function:
function saveData(newData) {
    lock()
    addToBuffer(newData)
    saveBufferToFile()
    unlock()
}
Since saving buffer to file on disk takes a lot of time, another thread that wants to add to the buffer has to wait for file operation to finish. To decrease wait time of other locks that call saveData function, lock before adding to buffer and unlock before saving to file:
function saveData(newData) {
    lock()
    addToBuffer(newData)
    unlock()
    saveBufferToFile()
}

Thursday, January 12, 2023

C++: Using non-standard data types

When you use non-standard data types like enum, bool, double, long double, it might become a problem if you are sending data to another platform, e.g. sending from x86 to ARM, or even when sending from Windows to Linux. For example, long double is 8 bytes in MSVC but 16 bytes in GCC. These size differences will result in errors when casting the received bytes to data. Always know non-standard data type lengths of the platform that you are communicating with.

Wednesday, January 11, 2023

Monday, January 9, 2023

C: char pointer vs array

In C, the difference between a char pointer and char array becomes clear when you want to do assignment:

  #include<stdio.h>
  #include<string.h> //for strcpy
  int main() {
      char s[][10] = {"aaa", "bbb", "ccc"};
      //char* s[] = {"aaa", "bbb", "ccc"}; //same as above
      char* t1 = s[1];
      char t2[10]; strcpy(t2, s[1]);
      //t2 = s[1];// error: assignment to expression with array type
      printf("s[1] = %s\n", s[1]);
      printf("t1 = %s\n", t1);
      printf("t2 = %s\n", t2);
      return 0;
  }

s[1] is a char pointer. You can assign a char pointer to another char pointer but you cannot assign a char array to char pointer, you have to use strcpy. 

Another interesting difference:

    char a[] = "string1"; 
    char *p1  = a;
    char *p2  = "string2";
    a[0] = 'z';
    printf("a[0] = %c\n", a[0]);
    p1[0] = 'k'; //works fine, changes a[0]
    printf("a[0] = %c\n", a[0]);
    p2[0] = 'm'; //results in segmentation fault
    printf("p2[0] = %c\n", p2[0]);