2 # Move symbols from other shared objects into libc.so.
3 # Copyright (C) 2020-2021 Free Software Foundation, Inc.
4 # This file is part of the GNU C Library.
6 # The GNU C Library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
11 # The GNU C Library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with the GNU C Library; if not, see
18 # <https://www.gnu.org/licenses/>.
20 """Move symbols from other shared objects into libc.so.
22 This script moves ABI symbols from non-libc abilists in to
23 libc.abilist. Symbol versions are preserved. The script must be
24 called from the top of the glibc source tree.
32 def replace_file(path
, new_contents
):
33 """Atomically replace PATH with lines from NEW_CONTENTS.
35 NEW_CONTENTS must be a sequence of strings.
39 with
open(temppath
, 'w') as out
:
40 for line
in new_contents
:
42 os
.rename(temppath
, path
)
44 class VersionedSymbol
:
45 """A combination of a symbol and its version."""
47 def __init__(self
, symbol
, version
):
48 """Construct a new versioned symbol."""
52 self
.version
= version
55 return self
.symbol
+ '@' + self
.version
57 def __eq__(self
, other
):
58 return self
.symbol
== other
.symbol
and self
.version
== other
.version
61 return hash(self
.symbol
) ^
hash(self
.version
)
63 def read_abilist(path
):
64 """Read the abilist file at PATH.
66 Return a dictionary from VersionedSymbols to their flags (as
71 with
open(path
) as inp
:
73 version
, symbol
, flags
= line
.strip().split(' ', 2)
74 result
[VersionedSymbol(symbol
, version
)] = flags
77 def abilist_lines(symbols
):
78 """Build the abilist file contents (as a list of lines).
80 SYMBOLS is a dictionary from VersionedSymbols to their flags.
84 for versym
, flags
in symbols
.items():
85 result
.append('{} {} {}\n'.format(
86 versym
.version
, versym
.symbol
, flags
))
90 def add_to_libc_path(path
, new_symbols
):
91 """Add SYMBOLS to the abilist file PATH.
93 NEW_SYMBOLS is a dictionary from VersionedSymbols to their flags.
96 original_symbols
= read_abilist(path
)
97 updated_symbols
= original_symbols
.copy()
98 updated_symbols
.update(new_symbols
)
99 if updated_symbols
!= original_symbols
:
100 sys
.stdout
.write('updating libc abilist {}\n'.format(path
))
101 replace_file(path
, abilist_lines(updated_symbols
))
103 # The name of the libc.so abilist file.
104 libc_abilist
= 'libc.abilist'
106 def add_to_libc_fallback(directory
, subdirs
, symbol_lines
):
107 """Add SYMBOL_LINES to the libc.abilist files in SUBDIRS in DIRECTORY.
109 All subdirectories must exist. If they do, return True. If not,
110 skip processing and return False.
113 abilists
= [os
.path
.join(directory
, subdir
, libc_abilist
)
114 for subdir
in subdirs
]
115 for abilist
in abilists
:
116 if not os
.path
.exists(abilist
):
118 for abilist
in abilists
:
119 add_to_libc_path(abilist
, symbol_lines
)
122 def add_to_libc(directory
, symbol_lines
):
124 """Add SYMBOL_LINES (a list of strings) to libc.abilist in DIRECTORY.
126 Try specific subdirectories as well if libc.abilist is not found
130 libc_path
= os
.path
.join(directory
, libc_abilist
)
131 if os
.path
.exists(libc_path
):
132 add_to_libc_path(libc_path
, symbol_lines
)
135 # Special case for powerpc32 and mips32 variants.
136 if add_to_libc_fallback(directory
, ('fpu', 'nofpu'), symbol_lines
):
139 # Special case for mips64.
140 if add_to_libc_fallback(directory
, ('n32', 'n64'), symbol_lines
):
143 raise IOError('No libc.abilist found for: {}'.format(directory
))
145 def move_symbols_1(path
, to_move
, moved_symbols
):
146 """Move SYMBOLS from the abilist file PATH to MOVED_SYMBOLS.
148 TO_MOVE must be a set of strings. MOVED_SYMBOLS is a dictionary.
152 assert path
.endswith('.abilist')
153 library
= os
.path
.basename(path
)[:-len(suffix
)]
154 placeholder
= '__{}_version_placeholder'.format(library
)
159 old_symbols
= read_abilist(path
)
160 old_versions
= set(versym
.version
for versym
in old_symbols
.keys())
161 matching_symbols
= dict(e
for e
in old_symbols
.items()
162 if e
[0].symbol
in to_move
)
164 sys
.stdout
.write('updating {} abilist {}\n'.format(library
, path
))
165 new_symbols
= dict(e
for e
in old_symbols
.items()
166 if e
[0].symbol
not in to_move
)
168 # Add placeholder symbols to prevent symbol versions from
169 # going away completely.
170 new_versions
= set(versym
.version
for versym
in new_symbols
.keys())
171 for missing_version
in old_versions
- new_versions
:
172 new_symbols
[VersionedSymbol(placeholder
, missing_version
)] = 'F'
174 replace_file(path
, abilist_lines(new_symbols
))
176 moved_symbols
.update(matching_symbols
)
178 def move_symbols(directory
, files
, symbols
):
179 """Move SYMBOLS from FILES (a list of abilist file names) in DIRECTORY.
181 SYMBOLS must be a set of strings.
185 for filename
in files
:
186 move_symbols_1(os
.path
.join(directory
, filename
), symbols
,
189 add_to_libc(directory
, moved_symbols
)
192 """Return an argument parser for this module."""
193 parser
= argparse
.ArgumentParser(description
=__doc__
)
194 parser
.add_argument('--only-linux', action
='store_true',
195 help='Restrict the operation to Linux abilists')
196 parser
.add_argument('symbols', help='name of the symbol to move',
201 """The main entry point."""
202 parser
= get_parser()
203 opts
= parser
.parse_args(argv
)
205 sysdeps
= 'sysdeps/unix/sysv/linux'
209 symbols
= frozenset(opts
.symbols
)
211 for directory
, dirs
, files
in os
.walk(sysdeps
):
212 move_symbols(directory
, [name
for name
in files
213 if name
!= 'libc.abilist'
214 and name
.endswith('.abilist')], symbols
)
216 if __name__
== '__main__':