Monday, July 28, 2008

Crashes Accessing Valid Memory (Due to Improper Packing and Structs)

Before reading this article, it would be helpful to become familiar with data alignment issues, and how packing can affect structures with regards to Windows CE.

Recently, I had to write some code that read in a bitmap from a file. For various reasons, I was not using any preexisting libraries however I was using the standard structures as defined by Microsoft.

The first issue that I noticed was that my application (which seemed fine) was crashing.

My algorithm was essentially:
  1. Open File (via CreateFile)
  2. Get Filesize of Bitmap
  3. Allocate enough Memory for Bitmap
  4. Read Entire File into Memory
  5. BITMAPFILEHEADER* pHeader = file In Memory;
  6. BITMAPINFOHEADER* pInfo = offset to Info;
  7. BYTE* pImageData = offset to Image Data;
The crash was occurring in step 6 when I was accessing the width field within the BITMAPINFOHEADER struct. The weird thing was after checking the pointer value I discovered that I was accessing a 32-bit value that was only aligned up to 16-bits (it wasn't 32-bit aligned.) Right away I knew that this wasn't right, unless of course the struct was packed (and the OS wasn't packing right.)

I quickly checked the initial alignment of my memory buffer and of the BITMAPFILEHEADER and found both to be valid. After this I checked the header definitions of the struct BITMAPFILEHEADER which was already defined within pragma pack(2) keywords as it was only 16-bit aligned. However, the struct itself was 14 bytes in size. This left the BITMAPINFOHEADER improperly aligned as it needs to be 32-bit aligned (but was 16-bit aligned instead.)

Because the BITMAPINFOHEADER itself is properly aligned the headers for Windows CE do not specify a packing. This behavior is in fact correct, as packing the headers will cause the OS to load data into and out of the struct much slower in situations where the struct is properly aligned.

To solve this problem, we really have a two options:
  1. Create 2 Buffers and call ReadFile twice. One call into ReadFile reads in the initial buffer which is packed to 2-bytes. The other call reads in the BITMAPINFOHEADER and remainng data which is packed to 4-bytes. This results in two calls to ReadFile (instead of 1) but causes BITMAPINFOHEADER to be properly aligned. Accessing the 32-bit data within the BITMAPINFOHEADER will be faster, however we have to do multiple System Calls into the OS.
  2. Redefine (Internally not in the PUBLIC\... code) BITMAPINFOHEADER to be say BITMAPINFOHEADER_PACKED and pack it to 2-bytes to account for the 16-bit alignment caused by the 14-byte size of BITMAPFILEHEADER.
This problem exists in far more than just with Bitmaps as it can actually exists with any struct. However, the example with the bitmap shows how subtle the problem may actually be. It also highlights why it may be a good idea to keep your structs alignment and size packed to 4 or 8 bytes from the start.