Reputation: 4776
I was looking for the source code of the abort()
function in C, and I came across this:
/* Copyright (C) 1991-2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <libc-lock.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sigsetops.h>
/* Try to get a machine dependent instruction which will make the
program crash. This is used in case everything else fails. */
#include <abort-instr.h>
#ifndef ABORT_INSTRUCTION
/* No such instruction is available. */
# define ABORT_INSTRUCTION
#endif
/* Exported variable to locate abort message in core files etc. */
struct abort_msg_s *__abort_msg __attribute__ ((nocommon));
libc_hidden_def (__abort_msg)
/* We must avoid to run in circles. Therefore we remember how far we
already got. */
static int stage;
/* We should be prepared for multiple threads trying to run abort. */
__libc_lock_define_initialized_recursive (static, lock);
/* Cause an abnormal program termination with core-dump. */
void
abort (void)
{
struct sigaction act;
sigset_t sigs;
/* First acquire the lock. */
__libc_lock_lock_recursive (lock);
/* Now it's for sure we are alone. But recursive calls are possible. */
/* Unblock SIGABRT. */
if (stage == 0)
{
++stage;
__sigemptyset (&sigs);
__sigaddset (&sigs, SIGABRT);
__sigprocmask (SIG_UNBLOCK, &sigs, 0);
}
/* Send signal which possibly calls a user handler. */
if (stage == 1)
{
/* This stage is special: we must allow repeated calls of
`abort' when a user defined handler for SIGABRT is installed.
This is risky since the `raise' implementation might also
fail but I don't see another possibility. */
int save_stage = stage;
stage = 0;
__libc_lock_unlock_recursive (lock);
raise (SIGABRT);
__libc_lock_lock_recursive (lock);
stage = save_stage + 1;
}
/* There was a handler installed. Now remove it. */
if (stage == 2)
{
++stage;
memset (&act, '\0', sizeof (struct sigaction));
act.sa_handler = SIG_DFL;
__sigfillset (&act.sa_mask);
act.sa_flags = 0;
__sigaction (SIGABRT, &act, NULL);
}
/* Try again. */
if (stage == 3)
{
++stage;
raise (SIGABRT);
}
/* Now try to abort using the system specific command. */
if (stage == 4)
{
++stage;
ABORT_INSTRUCTION;
}
/* If we can't signal ourselves and the abort instruction failed, exit. */
if (stage == 5)
{
++stage;
_exit (127);
}
/* If even this fails try to use the provided instruction to crash
or otherwise make sure we never return. */
while (1)
/* Try for ever and ever. */
ABORT_INSTRUCTION;
}
libc_hidden_def (abort)
Looking through, I saw this and got very confused:
After _exit
is called there is:
If even this fails try to use the provided instruction to crash or otherwise make sure we never return.
Why would _exit
fail? _exit
is never return and terminates the program. Why on Earth would _exit
be unable to terminate the program?
Upvotes: 2
Views: 277
Reputation: 81179
On many systems, there will be some code running which isn't in a "process" per se. As a simple example, the code that is responsible for performing task switching among processes will often not be able to run in the old nor new process, but rather in a context that isn't associated with either. Although only a small number of system functions should be called within such contexts, and neither exit()
nor abort()
is among them, the fact that something shouldn't happen is no guarantee that it won't. Situations may sometimes arise where all courses of action are expected to be bad, but calling abort
may be seen as the least bad among them.
If exit()
is called when code is in a context not associated with any identifiable process, it won't be able to carry out its normal duties. Its caller isn't likely going to be prepared for the possibility that it might return, so its most logical course of action may be to call abort()
. On the other hand, since the most natural course of action for abort()
would be to call exit()
to clean up the current process, this creates a deadly recursive mutual dependency. To accommodate that, the abort
function has a dedicated static object which is used to determine if it gets invoked recursively and, if so, attempt different means of forcing the program to exit. It's likely that the exit()
function also employs similar measures so that if some cleanup action triggers an abort()
which then calls exit()
, it will be able to try performing whatever portions of the cleanup would have followed the part that failed.
Note that if normal means of aborting fail, having an abort function enter an endless loop with a dummy side effect may be just as good as any other course of action, but in some cases an endless loop that repeatedly tries to execute ABORT_INSTRUCTION may be better, since other supervisory subsystems may be able to recognize when an execution context has become stuck in a loop that does nothing other than ABORT_INSTRUCTION and take appropriate action (e.g. forcing a system restart, depending upon what the crashed code had been doing).
Upvotes: 1
Reputation: 781096
Sometimes abort()
is called in situations where memory has become corrupted. This could potentially cause _exit()
to fail somehow.
Or there could be a bug in the OS that prevents _exit()
from working.
Or the program was linked with a library that replaces _exit()
.
All these things are very unlikely (and in the case of memory corruption, it could just as easily cause abort()
itself to fail), but abort()
is trying to cover all bases.
Upvotes: 3