10

我有以下“第一次机会异常”消息,它来自我编写的 DLL,该 DLL 在我没有编写的可执行文件中运行。也就是说,DLL 是一个插件。第一次触发此异常时,尝试打开共享内存映射文件失败。如果我忽略第一次机会异常并直接运行,应用程序最终会冻结或崩溃。

First-chance exception at 0x76a7c41f in notmyexe.exe: Microsoft C++ exception: boost::interprocess::interprocess_exception at memory location 0x002bc644..

几个小时后,它似乎是由一个无限循环的代码块引起的,直到预期的异常条件清除。事实证明,如果它永远不会清除,那么最终,这个异常会变成另一个低级异常条件和/或变成堆损坏。所有这些只是为了使用 Boost::interprocess 打开一个共享内存区域。

使事情复杂化的第一件事是,在我的基于 Visual C++ 2008 的项目中,第boost::interprocess::interprocess_exception一次机会异常没有被抛出并被识别为它来自的位置,因为 Visual C++ 2008 编译器无法找到复杂的 boost-flavor 模板代码问题。但是,通过单步执行汇编语言视图,我发现了崩溃的代码。

我自己的代码的顶级行开始变坏了:

  segment = new managed_shared_memory(   open_or_create
                                      ,  MEMORY_AREA_NAME
                                      , SHARED_AREA_SIZE );          

上面的managed_shared_memory类来自 interprocess_fwd.hpp,是 boost 共享内存 API/headers 的标准部分。因为它是基于模板的,所以上面的内容扩展为大约 2Kchars 长的 C++ boost 模板表达式,它被链接器和调试器以不同的长度截断。Visual C++ 2008 不再具有源代码调试功能,似乎这些限制在起作用。

例如,当它爆炸时,我得到这个调用堆栈:

    KernelBase.dll!76a7c41f()   
    [Frames below may be incorrect and/or missing, no symbols loaded for KernelBase.dll]    
    KernelBase.dll!76a7c41f()   
>   msvcr90d.dll!_malloc_dbg(unsigned int nSize=2290875461, int nBlockUse=264, const char * szFileName=0x01fcb983, int nLine=1962999808)  Line 160 + 0x1b bytes C++
    8bfc4d89()  

上面的堆栈转储中没有出现实际的最终用户编写的源函数。

我应该如何调试这个?其次,使用 Visual C++ 2008 的 boost-interprocess 是否存在一些已知问题?第三,下面的 boost 代码是做什么的,为什么它必须无限循环?

boost::interprocess::basic_managed_shared_memory<char,
   boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,
        boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,
        boost::interprocess::iset_index>::basic_managed_shared_memory<char,boo...

再往下,我们得到:

basic_managed_shared_memory (open_or_create_t,
                              const char *name, size_type size,
                              const void *addr = 0, const permissions& perm = permissions())
      : base_t()
      , base2_t(open_or_create, name, size, read_write, addr,
                create_open_func_t(get_this_pointer(),
                ipcdetail::DoOpenOrCreate), perm)
   {}  

无论如何,不​​要尝试在家里调试这个孩子,这就是发生的事情:

在此处输入图像描述

最后,使用我类似忍者的能力单步执行几百万行汇编语言,我克服了 Visual C++ 2008 的邪恶调试器限制,并找到了有问题的代码。

这实际上是爆炸的:create_device<FileBased>(dev...

这里有一些上下文:managed_open_or_create_impl.h line 351 ...

else if(type == DoOpenOrCreate){
         //This loop is very ugly, but brute force is sometimes better
         //than diplomacy. If someone knows how to open or create a
         //file and know if we have really created it or just open it
         //drop me a e-mail!
         bool completed = false;
         while(!completed){
            try{
               create_device<FileBased>(dev, id, size, perm, file_like_t()); // <-- KABOOM!
               created     = true;
               completed   = true;
            }
            catch(interprocess_exception &ex){
               if(ex.get_error_code() != already_exists_error){
                  throw;
               }
               else{
                  try{
                     DeviceAbstraction tmp(open_only, id, read_write);
                     dev.swap(tmp);
                     created     = false;
                     completed   = true;
                  }
                  catch(interprocess_exception &e){
                     if(e.get_error_code() != not_found_error){
                        throw;
                     }
                  }
                  catch(...){
                     throw;
                  }
               }
            }
            catch(...){
               throw;
            }
            thread_yield();
         }
      }
4

2 回答 2

2

我相信我遇到了一些与您相同的问题。看看“\boost\interprocess\shared_memory_object.hpp”中的函数“shared_memory_object::priv_open_or_create”。该函数的顶部是另一个函数调用“create_tmp_and_clean_old_and_get_filename”,它启动一个函数链,最终删除共享内存文件。我最终在 priv_open_or_create 函数中将该函数调用移到了 case 语句开始的位置。我相信我正在使用 boost 1.48。这是我修改的那个函数的最终版本:

inline bool shared_memory_object::priv_open_or_create
   (ipcdetail::create_enum_t type, const char *filename, mode_t mode, const permissions &perm)
{
   m_filename = filename;
   std::string shmfile;
   std::string root_tmp_name;

   //Set accesses
   if (mode != read_write && mode != read_only){
      error_info err = other_error;
      throw interprocess_exception(err);
   }

   switch(type){
      case ipcdetail::DoOpen:
            ipcdetail::get_tmp_base_dir(root_tmp_name);
            shmfile = root_tmp_name;
            shmfile += "/";
            shmfile += filename;
            m_handle = ipcdetail::open_existing_file(shmfile.c_str(), mode, true);
      break;
      case ipcdetail::DoCreate:
            ipcdetail::create_tmp_and_clean_old_and_get_filename(filename, shmfile);
          m_handle = ipcdetail::create_new_file(shmfile.c_str(), mode, perm, true);
      break;
      case ipcdetail::DoOpenOrCreate:
         ipcdetail::create_tmp_and_clean_old_and_get_filename(filename, shmfile);
          m_handle = ipcdetail::create_or_open_file(shmfile.c_str(), mode, perm, true);
      break;
      default:
         {
            error_info err = other_error;
            throw interprocess_exception(err);
         }
   }

   //Check for error
   if(m_handle == ipcdetail::invalid_file()){
      error_info err = system_error_code();
      this->priv_close();
      throw interprocess_exception(err);
   }

   m_mode = mode;
   return true;
}

顺便说一句,如果有人知道我可以通过官方渠道来尝试验证并添加到提升中,请告诉我,因为我讨厌在不知道其全部效果的情况下修改这样的东西。

希望这可以帮助!

于 2013-10-23T18:17:42.250 回答
0

Boost充满了惊人和可怕的东西。

在 Windows 上一个简单的解决方法,可能是切换到managed_windows_shared_memory而不是managed_shared_memory,您可以解决各种令人讨厌的崩溃/挂起问题,并且似乎会导致一种崩溃/挂起问题,反过来又是由 Windows 文件系统行为和unix 文件系统行为,特别是,似乎在使用 boost 和managed_shared_memory在 Windows 上运行时,可能会违反 Windows 文件系统锁定限制。我被告知已经在 BOost 1.53 中完成了处理这个问题的工作,但是我正在使用 Boost 1.53,我仍然遇到这个问题。

在 Windows 上使用常规managed_shared_memory,您可以获得超出任何客户端或服务器应用程序生命周期的持久性。在某些人的情况下,这可能是可取的,因此解决方法对这些人来说并不是真正的解决方案。

然而,就我而言,无论如何我并不真正需要它,虽然我认为它会很方便,但事实证明它比它的价值更痛苦,至少在 Windows 上的当前 Boost 实现中是这样。

我还想指出,删除共享内存文件似乎是导致上述问题中遇到的问题的竞争条件的根本原因。围绕文件的创建和检查以及删除的正确同步似乎对于系统的实际实现至关重要,特别是,如果您让主服务器(服务器)删除共享内存文件,这似乎是一个毁灭性的问题虽然一些客户仍在使用它。似乎需要重新启动才能清除由此产生的锁 + NTFS 文件系统混乱。

如果我找到一个真正的解决方案,我会发布它,但上面的信息比我在其他任何地方都找不到。警惕 managed_shared_memory并考虑使用managed_windows_shared_memory并忘记尝试使“持久共享内存”的想法奏效。而是使用非持久性 windows-only managed_windows_shared_memory

解决这个问题,同时将managed_shared_memory类保留在我的应用程序中可能意味着将对对象的所有访问包装managed_shared_memory在另一个级别的进程间同步原语中,甚至使用原始的 Win32 API 互斥锁。Boost 可以做一些等效的事情,但可能会引入更多意外的复杂性。

(旁白:我是不是唯一一个认为 Template-All-the-things 在一般使用中已经过分使用的人,尤其是在 Boost 中,这些天?)

更新 2: 我找到了另一种冻结方法,managed_shared_memory这反过来会冻结您使用它的任何应用程序。我没想到用 Boost 创建死锁会这么容易,但它很容易做到。实现中的互斥锁代码将永远冻结,等待托管共享内存的其他用户离开而未释放的互斥锁。这种无休止的睡眠等待一个永远不会被释放的互斥锁,是这个 boost 进程间实现中的另一个深层设计缺陷,到目前为止,我已经计算了几个严重的设计缺陷,至少在 Windows 上是这样。也许它在 Linux 上运行良好。

展示这一点的代码是 find() 方法,调用如下:

   boost::interprocess::managed_shared_memory * segment;
   std::pair<MyType*, std::size_t> f = segment->find<MyType>(name);

这是互斥锁死锁(又名无限等待,冻结任务)的堆栈跟踪:

到达这里的唯一解决方案是在停止或杀死所有等待此互斥锁的挂起进程后删除共享内存区域。

>   myapp.exe!boost::interprocess::winapi::sched_yield()  Line 998  C++
    myapp.exe!boost::interprocess::ipcdetail::thread_yield()  Line 60 + 0xe bytes   C++
    myapp.exe!boost::interprocess::ipcdetail::spin_mutex::lock()  Line 71   C++
    myapp.exe!boost::interprocess::ipcdetail::spin_recursive_mutex::lock()  Line 91 C++
    myapp.exe!boost::interprocess::interprocess_recursive_mutex::lock()  Line 161   C++
    myapp.exe!boost::interprocess::scoped_lock<boost::interprocess::interprocess_recursive_mutex>::lock()  Line 280 C++
    myapp.exe!boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index>::priv_get_lock(bool use_lock=true)  Line 1340   C++
    myapp.exe!boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index>::priv_generic_find<char>(const char * name=0x00394290, boost::interprocess::iset_index<boost::interprocess::ipcdetail::index_config<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0> > > & index={...}, boost::interprocess::ipcdetail::in_place_interface & table={...}, unsigned int & length=1343657312, boost::interprocess::ipcdetail::bool_<1> is_intrusive={...}, bool use_lock=true)  Line 854 + 0x11 bytes  C++
    myapp.exe!boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index>::priv_find_impl<boost::container::map<AreaKeyType,DATA_AREA_DESC,std::less<AreaKeyType>,boost::interprocess::allocator<std::pair<AreaKeyType const ,DATA_AREA_DESC>,boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index> > > >(const char * name=0x00394290, bool lock=true)  Line 728 + 0x25 bytes    C++
    myapp.exe!boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index>::find<boost::container::map<AreaKeyType,DATA_AREA_DESC,std::less<AreaKeyType>,boost::interprocess::allocator<std::pair<AreaKeyType const ,DATA_AREA_DESC>,boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index> > > >(const char * name=0x00394290)  Line 423 + 0x1e bytes  C++
    myapp.exe!boost::interprocess::ipcdetail::basic_managed_memory_impl<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index,8>::find<boost::container::map<AreaKeyType,DATA_AREA_DESC,std::less<AreaKeyType>,boost::interprocess::allocator<std::pair<AreaKeyType const ,DATA_AREA_DESC>,boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index> > > >(boost::interprocess::ipcdetail::char_ptr_holder<char> name={...})  Line 346 + 0x23 bytes   C++
    myapp.exe!boost::interprocess::basic_managed_shared_memory<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index>::find<boost::container::map<AreaKeyType,DATA_AREA_DESC,std::less<AreaKeyType>,boost::interprocess::allocator<std::pair<AreaKeyType const ,DATA_AREA_DESC>,boost::interprocess::segment_manager<char,boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family,boost::interprocess::offset_ptr<void,int,unsigned int,0>,0>,boost::interprocess::iset_index> > > >(boost::interprocess::ipcdetail::char_ptr_holder<char> name={...})  Line 208 + 0x10 bytes  C++
    myapp.exe!CCommonMemory::AllocateOrFindAreaMap(const char * name=0x00394290)  Line 128  C++
于 2013-05-26T19:03:24.437 回答