#!/usr/bin/perl -w

use strict;
use warnings;

if (scalar(@ARGV) != 1) {
    print <<END;
Usage:
    ./labelize.pl ApplicationOrFramework >labelized.txt
Or:
    otool -Vvt ApplicationOrFramework >unlabeled.txt
    (edit unlabeled.txt to contain just the interesting bits)
    ./labelize.pl ApplicationOrFramework <unlabeled.txt >labelized.txt
END
    exit;
}

my $app = shift @ARGV;
if (-t STDIN) {
    # Talking to a TTY means we weren't piped any input.
    open STDIN, "otool -Vvt $app |";
}

my %selectors = ();
open SELECTORS, "otool -Vv -s __DATA __objc_selrefs $app |";
foreach my $line (<SELECTORS>) {
    chomp($line);
    if ($line =~ /^([0-9a-f]{8})  __TEXT:__objc_methname:(.*)$/) {
        my $address = hex($1);
        my $name = $2;
        $selectors{$address} = $name;
    }
}
close SELECTORS;

my %cstrings = ();
open CSTRINGS, "otool -vV -s __TEXT __cstring $app |";
foreach my $line (<CSTRINGS>) {
    chomp($line);
    if ($line =~ /^([0-9a-f]{8})  (.*)$/) {
        my $address = hex($1);
        my $name = $2;
        $cstrings{$address} = $name;
    }
}
close CSTRINGS;

my %classnames = ();
open CLASSNAMES, "otool -vV -s __TEXT __objc_classname $app |";
foreach my $line (<CLASSNAMES>) {
    chomp($line);
    if ($line =~ /^([0-9a-f]{8})  (.*)$/) {
        my $address = hex($1);
        my $name = $2;
        $classnames{$address} = $name;
    }
}
close CLASSNAMES;

my %indirect_rels = ();
open INDIRECTS, "otool -vVI $app |";
foreach my $line (<INDIRECTS>) {
    chomp($line);
    if ($line =~ /0x([0-9a-f]{8})\s+(LOCAL|[0-9]+)\s+_(.*)$/) {
        my $address = hex($1);
        my $name = $3;
        $indirect_rels{$address} = $name;
    }
}
close INDIRECTS;

my %classrefs = ();
my %protorefs = ();
my %objc_const = ();
my %cfstrings = ();
my %objc_data = ();
my %data = ();
my %section_names = (
    "__DATA __objc_classrefs" => \%classrefs,
    "__DATA __objc_protorefs" => \%protorefs,
    "__DATA __objc_const" => \%objc_const,
    "__DATA __cfstring" => \%cfstrings,
    "__DATA __objc_data" => \%objc_data,
    "__DATA __data" => \%data
);
while (my ($section_name, $hashref) = each %section_names) {
    my @section_bytes = ();
    my $section_start = 0;
    open SECTION, "otool -vV -s $section_name $app |";
    foreach my $line (<SECTION>) {
        chomp($line);
        if ($line =~ /^([0-9a-f]{8})\t(.*)$/) {
            my $address = hex($1);
            my @bytes = split / /, $2;
            push @section_bytes, map(hex, @bytes);
            $section_start = ($section_start or $address);
        }
    }
    for (my $addr = $section_start; scalar(@section_bytes); $addr += 4) {
        my $key = shift @section_bytes;
        $key |= (shift @section_bytes) << 8;
        $key |= (shift @section_bytes) << 16;
        $key |= (shift @section_bytes) << 24;
        $hashref->{$addr} = $key;
    }
    close(SECTION);
}

while (my ($addr, $classptr) = each %classrefs) {
    my $roptr = $objc_data{$classptr + 16} // 0;
    my $nameptr = $objc_const{$roptr + 16} // 0;
    my $actual_name = $classnames{$nameptr} // sprintf("0x%08x", $addr);
    $classrefs{$addr} = $actual_name;
}

while (my ($addr, $cfptr) = each %cfstrings) {
    my $stringptr = $cstrings{$cfptr} // 0;
    if ($stringptr) {
        $cfstrings{$addr - 8} = $stringptr;
    }
}

while (my ($addr, $dataptr) = each %protorefs) {
    my $nameptr = $data{$dataptr + 4} // 0;
    my $actual_name = $classnames{$nameptr} // sprintf("0x%08x", $addr);
    $protorefs{$addr} = $actual_name;
}

my $picbase = 0;

foreach my $line (<>) {
    chomp($line);
    if ($line =~ /^([0-9a-f]{8})\s+(.*)$/) {
        my $address = hex($1);
        my $instruction = $2;
        if ($instruction =~ /calll\s+(0x[0-9a-f]{8})/) {
            my $where = hex($1);
            if ($where == $address+5) {
                $picbase = $where;
                my $hexed_picbase = sprintf("%08x", $picbase);
                $line .= "  // picbase = $hexed_picbase";
            }
        }

        if ($instruction =~ /0x([0-9a-f]{8})\(%e.[^p]\)/) {
            my $where = hex($1) + $picbase;
            if (defined $selectors{$where}) {
                $line .= "  // \@selector($selectors{$where})";
            } elsif (defined $cstrings{$where}) {
                $line .= "  // \"$cstrings{$where}\"";
            } elsif (defined $cfstrings{$where}) {
                $line .= "  // @\"$cfstrings{$where}\"";
            } elsif (defined $classrefs{$where}) {
                $line .= "  // [$classrefs{$where} class]";
            } elsif (defined $protorefs{$where}) {
                $line .= "  // \@protocol($protorefs{$where})";
            } elsif (defined $indirect_rels{$where}) {
                $line .= "  // extern $indirect_rels{$where}";
            } else {
                my $hexed_where = sprintf("%08x", $where);
                $line .= "  // $hexed_where";
            }
        }
    }

    print $line, "\n";
}
