Voo
Voo

Reputation: 30235

SIGFPE when accessing unordered_map

I have an unordered_map<Block, int> with Block being a simple struct defined as follows:

struct Block {
    size_t start;
    size_t end;

    bool operator==(const Block& b) const {
        return start == b.start && end == b.end;
    }
};

namespace std {
template<>
struct hash<Block> {
    size_t operator()(const Block& b) const {
        return b.start;
    }
};
} 

When trying to access the map, I do get the following error message in gdb (same for both g++ 4.7.1 as well as clang++ 3.1):

Program received signal SIGFPE, Arithmetic exception.
0x0000000000401e0b in std::__detail::_Mod_range_hashing::operator() (this=0x7fffffffd8e0, __num=0, __den=0)
    at /usr/include/c++/4.7/bits/hashtable_policy.h:245
245     { return __num % __den; }

My libstdc++ version is 3.4.17 (i.e. the version from GCC 4.7)

Relevant backtrace:

#0  0x0000000000401e0b in std::__detail::_Mod_range_hashing::operator() (this=0x7fffffffd8e0, __num=0, __den=0)
    at /usr/include/c++/4.7/bits/hashtable_policy.h:245
#1  0x0000000000407199 in std::__detail::_Hash_code_base<Block, std::pair<Block const, int>, std::_Select1st<std::pair<Block const, int> >, std::hash<Block>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, true>::_M_bucket_index (this=0x7fffffffd8e0, __c=0, __n=0) at /usr/include/c++/4.7/bits/hashtable_policy.h:787
#2  0x0000000000405230 in std::_Hashtable<Block, std::pair<Block const, int>, std::allocator<std::pair<Block const, int> >, std::_Select1st<std::pair<Block const, int> >, std::equal_to<Block>, std::hash<Block>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, true, false, true>::_M_bucket_index
    (this=0x7fffffffd8e0, __k=..., __c=0) at /usr/include/c++/4.7/bits/hashtable.h:466
#3  0x00000000004038de in std::__detail::_Map_base<Block, std::pair<Block const, int>, std::_Select1st<std::pair<Block const, int> >, true, std::_Hashtable<Block, std::pair<Block const, int>, std::allocator<std::pair<Block const, int> >, std::_Select1st<std::pair<Block const, int> >, std::equal_to<Block>, std::hash<Block>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, true, false, true> >::at (
    this=0x7fffffffd8e0, __k=...) at /usr/include/c++/4.7/bits/hashtable_policy.h:474
#4  0x0000000000403001 in SplicedAlignment::FindOptimalEndBlock() const::{lambda(Block const&)#1}::operator()(Block const&) const (__closure=0x7fffffffd990, block=...) at splicing.cpp:151
#5  0x00000000004040b3 in std::for_each<__gnu_cxx::__normal_iterator<Block const*, std::vector<Block, std::allocator<Block> > >, SplicedAlignment::FindOptimalEndBlock() const::{lambda(Block const&)#1}>(__gnu_cxx::__normal_iterator<Block const*, std::vector<Block, std::allocator<Block> > >, SplicedAlignment::FindOptimalEndBlock() const::{lambda(Block const&)#1}, SplicedAlignment::FindOptimalEndBlock() const::{lambda(Block const&)#1}) (__first=..., __last=..., __f=...)
    at /usr/include/c++/4.7/bits/stl_algo.h:4442

Edit: I didn't think it would actually make a difference where I call the function as long as I give it the same arguments, but apparently it does:

std::for_each(blocks.begin(), blocks.end(), [&](const Block& block) {
   map.at(block);
}

leads to the error, while just having:

const Block& block = blocks[0];
map.at(block);

works perfectly fine (blocks being a simple vector<Block>&)

Upvotes: 18

Views: 7921

Answers (6)

Per Lundberg
Per Lundberg

Reputation: 4220

In line with others, here's what led me to see SIGFPE exceptions in STL code.

I was seeing exceptions like this. I've printed the parameters to make it clear that what is happening is a division by zero error; the parameter values are also visible on the 0x00007fff743ad089 line if you scroll to the right.

❯ gdb --args src/Perlang.ConsoleApp/bin/Debug/net8.0/perlang  docs/examples/quickstart/hello_world.per
GNU gdb (Debian 13.1-3) 13.1
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from src/Perlang.ConsoleApp/bin/Debug/net8.0/perlang...
(No debugging symbols found in src/Perlang.ConsoleApp/bin/Debug/net8.0/perlang)
(gdb) run
Starting program: /home/per/git/perlang/src/Perlang.ConsoleApp/bin/Debug/net8.0/perlang docs/examples/quickstart/hello_world.per
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7fff7820f6c0 (LWP 675606)]
[New Thread 0x7fff77a0e6c0 (LWP 675607)]
[New Thread 0x7fff7720d6c0 (LWP 675608)]
[New Thread 0x7fff76a0c6c0 (LWP 675609)]
[New Thread 0x7fff761426c0 (LWP 675610)]
[New Thread 0x7fbecb4ce6c0 (LWP 675623)]

Thread 1 "perlang" received signal SIGFPE, Arithmetic exception.
0x00007fff743ad089 in std::__detail::_Mod_range_hashing::operator() (this=0x7fffffffacef, __num=1, __den=0)
    at /usr/include/c++/12/bits/hashtable_policy.h:488
488     { return __num % __den; }
(gdb) print __num
$1 = 1
(gdb) print __den
$2 = 0

The reason: outdated C++ bindings causing the constructor to not be called

My use case is a bit esoteric; I am calling C++ code from C# using CppSharp. This means that I have C# binding code (generated using CppSharp) which may or may not be up-to-date with what the C/C++ code looks like at the moment. In this case, it was not up-to-date; I think it was perhaps generated before the field existed in the class. This meant that the call to the constructor was missing => my std::unordered_map was never initialized properly. Regenerating the bindings gave me a diff that included this (note particularly the __Internal.ctor call which calls the C++ constructor).

diff --git src/Perlang.Common/PerlangCli.cs src/Perlang.Common/PerlangCli.cs
index d1eceb6..57f2137 100644
--- src/Perlang.Common/PerlangCli.cs
+++ src/Perlang.Common/PerlangCli.cs
@@ -1309,6 +1309,12 @@ public unsafe partial class StringTokenTypeDictionary : IDisposable
         [SuppressUnmanagedCodeSecurity, DllImport("perlang_cli", EntryPoint = "_ZN25StringTokenTypeDictionaryC2ERKS_", CallingConvention = __CallingConvention.Cdecl)]
         internal static extern void cctor(__IntPtr __instance, __IntPtr _0);
 
+        [SuppressUnmanagedCodeSecurity, DllImport("perlang_cli", EntryPoint = "_ZN25StringTokenTypeDictionaryC2Ev", CallingConvention = __CallingConvention.Cdecl)]
+        internal static extern void ctor(__IntPtr __instance);
+
+        [SuppressUnmanagedCodeSecurity, DllImport("perlang_cli", EntryPoint = "_ZN25StringTokenTypeDictionaryD2Ev", CallingConvention = __CallingConvention.Cdecl)]
+        internal static extern void dtor(__IntPtr __instance);
+
         [SuppressUnmanagedCodeSecurity, DllImport("perlang_cli", EntryPoint = "_ZN25StringTokenTypeDictionary3addEPKcN9TokenType9TokenTypeE", CallingConvention = __CallingConvention.Cdecl)]
         internal static extern void Add(__IntPtr __instance, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CppSharp.Runtime.UTF8Marshaller))] string _0, global::Perlang.TokenType _1);
 
@@ -1365,7 +1371,7 @@ public unsafe partial class StringTokenTypeDictionary : IDisposable
     private static void* __CopyValue(__Internal native)
     {
         var ret = Marshal.AllocHGlobal(sizeof(__Internal));
-        *(__Internal*) ret = native;
+        global::StringTokenTypeDictionary.__Internal.cctor(ret, new __IntPtr(&native));
         return ret.ToPointer();
     }
 
@@ -1383,19 +1389,23 @@ public unsafe partial class StringTokenTypeDictionary : IDisposable
         __Instance = new __IntPtr(native);
     }
 
-    public StringTokenTypeDictionary()
+    public StringTokenTypeDictionary(global::StringTokenTypeDictionary _0)
     {
         __Instance = Marshal.AllocHGlobal(sizeof(global::StringTokenTypeDictionary.__Internal));
         __ownsNativeInstance = true;
         __RecordNativeToManagedMapping(__Instance, this);
+        if (ReferenceEquals(_0, null))
+            throw new global::System.ArgumentNullException("_0", "Cannot be null because it is a C++ reference (&).");
+        var __arg0 = _0.__Instance;
+        __Internal.cctor(__Instance, __arg0);
     }
 
-    public StringTokenTypeDictionary(global::StringTokenTypeDictionary _0)
+    public StringTokenTypeDictionary()
     {
         __Instance = Marshal.AllocHGlobal(sizeof(global::StringTokenTypeDictionary.__Internal));
         __ownsNativeInstance = true;
         __RecordNativeToManagedMapping(__Instance, this);
-        *((global::StringTokenTypeDictionary.__Internal*) __Instance) = *((global::StringTokenTypeDictionary.__Internal*) _0.__Instance);
+        __Internal.ctor(__Instance);
     }
 
     public void Dispose()
@@ -1411,6 +1421,8 @@ public unsafe partial class StringTokenTypeDictionary : IDisposable
             return;
         NativeToManagedMap.TryRemove(__Instance, out _);
         DisposePartial(disposing);
+        if (callNativeDtor)
+            __Internal.dtor(__Instance);
         if (__ownsNativeInstance)
             Marshal.FreeHGlobal(__Instance);
         __Instance = IntPtr.Zero;

Bottom line: Always make sure your bindings are up-to-date

This is very basic but still deserves to be said. If you are calling into C++ code from some other language (C# in my case but it could really be anything: Rust, Go, you name it) where you use some form of "binding generator", always make sure your bindings are up-to-date at all times. It's otherwise easy to run into these kind of problems. You can even take it to the extreme and auto-generate the bindings on every local build, but depending on the overhead for running the binding generator, this may or may not be a good idea.

Upvotes: 0

user1641854
user1641854

Reputation:

In my case the same problem occurred because of static init fiasco. From one object file I called emplace() for static std::unordered_map which was defined in the second object file. Because of at start data was at BSS, value of bucket count was zero => SIGFPE.

Upvotes: 11

Jonathan Wakely
Jonathan Wakely

Reputation: 171403

Aside: if your hash function cannot throw then it's quite important to give it a noexcept exception-specification, otherwise the hash table needs to store every element's hash code alongside the element itself (which increases memory usage and affects performance) so that container operations that must not throw do not have to recalculate the hash code.

The SIGFPE implies a divide by zero and from the backtrace it happens here:

    { return __num % __den; }

which probably means __den is zero. That value comes from the hash map's bucket count, which should not be zero.

Can you confirm that when it crashes m._M_bucket_count is zero?

If so, that either indicates you've corrupted the map somehow (have you tried compiling with -D_GLIBCXX_DEBUG to turn on the libstdc++ Debug Mode checks? Have you tried running under valgrind?) or there's a bug in the libstdc++ code (which is possible, but unlikely).

Some of the other answers below give examples of how the map can be corrupted, e.g. allocating storage for it with malloc but not actually constructing an object in that storage, or overwriting the object with memset.

Another possibility is that your hash map is a global variable, and you are accessing it from the constructor of another global variable, which runs into the Static Initialization Order Fiasco. If the map is used before its constructor runs, then the bucket count will be zero.

Upvotes: 20

Amy
Amy

Reputation: 109

Will also post my circumstances for the FPE in STL code.

The code used malloc() to allocate the std::unordered_map. This of course does not call the constructor, leading to an aberrant state.

Upvotes: 2

Irfy
Irfy

Reputation: 9597

Following the tradition of other users reporting their own circumstances that caused FPE in STL code, here goes mine:

We had perfectly fine code that started dying with SIGFPE in std::map since we upgraded the compiler. Turns out, I compiled gcc 9.2 with gcc 4.8.5 in c++11 mode which is not a normal thing to do -- the normal gcc compilation is always performed in gnu++98 mode.

This caused the 4.8.5 gcc, which has an incomplete/unofficial support for c++11, to produce a faulty 9.2 gcc, which sporadically produced binaries that would exhibit various unrelated failures. For example, trying to debug the above SIGFPE led to SIGSEGV in std::basic_string destructor when compiled with -D_GLIBCXX_DEBUG, and unrelated memory corruption detected with -fsanitize=address.

Lesson here is two-fold:

  1. Do not modify the gcc build system unless you know very darn well what you're doing.
  2. Sometimes the fault can be in the compiler (but then again, the fault ends up being with whomever compiled the compiler...).

Upvotes: 0

Vitalik Verhovodov
Vitalik Verhovodov

Reputation: 674

I had exactly the same problem. It was caused by memset accidentally applied to container data.

Upvotes: 4

Related Questions