]>
Commit | Line | Data |
---|---|---|
b9215da1 MT |
1 | From a3189f66a5f2fe86568286fa025fa153be04c6c0 Mon Sep 17 00:00:00 2001 |
2 | From: Florian Weimer <fweimer@redhat.com> | |
3 | Date: Fri, 8 Sep 2023 12:32:14 +0200 | |
4 | Subject: [PATCH 15/27] elf: Always call destructors in reverse constructor | |
5 | order (bug 30785) | |
6 | ||
7 | The current implementation of dlclose (and process exit) re-sorts the | |
8 | link maps before calling ELF destructors. Destructor order is not the | |
9 | reverse of the constructor order as a result: The second sort takes | |
10 | relocation dependencies into account, and other differences can result | |
11 | from ambiguous inputs, such as cycles. (The force_first handling in | |
12 | _dl_sort_maps is not effective for dlclose.) After the changes in | |
13 | this commit, there is still a required difference due to | |
14 | dlopen/dlclose ordering by the application, but the previous | |
15 | discrepancies went beyond that. | |
16 | ||
17 | A new global (namespace-spanning) list of link maps, | |
18 | _dl_init_called_list, is updated right before ELF constructors are | |
19 | called from _dl_init. | |
20 | ||
21 | In dl_close_worker, the maps variable, an on-stack variable length | |
22 | array, is eliminated. (VLAs are problematic, and dlclose should not | |
23 | call malloc because it cannot readily deal with malloc failure.) | |
24 | Marking still-used objects uses the namespace list directly, with | |
25 | next and next_idx replacing the done_index variable. | |
26 | ||
27 | After marking, _dl_init_called_list is used to call the destructors | |
28 | of now-unused maps in reverse destructor order. These destructors | |
29 | can call dlopen. Previously, new objects do not have l_map_used set. | |
30 | This had to change: There is no copy of the link map list anymore, | |
31 | so processing would cover newly opened (and unmarked) mappings, | |
32 | unloading them. Now, _dl_init (indirectly) sets l_map_used, too. | |
33 | (dlclose is handled by the existing reentrancy guard.) | |
34 | ||
35 | After _dl_init_called_list traversal, two more loops follow. The | |
36 | processing order changes to the original link map order in the | |
37 | namespace. Previously, dependency order was used. The difference | |
38 | should not matter because relocation dependencies could already | |
39 | reorder link maps in the old code. | |
40 | ||
41 | The changes to _dl_fini remove the sorting step and replace it with | |
42 | a traversal of _dl_init_called_list. The l_direct_opencount | |
43 | decrement outside the loader lock is removed because it appears | |
44 | incorrect: the counter manipulation could race with other dynamic | |
45 | loader operations. | |
46 | ||
47 | tst-audit23 needs adjustments to the changes in LA_ACT_DELETE | |
48 | notifications. The new approach for checking la_activity should | |
49 | make it clearer that la_activty calls come in pairs around namespace | |
50 | updates. | |
51 | ||
52 | The dependency sorting test cases need updates because the destructor | |
53 | order is always the opposite order of constructor order, even with | |
54 | relocation dependencies or cycles present. | |
55 | ||
56 | There is a future cleanup opportunity to remove the now-constant | |
57 | force_first and for_fini arguments from the _dl_sort_maps function. | |
58 | ||
59 | Fixes commit 1df71d32fe5f5905ffd5d100e5e9ca8ad62 ("elf: Implement | |
60 | force_first handling in _dl_sort_maps_dfs (bug 28937)"). | |
61 | ||
62 | Reviewed-by: DJ Delorie <dj@redhat.com> | |
63 | (cherry picked from commit 6985865bc3ad5b23147ee73466583dd7fdf65892) | |
64 | --- | |
65 | NEWS | 7 ++ | |
66 | elf/dl-close.c | 113 +++++++++++++++++---------- | |
67 | elf/dl-fini.c | 152 +++++++++++++------------------------ | |
68 | elf/dl-init.c | 16 ++++ | |
69 | elf/dso-sort-tests-1.def | 19 ++--- | |
70 | elf/tst-audit23.c | 44 ++++++----- | |
71 | include/link.h | 4 + | |
72 | sysdeps/generic/ldsodefs.h | 4 + | |
73 | 8 files changed, 186 insertions(+), 173 deletions(-) | |
74 | ||
75 | diff --git a/NEWS b/NEWS | |
76 | index 8156572cdf..f1a14f45dd 100644 | |
77 | --- a/NEWS | |
78 | +++ b/NEWS | |
79 | @@ -4,6 +4,13 @@ See the end for copying conditions. | |
80 | ||
81 | Please send GNU C library bug reports via <https://sourceware.org/bugzilla/> | |
82 | using `glibc' in the "product" field. | |
83 | +\f | |
84 | +Version 2.38.1 | |
85 | + | |
86 | +The following bugs are resolved with this release: | |
87 | + | |
88 | + [30785] Always call destructors in reverse constructor order | |
89 | + | |
90 | \f | |
91 | Version 2.38 | |
92 | ||
93 | diff --git a/elf/dl-close.c b/elf/dl-close.c | |
94 | index b887a44888..ea62d0e601 100644 | |
95 | --- a/elf/dl-close.c | |
96 | +++ b/elf/dl-close.c | |
97 | @@ -138,30 +138,31 @@ _dl_close_worker (struct link_map *map, bool force) | |
98 | ||
99 | bool any_tls = false; | |
100 | const unsigned int nloaded = ns->_ns_nloaded; | |
101 | - struct link_map *maps[nloaded]; | |
102 | ||
103 | - /* Run over the list and assign indexes to the link maps and enter | |
104 | - them into the MAPS array. */ | |
105 | + /* Run over the list and assign indexes to the link maps. */ | |
106 | int idx = 0; | |
107 | for (struct link_map *l = ns->_ns_loaded; l != NULL; l = l->l_next) | |
108 | { | |
109 | l->l_map_used = 0; | |
110 | l->l_map_done = 0; | |
111 | l->l_idx = idx; | |
112 | - maps[idx] = l; | |
113 | ++idx; | |
114 | } | |
115 | assert (idx == nloaded); | |
116 | ||
117 | - /* Keep track of the lowest index link map we have covered already. */ | |
118 | - int done_index = -1; | |
119 | - while (++done_index < nloaded) | |
120 | + /* Keep marking link maps until no new link maps are found. */ | |
121 | + for (struct link_map *l = ns->_ns_loaded; l != NULL; ) | |
122 | { | |
123 | - struct link_map *l = maps[done_index]; | |
124 | + /* next is reset to earlier link maps for remarking. */ | |
125 | + struct link_map *next = l->l_next; | |
126 | + int next_idx = l->l_idx + 1; /* next->l_idx, but covers next == NULL. */ | |
127 | ||
128 | if (l->l_map_done) | |
129 | - /* Already handled. */ | |
130 | - continue; | |
131 | + { | |
132 | + /* Already handled. */ | |
133 | + l = next; | |
134 | + continue; | |
135 | + } | |
136 | ||
137 | /* Check whether this object is still used. */ | |
138 | if (l->l_type == lt_loaded | |
139 | @@ -171,7 +172,10 @@ _dl_close_worker (struct link_map *map, bool force) | |
140 | acquire is sufficient and correct. */ | |
141 | && atomic_load_acquire (&l->l_tls_dtor_count) == 0 | |
142 | && !l->l_map_used) | |
143 | - continue; | |
144 | + { | |
145 | + l = next; | |
146 | + continue; | |
147 | + } | |
148 | ||
149 | /* We need this object and we handle it now. */ | |
150 | l->l_map_used = 1; | |
151 | @@ -198,8 +202,11 @@ _dl_close_worker (struct link_map *map, bool force) | |
152 | already processed it, then we need to go back | |
153 | and process again from that point forward to | |
154 | ensure we keep all of its dependencies also. */ | |
155 | - if ((*lp)->l_idx - 1 < done_index) | |
156 | - done_index = (*lp)->l_idx - 1; | |
157 | + if ((*lp)->l_idx < next_idx) | |
158 | + { | |
159 | + next = *lp; | |
160 | + next_idx = next->l_idx; | |
161 | + } | |
162 | } | |
163 | } | |
164 | ||
165 | @@ -219,44 +226,65 @@ _dl_close_worker (struct link_map *map, bool force) | |
166 | if (!jmap->l_map_used) | |
167 | { | |
168 | jmap->l_map_used = 1; | |
169 | - if (jmap->l_idx - 1 < done_index) | |
170 | - done_index = jmap->l_idx - 1; | |
171 | + if (jmap->l_idx < next_idx) | |
172 | + { | |
173 | + next = jmap; | |
174 | + next_idx = next->l_idx; | |
175 | + } | |
176 | } | |
177 | } | |
178 | } | |
179 | - } | |
180 | ||
181 | - /* Sort the entries. We can skip looking for the binary itself which is | |
182 | - at the front of the search list for the main namespace. */ | |
183 | - _dl_sort_maps (maps, nloaded, (nsid == LM_ID_BASE), true); | |
184 | + l = next; | |
185 | + } | |
186 | ||
187 | - /* Call all termination functions at once. */ | |
188 | - bool unload_any = false; | |
189 | - bool scope_mem_left = false; | |
190 | - unsigned int unload_global = 0; | |
191 | - unsigned int first_loaded = ~0; | |
192 | - for (unsigned int i = 0; i < nloaded; ++i) | |
193 | + /* Call the destructors in reverse constructor order, and remove the | |
194 | + closed link maps from the list. */ | |
195 | + for (struct link_map **init_called_head = &_dl_init_called_list; | |
196 | + *init_called_head != NULL; ) | |
197 | { | |
198 | - struct link_map *imap = maps[i]; | |
199 | + struct link_map *imap = *init_called_head; | |
200 | ||
201 | - /* All elements must be in the same namespace. */ | |
202 | - assert (imap->l_ns == nsid); | |
203 | - | |
204 | - if (!imap->l_map_used) | |
205 | + /* _dl_init_called_list is global, to produce a global odering. | |
206 | + Ignore the other namespaces (and link maps that are still used). */ | |
207 | + if (imap->l_ns != nsid || imap->l_map_used) | |
208 | + init_called_head = &imap->l_init_called_next; | |
209 | + else | |
210 | { | |
211 | assert (imap->l_type == lt_loaded && !imap->l_nodelete_active); | |
212 | ||
213 | - /* Call its termination function. Do not do it for | |
214 | - half-cooked objects. Temporarily disable exception | |
215 | - handling, so that errors are fatal. */ | |
216 | - if (imap->l_init_called) | |
217 | + /* _dl_init_called_list is updated at the same time as | |
218 | + l_init_called. */ | |
219 | + assert (imap->l_init_called); | |
220 | + | |
221 | + if (imap->l_info[DT_FINI_ARRAY] != NULL | |
222 | + || imap->l_info[DT_FINI] != NULL) | |
223 | _dl_catch_exception (NULL, _dl_call_fini, imap); | |
224 | ||
225 | #ifdef SHARED | |
226 | /* Auditing checkpoint: we remove an object. */ | |
227 | _dl_audit_objclose (imap); | |
228 | #endif | |
229 | + /* Unlink this link map. */ | |
230 | + *init_called_head = imap->l_init_called_next; | |
231 | + } | |
232 | + } | |
233 | + | |
234 | + | |
235 | + bool unload_any = false; | |
236 | + bool scope_mem_left = false; | |
237 | + unsigned int unload_global = 0; | |
238 | + | |
239 | + /* For skipping un-unloadable link maps in the second loop. */ | |
240 | + struct link_map *first_loaded = ns->_ns_loaded; | |
241 | ||
242 | + /* Iterate over the namespace to find objects to unload. Some | |
243 | + unloadable objects may not be on _dl_init_called_list due to | |
244 | + dlopen failure. */ | |
245 | + for (struct link_map *imap = first_loaded; imap != NULL; imap = imap->l_next) | |
246 | + { | |
247 | + if (!imap->l_map_used) | |
248 | + { | |
249 | /* This object must not be used anymore. */ | |
250 | imap->l_removed = 1; | |
251 | ||
252 | @@ -267,8 +295,8 @@ _dl_close_worker (struct link_map *map, bool force) | |
253 | ++unload_global; | |
254 | ||
255 | /* Remember where the first dynamically loaded object is. */ | |
256 | - if (i < first_loaded) | |
257 | - first_loaded = i; | |
258 | + if (first_loaded == NULL) | |
259 | + first_loaded = imap; | |
260 | } | |
261 | /* Else imap->l_map_used. */ | |
262 | else if (imap->l_type == lt_loaded) | |
263 | @@ -404,8 +432,8 @@ _dl_close_worker (struct link_map *map, bool force) | |
264 | imap->l_loader = NULL; | |
265 | ||
266 | /* Remember where the first dynamically loaded object is. */ | |
267 | - if (i < first_loaded) | |
268 | - first_loaded = i; | |
269 | + if (first_loaded == NULL) | |
270 | + first_loaded = imap; | |
271 | } | |
272 | } | |
273 | ||
274 | @@ -476,10 +504,11 @@ _dl_close_worker (struct link_map *map, bool force) | |
275 | ||
276 | /* Check each element of the search list to see if all references to | |
277 | it are gone. */ | |
278 | - for (unsigned int i = first_loaded; i < nloaded; ++i) | |
279 | + for (struct link_map *imap = first_loaded; imap != NULL; ) | |
280 | { | |
281 | - struct link_map *imap = maps[i]; | |
282 | - if (!imap->l_map_used) | |
283 | + if (imap->l_map_used) | |
284 | + imap = imap->l_next; | |
285 | + else | |
286 | { | |
287 | assert (imap->l_type == lt_loaded); | |
288 | ||
289 | @@ -690,7 +719,9 @@ _dl_close_worker (struct link_map *map, bool force) | |
290 | if (imap == GL(dl_initfirst)) | |
291 | GL(dl_initfirst) = NULL; | |
292 | ||
293 | + struct link_map *next = imap->l_next; | |
294 | free (imap); | |
295 | + imap = next; | |
296 | } | |
297 | } | |
298 | ||
299 | diff --git a/elf/dl-fini.c b/elf/dl-fini.c | |
300 | index 9acb64f47c..e201d36651 100644 | |
301 | --- a/elf/dl-fini.c | |
302 | +++ b/elf/dl-fini.c | |
303 | @@ -24,116 +24,68 @@ | |
304 | void | |
305 | _dl_fini (void) | |
306 | { | |
307 | - /* Lots of fun ahead. We have to call the destructors for all still | |
308 | - loaded objects, in all namespaces. The problem is that the ELF | |
309 | - specification now demands that dependencies between the modules | |
310 | - are taken into account. I.e., the destructor for a module is | |
311 | - called before the ones for any of its dependencies. | |
312 | - | |
313 | - To make things more complicated, we cannot simply use the reverse | |
314 | - order of the constructors. Since the user might have loaded objects | |
315 | - using `dlopen' there are possibly several other modules with its | |
316 | - dependencies to be taken into account. Therefore we have to start | |
317 | - determining the order of the modules once again from the beginning. */ | |
318 | - | |
319 | - /* We run the destructors of the main namespaces last. As for the | |
320 | - other namespaces, we pick run the destructors in them in reverse | |
321 | - order of the namespace ID. */ | |
322 | -#ifdef SHARED | |
323 | - int do_audit = 0; | |
324 | - again: | |
325 | -#endif | |
326 | - for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns) | |
327 | - { | |
328 | - /* Protect against concurrent loads and unloads. */ | |
329 | - __rtld_lock_lock_recursive (GL(dl_load_lock)); | |
330 | - | |
331 | - unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded; | |
332 | - /* No need to do anything for empty namespaces or those used for | |
333 | - auditing DSOs. */ | |
334 | - if (nloaded == 0 | |
335 | -#ifdef SHARED | |
336 | - || GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit | |
337 | -#endif | |
338 | - ) | |
339 | - __rtld_lock_unlock_recursive (GL(dl_load_lock)); | |
340 | - else | |
341 | - { | |
342 | + /* Call destructors strictly in the reverse order of constructors. | |
343 | + This causes fewer surprises than some arbitrary reordering based | |
344 | + on new (relocation) dependencies. None of the objects are | |
345 | + unmapped, so applications can deal with this if their DSOs remain | |
346 | + in a consistent state after destructors have run. */ | |
347 | + | |
348 | + /* Protect against concurrent loads and unloads. */ | |
349 | + __rtld_lock_lock_recursive (GL(dl_load_lock)); | |
350 | + | |
351 | + /* Ignore objects which are opened during shutdown. */ | |
352 | + struct link_map *local_init_called_list = _dl_init_called_list; | |
353 | + | |
354 | + for (struct link_map *l = local_init_called_list; l != NULL; | |
355 | + l = l->l_init_called_next) | |
356 | + /* Bump l_direct_opencount of all objects so that they | |
357 | + are not dlclose()ed from underneath us. */ | |
358 | + ++l->l_direct_opencount; | |
359 | + | |
360 | + /* After this point, everything linked from local_init_called_list | |
361 | + cannot be unloaded because of the reference counter update. */ | |
362 | + __rtld_lock_unlock_recursive (GL(dl_load_lock)); | |
363 | + | |
364 | + /* Perform two passes: One for non-audit modules, one for audit | |
365 | + modules. This way, audit modules receive unload notifications | |
366 | + for non-audit objects, and the destructors for audit modules | |
367 | + still run. */ | |
368 | #ifdef SHARED | |
369 | - _dl_audit_activity_nsid (ns, LA_ACT_DELETE); | |
370 | + int last_pass = GLRO(dl_naudit) > 0; | |
371 | + Lmid_t last_ns = -1; | |
372 | + for (int do_audit = 0; do_audit <= last_pass; ++do_audit) | |
373 | #endif | |
374 | - | |
375 | - /* Now we can allocate an array to hold all the pointers and | |
376 | - copy the pointers in. */ | |
377 | - struct link_map *maps[nloaded]; | |
378 | - | |
379 | - unsigned int i; | |
380 | - struct link_map *l; | |
381 | - assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL); | |
382 | - for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next) | |
383 | - /* Do not handle ld.so in secondary namespaces. */ | |
384 | - if (l == l->l_real) | |
385 | - { | |
386 | - assert (i < nloaded); | |
387 | - | |
388 | - maps[i] = l; | |
389 | - l->l_idx = i; | |
390 | - ++i; | |
391 | - | |
392 | - /* Bump l_direct_opencount of all objects so that they | |
393 | - are not dlclose()ed from underneath us. */ | |
394 | - ++l->l_direct_opencount; | |
395 | - } | |
396 | - assert (ns != LM_ID_BASE || i == nloaded); | |
397 | - assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1); | |
398 | - unsigned int nmaps = i; | |
399 | - | |
400 | - /* Now we have to do the sorting. We can skip looking for the | |
401 | - binary itself which is at the front of the search list for | |
402 | - the main namespace. */ | |
403 | - _dl_sort_maps (maps, nmaps, (ns == LM_ID_BASE), true); | |
404 | - | |
405 | - /* We do not rely on the linked list of loaded object anymore | |
406 | - from this point on. We have our own list here (maps). The | |
407 | - various members of this list cannot vanish since the open | |
408 | - count is too high and will be decremented in this loop. So | |
409 | - we release the lock so that some code which might be called | |
410 | - from a destructor can directly or indirectly access the | |
411 | - lock. */ | |
412 | - __rtld_lock_unlock_recursive (GL(dl_load_lock)); | |
413 | - | |
414 | - /* 'maps' now contains the objects in the right order. Now | |
415 | - call the destructors. We have to process this array from | |
416 | - the front. */ | |
417 | - for (i = 0; i < nmaps; ++i) | |
418 | - { | |
419 | - struct link_map *l = maps[i]; | |
420 | - | |
421 | - if (l->l_init_called) | |
422 | - { | |
423 | - _dl_call_fini (l); | |
424 | + for (struct link_map *l = local_init_called_list; l != NULL; | |
425 | + l = l->l_init_called_next) | |
426 | + { | |
427 | #ifdef SHARED | |
428 | - /* Auditing checkpoint: another object closed. */ | |
429 | - _dl_audit_objclose (l); | |
430 | + if (GL(dl_ns)[l->l_ns]._ns_loaded->l_auditing != do_audit) | |
431 | + continue; | |
432 | + | |
433 | + /* Avoid back-to-back calls of _dl_audit_activity_nsid for the | |
434 | + same namespace. */ | |
435 | + if (last_ns != l->l_ns) | |
436 | + { | |
437 | + if (last_ns >= 0) | |
438 | + _dl_audit_activity_nsid (last_ns, LA_ACT_CONSISTENT); | |
439 | + _dl_audit_activity_nsid (l->l_ns, LA_ACT_DELETE); | |
440 | + last_ns = l->l_ns; | |
441 | + } | |
442 | #endif | |
443 | - } | |
444 | ||
445 | - /* Correct the previous increment. */ | |
446 | - --l->l_direct_opencount; | |
447 | - } | |
448 | + /* There is no need to re-enable exceptions because _dl_fini | |
449 | + is not called from a context where exceptions are caught. */ | |
450 | + _dl_call_fini (l); | |
451 | ||
452 | #ifdef SHARED | |
453 | - _dl_audit_activity_nsid (ns, LA_ACT_CONSISTENT); | |
454 | + /* Auditing checkpoint: another object closed. */ | |
455 | + _dl_audit_objclose (l); | |
456 | #endif | |
457 | - } | |
458 | - } | |
459 | + } | |
460 | ||
461 | #ifdef SHARED | |
462 | - if (! do_audit && GLRO(dl_naudit) > 0) | |
463 | - { | |
464 | - do_audit = 1; | |
465 | - goto again; | |
466 | - } | |
467 | + if (last_ns >= 0) | |
468 | + _dl_audit_activity_nsid (last_ns, LA_ACT_CONSISTENT); | |
469 | ||
470 | if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_STATISTICS)) | |
471 | _dl_debug_printf ("\nruntime linker statistics:\n" | |
472 | diff --git a/elf/dl-init.c b/elf/dl-init.c | |
473 | index ba4d2fdc85..ffd05b7806 100644 | |
474 | --- a/elf/dl-init.c | |
475 | +++ b/elf/dl-init.c | |
476 | @@ -21,6 +21,7 @@ | |
477 | #include <ldsodefs.h> | |
478 | #include <elf-initfini.h> | |
479 | ||
480 | +struct link_map *_dl_init_called_list; | |
481 | ||
482 | static void | |
483 | call_init (struct link_map *l, int argc, char **argv, char **env) | |
484 | @@ -42,6 +43,21 @@ call_init (struct link_map *l, int argc, char **argv, char **env) | |
485 | dependency. */ | |
486 | l->l_init_called = 1; | |
487 | ||
488 | + /* Help an already-running dlclose: The just-loaded object must not | |
489 | + be removed during the current pass. (No effect if no dlclose in | |
490 | + progress.) */ | |
491 | + l->l_map_used = 1; | |
492 | + | |
493 | + /* Record execution before starting any initializers. This way, if | |
494 | + the initializers themselves call dlopen, their ELF destructors | |
495 | + will eventually be run before this object is destructed, matching | |
496 | + that their ELF constructors have run before this object was | |
497 | + constructed. _dl_fini uses this list for audit callbacks, so | |
498 | + register objects on the list even if they do not have a | |
499 | + constructor. */ | |
500 | + l->l_init_called_next = _dl_init_called_list; | |
501 | + _dl_init_called_list = l; | |
502 | + | |
503 | /* Check for object which constructors we do not run here. */ | |
504 | if (__builtin_expect (l->l_name[0], 'a') == '\0' | |
505 | && l->l_type == lt_executable) | |
506 | diff --git a/elf/dso-sort-tests-1.def b/elf/dso-sort-tests-1.def | |
507 | index 4bf9052db1..61dc54f8ae 100644 | |
508 | --- a/elf/dso-sort-tests-1.def | |
509 | +++ b/elf/dso-sort-tests-1.def | |
510 | @@ -53,21 +53,14 @@ tst-dso-ordering10: {}->a->b->c;soname({})=c | |
511 | output: b>a>{}<a<b | |
512 | ||
513 | # Complex example from Bugzilla #15311, under-linked and with circular | |
514 | -# relocation(dynamic) dependencies. While this is technically unspecified, the | |
515 | -# presumed reasonable practical behavior is for the destructor order to respect | |
516 | -# the static DT_NEEDED links (here this means the a->b->c->d order). | |
517 | -# The older dynamic_sort=1 algorithm does not achieve this, while the DFS-based | |
518 | -# dynamic_sort=2 algorithm does, although it is still arguable whether going | |
519 | -# beyond spec to do this is the right thing to do. | |
520 | -# The below expected outputs are what the two algorithms currently produce | |
521 | -# respectively, for regression testing purposes. | |
522 | +# relocation(dynamic) dependencies. For both sorting algorithms, the | |
523 | +# destruction order is the reverse of the construction order, and | |
524 | +# relocation dependencies are not taken into account. | |
525 | tst-bz15311: {+a;+e;+f;+g;+d;%d;-d;-g;-f;-e;-a};a->b->c->d;d=>[ba];c=>a;b=>e=>a;c=>f=>b;d=>g=>c | |
526 | -output(glibc.rtld.dynamic_sort=1): {+a[d>c>b>a>];+e[e>];+f[f>];+g[g>];+d[];%d(b(e(a()))a()g(c(a()f(b(e(a()))))));-d[];-g[];-f[];-e[];-a[<a<c<d<g<f<b<e];} | |
527 | -output(glibc.rtld.dynamic_sort=2): {+a[d>c>b>a>];+e[e>];+f[f>];+g[g>];+d[];%d(b(e(a()))a()g(c(a()f(b(e(a()))))));-d[];-g[];-f[];-e[];-a[<g<f<a<b<c<d<e];} | |
528 | +output: {+a[d>c>b>a>];+e[e>];+f[f>];+g[g>];+d[];%d(b(e(a()))a()g(c(a()f(b(e(a()))))));-d[];-g[];-f[];-e[];-a[<g<f<e<a<b<c<d];} | |
529 | ||
530 | # Test that even in the presence of dependency loops involving dlopen'ed | |
531 | # object, that object is initialized last (and not unloaded prematurely). | |
532 | -# Final destructor order is indeterminate due to the cycle. | |
533 | +# Final destructor order is the opposite of constructor order. | |
534 | tst-bz28937: {+a;+b;-b;+c;%c};a->a1;a->a2;a2->a;b->b1;c->a1;c=>a1 | |
535 | -output(glibc.rtld.dynamic_sort=1): {+a[a2>a1>a>];+b[b1>b>];-b[<b<b1];+c[c>];%c(a1());}<a<a2<c<a1 | |
536 | -output(glibc.rtld.dynamic_sort=2): {+a[a2>a1>a>];+b[b1>b>];-b[<b<b1];+c[c>];%c(a1());}<a2<a<c<a1 | |
537 | +output: {+a[a2>a1>a>];+b[b1>b>];-b[<b<b1];+c[c>];%c(a1());}<c<a<a1<a2 | |
538 | diff --git a/elf/tst-audit23.c b/elf/tst-audit23.c | |
539 | index bb7d66c385..503699c36a 100644 | |
540 | --- a/elf/tst-audit23.c | |
541 | +++ b/elf/tst-audit23.c | |
542 | @@ -98,6 +98,8 @@ do_test (int argc, char *argv[]) | |
543 | char *lname; | |
544 | uintptr_t laddr; | |
545 | Lmid_t lmid; | |
546 | + uintptr_t cookie; | |
547 | + uintptr_t namespace; | |
548 | bool closed; | |
549 | } objs[max_objs] = { [0 ... max_objs-1] = { .closed = false } }; | |
550 | size_t nobjs = 0; | |
551 | @@ -117,6 +119,9 @@ do_test (int argc, char *argv[]) | |
552 | size_t buffer_length = 0; | |
553 | while (xgetline (&buffer, &buffer_length, out)) | |
554 | { | |
555 | + *strchrnul (buffer, '\n') = '\0'; | |
556 | + printf ("info: subprocess output: %s\n", buffer); | |
557 | + | |
558 | if (startswith (buffer, "la_activity: ")) | |
559 | { | |
560 | uintptr_t cookie; | |
561 | @@ -125,29 +130,26 @@ do_test (int argc, char *argv[]) | |
562 | &cookie); | |
563 | TEST_COMPARE (r, 2); | |
564 | ||
565 | - /* The cookie identifies the object at the head of the link map, | |
566 | - so we only add a new namespace if it changes from the previous | |
567 | - one. This works since dlmopen is the last in the test body. */ | |
568 | - if (cookie != last_act_cookie && last_act_cookie != -1) | |
569 | - TEST_COMPARE (last_act, LA_ACT_CONSISTENT); | |
570 | - | |
571 | if (this_act == LA_ACT_ADD && acts[nacts] != cookie) | |
572 | { | |
573 | + /* The cookie identifies the object at the head of the | |
574 | + link map, so we only add a new namespace if it | |
575 | + changes from the previous one. This works since | |
576 | + dlmopen is the last in the test body. */ | |
577 | + if (cookie != last_act_cookie && last_act_cookie != -1) | |
578 | + TEST_COMPARE (last_act, LA_ACT_CONSISTENT); | |
579 | + | |
580 | acts[nacts++] = cookie; | |
581 | last_act_cookie = cookie; | |
582 | } | |
583 | - /* The LA_ACT_DELETE is called in the reverse order of LA_ACT_ADD | |
584 | - at program termination (if the tests adds a dlclose or a library | |
585 | - with extra dependencies this will need to be adapted). */ | |
586 | + /* LA_ACT_DELETE is called multiple times for each | |
587 | + namespace, depending on destruction order. */ | |
588 | else if (this_act == LA_ACT_DELETE) | |
589 | - { | |
590 | - last_act_cookie = acts[--nacts]; | |
591 | - TEST_COMPARE (acts[nacts], cookie); | |
592 | - acts[nacts] = 0; | |
593 | - } | |
594 | + last_act_cookie = cookie; | |
595 | else if (this_act == LA_ACT_CONSISTENT) | |
596 | { | |
597 | TEST_COMPARE (cookie, last_act_cookie); | |
598 | + last_act_cookie = -1; | |
599 | ||
600 | /* LA_ACT_DELETE must always be followed by an la_objclose. */ | |
601 | if (last_act == LA_ACT_DELETE) | |
602 | @@ -179,6 +181,8 @@ do_test (int argc, char *argv[]) | |
603 | objs[nobjs].lname = lname; | |
604 | objs[nobjs].laddr = laddr; | |
605 | objs[nobjs].lmid = lmid; | |
606 | + objs[nobjs].cookie = cookie; | |
607 | + objs[nobjs].namespace = last_act_cookie; | |
608 | objs[nobjs].closed = false; | |
609 | nobjs++; | |
610 | ||
611 | @@ -201,6 +205,12 @@ do_test (int argc, char *argv[]) | |
612 | if (strcmp (lname, objs[i].lname) == 0 && lmid == objs[i].lmid) | |
613 | { | |
614 | TEST_COMPARE (objs[i].closed, false); | |
615 | + TEST_COMPARE (objs[i].cookie, cookie); | |
616 | + if (objs[i].namespace == -1) | |
617 | + /* No LA_ACT_ADD before the first la_objopen call. */ | |
618 | + TEST_COMPARE (acts[0], last_act_cookie); | |
619 | + else | |
620 | + TEST_COMPARE (objs[i].namespace, last_act_cookie); | |
621 | objs[i].closed = true; | |
622 | break; | |
623 | } | |
624 | @@ -209,11 +219,7 @@ do_test (int argc, char *argv[]) | |
625 | /* la_objclose should be called after la_activity(LA_ACT_DELETE) for | |
626 | the closed object's namespace. */ | |
627 | TEST_COMPARE (last_act, LA_ACT_DELETE); | |
628 | - if (!seen_first_objclose) | |
629 | - { | |
630 | - TEST_COMPARE (last_act_cookie, cookie); | |
631 | - seen_first_objclose = true; | |
632 | - } | |
633 | + seen_first_objclose = true; | |
634 | } | |
635 | } | |
636 | ||
637 | diff --git a/include/link.h b/include/link.h | |
638 | index 1d74feb2bd..69bda3ed17 100644 | |
639 | --- a/include/link.h | |
640 | +++ b/include/link.h | |
641 | @@ -278,6 +278,10 @@ struct link_map | |
642 | /* List of object in order of the init and fini calls. */ | |
643 | struct link_map **l_initfini; | |
644 | ||
645 | + /* Linked list of objects in reverse ELF constructor execution | |
646 | + order. Head of list is stored in _dl_init_called_list. */ | |
647 | + struct link_map *l_init_called_next; | |
648 | + | |
649 | /* List of the dependencies introduced through symbol binding. */ | |
650 | struct link_map_reldeps | |
651 | { | |
652 | diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h | |
653 | index e8b7359b04..9ea9389a39 100644 | |
654 | --- a/sysdeps/generic/ldsodefs.h | |
655 | +++ b/sysdeps/generic/ldsodefs.h | |
656 | @@ -1037,6 +1037,10 @@ extern int _dl_check_map_versions (struct link_map *map, int verbose, | |
657 | extern void _dl_init (struct link_map *main_map, int argc, char **argv, | |
658 | char **env) attribute_hidden; | |
659 | ||
660 | +/* List of ELF objects in reverse order of their constructor | |
661 | + invocation. */ | |
662 | +extern struct link_map *_dl_init_called_list attribute_hidden; | |
663 | + | |
664 | /* Call the finalizer functions of all shared objects whose | |
665 | initializer functions have completed. */ | |
666 | extern void _dl_fini (void) attribute_hidden; | |
667 | -- | |
668 | 2.39.2 | |
669 |