Repository: bitcoin/bitcoin (language: cpp)

Issue:
test: 32-bit Clang `ipc_test` failure at `-O0`
Noticed as part of this branch #29796, however it can also be reproduced with master (8fa10edcd1706a1f0dc9d8c3adbc8efa3c7755bf) by reproducing the equivalent CI & setting C(XX)FLAGS to -O0:
```bash
make -C depends/ MULTIPROCESS=1 NO_QT=1 NO_WALLET=1 NO_ZMQ=1 NO_USDT=1 CFLAGS="-O0" CXXFLAGS="-O0" DEBUG=1 -j19 HOST=i686-pc-linux-gnu
cmake -B build --toolchain /root/ci_scratch/depends/i686-pc-linux-gnu/toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER='clang;-m32' -DCMAKE_CXX_COMPILER='clang++;-m32'
cmake --build build -j18
```

```bash
./build/src/test/test_bitcoin --run_test=ipc*
Running 2 test cases...
terminate called after throwing an instance of 'kj::ExceptionImpl'
  what():  /root/ci_scratch/depends/i686-pc-linux-gnu/include/kj/common.h:1797: failed: expected start <= end && end <= size_; Out-of-bounds ArrayPtr::slice().
stack: 5ca0dd6d 5c78b5db 5c8cc599 5ca0adc9 5c94fb37 5c950070 5c9cd709 5c9cf58a 5c9d72fd 5c9d025e 5c776f11 5bf6351d 5bf6347d 5bf633cd 5bf6337b 5bf6331d 5bf63194 f7afa4b0 f773dff6 f77d55b7
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
    ??:0: returning here
unknown location(0): fatal error: in "ipc_tests/ipc_tests": signal: SIGABRT (application abort requested)
test/ipc_tests.cpp(12): last checkpoint: "ipc_tests" test entry
test_bitcoin: common/args.cpp:578: void ArgsManager::AddArg(const std::string &, const std::string &, unsigned int, const OptionsCategory &): Assertion `ret.second' failed.
unknown location(0): fatal error: in "ipc_tests/parse_address_test": signal: SIGABRT (application abort requested)
test/ipc_tests.cpp(20): last checkpoint: "parse_address_test" fixture ctor

*** 2 failures are detected in the test module "Bitcoin Core Test Suite"
```


Discussion / hints:
cc @ryanofsky 
I tried those steps except I left off the ` -DCMAKE_C_COMPILER='clang;-m32' -DCMAKE_CXX_COMPILER='clang++;-m32'` part and the test passed without a problem.

Is the clang part important? It seems odd to use gcc for the depends build and clang for the bitcoin build, and when when I tried the adding the clang arguments specified, cmake complained about not being able to find libstdc++, which maybe makes sense because, I don't know if it is supposed to work with the gnu library.

If there are any easier steps to reproduce this maybe using docker, or just a CI run I can look at that would be helpful. I ran into countless problems just getting i686 default build to work at all on my machine, so it is hard to know what things may be particular to the configuration this is happening in.
> If there are any easier steps to reproduce this maybe using docker,

Running `time env -i HOME="$HOME" PATH="$PATH" USER="$USER" bash -c 'FILE_ENV="./ci/test/00_setup_env_i686_multiprocess.sh" ./ci/test_run_all.sh'` with the branch from #29796.

> or just a CI run I can look at that would be helpful.

https://github.com/bitcoin/bitcoin/pull/29796/checks?check_run_id=36485685971
Thanks was able to get a stack trace by running `cd /ci_container_base/ci/scratch/build-i686-pc-linux-gnu`, `gdb -ex run --args ./src/test/test_bitcoin -t ipc_tests`, and `bt` in container:

```c++
#0  0xf7f4b5b9 in __kernel_vsyscall ()
#1  0xf79e6037 in ?? () from /lib32/libc.so.6
#2  0xf7994c51 in raise () from /lib32/libc.so.6
#3  0xf797c2b7 in abort () from /lib32/libc.so.6
#4  0xf7d4bf71 in ?? () from /lib32/libstdc++.so.6
#5  0xf7d65a97 in ?? () from /lib32/libstdc++.so.6
#6  0xf7d4b8f9 in std::terminate() () from /lib32/libstdc++.so.6
#7  0xf7d65ddc in __cxa_throw () from /lib32/libstdc++.so.6
#8  0x597d7e12 in kj::ExceptionCallback::RootExceptionCallback::onFatalException (this=0x5c8344b0, exception=...) at /usr/src/kj/exception.c++:1107
#9  0x597d6085 in kj::throwFatalException (exception=..., ignoreCount=1) at /usr/src/kj/exception.c++:1194
#10 0x597d05e5 in kj::_::Debug::Fault::fatal (this=0xf75cbc28) at /usr/src/kj/debug.c++:371
#11 0x597cf19a in kj::_::inlineRequireFailure (file=0x59e9a314 "/ci_container_base/depends/i686-pc-linux-gnu/include/kj/common.h", line=1797, expectation=0x59e9a2f7 "start <= end && end <= size_", 
    macroArgs=0x59e9a2d4 "\"Out-of-bounds ArrayPtr::slice().\"", message=0x59e9a2b0 "Out-of-bounds ArrayPtr::slice().") at /usr/src/kj/common.c++:36
#12 0x5954afc8 in kj::ArrayPtr<char const>::slice (this=0xf75cbd54, start=1498659230, end=13) at /usr/src/kj/common.h:1797
#13 0x5968bd88 in kj::StringPtr::slice (this=0xf75cbd54, start=1498659230) at /usr/src/kj/string.h:734
#14 0x597cc1e4 in kj::CidrRange::CidrRange (this=0x5a9ddc80 <kj::_::reservedCidrs()::result>, pattern=...) at /usr/src/kj/cidr.c++:53
#15 0x597105fe in kj::_::reservedCidrs () at /usr/src/kj/async-io.c++:3007
#16 0x59710b37 in kj::_::NetworkFilter::NetworkFilter (this=0x5c838b70) at /usr/src/kj/async-io.c++:3038
#17 0x5978e80c in kj::(anonymous namespace)::SocketNetwork::SocketNetwork (this=0x5c838b68, lowLevel=...) at /usr/src/kj/async-io-unix.c++:1742
#18 0x5979068d in kj::(anonymous namespace)::AsyncIoProviderImpl::AsyncIoProviderImpl (this=0x5c838b60, lowLevel=...) at /usr/src/kj/async-io-unix.c++:1974
#19 0x59798404 in kj::heap<kj::(anonymous namespace)::AsyncIoProviderImpl, kj::(anonymous namespace)::LowLevelAsyncIoProviderImpl&> () at /usr/src/kj/memory.h:609
#20 0x59791361 in kj::setupAsyncIo () at /usr/src/kj/async-io-unix.c++:2058
#21 0x595373c2 in mp::EventLoop::EventLoop(char const*, std::function<void (bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)>, void*) (this=0xf75cc110, 
    exe_name=0x59b257f6 "IpcPipeTest", log_fn=..., context=0x0) at /usr/src/mp/proxy.cpp:158
#22 0x58a4064e in IpcPipeTest()::$_0::operator()() const (this=0x5c8542d4) at ./test/ipc_test.cpp:60
#23 0x58a405ae in std::__invoke_impl<void, IpcPipeTest()::$_0>(std::__invoke_other, IpcPipeTest()::$_0&&) (__f=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/invoke.h:61
#24 0x58a404fe in std::__invoke<IpcPipeTest()::$_0>(IpcPipeTest()::$_0&&) (__fn=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/invoke.h:96
#25 0x58a404ac in std::thread::_Invoker<std::tuple<IpcPipeTest()::$_0> >::_M_invoke<0u>(std::_Index_tuple<0u>) (this=0x5c8542d4)
    at /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:292
#26 0x58a4044e in std::thread::_Invoker<std::tuple<IpcPipeTest()::$_0> >::operator()() (this=0x5c8542d4) at /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:299
#27 0x58a402c5 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<IpcPipeTest()::$_0> > >::_M_run() (this=0x5c8542d0)
    at /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:244
#28 0xf7d98d21 in ?? () from /lib32/libstdc++.so.6
#29 0xf79e4157 in ?? () from /lib32/libc.so.6
#30 0xf7a78e08 in ?? () from /lib32/libc.so.6
```
Stack trace seems to be showing something going wrong in capnproto code, but unclear what the cause is. The crash is happening in the CidrRange::CidrRange constructor here:

https://github.com/capnproto/capnproto/blob/b34ec28cceaf15b1082b74b50f03f770873c3636/c%2B%2B/src/kj/cidr.c%2B%2B#L53

which is being called on a static list of address patterns:

https://github.com/capnproto/capnproto/blob/b34ec28cceaf15b1082b74b50f03f770873c3636/c%2B%2B/src/kj/async-io.c%2B%2B#L2999-L3007

All of the patterns look valid so no reason there should be a parsing exception like seems to be happening. It seems like this might be a more strange compiler / build issue
gdb shows invalid results being returned from `pattern.findFirst('/')` call where pattern is `192.0.0.0/24`

https://github.com/capnproto/capnproto/blob/b34ec28cceaf15b1082b74b50f03f770873c3636/c%2B%2B/src/kj/cidr.c%2B%2B#L51C48-L51C57

```
#14 0x5971f1e4 in kj::CidrRange::CidrRange (this=0x5a930c80 <kj::_::reservedCidrs()::result>, pattern=...) at /usr/src/kj/cidr.c++:53
(gdb) p pattern
$6 = {content = {<kj::DisallowConstCopyIfNotConst<char const>> = {<No data fields>}, ptr = 0x59e13a45 "192.0.0.0/24", size_ = 13}}
(gdb) p slashPos
$7 = 1497950621
(gdb) p pattern.findFirst('/')
$8 = {ptr = {isSet = true, {value = 2155537998}}}
```
I thought I tracked down the problem, and some change I made to the test caused it to pass, but rerunning the the fix in a new container, it no longer works, so I have to go back.

Here were notes I was about to post about potential problem & fix. In any case I suspect problem would be avoided if we just consistently used gcc or clang for this build and didn't try to mix them.

---

Test failure seems to be caused by the container using incompatible ABI versions for the depends build and the main build. The depends build is using gcc with ABI version 18:

```bash
gcc -E -x c++ - -dM <<< "" | grep ABI
#define __GXX_ABI_VERSION 1018
```

The bitcoin build is using clang with ABI version 2:

```bash
clang++ -E -x c++ - -dM <<< "" | grep ABI
#define __GXX_ABI_VERSION 1002
```

In clang, the ABI version is determined when the compiler is built, but in gcc you can control it with the `-fabi-version` option, so the following change seems to fix the build:

```diff
--- a/depends/hosts/linux.mk
+++ b/depends/hosts/linux.mk
@@ -1,5 +1,5 @@
 linux_CFLAGS=-pipe -std=$(C_STANDARD)
-linux_CXXFLAGS=-pipe -std=$(CXX_STANDARD)
+linux_CXXFLAGS=-pipe -std=$(CXX_STANDARD) -fabi-version=2
 
 ifneq ($(LTO),)
 linux_AR = $(host_toolchain)gcc-ar
--- a/depends/packages/libmultiprocess.mk
+++ b/depends/packages/libmultiprocess.mk
@@ -27,3 +27,5 @@ endef
 define $(package)_stage_cmds
   $(MAKE) DESTDIR=$($(package)_staging_dir) install-lib
 endef
+
+$(package)_cxxflags += -fabi-version=11

```

First part of the diff setting ` -fabi-version=2` in the depends build is the main fix. The second part of the diff setting ` -fabi-version=11` is a workaround for an issue that happened specifically with the multiprocess package. Because when `-fabi-version` is set to 10 or below there is a compile error:


<details><summary>error: no matching function for call to ‘std::__uniq_ptr_data</summary>
<p>

```c++
[ 20%] Building CXX object CMakeFiles/mputil.dir/src/mp/util.cpp.o
In file included from /usr/include/c++/13/bits/shared_ptr_base.h:59,
                 from /usr/include/c++/13/bits/shared_ptr.h:53,
                 from /usr/include/c++/13/condition_variable:45,
                 from /usr/include/c++/13/future:41,
                 from /ci_container_base/depends/work/build/i686-pc-linux-gnu/libmultiprocess/07c917f7ca910d66abc6d3873162fc9061704074-722fc6d9234/include/mp/util.h:11,
                 from /ci_container_base/depends/work/build/i686-pc-linux-gnu/libmultiprocess/07c917f7ca910d66abc6d3873162fc9061704074-722fc6d9234/src/mp/util.cpp:6:
/usr/include/c++/13/bits/unique_ptr.h: In instantiation of ‘constexpr std::unique_ptr<_Tp, _Dp>::unique_ptr() [with _Del = std::__future_base::_Result_base::_Deleter; <template-parameter-2-2> = void; _Tp = std::__future_base::_Result_base; _Dp = std::__future_base::_Result_base::_Deleter]’:
/usr/include/c++/13/future:340:34:   required from here
/usr/include/c++/13/bits/unique_ptr.h:305:11: error: no matching function for call to ‘std::__uniq_ptr_data<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter, true, true>::__uniq_ptr_data()’
  305 |         : _M_t()
      |           ^~~~~~
/usr/include/c++/13/bits/unique_ptr.h:241:40: note: candidate: ‘template<class _Del> std::__uniq_ptr_data<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter, true, true>::__uniq_ptr_data(std::__uniq_ptr_impl<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::pointer, _Del&&) [inherited from std::__uniq_ptr_impl<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>]’
  241 |       using __uniq_ptr_impl<_Tp, _Dp>::__uniq_ptr_impl;
      |                                        ^~~~~~~~~~~~~~~
/usr/include/c++/13/bits/unique_ptr.h:241:40: note:   template argument deduction/substitution failed:
/usr/include/c++/13/bits/unique_ptr.h:305:11: note:   candidate expects 2 arguments, 0 provided
  305 |         : _M_t()
      |           ^~~~~~
/usr/include/c++/13/bits/unique_ptr.h:241:40: note: candidate: ‘std::__uniq_ptr_data<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter, true, true>::__uniq_ptr_data(std::__uniq_ptr_impl<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::pointer) [inherited from std::__uniq_ptr_impl<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>]’
  241 |       using __uniq_ptr_impl<_Tp, _Dp>::__uniq_ptr_impl;
      |                                        ^~~~~~~~~~~~~~~
/usr/include/c++/13/bits/unique_ptr.h:241:40: note:   candidate expects 1 argument, 0 provided
/usr/include/c++/13/bits/unique_ptr.h:242:7: note: candidate: ‘std::__uniq_ptr_data<_Tp, _Dp, <anonymous>, <anonymous> >::__uniq_ptr_data(std::__uniq_ptr_data<_Tp, _Dp, <anonymous>, <anonymous> >&&) [with _Tp = std::__future_base::_Result_base; _Dp = std::__future_base::_Result_base::_Deleter; bool <anonymous> = true; bool <anonymous> = true]’
  242 |       __uniq_ptr_data(__uniq_ptr_data&&) = default;
      |       ^~~~~~~~~~~~~~~
/usr/include/c++/13/bits/unique_ptr.h:242:7: note:   candidate expects 1 argument, 0 provided
make[4]: *** [CMakeFiles/mputil.dir/build.make:76: CMakeFiles/mputil.dir/src/mp/util.cpp.o] Error 1
```

</p>
</details>
Interesting that this did not result in a compile or link failure instead.

I switched to clang in commit fad0f21c3caba129106799fe6c14aff323ef99f2 to avoid OOM with g++, (which was before switching to 32-bit in commit fae0295a799499268caca9c385ac4d7061543980), but now that the CI machines have more memory, it should be fine if this CI task takes more memory and time.

Happy to review a pull, if someone submits one.
Can confirm switching from clang to gcc does seem to fix this. `-Wno-error=documentation` also had to be dropped because gcc does not support it.

```c++
--- a/ci/test/00_setup_env_i686_multiprocess.sh
+++ b/ci/test/00_setup_env_i686_multiprocess.sh
@@ -10,15 +10,12 @@ export HOST=i686-pc-linux-gnu
 export CONTAINER_NAME=ci_i686_multiprocess
 export CI_IMAGE_NAME_TAG="docker.io/ubuntu:24.04"
 export CI_IMAGE_PLATFORM="linux/amd64"
-export PACKAGES="llvm clang g++-multilib"
-export DEP_OPTS="DEBUG=1 MULTIPROCESS=1"
+export PACKAGES="g++-multilib"
+export DEP_OPTS="DEBUG=1 MULTIPROCESS=1 NO_QT=1"
 export GOAL="install"
 export TEST_RUNNER_EXTRA="--v2transport"
 export BITCOIN_CONFIG="\
  -DCMAKE_BUILD_TYPE=Debug \
- -DCMAKE_C_COMPILER='clang;-m32' \
- -DCMAKE_CXX_COMPILER='clang++;-m32' \
- -DCMAKE_CXX_FLAGS='-Wno-error=documentation' \
  -DAPPEND_CPPFLAGS='-DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE' \
 "
 export BITCOIND=bitcoin-node  # Used in functional tests
```

---

> Interesting that this did not result in a compile or link failure instead.

I was just seeing a lot of strange things here. Theoretically, I think it should be fine to use gcc and clang together. Some other things I noticed: when I ran gdb I could step into the [findFirst](https://github.com/capnproto/capnproto/blob/b34ec28cceaf15b1082b74b50f03f770873c3636/c%2B%2B/src/kj/common.h#L1882-L1920) methods and see it looking for the '/' character and `pos` pointing to the right place but then the return value was wrong (a very large number instead of the position of the character). Also in I couldn't even tell which findFirst overload I was in because line number did not seem to correspond to the source file.

When I added the -fabi-version flags and rebuilt I saw the ipc tests run and passed, and the  -fabi-version fix worked twice in two different containers when I applied it manually, but did not seem to work when it was already applied at the beginning of the build. So I think something else I was doing in my rebuild steps, like reconfiguring cmake was causing the problem to go away. So I don't know. I want to debug more but I think I already spent way too much time on this.
I tried to repeat previous steps rebuilding depends and test_bitcoin in container to figure out what I was doing that caused the test to stop crashing, but it seems to crash reliably, so I can't work out what I other changes I might have made while trying -fabi-version flags in https://github.com/bitcoin/bitcoin/issues/31772#issuecomment-2634917217 that would have caused it not to crash.

Additionally, I went back to original build and debugged it with GDB, and could easily step through and see where the bug is happening when I run with:

```bash
gdb -ex 'b findFirst' -ex run --args ./src/test/test_bitcoin -t ipc_tests
```

Everything works well up until reaching the `ArrayPtr<const char>::findFirst` method:

https://github.com/capnproto/capnproto/blob/b34ec28cceaf15b1082b74b50f03f770873c3636/c%2B%2B/src/kj/common.h#L1882-L1890

The method also seems to work fine until it reaches the `return pos - ptr;` line. Because the function returns a Maybe value, this calls Maybe constructor:

https://github.com/capnproto/capnproto/blob/b34ec28cceaf15b1082b74b50f03f770873c3636/c%2B%2B/src/kj/common.h#L1389

which has a NullableValue member and calls NullableValue constructor:

https://github.com/capnproto/capnproto/blob/b34ec28cceaf15b1082b74b50f03f770873c3636/c%2B%2B/src/kj/common.h#L1158

which calls a function called ctor:

https://github.com/capnproto/capnproto/blob/b34ec28cceaf15b1082b74b50f03f770873c3636/c%2B%2B/src/kj/common.h#L1061

and unfortunately that function seems to be completely broken and not do anything. The `ctor` function is supposed to construct a `T` value inside the `NullableValue<T>` object, but it fails to do that. In this case `T` is a `size_t` object so it is supposed to assign the `pos - ptr` size that was computed into the `Maybe<size_t>` object that is being returned. But it doesn't do this. So the `Maybe<size_t>` object is only half-initialized as `{isSet = true, {value = 1497966205}}`, where the value 1497966205 is just the preexisting value it held before it was constructed, and is much longer than the size of the string being searched, so the code later throws an exception when there is an attempt to slice the "192.0.0.0/24" string at that position.

This bug seems like it is is probably a compiler bug, but not one is necessarily going to happen reliably because if the unitialized memory location started of as 0 instead 1497966205, crash would not happen. So I'm not sure switching bitcoin compiler from clang to gcc really fixes the problem, or just makes it appear not to happen. And I"m not sure what I was doing before that caused the problem to disappear as well. It seems like there might be just be a problem with this version of gcc and -O0 and this piece of code.

Next steps might be to look at generated assembly and confirm compiler is really producing buggy code or try to reproduce a minimal test case. Another thing we could try to do is update to a new version of gcc. Version here seems to be `gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0`

Here is disassembly of relevant functions:

<details><summary>disassembly</summary>
<p>

```c++
(gdb) disassemble /s
Dump of assembler code for function _ZNK2kj8ArrayPtrIKcE9findFirstERS1_:
/usr/src/kj/common.h:
1883    inline Maybe<size_t> ArrayPtr<const char>::findFirst(const char& c) const {
   0x5977156c <+0>:     push   %ebp
   0x5977156d <+1>:     mov    %esp,%ebp
   0x5977156f <+3>:     push   %ebx
   0x59771570 <+4>:     sub    $0x24,%esp
   0x59771573 <+7>:     call   0x566e8da0 <__x86.get_pc_thunk.bx>
   0x59771578 <+12>:    add    $0x121eff0,%ebx
   0x5977157e <+18>:    mov    0x8(%ebp),%eax
   0x59771581 <+21>:    mov    %eax,-0x1c(%ebp)
   0x59771584 <+24>:    mov    0xc(%ebp),%eax
   0x59771587 <+27>:    mov    %eax,-0x20(%ebp)
   0x5977158a <+30>:    mov    0x10(%ebp),%eax
   0x5977158d <+33>:    mov    %eax,-0x24(%ebp)
=> 0x59771590 <+36>:    mov    %gs:0x14,%eax
   0x59771596 <+42>:    mov    %eax,-0xc(%ebp)
   0x59771599 <+45>:    xor    %eax,%eax

1884      const char* pos = reinterpret_cast<const char*>(memchr(ptr, c, size_));
   0x5977159b <+47>:    mov    -0x20(%ebp),%eax
   0x5977159e <+50>:    mov    0x4(%eax),%ecx
   0x597715a1 <+53>:    mov    -0x24(%ebp),%eax
   0x597715a4 <+56>:    movzbl (%eax),%eax
   0x597715a7 <+59>:    movsbl %al,%edx
   0x597715aa <+62>:    mov    -0x20(%ebp),%eax
   0x597715ad <+65>:    mov    (%eax),%eax
   0x597715af <+67>:    sub    $0x4,%esp
   0x597715b2 <+70>:    push   %ecx
   0x597715b3 <+71>:    push   %edx
   0x597715b4 <+72>:    push   %eax
   0x597715b5 <+73>:    call   0x5664a880 <memchr@plt>
   0x597715ba <+78>:    add    $0x10,%esp
   0x597715bd <+81>:    mov    %eax,-0x10(%ebp)

1885      if (pos == nullptr) {
   0x597715c0 <+84>:    cmpl   $0x0,-0x10(%ebp)
   0x597715c4 <+88>:    jne    0x597715d8 <_ZNK2kj8ArrayPtrIKcE9findFirstERS1_+108>

1886        return nullptr;
   0x597715c6 <+90>:    sub    $0x8,%esp
   0x597715c9 <+93>:    push   $0x0
   0x597715cb <+95>:    push   -0x1c(%ebp)
   0x597715ce <+98>:    call   0x595224a6 <_ZN2kj5MaybeIjEC2EDn>
   0x597715d3 <+103>:   add    $0x10,%esp
   0x597715d6 <+106>:   jmp    0x597715f9 <_ZNK2kj8ArrayPtrIKcE9findFirstERS1_+141>

1887      } else {
1888        return pos - ptr;
   0x597715d8 <+108>:   mov    -0x20(%ebp),%eax
   0x597715db <+111>:   mov    (%eax),%eax
   0x597715dd <+113>:   mov    -0x10(%ebp),%edx
   0x597715e0 <+116>:   sub    %eax,%edx
   0x597715e2 <+118>:   mov    %edx,%eax
   0x597715e4 <+120>:   mov    %eax,-0x14(%ebp)
   0x597715e7 <+123>:   sub    $0x8,%esp
   0x597715ea <+126>:   lea    -0x14(%ebp),%eax
   0x597715ed <+129>:   push   %eax
   0x597715ee <+130>:   push   -0x1c(%ebp)
   0x597715f1 <+133>:   call   0x595224d0 <_ZN2kj5MaybeIjEC2EOj>
   0x597715f6 <+138>:   add    $0x10,%esp

1889      }
1890    }
   0x597715f9 <+141>:   mov    -0xc(%ebp),%eax
   0x597715fc <+144>:   sub    %gs:0x14,%eax
   0x59771603 <+151>:   je     0x5977160a <_ZNK2kj8ArrayPtrIKcE9findFirstERS1_+158>
   0x59771605 <+153>:   call   0x597cf290 <__stack_chk_fail_local>
   0x5977160a <+158>:   mov    -0x1c(%ebp),%eax
   0x5977160d <+161>:   mov    -0x4(%ebp),%ebx
   0x59771610 <+164>:   leave
   0x59771611 <+165>:   ret    $0x4
End of assembler dump.
(gdb) disassemble /s _ZN2kj5MaybeIjEC2EOj
Dump of assembler code for function _ZN2kj5MaybeIjEC2EOj:
/usr/src/kj/common.h:
1389      Maybe(T&& t): ptr(kj::mv(t)) {}
   0x595224d0 <+0>:     push   %ebp
   0x595224d1 <+1>:     mov    %esp,%ebp
   0x595224d3 <+3>:     push   %esi
   0x595224d4 <+4>:     push   %ebx
   0x595224d5 <+5>:     call   0x566e8da0 <__x86.get_pc_thunk.bx>
   0x595224da <+10>:    add    $0x146e08e,%ebx
   0x595224e0 <+16>:    mov    0x8(%ebp),%esi
   0x595224e3 <+19>:    sub    $0xc,%esp
   0x595224e6 <+22>:    push   0xc(%ebp)
   0x595224e9 <+25>:    call   0x59502e4c <_ZN2kj2mvIjEEOT_RS1_>
   0x595224ee <+30>:    add    $0x10,%esp
   0x595224f1 <+33>:    sub    $0x8,%esp
   0x595224f4 <+36>:    push   %eax
   0x595224f5 <+37>:    push   %esi
   0x595224f6 <+38>:    call   0x5952b7d8 <_ZN2kj1_13NullableValueIjEC2EOj>
   0x595224fb <+43>:    add    $0x10,%esp
   0x595224fe <+46>:    nop
   0x595224ff <+47>:    lea    -0x8(%ebp),%esp
   0x59522502 <+50>:    pop    %ebx
   0x59522503 <+51>:    pop    %esi
   0x59522504 <+52>:    pop    %ebp
   0x59522505 <+53>:    ret
End of assembler dump.
(gdb) disassemble /s _ZN2kj1_13NullableValueIjEC2EOj
Dump of assembler code for function _ZN2kj1_13NullableValueIjEC2EOj:
/usr/src/kj/common.h:
1156      inline NullableValue(T&& t)
   0x5952b7d8 <+0>:     push   %ebp
   0x5952b7d9 <+1>:     mov    %esp,%ebp
   0x5952b7db <+3>:     push   %ebx
   0x5952b7dc <+4>:     sub    $0x4,%esp
   0x5952b7df <+7>:     call   0x566e8da0 <__x86.get_pc_thunk.bx>
   0x5952b7e4 <+12>:    add    $0x1464d84,%ebx

1157          : isSet(true) {
   0x5952b7ea <+18>:    mov    0x8(%ebp),%eax
   0x5952b7ed <+21>:    movb   $0x1,(%eax)

1158        ctor(value, kj::mv(t));
   0x5952b7f0 <+24>:    sub    $0xc,%esp
   0x5952b7f3 <+27>:    push   0xc(%ebp)
   0x5952b7f6 <+30>:    call   0x59502e4c <_ZN2kj2mvIjEEOT_RS1_>
   0x5952b7fb <+35>:    add    $0x10,%esp
   0x5952b7fe <+38>:    mov    0x8(%ebp),%edx
   0x5952b801 <+41>:    add    $0x4,%edx
   0x5952b804 <+44>:    sub    $0x8,%esp
   0x5952b807 <+47>:    push   %eax
   0x5952b808 <+48>:    push   %edx
   0x5952b809 <+49>:    call   0x595314f8 <_ZN2kj4ctorIjIjEEEvRT_DpOT0_>
   0x5952b80e <+54>:    add    $0x10,%esp

1159      }
   0x5952b811 <+57>:    nop
   0x5952b812 <+58>:    mov    -0x4(%ebp),%ebx
   0x5952b815 <+61>:    leave
   0x5952b816 <+62>:    ret
End of assembler dump.
(gdb) disassemble /s _ZN2kj4ctorIjIjEEEvRT_DpOT0_

Dump of assembler code for function _ZN2kj4ctorIjIjEEEvRT_DpOT0_:
/usr/src/kj/common.h:
1060    inline void ctor(T& location, Params&&... params) {
   0x595314f8 <+0>:     push   %ebp
   0x595314f9 <+1>:     mov    %esp,%ebp
   0x595314fb <+3>:     push   %esi
   0x595314fc <+4>:     push   %ebx
   0x595314fd <+5>:     call   0x566e8da0 <__x86.get_pc_thunk.bx>
   0x59531502 <+10>:    add    $0x145f066,%ebx

1061      new (_::PlacementNew(), &location) T(kj::fwd<Params>(params)...);
   0x59531508 <+16>:    sub    $0x4,%esp
   0x5953150b <+19>:    push   0x8(%ebp)
   0x5953150e <+22>:    push   %eax
   0x5953150f <+23>:    push   $0x4
   0x59531511 <+25>:    call   0x58a0ec00 <_ZnwjN2kj1_12PlacementNewEPv>
   0x59531516 <+30>:    add    $0x10,%esp
   0x59531519 <+33>:    mov    %eax,%esi
   0x5953151b <+35>:    test   %esi,%esi
   0x5953151d <+37>:    je     0x59531531 <_ZN2kj4ctorIjIjEEEvRT_DpOT0_+57>
   0x5953151f <+39>:    sub    $0xc,%esp
   0x59531522 <+42>:    push   0xc(%ebp)
   0x59531525 <+45>:    call   0x59502e70 <_ZN2kj3fwdIjEEOT_RNS_8NoInfer_IS1_E4TypeE>
   0x5953152a <+50>:    add    $0x10,%esp
   0x5953152d <+53>:    mov    (%eax),%eax
   0x5953152f <+55>:    mov    %eax,(%esi)

1062    }
   0x59531531 <+57>:    nop
   0x59531532 <+58>:    lea    -0x8(%ebp),%esp
   0x59531535 <+61>:    pop    %ebx
   0x59531536 <+62>:    pop    %esi
   0x59531537 <+63>:    pop    %ebp
   0x59531538 <+64>:    ret
End of assembler dump.
(gdb) 
Dump of assembler code for function _ZN2kj4ctorIjIjEEEvRT_DpOT0_:
/usr/src/kj/common.h:
1060    inline void ctor(T& location, Params&&... params) {
   0x595314f8 <+0>:     push   %ebp
   0x595314f9 <+1>:     mov    %esp,%ebp
   0x595314fb <+3>:     push   %esi
   0x595314fc <+4>:     push   %ebx
   0x595314fd <+5>:     call   0x566e8da0 <__x86.get_pc_thunk.bx>
   0x59531502 <+10>:    add    $0x145f066,%ebx

1061      new (_::PlacementNew(), &location) T(kj::fwd<Params>(params)...);
   0x59531508 <+16>:    sub    $0x4,%esp
   0x5953150b <+19>:    push   0x8(%ebp)
   0x5953150e <+22>:    push   %eax
   0x5953150f <+23>:    push   $0x4
   0x59531511 <+25>:    call   0x58a0ec00 <_ZnwjN2kj1_12PlacementNewEPv>
   0x59531516 <+30>:    add    $0x10,%esp
   0x59531519 <+33>:    mov    %eax,%esi
   0x5953151b <+35>:    test   %esi,%esi
   0x5953151d <+37>:    je     0x59531531 <_ZN2kj4ctorIjIjEEEvRT_DpOT0_+57>
   0x5953151f <+39>:    sub    $0xc,%esp
   0x59531522 <+42>:    push   0xc(%ebp)
   0x59531525 <+45>:    call   0x59502e70 <_ZN2kj3fwdIjEEOT_RNS_8NoInfer_IS1_E4TypeE>
   0x5953152a <+50>:    add    $0x10,%esp
   0x5953152d <+53>:    mov    (%eax),%eax
   0x5953152f <+55>:    mov    %eax,(%esi)

1062    }
   0x59531531 <+57>:    nop
   0x59531532 <+58>:    lea    -0x8(%ebp),%esp
   0x59531535 <+61>:    pop    %ebx
   0x59531536 <+62>:    pop    %esi
   0x59531537 <+63>:    pop    %ebp
   0x59531538 <+64>:    ret
End of assembler dump.
(gdb) disassemble /s _ZnwjN2kj1_12PlacementNewEPv
Dump of assembler code for function _ZnwjN2kj1_12PlacementNewEPv:
/ci_container_base/depends/i686-pc-linux-gnu/include/kj/common.h:
1051    inline void* operator new(size_t, kj::_::PlacementNew, void* __p) noexcept {
   0x58a0ec00 <+0>:     endbr32
   0x58a0ec04 <+4>:     push   %ebp
   0x58a0ec05 <+5>:     mov    %esp,%ebp
   0x58a0ec07 <+7>:     push   %ebx
   0x58a0ec08 <+8>:     sub    $0x14,%esp
   0x58a0ec0b <+11>:    call   0x58a0ec10 <_ZnwjN2kj1_12PlacementNewEPv+16>
   0x58a0ec10 <+16>:    pop    %eax
   0x58a0ec11 <+17>:    add    $0x1f81958,%eax
   0x58a0ec17 <+23>:    mov    %eax,-0x14(%ebp)
   0x58a0ec1a <+26>:    mov    0xc(%ebp),%eax
   0x58a0ec1d <+29>:    mov    0x8(%ebp),%eax
   0x58a0ec20 <+32>:    mov    %gs:0x14,%eax
   0x58a0ec26 <+38>:    mov    %eax,-0x8(%ebp)

1052      return __p;
   0x58a0ec29 <+41>:    mov    0xc(%ebp),%eax
   0x58a0ec2c <+44>:    mov    %eax,-0x10(%ebp)
   0x58a0ec2f <+47>:    mov    %gs:0x14,%eax
   0x58a0ec35 <+53>:    mov    -0x8(%ebp),%ecx
   0x58a0ec38 <+56>:    cmp    %ecx,%eax
   0x58a0ec3a <+58>:    jne    0x58a0ec49 <_ZnwjN2kj1_12PlacementNewEPv+73>
   0x58a0ec40 <+64>:    mov    -0x10(%ebp),%eax
   0x58a0ec43 <+67>:    add    $0x14,%esp
   0x58a0ec46 <+70>:    pop    %ebx
   0x58a0ec47 <+71>:    pop    %ebp
   0x58a0ec48 <+72>:    ret
   0x58a0ec49 <+73>:    mov    -0x14(%ebp),%ebx
   0x58a0ec4c <+76>:    call   0x56649250 <__stack_chk_fail@plt>
End of assembler dump.
(gdb) disassemble /s _ZN2kj3fwdIjEEOT_RNS_8NoInfer_IS1_E4TypeE
Dump of assembler code for function _ZN2kj3fwdIjEEOT_RNS_8NoInfer_IS1_E4TypeE:
/ci_container_base/depends/i686-pc-linux-gnu/include/kj/common.h:
700     template<typename T> constexpr T&& fwd(NoInfer<T>& t) noexcept { return static_cast<T&&>(t); }
   0x59502e70 <+0>:     push   %ebp
   0x59502e71 <+1>:     mov    %esp,%ebp
   0x59502e73 <+3>:     call   0x584df28a <__x86.get_pc_thunk.ax>
   0x59502e78 <+8>:     add    $0x148d6f0,%eax
   0x59502e7d <+13>:    mov    0x8(%ebp),%eax
   0x59502e80 <+16>:    pop    %ebp
   0x59502e81 <+17>:    ret
End of assembler dump.
```

</p>
</details>

When I fed this to chatgpt (https://chatgpt.com/share/67a2aa92-e100-800a-b5b3-999982d1a648) it claimed to find a bug in the dissembly where operator new function is not interpreting its parameters correctly, and I could confirm this with gdb (at least to the best of my understanding, I am not that familiar with assembly and calling conventions). But gdb definitely showed operator new returning the wrong address (source address not destination address), which explained why the destination was not being updated and contained a garbage value.

So I think there is pretty good evidence that this version of gcc contains a bug with -O0 and is miscompiling the code. Again I'm not sure if we care about this or not. It's an older version of gcc so might be logical to just update it. Or just not compile this code with -O0.



> This bug seems like it is is probably a compiler bug, but not one is necessarily going to happen reliably because if the unitialized memory location started of as 0 instead 1497966205, crash would not happen. So I'm not sure switching bitcoin compiler from clang to gcc really fixes the problem, or just makes it appear not to happen. And I"m not sure what I was doing before that caused the problem to disappear as well. It seems like there might be just be a problem with this version of gcc and -O0 and this piece of code.
> 
> Next steps might be to look at generated assembly and confirm compiler is really producing buggy code or try to reproduce a minimal test case. Another thing we could try to do is update to a new version of gcc. Version here seems to be `gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0`

Interesting. It would be nice to reduce this, if someone wants to spend more time on this. However, I am not familiar with libmp and the build process around it, so if someone manages to produce one (or two) cpp files (even if large) with the corresponding compiler flags, I am happy to take it from there and minimize further.
> Interesting. It would be nice to reduce this

So I reduced the failure down to a standalone case, but it could be reduced further. Now it is unclear to me if there is a compiler bug here or not, because this is caused by a very specific interaction between the two compiler versions we are using:

- gcc version Ubuntu 13.3.0-6ubuntu2~24.04
- clang version 18.1.3 (1ubuntu1)

and maybe the specific flags we are using. The bug happens because the linker links together the

- `_ZN2kj4ctorIjIjEEEvRT_DpOT0_` function (`void kj::ctor<unsigned int, unsigned int>(unsigned int&, unsigned int&&)`)

generated by GCC in the capnproto static libraries

-  `_ZnwjN2kj1_12PlacementNewEPv` function (`operator new(unsigned int, kj::_::PlacementNew, void*)`)

generated by clang in the `libbitcoin_ipc_test.a` library.

This happens due to linker command line order. The `ipc_test` library comes before the kj static libraries in the command line, but it only contains an `operator new` symbol, not a `ctor` symbol, while kj static libraries contain both symbols. The link will prefer the first symbol definition it sees so it uses the `operator new` from bitcoin together with the `ctor` from libkj and this does not work because gcc `ctor` calls clang `operator new` with a calling convention it is not expecting (as described by chatgpt above).

Here's the reduced test case I have which reproduces the bug with the same code and flags as the CI build:

```c++
cat > test.h <<EOS
#include <cstddef>
template <typename T> struct NoInfer_ { typedef T Type; };
template <typename T> using NoInfer = typename NoInfer_<T>::Type;

template<typename T> constexpr T&& fwd(NoInfer<T>& t) noexcept { return static_cast<T&&>(t); }

struct PlacementNew {};

void* operator new(size_t, PlacementNew, void* __p) noexcept;

template <typename T, typename... Params>
inline void ctor(T& location, Params&&... params) {
  new (PlacementNew(), &location) T(fwd<Params>(params)...);
}
EOS

cat > test_gcc.cpp <<EOS
#include "test.h"
template void ctor<size_t, size_t>(size_t&, size_t&&);
EOS

cat > test_clang.cpp <<EOS
#include "test.h"
#include <iostream>

void* operator new(size_t, PlacementNew, void* __p) noexcept {
  return __p;
}

size_t f() {
    size_t i = 10;
    size_t j = 20;
    ctor(i, std::move(j));
    return i;
}

int main() {
    std::cout << "i = " << f() << std::endl;
}
EOS

g++ -m32 -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG -pipe -std=c++20 -O0 -g -fPIC -Wall -Wextra -Wno-strict-aliasing -Wno-sign-compare -Wno-unused-parameter -pthread -c test_gcc.cpp -o test_gcc.o
clang++ -m32 -Wno-error=documentation -O0 -ftrapv -O0 -g3 -g3 -fstack-protector-all -fcf-protection=full -fstack-clash-protection -fPIE -c test_clang.cpp -o test_clang.o
clang++ -m32 -Wno-error=documentation -O0 -ftrapv -O0 -g3 -g3 -fstack-protector-all -fcf-protection=full -fstack-clash-protection -Wl,-z,relro -Wl,-z,now -Wl,-z,separate-code -fPIE -pie test_gcc.o test_clang.o
./a.out
```

If the test case were compiled properly it would print 20, but due to the calling convention problem here it prints 10, because the line `ctor(i, std::move(j));` is supposed to assign `j` (20) to `i`, but doesn't do that because `operator new` returns a garbage value.

I confirmed that adding `-fabi-version=2` to gcc command line above does not fix the problem (the program still prints 10 instead of 20). But I did not experiment further to see which of the compiler flags we are passing may be causing the incompatibility, or if the flags are irrelevant and gcc and clang just disagree on the calling convention for this `operator new`.

I am not sure if there is a general solution for this problem. If linker order were changed so that kj libraries were always specified first before the bitcoin library, that would prevent crashing inside kj libraries, but then it might cause a similar crash in the bitcoin code.

All of this suggests that if you are using depends, and you want to change the compiler or pass different flags in bitcoin, you really need to make the same changes in both builds. Mixing different flags and compiler versions might not be a good idea.
Following seem to be minimal flags to reproduce this issue. None of the other flags except for `-fPIC` was making any difference, and without `-fPIC` the program just segfaults instead of printing the wrong value:

```bash
g++ -m32 -fPIC -c test_gcc.cpp -o test_gcc.o
clang++ -m32 -fPIC -c test_clang.cpp -o test_clang.o
clang++ -m32 -fPIC test_gcc.o test_clang.o
./a.out
```

So it seems like this version of gcc and clang just always (regardless of flags) assume incompatible calling conventions for `operator new` and can't be used together here.

Fix for the bug should just be to use either clang or gcc for this CI build and not try to use both.
This also seems to be a known issue: "It has come to my attention that GCC and clang generate incompatible code
for passing an argument of an empty class type." https://itanium-cxx-abi.github.io/cxx-abi/cxx-abi-dev/archives/2015-December/002869.html
I wonder if there is a way to detect those at compile or link time. Other than that, printing a warning when mixing two compilers when compiling with depends may be useful? 
> Other than that, printing a warning when mixing two compilers when compiling with depends may be useful?

Why only depends though? If the issue is generally mixing compilers/flags, then using Clang + system libs on any (gcc-based) Linux system (potentially) has the same problem. It's currently the case that we use Clang + (GCC built) system libs in at least the ASAN,fuzz,valgrind CIs, and Clang + GCC depends libs in the 32-bit job.
> Why only depends though? If the issue is generally mixing compilers/flags, then using Clang + system libs on any (gcc-based) Linux system (potentially) has the same problem.

Yes this issue is not specific to depends, and could theoretically could happen if, for example an ubuntu library package was compiled with gcc and exposed a function with an empty struct parameter, and you tried to use the library with clang. 

The point of having an ABI is to prevent issues like this and allow different compilers to interoperate, but this is a corner case where something hasn't been standardized. Also, in this case, issue goes away if either package is compiled with any optimization (even `-O` is sufficient) because this makes the call to `operator new` inlined. And this issue could also be masked by having a different linker command line order that would cause linker to choose compatible `ctor` and `operator new` symbol definitions instead of incompatible ones.

Given corner case nature of this problem, I'm thinking it would be good to send a patch to upstream capnproto to avoid the issue by changing parameter order. Following patch seems to fix the issue in our CI job:

```diff
diff --git a/depends/packages/capnp.mk b/depends/packages/capnp.mk
index 0c211cbc455d..00ccf08acf4b 100644
--- a/depends/packages/capnp.mk
+++ b/depends/packages/capnp.mk
@@ -5,6 +5,12 @@ $(package)_download_file=$(native_$(package)_download_file)
 $(package)_file_name=$(native_$(package)_file_name)
 $(package)_sha256_hash=$(native_$(package)_sha256_hash)
 
+$(package)_patches = abifix.patch
+
+define $(package)_preprocess_cmds
+  patch -p2 < $($(package)_patch_dir)/abifix.patch
+endef
+
 define $(package)_set_vars :=
   $(package)_config_opts := -DBUILD_TESTING=OFF
   $(package)_config_opts += -DWITH_OPENSSL=OFF
diff --git a/depends/patches/capnp/abifix.patch b/depends/patches/capnp/abifix.patch
new file mode 100644
index 000000000000..1386aadc7452
--- /dev/null
+++ b/depends/patches/capnp/abifix.patch
@@ -0,0 +1,35 @@
+diff --git a/c++/src/kj/common.h b/c++/src/kj/common.h
+index 237c41d3..dc2e6381 100644
+--- a/c++/src/kj/common.h
++++ b/c++/src/kj/common.h
+@@ -1041,24 +1041,26 @@ private:
+ 
+ // We want placement new, but we don't want to #include <new>.  operator new cannot be defined in
+ // a namespace, and defining it globally conflicts with the definition in <new>.  So we have to
+-// define a dummy type and an operator new that uses it.
++// define a dummy type and an operator new that uses it. The dummy type is intentionally passed
++// as the last parameter to avoid an ABI issues caused by GCC and clang using incompatible calling
++// conventions for passing empty struct parameters.
+ 
+ namespace _ {  // private
+ struct PlacementNew {};
+ }  // namespace _ (private)
+ } // namespace kj
+ 
+-inline void* operator new(size_t, kj::_::PlacementNew, void* __p) noexcept {
++inline void* operator new(size_t, void* __p, kj::_::PlacementNew) noexcept {
+   return __p;
+ }
+ 
+-inline void operator delete(void*, kj::_::PlacementNew, void* __p) noexcept {}
++inline void operator delete(void*, void* __p, kj::_::PlacementNew) noexcept {}
+ 
+ namespace kj {
+ 
+ template <typename T, typename... Params>
+ inline void ctor(T& location, Params&&... params) {
+-  new (_::PlacementNew(), &location) T(kj::fwd<Params>(params)...);
++  new (&location, _::PlacementNew()) T(kj::fwd<Params>(params)...);
+ }
+ 
+ template <typename T>
```


Submitted patch in https://github.com/capnproto/capnproto/pull/2235

Fix patch:
diff --git a/depends/packages/capnp.mk b/depends/packages/capnp.mk
index 7f41d3b5a4ed3..dff92d9e61fe8 100644
--- a/depends/packages/capnp.mk
+++ b/depends/packages/capnp.mk
@@ -4,6 +4,7 @@ $(package)_download_path=$(native_$(package)_download_path)
 $(package)_download_file=$(native_$(package)_download_file)
 $(package)_file_name=$(native_$(package)_file_name)
 $(package)_sha256_hash=$(native_$(package)_sha256_hash)
+$(package)_patches=abi_placement_new.patch
 
 define $(package)_set_vars :=
   $(package)_config_opts := -DBUILD_TESTING=OFF
@@ -12,6 +13,10 @@ define $(package)_set_vars :=
   $(package)_cxxflags += -fdebug-prefix-map=$($(package)_extract_dir)=/usr -fmacro-prefix-map=$($(package)_extract_dir)=/usr
 endef
 
+define $(package)_preprocess_cmds
+  patch -p2 < $($(package)_patch_dir)/abi_placement_new.patch
+endef
+
 define $(package)_config_cmds
   $($(package)_cmake) .
 endef
diff --git a/depends/patches/capnp/abi_placement_new.patch b/depends/patches/capnp/abi_placement_new.patch
new file mode 100644
index 0000000000000..9aef85db8e86b
--- /dev/null
+++ b/depends/patches/capnp/abi_placement_new.patch
@@ -0,0 +1,71 @@
+From 74560f26f75dda4257dce541ca362a1e763b2971 Mon Sep 17 00:00:00 2001
+From: Ryan Ofsky <ryan@ofsky.org>
+Date: Thu, 6 Feb 2025 08:39:05 -0500
+Subject: [PATCH 1/1] Avoid gcc/clang ABI incompatibility caused by
+ PlacementNew
+
+GCC and clang do not use same calling convention for passing empty struct
+parameters. There is more information about this in
+https://itanium-cxx-abi.github.io/cxx-abi/cxx-abi-dev/archives/2015-December/002869.html
+
+Unfortunately this can create an issue in capnproto if it is built without
+optimizations in GCC, and the resulting static libraries are used in a clang
+program, or vice versa.
+
+Depending on what order libraries are specified on the linker command line, and
+whether code compiled with the other compiler is calling any header functions
+that cause weak a `operator new(unsigned int, kj::_::PlacementNew, void*)`
+symbol to be defined in its own objects, this can cause the linker to link a
+GCC-generated `kj::ctor` with a clang-generated `operator new`, and the
+resulting program to crash due to the compilers using different calling
+conventions for `operator new`.
+
+This problem is difficult to avoid in general, but pretty easy to avoid here by
+changing `operator new` parameter order so the empty struct parameter is last.
+
+This change should be beneficial for capnproto users that may be compiling it
+without optimizations, and not necessarily using a single compiler to build all
+their dependencies.
+
+The problem does not occur if any optimizations are enabled because `operator
+new` calls are inlined in that case.
+---
+ c++/src/kj/common.h | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+diff --git a/c++/src/kj/common.h b/c++/src/kj/common.h
+index b8edde3c..28ab11d6 100644
+--- a/c++/src/kj/common.h
++++ b/c++/src/kj/common.h
+@@ -1034,24 +1034,27 @@ private:
+ 
+ // We want placement new, but we don't want to #include <new>.  operator new cannot be defined in
+ // a namespace, and defining it globally conflicts with the definition in <new>.  So we have to
+-// define a dummy type and an operator new that uses it.
++// define a dummy type and an operator new that uses it.  The dummy type is intentionally passed
++// as the last parameter so clang and GCC ABI calling conventions for empty struct struct parameters
++// are compatible, and there are not segfaults trying to call clang operator new/delete from GCC or
++// vice versa.
+ 
+ namespace _ {  // private
+ struct PlacementNew {};
+ }  // namespace _ (private)
+ } // namespace kj
+ 
+-inline void* operator new(size_t, kj::_::PlacementNew, void* __p) noexcept {
++inline void* operator new(size_t, void* __p, kj::_::PlacementNew) noexcept {
+   return __p;
+ }
+ 
+-inline void operator delete(void*, kj::_::PlacementNew, void* __p) noexcept {}
++inline void operator delete(void*, void* __p, kj::_::PlacementNew) noexcept {}
+ 
+ namespace kj {
+ 
+ template <typename T, typename... Params>
+ inline void ctor(T& location, Params&&... params) {
+-  new (_::PlacementNew(), &location) T(kj::fwd<Params>(params)...);
++  new (&location, _::PlacementNew()) T(kj::fwd<Params>(params)...);
+ }
+ 
+ template <typename T>


Test patch: