Geoff Chappell - Software Analyst
EXPORTS is a multi-definition statement. An empty statement is valid. The EXPORTS tag must run to the end of the line, else be followed by a space or tab. If the first definition is on the same line as the tag, then the space or tab may be followed by any amount of white space, including none, before the definition. Definitions other than the first must each start on a new line.
When not building for a VxD, each definition has the general form:
entryname[=internalname] [@ordinal [NONAME]] [CONSTANT | PRIVATE | DATA]
The entryname extends up to but not including a space, tab or equals sign. However, if this token is a recognised statement tag, then the EXPORTS statement is considered to have ended with the preceding definition (if any), so that the token is not an entryname but is instead the beginning of a new statement. For the purpose of parsing that next statement, an equals sign such as might have meaningfully separated an entryname from an internalname is effectively a space or tab after the statement tag.
An equals sign after any number of spaces or tabs, including none, introduces an internalname. There may be any number of spaces or tabs between the equals sign and the internalname, which then extends up to but not including the next space or tab. It is not an error if there is no text for the internalname: it is just that the definition has ended (and has provided just an entryname).
An @ sign, with at least one space or tab before it (to separate it from the entryname or internalname), introduces an ordinal. There may be any number of spaces or tabs, including none, between the @ and the ordinal, which then extends up to but not including the next space or tab. The ordinal is expected to begin with decimal digits that evaluate to a non-zero number less than 64K. The present coding ignores any characters that remain after the digits. It is a fatal error (LNK1119) to have the @ but either no ordinal or an invalid ordinal.
The remaining one or two arguments are case-insensitive keywords delimited by spaces or tabs. NONAME is recognised only if an ordinal is given. LIB allows for only one of CONSTANT, PRIVATE and DATA. Specification of CONSTANT results in a warning (LNK4087) about being obsolete.
It is a fatal error (LNK1118) to have any additional text in the definition.
The form that seems intended for an EXPORTS definition when building for a VxD is:
Another form is coded but not obviously intended:
Either way, the entryname extends up to but not including a space, tab or equals sign. However, if this token is a recognised statement tag, then the EXPORTS statement is considered to have ended with the preceding definition (if any), so that the token is not an entryname but is instead the beginning of a new statement. For the purpose of parsing that next statement, an equals sign after what would otherwise have been the entryname is effectively a space or tab after the statement tag.
When the entryname is delimited by a space or tab, if the first character after any amount of white space, including none, is an @ sign, there may follow a sequence of decimal digits that form the ordinal. Anything else or anything extra, including non-numerical characters immediately after the digits of the ordinal, is ignored.
When the entryname is delimited by an equals sign, all remaining characters on the line are ignored.
When not building for a VxD, LIB processes the definitions of an EXPORTS statement directly into the import library and export file, as opposed to translating them to a command line in the export file. The details are presently beyond the scope of these notes.
When building for a VxD, each definition in an EXPORTS statement is translated into a /exports option for the export-file command line:
The digits that form the ordinal in the command line are copied from the EXPORTS definition character by character without interpretation. If the @ is given in the definition, but without being followed by decimal digits for an ordinal, then the command line will continue to the @ even though the ordinal is empty.
If there is no space, tab or equals sign to terminate the entryname before the end of the line, LIB continues parsing. The subsequent coding allows that if the entryname starts with an @ sign then LIB can pick up an ordinal from a comment and pass this into the command-line translation. For a demonstration, prepare a module definition file, here named TEST.DEF, with the line
EXPORTS @ContrivedEntryName; 12345678 would be an invalid ordinal
and run the command
lib /def:test.def /machine:x86 /vxd
This executes without complaint to create the export file TEST.EXP, within which may be found the command line
The code that generates the /export switch uses a 128-byte buffer on the stack but does not check the lengths of the strings it copies there. A sufficiently long entryname or ordinal can therefore induce an overrun and corrupt the stack, including to overwrite the relevant procedure’s return address. In the version studied for these notes, namely 7.00.9466, the procedure has been compiled with the Buffer Security Check enabled and the placement of the buffer is such that even a one-byte overrun is caught.
In practice, of course, the entryname and ordinal are not nearly long enough to exceed these assumed limits and cause a problem. However, a demonstration is easy enough. Prepare a module definition file, again named TEST.DEF, containing the following two lines:
where the ellipsis stands for as many repetitions of 1234567890 as needed for the entryname argument to count to 120. With 8 bytes for the characters of the /export switch and its colon, the null byte at the end of the string will be one byte too many. Running
lib /def:test.def /machine:x86 /vxd
triggers the buffer overrun. Repeat with entryname reduced by one byte, and there is no buffer overrun.