11/*
22 * Copyright 2021 Andrei Pangin
3+ * Copyright 2026, Datadog, Inc.
34 *
45 * Licensed under the Apache License, Version 2.0 (the "License");
56 * you may not use this file except in compliance with the License.
@@ -92,8 +93,7 @@ FrameDesc FrameDesc::default_frame = {0, DW_REG_FP | LINKED_FRAME_SIZE << 8,
9293FrameDesc FrameDesc::default_clang_frame = {0 , DW_REG_FP | LINKED_FRAME_CLANG_SIZE << 8 , -LINKED_FRAME_CLANG_SIZE , -LINKED_FRAME_CLANG_SIZE + DW_STACK_SLOT };
9394FrameDesc FrameDesc::no_dwarf_frame = {0 , DW_REG_INVALID , DW_REG_INVALID , DW_REG_INVALID };
9495
95- DwarfParser::DwarfParser (const char *name, const char *image_base,
96- const char *eh_frame_hdr) {
96+ void DwarfParser::init (const char *name, const char *image_base) {
9797 _name = name;
9898 _image_base = image_base;
9999
@@ -105,10 +105,21 @@ DwarfParser::DwarfParser(const char *name, const char *image_base,
105105 _code_align = sizeof (instruction_t );
106106 _data_align = -(int )sizeof (void *);
107107 _linked_frame_size = -1 ;
108+ _has_z_augmentation = false ;
109+ }
108110
111+ DwarfParser::DwarfParser (const char *name, const char *image_base,
112+ const char *eh_frame_hdr) {
113+ init (name, image_base);
109114 parse (eh_frame_hdr);
110115}
111116
117+ DwarfParser::DwarfParser (const char *name, const char *image_base,
118+ const char *eh_frame, size_t eh_frame_size) {
119+ init (name, image_base);
120+ parseEhFrame (eh_frame, eh_frame_size);
121+ }
122+
112123static constexpr u8 omit_sign_bit (u8 value) {
113124 // each signed flag = unsigned equivalent | 0x80
114125 return value & 0xf7 ;
@@ -144,6 +155,93 @@ void DwarfParser::parse(const char *eh_frame_hdr) {
144155 }
145156}
146157
158+ // Parse raw .eh_frame (or __eh_frame on macOS) without a binary-search index.
159+ // Records are CIE/FDE sequences laid out linearly; terminated by a 4-byte zero or EOF.
160+ void DwarfParser::parseEhFrame (const char *eh_frame, size_t size) {
161+ if (eh_frame == NULL || size < 4 ) {
162+ return ;
163+ }
164+ const char *section_end = eh_frame + size;
165+ _ptr = eh_frame;
166+
167+ while (_ptr + 4 <= section_end) {
168+ const char *record_start = _ptr;
169+ u32 length = get32 ();
170+ if (length == 0 ) {
171+ break ; // terminator
172+ }
173+ if (length == 0xffffffff ) {
174+ break ; // 64-bit DWARF not supported
175+ }
176+
177+ if (length > (size_t )(section_end - record_start) - 4 ) {
178+ break ;
179+ }
180+ const char *record_end = record_start + 4 + length;
181+
182+ u32 cie_id = get32 ();
183+
184+ if (cie_id == 0 ) {
185+ // CIE: update code and data alignment factors.
186+ // Layout after cie_id: [1-byte version][augmentation string \0][code_align LEB][data_align SLEB]
187+ // [return_address_register][augmentation data (if 'z')]...
188+ // return_address_register and everything after data_align are not consumed; _ptr = record_end
189+ // at the bottom of the loop skips them.
190+ //
191+ // _has_z_augmentation is overwritten by every CIE encountered. The DWARF spec allows
192+ // multiple CIEs with different augmentation strings in a single .eh_frame section, so
193+ // strictly speaking each FDE should resolve its own CIE via the backward cie_id offset.
194+ // We intentionally skip that: macOS binaries compiled by clang typically emit a single CIE
195+ // per module, and this parser is only called for macOS __eh_frame sections. Multi-CIE
196+ // binaries are not produced by the toolchains we target here.
197+ if (_ptr >= record_end) {
198+ _ptr = record_end;
199+ continue ;
200+ }
201+ _ptr++; // skip version
202+ if (_ptr >= record_end) {
203+ _ptr = record_end;
204+ continue ;
205+ }
206+ _has_z_augmentation = (*_ptr == ' z' );
207+ while (_ptr < record_end && *_ptr++) {
208+ } // skip null-terminated augmentation string
209+ if (_ptr >= record_end) {
210+ _ptr = record_end;
211+ continue ;
212+ }
213+ _code_align = getLeb (record_end);
214+ _data_align = getSLeb (record_end);
215+ } else {
216+ // FDE: parse frame description for the covered PC range.
217+ // After cie_id: [pcrel-range-start 4 bytes][range-len 4 bytes][aug-data-len LEB][aug-data][instructions]
218+ // Assumes DW_EH_PE_pcrel | DW_EH_PE_sdata4 encoding for range-start (clang macOS default).
219+ // The augmentation data length field (and the data itself) is only present when the CIE
220+ // augmentation string starts with 'z'.
221+ if (_ptr + 8 > record_end) {
222+ break ;
223+ }
224+ u32 range_start = (u32 )(getPtr () - _image_base);
225+ u32 range_len = get32 ();
226+ if (_has_z_augmentation) {
227+ _ptr += getLeb (record_end); // getLeb reads the length; advance past the augmentation data bytes
228+ if (_ptr > record_end) {
229+ break ;
230+ }
231+ }
232+ parseInstructions (range_start, record_end);
233+ addRecord (range_start + range_len, DW_REG_FP , LINKED_FRAME_CLANG_SIZE ,
234+ -LINKED_FRAME_CLANG_SIZE , -LINKED_FRAME_CLANG_SIZE + DW_STACK_SLOT );
235+ }
236+
237+ _ptr = record_end;
238+ }
239+
240+ if (_count > 1 ) {
241+ qsort (_table, _count, sizeof (FrameDesc), FrameDesc::comparator);
242+ }
243+ }
244+
147245void DwarfParser::parseCie () {
148246 u32 cie_len = get32 ();
149247 if (cie_len == 0 || cie_len == 0xffffffff ) {
0 commit comments