Mir

Merge lp:~raof/mir/mir-module-throw-exception into lp:mir

Proposed by Chris Halse Rogers
Status: Work in progress
Proposed branch: lp:~raof/mir/mir-module-throw-exception
Merge into: lp:mir
Diff against target: 364 lines (+267/-11)
7 files modified
include/test/mir/test/doubles/null_platform.h (+0/-5)
src/common/sharedlibrary/CMakeLists.txt (+4/-1)
src/common/sharedlibrary/module_throw_exception.cpp (+94/-0)
src/common/symbols.map (+1/-0)
src/include/common/mir/module_throw_exception.h (+69/-0)
tests/acceptance-tests/test_server_shutdown.cpp (+15/-1)
tests/mir_test_framework/platform_graphics_throw.cpp (+84/-4)
To merge this branch: bzr merge lp:~raof/mir/mir-module-throw-exception
Reviewer Review Type Date Requested Status
Mir CI Bot continuous-integration Needs Fixing
Mir development team Pending
Review via email: mp+292467@code.launchpad.net

Commit message

Add MIR_MODULE_THROW_EXCEPTION.

This is like BOOST_THROW_EXCEPTION, but it additionally ensures that the dynamic library
that contains the throwing code is not unloaded before exception processing finishes.

This prevents annoying-to-debug SIGSEGVs when throwing an exception from a platform module
and stack unwinding causes the mir::UniqueModulePtr to be destroyed (and the DSO unloaded)
before the catch {} attempts to access code (such as destructors) or data (such as
static strings) from the now-unloaded DSO with hilarious consequences!

Description of the change

Add MIR_MODULE_THROW_EXCEPTION.

This is like BOOST_THROW_EXCEPTION, but it additionally ensures that the dynamic library
that contains the throwing code is not unloaded before exception processing finishes.

This prevents annoying-to-debug SIGSEGVs when throwing an exception from a platform module
and stack unwinding causes the mir::UniqueModulePtr to be destroyed (and the DSO unloaded)
before the catch {} attempts to access code (such as destructors) or data (such as
static strings) from the now-unloaded DSO with hilarious consequences!

Part of the infrastructure required to fix https://bugs.launchpad.net/ubuntu/+source/unity-system-compositor/+bug/1553549

I ran into this with graphics-eglstream. This, not anything the nvidia driver was doing, was the reason that all my exceptions caused SIGSEGV within libstdc++!

To post a comment you must log in.
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
3470. By Chris Halse Rogers

Babysit g++5 by explicitly using std::hash<uint32_t>.

I don't really know why g++5 can't handle std::unordered_map<MyEnumClass, bool> while g++6 can,
but that's apparently the case.

Make life easier on poor little g++5

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3470
https://mir-jenkins.ubuntu.com/job/mir-ci/882/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/908/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/945
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/936
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/936
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=vivid+overlay/918/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/918/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/918/console
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/918/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/918
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/918/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/918/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/882/rebuild

review: Needs Fixing (continuous-integration)

Unmerged revisions

3470. By Chris Halse Rogers

Babysit g++5 by explicitly using std::hash<uint32_t>.

I don't really know why g++5 can't handle std::unordered_map<MyEnumClass, bool> while g++6 can,
but that's apparently the case.

Make life easier on poor little g++5

3469. By Chris Halse Rogers

Use MIR_MODULE_THROW_EXCEPTION in graphics-throw.so

3468. By Chris Halse Rogers

Add MIR_MODULE_THROW_EXCEPTION.

This is like BOOST_THROW_EXCEPTION, but it additionally ensures that the dynamic library
that contains the throwing code is not unloaded before exception processing finishes.

This prevents annoying-to-debug SIGSEGVs when throwing an exception from a platform module
and stack unwinding causes the mir::UniqueModulePtr to be destroyed (and the DSO unloaded)
before the catch {} attempts to access code (such as destructors) or data (such as
static strings) from the now-unloaded DSO with hilarious consequences!

3467. By Chris Halse Rogers

Test exception-safety of all mg::Platform methods.

Spoiler: only the constructor is exception-safe. All other methods SIGSEGV trying to run
code or load data found in the DSO that's just been unloaded...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/test/mir/test/doubles/null_platform.h'
2--- include/test/mir/test/doubles/null_platform.h 2016-01-29 08:18:22 +0000
3+++ include/test/mir/test/doubles/null_platform.h 2016-04-21 07:37:38 +0000
4@@ -46,11 +46,6 @@
5 return mir::make_module_ptr<NullDisplay>();
6 }
7
8- std::shared_ptr<graphics::PlatformIPCPackage> connection_ipc_package()
9- {
10- return std::make_shared<graphics::PlatformIPCPackage>();
11- }
12-
13 mir::UniqueModulePtr<graphics::PlatformIpcOperations> make_ipc_operations() const override
14 {
15 return mir::make_module_ptr<NullPlatformIpcOperations>();
16
17=== modified file 'src/common/sharedlibrary/CMakeLists.txt'
18--- src/common/sharedlibrary/CMakeLists.txt 2016-03-23 06:39:56 +0000
19+++ src/common/sharedlibrary/CMakeLists.txt 2016-04-21 07:37:38 +0000
20@@ -17,7 +17,10 @@
21 add_library(mirsharedsharedlibrary OBJECT
22 module_deleter.cpp
23 shared_library.cpp
24- shared_library_prober.cpp ${PROJECT_SOURCE_DIR}/src/include/common/mir/shared_library_prober.h
25+ shared_library_prober.cpp
26+ ${PROJECT_SOURCE_DIR}/src/include/common/mir/shared_library_prober.h
27+ module_throw_exception.cpp
28+ ${PROJECT_SOURCE_DIR}/src/include/common/mir/module_throw_exception.h
29 )
30
31 list(APPEND MIR_COMMON_SOURCES
32
33=== added file 'src/common/sharedlibrary/module_throw_exception.cpp'
34--- src/common/sharedlibrary/module_throw_exception.cpp 1970-01-01 00:00:00 +0000
35+++ src/common/sharedlibrary/module_throw_exception.cpp 2016-04-21 07:37:38 +0000
36@@ -0,0 +1,94 @@
37+/*
38+ * Copyright © 2016 Canonical Ltd.
39+ *
40+ * This program is free software: you can redistribute it and/or modify it
41+ * under the terms of the GNU Lesser General Public License version 3,
42+ * as published by the Free Software Foundation.
43+ *
44+ * This program is distributed in the hope that it will be useful,
45+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
46+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47+ * GNU Lesser General Public License for more details.
48+ *
49+ * You should have received a copy of the GNU Lesser General Public License
50+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
51+ *
52+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
53+ */
54+
55+#include "mir/module_throw_exception.h"
56+
57+#include "mir/shared_library.h"
58+#include "mir/log.h"
59+
60+#include <memory>
61+#include <boost/exception/info.hpp>
62+#include <boost/exception/enable_error_info.hpp>
63+
64+
65+namespace
66+{
67+
68+/*
69+ * We use a ModuleGuard type with custom to_string() implementation to control
70+ * how boost::diagnostic_information displays the error_info.
71+ */
72+struct ModuleGuard
73+{
74+ ModuleGuard(char const* library)
75+ : library_name{library},
76+ library{std::make_shared<mir::SharedLibrary>(library)}
77+ {
78+ }
79+
80+ std::string library_name;
81+ std::shared_ptr<mir::SharedLibrary> library;
82+};
83+
84+std::string to_string(ModuleGuard const& library)
85+{
86+ return library.library_name;
87+}
88+
89+typedef boost::error_info<struct thrown_from_dso, ModuleGuard> module_guard;
90+}
91+
92+void mir::detail::module_throw_exception(
93+ std::exception_ptr exception,
94+ char const* library,
95+ char const* function_name,
96+ char const* file_name,
97+ int line_number)
98+{
99+ /*
100+ * We can't accept a boost::exception const&, add the information, and then throw it because
101+ * boost::exception is abstract.
102+ *
103+ * Likewise, we *shouldn't* accept a std::exception const&, add the information, and throw it because
104+ * that erases the original type of the exception.
105+ *
106+ * So, instead we take a std::exception_ptr, rethrow it, and add the data in the catch branch.
107+ * This preserves the original exception type.
108+ */
109+ try
110+ {
111+ std::rethrow_exception(exception);
112+ }
113+ catch (boost::exception const& e)
114+ {
115+ e << ::boost::throw_function(function_name);
116+ e << ::boost::throw_file(file_name);
117+ e << ::boost::throw_line(line_number);
118+ e << module_guard(library);
119+ throw;
120+ }
121+ catch (...)
122+ {
123+ mir::log_warning("Called module_throw_exception with an exception not deriving from boost::exception.");
124+ mir::log_warning("This will *NOT* ensure the module remains live for the duration of exception processing.");
125+ mir::log_warning("If Mir subsequently crashes without useful error message, this was likely the cause.");
126+ mir::log_warning("Called from:\n\tDSO: %s\n\tfile: %s\n\tfunction: %s\n\tline number: %i",
127+ library, file_name, function_name, line_number);
128+ throw;
129+ }
130+}
131
132=== modified file 'src/common/symbols.map'
133--- src/common/symbols.map 2016-04-04 03:30:04 +0000
134+++ src/common/symbols.map 2016-04-21 07:37:38 +0000
135@@ -222,6 +222,7 @@
136 MIR_COMMON_unreleased {
137 global:
138 extern "C++" {
139+ mir::detail::module_throw_exception*;
140 MirEvent::to_surface*;
141 MirEvent::to_resize*;
142 MirEvent::to_orientation*;
143
144=== added file 'src/include/common/mir/module_throw_exception.h'
145--- src/include/common/mir/module_throw_exception.h 1970-01-01 00:00:00 +0000
146+++ src/include/common/mir/module_throw_exception.h 2016-04-21 07:37:38 +0000
147@@ -0,0 +1,69 @@
148+/*
149+ * Copyright © 2016 Canonical Ltd.
150+ *
151+ * This program is free software: you can redistribute it and/or modify it
152+ * under the terms of the GNU Lesser General Public License version 3,
153+ * as published by the Free Software Foundation.
154+ *
155+ * This program is distributed in the hope that it will be useful,
156+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
157+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
158+ * GNU Lesser General Public License for more details.
159+ *
160+ * You should have received a copy of the GNU Lesser General Public License
161+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
162+ *
163+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
164+ */
165+
166+#ifndef MIR_MODULE_THROW_EXCEPTION_H_
167+#define MIR_MODULE_THROW_EXCEPTION_H_
168+
169+#include "mir/libname.h"
170+
171+#include <exception>
172+#include <boost/exception/enable_error_info.hpp>
173+
174+/**
175+ * Throw an exception, ensuring the DSO outlives exception processing
176+ *
177+ * This acts like BOOST_THROW_EXCEPTION, except that it additionally ensures that the
178+ * DSO that contains the call will not be unloaded until exception processing has completed.
179+ *
180+ * This is useful, and should be used, for throwing exceptions from Mir platform modules,
181+ * where otherwise the destruction of mir::UniqueModulePtr during stack unwinding may result
182+ * in the dynamic object containing exception information being unloaded before that exception
183+ * information is referenced.
184+ *
185+ * All exceptions thrown from platform modules should be thrown with this macro.
186+ *
187+ * \param [in] exception The exception to throw. There are no constraints on this parameter,
188+ * but, like all exceptions, it SHOULD derive from std::exception.
189+ */
190+#define MIR_MODULE_THROW_EXCEPTION(exception) \
191+ ::mir::detail::module_throw_exception( \
192+ std::make_exception_ptr(::boost::enable_error_info(exception)), \
193+ mir::libname(), \
194+ __PRETTY_FUNCTION__, \
195+ __FILE__, \
196+ (int)__LINE__)
197+
198+namespace mir
199+{
200+namespace detail
201+{
202+/**
203+ * Implementation detail of MIR_MODULE_THROW_EXCEPTION.
204+ *
205+ * It SHOULD NOT be called directly.
206+ */
207+void module_throw_exception(
208+ std::exception_ptr exception,
209+ char const* library,
210+ char const* function_name,
211+ char const* file_name,
212+ int line_number) __attribute__((noreturn));
213+}
214+}
215+
216+#endif //MIR_MODULE_THROW_EXCEPTION_H_
217
218=== modified file 'tests/acceptance-tests/test_server_shutdown.cpp'
219--- tests/acceptance-tests/test_server_shutdown.cpp 2016-03-23 06:39:56 +0000
220+++ tests/acceptance-tests/test_server_shutdown.cpp 2016-04-21 07:37:38 +0000
221@@ -55,10 +55,13 @@
222 }
223 }
224
225+using ServerShutdownWithGraphicsPlatformException = ::testing::TestWithParam<char const*>;
226+
227 // Regression test for LP: #1528135
228-TEST(ServerShutdownWithException, clean_shutdown_on_plugin_construction_exception)
229+TEST_P(ServerShutdownWithGraphicsPlatformException, clean_shutdown_on_plugin_construction_exception)
230 {
231 char const* argv = "ServerShutdownWithException";
232+ mtf::TemporaryEnvironmentValue exception_set("MIR_TEST_FRAMEWORK_THROWING_PLATFORM_EXCEPTIONS", GetParam());
233 mtf::TemporaryEnvironmentValue graphics_platform("MIR_SERVER_PLATFORM_GRAPHICS_LIB", mtf::server_platform("graphics-throw.so").c_str());
234 mtf::TemporaryEnvironmentValue input_platform("MIR_SERVER_PLATFORM_INPUT_LIB", mtf::server_platform("input-stub.so").c_str());
235 mir::Server server;
236@@ -70,6 +73,17 @@
237 server.run();
238 }
239
240+INSTANTIATE_TEST_CASE_P(
241+ PlatformExceptions,
242+ ServerShutdownWithGraphicsPlatformException,
243+ ::testing::Values(
244+ "constructor",
245+ "create_buffer_allocator",
246+ "create_display",
247+ "make_ipc_operations",
248+ "egl_native_display"
249+ ));
250+
251 using ServerShutdownDeathTest = ServerShutdown;
252
253 TEST_F(ServerShutdownDeathTest, abort_removes_endpoint)
254
255=== modified file 'tests/mir_test_framework/platform_graphics_throw.cpp'
256--- tests/mir_test_framework/platform_graphics_throw.cpp 2016-01-18 08:54:58 +0000
257+++ tests/mir_test_framework/platform_graphics_throw.cpp 2016-04-21 07:37:38 +0000
258@@ -20,8 +20,14 @@
259 #include "mir/test/doubles/null_platform.h"
260 #include "mir/assert_module_entry_point.h"
261 #include "mir/libname.h"
262+#include "mir/module_throw_exception.h"
263
264 #include <boost/throw_exception.hpp>
265+#include <stdlib.h>
266+#include <unordered_map>
267+#include <libgen.h>
268+#include <malloc.h>
269+#include <mir/shared_library.h>
270
271 namespace mg = mir::graphics;
272 namespace mo = mir::options;
273@@ -35,13 +41,87 @@
274
275 namespace
276 {
277-class ExceptionThrowingPlatform : public mir::test::doubles::NullPlatform
278+class ExceptionThrowingPlatform : public mg::Platform
279 {
280 public:
281 ExceptionThrowingPlatform()
282- {
283- BOOST_THROW_EXCEPTION(std::runtime_error("Exception during construction"));
284- }
285+ : should_throw{parse_exception_request(getenv("MIR_TEST_FRAMEWORK_THROWING_PLATFORM_EXCEPTIONS"))}
286+ {
287+ if (should_throw.at(ExceptionLocation::at_constructor))
288+ MIR_MODULE_THROW_EXCEPTION(std::runtime_error("Exception during construction"));
289+
290+ std::unique_ptr<char, void(*)(void*)> library_path{strdup(mir::libname()), &free};
291+ auto platform_path = dirname(library_path.get());
292+
293+ mir::SharedLibrary stub_platform_library{std::string(platform_path) + "/graphics-dummy.so"};
294+ auto create_stub_platform = stub_platform_library.load_function<mg::CreateHostPlatform>("create_host_platform");
295+
296+ stub_platform = create_stub_platform(nullptr, nullptr, nullptr);
297+ }
298+
299+ mir::UniqueModulePtr<mg::GraphicBufferAllocator> create_buffer_allocator() override
300+ {
301+ if (should_throw.at(ExceptionLocation::at_create_buffer_allocator))
302+ MIR_MODULE_THROW_EXCEPTION(std::runtime_error("Exception during create_buffer_allocator"));
303+
304+ return stub_platform->create_buffer_allocator();
305+ }
306+
307+ mir::UniqueModulePtr<mg::Display> create_display(
308+ std::shared_ptr<mg::DisplayConfigurationPolicy> const& ptr,
309+ std::shared_ptr<mg::GLConfig> const& shared_ptr) override
310+ {
311+ if (should_throw.at(ExceptionLocation::at_create_display))
312+ MIR_MODULE_THROW_EXCEPTION(std::runtime_error("Exception during create_display"));
313+
314+ return stub_platform->create_display(ptr, shared_ptr);
315+ }
316+
317+ mir::UniqueModulePtr<mg::PlatformIpcOperations> make_ipc_operations() const override
318+ {
319+ if (should_throw.at(ExceptionLocation::at_make_ipc_operations))
320+ MIR_MODULE_THROW_EXCEPTION(std::runtime_error("Exception during make_ipc_operations"));
321+
322+ return stub_platform->make_ipc_operations();
323+ }
324+
325+ EGLNativeDisplayType egl_native_display() const override
326+ {
327+ if (should_throw.at(ExceptionLocation::at_egl_native_display))
328+ MIR_MODULE_THROW_EXCEPTION(std::runtime_error("Exception during egl_native_display"));
329+
330+ return stub_platform->egl_native_display();
331+ }
332+
333+private:
334+ enum ExceptionLocation : uint32_t
335+ {
336+ at_constructor,
337+ at_create_buffer_allocator,
338+ at_create_display,
339+ at_make_ipc_operations,
340+ at_egl_native_display
341+ };
342+
343+ static std::unordered_map<ExceptionLocation, bool, std::hash<uint32_t>> parse_exception_request(char const* request)
344+ {
345+ std::unordered_map<ExceptionLocation, bool, std::hash<uint32_t>> requested_exceptions;
346+ requested_exceptions[ExceptionLocation::at_constructor] =
347+ static_cast<bool>(strstr(request, "constructor"));
348+ requested_exceptions[ExceptionLocation::at_create_buffer_allocator] =
349+ static_cast<bool>(strstr(request, "create_buffer_allocator"));
350+ requested_exceptions[ExceptionLocation::at_create_display] =
351+ static_cast<bool>(strstr(request, "create_display"));
352+ requested_exceptions[ExceptionLocation::at_make_ipc_operations] =
353+ static_cast<bool>(strstr(request, "make_ipc_operations"));
354+ requested_exceptions[ExceptionLocation::at_egl_native_display] =
355+ static_cast<bool>(strstr(request, "egl_native_display"));
356+
357+ return requested_exceptions;
358+ };
359+
360+ std::unordered_map<ExceptionLocation, bool, std::hash<uint32_t>> const should_throw;
361+ mir::UniqueModulePtr<mg::Platform> stub_platform;
362 };
363
364 }

Subscribers

People subscribed via source and target branches