Trimming The Fat - Smaller Windows .exe Sizes With Delphi And C++Builder

Brian Long Consultancy & Training Services Ltd.
January 2016

Contents

C++Builder Delphi

Introduction

[Update 20160105: After a welcome communication from reader Maël Hörz on 5th Jan 2016 I've had to make a few alterations to some of the statements/advice herein]

If you’ve been working on a Windows project, maybe in a team for some time, chances are that the output executable has become pretty sizeable. This is not always the case, but many a vintage project has a history of growing and growing.

With current hardware, storage sizes and prices and so on, many have no interest in the size of their executables and that’s absolutely terrific. Some, though, are always looking for an opportunity to get an easy win with regard to file sizes and application footprint.footprint.

If you are of the former mindset then I bid you a good day; there is likely nothing more for you here.

If, however, you fall into the latter camp then please read on for a nice hint or two (or a reminder for some who saw this notion some long time ago).

There’s a trivial way of getting a reduction in executable file size for your applications just waiting to be employed in both Delphi and C++Builder, including the (currently, at the time of writing) latest release of Delphi 10 Seattle, C++Builder 10 Seattle (or RAD Studio 10 Seattle, which contains them both).

Relocation table

You are probably well aware of (even if not a regular user of) the Image Base linker option. This value defaults to 400000 for all Delphi and C++Builder projects and many of us never change it. Note this is a hexadecimal value, so is actually $400000 or 0x400000.

Delphi linker options

It relates to the address that the executable file has embedded into it, telling Windows at which address to load your PE (Portable Executable file format) file into memory. Often times when DLLs get loaded, there is already something at their specified base address or near it, which would cause an overlap, so Windows has to then move the image to a free space in memory. Windows is able to perform this relocation by using a relocation table, which is also found in the PE file.

The thing is, though, while DLLs can be loaded hither and thither around the address space an application will always* be loaded at address 0x400000. Because of this fact the relocation table is, strictly speaking, unnecessary in a .exe file. The bigger the executable file the bigger the relocation table will be, often between 5% and 15% of the total .exe size.

* To be clear, in his March 1994 MSJ article, Peering Inside the PE: A Tour of the Win32 Portable Executable File Format, Matt Pietrek (previously of Borland) does claim there could be a risk if you ran an executable without a relocation table on another Win32 implementation that didn't use 0x400000 as its base address, but given that all the current supported Microsoft Win32 implementations do, it shouldn't be an issue. This is rather backed up by Visual Studio 2015 defaulting to stripping out the relocation table, as per this documentation page on Visual C++'s /FIXED linker command.

Once upon a day enterprising individuals could remove this relocation table by resorting to dedicated tools, such as StripReloc. But those days are long gone: given the appropriate enticement both Delphi and C++Builder will remove the relocation table for you. Yay! We just need to be careful to only do this for projects that generate .exe files. Anything else that is generating a .dll or equivalent (.cpl, .ocx, etc.) definitely definitely requires its relocation table.

[Update 20160105: in fact an application set up for ASLR (see later in this post) will actually require relocations to be present in order for ASLR to be activated by the OS. Thus, if you want ASLR, maybe this isn't the way to shrink your executable.]

So what is the trick, for those of us who want to avail ourselves of it? Well, let’s look at Delphi first.

Delphi relocation table removal

In the project file add Windows (or Winapi.Windows) to the uses clause and follow the uses clause with this:

{$SETPEFLAGS IMAGE_FILE_RELOCS_STRIPPED}

This sets a PE file header characteristics flag telling Windows there is no relocation table. In addition, Delphi takes the hint and ensures that no relocation table is placed in the PE file. Easy!

If you don’t want to add the Windows unit to your project file uses clause you can alternatively use this simpler, but more impenetrable version:

{$SETPEFLAGS 1}

This is the equivalent of the command-line compiler switch: -–peflags:1

[Update 20160105: Note that removing the relocation table will disable ASLR (see later), so if you want ASLR this is not what you want to do.]

C++Builder relocation table removal

Now what about C++Builder? Well, according to the documentation on ILINK, the –B switch lets you specify the base address and request the relocation table is removed so we just need to ensure we get that option passed along to the linker. This documentation page on advanced linker options says we can pass command-line switches along to ILINK with the Additional Options setting. So in your project option options on the advanced linker options page, set the Additional Options setting to -B:0x00400000 and there you have it.

C++ linker options

[Update 20160105: Removing the relocation table will prevent ASLR (see later) from functioning, however since I haven't yet bumped into how to enable ALSR from C++Builder, this may be of no concern.]

Delphi RTTI reduction

It has been noted online by David Heffernan that if your application is not actively using RTTI, then you can throw out another chunk of your executable file by minimising the RTTI stored therein.

Taking this step has much greater risk as by doing so, you may well break anything that uses RTTI, such as Visual Live Bindings, or custom code that explicitly accesses RTTI. However if you are reasonably confident, or want to have a go and see what happens, add this immediately after the program line in your project source file:

{$IFOPT D-}{$WEAKLINKRTTI ON}{$ENDIF}
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}

Just be warned, this isn't as sure-fire a success as the relocation table removal.

Delphi and other PE header flags

Back in this tip-laden blog post I talked about another option you can enable using a PE file header characteristics flag, which tells Windows that when run from a network share, the executable should be copied into the local paging file and run from there, a feature which does not affect any programmatic reference to the executable file location (so Application.ExeName works unaffected and .ini files located by the .exe file will run as expected). This tip improves the user experience if there are glitches in network connectivity.

{$SETPEFLAGS IMAGE_FILE_NET_RUN_FROM_SWAP}

This equates to a command-line compiler switch of: --peflags:2048

There is a similar option that applies when running an executable from removable media, such as a CD drive:

{$SETPEFLAGS IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP}

This equates to a command-line compiler switch of: --peflags:1024

In addition in this post I talked about a couple of flags that could be plugged into the PE header file optional image header DLL characteristics field that can enable DEP and ASLR, as in:

const
  IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = $0040;
  IMAGE_DLLCHARACTERISTICS_NX_COMPAT = $0100;

{$SETPEOPTFLAGS IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE or IMAGE_DLLCHARACTERISTICS_NX_COMPAT}

or this equivalent:

const
  IMAGE_DLLCHARACTERISTICS_NX_COMPAT = $0100;

{$SETPEOPTFLAGS IMAGE_DLLCHARACTERISTICS_NX_COMPAT}
{$DYNAMICBASE ON}

or even this equivalent:

{$SETPEOPTFLAGS $140}

This is the equivalent of this command-line compiler option: --peoptflags:320

So by combining all these together we enable a number of useful options that can be slipped into the project source may think we are enabling a gamut of useful options via a little project source content:

{$SETPEFLAGS IMAGE_FILE_RELOCS_STRIPPED or IMAGE_FILE_NET_RUN_FROM_SWAP or IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP}
const
  IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = $0040;
  IMAGE_DLLCHARACTERISTICS_NX_COMPAT = $0100;

{$SETPEOPTFLAGS IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE or IMAGE_DLLCHARACTERISTICS_NX_COMPAT}

or from the command-line: --peflags:3073 --peoptflags:320

[Update 20160105]

Well, actually it turns out (and you can read more on the subject here) that dynamic base, or ASLR, requires a relocation table to be present, so in fact the above is a little self-defeating. Granted, ASLR in an executable doesn't move the executable's base - that sticks at 0x400000, but the other things such as the location of heaps and other things, still rely on Windows spotting your relocation table. Given this, I guess we can use one of these two variations:

Smaller executable size, better support when launched from network shares or removable media, and DEP enabled (if the CPU supports it):

{$SETPEFLAGS IMAGE_FILE_RELOCS_STRIPPED or IMAGE_FILE_NET_RUN_FROM_SWAP or IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP}
const
  IMAGE_DLLCHARACTERISTICS_NX_COMPAT = $0100;

{$SETPEOPTFLAGS IMAGE_DLLCHARACTERISTICS_NX_COMPAT}

or from the command-line: --peflags:3073 --peoptflags:256

Better support when launched from network shares or removable media, DEP enabled (if the CPU supports it) and ASLR:

{$SETPEFLAGS IMAGE_FILE_NET_RUN_FROM_SWAP or IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP}
const
  IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = $0040;
  IMAGE_DLLCHARACTERISTICS_NX_COMPAT = $0100;

{$SETPEOPTFLAGS IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE or IMAGE_DLLCHARACTERISTICS_NX_COMPAT}

or from the command-line: --peflags:3072 --peoptflags:320

[end of Update]

C++Builder and other PE header flagss

You can specify other PE file header characteristics flags using the Image flags option on the C++ Linker, Output section of the project options according to this page of the documentation.

C++ linker output options

The same can be achieved using the command line options: –GF:SWAPNET –GF:SWAPCD

This can also be expressed with: –GF:0xC00

Conclusion

It's pretty trivial to get between 5% and 15% size reduction on a regular Windows executable file, with no notable drawbacks [Update 20160105: other than disabling ASLR, if requested, in the case of Delphi projects].

Thanks go to the inimitable Hallvard Vassbotn for his near-decade old post that introduced me to this Delphi feature, which this post is intended to re-broadcast to the current spate of Delphi and C++Builder programmers. Also thanks to David Heffernan for his comments on Stack Overflow and to Maël Hörz for his follow-up.


Go back to the top of this page