Calculating the Checksum for a Segmented-Executable File (71971)
The information in this article applies to:
- Microsoft LINK for MS-DOS 5.0x
- Microsoft LINK for MS-DOS 5.1x
- Microsoft LINK for MS-DOS 5.2
- Microsoft LINK for MS-DOS 5.3x
- Microsoft LINK for MS-DOS 5.5
- Microsoft LINK for MS-DOS 5.6
- Microsoft LINK for OS/2 5.0x
- Microsoft LINK for OS/2 5.1
- Microsoft LINK for OS/2 5.11
- Microsoft LINK for OS/2 5.13
- Microsoft LINK for OS/2 5.15
This article was previously published under Q71971 SUMMARY
The header of an executable file created by Microsoft LINK contains two
different checksum values. The first, located at offset 12-13h in the old
.EXE header, is the complemented checksum of all words in the file. The
second, located at offset 08-0Bh in the new .EXE header, is the 32-bit
checksum of all DWORD values in the file. The text below describes the
process involved to calculate these checksum values.
MORE INFORMATION
The following information that describes calculating the first checksum is
from page 122 or the "MS-DOS Encyclopedia" (Microsoft Press):
12-13H (Complemented Checksum) This word contains the one's
complement of the summation of all words in the .EXE file. Current
versions of MS-DOS basically ignore this word when they load a .EXE
program; however, future versions might not. When LINK generates a
.EXE file, it adds together all the contents of the .EXE file
(including the .EXE header) by treating the entire file as a long
sequence of 16-bit words. During this addition, LINK gives the
Complemented Checksum word (12-13H) a temporary value of 0000H. If
the file consists of an odd number of bytes, then the final byte is
treated as a word with a high byte of 00H. Once LINK has totaled
all words in the .EXE file, it performs a one's complement
operation on the total and records the answer in the .EXE file
header at offsets 12-13H. The validity of a .EXE file can then be
checked by performing the same word-totaling process as LINK
performed. The total should be FFFFH, because the total will
include LINK's calculated complemented checksum, which is designed
to give the file the FFFFH total.
The sample code below shows one implementation of this technique.
Note that Microsoft LINK does not correctly calculate the checksum if the
linker command line includes the /CODEVIEW or /EXEPACK option switches.
However, because the MS-DOS, Microsoft Windows, and OS/2 versions 1.x do
not verify the checksum, this behavior does not present a problem under
normal circumstances. Microsoft LINK version 5.3 and later do not compute a
16-bit or 32-bit checksum. The reserved bytes in the .EXE header are set to
zero.
The system uses the old .EXE header checksum when an MS-DOS application
runs. However, for any other type of executable file, (an application or
dynamic-link library [DLL] for Windows or OS/2 versions 1.x, or a bound
application), LINK computes a 32-bit checksum for the new .EXE header. This
calculation is only slightly more complicated than that discussed earlier.
First of all, the checksum is not the sum of every DWORD value in the
physical file. It is the sum of the DWORD values in the executable portion
of the file. As other items, like resources, symbolic information, and so
on, are added to the .EXE file, the checksum value does not change. To
determine the size of the executable file, look at the two words at offset
02h and 04h in the old .EXE file header.
Second, the 32-bit checksum is the sum of each DWORD value in the
executable file EXCEPT for the checksum itself. Because Microsoft LINK does
not perform a complement operation, the result is NOT FFFFFFFFh when all
DWORD values are summed.
The following code example demonstrates calculating these checksum values.
Sample Code
/*
* Compile options needed: none
*/
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
void Calc16ChkSum(FILE *fp);
void Calc32ChkSum(FILE *fp);
void main(int, char **);
FILE *fp;
unsigned long NewHdrOffset, FileSize = 0L;
unsigned int PageCnt;
#define NEWHDROFFSET 0x3C /* Location in Old Header with offset
of new header in EXE */
void main (int argc, char * argv[])
{
if (argc != 2)
{
printf("\n\nUsage: %s <EXEfilename>\n\n", argv[0]);
exit(-1);
}
if ((fp = fopen(argv[1], "rb")) == NULL)
{
printf("\n\nError: Unable to open file : %s\n\n", argv[1]);
exit(-1);
}
fread(&PageCnt, sizeof(int), 1, fp); /* Read past the signature */
fread(&PageCnt, sizeof(int), 1, fp); /* Read the last page size */
FileSize = PageCnt;
fread(&PageCnt, sizeof(int), 1, fp); /* Read the full page count */
if (FileSize == 0L)
FileSize = PageCnt * 512;
else
FileSize += (PageCnt - 1) * 512;
fseek(fp, NEWHDROFFSET, SEEK_SET); /* Locate the New EXE offset */
fread(&NewHdrOffset, sizeof(long), 1, fp); /* and read it */
if (NewHdrOffset == 0L)
Calc16ChkSum(fp);
else
Calc32ChkSum(fp);
fcloseall();
return;
}
void Calc16ChkSum(FILE *fp)
{
unsigned int sum16, NxtInt, x;
unsigned char NxtChar;
sum16 = 0;
fseek(fp, 0, SEEK_SET);
for (x = 0L; x < FileSize / 2L; x++)
{
fread(&NxtInt, sizeof(int), 1, fp);
sum16 += NxtInt;
}
/* make sure and get the last byte if odd size... */
if (FileSize % 2)
{
fread(&NxtChar, sizeof(char), 1, fp);
sum16 += (unsigned int)NxtChar;
}
printf("\nThe 16 bit checksum should be FFFF, it is %x\n\n",
sum16);
}
void Calc32ChkSum(FILE *fp)
{
unsigned long sum32, NxtLong, CheckSum32, x;
unsigned char NxtByte, y;
sum32 = 0;
fseek(fp, 0, SEEK_SET);
/* Calculate the number of DWORDs before the checksum, and add
* them together.
* (Note: The checksum *always* starts on a DWORD boundary.) */
x = (NewHdrOffset + 8) / 4;
for ( ; x; x--)
{
fread(&NxtLong, sizeof(long), 1, fp);
sum32 += NxtLong;
}
/* Read the actual check sum... */
fread(&CheckSum32, sizeof(long), 1, fp);
/* Then the rest of the DWORDs in the file. */
for (x = 0; x < (FileSize - NewHdrOffset - 12) / 4; x++)
{
fread(&NxtLong, sizeof(long), 1, fp);
sum32 += NxtLong;
}
/* We have to account for the extra bytes in the file. Basically,
* they are used to form a long with the high order bytes set to
* zero. */
if (0L != (x = FileSize % 4L))
{
NxtLong = 0L;
for (y = 0 ; y < x ; y++)
{
fread(&NxtByte, sizeof(char), 1, fp);
NxtLong += (unsigned long)NxtByte << (8 * y);
}
sum32 += NxtLong;
}
printf("\nThe 32-bit checksum should be %lx, it is %lx\n\n",
CheckSum32, sum32);
}
Modification Type: | Major | Last Reviewed: | 10/23/2003 |
---|
Keywords: | kb16bitonly KB71971 |
---|
|