about summary refs log tree commit diff
path: root/dlfcn/dlerror.c
blob: 7db70a26d034658ae02f15aa1d5c008107843945 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/* Return error detail for failing <dlfcn.h> functions.
   Copyright (C) 1995-2021 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>

#if !defined SHARED && IS_IN (libdl)

char *
dlerror (void)
{
  return __dlerror ();
}

#else

char *
__dlerror (void)
{
# ifdef SHARED
  if (!rtld_active ())
    return _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
    n = __asprintf (&buf, "%s%s%s: %s",
		    result->objname,
		    result->objname[0] == '\0' ? "" : ": ",
		    _(result->errstring),
		    strerror (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;
}
# ifdef SHARED
strong_alias (__dlerror, dlerror)
# 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;
    }
}

# ifdef SHARED

struct dlfcn_hook *_dlfcn_hook __attribute__((nocommon));
libdl_hidden_data_def (_dlfcn_hook)

# else

static struct dlfcn_hook _dlfcn_hooks =
  {
    .dlopen = __dlopen,
    .dlclose = __dlclose,
    .dlsym = __dlsym,
    .dlvsym = __dlvsym,
    .dlerror = __dlerror,
    .dladdr = __dladdr,
    .dladdr1 = __dladdr1,
    .dlinfo = __dlinfo,
    .dlmopen = __dlmopen
  };

void
__libc_register_dlfcn_hook (struct link_map *map)
{
  struct dlfcn_hook **hook;

  hook = (struct dlfcn_hook **) __libc_dlsym_private (map, "_dlfcn_hook");
  if (hook != NULL)
    *hook = &_dlfcn_hooks;
}
# endif
#endif