Skip to content

Commit 215df8f

Browse files
obdevfootkaLINxiansheng
authored andcommitted
fix PL/SQL exception handler not catching errors on macOS ARM64
Co-authored-by: footka <672528926@qq.com> Co-authored-by: LINxiansheng <wangzelin19961202@gmail.com>
1 parent 33e0c3d commit 215df8f

3 files changed

Lines changed: 252 additions & 11 deletions

File tree

src/objit/src/core/ob_jit_memory_manager.h

Lines changed: 188 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,204 @@ class ObJitMemoryManager : public llvm::RTDyldMemoryManager
3333
void operator=(const ObJitMemoryManager&);
3434
public:
3535
explicit ObJitMemoryManager(ObJitAllocator &allocator)
36-
: allocator_(allocator)
36+
: allocator_(allocator),
37+
code_section_addr_(nullptr),
38+
gcc_except_tab_addr_(nullptr),
39+
gcc_except_tab_size_(0)
3740
{}
3841
virtual ~ObJitMemoryManager() {}
3942

40-
/// Allocate a memory block of (at least) the given size suitable for
41-
/// executable code. The SectionID is a unique identifier assigned by the JIT
42-
/// engine, and optionally recorded by the memory manager to access a loaded
43-
/// section.
4443
virtual uint8_t *allocateCodeSection(
4544
uintptr_t Size, unsigned Alignment, unsigned SectionID,
4645
llvm::StringRef SectionName)
4746
{
48-
return reinterpret_cast<uint8_t*>(allocator_.alloc(JMT_RWE, Size, Alignment));
47+
uint8_t *ptr = reinterpret_cast<uint8_t*>(allocator_.alloc(JMT_RWE, Size, Alignment));
48+
#if defined(__APPLE__)
49+
// Track __text section address for .eh_frame pc_begin fixup.
50+
if (SectionName == "__text") {
51+
code_section_addr_ = ptr;
52+
}
53+
#endif
54+
return ptr;
4955
}
5056

51-
/// Allocate a memory block of (at least) the given size suitable for data.
52-
/// The SectionID is a unique identifier assigned by the JIT engine, and
53-
/// optionally recorded by the memory manager to access a loaded section.
5457
virtual uint8_t *allocateDataSection(
5558
uintptr_t Size, unsigned Alignment, unsigned SectionID,
5659
llvm::StringRef SectionName, bool IsReadOnly){
57-
return reinterpret_cast<uint8_t*>(allocator_.alloc(JMT_RO, Size, Alignment));
60+
uint8_t *ptr = reinterpret_cast<uint8_t*>(allocator_.alloc(JMT_RO, Size, Alignment));
61+
#if defined(__APPLE__)
62+
// Track __gcc_except_tab section address for .eh_frame LSDA fixup.
63+
// On macOS ARM64, RuntimeDyld doesn't properly relocate the LSDA pointer
64+
// in .eh_frame (ARM64_RELOC_SUBTRACTOR+UNSIGNED pair), so we fix it up
65+
// manually in registerEHFrames.
66+
if (SectionName == "__gcc_except_tab") {
67+
gcc_except_tab_addr_ = ptr;
68+
gcc_except_tab_size_ = Size;
69+
}
70+
#endif
71+
return ptr;
72+
}
73+
74+
virtual void registerEHFrames(uint8_t *Addr, uint64_t LoadAddr, size_t Size) {
75+
#if defined(__APPLE__)
76+
// Fix up LSDA pointers in .eh_frame before registration.
77+
// RuntimeDyld on MachO ARM64 does not correctly apply the
78+
// ARM64_RELOC_SUBTRACTOR + ARM64_RELOC_UNSIGNED relocation pair
79+
// within __eh_frame, leaving the LSDA pointer unrelocated and pointing
80+
// to garbage memory. We parse the CIE/FDE and patch the LSDA pointer
81+
// to correctly reference __gcc_except_tab.
82+
if (Size > 0) {
83+
fixupEHFrameRelocations(Addr, Size);
84+
}
85+
#endif
86+
llvm::RTDyldMemoryManager::registerEHFrames(Addr, LoadAddr, Size);
87+
// Reset for next module
88+
code_section_addr_ = nullptr;
89+
gcc_except_tab_addr_ = nullptr;
90+
gcc_except_tab_size_ = 0;
91+
}
92+
93+
virtual void deregisterEHFrames() {
94+
llvm::RTDyldMemoryManager::deregisterEHFrames();
95+
}
96+
97+
private:
98+
#if defined(__APPLE__)
99+
// Read a ULEB128 value, advancing the pointer
100+
static uint64_t readULEB128(const uint8_t **p) {
101+
uint64_t result = 0;
102+
unsigned shift = 0;
103+
uint8_t byte;
104+
do {
105+
byte = **p; (*p)++;
106+
result |= ((uint64_t)(byte & 0x7f)) << shift;
107+
shift += 7;
108+
} while (byte & 0x80);
109+
return result;
110+
}
111+
112+
// Get byte size of an encoded pointer given the DWARF encoding
113+
static size_t getEncodedPtrSize(uint8_t enc) {
114+
if (enc == 0xFF) return 0; // DW_EH_PE_omit
115+
switch (enc & 0x0F) {
116+
case 0x00: return sizeof(uintptr_t); // DW_EH_PE_absptr
117+
case 0x02: return 2; // DW_EH_PE_udata2
118+
case 0x03: return 4; // DW_EH_PE_udata4
119+
case 0x04: return 8; // DW_EH_PE_udata8
120+
case 0x09: return 2; // DW_EH_PE_sdata2
121+
case 0x0A: return 4; // DW_EH_PE_sdata4 (note: some refs use 0x0B)
122+
case 0x0B: return 4; // DW_EH_PE_sdata4
123+
case 0x0C: return 8; // DW_EH_PE_sdata8
124+
default: return sizeof(uintptr_t);
125+
}
126+
}
127+
128+
// Fix up pc_begin and LSDA pointers in .eh_frame FDEs.
129+
// RuntimeDyld on MachO ARM64 does not correctly apply
130+
// ARM64_RELOC_SUBTRACTOR + ARM64_RELOC_UNSIGNED pairs in __eh_frame,
131+
// so both pc_begin (function start) and LSDA pointer end up wrong.
132+
void fixupEHFrameRelocations(uint8_t *ehFrame, size_t size) {
133+
const uint8_t *p = ehFrame;
134+
const uint8_t *end = ehFrame + size;
135+
136+
// --- Parse CIE ---
137+
if (p + 4 > end) return;
138+
uint32_t cie_length = *(const uint32_t *)p; p += 4;
139+
if (cie_length == 0 || p + cie_length > end) return;
140+
const uint8_t *cie_end = p + cie_length;
141+
142+
if (p + 4 > cie_end) return;
143+
uint32_t cie_id = *(const uint32_t *)p; p += 4;
144+
if (cie_id != 0) return; // not a CIE
145+
146+
if (p >= cie_end) return;
147+
uint8_t version = *p++;
148+
149+
const char *aug_str = (const char *)p;
150+
while (p < cie_end && *p) p++;
151+
if (p >= cie_end) return;
152+
p++; // skip null terminator
153+
154+
bool has_z = false, has_L = false, has_P = false, has_R = false;
155+
for (const char *a = aug_str; *a; a++) {
156+
switch (*a) {
157+
case 'z': has_z = true; break;
158+
case 'L': has_L = true; break;
159+
case 'P': has_P = true; break;
160+
case 'R': has_R = true; break;
161+
}
162+
}
163+
164+
readULEB128(&p); // code_alignment_factor
165+
{ uint8_t b; do { b = *p++; } while (b & 0x80); } // data_alignment_factor (SLEB128)
166+
if (version >= 3) { readULEB128(&p); } else { p++; } // return_address_register
167+
168+
uint8_t lsda_encoding = 0xFF;
169+
uint8_t fde_encoding = 0x00;
170+
171+
if (has_z) {
172+
readULEB128(&p); // augmentation_data_length
173+
for (const char *a = aug_str; *a; a++) {
174+
if (*a == 'z') continue;
175+
if (*a == 'P') {
176+
uint8_t penc = *p++;
177+
p += getEncodedPtrSize(penc);
178+
} else if (*a == 'L') {
179+
lsda_encoding = *p++;
180+
} else if (*a == 'R') {
181+
fde_encoding = *p++;
182+
}
183+
}
184+
}
185+
186+
// --- Parse FDE(s) ---
187+
p = cie_end;
188+
while (p + 4 <= end) {
189+
uint32_t fde_length = *(const uint32_t *)p; p += 4;
190+
if (fde_length == 0) break;
191+
if (p + fde_length > end) break;
192+
const uint8_t *fde_end = p + fde_length;
193+
194+
uint32_t cie_ptr = *(const uint32_t *)p; p += 4;
195+
if (cie_ptr == 0) { p = fde_end; continue; } // another CIE
196+
197+
// --- Fix pc_begin ---
198+
size_t fde_ptr_sz = getEncodedPtrSize(fde_encoding);
199+
bool fde_pcrel = ((fde_encoding & 0x70) == 0x10);
200+
if (code_section_addr_ != nullptr && fde_pcrel) {
201+
uint8_t *pc_begin_field = const_cast<uint8_t *>(p);
202+
if (fde_ptr_sz == 8) {
203+
int64_t correct = (int64_t)code_section_addr_ - (int64_t)pc_begin_field;
204+
*(int64_t *)pc_begin_field = correct;
205+
} else if (fde_ptr_sz == 4) {
206+
int32_t correct = (int32_t)((int64_t)code_section_addr_ - (int64_t)pc_begin_field);
207+
*(int32_t *)pc_begin_field = correct;
208+
}
209+
}
210+
p += fde_ptr_sz; // skip pc_begin
211+
p += fde_ptr_sz; // skip pc_range (not relocated, leave as-is)
212+
213+
// --- Fix LSDA pointer ---
214+
if (has_z) {
215+
readULEB128(&p); // augmentation_data_length
216+
if (has_L && lsda_encoding != 0xFF && gcc_except_tab_addr_ != nullptr) {
217+
uint8_t *lsda_field = const_cast<uint8_t *>(p);
218+
size_t lsda_ptr_sz = getEncodedPtrSize(lsda_encoding);
219+
bool lsda_pcrel = ((lsda_encoding & 0x70) == 0x10);
220+
221+
if (lsda_pcrel && lsda_ptr_sz == 8) {
222+
int64_t correct = (int64_t)gcc_except_tab_addr_ - (int64_t)lsda_field;
223+
*(int64_t *)lsda_field = correct;
224+
} else if (lsda_pcrel && lsda_ptr_sz == 4) {
225+
int32_t correct = (int32_t)((int64_t)gcc_except_tab_addr_ - (int64_t)lsda_field);
226+
*(int32_t *)lsda_field = correct;
227+
}
228+
}
229+
}
230+
p = fde_end;
231+
}
58232
}
233+
#endif
59234

60235
/// This method is called when object loading is complete and section page
61236
/// permissions can be applied. It is up to the memory manager implementation
@@ -99,6 +274,9 @@ class ObJitMemoryManager : public llvm::RTDyldMemoryManager
99274

100275
private:
101276
ObJitAllocator &allocator_;
277+
uint8_t *code_section_addr_;
278+
uint8_t *gcc_except_tab_addr_;
279+
size_t gcc_except_tab_size_;
102280
};
103281

104282
} // core

src/objit/src/core/ob_orc_jit.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ int ObOrcJit::init()
6262
return std::make_unique<ObJitMemoryManager>(JITAllocator);
6363
});
6464

65+
#if defined(__APPLE__) && defined(__aarch64__)
66+
// Process all sections (including .eh_frame / __eh_frame) even if they
67+
// don't contain symbols. Without this, the .eh_frame section is skipped
68+
// by RuntimeDyld, EH frames are not registered with the system via
69+
// __register_frame, and PL exception handlers will not work because
70+
// the unwinder cannot find the personality function or LSDA data for
71+
// JIT-compiled PL code.
72+
ObjLinkingLayer->setProcessAllSections(true);
73+
#endif
6574
ObjLinkingLayer->registerJITEventListener(NotifyLoaded);
6675
return ObjLinkingLayer;
6776
});
@@ -85,6 +94,16 @@ int ObOrcJit::init()
8594
ret = OB_ERR_UNEXPECTED;
8695
LOG_WARN("failed to get target machine", K(msg.c_str()));
8796
} else {
97+
#if defined(__APPLE__) && defined(__aarch64__)
98+
// Force DwarfCFI exception handling model to ensure .eh_frame and LSDA
99+
// (Language-Specific Data Area) are generated by the JIT code generator.
100+
// Without this, on macOS ARM64 the default ExceptionModel is None, which
101+
// causes compact unwind to be used instead. Compact unwind cannot carry
102+
// LSDA data, so the PL personality function (ObPLEH::eh_personality)
103+
// cannot find exception handlers, causing PL DECLARE HANDLER to fail.
104+
tm_builder_wrapper->getOptions().ExceptionModel = ExceptionHandling::DwarfCFI;
105+
tm_builder_wrapper->getOptions().MCOptions.EmitDwarfUnwind = EmitDwarfUnwindType::Always;
106+
#endif
88107
ObEngineBuilder.setJITTargetMachineBuilder(*tm_builder_wrapper);
89108
}
90109

src/pl/ob_pl_exception_handling.cpp

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,45 @@ namespace pl
2626
{
2727

2828
_RLOCAL(_Unwind_Exception*, tl_eptr);
29+
30+
#if defined(__APPLE__) && defined(__aarch64__)
31+
// Workaround for Apple libunwind crash in _Unwind_SetIP on macOS ARM64.
32+
// Apple's unw_set_reg for UNW_REG_IP tries to re-lookup unwind info (compact
33+
// unwind) for the new IP, but JIT frames registered via __register_frame only
34+
// have DWARF unwind info. The lookup returns NULL, causing a crash.
35+
// We bypass _Unwind_SetIP by locating the PC register in the cursor's
36+
// Registers_arm64 layout and writing directly.
37+
//
38+
// Registers_arm64 layout:
39+
// x[0..28] (29 regs * 8 = 232), fp(x29), lr(x30), sp, pc
40+
// PC is at offset 32*8 = 256 bytes from x0.
41+
static void safe_Unwind_SetIP(struct _Unwind_Context *context, uintptr_t new_ip) {
42+
// Place a unique magic value in the eh_return_data register (x0) to find it
43+
const uintptr_t magic = 0xDEAD1234CAFE5678ULL;
44+
uintptr_t saved = _Unwind_GetGR(context, __builtin_eh_return_data_regno(0));
45+
_Unwind_SetGR(context, __builtin_eh_return_data_regno(0), magic);
46+
47+
uint8_t *ctx = (uint8_t *)context;
48+
bool found = false;
49+
// Scan first 2KB for the magic value (register state is near the front)
50+
for (size_t i = 0; i + 256 + 8 <= 2048; i += sizeof(uintptr_t)) {
51+
if (*(uintptr_t *)(ctx + i) == magic) {
52+
// Found x0 at offset i. PC is 256 bytes later.
53+
*(uintptr_t *)(ctx + i + 256) = new_ip;
54+
found = true;
55+
break;
56+
}
57+
}
58+
59+
// Restore x0 to the saved value
60+
_Unwind_SetGR(context, __builtin_eh_return_data_regno(0), saved);
61+
62+
if (!found) {
63+
// Fallback to standard API (may crash — shouldn't reach here)
64+
_Unwind_SetIP(context, new_ip);
65+
}
66+
}
67+
#endif
2968
ObPLException pre_reserved_e(OB_ALLOCATE_MEMORY_FAILED); // reserved exception space to prevent exceptions from not being thrown when there is no memory
3069

3170
void ObPLEH::eh_debug_int64(const char *name_ptr, int64_t name_len, int64_t object)
@@ -571,7 +610,11 @@ _Unwind_Reason_Code ObPLEH::handleLsda(int version,
571610
}
572611

573612
// To execute landing pad set here
613+
#if defined(__APPLE__) && defined(__aarch64__)
614+
safe_Unwind_SetIP(context, funcStart + landingPad);
615+
#else
574616
_Unwind_SetIP(context, funcStart + landingPad);
617+
#endif
575618
ret = _URC_INSTALL_CONTEXT;
576619
} else if (exceptionMatched) {
577620
ret = _URC_HANDLER_FOUND;
@@ -595,8 +638,9 @@ _Unwind_Reason_Code ObPLEH::eh_personality(int version, _Unwind_Action actions,
595638
struct _Unwind_Context *context)
596639
{
597640
const uint8_t *lsda = reinterpret_cast<const uint8_t *>(_Unwind_GetLanguageSpecificData(context));
641+
_Unwind_Reason_Code ret = handleLsda(version, lsda, actions, exceptionClass, exceptionObject, context);
598642
LOG_DEBUG(">>>>>>>>>>0", K(version), K(actions), K(exceptionClass), K(lsda));
599-
return handleLsda(version, lsda, actions, exceptionClass, exceptionObject, context);
643+
return ret;
600644
}
601645

602646
}

0 commit comments

Comments
 (0)