glibc/dlfcn/dlerror.c

199 lines
5.8 KiB
C

/* Return error detail for failing <dlfcn.h> functions.
Copyright (C) 1995-2024 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
<https://www.gnu.org/licenses/>. */
#include <dlfcn.h>
#include <libintl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libc-lock.h>
#include <ldsodefs.h>
#include <libc-symbols.h>
#include <assert.h>
#include <dlerror.h>
char *
__dlerror (void)
{
# ifdef SHARED
if (GLRO (dl_dlfcn_hook) != NULL)
return GLRO (dl_dlfcn_hook)->dlerror ();
# endif
struct dl_action_result *result = __libc_dlerror_result;
/* No libdl function has been called. No error is possible. */
if (result == NULL)
return NULL;
/* For an early malloc failure, clear the error flag and return the
error message. This marks the error as delivered. */
if (result == dl_action_result_malloc_failed)
{
__libc_dlerror_result = NULL;
return (char *) "out of memory";
}
/* Placeholder object. This can be observed in a recursive call,
e.g. from an ELF constructor. */
if (result->errstring == NULL)
return NULL;
/* If we have already reported the error, we can free the result and
return NULL. See __libc_dlerror_result_free. */
if (result->returned)
{
__libc_dlerror_result = NULL;
dl_action_result_errstring_free (result);
free (result);
return NULL;
}
assert (result->errstring != NULL);
/* Create the combined error message. */
char *buf;
int n;
if (result->errcode == 0)
n = __asprintf (&buf, "%s%s%s",
result->objname,
result->objname[0] == '\0' ? "" : ": ",
_(result->errstring));
else
{
__set_errno (result->errcode);
n = __asprintf (&buf, "%s%s%s: %m",
result->objname,
result->objname[0] == '\0' ? "" : ": ",
_(result->errstring));
/* Set errno again in case asprintf clobbered it. */
__set_errno (result->errcode);
}
/* Mark the error as delivered. */
result->returned = true;
if (n >= 0)
{
/* Replace the error string with the newly allocated one. */
dl_action_result_errstring_free (result);
result->errstring = buf;
result->errstring_source = dl_action_result_errstring_local;
return buf;
}
else
/* We could not create the combined error message, so use the
existing string as a fallback. */
return result->errstring;
}
versioned_symbol (libc, __dlerror, dlerror, GLIBC_2_34);
#if OTHER_SHLIB_COMPAT (libdl, GLIBC_2_0, GLIBC_2_34)
compat_symbol (libdl, __dlerror, dlerror, GLIBC_2_0);
#endif
int
_dlerror_run (void (*operate) (void *), void *args)
{
struct dl_action_result *result = __libc_dlerror_result;
if (result != NULL)
{
if (result == dl_action_result_malloc_failed)
{
/* Clear the previous error. */
__libc_dlerror_result = NULL;
result = NULL;
}
else
{
/* There is an existing object. Free its error string, but
keep the object. */
dl_action_result_errstring_free (result);
/* Mark the object as not containing an error. This ensures
that call to dlerror from, for example, an ELF
constructor will not notice this result object. */
result->errstring = NULL;
}
}
const char *objname;
const char *errstring;
bool malloced;
int errcode = GLRO (dl_catch_error) (&objname, &errstring, &malloced,
operate, args);
/* ELF constructors or destructors may have indirectly altered the
value of __libc_dlerror_result, therefore reload it. */
result = __libc_dlerror_result;
if (errstring == NULL)
{
/* There is no error. We no longer need the result object if it
does not contain an error. However, a recursive call may
have added an error even if this call did not cause it. Keep
the other error. */
if (result != NULL && result->errstring == NULL)
{
__libc_dlerror_result = NULL;
free (result);
}
return 0;
}
else
{
/* A new error occurred. Check if a result object has to be
allocated. */
if (result == NULL || result == dl_action_result_malloc_failed)
{
/* Allocating storage for the error message after the fact
is not ideal. But this avoids an infinite recursion in
case malloc itself calls libdl functions (without
triggering errors). */
result = malloc (sizeof (*result));
if (result == NULL)
{
/* Assume that the dlfcn failure was due to a malloc
failure, too. */
if (malloced)
dl_error_free ((char *) errstring);
__libc_dlerror_result = dl_action_result_malloc_failed;
return 1;
}
__libc_dlerror_result = result;
}
else
/* Deallocate the existing error message from a recursive
call, but reuse the result object. */
dl_action_result_errstring_free (result);
result->errcode = errcode;
result->objname = objname;
result->errstring = (char *) errstring;
result->returned = false;
/* In case of an error, the malloced flag indicates whether the
error string is constant or not. */
if (malloced)
result->errstring_source = dl_action_result_errstring_rtld;
else
result->errstring_source = dl_action_result_errstring_constant;
return 1;
}
}