In ELF binary, there are two sections to support exception handling routines that are predominately used by C++ applications: .eh_frame and .eh_frame_hdr. However, System V Application Binary Interface (ABI) for AMD64 mandates to have those sections even they are written in C.
a) .eh_frame section
The .eh_frame section has the same structure with .debug_frame, which follows DWARF format. It represents the table that describes how to set registers to restore the previous call frame at runtime. DWARF designers allow for having flexible mechanism to be able to unwind the stack with various expressions including constant values, arithmetic, memory dereference, register contents, and control flow.
The .eh_frame section contains at least one or more Call Frame Information (CFI) records. Each CFI consists of two entry forms: Common Information Entry (CIE) and Frame Description Entry (FDE). Every CFI has a single CIE and one or more FDEs. CFI usually corresponds to a single object file. Likewise, so does FDE to a single function. However, there might be multiple CIEs and FDEs corresponding to the parts of a function when the function has a non-contiguous range in a virtual memory space. The following shows the fields of each entry in detail.
CIE Fields | Data Format | Description |
length | 4 bytes | Total length of the CIE except this field |
CIE_id | 4 or 8 bytes | 0 for .eh_frame |
Version | 1 byte | Value 1 |
Augmentation | A null-terminated UTF-8 string | 0 if no augmetation |
Code alignment factor | unsigned LEB128 | Usually 1 |
Data alignment factor | signed LEB128 | Usually -4 (encoded as 0x7C) |
return_address_register | unsigned LEB128 | Dwarf number of the return register |
Augmentation data length | unsigned LEB128 | Present if Augmentation has ‘z’ |
Initial instructions | array of bytes | Dwarf Call Frame Instructions |
padding | array of bytes | DW_CFA_nop instructions to match the length |
FDE Fields | Data Format | Description |
Length | 4 bytes | Total length of the FDE except this field; 0 means end of all records |
CIE pointer | 4 or 8 bytes | Distance to the nearest preceding (parent) CIE |
Initial location | various bytes | Reference to the function corresponding to the FDE |
Range length | various bytes | Size of the function corresponding to the FDE |
Augmentation data length | unsigned LEB128 | Present if CIE Augmentation is non-empty |
Instructions | array of bytes | Dwarf Call Frame Instructions |
Here is an example of parsing a CIE and FDE (with a nice Skochinsky’s script for IDA Pro). The sizes of the following CIE and FDE are 0x14 and 0x34 respectively. The FDE has a CIE pointer pointing to the parent CIE (at 0x4090A0). The function location corresponding to this FDE starts from 0x400c70 to 0x4010c0, whose size is 0x450.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
CIE (Common Information Entry) .eh_frame:0x04090A0 14 00 00 00 ; Size .eh_frame:0x04090A4 00 00 00 00 ; CIE id .eh_frame:0x04090A8 01 ; Version .eh_frame:0x04090A9 7A 52 00 ; Augmentation String (aZr) .eh_frame:0x04090AC 01 ; Code alignment factor .eh_frame:0x04090AD 78 ; Data alignment factor .eh_frame:0x04090AE 10 ; Return register .eh_frame:0x04090AF 01 ; Augmentation data length .eh_frame:0x04090B0 1B ; R: FDE pointers encoding .eh_frame:0x04090B1 0C 07 08 90+ ; Initial CFE Instructions FDE (Frame Descriptor Entry) .eh_frame:0x04090B8 34 00 00 00 ; Size .eh_frame:0x04090BC 1C 00 00 00 ; CIE pointer (0x4090A0) .eh_frame:0x04090C0 B0 7B FF FF ; Initial location=0x400C70 .eh_frame:0x04090C4 50 04 00 00 ; Range length=0x450(end=0x4010C0) .eh_frame:0x04090C8 00 ; Augmentation data length .eh_frame:0x04090C9 41 0E 10 42+ ; CFE Instructions ... |
b) .eh_frame_hdr section
The .eh_frame_hdr section contains a series of attributes, followed by the table of multiple pairs of (initial location, pointer to the FDE in the .eh_frame). The entries are sorted by functions that allows to perform a quick binary search of O(log n).
1 2 3 4 5 6 7 8 9 10 |
version (uint8) structure version (=1) eh_frame_ptr_enc (uint8) encoding of eh_frame_ptr fde_count_enc (uint8) encoding of fde_count table_enc (uint8) encoding of table entries eh_frame_ptr (enc) pointer to the start of the .eh_frame section fde_count (enc) number of entries in the table ----------------------- Table starts from here ------------------------- initial_loc[i] initial location for the FDE fde_ptr[i] corresponding FDE ------------------------------------------------------------------------ |
The next figure is a brief illustration of the relationship of two sections.
Note that the attribute, table_enc, describes how the table has been encoded. It consists of lower 4 bits for value and upper 4 bits for encoding as follows: DWARF Exception Header value format (lower 4 bits) and DWARF Exception Header Encoding (upper 4 bits)
1 2 3 4 5 6 7 8 9 |
DW_EH_PE_omit 0xff No value is present DW_EH_PE_uleb128 0x01 Unsigned value is encoded using LEB128 DW_EH_PE_udata2 0x02 A 2 bytes unsigned value DW_EH_PE_udata4 0x03 A 4 bytes unsigned value DW_EH_PE_udata8 0x04 An 8 bytes unsigned value DW_EH_PE_sleb128 0x09 Signed value is encoded using LEB128 DW_EH_PE_sdata2 0x0A A 2 bytes signed value DW_EH_PE_sdata4 0x0B A 4 bytes signed value DW_EH_PE_sdata8 0x0C An 8 bytes signed value |
1 2 3 4 |
DW_EH_PE_absptr 0x00 Used with no modification DW_EH_PE_pcrel 0x10 Relative to the current program counter DW_EH_PE_datarel 0x30 Relative to the beginning of the .eh_frame_hdr DW_EH_PE_omit 0xff No value is present |
References
https://refspecs.linuxfoundation.org/LSB_1.3.0/gLSB/gLSB/ehframehdr.htm
http://www.dwarfstd.org/doc/DWARF4.pdf
http://www.hexblog.com/wp-content/uploads/2012/06/Recon-2012-Skochinsky-Compiler-Internals.pdf
https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf
one thing i dont understand is, what does 0x1b mean in FDE pointer encoding ?? i cant see EH_PE related encoding for this.
.eh_frame:0x04090AF 01 ; Augmentation data length
.eh_frame:0x04090B0 1B ; R: FDE pointers encoding
.eh_frame:0x04090B1 0C 07 08 90+ ; Initial CFE Instructions
Thanks for the comment. That is a good question.
I looked into the value (0x1b) in both an official DWARF documentation and the Skochinsky’s script. It seems the value is part of “initial_instructions”. The DWARF format describes it as “The default rule for all columns before interpretation of the initial instructions is the undefined rule. However, an ABI authoring body or a compilation system authoring body may specify an alternate default value for any or all columns”.
According to the script, the definition of the specific encoding in this example is “DW_EH_PE_sdata4 | DW_EH_PE_pcrel” or “0x10 | 0x0b”.
The format_enc() function shows the value comes from “val_format & 0x0F = 0x0b” (the last 4 bits) and “val_appl & 0x70 = 0x10” (the first 4 bits), resulting in 0x1b. The default value is 0xff as the undefined rule (DW_EH_PE_omit = 0xFF).
The whole rules for the initial_instructions are defined as follow. This makes sense because it informs how the following instructions should be interpreted beforehand, but I am not sure why the DWARF documentation misses the information though.
val_format = {
0x00: “DW_EH_PE_ptr”,
0x01: “DW_EH_PE_uleb128”,
0x02: “DW_EH_PE_udata2”,
0x03: “DW_EH_PE_udata4”,
0x04: “DW_EH_PE_udata8”,
0x08: “DW_EH_PE_signed”,
0x09: “DW_EH_PE_sleb128”,
0x0A: “DW_EH_PE_sdata2”,
0x0B: “DW_EH_PE_sdata4”,
0x0C: “DW_EH_PE_sdata8”,
}
val_appl = {
0x00: “DW_EH_PE_absptr”,
0x10: “DW_EH_PE_pcrel”,
0x20: “DW_EH_PE_textrel”,
0x30: “DW_EH_PE_datarel”,
0x40: “DW_EH_PE_funcrel”,
0x50: “DW_EH_PE_aligned”,
}
Let me know if you have a better insight. =)
you are right. “val_appl” is modifier which tell us how to use the value retrieved from first 4 bit.
Eg:
0x1B means read 4 byte signed value(DW_EH_PE_sdata4) and (0x10 = DW_EH_PE_pcrel) means the “signed value” read should be added to address start of this section plus off this byte relative to this section to get the final value.
Note: same cant be applied to “Instruction length Range”.
Hope it explains, if not let me know.
@vignesh, thanks for your clarification.