如何调试或修复涉及boost::interprocess managed_shared_memory的无限循环和堆栈破坏问题?

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打开一个共享内存区域。

第一个复杂的boost-flavor模板代码所在位置未被识别,因此在基于Visual C++ 2008的项目中,第一个boost::interprocess::interprocess_exception first-chance异常未被抛出或标识。然而,通过单步调试汇编语言视图,我找到了出错的代码。

导致所有问题开始恶化的是我的代码的顶层行:

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

上面的managed_shared_memory类来自于interprocess_fwd.hpp,是Boost共享内存API/头文件的标准部分。由于它是基于模板的,以上内容会扩展为一个大约2K字符长的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()  

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

我该如何调试?其次,boost-interprocess在Visual C++ 2008中是否存在某些已知问题?第三,下面的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)
   {}  

总之,不要试图在家里调试这个问题,孩子们。以下是发生的情况:

enter image description here

最终,我利用类忍者的能力单步跟踪了数百万行汇编代码,克服了 Visual C++ 2008 的恶意调试器限制,并找到了有问题的代码。

事实上,这就是引起问题的地方:create_device<FileBased>(dev...

这里提供一些背景:managed_open_or_create_impl.h 第 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();
         }
      }

1
+1 鼓励自由发挥的不满表情。 - John Dibling
这里有很多需要吸收的内容,但你是否考虑过第一次机会异常可能完全是良性的可能性? - John Dibling
我进行了一些编辑,并删除了这个问题中非常不专业的内容。对于任何不得不阅读原始版本的人,我深表歉意。 - Warren P
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;
}

顺便说一下,如果有人知道我可以通过哪些官方渠道来尝试获取验证并将其添加到boost中,请告诉我,因为我不希望在不知道其全部影响的情况下修改此类内容。

希望这能帮到你!


我认为Boost邮件列表是发布这个问题的好地方,因为Boost :: Interprocess的作者可以说出是否有一个好的一般修复方法。 - Warren P

0
Boost充满了惊奇和可怕的事情。
在Windows上,一个简单的解决方法可能是切换到managed_windows_shared_memory而不是managed_shared_memory,你可以解决各种讨厌的崩溃/挂起问题,其中一种崩溃/挂起问题似乎是由于Windows文件系统行为和Unix文件系统行为之间的差异引起的,特别是,在Windows上使用boost和managed_shared_memory时,可能会违反Windows文件系统锁定限制。我被告知,BOost 1.53已经完成了处理这个问题的努力,但我正在使用Boost 1.53,我仍然有这个问题。
在Windows上使用常规的managed_shared_memory,你可以获得超出任何客户端或服务器应用程序生命周期的持久性。这可能在某些人的情况下是可取的,因此对于这些人来说,解决方法并不是真正的解决方法。
然而,在我的情况下,我实际上并不需要那个,尽管我认为它会很方便,但事实证明它比它值得的更加痛苦,至少在当前的Boost实现中在Windows上是这样。
我想指出,共享内存文件的删除似乎是导致上述问题中出现竞争条件的根本原因。在创建、检查和删除文件周围进行适当的同步似乎对系统的实际实现至关重要,特别是如果您的主服务器在某些客户端仍在使用它时删除了共享内存文件,则这似乎是一个灾难性的问题。需要重新启动以清除由此产生的锁定+NTFS文件系统混乱。
如果我找到真正的解决方案,我会发布它,但以上信息比我在任何其他地方找到的信息都更多。请注意managed_shared_memory,并考虑使用managed_windows_shared_memory,不要试图让“持久共享内存”思想起作用。相反,使用非持久的仅限于Windows的managed_windows_shared_memory
解决这个问题,同时保持我的应用程序中的managed_shared_memory类可能意味着在另一层进程间同步原语中包装对managed_shared_memory对象的所有访问,甚至使用原始的Win32 API互斥体。Boost可以做出类似的事情,但可能会引入更多的意外复杂性。

(顺便说一句:我是不是唯一一个认为在一般使用中,特别是在Boost中,Template-All-the-things已经被过度使用了?)

更新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++

你有没有修复更新2部分?当我使用它时,我的消费者代码会无限期地休眠。 MyDeque *Mydeque = segment.find<MyDeque>("dequeName").first; - PrimeOfKnights
不行。我发现任何我不能读懂的聪明代码都不是我代码库中的正确解决方案。我转而使用更简单、更易于阅读的东西。DDE。没错。DDE。在20xx年。 - Warren P
我不确定你是否尝试过这个。我的第一个应用程序是在使用 Visual Studio 64 位构建的,我的消费者应用程序是在 32 位上运行的。然后我把两个都改成了 64 位构建,现在可以工作了。希望对你有所帮助。 - PrimeOfKnights

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接