EchoLynx EchoLynx - 2 months ago 10
C Question

What causes object files to vary between compilations even when the source and compiler flags are unmodified?

If you clicked on this because you thought that this can't be possible, I thought the same thing until I ran into it.

I was working on a project, written in C for a PIC, that is built with a Makefile. The Makefile was very disorganized, so I wanted to clean it up. To make sure I didn't break anything while I did it, I recorded the hashes of all the files following a fresh make: (No subdirectories in this project. Built with SDCC and GPUTILS.)


make clean
make
md5sum ./* > ../allsums.txt


Then I modified the Makefile and tried again, this time comparing the resulting files to allsums.txt.

make clean
vim Makefile
make
md5sum -c ../allsums.txt


Interestingly, the hashes of the .o files did not match, but the end result did. Assuming that the problem to be one I created somehow, I spent a lot of time trying to hunt it down.

Then, on a hunch, I did this using the original Makefile:

make clean
make
md5sum ./* > ../allsums.txt
make clean
make
md5sum -c ../allsums.txt


I found that the object files changed here, too! Some searching lead me to this question, which confirmed that (at least for gcc) the .o files change between each compilation.

What causes this?

Answer

The debugging information (symbols, date) in the object can make the object change, even if the code is strictly identical.

To ensure you don't have any change, just strip the objects:

strip *.o

The best way of comparing objects/performing a checksum on them is on stripped objects, otherwise you can never be sure.

(The same technique can be applied to executables)

Note: Once you stripped the objects you can link them but you'll have a hard time to debug. You can do it on a copy (theobject.o is unchanged, then):

strip theobject.o -o theobject_stripped.o

We use that process when performing "formal production" of our executables before delivery.

Actually we do it the other way round: we compare stripped executables, and if there's a difference, we compare stripped objects to find the culprit and narrow it down. Then we use our version control system on the sources to find why it changed.

Edit: if a custom time-dependent macro is used to define the date in the object files (-DDATE=\"somedate\") the checksum process will need more than a strip operation. The reverse operation (removing the date/version/whatever) from the object file (or files) has to be done with custom tools. You can benefit from this feature and have most of the object files untouched by applying the macro only on one file which contains the version (Version.o) on an exported symbol.

Checksum on that file will be different, but the other ones will be identical (or your coleagues are making it very hard for you pointlessly)

EDIT: for SDCC you have a similar tool called sdobjcopy the interface of which looks very very much like objcopy and has features to strip objects

sdobjcopy --strip-all theobject.o theobject_stripped.o

(there's also a --strip-debug option if the --strip-all option is too "violent")

check sdobjcopy man page for more details.