chriskohlhoff / asio

Asio C++ Library
http://think-async.com/Asio
4.94k stars 1.22k forks source link

Dynamically loading ASIO in some host processes on macOS crashes when accessing scheduler_.concurrency_hint() #1213

Open meverett opened 1 year ago

meverett commented 1 year ago

The Crash

In some cases when loading ASIO dynamically into a host process on macOS the address of the service_registry::create static method fails to correctly resolve. This leads to a cascade of issues, including uninitialized objects. The end result is that the call to scheduler_.concurrency_hint() in kqueue_reactor fails with a EXC_BAD_ACCESS crash.

So far, this has occurred on macOS running on an M1 processor. I'm not sure if other macOS architectures are affected, and it doesn't seem to be an issue on Windows.

When debugging in Xcode, the memory address of the factory method is incorrect, which means that the call to the factory method in the service_registry::do_use_service method fails and the kqueue_reactor and scheduler objects do not initialize correctly.

There is a stackoverflow issue that goes into more details and highlights others who have run into the issue here.

The code that fails is in detail/impl/service_registry.hpp - this is it:

template <typename Service>
Service& service_registry::use_service(io_context& owner)
{
  execution_context::service::key key;
  init_key<Service>(key, 0);
  factory_type factory = &service_registry::create<Service, io_context>;
  return *static_cast<Service*>(do_use_service(key, factory, &owner));
}

Specifically, this is the line where the method address fails to resolve correctly:

factory_type factory = &service_registry::create<Service, io_context>;

So when the call to service_registry::do_use_service occurs on the final line, and it attempts to call the factory method to create a new instance of the service, it fails, which causes uninitialized objects leading to the crash.

The Fix

The fix that worked for me was to simply add ASIO_DECL to the definition of the service_registry::create method, as follows:

// Factory function for creating a service instance.
template <typename Service, typename Owner>
ASIO_DECL static execution_context::service* create(void* owner);

Is there any reason not to declare the create method like this? The matching destroy method right below it is declared with ASIO_DECL.

I'm still not sure why dynamically loading in some host processes works fine, and yet others fail to resolve the address correctly.

rodrigodzf commented 1 year ago

I had the same issue and can confirm that other macOS (Intel) architectures are also affected. I created a related pull request with the minor fix proposed by @meverett https://github.com/chriskohlhoff/asio/pull/1257