Detailed description of PE File Format (Part 1)
Summary
Windows NT 3.1 introduces a new executable file format named PE file format. The PE file format specification is included in the msdn Cd (specs and strategy, specifications, Windows NT file format specifications), but it is very obscure.
However, this document does not provide sufficient information, so developers cannot understand the PE format well. This article aims to solve this problem. It will thoroughly explain the entire PE file format. In addition, this article also provides a description of all required structures and a source code example to demonstrate how to use the information.
To obtain important information contained in the PE file, I wrote a dynamic link library named pefile. dll. All the source code examples in this article are also taken from this. This DLL and its source code are included in the CD as part of the pefile sample program (the sample program can be found in msdn and will not be provided on this site ), you can use this DLL in your own application. Similarly, you can use and build its source code as you wish. At the end of this article, you will find the function export list of pefile. dll and a description of how to use them. I think you will find that these functions allow you to handle the PE file format with ease.
Introduction
The recently added Windows NT in the Windows operating system family has greatly changed the development environment and application itself. One of the most significant changes is the PE file format. The new PE File Format mainly comes from the coff specification commonly used in UNIX operating systems, in order to ensure compatibility with the old version of MS-DOS and Windows operating systems, the PE file format also retains the familiar MZ header in the MS-DOS.
In this article, the PE file format is interpreted in the top-down order. This article will discuss in detail every part of the PE file as you study the file content from the beginning.
Many separate file Component definitions come from the WINNT. h file in the Microsoft Win32 SDK. In this file, you will find the Structure Type Definitions used to describe various components such as the file header and data directory. However, the PE file structure is missing in winnt. H. In this case, I define my own structure to access file data. You will find the definitions of these structures in pefile. H of the pefile. dll project. The complete pefile. h development file is included in the pefile sample program.
In addition to the pefile. dll sample code, the sample program supporting this article also has a separate Win32 sample application named exeview. EXE. The purpose of this example is to test the pefile. DLL functions, and in some cases require me to view multiple files at the same time; secondly, a lot of work to solve the PE file format is related to direct viewing of data. For example, if you want to understand how the import address name table is composed, You have to view it at the same time. idata segment header, imported image data directory, optional Header, and current. idata segment entity while exeview. EXE is the best example of viewing this information.
Let's get started.
PE file structure
The PE file format is organized into a linear data stream that begins with a MS-DOS header followed by a pattern program residue and a PE file sign, this is followed by the PE file header and optional Header. These are followed by all segment headers, followed by all segment entities. The end of the file is some other areas, including some mixed information, including the redistribution information, symbol table information, row number information, and string table data. I listed all these components in Figure 1.
Figure 1. PE file image structure
Starting from the MS-DOS File Header structure, I will discuss the Appearance Order of each component in the PE file format, most of the discussions are based on the sample code to demonstrate how to obtain file information. All source code is taken from the pefile. c file of the pefile. dll module. These examples all take advantage of the memory ing file, one of the coolest features of Windows NT, which allows users to use a simple pointer to access the data contained in the file, therefore, all examples use memory ing files to access data in PE files.
NOTE: Refer to the section at the end of this article about how to use pefile. dll.
MS-DOS head/solid mode head
As mentioned above, the first component of the PE file format is the MS-DOS header. In PE file format, it is not a new concept because it is exactly the same as the existing MS-DOS header since MS-DOS 2.0. The main reason to keep this same structure is that when you try to load a file under Windows 3.1 or MS-DOS 2.0 or later, the operating system can read this file and understand that it is incompatible with the current system. In other words, when you run a Windows NT executable file in MS-DOS 6.0, you will get the message "this program cannot be run in DOS mode. if the MS-DOS header is not the first part of the PE file format, the operating system fails to load the file and provides some completely useless information, such: "The name specified is not recognized as an internal or external command, operable program or batch file."
The MS-DOS header occupies the header 64 bytes of the PE file, and the structure of its content is described as follows:
// Winnt. h typedef struct _ image_dos_header {// dos. EXE header ushort e_magic; // magic number ushort e_cblp; // number of bytes on the last page of the file ushort e_cp; // number of file pages ushort e_crlc; // Number of redefinition elements ushort e_cparhdr; // header size, in the unit of section ushort e_minalloc; // the required minimum additional segment ushort e_maxalloc; // The required maximum additional segment ushort e_ss; // The initial SS value (relative offset) ushort e_sp; // The initial sp value ushort e_csum; // verifies ushort e_ip; // The initial IP value ushort e_cs; // initial Cs value (relative offset) ushort e_lfarlc ;// Reallocate table file address ushort e_ovno; // overwrite number ushort e_res [4]; // reserved word ushort e_oemid; // OEM identifier (relative to e_oeminfo) ushort e_oeminfo; // OEM information ushort e_res2 [10]; // reserved word long e_lfanew; // file address of the new EXE header} image_dos_header, * pimage_dos_header; the first domain e_magic is called a magic number, it is used to indicate a MS-DOS-compatible file type. All MS-DOS-compatible executable files set this value to 0x5a4d, representing the ASCII character MZ. This is why MS-DOS headers are sometimes called MZ headers. There are also many other domains that are useful for MS-DOS operating systems, but for Windows NT, this structure has only one useful domain-the last domain e_lfnew, a 4-byte file offset, the PE file header is located by it. For Windows nt pe files, the PE file header is followed by the MS-DOS header and the residual real-Mode Program.
Real-mode residual Program
A real-mode residual Program is an actual program that can be run by a MS-DOS during loading. For an executable image file of a MS-DOS, the application is executed from here. For Windows, OS/2, Windows NT operating systems, MS-DOS residual programs replace the master program's location here. This type of residual program usually does nothing, but only outputs a line of text, for example: "This program requires Microsoft Windows v3.1 or greater. "Of course, users can put any residual program here, which means you may often see something like this:" You can "t run a Windows NT application on OS/2, it "s simply not possible."
When you build an application for Windows 3.1, the linker links a default residual program named winstub. EXE to your executable file. You can replace winstub with a MS-DOS-based effective program, and use the stub module to define statements to indicate the linker, so as to replace the default behavior of the linker. Applications developed for Windows NT can be implemented by using the-stub: Linker option.
PE file header and flag
The PE file header is located by the e_lfanew domain of the MS-DOS header, this domain only gives the offset of the file, so you need to determine the actual memory ing address of the PE Header, you need to add the memory ing base address of the file. For example, the following macros are included in the pefile. H source file:
// Pefile. h # define ntsignature (A) (lpvoid) (byte *) A +/(pimage_dos_header) A)-> e_lfanew) when processing PE file information, I found that some of the files need to be checked frequently. Since these locations are only the offset of files, it is easier to use macros to locate these locations, because they are better than functions.
Note that the macro obtains the PE file mark, not the offset of the PE file header. That is because the. exe files are marked by the target operating system since the executable files of windows and OS/2. For the PE file format of Windows NT, this benchmark is prior to the PE File Header structure. In some versions of Windows and OS/2, this flag is the first word in the file header. Similarly, for the PE file format, Windows NT uses a DWORD Value.
The preceding macro returns the offset of the file flag, regardless of the type of executable file. Therefore, whether the file header is after the DWORD mark or at the word mark is determined by whether the mark is a Windows NT file mark. To solve this problem, I have compiled the imagefiletype function (as shown below), which returns the type of the image file:
// Pefile. c DWORD winapi imagefiletype (lpvoid lpfile) {/* First displays the DOS file mark */If (* (ushort *) lpfile = image_dos_signature) {/* determine the position of the PE File Header by the DOS header */If (loword (* (DWORD *) ntsignature (lpfile) = image_os2_signature | loword (* (DWORD *) ntsignature (lpfile) = image_os2_signature_le) Return (DWORD) loword (* (DWORD *) ntsignature (lpfile); else if (* (DWORD *) ntsignature (lpfile) = image_nt_signature) Return image_nt_signature; else return image_dos_signature;} else/* unknown file type */return 0;} The Code listed above immediately tells you how useful the ntsignature macro is. For comparing different file types and returning an appropriate file type, this macro will make the two things very simple. Winnt. the four different file types defined in H are: // winnt. h # define image_dos_signature 0x5a4d // MZ # define image_os2_signature 0x454e // ne # define 0x454c // le # define image_nt_signature 0 × 00004550 // pe00 first, windows's executable file type does not appear in this list, which looks strange. However, after a little research, we can see the reason: apart from the differences in operating system version specifications, there is no difference between Windows executable files and OS/2 executable files. The two operating systems have the same executable file structure.
Now we turn our attention to the Windows nt pe file format, we will find that as long as we get the file mark location, the PE file will be followed by four bytes. The next macro identifies the header of the PE file: // pefile. C # define pefhdroffset (A) (lpvoid) (byte *) A +/(pimage_dos_header) A)-> e_lfanew +/size_of_nt_signature )) the only difference between this macro and the previous macro is that this macro is added with a constant size_of_nt_signature. Unfortunately, this constant is not defined in winnt. H, So I defined it in pefile. H, which is the size of a DWORD.
Now that we know the position of the PE File Header, we can check the header data. We only need to assign this position to a structure, as shown below: pimage_file_header PFH; PFH = (pimage_file_header) pefhdroffset (lpfile); in this example, lpfile indicates a pointer to the base address of the executable file memory image. This shows the benefits of memory ing files: I/O of the execution file is not required, you only need to use the pointer PFH to access the information in the file. The PE File Header structure is defined as: // winnt. h typedef struct _ image_file_header {ushort machine; ushort numberofsections; ulong timedatestamp; ulong functions; ushort characteristics;} image_file_header, * pimage_file_header; # define image_sizeof_file_header 20 Please note that the size of the file header has been defined in this included file. In this way, it is very convenient to get the size of this structure. However, I think it is easier to use the sizeof operator for the structure itself, because in this case, I do not have to remember the constant name image_sizeof_file_header, you only need to remember the name of the image_file_header structure. On the other hand, it is challenging to remember the names of all structures, especially when these structures are only available in winnt. h.
The information in the PE file is basically some advanced information, which is used by the operating system or application to decide how to process the file. The first domain is used to indicate the type of target machine that the executable file is built, such as DEC (r) Alpha, MIPS r4000, Intel (r) x86, or some other processors. The system uses this information to determine how to process the file before reading other data.
The characteristics field represents some features of the file. For example, how to separate the debugging file for an executable file. The debugger usually uses the method of separating debugging information from the PE file and saving it to a debugging file (. DBG. To do this, the debugger needs to know whether to find debugging information in a separate file and whether the debugging information has been separated from the file. We can do this by going deep into the executable file and looking for debugging information. The image_file_debug_stripped feature is required for the debugger not to be found in the file. It indicates whether the debugging information of the file has been separated. In this way, the debugger can quickly check the header of the PE file to determine whether debugging information exists in the file.
Winnt. h defines several other tags that indicate the file header information, which is similar to the above example. I leave the study of these tags to readers as exercises to see if they are interesting. These tags are located after the image_file_header structure in winnt. h.
Another useful entry in the PE File Header structure is the numberofsections field, which indicates how many segments You Need To Know If You Want To conveniently extract file information-more specifically, number of segment headers and number of segment entities. Each segment header and Its entity are arranged consecutively in the file. Therefore, it is necessary to determine where the segment header and Its entity end. The following function extracts the number of segments from the PE file header: pefile. c int winapi numofsections (lpvoid lpfile) {/* Number of segments in the file header */Return (INT) (pimage_file_header) pefhdroffset (lpfile)-> numberofsections );} as you can see, pefhdroffset and other macros are very convenient to use.
PE Header
The next 224 bytes in the PE Executable File constitute the PE Optional header. Although its name is "optional Header", be sure that this header is not "optional" but "required. The opthdroffset macro can get a pointer to the optional Header: // pefile. h # define opthdroffset (A) (lpvoid) (byte *) A +/(pimage_dos_header) A)-> e_lfanew +/size_of_nt_signature +/sizeof (image_file_header ))) the optional Header contains a lot of important information about the executable image, such as the initial stack size, position of the program entry point, preferred base address, operating system version, and segment alignment. The image_optional_header structure is as follows: // winnt. h typedef struct _ construct {/// standard domain // ushort magic; uchar majorlinkerversion; uchar minorlinkerversion; ulong sizeofcode; ulong vertex; ulong addressofentrypoint; ulong baseofcode; ulong baseofdata; // nt additional domain // ulong imagebase; ulong sectionalignment; ulong filealignment; ushort majoroperatingsystemv Ersion; ushort kernel; ushort majorimageversion; ushort minorimageversion; ushort kernel; ulong sizeofimage; ulong kernel; ulong checksum; ushort subsystem; ushort kernel; ulong kernel; ulong sizeofstackcommit; ulong sizeofheapreserve; ulong sizeofheapcommit; ulong loaderflags; ulong n Umberofrvaandsizes; image_data_directory datadirectory [image_numberof_directory_entries];} image_optional_header, * pimage_optional_header; as you can see, the fields listed in this structure are too long. To prevent you from getting bored with all these domains, I will only discuss useful-that is, useful for exploring PE file formats.
Standard domain
First, note that this structure is divided into "Standard domain" and "nt additional domain ". The so-called standard domain is a common part of the coff format of UNIX executable files. Although the names defined in coff are retained in the standard domain, Windows NT still uses them for different purposes-although changing names is better.
· Magic. I don't know what this domain is for, for the example program exeview. for the EXE sample program, the value is 0x010b or 267. EXE, 0 × 0107 is the ROM image, and this information is obtained from the exists ).
· Majorlinkerversion and minorlinkerversion. Indicates the version of the linker that links the image. Windows nt sdk that is compatible with Windows NT build 438 contains a 2.39 (hexadecimal 2.27) linker version ).
· Sizeofcode. Executable code size.
· Sizeofinitializeddata. Size of initialized data.
· Sizeofuninitializeddata. Uninitialized data size.
· Addressofentrypoint. In the standard domain, the addressofentrypoint domain is the most interesting for the PE file format. This field indicates the position of the application entry point. In addition, for system hackers, this location is the end of the import Address Table (IAT. The following functions demonstrate how to obtain the Windows NT executable image entry point from the optional Header. // Pefile. c lpvoid winapi getmoduleentrypoint (lpvoid lpfile) {pimage_optional_header Poh; Poh = (pimage_optional_header) opthdroffset (lpfile); If (Poh! = NULL) Return (lpvoid) Poh-> addressofentrypoint; else return NULL;} · baseofcode. The relative offset of the loaded Image Code (". Text" segment.
· Baseofdata. The relative offset of the uninitialized data (". BSS" segment) of the loaded image.
Windows NT additional domain
The additional domains added to the Windows nt pe file format provide the loader support for specific process behavior of Windows NT. The following is an overview of these domains.
· Imagebase. The preferred base address in the process image address space. The Microsoft Win32 SDK linker of Windows NT sets this value to 0 × 00400000 by default, but you can use the-base: Linker switch to change this value.
· Sectionalignment. Starting from imagebase, each segment is successively loaded into the address space of the process. Sectionalignment specifies the minimum space that can be occupied during the loading period-that is, the segment is about sectionalignment alignment.
Windows NT virtual memory manager specifies that segment alignment must be at least the page size (the current X86 platform is 4096 bytes) and must be multiplied by page size. The 4096 byte is the default value of the x86 linker, but it can be set through the-align: Linker switch.
· Filealignment. The minimum information block interval at which the image file is first loaded. For example, the linker adds a segment entity (the original data of the segment) to the nearest filealignment boundary of the file. The 2.39 linker mentioned earlier alignment the image file with a boundary of 0 × 200 bytes. This value can be forcibly changed to 512 to 65535.
· Majoroperatingsystemversion. Indicates the main version number of the Windows NT operating system. Generally, this value is set to 1 for Windows NT 1.0.
· Minoroperatingsystemversion. Indicates the version number of the Windows NT operating system. Generally, this value is set to 0 for Windows NT 1.0.
· Majorimageversion. The primary version number of the application. For Microsoft Excel 4.0, the value is 4.
· Minorimageversion. Indicates the minor version number of the application. For Microsoft Excel 4.0, the value is 0.
· Majorsubsystemversion. Indicates the main version number of the Windows NT Win32 subsystem. Generally, this value is set to 3 for Windows NT 3.10.
· Minorsubsystemversion. Indicates the minor version number of the Windows NT Win32 subsystem. Generally, this value is set to 10 for Windows NT 3.10.
· Reserved1. Unknown purpose, usually not used by the system and set to 0 by the linker.
· Sizeofimage. Indicates the size of the address space to be retained in the address space of the loaded executable image. This number is largely affected by the sectionalignment. For example, consider a system with a fixed page size of 4096 bytes. If you have an executable file with 11 segments, each segment contains less than 4096 bytes, and for the 65536-byte boundary alignment, the sizeofimage domain will be set to 11*65536 = 720896 (page 176 ). If an identical file is 4096-byte aligned, The sizeofimage domain will return 11*4096 = 45056 (11 pages ). This is just a simple example. It indicates that each segment requires less memory than one page. In reality, the linker calculates each segment individually to determine the exact value of sizeofimage. It first determines the number of bytes required for each segment, and finally rounds up the total number of pages to the closest sectionalignment boundary. Then the total number is the sum of the individual requirements of each segment.
· Sizeofheaders. This field represents how much space in the file is used to store all the file headers, including the MS-DOS header, PE File Header, PE Optional header, and PE segment header. All the segment entities in the file start at this position.
· Checksum. The Checksum is used to verify the executable file during loading. It is set and verified by the linker. Since the algorithms used to create these checksum are private information, we will not discuss them here.
· Subsystem. The domain used to identify the target subsystem of the executable file. Each possible subsystem value is listed after the image_optional_header structure of WINNT. h.
· Dllcharacteristics. Indicates whether a DLL image is a mark containing the entry point for process and thread initialization and termination.
· Sizeofstackreserve, sizeofstackcommit, sizeofheapreserve, and sizeofheapcommit. These domains control the number of address spaces to be retained and apply for stacks and default stacks. By default, the stack and heap both have the application value of one page and the reserved value of 16 pages. These values can be set using the linker switch-stacksize: And-heapsize.
· Loaderflags. Instruct the loader whether to stop and debug the loader during loading, or run normally by default.
· Numberofrvaandsizes. This field identifies the next datadirectory array. Note that it is used to identify this array, rather than the entry numbers in the array, which is very important.
· Datadirectory. The data directory indicates the location of other important components of executable information in the file. It is actually an array of image_data_directory structures, located at the end of the optional Header structure. The current PE file format defines 16 Possible Data Directories, 11 of which are currently in use.
Data Directory
Winnt. the data directory defined in H is: // winnt. h // directory entry // export directory # define defaults 0 // import directory # define image_directory_entry_import 1 // Resource Directory # define image_directory_entry_resource 2 // exception directory # define image_directory_entry_exception 3 // Security directory # define image_directory_entry_security 4 // relocation basic table # define image_directory_entry_basereloc 5 // DEBUG directory # define image_directory_entry_debug 6 // description string # define image_di Rectory_entry_copyright 7 // machine value (MIPs GP) # define image_directory_entry_globalptr 8 // TLS directory # define image_directory_entry_tls 9 // load the configuration directory # define limit 10 basically, each data directory is a structure defined as image_data_directory. Although the data directory entries are the same, each specific directory type is completely unique. The definition of each data directory is described as a pre-defined segment later in this article ". // Winnt. h typedef struct _ image_data_directory {ulong virtualaddress; ulong size;} image_data_directory, * pimage_data_directory; each data directory entry specifies the directory size and relative virtual address. If you want to define a specific directory, You need to determine the relative address from the data directory array in the optional Header, and then use the virtual address to determine the segment where the directory is located. Once you decide which segment contains the directory, the segment header of the segment will be used to find the exact file offset location of the data directory.
To obtain a data directory, you must first understand the concept of segments. I will describe it below. After this discussion, there will be an example of how to locate the data directory.
PE file segment
The PE File Specification consists of the headers defined so far and a general object named "segment. The segment contains the file content, including code, Data, resources, and other executable information. Each segment has a header and an entity (raw data ). I will describe the information about the segment header below, but the segment entity lacks a strict file structure. Therefore, they can be organized by the linker in almost any way, as long as its header is filled with enough information to interpret the data.
Segment Header
In PE file format, all segment headers are placed behind the optional Header. Each segment header is 40 bytes long without any filling information. The segment header is defined as the following structure: // winnt. h # define 8 typedef struct _ image_section_header {uchar name [Operator]; Union {ulong physicaladdress; ulong virtualsize;} MISC; ulong virtualaddress; ulong sizeofrawdata; ulong pointertorawdata; ulong worker; ulong pointertolinenumbers; ushort numberofrelocations; ushort numberoflinenumbers; ulong characteristics;} image _ Section_header, * pimage_section_header; how do you obtain the segment header information of a specific segment? Since the segment header is continuously organized without a specific order, the segment header must be located by name. The following functions demonstrate how to obtain a segment header from a PE image file with a given segment name: // pefile. c bool winapi require (lpvoid lpfile, image_section_header * Sh, char * szsection) {pimage_section_header PSH; int nsections = numofsections (lpfile); int I; If (PSH = (pimage_section_header) sechdroffset (lpfile ))! = NULL) {/* search for segments by name */for (I = 0; I <nsections; I ++) {If (! Strcmp (PSH-> name, szsection) {/* Copy data to the header */copymemory (lpvoid) Sh, (lpvoid) Psh, sizeof (image_section_header )); return true;} else PSH ++;} return false;} This function uses the sechdroffset macro to locate the header of the first segment, and then it starts to loop in all segments, and compare the name of the expected segment with the name of each segment until the correct one is found. When the segment is found, the function copies the data in the memory image file to the input function structure, and then the fields in the image_section_header structure can be directly accessed.
Segment header domain
· Name. Each segment has an 8-Character Domain Name, and the first character must be a period.
· Physicaladdress or virtualsize. The second domain is a Union domain and is no longer used.
· Virtualaddress. This domain identifies the virtual address of the segment to be loaded in the process address space. The actual address is obtained by adding the value of this field to the imagebase virtual address in the optional Header structure. Remember, if the image file is a DLL, the DLL will not be loaded to the location required by imagebase. So once the file is loaded into a process, the actual imagebase value should be verified by using getmodulehandle.
· Sizeofrawdata. This field indicates the segment entity size relative to filealignment. The actual segment entity size in the file will be less than or equal to the integer multiple of filealignment. Once the image is loaded into the address space of a process, the size of the segment object will be less than or equal to the integer multiple of filealignment.
· Pointertorawdata. This is the offset of the object position in the middle section of a file.
· Pointertorelocations, pointertolinenumbers, numberofrelocations, and numberoflinenumbers. These fields are not used in PE format.
· Characteristics. Defines the features of segments. These values can be found in the PE format specification of WINNT. h and the optical disc (: msdn.
Value definition
0 × 00000020 code segment
0 × 00000040 initialized data segments
0 × 00000080 uninitialized data segment
0 × 04000000 this segment of data cannot be cached
0 × 08000000 the segment cannot be paged
0 × 10000000 shared segments
0 × 20000000 executable segments
0 × 40000000 readable segments
0 × 80000000 writable segments
Locate data directory
Data Directories exist in their corresponding data segments. Typically, a data directory is the first structure in a segment object, but not required. For this reason, if you need to locate a specified data directory, You need to obtain information from the segment header and the optional Header.
To simplify this process, I have compiled the following functions to locate any data directory defined in winnt. h. // Pefile. c lpvoid winapi imagedirectoryoffset (lpvoid lpfile, DWORD dwimage_directory) {export Poh; pimage_section_header PSH; int nsections = numofsections (lpfile); int I = 0; lpvoid vaimagedir; /* must be 0 to (NumberOfRvaAndSizes-1) */If (dwimage_directory> = Poh-> numberofrvaandsizes) return NULL; /* Get the offset of the optional Header and segment header */Poh = (pimage_optional_header) opthdroffset (lpfile); PSH = (pimage _ Section_header) sechdroffset (lpfile);/* locate the relative virtual address of the image directory */vaimagedir = (lpvoid) Poh-> datadirectory [dwimage_directory]. virtualaddress;/* locate the segment containing the image directory */while (I ++ <nsections) {If (PSH-> virtualaddress <= (DWORD) vaimagedir & Psh-> virtualaddress + Psh-> sizeofrawdata> (DWORD) vaimagedir) break; PSH ++;} if (I> nsections) return NULL; /* return the offset of the image import directory */Return (lpvoid) (INT) lpfile + (INT) vaimagedir. Psh-> virtualaddress) + (INT) Psh-> pointertorawdata);} the function first confirms the number of the requested data directory entry, then it obtains two pointers pointing to the optional Header and the first segment header respectively. It determines the virtual address of the data directory from the optional Header, And then it uses this value to determine which segment of the data directory is located in. If the appropriate segment object has been identified, you can find the specific location of the data directory by converting its relative virtual address to the address in the file.
Link: http://fcjblog.com/tutorial-crack-detailed-pe-file-format-top