2 * Copyright (C) 2011 Jan Niklas Hasse <jhasse@gmail.com>
3 * Copyright (C) 2013 Upstairs Laboratories Inc.
5 * This library is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 2.1 of
8 * the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * Jan Niklas Hasse <jhasse@gmail.com>
21 * Tristan Van Berkom <tristan@upstairslabs.com>
25 static const string DEFAULT_TARGET
= "2.7";
26 static const string DEFAULT_TARGET_HELP
= "Target glibc ABI (Default 2.7)";
28 /***************************************************************
29 * Debugging Facilities *
30 ***************************************************************/
40 static const GLib
.DebugKey
[] libcwrap_debug_keys
= {
41 { "collect", DF
.COLLECT
},
42 { "filter", DF
.FILTER
},
43 { "version-parse", DF
.VERSION_PARSE
},
44 { "version-compare", DF
.VERSION_COMPARE
},
45 { "dump-files", DF
.DUMP_FILES
}
48 private uint libcwrap_debug_mask
= 0;
49 public delegate
void DebugFunc ();
51 public void libcwrap_note (int domain
, DebugFunc debug_func
)
53 if ((libcwrap_debug_mask
& domain
) != 0)
57 /***************************************************************
58 * VersionNumber class *
59 ***************************************************************/
60 class VersionNumber
: Object
62 private int major
{ get; set; } // x.0.0
63 private int minor
{ get; set; } // 0.x.0
64 private int revision
{ get; set; } // 0.0.x
65 private string originalString
;
67 public VersionNumber (string version
) {
69 originalString
= version
;
72 var regex
= new
Regex("([[:digit:]]*)\\.([[:digit:]]*)\\.*([[:digit:]]*)");
73 var split
= regex
.split(version
);
75 assert (split
.length
> 1); // TODO: Don't use assert, print a nice error message instead
76 major
= int.parse (split
[1]);
79 minor
= int.parse (split
[2]);
84 revision
= int.parse (split
[3]);
87 } catch (GLib
.RegexError e
) {
88 stdout
.printf ("Error compiling regular expression: %s", e
.message
);
92 libcwrap_note (DF
.VERSION_PARSE
, () =>
93 stdout
.printf ("Version string '%s' parsed as major: %d minor: %d rev: %d\n",
94 originalString
, major
, minor
, revision
));
97 public bool newerThan(VersionNumber other
) {
98 bool newerThanOther
= false;
100 if (major
> other
.major
) {
101 newerThanOther
= true;
102 } else if (major
== other
.major
) {
104 if (minor
> other
.minor
) {
105 newerThanOther
= true;
106 } else if (minor
== other
.minor
) {
108 if(revision
> other
.revision
) {
109 newerThanOther
= true;
114 libcwrap_note (DF
.VERSION_COMPARE
, () =>
115 stdout
.printf ("Version '%s' is %s than version '%s'\n",
117 newerThanOther ?
"newer" : "older",
120 return newerThanOther
;
123 public string getString() {
124 return originalString
;
128 /***************************************************************
130 ***************************************************************/
131 public class Main
: Object
{
133 /* Command line options */
134 private static string? libdir
= null;
135 private static string? output
= null;
136 private static string? target
= null;
138 private const GLib
.OptionEntry
[] options
= {
139 { "libdir", 'l', 0, OptionArg
.FILENAME
, ref libdir
, "Library directory", "<DIRECTORY>" },
140 { "output", 'o', 0, OptionArg
.STRING
, ref output
, "Header to create", "<FILENAME>" },
141 { "target", 't', 0, OptionArg
.STRING
, ref target
, DEFAULT_TARGET_HELP
, "<MAJOR.MINOR[.MICRO]>" },
145 /* Local variables */
146 private static VersionNumber minimumVersion
;
147 private static Gee
.HashMap
<string, VersionNumber
>symbolMap
;
148 private static Gee
.HashSet
<string>filterMap
;
150 public static int main (string[] args
) {
152 /* Initialize debugging */
153 string libcwrap_debug_env
= GLib
.Environment
.get_variable ("LIBCWRAP_DEBUG");
154 libcwrap_debug_mask
= GLib
.parse_debug_string (libcwrap_debug_env
, libcwrap_debug_keys
);
156 /* Initialize the default here */
157 target
= DEFAULT_TARGET
;
160 var opt_context
= new
OptionContext ("- Libc compatibility header generator");
161 opt_context
.set_help_enabled (true);
162 opt_context
.add_main_entries (options
, null);
163 opt_context
.parse (ref args
);
164 } catch (OptionError e
) {
165 stdout
.printf ("error: %s\n", e
.message
);
166 stdout
.printf ("Run '%s --help' for a list of available command line options.\n", args
[0]);
170 if (libdir
== null) {
171 stdout
.printf ("Must specify --libdir\n");
172 stdout
.printf ("Run '%s --help' for a list of available command line options.\n", args
[0]);
176 if (output
== null) {
177 stdout
.printf ("Must specify --output\n");
178 stdout
.printf ("Run '%s --help' for a list of available command line options.\n", args
[0]);
182 /* Initialize local resources */
183 minimumVersion
= new
VersionNumber (target
);
185 /* All symbols, containing the newest possible version before 'minimumVersion' if possible */
186 symbolMap
= new Gee
.HashMap
<string, VersionNumber
>((Gee
.HashDataFunc
<string>)GLib
.str_hash
,
187 (Gee
.EqualDataFunc
<string>)GLib
.str_equal
);
189 /* All symbols which did not have any version > minimumVersion */
190 filterMap
= new Gee
.HashSet
<string>((Gee
.HashDataFunc
<string>)GLib
.str_hash
,
191 (Gee
.EqualDataFunc
<string>)GLib
.str_equal
);
195 stdout
.printf ("Generating %s (glibc %s) from libs at '%s' .", output
, minimumVersion
.getString(), libdir
);
202 warning("%s", e
.message
);
206 stdout
.printf(" OK\n");
211 private static void parseLibrary (Regex regex
, FileInfo fileinfo
) throws Error
{
212 string commandToUse
= "objdump -T";
213 string output
, errorOutput
;
216 /* For testing on objdump files which might be collected on other systems,
217 * run the generator with --libdir /path/to/objdump/files/
219 if ((libcwrap_debug_mask
& DF
.DUMP_FILES
) != 0)
220 commandToUse
= "cat";
222 Process
.spawn_command_line_sync (commandToUse
+ " " + libdir
+ "/" + fileinfo
.get_name(),
223 out output
, out errorOutput
, out returnCode
);
228 foreach (var line
in output
.split ("\n")) {
230 if (regex
.match (line
) && !("PRIVATE" in line
)) {
231 var version
= new
VersionNumber (regex
.split (line
)[3]);
232 var symbolName
= regex
.split (line
)[7];
233 var versionInMap
= symbolMap
.get (symbolName
);
235 /* Some versioning symbols exist in the objdump output, let's skip those */
236 if (symbolName
.has_prefix ("GLIBC"))
239 libcwrap_note (DF
.COLLECT
, () =>
240 stdout
.printf ("Selected symbol '%s' version '%s' from objdump line %s\n",
241 symbolName
, version
.getString(), line
));
243 if (versionInMap
== null) {
245 symbolMap
.set (symbolName
, version
);
247 /* First occurance of the symbol, if it's older
248 * than the minimum required, put it in that table also
250 if (minimumVersion
.newerThan (version
)) {
251 filterMap
.add (symbolName
);
252 libcwrap_note (DF
.FILTER
, () =>
253 stdout
.printf ("Adding symbol '%s %s' to the filter\n",
254 symbolName
, version
.getString()));
259 /* We want the newest possible version of a symbol which is older than the
260 * minimum glibc version specified (or the only version of the symbol if
261 * it's newer than the minimum version)
264 /* This symbol is <= minimumVersion
266 if (!version
.newerThan (minimumVersion
)) {
268 /* What we have in the map is > minimumVersion, so we need this version */
269 if (versionInMap
.newerThan (minimumVersion
))
270 symbolMap
.set (symbolName
, version
);
272 /* What we have in the map is already <= minimumVersion, but we want
273 * the most recent acceptable symbol
275 else if (version
.newerThan (versionInMap
))
276 symbolMap
.set (symbolName
, version
);
279 } else { /* This symbol is > minimumVersion */
281 /* If there are only versions > minimumVersion, then we want
282 * the lowest possible version, this is because we try to provide
283 * information in the linker warning about what version the symbol
284 * was initially introduced in.
286 if (versionInMap
.newerThan (minimumVersion
) &&
287 versionInMap
.newerThan (version
))
288 symbolMap
.set (symbolName
, version
);
291 /* While trucking along through the huge symbol list, remove symbols from
292 * the 'safe to exclude' if there is a version found which is newer
293 * than the minimum requirement
295 if (version
.newerThan (minimumVersion
)) {
296 filterMap
.remove(symbolName
);
297 libcwrap_note (DF
.FILTER
, () =>
298 stdout
.printf ("Removing symbol '%s' from the filter, found version '%s'\n",
299 symbolName
, version
.getString()));
304 libcwrap_note (DF
.COLLECT
, () => stdout
.printf ("Rejected objdump line %s\n", line
));
309 private static void parseLibraries () throws Error
{
310 var libPath
= File
.new_for_path (libdir
);
311 var enumerator
= libPath
.enumerate_children (FileAttribute
.STANDARD_NAME
, 0, null);
312 var regex
= new
Regex ("(.*)(GLIBC_)([0-9]+\\.([0-9]+\\.)*[0-9]+)(\\)?)([ ]*)(.+)");
317 while ((fileinfo
= enumerator
.next_file(null)) != null) {
319 if (++counter
% 50 == 0) {
324 parseLibrary (regex
, fileinfo
);
328 private static void appendSymbols (StringBuilder headerFile
, bool unavailableSymbols
) {
330 if (unavailableSymbols
)
331 headerFile
.append("\n/* Symbols introduced in newer glibc versions, which must not be used */\n");
333 headerFile
.append("\n/* Symbols redirected to earlier glibc versions */\n");
335 foreach (var it
in symbolMap
.keys
) {
336 var version
= symbolMap
.get (it
);
339 /* If the symbol only has occurrences older than the minimum required glibc version,
340 * then there is no need to output anything for this symbol
342 if (filterMap
.contains (it
))
345 /* If the only available symbol is > minimumVersion, then redirect it
346 * to a comprehensible linker error, otherwise redirect the symbol
347 * to it's existing version <= minimumVersion.
349 if (version
.newerThan (minimumVersion
)) {
351 versionToUse
= "DONT_USE_THIS_VERSION_%s".printf (version
.getString());
352 if (!unavailableSymbols
)
357 versionToUse
= version
.getString ();
358 if (unavailableSymbols
)
362 headerFile
.append("__asm__(\".symver %s, %s@GLIBC_%s\");\n".printf(it
, it
, versionToUse
));
366 private static void generateHeader () throws Error
{
367 var headerFile
= new
StringBuilder ();
369 /* FIXME: Currently we do:
371 * if !defined (__OBJC__) && !defined (__ASSEMBLER__)
373 * But what we want is a clause which accepts any form of C including C++ and probably
374 * also including ObjC. That said, the generated header works fine for C and C++ sources.
376 headerFile
.append ("/* glibc bindings for target ABI version glibc " + minimumVersion
.getString() + " */\n");
377 headerFile
.append ("#if !defined (__LIBC_CUSTOM_BINDINGS_H__)\n");
378 headerFile
.append ("\n");
379 headerFile
.append ("# if !defined (__OBJC__) && !defined (__ASSEMBLER__)\n");
380 headerFile
.append ("# if defined (__cplusplus)\n");
381 headerFile
.append ("extern \"C\" {\n");
382 headerFile
.append ("# endif\n");
384 /* First generate the available redirected symbols, then the unavailable symbols */
385 appendSymbols (headerFile
, false);
386 appendSymbols (headerFile
, true);
388 headerFile
.append ("\n");
389 headerFile
.append ("# if defined (__cplusplus)\n");
390 headerFile
.append ("}\n");
391 headerFile
.append ("# endif\n");
392 headerFile
.append ("# endif /* !defined (__OBJC__) && !defined (__ASSEMBLER__) */\n");
393 headerFile
.append ("#endif\n");
395 FileUtils
.set_contents (output
, headerFile
.str
);