Index: ps/trunk/build/premake/premake.lua =================================================================== --- ps/trunk/build/premake/premake.lua (revision 5150) +++ ps/trunk/build/premake/premake.lua (revision 5151) @@ -1,908 +1,908 @@ addoption("atlas", "Include Atlas scenario editor packages") addoption("collada", "Include COLLADA packages (requires FCollada library)") addoption("icc", "Use Intel C++ Compiler (Linux only; should use either \"--cc icc\" or --without-pch too, and then set CXX=icpc before calling make)") addoption("outpath", "Location for generated project files") addoption("without-tests", "Disable generation of test projects") addoption("without-pch", "Disable generation and usage of precompiled headers") dofile("functions.lua") dofile("extern_libs.lua") -- Set up the Project project.name = "pyrogenesis" project.bindir = "../../binaries/system" project.libdir = "../../binaries/system" project.debugdir = "../../binaries/system" if not options["outpath"] then error("You must specify the 'outpath' parameter") end project.path = options["outpath"] project.configs = { "Debug", "Release", "Testing" } if OS == "windows" then project.nasmpath = "../../build/bin/nasm.exe" project.cxxtestpath = "../../build/bin/cxxtestgen.exe" has_broken_pch = false else project.cxxtestpath = "../../build/bin/cxxtestgen.pl" -- GCC bug (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10591) - PCH breaks anonymous namespaces -- Fixed in 4.2.0, but we have to disable PCH for earlier versions, else -- it conflicts annoyingly with wx 2.8 headers. -- It's too late to do this test by the time we start compiling the PCH file, so -- do the test in this build script instead (which is kind of ugly - please fix if -- you have a better idea) if not options["icc"] then os.execute("gcc -dumpversion > .gccver.tmp") f = io.open(".gccver.tmp") major, dot, minor = f:read(1, 1, 1) major = 0+major -- coerce to number minor = 0+minor has_broken_pch = (major < 4 or (major == 4 and minor < 2)) end end source_root = "../../../source/" -- default for most projects - overridden by local in others -- Rationale: packages should not have any additional include paths except for -- those required by external libraries. Instead, we should always write the -- full relative path, e.g. #include "maths/Vector3d.h". This avoids confusion -- ("which file is meant?") and avoids enormous include path lists. -- packages: engine static libs, main exe, atlas, atlas frontends, test. -------------------------------------------------------------------------------- -- package helper functions -------------------------------------------------------------------------------- function package_set_target(package_name) -- Note: On Windows, ".exe" is added on the end, on unices the name is used directly package.config["Debug" ].target = package_name.."_dbg" package.config["Testing"].target = package_name.."_test" package.config["Release"].target = package_name local obj_dir_prefix = "obj/"..package_name.."_" package.config["Debug" ].objdir = obj_dir_prefix.."Debug" package.config["Testing"].objdir = obj_dir_prefix.."Test" package.config["Release"].objdir = obj_dir_prefix.."Release" end function package_set_build_flags() package.buildflags = { "with-symbols", "no-edit-and-continue" } if not options["icc"] then tinsert(package.buildflags, "extra-warnings") -- this causes far too many warnings/remarks on ICC end -- PremakeWiki says with-symbols and optimize are automatically set for -- Debug and Release builds, respectively. doesn't happen though, so do it manually. package.config["Testing"].buildflags = { "no-runtime-checks" } package.config["Testing"].defines = { "TESTING" } package.config["Release"].buildflags = { "no-runtime-checks", "optimize-speed" } package.config["Release"].defines = { "NDEBUG" } -- various platform-specific build flags if OS == "windows" then tinsert(package.buildflags, "no-rtti") -- use native wchar_t type (not typedef to unsigned short) tinsert(package.buildflags, "native-wchar_t") else -- *nix if options["icc"] then tinsert(package.buildoptions, { "-w1", -- "-Wabi", -- "-Wp64", -- complains about OBJECT_TO_JSVAL which is annoying "-Wpointer-arith", "-Wreturn-type", -- "-Wshadow", "-Wuninitialized", "-Wunknown-pragmas", "-Wunused-function", }) tinsert(package.config["Debug"].buildoptions, { "-O0", -- ICC defaults to -O2 }) else tinsert(package.buildoptions, { "-Wall", "-Wunused-parameter", -- needs to be enabled explicitly "-Wno-switch", -- enumeration value not handled in switch "-Wno-reorder", -- order of initialization list in constructors "-Wno-non-virtual-dtor", "-Wno-invalid-offsetof", -- offsetof on non-POD types -- do something (?) so that ccache can handle compilation with PCH enabled "-fpch-preprocess", -- speed up math functions by inlining. warning: this may result in -- non-IEEE-conformant results, but haven't noticed any trouble so far. "-ffast-math", }) end tinsert(package.buildoptions, { -- Hide symbols in dynamic shared objects by default, for efficiency and for equivalence with -- Windows - they should be exported explicitly with __attribute__ ((visibility ("default"))) "-fvisibility=hidden", "-fvisibility-inlines-hidden", }) -- Include and lib paths: -- X11 includes may be installed in one of a gaszillion of three places -- And MacPorts uses /opt/local as its prefix -- Famous last words: "You can't include too much! ;-)" package.includepaths = { "/usr/X11R6/include/X11", "/usr/X11R6/include", "/usr/include/X11", "/opt/local/include", } package.libpaths = { "/opt/local/lib", "/usr/X11R6/lib" } if OS == "linux" and options["icc"] then tinsert(package.libpaths, "/usr/i686-pc-linux-gnu/lib") -- needed for ICC to find libbfd end package.defines = { -- "CONFIG_USE_MMGR", } end end -- create a package and set the attributes that are common to all packages. function package_create(package_name, target_type) -- Note: don't store in local variable. A global variable needs to -- be set for Premake's use; it is implicitly used in e.g. matchfiles() package = newpackage() package.path = project.path package.language = "c++" package.name = package_name package.kind = target_type package.includepaths = {} package_set_target(package_name) package_set_build_flags() return package end -- extra_params: table including zero or more of the following: -- * no_default_pch: (any type) prevents adding the PCH include dir. -- see setup_static_lib_package() for explanation of this scheme and rationale. -- * extra_files: table of filenames (relative to source_root) to add to project -- * extra_links: table of library names to add to link step function package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) -- We don't want the VC project to be deeply nested (once for each -- folder in source_root). Therefore, remove the source_root -- directory from the filenames (where those -- names are used by Premake to construct the project tree), but set -- 'trimprefix' (with Premake altered to recognise that) so the project -- will still point to the correct filenames. package.trimprefix = source_root package.files = sourcesfromdirs(source_root, rel_source_dirs) -- Put the project-specific PCH directory at the start of the -- include path, so '#include "precompiled.h"' will look in -- there first if not extra_params["no_default_pch"] then tinsert(package.includepaths, source_root .. "pch/" .. package.name) end -- next is source root dir, for absolute (nonrelative) includes -- (e.g. "lib/precompiled.h") tinsert(package.includepaths, source_root) for i,v in rel_include_dirs do tinsert(package.includepaths, source_root .. v) end if extra_params["extra_files"] then for i,v in extra_params["extra_files"] do tinsert(package.files, source_root .. v) end end if extra_params["extra_links"] then listconcat(package.links, extra_params["extra_links"]) end end -- Detect and set up PCH for the current package function package_setup_pch(pch_dir, header, source) if OS == "windows" or not options["without-pch"] then package.pchheader = header -- "precompiled.h" package.pchsource = source -- "precompiled.cpp" if pch_dir then tinsert(package.files, { pch_dir..header, pch_dir..source }) end for i,v in project.configs do tinsert(package.config[v].defines, "USING_PCH") end end end -------------------------------------------------------------------------------- -- engine static libraries -------------------------------------------------------------------------------- -- the engine is split up into several static libraries. this eases separate -- distribution of those components, reduces dependencies a bit, and can -- also speed up builds. -- more to the point, it is necessary to efficiently support a separate -- test executable that also includes much of the game code. -- names of all static libs created. automatically added to the -- main app project later (see explanation at end of this file) static_lib_names = {} -- set up one of the static libraries into which the main engine code is split. -- extra_params: see package_add_contents(). -- note: rel_source_dirs and rel_include_dirs are relative to global source_root. function setup_static_lib_package (package_name, rel_source_dirs, extern_libs, extra_params) package_create(package_name, "lib") package_add_contents(source_root, rel_source_dirs, {}, extra_params) package_add_extern_libs(extern_libs) tinsert(static_lib_names, package_name) -- Precompiled Headers -- rationale: we need one PCH per static lib, since one global header would -- increase dependencies. To that end, we can either include them as -- "packagedir/precompiled.h", or add "source/PCH/packagedir" to the -- include path and put the PCH there. The latter is better because -- many packages contain several dirs and it's unclear where there the -- PCH should be stored. This way is also a bit easier to use in that -- source files always include "precompiled.h". -- Notes: -- * Visual Assist manages to use the project include path and can -- correctly open these files from the IDE. -- * precompiled.cpp (needed to "Create" the PCH) also goes in -- the abovementioned dir. pch_dir = source_root.."pch/"..package_name.."/" package_setup_pch(pch_dir, "precompiled.h", "precompiled.cpp") end -- this is where the source tree is chopped up into static libs. -- can be changed very easily; just copy+paste a new setup_static_lib_package, -- or remove existing ones. static libs are automagically added to -- main_exe link step. function setup_all_libs () -- relative to global source_root. local source_dirs = {} -- names of external libraries used (see libraries_dir comment) local extern_libs = {} source_dirs = { "network", } extern_libs = { "spidermonkey", "xerces", "boost", -- dragged in via server->simulation.h->random } setup_static_lib_package("network", source_dirs, extern_libs, {}) source_dirs = { "ps", "ps/scripting", "ps/Network", "ps/GameSetup", "ps/XML", "simulation", "simulation/scripting", "sound", "scripting", "maths", "maths/scripting" } extern_libs = { "spidermonkey", "sdl", -- key definitions "xerces", "opengl", "zlib", "boost" } setup_static_lib_package("engine", source_dirs, extern_libs, {}) source_dirs = { "graphics", "graphics/scripting", "renderer" } extern_libs = { "opengl", "sdl", -- key definitions "spidermonkey", -- for graphics/scripting "boost" } setup_static_lib_package("graphics", source_dirs, extern_libs, {}) -- internationalization = i18n -- note: this package isn't large, but is separate because it may be -- useful for other projects. source_dirs = { "i18n" } extern_libs = { "spidermonkey", "boost" } setup_static_lib_package("i18n", source_dirs, extern_libs, {}) source_dirs = { "tools/atlas/GameInterface", "tools/atlas/GameInterface/Handlers" } extern_libs = { "boost", "sdl", -- key definitions "opengl", "spidermonkey" } setup_static_lib_package("atlas", source_dirs, extern_libs, {}) source_dirs = { "gui", "gui/scripting" } extern_libs = { "spidermonkey", "sdl", -- key definitions "opengl", "boost" } setup_static_lib_package("gui", source_dirs, extern_libs, {}) source_dirs = { "lib", "lib/posix", "lib/sysdep", "lib/sysdep/ia32", "lib/res", "lib/res/file", "lib/res/graphics", "lib/res/sound", "lib/external_libraries" } extern_libs = { "boost", "sdl", "opengl", "libpng", "zlib", "openal", "vorbis", "libjpg", "dbghelp", "directx", "cryptopp" } setup_static_lib_package("lowlevel", source_dirs, extern_libs, {}) sysdep_dirs = { linux = { "lib/sysdep/unix" }, -- note: RC file must be added to main_exe package. -- note: don't add "lib/sysdep/win/aken.cpp" because that must be compiled with the DDK. windows = { "lib/sysdep/win", "lib/sysdep/win/wposix", "lib/sysdep/win/whrt" }, macosx = { "lib/sysdep/osx", "lib/sysdep/unix" }, } tinsert(package.files, sourcesfromdirs(source_root, sysdep_dirs[OS])); end -------------------------------------------------------------------------------- -- main EXE -------------------------------------------------------------------------------- -- used for main EXE as well as test used_extern_libs = { "opengl", "sdl", "libjpg", "libpng", "zlib", "spidermonkey", "xerces", "openal", "vorbis", "boost", "dbghelp", "cxxtest", "directx" } -- Bundles static libs together with main.cpp and builds game executable. function setup_main_exe () package_create("pyrogenesis", "winexe") -- For VS2005, tell the linker to use the libraries' .obj files instead of -- the .lib, to allow incremental linking. -- (Reduces re-link time from ~20 seconds to ~2 secs) tinsert(package.buildflags, "use-library-dep-inputs") local extra_params = { extra_files = { "main.cpp" }, } package_add_contents(source_root, {}, {}, extra_params) package_add_extern_libs(used_extern_libs) -- Platform Specifics if OS == "windows" then tinsert(package.files, source_root.."lib/sysdep/win/icon.rc") -- from "lowlevel" static lib; must be added here to be linked in tinsert(package.files, source_root.."lib/sysdep/win/error_dialog.rc") -- VS2005 generates its own manifest, but earlier ones need us to add it manually if (options["target"] == "vs2002" or options["target"] == "vs2003") then tinsert(package.files, source_root.."lib/sysdep/win/manifest.rc") end package.linkoptions = { - -- required since main.cpp uses main instead of WinMain and subsystem=Win32 - "/ENTRY:mainCRTStartup", + -- wraps main thread in a __try block(see wseh.cpp). replace with mainCRTStartup if that's undesired. + "/ENTRY:wseh_EntryPoint", -- see wstartup.h "/INCLUDE:_wstartup_InitAndRegisterShutdown", -- delay loading of various Windows DLLs (not specific to any of the -- external libraries; those are handled separately) "/DELAYLOAD:oleaut32.dll", "/DELAYLOAD:advapi32.dll", "/DELAYLOAD:user32.dll", "/DELAYLOAD:ws2_32.dll", "/DELAYLOAD:version.dll", "/DELAYLOAD:winmm.dll", -- allow manual unload of delay-loaded DLLs "/DELAY:UNLOAD" } elseif OS == "linux" then -- Libraries tinsert(package.links, { "fam", -- Utilities "pthread", "rt", -- Debugging "bfd", "iberty" }) -- For debug_resolve_symbol package.config["Debug"].linkoptions = { "-rdynamic" } package.config["Testing"].linkoptions = { "-rdynamic" } elseif OS == "macosx" then -- Libraries tinsert(package.links, { -- Utilities "pthread" }) tinsert(package.libpaths, "/usr/X11R6/lib") end end -------------------------------------------------------------------------------- -- atlas -------------------------------------------------------------------------------- -- setup a typical Atlas component package -- extra_params: as in package_add_contents; also zero or more of the following: -- * pch: (any type) set precompiled.h and .cpp as PCH function setup_atlas_package(package_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) local source_root = "../../../source/tools/atlas/" .. package_name .. "/" package_create(package_name, target_type) -- Don't add the default 'sourceroot/pch/projectname' for finding PCH files extra_params["no_default_pch"] = 1 package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) package_add_extern_libs(extern_libs) if extra_params["pch"] then package_setup_pch(nil, "precompiled.h", "precompiled.cpp"); end -- Platform Specifics if OS == "windows" then tinsert(package.defines, "_UNICODE") -- Link to required libraries package.links = { "winmm", "comctl32", "rpcrt4", "delayimp" } -- required to use WinMain() on Windows, otherwise will default to main() tinsert(package.buildflags, "no-main") if extra_params["extra_links"] then listconcat(package.links, extra_params["extra_links"]) end else -- Non-Windows, = Unix tinsert(package.buildoptions, "-rdynamic") tinsert(package.linkoptions, "-rdynamic") if extra_params["no_unused_warnings"] then if not options["icc"] then tinsert(package.buildoptions, "-Wno-unused-parameter") end end end end -- build all Atlas component packages function setup_atlas_packages() setup_atlas_package("AtlasObject", "lib", { -- src "" },{ -- include },{ -- extern_libs "xerces", "wxwidgets" },{ -- extra_params }) setup_atlas_package("AtlasScript", "lib", { -- src "" },{ -- include ".." },{ -- extern_libs "spidermonkey", "wxwidgets" },{ -- extra_params }) setup_atlas_package("wxJS", "lib", { -- src "", "common", "ext", "gui", "gui/control", "gui/event", "gui/misc", "io", },{ -- include },{ -- extern_libs "spidermonkey", "wxwidgets" },{ -- extra_params pch = (not has_broken_pch), no_unused_warnings = 1, -- wxJS has far too many and we're never going to fix them, so just ignore them }) setup_atlas_package("AtlasUI", "dll", { -- src "ActorEditor", "ActorViewer", "ArchiveViewer", "ColourTester", "CustomControls/Buttons", "CustomControls/Canvas", "CustomControls/ColourDialog", "CustomControls/DraggableListCtrl", "CustomControls/EditableListCtrl", "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", "ErrorReporter", "FileConverter", "General", "General/VideoRecorder", "Misc", "ScenarioEditor", "ScenarioEditor/Sections/Common", "ScenarioEditor/Sections/Cinematic", "ScenarioEditor/Sections/Environment", "ScenarioEditor/Sections/Map", "ScenarioEditor/Sections/Object", "ScenarioEditor/Sections/Terrain", "ScenarioEditor/Sections/Trigger", "ScenarioEditor/Tools", "ScenarioEditor/Tools/Common" },{ -- include "..", "CustomControls", "Misc" },{ -- extern_libs "boost", "devil", "ffmpeg", "spidermonkey", "wxwidgets", "xerces" },{ -- extra_params pch = (not has_broken_pch), extra_links = { "AtlasObject", "AtlasScript", "wxJS", "DatafileIO" }, extra_files = { "Misc/atlas.rc" } }) setup_atlas_package("DatafileIO", "lib", { -- src "", "BAR", "DDT", "SCN", "Stream", "XMB" },{ -- include },{ -- extern_libs "devil", "xerces", "zlib" },{ -- extra_params pch = 1, }) end -- Atlas 'frontend' tool-launching packages function setup_atlas_frontend_package (package_name) package_create(package_name, "winexe") local source_root = "../../../source/tools/atlas/AtlasFrontends/" package.files = { source_root..package_name..".cpp", source_root..package_name..".rc" } package.trimprefix = source_root package.includepaths = { source_root .. ".." } -- Platform Specifics if OS == "windows" then tinsert(package.defines, "_UNICODE") -- required to use WinMain() on Windows, otherwise will default to main() tinsert(package.buildflags, "no-main") else -- Non-Windows, = Unix tinsert(package.links, "DatafileIO") tinsert(package.links, "AtlasObject") end tinsert(package.links, "AtlasUI") end function setup_atlas_frontends() setup_atlas_frontend_package("ActorEditor") setup_atlas_frontend_package("ArchiveViewer") setup_atlas_frontend_package("ColourTester") setup_atlas_frontend_package("FileConverter") end -------------------------------------------------------------------------------- -- collada -------------------------------------------------------------------------------- function setup_collada_package(package_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) package_create(package_name, target_type) -- Don't add the default 'sourceroot/pch/projectname' for finding PCH files extra_params["no_default_pch"] = 1 package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) package_add_extern_libs(extern_libs) if extra_params["pch"] then package_setup_pch(nil, "precompiled.h", "precompiled.cpp"); end -- Platform Specifics if OS == "windows" then -- required to use WinMain() on Windows, otherwise will default to main() tinsert(package.buildflags, "no-main") if extra_params["extra_links"] then listconcat(package.links, extra_params["extra_links"]) end else -- Non-Windows, = Unix tinsert(package.defines, "LINUX"); tinsert(package.includepaths, "/usr/include/libxml2") tinsert(package.links, "xml2") tinsert(package.buildoptions, "-rdynamic") tinsert(package.linkoptions, "-rdynamic") end end -- build all Collada component packages function setup_collada_packages() setup_collada_package("Collada", "dll", { -- src "collada" },{ -- include },{ -- extern_libs "fcollada", },{ -- extra_params pch = 1, }) end -------------------------------------------------------------------------------- -- tests -------------------------------------------------------------------------------- function get_all_test_files(root, src_files, hdr_files) -- note: lua doesn't have any directory handling functions at all, ugh. -- premake's matchrecursive on patterns like *tests*.h doesn't work - -- apparently it applies them to filenames, not the complete path. -- our workaround is to enumerate all files and manually filter out the -- desired */tests/* files. this is a bit slow, but hey. local all_files = matchrecursive(root .. "*.h") for i,v in all_files do -- header file in subdirectory test if string.sub(v, -2) == ".h" and string.find(v, "/tests/") then -- don't include sysdep tests on the wrong sys if not (string.find(v, "/sysdep/win/") and OS ~= "windows") then tinsert(hdr_files, v) -- add the corresponding source file immediately, instead of -- waiting for it to appear after cxxtestgen. this avoids -- having to recreate workspace 2x after adding a test. tinsert(src_files, string.sub(v, 1, -3) .. ".cpp") end end end end function setup_tests() local src_files = {} local hdr_files = {} get_all_test_files(source_root, src_files, hdr_files) package_create("test_3_gen", "cxxtestgen") package.files = hdr_files package.rootfile = source_root .. "test_root.cpp" package.testoptions = "--have-std" package.rootoptions = "--have-std" if OS == "windows" then package.rootoptions = package.rootoptions .. " --gui=Win32Gui --runner=ParenPrinter" else package.rootoptions = package.rootoptions .. " --runner=ErrorPrinter" end -- precompiled headers - the header is added to all generated .cpp files -- note that the header isn't actually precompiled here, only #included -- so that the build stage can use it as a precompiled header. include = " --include=precompiled.h" package.rootoptions = package.rootoptions .. include package.testoptions = package.testoptions .. include package_create("test_2_build", "winexe") links = static_lib_names tinsert(links, "test_3_gen") extra_params = { extra_files = { "test_root.cpp", "test_setup.cpp" }, extra_links = links, } package_add_contents(source_root, {}, {}, extra_params) -- note: these are not relative to source_root and therefore can't be included via package_add_contents. listconcat(package.files, src_files) package_add_extern_libs(used_extern_libs) if OS == "windows" then -- from "lowlevel" static lib; must be added here to be linked in tinsert(package.files, source_root.."lib/sysdep/win/error_dialog.rc") -- see wstartup.h tinsert(package.linkoptions, "/INCLUDE:_wstartup_InitAndRegisterShutdown") elseif OS == "linux" then tinsert(package.links, { "fam", -- Utilities "pthread", "rt", -- Debugging "bfd", "iberty" }) -- For debug_resolve_symbol package.config["Debug"].linkoptions = { "-rdynamic" } package.config["Testing"].linkoptions = { "-rdynamic" } tinsert(package.includepaths, source_root .. "pch/test/") tinsert(package.libpaths, "/usr/X11R6/lib") end package_setup_pch( source_root .. "pch/test/", "precompiled.h", "precompiled.cpp"); tinsert(package.buildflags, "use-library-dep-inputs") package_create("test_1_run", "run") package.links = { "test_2_build" } -- This determines which project's executable to run end -- must come first, so that VC sets it as the default project and therefore -- allows running via F5 without the "where is the EXE" dialog. setup_main_exe() -- save package global variable for later (will be overwritten by setup_all_libs) main_exe_package = package setup_all_libs() -- HACK: add the static libs to the main EXE project. only now (after -- setup_all_libs has run) are the lib names known. cannot move -- setup_main_exe to run after setup_all_libs (see comment above). -- we also don't want to hardcode the names - that would require more -- work when changing the static lib breakdown. listconcat(main_exe_package.links, static_lib_names) if options["atlas"] then setup_atlas_packages() setup_atlas_frontends() end if options["collada"] then setup_collada_packages() end if not options["without-tests"] then setup_tests() end Index: ps/trunk/source/lib/sysdep/win/wposix/wpthread.cpp =================================================================== --- ps/trunk/source/lib/sysdep/win/wposix/wpthread.cpp (revision 5150) +++ ps/trunk/source/lib/sysdep/win/wposix/wpthread.cpp (revision 5151) @@ -1,546 +1,551 @@ /** * ========================================================================= * File : wpthread.cpp * Project : 0 A.D. * Description : emulate pthreads on Windows. * ========================================================================= */ // license: GPL; see lib/license.txt #include "precompiled.h" #include "wpthread.h" #include #include #include "lib/sysdep/cpu.h" // CAS #include "wposix_internal.h" -#include "wtime.h" // timespec +#include "wtime.h" // timespec +#include "../wseh.h" // wseh_ExceptionFilter static HANDLE HANDLE_from_pthread(pthread_t p) { return (HANDLE)((char*)0 + p); } static pthread_t pthread_from_HANDLE(HANDLE h) { return (pthread_t)(uintptr_t)h; } //----------------------------------------------------------------------------- // misc //----------------------------------------------------------------------------- pthread_t pthread_self(void) { return pthread_from_HANDLE(GetCurrentThread()); } int pthread_once(pthread_once_t* once, void (*init_routine)(void)) { if(CAS(once, 0, 1)) init_routine(); return 0; } int pthread_getschedparam(pthread_t thread, int* policy, struct sched_param* param) { if(policy) { DWORD pc = GetPriorityClass(GetCurrentProcess()); *policy = (pc >= HIGH_PRIORITY_CLASS)? SCHED_FIFO : SCHED_RR; } if(param) { const HANDLE hThread = HANDLE_from_pthread(thread); param->sched_priority = GetThreadPriority(hThread); } return 0; } int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param* param) { const int pri = param->sched_priority; // additional boost for policy == SCHED_FIFO DWORD pri_class = NORMAL_PRIORITY_CLASS; if(policy == SCHED_FIFO) { pri_class = HIGH_PRIORITY_CLASS; if(pri == 2) pri_class = REALTIME_PRIORITY_CLASS; } SetPriorityClass(GetCurrentProcess(), pri_class); // choose fixed Windows values from pri const HANDLE hThread = HANDLE_from_pthread(thread); SetThreadPriority(hThread, pri); return 0; } //----------------------------------------------------------------------------- // thread-local storage //----------------------------------------------------------------------------- // minimum amount of TLS slots every Windows version provides; // used to validate indices. static const uint TLS_LIMIT = 64; // rationale: don't use an array of dtors for every possible TLS slot. // other DLLs may allocate any number of them in their DllMain, so the // array would have to be quite large. instead, store both key and dtor - // we are thus limited only by pthread_key_create calls (which we control). static const uint MAX_DTORS = 4; static struct { pthread_key_t key; void (*dtor)(void*); } dtors[MAX_DTORS]; int pthread_key_create(pthread_key_t* key, void (*dtor)(void*)) { DWORD idx = TlsAlloc(); if(idx == TLS_OUT_OF_INDEXES) return -ENOMEM; debug_assert(idx < TLS_LIMIT); *key = (pthread_key_t)idx; // acquire a free dtor slot uint i; for(i = 0; i < MAX_DTORS; i++) { if(CAS(&dtors[i].dtor, 0, dtor)) goto have_slot; } // not enough slots; we have a valid key, but its dtor won't be called. WARN_ERR(ERR::LIMIT); return -1; have_slot: dtors[i].key = *key; return 0; } int pthread_key_delete(pthread_key_t key) { DWORD idx = (DWORD)key; debug_assert(idx < TLS_LIMIT); BOOL ret = TlsFree(idx); debug_assert(ret != 0); return 0; } void* pthread_getspecific(pthread_key_t key) { DWORD idx = (DWORD)key; debug_assert(idx < TLS_LIMIT); // TlsGetValue sets last error to 0 on success (boo). // we don't want this to hide previous errors, so it's restored below. DWORD last_err = GetLastError(); void* data = TlsGetValue(idx); // no error if(GetLastError() == 0) { // we care about performance here. SetLastError is low overhead, // but last error = 0 is expected. if(last_err != 0) SetLastError(last_err); } else WARN_ERR(ERR::FAIL); return data; } int pthread_setspecific(pthread_key_t key, const void* value) { DWORD idx = (DWORD)key; debug_assert(idx < TLS_LIMIT); BOOL ret = TlsSetValue(idx, (void*)value); debug_assert(ret != 0); return 0; } static void call_tls_dtors() { again: bool had_valid_tls = false; // for each registered dtor: (call order unspecified by SUSv3) for(uint i = 0; i < MAX_DTORS; i++) { // is slot #i in use? void (*dtor)(void*) = dtors[i].dtor; if(!dtor) continue; // clear slot and call dtor with its previous value. const pthread_key_t key = dtors[i].key; void* tls = pthread_getspecific(key); if(tls) { WARN_ERR(pthread_setspecific(key, 0)); dtor(tls); had_valid_tls = true; } } // rationale: SUSv3 says we're allowed to loop infinitely. we do so to // expose any dtor bugs - this shouldn't normally happen. if(had_valid_tls) goto again; } //----------------------------------------------------------------------------- // mutex //----------------------------------------------------------------------------- // rationale: CRITICAL_SECTIONS have less overhead than Win32 Mutex. // disadvantage is that pthread_mutex_timedlock isn't supported, but // the user can switch to semaphores if this facility is important. // DeleteCriticalSection currently doesn't complain if we double-free // (e.g. user calls destroy() and static initializer atexit runs), // and dox are ambiguous. // note: pthread_mutex_t must not be an opaque struct, because the // initializer returns pthread_mutex_t directly and CRITICAL_SECTIONS // shouldn't be copied. // // note: must not use new/malloc to allocate the critical section // because mmgr.cpp uses a mutex and must not be called to allocate // anything before it is initialized. pthread_mutex_t pthread_mutex_initializer() { CRITICAL_SECTION* cs = (CRITICAL_SECTION*)win_alloc(sizeof(CRITICAL_SECTION)); InitializeCriticalSection(cs); return (pthread_mutex_t)cs; } int pthread_mutex_destroy(pthread_mutex_t* m) { CRITICAL_SECTION* cs = (CRITICAL_SECTION*)(*m); DeleteCriticalSection(cs); win_free(cs); return 0; } int pthread_mutex_init(pthread_mutex_t* m, const pthread_mutexattr_t*) { *m = pthread_mutex_initializer(); return 0; } int pthread_mutex_lock(pthread_mutex_t* m) { CRITICAL_SECTION* cs = (CRITICAL_SECTION*)(*m); EnterCriticalSection(cs); return 0; } int pthread_mutex_trylock(pthread_mutex_t* m) { CRITICAL_SECTION* cs = (CRITICAL_SECTION*)(*m); BOOL got_it = TryEnterCriticalSection(cs); return got_it? 0 : -1; } int pthread_mutex_unlock(pthread_mutex_t* m) { CRITICAL_SECTION* cs = (CRITICAL_SECTION*)(*m); LeaveCriticalSection(cs); return 0; } // not implemented - pthread_mutex is based on CRITICAL_SECTION, // which doesn't support timeouts. use sem_timedwait instead. int pthread_mutex_timedlock(pthread_mutex_t* UNUSED(m), const struct timespec* UNUSED(abs_timeout)) { return -ENOSYS; } //----------------------------------------------------------------------------- // semaphore //----------------------------------------------------------------------------- static HANDLE HANDLE_from_sem_t(sem_t* sem) { return (HANDLE)*sem; } int sem_init(sem_t* sem, int pshared, unsigned value) { SECURITY_ATTRIBUTES sec = { sizeof(SECURITY_ATTRIBUTES) }; sec.bInheritHandle = (BOOL)pshared; HANDLE h = CreateSemaphore(&sec, (LONG)value, 0x7fffffff, 0); WARN_IF_FALSE(h); *sem = (uintptr_t)h; return 0; } int sem_post(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); WARN_IF_FALSE(ReleaseSemaphore(h, 1, 0)); return 0; } int sem_wait(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); DWORD ret = WaitForSingleObject(h, INFINITE); debug_assert(ret == WAIT_OBJECT_0); return 0; } int sem_destroy(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); WARN_IF_FALSE(CloseHandle(h)); return 0; } // helper function for sem_timedwait - multiple return is convenient. // converts an absolute timeout deadline into a relative length for use with // WaitForSingleObject with the following peculiarity: if the semaphore // could be locked immediately, abs_timeout must be ignored (see SUS). // to that end, we return a timeout of 0 and pass back = false if // abs_timeout is invalid. static DWORD calc_timeout_length_ms(const struct timespec* abs_timeout, bool& timeout_is_valid) { timeout_is_valid = false; if(!abs_timeout) return 0; // SUS requires we fail if not normalized if(abs_timeout->tv_nsec >= 1000000000) return 0; struct timespec cur_time; if(clock_gettime(CLOCK_REALTIME, &cur_time) != 0) return 0; timeout_is_valid = true; // convert absolute deadline to relative length, rounding up to [ms]. // note: use i64 to avoid overflow in multiply. const i64 ds = abs_timeout->tv_sec - cur_time.tv_sec; const long dn = abs_timeout->tv_nsec - cur_time.tv_nsec; i64 length_ms = ds*1000 + (dn+500000)/1000000; // .. deadline already reached; we'll still attempt to lock once if(length_ms < 0) return 0; // .. length > 49 days => result won't fit in 32 bits. most likely bogus. // note: we're careful to avoid returning exactly -1 since // that's the Win32 INFINITE value. if(length_ms >= 0xFFFFFFFF) { WARN_ERR(ERR::LIMIT); length_ms = 0xfffffffe; } return (DWORD)(length_ms & 0xFFFFFFFF); } int sem_timedwait(sem_t* sem, const struct timespec* abs_timeout) { bool timeout_is_valid; DWORD timeout_ms = calc_timeout_length_ms(abs_timeout, timeout_is_valid); HANDLE h = HANDLE_from_sem_t(sem); DWORD ret = WaitForSingleObject(h, timeout_ms); // successfully decremented semaphore; bail. if(ret == WAIT_OBJECT_0) return 0; // we're going to return -1. decide what happened: // .. abs_timeout was invalid (must not check this before trying to lock) if(!timeout_is_valid) errno = EINVAL; // .. timeout reached (not a failure) else if(ret == WAIT_TIMEOUT) errno = ETIMEDOUT; return -1; } // wait until semaphore is locked or a message arrives. non-portable. // // background: on Win32, UI threads must periodically pump messages, or // else deadlock may result (see WaitForSingleObject docs). that entails // avoiding any blocking functions. when event waiting is needed, // one cheap workaround would be to time out periodically and pump messages. // that would work, but either wastes CPU time waiting, or introduces // message latency. to avoid this, we provide an API similar to sem_wait and // sem_timedwait that gives MsgWaitForMultipleObjects functionality. // // return value: 0 if the semaphore has been locked (SUS terminology), // -1 otherwise. errno differentiates what happened: ETIMEDOUT if a // message arrived (this is to ease switching between message waiting and // periodic timeout), or an error indication. int sem_msgwait_np(sem_t* sem) { HANDLE h = HANDLE_from_sem_t(sem); DWORD ret = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLEVENTS); // semaphore is signalled if(ret == WAIT_OBJECT_0) return 0; // something else: // .. message came up if(ret == WAIT_OBJECT_0+1) errno = ETIMEDOUT; // .. error else { errno = EINVAL; WARN_ERR(ERR::FAIL); } return -1; } //----------------------------------------------------------------------------- // threads //----------------------------------------------------------------------------- // _beginthreadex cannot call the user's thread function directly due to // differences in calling convention; we need to pass its address and // the user-specified data pointer to our trampoline. // // rationale: // - a local variable in pthread_create isn't safe because the // new thread might not start before pthread_create returns. // - using one static FuncAndArg protected by critical section doesn't // work. win_lock allows recursive locking, so if creating 2 threads, // the parent thread may create both without being stopped and thus // stomp on the first thread's func_and_arg. // - blocking pthread_create until the trampoline has latched func_and_arg // would work. this is a bit easier to understand than nonrecursive CS. // deadlock is impossible because thread_start allows the parent to // continue before doing anything dangerous. however, this requires // initializing a semaphore, which leads to init order problems. // - stashing func and arg in TLS would work, but it is a very limited // resource and __declspec(thread) is documented as possibly // interfering with delay loading. // - heap allocations are the obvious safe solution. we'd like to // minimize them, but it's the least painful way. struct FuncAndArg { void* (*func)(void*); void* arg; }; // bridge calling conventions required by _beginthreadex and POSIX. static unsigned __stdcall thread_start(void* param) { const FuncAndArg* func_and_arg = (const FuncAndArg*)param; void* (*func)(void*) = func_and_arg->func; void* arg = func_and_arg->arg; win_free(param); - u8 xrrStorage[WDBG_XRR_STORAGE_SIZE]; - wdbg_InstallExceptionHandler(xrrStorage); - - void* ret = func(arg); - - call_tls_dtors(); + void* ret = 0; + __try + { + ret = func(arg); + call_tls_dtors(); + } + __except(wseh_ExceptionFilter(GetExceptionInformation())) + { + ret = 0; + } return (unsigned)(uintptr_t)ret; } int pthread_create(pthread_t* thread_id, const void* UNUSED(attr), void* (*func)(void*), void* arg) { // notes: // - use win_alloc instead of the normal heap because we /might/ // potentially be called before _cinit. // - placement new is more trouble than it's worth // (see REDEFINED_NEW), so we don't bother with a ctor. FuncAndArg* func_and_arg = (FuncAndArg*)win_alloc(sizeof(FuncAndArg)); if(!func_and_arg) { WARN_ERR(ERR::NO_MEM); return -1; } func_and_arg->func = func; func_and_arg->arg = arg; // _beginthreadex has more overhead and no value added vs. // CreateThread, but it avoids small memory leaks in // ExitThread when using the statically-linked CRT (-> MSDN). const uintptr_t id = _beginthreadex(0, 0, thread_start, func_and_arg, 0, 0); if(!id) { WARN_ERR(ERR::FAIL); return -1; } // SUSv3 doesn't specify whether this is optional - go the safe route. if(thread_id) *thread_id = (pthread_t)id; return 0; } int pthread_cancel(pthread_t thread) { HANDLE hThread = HANDLE_from_pthread(thread); TerminateThread(hThread, 0); debug_printf("WARNING: pthread_cancel is unsafe\n"); return 0; } int pthread_join(pthread_t thread, void** value_ptr) { HANDLE hThread = HANDLE_from_pthread(thread); // note: pthread_join doesn't call for a timeout. if this wait // locks up the process, at least it'll be easy to see why. DWORD ret = WaitForSingleObject(hThread, INFINITE); if(ret != WAIT_OBJECT_0) { WARN_ERR(ERR::FAIL); return -1; } // pass back the code that was passed to pthread_exit. // SUS says <*value_ptr> need only be set on success! if(value_ptr) GetExitCodeThread(hThread, (LPDWORD)value_ptr); CloseHandle(hThread); return 0; } Index: ps/trunk/source/lib/sysdep/win/wseh.h =================================================================== --- ps/trunk/source/lib/sysdep/win/wseh.h (nonexistent) +++ ps/trunk/source/lib/sysdep/win/wseh.h (revision 5151) @@ -0,0 +1,18 @@ +/** + * ========================================================================= + * File : wseh.h + * Project : 0 A.D. + * Description : Win32 debug support code and exception handler. + * ========================================================================= + */ + +// license: GPL; see lib/license.txt + +#ifndef INCLUDED_WSEH +#define INCLUDED_WSEH + +extern LONG WINAPI wseh_ExceptionFilter(EXCEPTION_POINTERS* ep); + +EXTERN_C int wseh_EntryPoint(); + +#endif // #ifndef INCLUDED_WSEH Index: ps/trunk/source/lib/sysdep/win/wdbg.h =================================================================== --- ps/trunk/source/lib/sysdep/win/wdbg.h (revision 5150) +++ ps/trunk/source/lib/sysdep/win/wdbg.h (revision 5151) @@ -1,35 +1,23 @@ /** * ========================================================================= * File : wdbg.h * Project : 0 A.D. * Description : Win32 debug support code and exception handler. * ========================================================================= */ // license: GPL; see lib/license.txt #ifndef INCLUDED_WDBG #define INCLUDED_WDBG #if HAVE_MS_ASM # define debug_break() __asm { int 3 } #else # error "port this or define to implementation function" #endif extern void wdbg_set_thread_name(const char* name); - -const size_t WDBG_XRR_STORAGE_SIZE = 16; - -/** - * install an SEH handler for the current thread. - * - * @param xrrStorage - storage used to hold the SEH handler node that's - * entered into the TIB's list. must be at least WDBG_XRR_STORAGE_SIZE bytes - * and remain valid over the lifetime of the thread. - **/ -void wdbg_InstallExceptionHandler(void* xrrStorage); - #endif // #ifndef INCLUDED_WDBG Index: ps/trunk/source/lib/sysdep/win/whrt/hpet.cpp =================================================================== --- ps/trunk/source/lib/sysdep/win/whrt/hpet.cpp (revision 5150) +++ ps/trunk/source/lib/sysdep/win/whrt/hpet.cpp (revision 5151) @@ -1,122 +1,122 @@ /** * ========================================================================= * File : hpet.cpp * Project : 0 A.D. * Description : Timer implementation using timeGetTime * ========================================================================= */ // license: GPL; see lib/license.txt #include "precompiled.h" #include "hpet.h" #include "lib/sysdep/win/win.h" #include "lib/sysdep/win/mahaf.h" #include "lib/sysdep/acpi.h" #include "lib/bits.h" #pragma pack(push, 1) struct HpetDescriptionTable { AcpiTable header; u32 eventTimerBlockId; AcpiGenericAddress baseAddress; u8 sequenceNumber; u16 minimumPeriodicTicks; u8 attributes; }; struct CounterHPET::HpetRegisters { u64 capabilities; u64 reserved1; u64 config; u64 reserved2; u64 interruptStatus; u64 reserved3[25]; u64 counterValue; u64 reserved4; // .. followed by blocks for timers 0..31 }; #pragma pack(pop) static const u64 CAP_SIZE64 = BIT64(13); static const u64 CONFIG_ENABLE = BIT64(0); //----------------------------------------------------------------------------- LibError CounterHPET::Activate() { if(!mahaf_Init()) return ERR::FAIL; // NOWARN (no Administrator privileges) if(!acpi_Init()) WARN_RETURN(ERR::FAIL); const HpetDescriptionTable* hpet = (const HpetDescriptionTable*)acpi_GetTable("HPET"); if(!hpet) return ERR::NO_SYS; // NOWARN (HPET not reported by BIOS) debug_assert(hpet->baseAddress.addressSpaceId == ACPI_AS_MEMORY); m_hpetRegisters = (volatile HpetRegisters*)mahaf_MapPhysicalMemory(hpet->baseAddress.address, sizeof(HpetRegisters)); if(!m_hpetRegisters) WARN_RETURN(ERR::NO_MEM); // start the counter (if not already running) // note: do not reset value to 0 to avoid interfering with any // other users of the timer (e.g. Vista QPC) m_hpetRegisters->config |= CONFIG_ENABLE; return INFO::OK; } void CounterHPET::Shutdown() { if(m_hpetRegisters) mahaf_UnmapPhysicalMemory((void*)m_hpetRegisters); acpi_Shutdown(); mahaf_Shutdown(); } bool CounterHPET::IsSafe() const { - // the HPET being created to address other timers' problems, it has - // no issues of its own. + // the HPET having been created to address other timers' problems, + // it has no issues of its own. return true; } u64 CounterHPET::Counter() const { // note: we assume the data bus can do atomic 64-bit transfers, // which has been the case since the original Pentium. // (note: see implementation of GetTickCount for an algorithm to // cope with non-atomic reads) return m_hpetRegisters->counterValue; } /** * WHRT uses this to ensure the counter (running at nominal frequency) * doesn't overflow more than once during CALIBRATION_INTERVAL_MS. **/ uint CounterHPET::CounterBits() const { const u64 caps = m_hpetRegisters->capabilities; const uint counterBits = (caps & CAP_SIZE64)? 64 : 32; return counterBits; } /** * initial measurement of the tick rate. not necessarily correct * (e.g. when using TSC: cpu_ClockFrequency isn't exact). **/ double CounterHPET::NominalFrequency() const { const u64 caps = m_hpetRegisters->capabilities; const u32 timerPeriod_fs = bits64(caps, 32, 63); const double frequency = 1e15 / timerPeriod_fs; return frequency; } Index: ps/trunk/source/lib/sysdep/win/wseh.cpp =================================================================== --- ps/trunk/source/lib/sysdep/win/wseh.cpp (nonexistent) +++ ps/trunk/source/lib/sysdep/win/wseh.cpp (revision 5151) @@ -0,0 +1,362 @@ +/** + * ========================================================================= + * File : wseh.cpp + * Project : 0 A.D. + * Description : Structured Exception Handling support + * ========================================================================= + */ + +// license: GPL; see lib/license.txt + +#include "precompiled.h" +#include "wdbg.h" + +#include "lib/byte_order.h" // FOURCC +#include "lib/sysdep/cpu.h" +#include "win.h" +#include "wutil.h" +#include "wdbg_sym.h" // wdbg_sym_write_minidump + +#if MSC_VERSION >= 1400 +# include // __security_init_cookie +# define NEED_COOKIE_INIT +#endif + + +//----------------------------------------------------------------------------- +// analyze an exception (determine description and locus) + +// VC++ exception handling internals. +// see http://www.codeproject.com/cpp/exceptionhandler.asp +struct XTypeInfo +{ + DWORD _; + const std::type_info* ti; + // .. +}; + +struct XTypeInfoArray +{ + DWORD count; + const XTypeInfo* types[1]; +}; + +struct XInfo +{ + DWORD _[3]; + const XTypeInfoArray* array; +}; + + +// does the given SEH exception look like a C++ exception? +// (compiler-specific). +static bool IsCppException(const EXCEPTION_RECORD* er) +{ +#if MSC_VERSION + // notes: + // - value of multibyte character constants (e.g. 'msc') aren't + // specified by C++, so use FOURCC instead. + // - "MS C" compiler is the only interpretation of this magic value that + // makes sense, so it is apparently stored in big-endian format. + if(er->ExceptionCode != FOURCC_BE(0xe0, 'm','s','c')) + return false; + + // exception info = (magic, &thrown_Cpp_object, &XInfo) + if(er->NumberParameters != 3) + return false; + + // MAGIC_NUMBER1 from exsup.inc + if(er->ExceptionInformation[0] != 0x19930520) + return false; + + return true; +#else +# error "port" +#endif +} + +/** + * @param er an exception record for which IsCppException returned true. + **/ +static const wchar_t* GetCppExceptionDescription(const EXCEPTION_RECORD* er, + wchar_t* description, size_t maxChars) +{ + // see above for interpretation + const ULONG_PTR* const ei = er->ExceptionInformation; + + // note: we can't share a __try below - the failure of + // one attempt must not abort the others. + + // get std::type_info + char type_buf[100] = {'\0'}; + const char* type_name = type_buf; + __try + { + const XInfo* xi = (XInfo*)ei[2]; + const XTypeInfoArray* xta = xi->array; + const XTypeInfo* xti = xta->types[0]; + const std::type_info* ti = xti->ti; + + // strip "class " from start of string (clutter) + strcpy_s(type_buf, ARRAY_SIZE(type_buf), ti->name()); + if(!strncmp(type_buf, "class ", 6)) + type_name += 6; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } + + // std::exception.what() + char what[160] = {'\0'}; + __try + { + std::exception* e = (std::exception*)ei[1]; + strcpy_s(what, ARRAY_SIZE(what), e->what()); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } + + // format the info we got (if both are empty, then something is seriously + // wrong; it's better to show empty strings than returning 0 to have our + // caller display the SEH info) + swprintf(description, maxChars, L"%hs(\"%hs\")", type_name, what); + return description; +} + + +static const wchar_t* GetSehExceptionDescription(const EXCEPTION_RECORD* er, + wchar_t* description, size_t maxChars) +{ + const DWORD code = er->ExceptionCode; + const ULONG_PTR* ei = er->ExceptionInformation; + + // rationale: we don't use FormatMessage because it is unclear whether + // NTDLL's symbol table will always include English-language strings + // (we don't want to receive crashlogs in foreign gobbledygook). + // it also adds unwanted formatting (e.g. {EXCEPTION} and trailing .). + + switch(code) + { + case EXCEPTION_ACCESS_VIOLATION: + { + // special case: display type and address. + const wchar_t* accessType = (ei[0])? L"writing" : L"reading"; + const ULONG_PTR address = ei[1]; + swprintf(description, maxChars, L"Access violation %s 0x%08X", accessType, address); + return description; + } + case EXCEPTION_DATATYPE_MISALIGNMENT: return L"Datatype misalignment"; + case EXCEPTION_BREAKPOINT: return L"Breakpoint"; + case EXCEPTION_SINGLE_STEP: return L"Single step"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return L"Array bounds exceeded"; + case EXCEPTION_FLT_DENORMAL_OPERAND: return L"FPU denormal operand"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return L"FPU divide by zero"; + case EXCEPTION_FLT_INEXACT_RESULT: return L"FPU inexact result"; + case EXCEPTION_FLT_INVALID_OPERATION: return L"FPU invalid operation"; + case EXCEPTION_FLT_OVERFLOW: return L"FPU overflow"; + case EXCEPTION_FLT_STACK_CHECK: return L"FPU stack check"; + case EXCEPTION_FLT_UNDERFLOW: return L"FPU underflow"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return L"Integer divide by zero"; + case EXCEPTION_INT_OVERFLOW: return L"Integer overflow"; + case EXCEPTION_PRIV_INSTRUCTION: return L"Privileged instruction"; + case EXCEPTION_IN_PAGE_ERROR: return L"In page error"; + case EXCEPTION_ILLEGAL_INSTRUCTION: return L"Illegal instruction"; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: return L"Noncontinuable exception"; + case EXCEPTION_STACK_OVERFLOW: return L"Stack overflow"; + case EXCEPTION_INVALID_DISPOSITION: return L"Invalid disposition"; + case EXCEPTION_GUARD_PAGE: return L"Guard page"; + case EXCEPTION_INVALID_HANDLE: return L"Invalid handle"; + } + + // anything else => unknown; display its exception code. + // we don't punt to GetExceptionDescription because anything + // we get called for will actually be a SEH exception. + swprintf(description, maxChars, L"Unknown (0x%08X)", code); + return description; +} + + +/** + * @return a description of the exception type and cause (in English). + **/ +static const wchar_t* GetExceptionDescription(const EXCEPTION_POINTERS* ep, + wchar_t* description, size_t maxChars) +{ + const EXCEPTION_RECORD* const er = ep->ExceptionRecord; + + if(IsCppException(er)) + return GetCppExceptionDescription(er, description, maxChars); + else + return GetSehExceptionDescription(er, description, maxChars); +} + + +// return location at which the exception occurred. +// params: see debug_resolve_symbol. +static void GetExceptionLocus(const EXCEPTION_POINTERS* ep, + char* file, int* line, char* func) +{ + // HACK: provides no useful information - ExceptionAddress always + // points to kernel32!RaiseException. we use debug_get_nth_caller to + // determine the real location. + + const uint skip = 1; // skip RaiseException + void* func_addr = debug_get_nth_caller(skip, ep->ContextRecord); + (void)debug_resolve_symbol(func_addr, func, file, line); +} + + +//----------------------------------------------------------------------------- +// exception filter + +// called when an exception is detected (see below); provides detailed +// debugging information and exits. +// +// note: keep memory allocs and locking to an absolute minimum, because +// they may deadlock the process! +LONG WINAPI wseh_ExceptionFilter(EXCEPTION_POINTERS* ep) +{ + // OutputDebugString raises an exception, which OUGHT to be swallowed + // by WaitForDebugEvent but sometimes isn't. if we see it, ignore it. + if(ep->ExceptionRecord->ExceptionCode == 0x40010006) // DBG_PRINTEXCEPTION_C + return EXCEPTION_CONTINUE_EXECUTION; + + // if run in a debugger, let it handle exceptions (tends to be more + // convenient since it can bring up the crash location) + if(IsDebuggerPresent()) + return EXCEPTION_CONTINUE_SEARCH; + + // make sure we don't recurse infinitely if this function raises an + // SEH exception. (we may only have the guard page's 4 KB worth of + // stack space if the exception is EXCEPTION_STACK_OVERFLOW) + static intptr_t nestingLevel = 0; + cpu_AtomicAdd(&nestingLevel, 1); + if(nestingLevel >= 3) + return EXCEPTION_CONTINUE_SEARCH; + + // someone is already holding the dbghelp lock - this is bad. + // we'll report this problem first and then try to display the + // exception info regardless (maybe dbghelp won't blow up). + if(win_is_locked(WDBG_SYM_CS) == 1) + DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock.."); + + // extract details from ExceptionRecord. + wchar_t descriptionBuf[150]; + const wchar_t* description = GetExceptionDescription(ep, descriptionBuf, ARRAY_SIZE(descriptionBuf)); + char file[DBG_FILE_LEN] = {0}; + int line = 0; + char func[DBG_SYMBOL_LEN] = {0}; + GetExceptionLocus(ep, file, &line, func); + + // this must happen before the error dialog because user could choose to + // exit immediately there. + wdbg_sym_write_minidump(ep); + + wchar_t message[500]; + const wchar_t* messageFormat = + L"Much to our regret we must report the program has encountered an error.\r\n" + L"\r\n" + L"Please let us know at http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n" + L"\r\n" + L"Details: unhandled exception (%s)\r\n"; + swprintf(message, ARRAY_SIZE(message), messageFormat, description); + + uint flags = 0; + if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) + flags = DE_NO_CONTINUE; + ErrorReaction er = debug_display_error(message, flags, 1,ep->ContextRecord, file,line,func, 0); + debug_assert(er == ER_CONTINUE); // nothing else possible + + // invoke the Win32 default handler - it calls ExitProcess for + // most exception types. + return EXCEPTION_CONTINUE_SEARCH; +} + + +//----------------------------------------------------------------------------- +// install SEH exception handler + +/* + +rationale: +we want to replace the OS "program error" dialog box because it is not +all too helpful in debugging. to that end, there are 5 ways to make sure +unhandled SEH exceptions are caught: +- via WaitForDebugEvent; the app is run from a separate debugger process. + the exception is in another address space, so we'd have to deal with that + and basically implement a full-featured debugger - overkill. +- by wrapping all threads (their handler chains are in TLS) in __try. + this can be done with the cooperation of wpthread, but threads not under + our control aren't covered. +- with a vectored exception handler. this works across threads, but it's + only available on WinXP (unacceptable). since it is called before __try + blocks, we would receive expected/legitimate exceptions. +- by setting the per-process unhandled exception filter. as above, this works + across threads and is at least portable across Win32. unfortunately, some + Win32 DLLs appear to register their own handlers, so this isn't reliable. +- by hooking the exception dispatcher. this isn't future-proof. + +note that the vectored and unhandled-exception filters aren't called when +the process is being debugged (messing with the PEB flag doesn't help; +root cause is the Win32 KiUserExceptionDispatcher implementation). +however, this is fine since the IDE's debugger is more helpful than our +dialog (it is able to jump directly to the offending code). + +wrapping all threads in a __try appears to be the best choice. unfortunately, +we cannot retroactively install an SEH handler: the OS ensures SEH chain +nodes are on the thread's stack (as defined by NT_TIB) in ascending order. +(the handler would also have to be marked via .safeseh, but that is doable) +consequently, we'll have to run within a __try; if the init code is to be +covered, this must happen within the program entry point. + +note: since C++ exceptions are implemented via SEH, we can also catch +those here; it's nicer than a global try{} and avoids duplicating this code. +we can still get at the C++ information (std::exception.what()) by examining +the internal exception data structures. these are compiler-specific, but +haven't changed from VC5-VC7.1. +alternatively, _set_se_translator could to translate all SEH exceptions to +C++ classes. this way is more reliable/documented, but has several drawbacks: +- it wouldn't work at all in C programs, +- a new fat exception class would have to be created to hold the + SEH exception information (e.g. CONTEXT for a stack trace), and +- this information would not be available for C++ exceptions. + +*/ + +EXTERN_C int mainCRTStartup(); + +static int CallStartupWithinTryBlock() +{ + int ret; + __try + { + ret = mainCRTStartup(); + } + __except(wseh_ExceptionFilter(GetExceptionInformation())) + { + ret = -1; + } + return ret; +} + +EXTERN_C int wseh_EntryPoint() +{ +#ifdef NEED_COOKIE_INIT + // 2006-02-16 workaround for R6035 on VC8: + // + // SEH code compiled with /GS pushes a "security cookie" onto the + // stack. since we're called before CRT init, the cookie won't have + // been initialized yet, which would cause the CRT to FatalAppExit. + // to solve this, we must call __security_init_cookie before any + // hidden compiler-generated SEH registration code runs, + // which means the __try block must be moved into a helper function. + // + // NB: wseh_EntryPoint() must not contain local string buffers, + // either - /GS would install a cookie here as well (same problem). + // + // see http://msdn2.microsoft.com/en-US/library/ms235603.aspx + __security_init_cookie(); +#endif + return CallStartupWithinTryBlock(); +} Index: ps/trunk/source/lib/sysdep/win/wdbg.cpp =================================================================== --- ps/trunk/source/lib/sysdep/win/wdbg.cpp (revision 5150) +++ ps/trunk/source/lib/sysdep/win/wdbg.cpp (revision 5151) @@ -1,823 +1,451 @@ /** * ========================================================================= * File : wdbg.cpp * Project : 0 A.D. * Description : Win32 debug support code and exception handler. * ========================================================================= */ // license: GPL; see lib/license.txt #include "precompiled.h" #include "wdbg.h" #include #include #include #include "lib/bits.h" #include "lib/posix/posix_pthread.h" -#include "lib/byte_order.h" // FOURCC #include "lib/app_hooks.h" #include "lib/sysdep/cpu.h" #include "win.h" #include "wdbg_sym.h" -#include "winit.h" #include "wutil.h" -WINIT_REGISTER_EARLY_INIT(wdbg_Init); // registers exception handler - // protects the breakpoint helper thread. static void lock() { win_lock(WDBG_CS); } static void unlock() { win_unlock(WDBG_CS); } static NT_TIB* get_tib() { +#if CPU_IA32 NT_TIB* tib; __asm { mov eax, fs:[NT_TIB.Self] mov [tib], eax } return tib; +#endif } //----------------------------------------------------------------------------- // debug memory allocator //----------------------------------------------------------------------------- // check heap integrity (independently of mmgr). // errors are reported by the CRT or via debug_display_error. void debug_heap_check() { int ret; __try { ret = _heapchk(); } __except(EXCEPTION_EXECUTE_HANDLER) { ret = _HEAPBADNODE; } if(ret != _HEAPOK) DISPLAY_ERROR(L"debug_heap_check: heap is corrupt"); } // call at any time; from then on, the specified checks will be performed. // if not called, the default is DEBUG_HEAP_NONE, i.e. do nothing. void debug_heap_enable(DebugHeapChecks what) { // note: if mmgr is enabled, crtdbg.h will not have been included. // in that case, we do nothing here - this interface is too basic to // control mmgr; use its API instead. #if !CONFIG_USE_MMGR && HAVE_VC_DEBUG_ALLOC uint flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); switch(what) { case DEBUG_HEAP_NONE: // note: do not set flags to zero because we might trash some // important flag value. flags &= ~(_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF); break; case DEBUG_HEAP_NORMAL: flags |= _CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF; break; case DEBUG_HEAP_ALL: flags |= (_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF); break; default: debug_assert("debug_heap_enable: invalid what"); } _CrtSetDbgFlag(flags); #else UNUSED2(what); #endif // HAVE_DEBUGALLOC } //----------------------------------------------------------------------------- // thread suspension // suspend a thread, execute a user callback, revive the thread. // to avoid deadlock, be VERY CAREFUL to avoid anything that may block, // including locks taken by the OS (e.g. malloc, GetProcAddress). typedef LibError (*WhileSuspendedFunc)(HANDLE hThread, void* user_arg); struct WhileSuspendedParam { HANDLE hThread; WhileSuspendedFunc func; void* user_arg; }; static void* while_suspended_thread_func(void* user_arg) { debug_set_thread_name("suspender"); WhileSuspendedParam* param = (WhileSuspendedParam*)user_arg; DWORD err = SuspendThread(param->hThread); // abort, since GetThreadContext only works if the target is suspended. if(err == (DWORD)-1) { debug_warn("while_suspended_thread_func: SuspendThread failed"); return (void*)(intptr_t)-1; } // target is now guaranteed to be suspended, // since the Windows counter never goes negative. LibError ret = param->func(param->hThread, param->user_arg); WARN_IF_FALSE(ResumeThread(param->hThread)); return (void*)(intptr_t)ret; } static LibError call_while_suspended(WhileSuspendedFunc func, void* user_arg) { int err; // we need a real HANDLE to the target thread for use with // Suspend|ResumeThread and GetThreadContext. // alternative: DuplicateHandle on the current thread pseudo-HANDLE. // this way is a bit more obvious/simple. const DWORD access = THREAD_GET_CONTEXT|THREAD_SET_CONTEXT|THREAD_SUSPEND_RESUME; HANDLE hThread = OpenThread(access, FALSE, GetCurrentThreadId()); if(hThread == INVALID_HANDLE_VALUE) WARN_RETURN(ERR::FAIL); WhileSuspendedParam param = { hThread, func, user_arg }; pthread_t thread; WARN_ERR(pthread_create(&thread, 0, while_suspended_thread_func, ¶m)); void* ret; err = pthread_join(thread, &ret); debug_assert(err == 0 && ret == 0); return (LibError)(intptr_t)ret; } //----------------------------------------------------------------------------- // breakpoints //----------------------------------------------------------------------------- // breakpoints are set by storing the address of interest in a // debug register and marking it 'enabled'. // // the first problem is, they are only accessible from Ring0; // we get around this by updating their values via SetThreadContext. // that in turn requires we suspend the current thread, // spawn a helper to change the registers, and resume. // parameter passing to helper thread. currently static storage, // but the struct simplifies switching to a queue later. struct BreakInfo { uintptr_t addr; DbgBreakType type; // determines what brk_thread_func will do. // set/reset by debug_remove_all_breaks. bool want_all_disabled; }; static BreakInfo brk_info; // Local Enable bits of all registers we enabled (used when restoring all). static DWORD brk_all_local_enables; // IA-32 limit; will need to update brk_enable_in_ctx if this changes. static const uint MAX_BREAKPOINTS = 4; // remove all breakpoints enabled by debug_set_break from . // called while target is suspended. static LibError brk_disable_all_in_ctx(BreakInfo* UNUSED(bi), CONTEXT* context) { context->Dr7 &= ~brk_all_local_enables; return INFO::OK; } // find a free register, set type according to and // mark it as enabled in . // called while target is suspended. static LibError brk_enable_in_ctx(BreakInfo* bi, CONTEXT* context) { uint reg; // index (0..3) of first free reg uint LE; // local enable bit for // find free debug register. for(reg = 0; reg < MAX_BREAKPOINTS; reg++) { LE = BIT(reg*2); // .. this one is currently not in use. if((context->Dr7 & LE) == 0) goto have_reg; } WARN_RETURN(ERR::LIMIT); have_reg: // store breakpoint address in debug register. DWORD addr = (DWORD)bi->addr; // .. note: treating Dr0..Dr3 as an array is unsafe due to // possible struct member padding. switch(reg) { case 0: context->Dr0 = addr; break; case 1: context->Dr1 = addr; break; case 2: context->Dr2 = addr; break; case 3: context->Dr3 = addr; break; NODEFAULT; } // choose breakpoint settings: // .. type uint rw = 0; switch(bi->type) { case DBG_BREAK_CODE: rw = 0; break; case DBG_BREAK_DATA: rw = 1; break; case DBG_BREAK_DATA_WRITE: rw = 3; break; default: debug_warn("invalid type"); } // .. length (determine from addr's alignment). // note: IA-32 requires len=0 for code breakpoints. uint len = 0; if(bi->type != DBG_BREAK_CODE) { const uint alignment = (uint)(bi->addr % 4); // assume 2 byte range if(alignment == 2) len = 1; // assume 4 byte range else if(alignment == 0) len = 3; // else: 1 byte range; len already set to 0 } // update Debug Control register const uint shift = (16 + reg*4); // .. clear previous contents of this reg's field // (in case the previous user didn't do so on disabling). context->Dr7 &= ~(0xFu << shift); // .. write settings context->Dr7 |= ((len << 2)|rw) << shift; // .. mark as enabled context->Dr7 |= LE; brk_all_local_enables |= LE; return INFO::OK; } // carry out the request stored in the BreakInfo* parameter. // called while target is suspended. static LibError brk_do_request(HANDLE hThread, void* arg) { LibError ret; BreakInfo* bi = (BreakInfo*)arg; CONTEXT context; context.ContextFlags = CONTEXT_DEBUG_REGISTERS; if(!GetThreadContext(hThread, &context)) WARN_RETURN(ERR::FAIL); #if CPU_IA32 if(bi->want_all_disabled) ret = brk_disable_all_in_ctx(bi, &context); else ret = brk_enable_in_ctx (bi, &context); #else #error "port" #endif if(!SetThreadContext(hThread, &context)) WARN_RETURN(ERR::FAIL); RETURN_ERR(ret); return INFO::OK; } // arrange for a debug exception to be raised when is accessed // according to . // for simplicity, the length (range of bytes to be checked) is // derived from addr's alignment, and is typically 1 machine word. // breakpoints are a limited resource (4 on IA-32); abort and // return ERR::LIMIT if none are available. LibError debug_set_break(void* p, DbgBreakType type) { lock(); brk_info.addr = (uintptr_t)p; brk_info.type = type; LibError ret = call_while_suspended(brk_do_request, &brk_info); unlock(); return ret; } // remove all breakpoints that were set by debug_set_break. // important, since these are a limited resource. LibError debug_remove_all_breaks() { lock(); brk_info.want_all_disabled = true; LibError ret = call_while_suspended(brk_do_request, &brk_info); brk_info.want_all_disabled = false; unlock(); return ret; } //----------------------------------------------------------------------------- -// analyze SEH exceptions - -// -// analyze exceptions; determine their type and locus -// - -// storage for strings built by get_SEH_exception_description and get_cpp_exception_description. -static wchar_t description_buf[128]; - -// VC++ exception handling internals. -// see http://www.codeproject.com/cpp/exceptionhandler.asp -struct XTypeInfo -{ - DWORD _; - const std::type_info* ti; - // .. -}; - -struct XTypeInfoArray -{ - DWORD count; - const XTypeInfo* types[1]; -}; - -struct XInfo -{ - DWORD _[3]; - const XTypeInfoArray* array; -}; - - -// does the given SEH exception look like a C++ exception? -// (compiler-specific). -static bool isCppException(const EXCEPTION_RECORD* er) -{ -#if MSC_VERSION - // notes: - // - value of multibyte character constants (e.g. 'msc') aren't - // specified by C++, so use FOURCC instead. - // - "MS C" compiler is the only interpretation of this magic value that - // makes sense, so it is apparently stored in big-endian format. - if(er->ExceptionCode != FOURCC_BE(0xe0, 'm','s','c')) - return false; - - // exception info = (magic, &thrown_Cpp_object, &XInfo) - if(er->NumberParameters != 3) - return false; - - // MAGIC_NUMBER1 from exsup.inc - if(er->ExceptionInformation[0] != 0x19930520) - return false; - - return true; -#else -# error "port" -#endif -} - - -// if is not a C++ exception, return 0. otherwise, return a description -// of the exception type and cause (in English). uses static storage. -static const wchar_t* get_cpp_exception_description(const EXCEPTION_RECORD* er) -{ - if(!isCppException(er)) - return 0; - - // see above for interpretation - const ULONG_PTR* const ei = er->ExceptionInformation; - - // note: we can't share a __try below - the failure of - // one attempt must not abort the others. - - // get std::type_info - char type_buf[100] = {'\0'}; - const char* type_name = type_buf; - __try - { - const XInfo* xi = (XInfo*)ei[2]; - const XTypeInfoArray* xta = xi->array; - const XTypeInfo* xti = xta->types[0]; - const std::type_info* ti = xti->ti; - - // strip "class " from start of string (clutter) - strcpy_s(type_buf, ARRAY_SIZE(type_buf), ti->name()); - if(!strncmp(type_buf, "class ", 6)) - type_name += 6; - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } - - // std::exception.what() - char what[100] = {'\0'}; - __try - { - std::exception* e = (std::exception*)ei[1]; - strcpy_s(what, ARRAY_SIZE(what), e->what()); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } - - - // we got meaningful data; format and return it. - if(type_name[0] != '\0' || what[0] != '\0') - { - swprintf(description_buf, ARRAY_SIZE(description_buf), L"%hs(\"%hs\")", type_name, what); - return description_buf; - } - - // not a C++ exception; we can't say anything about it. - return 0; -} - - -// return a description of the exception type (in English). -// uses static storage. -static const wchar_t* get_SEH_exception_description(const EXCEPTION_RECORD* er) -{ - const DWORD code = er->ExceptionCode; - const ULONG_PTR* ei = er->ExceptionInformation; - - // special case for access violations: display type and address. - if(code == EXCEPTION_ACCESS_VIOLATION) - { - const wchar_t* op = (ei[0])? L"writing" : L"reading"; - const wchar_t* fmt = L"Access violation %s 0x%08X"; - swprintf(description_buf, ARRAY_SIZE(description_buf), ah_translate(fmt), ah_translate(op), ei[1]); - return description_buf; - } - - // rationale: we don't use FormatMessage because it is unclear whether - // NTDLL's symbol table will always include English-language strings - // (we don't want to receive crashlogs in foreign gobbledygook). - // it also adds unwanted formatting (e.g. {EXCEPTION} and trailing .). - - switch(code) - { -// case EXCEPTION_ACCESS_VIOLATION: return L"Access violation"; - case EXCEPTION_DATATYPE_MISALIGNMENT: return L"Datatype misalignment"; - case EXCEPTION_BREAKPOINT: return L"Breakpoint"; - case EXCEPTION_SINGLE_STEP: return L"Single step"; - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return L"Array bounds exceeded"; - case EXCEPTION_FLT_DENORMAL_OPERAND: return L"FPU denormal operand"; - case EXCEPTION_FLT_DIVIDE_BY_ZERO: return L"FPU divide by zero"; - case EXCEPTION_FLT_INEXACT_RESULT: return L"FPU inexact result"; - case EXCEPTION_FLT_INVALID_OPERATION: return L"FPU invalid operation"; - case EXCEPTION_FLT_OVERFLOW: return L"FPU overflow"; - case EXCEPTION_FLT_STACK_CHECK: return L"FPU stack check"; - case EXCEPTION_FLT_UNDERFLOW: return L"FPU underflow"; - case EXCEPTION_INT_DIVIDE_BY_ZERO: return L"Integer divide by zero"; - case EXCEPTION_INT_OVERFLOW: return L"Integer overflow"; - case EXCEPTION_PRIV_INSTRUCTION: return L"Privileged instruction"; - case EXCEPTION_IN_PAGE_ERROR: return L"In page error"; - case EXCEPTION_ILLEGAL_INSTRUCTION: return L"Illegal instruction"; - case EXCEPTION_NONCONTINUABLE_EXCEPTION: return L"Noncontinuable exception"; - case EXCEPTION_STACK_OVERFLOW: return L"Stack overflow"; - case EXCEPTION_INVALID_DISPOSITION: return L"Invalid disposition"; - case EXCEPTION_GUARD_PAGE: return L"Guard page"; - case EXCEPTION_INVALID_HANDLE: return L"Invalid handle"; - } - - // anything else => unknown; display its exception code. - // we don't punt to get_exception_description because anything - // we get called for will actually be a SEH exception. - swprintf(description_buf, ARRAY_SIZE(description_buf), L"Unknown (0x%08X)", code); - return description_buf; -} - - -// return a description of the exception (in English). -// it is only valid until the next call, since static storage is used. -static const wchar_t* get_exception_description(const EXCEPTION_POINTERS* ep) -{ - const EXCEPTION_RECORD* const er = ep->ExceptionRecord; - - // note: more specific than SEH, so try it first. - const wchar_t* d = get_cpp_exception_description(er); - if(d) - return d; - - return get_SEH_exception_description(er); -} - - -// return location at which the exception occurred. -// params: see debug_resolve_symbol. -static void get_exception_locus(const EXCEPTION_POINTERS* ep, - char* file, int* line, char* func) -{ - // HACK: provides no useful information - ExceptionAddress always - // points to kernel32!RaiseException. we use debug_get_nth_caller to - // determine the real location. - - const uint skip = 1; // skip RaiseException - void* func_addr = debug_get_nth_caller(skip, ep->ContextRecord); - (void)debug_resolve_symbol(func_addr, func, file, line); -} - - -//----------------------------------------------------------------------------- -// exception handler - -/* - -rationale: -we want to replace the OS "program error" dialog box because -it is not all too helpful in debugging. to that end, there are -5 ways to make sure unhandled SEH exceptions are caught: -- via WaitForDebugEvent; the app is run from a separate debugger process. - this complicates analysis, since the exception is in another - address space. also, we are basically implementing a full-featured - debugger - overkill. -- by wrapping all threads in __try (necessary since the handler chain - is in TLS). this can be done with the cooperation of wpthread, - but threads not under our control aren't covered. -- with a vectored exception handler. this works across threads, but - is never called when the process is being debugged (messing with - the PEB flag doesn't help; root cause is the Win32 - KiUserExceptionDispatcher implementation). also, it's only available - on WinXP (unacceptable). finally, it is called before __try blocks, - so would receive expected/legitimate exceptions. -- by setting the per-process unhandled exception filter. as above, - this works across threads and isn't called while a debugger is active; - it is at least portable across Win32. unfortunately, some Win32 DLLs - appear to register their own handlers, so this isn't reliable. -- by hooking the exception dispatcher. this isn't future-proof. - -wrapping all threads in a __try appears to be the best choice. however, -with wstartup no longer commandeering the exit point, we need to -retroactively install an SEH handler. this is done by directly -hooking into the TIB handler list. - -since C++ exceptions are implemented via SEH, we can also catch those here; -it's nicer than a global try{} and avoids duplicating this code. -we can still get at the C++ information (std::exception.what()) by -examining the internal exception data structures. these are -compiler-specific, but haven't changed from VC5-VC7.1. -alternatively, _set_se_translator could be used to translate all -SEH exceptions to C++. this way is more reliable/documented, but has -several drawbacks: -- it wouldn't work at all in C programs, -- a new fat exception class would have to be created to hold the - SEH exception information (e.g. CONTEXT for a stack trace), and -- this information would not be available for C++ exceptions. - -*/ - -// called when an exception is detected (see below); provides detailed -// debugging information and exits. -// -// note: keep memory allocs and locking to an absolute minimum, because -// they may deadlock the process! -LONG WINAPI wdbg_exception_filter(EXCEPTION_POINTERS* ep) -{ - // OutputDebugString raises an exception, which OUGHT to be swallowed - // by WaitForDebugEvent but sometimes isn't. if we see it, ignore it. - if(ep->ExceptionRecord->ExceptionCode == 0x40010006) // DBG_PRINTEXCEPTION_C - return EXCEPTION_CONTINUE_EXECUTION; - - // if run in a debugger, let it handle exceptions (tends to be more - // convenient since it can bring up the crash location) - if(IsDebuggerPresent()) - return EXCEPTION_CONTINUE_SEARCH; - - // note: we risk infinite recursion if someone raises an SEH exception - // from within this function. therefore, abort immediately if we have - // already been called; the first error is the most important, anyway. - static uintptr_t already_crashed = 0; - if(!CAS(&already_crashed, 0, 1)) - return EXCEPTION_CONTINUE_SEARCH; - - // someone is already holding the dbghelp lock - this is bad. - // we'll report this problem first and then try to display the - // exception info regardless (maybe dbghelp won't blow up). - if(win_is_locked(WDBG_SYM_CS) == 1) - DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock.."); - - // extract details from ExceptionRecord. - const wchar_t* description = get_exception_description(ep); - char file[DBG_FILE_LEN] = {0}; - int line = 0; - char func_name[DBG_SYMBOL_LEN] = {0}; - get_exception_locus(ep, file, &line, func_name); - - // this must happen before the error dialog because user could choose to - // exit immediately there. - wdbg_sym_write_minidump(ep); - - wchar_t buf[500]; - const wchar_t* msg_fmt = - L"Much to our regret we must report the program has encountered an error.\r\n" - L"\r\n" - L"Please let us know at http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n" - L"\r\n" - L"Details: unhandled exception (%s)\r\n"; - swprintf(buf, ARRAY_SIZE(buf), msg_fmt, description); - uint flags = 0; - - if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) - flags = DE_NO_CONTINUE; - ErrorReaction er = debug_display_error(buf, flags, 1,ep->ContextRecord, file,line,func_name, NULL); - debug_assert(er == ER_CONTINUE); // nothing else possible - - // invoke the Win32 default handler - it calls ExitProcess for - // most exception types. - return EXCEPTION_CONTINUE_SEARCH; -} - - -//----------------------------------------------------------------------------- -// install SEH exception handler - -typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE)(_EXCEPTION_RECORD* ExceptionRecord, - PVOID EstablisherFrame, _CONTEXT* ContextRecord, PVOID DispatcherContext); - -struct _EXCEPTION_REGISTRATION_RECORD -{ - _EXCEPTION_REGISTRATION_RECORD* Next; - PEXCEPTION_ROUTINE Handler; -}; - -static bool IsUnwinding(DWORD exceptionFlags) -{ - return (exceptionFlags & 2) != 0; -} - -static EXCEPTION_DISPOSITION ExceptionHandler(_EXCEPTION_RECORD* ExceptionRecord, - PVOID UNUSED(EstablisherFrame), _CONTEXT* ContextRecord, PVOID UNUSED(DispatcherContext)) -{ - if(!IsUnwinding(ExceptionRecord->ExceptionFlags)) - { - EXCEPTION_POINTERS ep; - ep.ExceptionRecord = ExceptionRecord; - ep.ContextRecord = ContextRecord; - if(wdbg_exception_filter(&ep) == EXCEPTION_CONTINUE_EXECUTION) - return ExceptionContinueExecution; - } - - return ExceptionContinueSearch; -} - - -cassert(WDBG_XRR_STORAGE_SIZE >= sizeof(_EXCEPTION_REGISTRATION_RECORD)); - -void wdbg_InstallExceptionHandler(void* xrrStorage) -{ - _EXCEPTION_REGISTRATION_RECORD* xrr = (_EXCEPTION_REGISTRATION_RECORD*)xrrStorage; - - // add to front of handler list - NT_TIB* tib = get_tib(); - xrr->Handler = ExceptionHandler; - xrr->Next = tib->ExceptionList; - tib->ExceptionList = xrr; -} - - -//----------------------------------------------------------------------------- - -static LibError wdbg_Init(void) -{ - static _EXCEPTION_REGISTRATION_RECORD xrrStorage; - wdbg_InstallExceptionHandler(&xrrStorage); - - return INFO::OK; -} - - -//----------------------------------------------------------------------------- -// stateless functions // return 1 if the pointer appears to be totally bogus, otherwise 0. // this check is not authoritative (the pointer may be "valid" but incorrect) // but can be used to filter out obviously wrong values in a portable manner. int debug_is_pointer_bogus(const void* p) { #if CPU_IA32 if(p < (void*)0x10000) return true; if(p >= (void*)(uintptr_t)0x80000000) return true; #endif // notes: // - we don't check alignment because nothing can be assumed about a // string pointer and we mustn't reject any actually valid pointers. // - nor do we bother checking the address against known stack/heap areas // because that doesn't cover everything (e.g. DLLs, VirtualAlloc). // - cannot use IsBadReadPtr because it accesses the mem // (false alarm for reserved address space). return false; } bool debug_is_code_ptr(void* p) { uintptr_t addr = (uintptr_t)p; // totally invalid pointer if(debug_is_pointer_bogus(p)) return false; // comes before load address static const HMODULE base = GetModuleHandle(0); if(addr < (uintptr_t)base) return false; return true; } bool debug_is_stack_ptr(void* p) { uintptr_t addr = (uintptr_t)p; // totally invalid pointer if(debug_is_pointer_bogus(p)) return false; // not aligned if(addr % sizeof(void*)) return false; // out of bounds (note: IA-32 stack grows downwards) NT_TIB* tib = get_tib(); if(!(tib->StackLimit < p && p < tib->StackBase)) return false; return true; } void debug_puts(const char* text) { OutputDebugStringA(text); } // inform the debugger of the current thread's description, which it then // displays instead of just the thread handle. void wdbg_set_thread_name(const char* name) { // we pass information to the debugger via a special exception it // swallows. if not running under one, bail now to avoid // "first chance exception" warnings. if(!IsDebuggerPresent()) return; // presented by Jay Bazuzi (from the VC debugger team) at TechEd 1999. const struct ThreadNameInfo { DWORD type; const char* name; DWORD thread_id; // any valid ID or -1 for current thread DWORD flags; } info = { 0x1000, name, (DWORD)-1, 0 }; __try { RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info); } __except(EXCEPTION_EXECUTE_HANDLER) { // if we get here, the debugger didn't handle the exception. debug_warn("thread name hack doesn't work under this debugger"); } }