Skip to content

Instantly share code, notes, and snippets.

@stbuehler
Created January 29, 2012 18:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stbuehler/1700019 to your computer and use it in GitHub Desktop.
Save stbuehler/1700019 to your computer and use it in GitHub Desktop.
preware build.git: build stub libs for libs from PalmSDK
#!/usr/bin/env perl
# Creates source code to build a stub library for a shared library
# Takes the path to a shared library, creates sources in the local
# directory (does NOT override anything) and prints Makefile lines
# to stdout.
# needs "objdump" in the path
# Copyright (C) 2012 Stefan Bühler <stbuehler@web.de>
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 2
# as published by the Free Software Foundation
# This program 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 General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
use strict;
use Fcntl qw(:DEFAULT :mode);
use IO::Handle;
use File::Basename;
my $fh;
my $lib = $ARGV[0];
if ($lib !~ /\.so[0-9.]*/) {
print STDERR "'$lib' doesn't look like a shared libary filename\n";
exit(1);
}
{
my @st = lstat($lib);
unless (scalar(@st)) {
print STDERR "couldn't open shared library '$lib'\n";
exit(1);
}
if (S_ISLNK($st[2])) {
# symlink
my $target = readlink($lib);
print "$lib: $target\n\tln -s $target $lib\n\n";
exit(0);
}
unless (S_ISREG($st[2])) {
print STDERR "couldn't open shared library: not a regular file: '$lib'\n";
exit(1);
}
}
my %symVersions;
my @localSymbols;
# needed for versioned symbols, so we have "unique" internal names
my $MAGIC = 'voi2ees';
my $counter = 1;
my $sourceText;
sub warnSymbol {
my ($msg) = shift;
print "Warning '$msg': couldn't parse symbol '@_'\n";
}
sub versionName {
my ($name) = @_;
if ($name =~ m/^\((.*)\)$/) {
return $1 unless wantarray;
return ($1, 0);
} else {
return $name unless wantarray;
return ($name, 1);
}
}
# creates version if it didn't exist
sub getVersion {
my ($name) = @_;
$name = versionName($name);
$symVersions{$name} = { base => undef, symbols => [ ] } unless defined $symVersions{$name};
return $symVersions{$name};
}
sub isVersion {
my ($name) = @_;
$name = versionName($name);
return (defined $symVersions{$name});
}
sub addVersionInheritance {
my ($base, $version) = @_;
my $v = getVersion($version);
$v->{'base'} = $base;
getVersion($base);
return;
}
sub addVersionSymbol {
my ($version, $symbol) = @_;
my $v = getVersion($version);
push @{$v->{'symbols'}}, $symbol;
return ($symbol, '') if $version eq 'Base';
# setup version mapping
my $priv_sym = $symbol.'__'.$MAGIC.'_'.$counter;
++$counter;
my $default;
($version, $default) = versionName($version);
push @localSymbols, $priv_sym;
return ($priv_sym, 'asm(".symver '.$priv_sym.','.$symbol . ($default ? '@@' : '@') . $version . '");' . "\n");
}
# normal C function with 'g DF' flags
# we don't care about address and size
sub addFunction {
my ($section, $version, $name) = @_;
my $symText;
($name, $symText) = addVersionSymbol($version, $name);
my $attrs = '';
$attrs .= ' __attribute__ ((section ("'.$section.'")))' if $section ne '.text';
$sourceText .= "void$attrs $name(void) { }\n".$symText;
return;
}
# flags 'g DO', section '.rodata'. need size, don't care about address
sub addReadonlyObject {
my ($size, $version, $name) = @_;
my $symText;
($name, $symText) = addVersionSymbol($version, $name);
if ($size > 0) {
$sourceText .= sprintf("const unsigned char %s[0x%x] = { 0 };\n", $name, $size).$symText;
} else {
$sourceText .= sprintf("const unsigned char %s[0] = { };\n", $name).$symText;
}
}
# flags 'g DO', section '.data'. need size, don't care about address
sub addInitializedObject {
my ($size, $version, $name) = @_;
my $symText;
($name, $symText) = addVersionSymbol($version, $name);
if ($size > 0) {
$sourceText .= sprintf("unsigned char %s[0x%x] = { 1 };\n", $name, $size).$symText;
} else {
# cheat: size 0 with initialization not possible. probably not needed anyway :)
$sourceText .= sprintf("unsigned char %s[1] = { 1 };\n", $name).$symText;
}
}
# flags 'g DO', section '.bss'. need size, don't care about address
sub addUninitializedObject {
my ($size, $version, $name) = @_;
my $symText;
($name, $symText) = addVersionSymbol($version, $name);
$sourceText .= sprintf("unsigned char %s[0x%x];\n", $name, $size).$symText;
}
# flags 'g DO', section '.data.rel.ro'. need size, don't care about address
sub addDynRelReadonlyObject {
my ($size, $version, $name) = @_;
my $symText;
($name, $symText) = addVersionSymbol($version, $name);
# create self reference, with -fPIC this results in .data.rel.ro
$sourceText .= sprintf("void * const %s[0x%x/sizeof(void*)] = { (void*) &%s };\n", $name, $size, $name).$symText;
}
sub parseSymbol {
my ($value, $flags, $section, $size_or_align, $version, $name) = @_;
my @flags = split //, $flags;
my $ivalue = hex($value);
my $isize_or_align = hex($size_or_align);
return if ($flags[0] eq 'l' || $flags[0] eq ' '); # skip local and "neither local/global" symbols
return warnSymbol('global+local symbol', @_) if ($flags[0] eq '!');
# now $flags[0] is either g (global) or u (unique global)
return if ($section eq '.init' && $name eq '_init');
return if ($section eq '.fini' && $name eq '_fini');
return if ($flags[6] eq ' '); # drop non-"object/filename/function"
return warnSymbol('symbol refers to a filename', @_) if $flags[6] eq 'f';
return addFunction($section, $version, $name) if $flags eq 'g DF';
return addReadonlyObject($isize_or_align, $version, $name) if ($flags eq 'g DO' && $section eq '.rodata');
return addInitializedObject($isize_or_align, $version, $name) if ($flags eq 'g DO' && $section eq '.data');
return addUninitializedObject($isize_or_align, $version, $name) if ($flags eq 'g DO' && $section eq '.bss');
return addDynRelReadonlyObject($isize_or_align, $version, $name) if ($flags eq 'g DO' && $section eq '.data.rel.ro');
if ($version eq $name || defined $symVersions{$name}) {
return warnSymbol('invalid version symbol', @_) if ($ivalue != 0 || $flags ne 'g DO' || $section ne '*ABS*' || $isize_or_align != 0);
addVersionInheritance($version, $name);
return;
}
print "Found Symbol: '@_'\n";
}
sub generateMapEntry {
my ($version) = @_;
my $v = getVersion($version);
return '' if $v->{'generated'};
$v->{'generated'} = 1;
return '' if ($version eq 'Base'); # don't generate "Base"
my $txt = '';
$txt .= generateMapEntry($v->{'base'}) if defined $v->{'base'};
$txt .= $version . " {\n";
$txt .= "\tglobal:\n\t\t" . join(";\n\t\t", @{$v->{'symbols'}}) . ";\n" if scalar(@{$v->{'symbols'}}) > 0;
if (scalar(@localSymbols) > 0) {
$txt .= "\tlocal:\n\t\t" . join(";\n\t\t", @localSymbols) . ";\n";
@localSymbols = ();
}
$txt .= '}';
$txt .= ' '.$v->{'base'} if defined $v->{'base'} && $v->{'base'} ne $version;
$txt .= ";\n";
return $txt;
}
# only run once!
sub generateMap {
my @versions = keys %symVersions;
my $txt = '';
if (0 == scalar(@versions) || (1 == scalar(@versions) && $versions[0] eq 'Base')) {
$txt .= "{\n";
$txt .= "\tglobal: *;\n";
if (scalar(@localSymbols) > 0) {
$txt .= "\tlocal:\n\t\t" . join(";\n\t\t", @localSymbols) . ";\n";
@localSymbols = ();
}
$txt .= "}\n;";
} else {
foreach my $v (@versions) {
$txt .= generateMapEntry($v);
}
}
return $txt;
}
open($fh, '-|', "objdump -T -p $lib");
my $mode = 0;
my $soName;
while (<$fh>) {
chomp;
if (0 == $mode) {
if (/^DYNAMIC SYMBOL TABLE:$/) {
$mode = 1;
} elsif (/^Dynamic Section:$/) {
$mode = 2;
}
} elsif (1 == $mode) {
if (/^$/) {
$mode = 0;
} elsif (/^([0-9a-f]+)[ \t]([lgu! ][w ][C ][W ][Ii ][dD ][FfO ])[ \t](\S+)[ \t]+([0-9a-f]+)[ \t]+(\S*)[ \t]+(\S+)$/) {
parseSymbol($1, $2, $3, $4, $5, $6);
} else {
print STDERR "Cannot parse symbol table line: '", $_, "'\n";
}
} elsif (2 == $mode) {
if (/^$/) {
$mode = 0;
} elsif (/^[ \t]*SONAME[ \t]+(\S+)$/) {
$soName = "-Wl,-soname,$1";
}
}
}
$lib = basename($lib);
my $basename = $lib;
my $srcFname = $lib . '_stub.c';
my $mapFname = $lib . '_stub.map';
print "$lib: $srcFname $mapFname\n";
print "\t\$(CC) \$(CFLAGS) -shared $soName $srcFname -o $lib -fPIC -Wl,--no-undefined -Wl,--version-script=$mapFname\n\n";
my ($SRCFILE, $MAPFILE);
sysopen($SRCFILE, $srcFname, O_WRONLY|O_CREAT|O_EXCL) or die "couldn't open '$srcFname': $!";
print $SRCFILE $sourceText;
close $SRCFILE;
sysopen($MAPFILE, $mapFname, O_WRONLY|O_CREAT|O_EXCL) or die "couldn't open '$srcFname': $!";
print $MAPFILE generateMap();
close $MAPFILE;
exit(0);
NAME = PDK
TITLE = PDK
APP_ID = org.webosinternals.toolchain.${NAME}
VERSION = 1.0-1
MAINTAINER = WebOS Internals <support@webos-internals.org>
.PHONY: package
package: build/.built-${VERSION}
.PHONY: stage
stage: build/.staged-${VERSION}
build/.staged-${VERSION}: build/armv7.staged-${VERSION} build/armv6.staged-${VERSION} build/i686.staged-${VERSION}
touch $@
build/%.staged-${VERSION} : build/%.built-${VERSION}
mkdir -p ../../staging/$*/usr/include
cp -rp build/$*/usr/include/* ../../staging/$*/usr/include/
mkdir -p ../../staging/$*/usr/lib
cp -rp build/$*/usr/lib/* ../../staging/$*/usr/lib/
touch $@
include ../../support/cross-compile.mk
.PHONY: unpack
unpack: build/.unpacked-${VERSION}
build/.unpacked-${VERSION}:
rm -rf build
mkdir -p build/armv6 build/armv7 build/i686
cp -rip src build/src
touch $@
.PHONY: build
build: build/.built-${VERSION}
build/.built-${VERSION}: build/armv7.built-${VERSION} build/armv6.built-${VERSION} build/i686.built-${VERSION}
touch $@
build/%.built-${VERSION}: build/.unpacked-${VERSION}
rm -rf build/$*
${MAKE} -C build/src CC=${CROSS_COMPILE_$*}gcc DESTDIR=../$*/ clobber all install
touch $@
clobber::
rm -rf build
update-source:
rm -rf src
mkdir -p src
cp -ar /opt/PalmPDK/include src/
# exclude runtime provided libs: libc-* libc.* libcrypt.so* libdl.* libgcc_s.so* libm-* libm.* libpthread-* libpthread.* librt.so* libstdc++.so* libutil.so*
@echo ".PHONY: libs all clobber install" >> src/Makefile
@echo "all: libs" >> src/Makefile
@echo >> src/Makefile
@(cd src; \
LIBS=""; \
for lib in $$(find /opt/PalmPDK/device/lib/ -type f -name '*.so*' \
-a ! -name libc-* -a ! -name libc.* -a ! -name libcrypt.so* -a ! -name libdl.* -a ! -name libgcc_s.so* -a ! -name libm-* \
-a ! -name libm.* -a ! -name libpthread-* -a ! -name libpthread.* -a ! -name librt.so* -a ! -name libstdc++.so* -a ! -name libutil.so* | sort); do \
LIBS="$$LIBS $$(basename $$lib)"; \
../create-stub-lib $$lib >> Makefile; \
done; \
echo "libs: $$LIBS" >> Makefile; \
echo >> Makefile; \
echo "clobber:" >> Makefile; \
echo " rm -f $$LIBS" >> Makefile; \
echo >> Makefile; \
echo "install: libs" >> Makefile; \
echo ' mkdir -p $$(DESTDIR)/usr/lib' >> Makefile; \
echo ' cp -ar include $$(DESTDIR)/usr/' >> Makefile; \
echo " cp -a $$LIBS "'$$(DESTDIR)/usr/lib/' >> Makefile; \
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment