Skip to content

Commit 2f622b0

Browse files
committed
PROF-14088
1 parent abe43f3 commit 2f622b0

4 files changed

Lines changed: 238 additions & 2 deletions

File tree

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

Lines changed: 74 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.
@@ -104,10 +105,28 @@ DwarfParser::DwarfParser(const char *name, const char *image_base,
104105

105106
_code_align = sizeof(instruction_t);
106107
_data_align = -(int)sizeof(void *);
108+
_has_z_augmentation = false;
107109

108110
parse(eh_frame_hdr);
109111
}
110112

113+
DwarfParser::DwarfParser(const char *name, const char *image_base,
114+
const char *eh_frame, size_t eh_frame_size) {
115+
_name = name;
116+
_image_base = image_base;
117+
118+
_capacity = 128;
119+
_count = 0;
120+
_table = (FrameDesc *)malloc(_capacity * sizeof(FrameDesc));
121+
_prev = NULL;
122+
123+
_code_align = sizeof(instruction_t);
124+
_data_align = -(int)sizeof(void *);
125+
_has_z_augmentation = false;
126+
127+
parseEhFrame(eh_frame, eh_frame_size);
128+
}
129+
111130
static constexpr u8 omit_sign_bit(u8 value) {
112131
// each signed flag = unsigned equivalent | 0x80
113132
return value & 0xf7;
@@ -143,6 +162,61 @@ void DwarfParser::parse(const char *eh_frame_hdr) {
143162
}
144163
}
145164

165+
// Parse raw .eh_frame (or __eh_frame on macOS) without a binary-search index.
166+
// Records are CIE/FDE sequences laid out linearly; terminated by a 4-byte zero.
167+
void DwarfParser::parseEhFrame(const char *eh_frame, size_t size) {
168+
const char *section_end = eh_frame + size;
169+
_ptr = eh_frame;
170+
171+
while (_ptr + 4 <= section_end) {
172+
const char *record_start = _ptr;
173+
u32 length = get32();
174+
if (length == 0) {
175+
break; // terminator
176+
}
177+
if (length == 0xffffffff) {
178+
break; // 64-bit DWARF not supported
179+
}
180+
181+
const char *record_end = record_start + 4 + length;
182+
if (record_end > section_end) {
183+
break;
184+
}
185+
186+
u32 cie_id = get32();
187+
188+
if (cie_id == 0) {
189+
// CIE: update code and data alignment factors.
190+
// Layout after cie_id: [1-byte version][augmentation string][code_align LEB][data_align SLEB]...
191+
_ptr++; // skip version
192+
_has_z_augmentation = (*_ptr == 'z');
193+
while (_ptr < record_end && *_ptr++) {
194+
} // skip null-terminated augmentation string
195+
_code_align = getLeb();
196+
_data_align = getSLeb();
197+
} else {
198+
// FDE: parse frame description for the covered PC range.
199+
// After cie_id: [pcrel-range-start 4 bytes][range-len 4 bytes][aug-data-len LEB][aug-data][instructions]
200+
// The augmentation data length field (and the data itself) is only present when the CIE
201+
// augmentation string starts with 'z'.
202+
u32 range_start = (u32)(getPtr() - _image_base);
203+
u32 range_len = get32();
204+
if (_has_z_augmentation) {
205+
_ptr += getLeb(); // skip augmentation data length + data
206+
}
207+
parseInstructions(range_start, record_end);
208+
addRecord(range_start + range_len, DW_REG_FP, LINKED_FRAME_SIZE,
209+
-LINKED_FRAME_SIZE, -LINKED_FRAME_SIZE + DW_STACK_SLOT);
210+
}
211+
212+
_ptr = record_end;
213+
}
214+
215+
if (_count > 1) {
216+
qsort(_table, _count, sizeof(FrameDesc), FrameDesc::comparator);
217+
}
218+
}
219+
146220
void DwarfParser::parseCie() {
147221
u32 cie_len = get32();
148222
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

@@ -104,6 +104,7 @@ class DwarfParser {
104104

105105
u32 _code_align;
106106
int _data_align;
107+
bool _has_z_augmentation;
107108

108109
const char* add(size_t size) {
109110
const char* ptr = _ptr;
@@ -167,6 +168,7 @@ class DwarfParser {
167168
}
168169

169170
void parse(const char* eh_frame_hdr);
171+
void parseEhFrame(const char* eh_frame, size_t size);
170172
void parseCie();
171173
void parseFde();
172174
void parseInstructions(u32 loc, const char* end);
@@ -177,6 +179,7 @@ class DwarfParser {
177179

178180
public:
179181
DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr);
182+
DwarfParser(const char* name, const char* image_base, const char* eh_frame, size_t eh_frame_size);
180183

181184
FrameDesc* table() const {
182185
return _table;

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

Lines changed: 20 additions & 1 deletion
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,6 +12,7 @@
1112
#include <mach-o/dyld.h>
1213
#include <mach-o/loader.h>
1314
#include <mach-o/nlist.h>
15+
#include "dwarf.h"
1416
#include "symbols.h"
1517
#include "log.h"
1618

@@ -32,6 +34,8 @@ class MachOParser {
3234
CodeCache* _cc;
3335
const mach_header* _image_base;
3436
const char* _vmaddr_slide;
37+
const char* _eh_frame;
38+
size_t _eh_frame_size;
3539

3640
static const char* add(const void* base, uint64_t offset) {
3741
return (const char*)base + offset;
@@ -123,7 +127,8 @@ class MachOParser {
123127

124128
public:
125129
MachOParser(CodeCache* cc, const mach_header* image_base, const char* vmaddr_slide) :
126-
_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) {}
127132

128133
bool parse() {
129134
if (_image_base->magic != MH_MAGIC_64) {
@@ -145,6 +150,11 @@ class MachOParser {
145150
if (strcmp(sc->segname, "__TEXT") == 0) {
146151
_cc->updateBounds(_image_base, add(_image_base, sc->vmsize));
147152
stubs_section = findSection(sc, "__stubs");
153+
const section_64* eh_frame_section = findSection(sc, "__eh_frame");
154+
if (eh_frame_section != NULL) {
155+
_eh_frame = _vmaddr_slide + eh_frame_section->addr;
156+
_eh_frame_size = eh_frame_section->size;
157+
}
148158
} else if (strcmp(sc->segname, "__LINKEDIT") == 0) {
149159
link_base = _vmaddr_slide + sc->vmaddr - sc->fileoff;
150160
} else if (strcmp(sc->segname, "__DATA") == 0 || strcmp(sc->segname, "__DATA_CONST") == 0) {
@@ -168,6 +178,11 @@ class MachOParser {
168178
}
169179
}
170180

181+
if (DWARF_SUPPORTED && _eh_frame != NULL && _eh_frame_size > 0) {
182+
DwarfParser dwarf(_cc->name(), _vmaddr_slide, _eh_frame, _eh_frame_size);
183+
_cc->setDwarfTable(dwarf.table(), dwarf.count());
184+
}
185+
171186
return true;
172187
}
173188
};
@@ -230,4 +245,8 @@ bool Symbols::isLibcOrPthreadAddress(uintptr_t pc) {
230245
return false;
231246
}
232247

248+
void Symbols::clearParsingCaches() {
249+
_parsed_libraries.clear();
250+
}
251+
233252
#endif // __APPLE__
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright 2026, Datadog, Inc.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include <gtest/gtest.h>
7+
8+
#include "dwarf.h"
9+
#include "../../main/cpp/gtest_crash_handler.h"
10+
11+
#include <cstdint>
12+
#include <cstring>
13+
#include <vector>
14+
15+
// Test name for crash handler
16+
static constexpr char DWARF_TEST_NAME[] = "DwarfTest";
17+
18+
class DwarfGlobalSetup {
19+
public:
20+
DwarfGlobalSetup() {
21+
installGtestCrashHandler<DWARF_TEST_NAME>();
22+
}
23+
~DwarfGlobalSetup() {
24+
restoreDefaultSignalHandlers();
25+
}
26+
};
27+
static DwarfGlobalSetup dwarf_global_setup;
28+
29+
#if DWARF_SUPPORTED
30+
31+
// Helpers to write little-endian integers into a byte buffer.
32+
static void put32(std::vector<uint8_t>& buf, uint32_t v) {
33+
buf.push_back(static_cast<uint8_t>(v));
34+
buf.push_back(static_cast<uint8_t>(v >> 8));
35+
buf.push_back(static_cast<uint8_t>(v >> 16));
36+
buf.push_back(static_cast<uint8_t>(v >> 24));
37+
}
38+
39+
static void put8(std::vector<uint8_t>& buf, uint8_t v) {
40+
buf.push_back(v);
41+
}
42+
43+
// Append a minimal CIE with "z" augmentation to buf.
44+
// Layout: [4-len=11][4-cie_id=0][1-ver=1][2-aug="z\0"][1-code_align=4][1-data_align=-8][1-ra=30][1-aug_data_len=0]
45+
// Total: 15 bytes.
46+
static void appendCie(std::vector<uint8_t>& buf) {
47+
// body size = cie_id(4) + version(1) + "z\0"(2) + code_align(1) + data_align(1) + ra_col(1) + aug_data_len(1) = 11
48+
put32(buf, 11); // length
49+
put32(buf, 0); // cie_id = 0
50+
put8(buf, 1); // version
51+
put8(buf, 'z'); // augmentation "z"
52+
put8(buf, 0); // null terminator
53+
put8(buf, 4); // code_align = 4 (LEB128)
54+
put8(buf, 0x78); // data_align = -8 (SLEB128: 0x78)
55+
put8(buf, 30); // return address column = 30 (lr)
56+
put8(buf, 0); // augmentation data length = 0 (LEB128)
57+
}
58+
59+
// Append an FDE referencing the CIE at cie_start_offset from the buf start.
60+
// cie_offset in the FDE = offset from FDE's cie_id field to the CIE start.
61+
// range_start is encoded as a 4-byte PC-relative signed integer (pcrel).
62+
// With pcrel=0 and image_base=&buf[0]: range_start = offset_of_pcrel_field_within_buf.
63+
// Layout: [4-len=13][4-cie_offset][4-pcrel=0][4-range_len][1-aug_data_len=0]
64+
// Total: 17 bytes.
65+
static void appendFde(std::vector<uint8_t>& buf, uint32_t cie_start_offset, uint32_t range_len) {
66+
// The FDE's cie_id field will be at buf.size() + 4 (after length field).
67+
uint32_t cie_id_field_offset = static_cast<uint32_t>(buf.size()) + 4;
68+
uint32_t cie_offset = cie_id_field_offset - cie_start_offset;
69+
70+
// body = cie_offset(4) + range_start(4) + range_len(4) + aug_data_len(1) = 13
71+
put32(buf, 13); // length
72+
put32(buf, cie_offset); // cie_offset from this field back to CIE start
73+
put32(buf, 0); // range_start pcrel = 0 (absolute value = field_address - image_base)
74+
put32(buf, range_len); // range_len
75+
put8(buf, 0); // aug data length = 0 (LEB128, for "z" augmentation)
76+
// no DWARF call frame instructions
77+
}
78+
79+
static void appendTerminator(std::vector<uint8_t>& buf) {
80+
put32(buf, 0);
81+
}
82+
83+
// Parse a raw __eh_frame section using the linear DwarfParser constructor.
84+
// image_base is set to buf.data() so that pcrel=0 yields range_start = field_offset_in_buf.
85+
static DwarfParser* parseBuf(const std::vector<uint8_t>& buf) {
86+
const char* base = reinterpret_cast<const char*>(buf.data());
87+
return new DwarfParser("test", base, base, buf.size());
88+
}
89+
90+
TEST(DwarfEhFrame, EmptySection) {
91+
std::vector<uint8_t> buf;
92+
DwarfParser* dwarf = parseBuf(buf);
93+
EXPECT_EQ(dwarf->count(), 0);
94+
delete dwarf;
95+
}
96+
97+
TEST(DwarfEhFrame, TerminatorOnly) {
98+
std::vector<uint8_t> buf;
99+
appendTerminator(buf);
100+
DwarfParser* dwarf = parseBuf(buf);
101+
EXPECT_EQ(dwarf->count(), 0);
102+
delete dwarf;
103+
}
104+
105+
TEST(DwarfEhFrame, CieOnly) {
106+
std::vector<uint8_t> buf;
107+
appendCie(buf);
108+
appendTerminator(buf);
109+
DwarfParser* dwarf = parseBuf(buf);
110+
// CIE alone generates no frame records.
111+
EXPECT_EQ(dwarf->count(), 0);
112+
delete dwarf;
113+
}
114+
115+
TEST(DwarfEhFrame, CieAndFde) {
116+
// CIE starts at offset 0.
117+
std::vector<uint8_t> buf;
118+
appendCie(buf); // 15 bytes
119+
appendFde(buf, 0, 256); // 17 bytes (cie_offset = 19)
120+
appendTerminator(buf); // 4 bytes
121+
ASSERT_EQ(buf.size(), static_cast<size_t>(36));
122+
123+
DwarfParser* dwarf = parseBuf(buf);
124+
// The FDE with no instructions generates two records:
125+
// one from parseInstructions (initial state at range_start) and one sentinel (at range_start + range_len).
126+
EXPECT_EQ(dwarf->count(), 2);
127+
128+
// Table must be in ascending loc order (sorted).
129+
const FrameDesc* table = dwarf->table();
130+
ASSERT_NE(table, nullptr);
131+
EXPECT_LT(table[0].loc, table[1].loc);
132+
133+
// Sentinel record covers the end of the FDE's range.
134+
// range_start = offset of pcrel field in buf = 15+4+4 = 23; range_end = 23+256 = 279.
135+
EXPECT_EQ(table[1].loc, static_cast<uint32_t>(279));
136+
137+
delete dwarf;
138+
}
139+
140+
#endif // DWARF_SUPPORTED

0 commit comments

Comments
 (0)