Skip to content

Commit 4466cfb

Browse files
committed
PROF-14088
1 parent 7d049f2 commit 4466cfb

5 files changed

Lines changed: 271 additions & 14 deletions

File tree

ddprof-lib/src/main/cpp/codeCache.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void CodeCache::copyFrom(const CodeCache& other) {
9999
_imports_patchable = other._imports_patchable;
100100

101101
_dwarf_table_length = other._dwarf_table_length;
102-
_dwarf_table = new FrameDesc[_dwarf_table_length];
102+
_dwarf_table = (FrameDesc*)malloc(_dwarf_table_length * sizeof(FrameDesc));
103103
memcpy(_dwarf_table, other._dwarf_table,
104104
_dwarf_table_length * sizeof(FrameDesc));
105105
_default_frame = other._default_frame;
@@ -120,7 +120,7 @@ CodeCache &CodeCache::operator=(const CodeCache &other) {
120120
}
121121

122122
NativeFunc::destroy(_name);
123-
delete[] _dwarf_table;
123+
free(_dwarf_table);
124124
delete[] _blobs;
125125
free(_build_id);
126126

@@ -135,7 +135,7 @@ CodeCache::~CodeCache() {
135135
}
136136
NativeFunc::destroy(_name);
137137
delete[] _blobs;
138-
delete[] _dwarf_table;
138+
free(_dwarf_table);
139139
free(_build_id); // Free build-id memory
140140
}
141141

ddprof-lib/src/main/cpp/dwarf.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
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.
@@ -105,10 +106,29 @@ DwarfParser::DwarfParser(const char *name, const char *image_base,
105106
_code_align = sizeof(instruction_t);
106107
_data_align = -(int)sizeof(void *);
107108
_linked_frame_size = -1;
109+
_has_z_augmentation = false;
108110

109111
parse(eh_frame_hdr);
110112
}
111113

114+
DwarfParser::DwarfParser(const char *name, const char *image_base,
115+
const char *eh_frame, size_t eh_frame_size) {
116+
_name = name;
117+
_image_base = image_base;
118+
119+
_capacity = 128;
120+
_count = 0;
121+
_table = (FrameDesc *)malloc(_capacity * sizeof(FrameDesc));
122+
_prev = NULL;
123+
124+
_code_align = sizeof(instruction_t);
125+
_data_align = -(int)sizeof(void *);
126+
_linked_frame_size = -1;
127+
_has_z_augmentation = false;
128+
129+
parseEhFrame(eh_frame, eh_frame_size);
130+
}
131+
112132
static constexpr u8 omit_sign_bit(u8 value) {
113133
// each signed flag = unsigned equivalent | 0x80
114134
return value & 0xf7;
@@ -144,6 +164,81 @@ void DwarfParser::parse(const char *eh_frame_hdr) {
144164
}
145165
}
146166

167+
// Parse raw .eh_frame (or __eh_frame on macOS) without a binary-search index.
168+
// Records are CIE/FDE sequences laid out linearly; terminated by a 4-byte zero.
169+
void DwarfParser::parseEhFrame(const char *eh_frame, size_t size) {
170+
if (eh_frame == NULL || size < 4) {
171+
return;
172+
}
173+
const char *section_end = eh_frame + size;
174+
_ptr = eh_frame;
175+
176+
while (_ptr + 4 <= section_end) {
177+
const char *record_start = _ptr;
178+
u32 length = get32();
179+
if (length == 0) {
180+
break; // terminator
181+
}
182+
if (length == 0xffffffff) {
183+
break; // 64-bit DWARF not supported
184+
}
185+
186+
const char *record_end = record_start + 4 + length;
187+
if (record_end > section_end) {
188+
break;
189+
}
190+
191+
u32 cie_id = get32();
192+
193+
if (cie_id == 0) {
194+
// CIE: update code and data alignment factors.
195+
// Layout after cie_id: [1-byte version][augmentation string][code_align LEB][data_align SLEB]...
196+
//
197+
// _has_z_augmentation is overwritten by every CIE encountered. The DWARF spec allows
198+
// multiple CIEs with different augmentation strings in a single .eh_frame section, so
199+
// strictly speaking each FDE should resolve its own CIE via the backward cie_id offset.
200+
// We intentionally skip that: macOS binaries compiled by clang always emit a single CIE
201+
// per module, and this parser is only called for macOS __eh_frame sections. Multi-CIE
202+
// binaries are not produced by the toolchains we target here.
203+
_ptr++; // skip version
204+
_has_z_augmentation = (*_ptr == 'z');
205+
while (_ptr < record_end && *_ptr++) {
206+
} // skip null-terminated augmentation string
207+
if (_ptr + 2 > record_end) {
208+
_ptr = record_end;
209+
continue;
210+
}
211+
_code_align = getLeb();
212+
_data_align = getSLeb();
213+
} else {
214+
// FDE: parse frame description for the covered PC range.
215+
// After cie_id: [pcrel-range-start 4 bytes][range-len 4 bytes][aug-data-len LEB][aug-data][instructions]
216+
// The augmentation data length field (and the data itself) is only present when the CIE
217+
// augmentation string starts with 'z'.
218+
if (_ptr + 8 > record_end) {
219+
break;
220+
}
221+
u32 range_start = (u32)(getPtr() - _image_base);
222+
u32 range_len = get32();
223+
if (_has_z_augmentation) {
224+
_ptr += getLeb(); // skip augmentation data length + data
225+
if (_ptr > record_end) {
226+
break;
227+
}
228+
}
229+
parseInstructions(range_start, record_end);
230+
addRecord(range_start + range_len, DW_REG_FP, LINKED_FRAME_SIZE,
231+
-LINKED_FRAME_SIZE, -LINKED_FRAME_SIZE + DW_STACK_SLOT);
232+
}
233+
234+
_ptr = record_end;
235+
}
236+
237+
if (_count > 1) {
238+
qsort(_table, _count, sizeof(FrameDesc), FrameDesc::comparator);
239+
}
240+
}
241+
147242
void DwarfParser::parseCie() {
148243
u32 cie_len = get32();
149244
if (cie_len == 0 || cie_len == 0xffffffff) {

ddprof-lib/src/main/cpp/dwarf.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Copyright The async-profiler authors
3-
* Copyright 2025, Datadog, Inc.
3+
* Copyright 2025, 2026, Datadog, Inc.
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

@@ -116,6 +116,7 @@ class DwarfParser {
116116
u32 _code_align;
117117
int _data_align;
118118
int _linked_frame_size; // detected from FP-based DWARF entries; -1 = undetected
119+
bool _has_z_augmentation;
119120

120121
const char* add(size_t size) {
121122
const char* ptr = _ptr;
@@ -179,6 +180,7 @@ class DwarfParser {
179180
}
180181

181182
void parse(const char* eh_frame_hdr);
183+
void parseEhFrame(const char* eh_frame, size_t size);
182184
void parseCie();
183185
void parseFde();
184186
void parseInstructions(u32 loc, const char* end);
@@ -189,6 +191,7 @@ class DwarfParser {
189191

190192
public:
191193
DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr);
194+
DwarfParser(const char* name, const char* image_base, const char* eh_frame, size_t eh_frame_size);
192195

193196
FrameDesc* table() const {
194197
return _table;

ddprof-lib/src/main/cpp/symbols_macos.cpp

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* Copyright The async-profiler authors
3+
* Copyright 2026, Datadog, Inc.
34
* SPDX-License-Identifier: Apache-2.0
45
*/
56

@@ -11,8 +12,8 @@
1112
#include <mach-o/dyld.h>
1213
#include <mach-o/loader.h>
1314
#include <mach-o/nlist.h>
14-
#include "symbols.h"
1515
#include "dwarf.h"
16+
#include "symbols.h"
1617
#include "log.h"
1718

1819
UnloadProtection::UnloadProtection(const CodeCache *cc) {
@@ -33,6 +34,8 @@ class MachOParser {
3334
CodeCache* _cc;
3435
const mach_header* _image_base;
3536
const char* _vmaddr_slide;
37+
const char* _eh_frame;
38+
size_t _eh_frame_size;
3639

3740
static const char* add(const void* base, uint64_t offset) {
3841
return (const char*)base + offset;
@@ -124,7 +127,8 @@ class MachOParser {
124127

125128
public:
126129
MachOParser(CodeCache* cc, const mach_header* image_base, const char* vmaddr_slide) :
127-
_cc(cc), _image_base(image_base), _vmaddr_slide(vmaddr_slide) {}
130+
_cc(cc), _image_base(image_base), _vmaddr_slide(vmaddr_slide),
131+
_eh_frame(NULL), _eh_frame_size(0) {}
128132

129133
bool parse() {
130134
if (_image_base->magic != MH_MAGIC_64) {
@@ -139,15 +143,17 @@ class MachOParser {
139143
const symtab_command* symtab = NULL;
140144
const dysymtab_command* dysymtab = NULL;
141145
const section_64* stubs_section = NULL;
142-
bool has_eh_frame = false;
143-
144146
for (uint32_t i = 0; i < header->ncmds; i++) {
145147
if (lc->cmd == LC_SEGMENT_64) {
146148
const segment_command_64* sc = (const segment_command_64*)lc;
147149
if (strcmp(sc->segname, "__TEXT") == 0) {
148150
_cc->updateBounds(_image_base, add(_image_base, sc->vmsize));
149151
stubs_section = findSection(sc, "__stubs");
150-
has_eh_frame = findSection(sc, "__eh_frame") != NULL;
152+
const section_64* eh_frame_section = findSection(sc, "__eh_frame");
153+
if (eh_frame_section != NULL) {
154+
_eh_frame = _vmaddr_slide + eh_frame_section->addr;
155+
_eh_frame_size = eh_frame_section->size;
156+
}
151157
} else if (strcmp(sc->segname, "__LINKEDIT") == 0) {
152158
link_base = _vmaddr_slide + sc->vmaddr - sc->fileoff;
153159
} else if (strcmp(sc->segname, "__DATA") == 0 || strcmp(sc->segname, "__DATA_CONST") == 0) {
@@ -171,11 +177,16 @@ class MachOParser {
171177
}
172178
}
173179

174-
// GCC emits __eh_frame (DWARF CFI); clang emits __unwind_info (compact unwind).
175-
// On aarch64, GCC and clang use different frame layouts, so detecting the
176-
// compiler matters. On x86_64 both use the same layout (no-op distinction).
177-
const FrameDesc& frame = has_eh_frame ? FrameDesc::default_frame : FrameDesc::fallback_default_frame();
178-
_cc->setDwarfTable(NULL, 0, frame);
180+
if (DWARF_SUPPORTED && _eh_frame != NULL && _eh_frame_size > 0) {
181+
DwarfParser dwarf(_cc->name(), _vmaddr_slide, _eh_frame, _eh_frame_size);
182+
_cc->setDwarfTable(dwarf.table(), dwarf.count(), dwarf.detectedDefaultFrame());
183+
} else {
184+
// No __eh_frame (clang compact-unwind-only libraries): fall back to the
185+
// library-wide default frame. On aarch64, clang uses a different frame
186+
// layout from GCC, so we must pass fallback_default_frame() rather than
187+
// letting CodeCache keep its constructor default of FrameDesc::default_frame.
188+
_cc->setDwarfTable(NULL, 0, FrameDesc::fallback_default_frame());
189+
}
179190

180191
return true;
181192
}
@@ -239,4 +250,8 @@ bool Symbols::isLibcOrPthreadAddress(uintptr_t pc) {
239250
return false;
240251
}
241252

253+
void Symbols::clearParsingCaches() {
254+
_parsed_libraries.clear();
255+
}
256+
242257
#endif // __APPLE__

0 commit comments

Comments
 (0)