Hi! I have an instance that started to consistently crash with segfault or bus error and most of the generated coredumps had corrupted stacks. Some salvageable frames showed the error happening within ExecRunCompiledExpr. Sure enough, running the query with jit disabled stopped the crashes. The issue happens with the following setup:
Ubuntu jammy on arm64, 30G postgresql-14 14.12-1.pgdg22.04+1 libllvm15 1:15.0.7-0ubuntu0.22.04.3 I was able to isolate the impacted database the db (pg_dump of the table was not enough, a base backup had to be used) and reproduce the issue on a debug build of PostgresSQL. This time, there's no crash but it was stuck in an infinite loop within jit tuple deforming: #0 0x0000ec53660aa14c in deform_0_1 () #1 0x0000ec53660aa064 in evalexpr_0_0 () #2 0x0000ab8f9b322948 in ExecEvalExprSwitchContext (isNull=0xfffff47c3c87, econtext=0xab8fd0f13878, state=0xab8fd0f13c50) at executor/./build/../src/include/executor/executor.h:342 #3 ExecProject (projInfo=0xab8fd0f13c48) at executor/./build/../src/include/executor/executor.h:376 Looking at the generated assembly, the infinite loop happens between deform_0_1+140 and deform_0_1+188 // Store address page in x11 register 0xec53660aa130 <deform_0_1+132> adrp x11, 0xec53fd308000 // Start of the infinite loop 0xec53660aa138 <deform_0_1+140> adr x8, 0xec53660aa138 <deform_0_1+140> // Load the content of 0xec53fd308000[x12] in x10, x12 was 0 at that time 0xec53660aa13c <deform_0_1+144> ldrsw x10, [x11, x12, lsl #2] // Add the loaded offset to x8 0xec53660aa140 <deform_0_1+148> add x8, x8, x10 ... // Branch to address in x8. Since x10 was 0, x8 has the value deform_0_1+140, creating the infinite loop 0xec53660aa168 <deform_0_1+188> br x8 Looking at the content of 0xec53fd308000, We only see 0 values stored at the address. x/6 0xec53fd308000 0xec53fd308000: 0x00000000 0x00000000 0x00000000 0x00000000 0xec53fd308010: 0x00000000 0x00000000 The assembly matches the code for the find_start switch case in llvmjit_deform[1]. The content at the address 0xec53fd308000 should contain the offset table from the PC to branch to the correct attcheckattnoblocks block. As a comparison, if I execute a query not impacted by the issue (the size of the jit compiled module seems to be a factor), I can see that the offset table was correctly filled. x/6 0xec55fd30700 0xec55fd307000: 0x00000060 0x00000098 0x000000e8 0x00000170 0xec55fd307010: 0x0000022c 0x000002e8 I was suspecting something was erasing the content of the offset table so I've checked with rr. However, it was only initialized and nothing was written at this memory address. I was starting to suspect a possible LLVM issue and ran the query against a debug build of llvm_jit. It immediately triggered the following assertion[2]: void llvm::RuntimeDyldELF::resolveAArch64Relocation(const llvm::SectionEntry &, uint64_t, uint64_t, uint32_t, int64_t): Assertion `isInt<33>(Result) && "overflow check failed for relocation"' failed. This happens when LLVM is resolving relocations. #5 __GI___assert_fail (assertion=0xf693f214771a "isInt<33>(Result) && \"overflow check failed for relocation\"", file=0xf693f2147269 "/var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp", line=507, function=0xf693f214754f "void llvm::RuntimeDyldELF::resolveAArch64Relocation(const llvm::SectionEntry &, uint64_t, uint64_t, uint32_t, int64_t)") at ./assert/assert.c:101 #6 llvm::RuntimeDyldELF::resolveAArch64Relocation () at /var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp:507 #7 llvm::RuntimeDyldELF::resolveRelocation () at /var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp:1044 #8 llvm::RuntimeDyldELF::resolveRelocation () at /var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp:1026 #9 llvm::RuntimeDyldImpl::resolveRelocationList () at /var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp:1112 #10 llvm::RuntimeDyldImpl::resolveLocalRelocations () at /var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp:157 #11 llvm::RuntimeDyldImpl::finalizeAsync() at /var/lib/postgresql/llvm-project/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyld.cpp:1247 During the assertion failure, I have the following values: Value: 0xfbc84fab9000 FinalAddress: 0xfbc5b9cea12c Addend: 0x0 Result: 0x295dcf000 The result is indeed greater than an int32, triggering the assert. Looking at the sections created by LLVM in allocateSection[3], we have 3 sections created: .text {Address = 0xfbc5b9cea000, AllocatedSize = 90112} .rodata {Address = 0xfbc84fab9000, AllocatedSize = 4096} .eh_frame {Address = 0xfbc84fab7000, AllocatedSize = 8192} When resolving relocation, the difference between the rodata section and the PC is computed and stored in the ADRP instruction. However, when a new section is allocated, LLVM will request a new memory block from the memory allocator[4]. The MemGroup.Near is passed as the start hint of mmap but that's only a hint and the kernel doesn't provide any guarantee that the new allocated block will be near. With the impacted query, there are more than 10GB of gap between the .text section and the .rodata section, making it impossible for the code in the .text section to correctly fetch data from the .rodata section as the address in ADRP is limited to a +/-4GB range. There are mentions about this in the ABI that the GOT section should be within 4GB from the text section[5]. Though in this case, there's no GOT section as the offsets are stored in the .rodata section but the constraint is going to be similar. This is a known LLVM issue[6] that impacted Impala, Numba and Julia. There's an open PR[7] to fix the issue by allocating all sections as a single memory block, avoiding the gaps between sections. There's also a related discussion on this on llvm-rtdyld discourse[8]. A possible mitigation is to switch from RuntimeDyld to JITLinking but this requires at least LLVM15 as LLVM14 doesn't have any significant relocation support for aarch64[9]. I did test using JITLinking on my impacted db and it seems to fix the issue. JITLinking has no exposed C interface though so it requires additional wrapping. I don't necessarily have a good answer for this issue. I've tried to tweak relocation settings or the jit code to avoid relocation without too much success. Ideally, the llvm fix will be merged and backported in llvm but the PR has been open for some time now. I've seen multiple segfault reports that look similar to this issue (example: [10], [11]) but I don't think it was linked to the LLVM bug so I figured I would at least share my findings. [1] https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/jit/llvm/llvmjit_deform.c#L364-L382 [2] https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldELF.cpp#L501-L513 [3] https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/SectionMemoryManager.cpp#L41C32-L41C47 [4] https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/SectionMemoryManager.cpp#L94-L110 [5] https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst#7code-models [6] https://github.com/llvm/llvm-project/issues/71963 [7] https://github.com/llvm/llvm-project/pull/71968 [8] https://discourse.llvm.org/t/llvm-rtdyld-aarch64-abi-relocation-restrictions/74616 [9] https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp#L75-L84 [10] https://www.postgresql.org/message-id/flat/CABa%2BnRvwZy_5t1QF9NJNGwAf03tv_PO_Sg1FsN1%2B-3Odb1XgBA%40mail.gmail.com [11] https://www.postgresql.org/message-id/flat/CADAf1kavcN-kY%3DvEm3MYxhUa%2BrtGFs7tym5d7Ee6Ni2cwwxGqQ%40mail.gmail.com Regards, Anthonin Bonnefoy