diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index 4f5c923..8335531 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -116,9 +116,10 @@ function error_displayable($error = NULL) {
  *   TRUE if the error is fatal.
  */
 function _drupal_log_error($error, $fatal = FALSE) {
+  $is_installer = drupal_installation_attempted();
   // Initialize a maintenance theme if the bootstrap was not complete.
   // Do it early because drupal_set_message() triggers a drupal_theme_initialize().
-  if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
+  if (!$is_installer && $fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
     unset($GLOBALS['theme']);
     if (!defined('MAINTENANCE_MODE')) {
       define('MAINTENANCE_MODE', 'error');
@@ -215,14 +216,20 @@ function _drupal_log_error($error, $fatal = FALSE) {
       // We fallback to a maintenance page at this point, because the page generation
       // itself can generate errors.
       // Should not translate the string to avoid errors producing more errors.
-      $output = theme('maintenance_page', array('content' => 'The website has encountered an error. Please try again later.'));
-
-      $response = new Response($output, 500);
-      if ($fatal) {
-        $response->setStatusCode(500, '500 Service unavailable (with message)');
+      $message = 'The website has encountered an error. Please try again later.';
+      if ($is_installer) {
+        // install_display_output() prints the output and ends script execution.
+        install_display_output($message, $GLOBALS['install_state']);
+      }
+      else {
+        $output = theme('maintenance_page', array('content' => $message));
       }
 
-      return $response;
+      $response = new Response($output, 500);
+      $response->setStatusCode(500, '500 Service unavailable (with message)');
+      // An exception must halt script execution.
+      $response->send();
+      exit;
     }
   }
 }
@@ -241,13 +248,12 @@ function _drupal_log_error($error, $fatal = FALSE) {
  *   The current error level.
  */
 function _drupal_get_error_level() {
-  try {
-    return \Drupal::config('system.logging')->get('error_level');
-  }
-  catch (Exception $e) {
-    // During very early install the cache_config table does not exist.
-    return ERROR_REPORTING_DISPLAY_ALL;
+  // Raise the error level to maximum for the early installer, so users are able
+  // to file proper bug reports for early installer errors.
+  if (drupal_installation_attempted() && empty($GLOBALS['install_state']['base_system_verified'])) {
+    return ERROR_REPORTING_DISPLAY_VERBOSE;
   }
+  return \Drupal::config('system.logging')->get('error_level');
 }
 
 /**
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 9d86cb9..c93ddcd 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -88,24 +88,14 @@ function install_drupal($settings = array()) {
   // installation.
   $interactive = empty($settings);
   $install_state = $settings + array('interactive' => $interactive) + install_state_defaults();
-  try {
-    // Begin the page request. This adds information about the current state of
-    // the Drupal installation to the passed-in array.
-    install_begin_request($install_state);
-    // Based on the installation state, run the remaining tasks for this page
-    // request, and collect any output.
-    $output = install_run_tasks($install_state);
-  }
-  catch (Exception $e) {
-    // When an installation error occurs, either send the error to the web
-    // browser or pass on the exception so the calling script can use it.
-    if ($install_state['interactive']) {
-      install_display_output($e->getMessage(), $install_state);
-    }
-    else {
-      throw $e;
-    }
-  }
+
+  // Begin the page request. This adds information about the current state of
+  // the Drupal installation to the passed-in array.
+  install_begin_request($install_state);
+  // Based on the installation state, run the remaining tasks for this page
+  // request, and collect any output.
+  $output = install_run_tasks($install_state);
+
   // After execution, all tasks might be complete, in which case
   // $install_state['installation_finished'] is TRUE. In case the last task
   // has been processed, remove the global $install_state, so other code can
@@ -288,6 +278,23 @@ function install_begin_request(&$install_state) {
 
   drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
 
+  require_once __DIR__ . '/../modules/system/system.install';
+  require_once __DIR__ . '/common.inc';
+  require_once __DIR__ . '/file.inc';
+  require_once __DIR__ . '/install.inc';
+  require_once __DIR__ . '/schema.inc';
+  require_once __DIR__ . '/../../' . settings()->get('path_inc', 'core/includes/path.inc');
+  require_once __DIR__ . '/cache.inc';
+  require_once __DIR__ . '/database.inc';
+  require_once __DIR__ . '/form.inc';
+  require_once __DIR__ . '/batch.inc';
+  require_once __DIR__ . '/ajax.inc';
+
+  // Load module basics (needed for hook invokes).
+  include_once __DIR__ . '/module.inc';
+  include_once __DIR__ . '/session.inc';
+  require_once __DIR__ . '/entity.inc';
+
   // If the hash salt leaks, it becomes possible to forge a valid testing user
   // agent, install a new copy of Drupal, and take over the original site. To
   // avoid this yet allow for automated testing of the installer, make sure
@@ -314,23 +321,6 @@ function install_begin_request(&$install_state) {
   $container->register('language_manager', 'Drupal\Core\Language\LanguageManager')
     ->addArgument(new Reference('language.default'));
 
-  require_once __DIR__ . '/../modules/system/system.install';
-  require_once __DIR__ . '/common.inc';
-  require_once __DIR__ . '/file.inc';
-  require_once __DIR__ . '/install.inc';
-  require_once __DIR__ . '/schema.inc';
-  require_once __DIR__ . '/../../' . settings()->get('path_inc', 'core/includes/path.inc');
-  require_once __DIR__ . '/cache.inc';
-  require_once __DIR__ . '/database.inc';
-  require_once __DIR__ . '/form.inc';
-  require_once __DIR__ . '/batch.inc';
-  require_once __DIR__ . '/ajax.inc';
-
-  // Load module basics (needed for hook invokes).
-  include_once __DIR__ . '/module.inc';
-  include_once __DIR__ . '/session.inc';
-  require_once __DIR__ . '/entity.inc';
-
   // Register the translation services.
   install_register_translation_service($container);
   \Drupal::setContainer($container);
@@ -504,7 +494,7 @@ function install_begin_request(&$install_state) {
     // Override the module list with a minimal set of modules.
     $module_handler->setModuleList(array('system' => 'core/modules/system/system.module'));
   }
-  $module_handler->load('system');
+  $module_handler->loadAll();
 
   // Prepare for themed output. We need to run this at the beginning of the
   // page request to avoid a different theme accidentally getting set. (We also
@@ -963,6 +953,11 @@ function install_full_redirect_url($install_state) {
  *   An array of information about the current installation state.
  */
 function install_display_output($output, $install_state) {
+  // Ensure the maintenance theme is initialized.
+  // Early installer errors may not reach the regular initial call in
+  // install_begin_request().
+  drupal_maintenance_theme();
+
   drupal_page_header();
 
   // Prevent install.php from being indexed when installed in a sub folder.
diff --git a/core/lib/Drupal/Core/SystemListingInfo.php b/core/lib/Drupal/Core/SystemListingInfo.php
index d302699..34a8599 100644
--- a/core/lib/Drupal/Core/SystemListingInfo.php
+++ b/core/lib/Drupal/Core/SystemListingInfo.php
@@ -19,6 +19,15 @@ class SystemListingInfo extends SystemListing {
    */
   protected function profiles($directory) {
     $searchdir = array();
+    // If install.php is invoked with a ?profile= query parameter, then
+    // $install_state['parameters']['profile'] will contain that value already,
+    // drupal_get_profile() will return it, and the below calls to
+    // drupal_get_path() will call into drupal_get_filename(), which triggers an
+    // infinite recursion.
+    // @todo Use SystemListing in install_begin_request()?
+    if (drupal_installation_attempted() && empty($GLOBALS['install_state']['profiles'])) {
+      return $searchdir;
+    }
     // The 'core/profiles' directory contains pristine collections of modules
     // and themes as provided by a distribution. It is pristine in the same
     // way that the 'core/modules' directory is pristine for core; users
diff --git a/core/lib/Drupal/Core/Utility/Error.php b/core/lib/Drupal/Core/Utility/Error.php
index 6cdc7e3..49ec9a7 100644
--- a/core/lib/Drupal/Core/Utility/Error.php
+++ b/core/lib/Drupal/Core/Utility/Error.php
@@ -89,9 +89,14 @@ public static function decodeException(\Exception $exception) {
    */
   public static function renderExceptionSafe(\Exception $exception) {
     $decode = static::decodeException($exception);
+    $backtrace = $decode['backtrace'];
     unset($decode['backtrace']);
+    // Remove 'main()'.
+    array_shift($backtrace);
 
-    return String::format('%type: !message in %function (line %line of %file).', $decode);
+    $output = String::format('%type: !message in %function (line %line of %file).', $decode);
+    $output .= '<pre>' . static::formatBacktrace($backtrace) . '</pre>';
+    return $output;
   }
 
   /**
