| File: | Dpkg/Shlibs/Objdump/Object.pm |
| Coverage: | 74.5% |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | # Copyright © 2007-2010 Raphaël Hertzog <hertzog@debian.org> | ||||||
| 2 | # Copyright © 2007-2009,2012-2015,2017-2018 Guillem Jover <guillem@debian.org> | ||||||
| 3 | # | ||||||
| 4 | # This program is free software; you can redistribute it and/or modify | ||||||
| 5 | # it under the terms of the GNU General Public License as published by | ||||||
| 6 | # the Free Software Foundation; either version 2 of the License, or | ||||||
| 7 | # (at your option) any later version. | ||||||
| 8 | # | ||||||
| 9 | # This program is distributed in the hope that it will be useful, | ||||||
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||||
| 12 | # GNU General Public License for more details. | ||||||
| 13 | # | ||||||
| 14 | # You should have received a copy of the GNU General Public License | ||||||
| 15 | # along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||||
| 16 | |||||||
| 17 | =encoding utf8 | ||||||
| 18 | |||||||
| 19 - 30 | =head1 NAME Dpkg::Shlibs::Objdump::Object - represent an object from objdump output =head1 DESCRIPTION This module provides a class to represent an object parsed from L<objdump(1)> output. B<Note>: This is a private module, its API can change at any time. =cut | ||||||
| 31 | |||||||
| 32 | package Dpkg::Shlibs::Objdump::Object 0.01; | ||||||
| 33 | |||||||
| 34 | 6 6 6 | 20 6 142 | use strict; | ||||
| 35 | 6 6 6 | 12 2 242 | use warnings; | ||||
| 36 | 6 6 6 | 24 2 264 | use feature qw(state); | ||||
| 37 | |||||||
| 38 | 6 6 6 | 14 2 220 | use Dpkg::Gettext; | ||||
| 39 | 6 6 6 | 10 6 232 | use Dpkg::ErrorHandling; | ||||
| 40 | 6 6 6 | 5780 6 252 | use Dpkg::Path qw(find_command); | ||||
| 41 | 6 6 6 | 22 10 9734 | use Dpkg::Arch qw(debarch_to_gnutriplet get_build_arch get_host_arch); | ||||
| 42 | |||||||
| 43 | sub new { | ||||||
| 44 | 48 | 0 | 83 | my $this = shift; | |||
| 45 | 48 | 437 | my $file = shift // ''; | ||||
| 46 | 48 | 515 | my $class = ref($this) || $this; | ||||
| 47 | 48 | 72 | my $self = {}; | ||||
| 48 | 48 | 90 | bless $self, $class; | ||||
| 49 | |||||||
| 50 | 48 | 129 | $self->reset; | ||||
| 51 | 48 | 96 | if ($file) { | ||||
| 52 | 0 | 0 | $self->analyze($file); | ||||
| 53 | } | ||||||
| 54 | |||||||
| 55 | 48 | 7833 | return $self; | ||||
| 56 | } | ||||||
| 57 | |||||||
| 58 | sub reset { | ||||||
| 59 | 72 | 0 | 100 | my $self = shift; | |||
| 60 | |||||||
| 61 | 72 | 196 | $self->{file} = ''; | ||||
| 62 | 72 | 161 | $self->{id} = ''; | ||||
| 63 | 72 | 142 | $self->{HASH} = ''; | ||||
| 64 | 72 | 203 | $self->{GNU_HASH} = ''; | ||||
| 65 | 72 | 312 | $self->{INTERP} = 0; | ||||
| 66 | 72 | 159 | $self->{SONAME} = ''; | ||||
| 67 | 72 | 132 | $self->{NEEDED} = []; | ||||
| 68 | 72 | 144 | $self->{RPATH} = []; | ||||
| 69 | 72 | 31888 | $self->{dynsyms} = {}; | ||||
| 70 | 72 | 193 | $self->{flags} = {}; | ||||
| 71 | 72 | 307 | $self->{dynrelocs} = {}; | ||||
| 72 | |||||||
| 73 | 72 | 133 | return $self; | ||||
| 74 | } | ||||||
| 75 | |||||||
| 76 | sub _select_objdump { | ||||||
| 77 | # Decide which objdump to call | ||||||
| 78 | 0 | 0 | if (get_build_arch() ne get_host_arch()) { | ||||
| 79 | 0 | 0 | my $od = debarch_to_gnutriplet(get_host_arch()) . '-objdump'; | ||||
| 80 | 0 | 0 | return $od if find_command($od); | ||||
| 81 | } | ||||||
| 82 | 0 | 0 | return 'objdump'; | ||||
| 83 | } | ||||||
| 84 | |||||||
| 85 | sub analyze { | ||||||
| 86 | 0 | 0 | 0 | my ($self, $file) = @_; | |||
| 87 | |||||||
| 88 | 0 | 0 | $file ||= $self->{file}; | ||||
| 89 | 0 | 0 | return unless $file; | ||||
| 90 | |||||||
| 91 | 0 | 0 | $self->reset; | ||||
| 92 | 0 | 0 | $self->{file} = $file; | ||||
| 93 | |||||||
| 94 | 0 | 0 | $self->{exec_abi} = Dpkg::Shlibs::Objdump::get_format($file); | ||||
| 95 | |||||||
| 96 | 0 | 0 | if (not defined $self->{exec_abi}) { | ||||
| 97 | 0 | 0 | warning(g_("unknown executable format in file '%s'"), $file); | ||||
| 98 | 0 | 0 | return; | ||||
| 99 | } | ||||||
| 100 | |||||||
| 101 | 0 | 0 | state $OBJDUMP = _select_objdump(); | ||||
| 102 | 0 | 0 | local $ENV{LC_ALL} = 'C'; | ||||
| 103 | 0 | 0 | open(my $objdump, '-|', $OBJDUMP, '-w', '-f', '-p', '-T', '-R', $file) | ||||
| 104 | or syserr(g_('cannot fork for %s'), $OBJDUMP); | ||||||
| 105 | 0 | 0 | my $ret = $self->parse_objdump_output($objdump); | ||||
| 106 | 0 | 0 | close($objdump); | ||||
| 107 | 0 | 0 | return $ret; | ||||
| 108 | } | ||||||
| 109 | |||||||
| 110 | sub parse_objdump_output { | ||||||
| 111 | 72 | 0 | 155 | my ($self, $fh) = @_; | |||
| 112 | |||||||
| 113 | 72 | 111 | my $section = 'none'; | ||||
| 114 | 72 | 13894 | while (<$fh>) { | ||||
| 115 | 39408 | 206053 | s/\s*$//; | ||||
| 116 | 39408 | 40238 | next if length == 0; | ||||
| 117 | |||||||
| 118 | 38856 | 77388 | if (/^DYNAMIC SYMBOL TABLE:/) { | ||||
| 119 | 72 | 98 | $section = 'dynsym'; | ||||
| 120 | 72 | 134 | next; | ||||
| 121 | } elsif (/^DYNAMIC RELOCATION RECORDS/) { | ||||||
| 122 | 36 | 82 | $section = 'dynreloc'; | ||||
| 123 | 36 | 78 | $_ = <$fh>; # Skip header | ||||
| 124 | 36 | 79 | next; | ||||
| 125 | } elsif (/^Dynamic Section:/) { | ||||||
| 126 | 66 | 94 | $section = 'dyninfo'; | ||||
| 127 | 66 | 206 | next; | ||||
| 128 | } elsif (/^Program Header:/) { | ||||||
| 129 | 60 | 181 | $section = 'program'; | ||||
| 130 | 60 | 141 | next; | ||||
| 131 | } elsif (/^Version definitions:/) { | ||||||
| 132 | 30 | 66 | $section = 'verdef'; | ||||
| 133 | 30 | 80 | next; | ||||
| 134 | } elsif (/^Version References:/) { | ||||||
| 135 | 60 | 84 | $section = 'verref'; | ||||
| 136 | 60 | 157 | next; | ||||
| 137 | } | ||||||
| 138 | |||||||
| 139 | 38532 | 35509 | if ($section eq 'dynsym') { | ||||
| 140 | 32130 | 30532 | $self->parse_dynamic_symbol($_); | ||||
| 141 | } elsif ($section eq 'dynreloc') { | ||||||
| 142 | 3084 | 4475 | if (/^\S+\s+(\S+)\s+(.+)$/) { | ||||
| 143 | 3084 | 6922 | $self->{dynrelocs}{$2} = $1; | ||||
| 144 | } else { | ||||||
| 145 | 0 | 0 | warning(g_("couldn't parse dynamic relocation record: %s"), $_); | ||||
| 146 | } | ||||||
| 147 | } elsif ($section eq 'dyninfo') { | ||||||
| 148 | 1470 | 4220 | if (/^\s*NEEDED\s+(\S+)/) { | ||||
| 149 | 108 108 | 112 447 | push @{$self->{NEEDED}}, $1; | ||||
| 150 | } elsif (/^\s*SONAME\s+(\S+)/) { | ||||||
| 151 | 54 | 226 | $self->{SONAME} = $1; | ||||
| 152 | } elsif (/^\s*HASH\s+(\S+)/) { | ||||||
| 153 | 24 | 68 | $self->{HASH} = $1; | ||||
| 154 | } elsif (/^\s*GNU_HASH\s+(\S+)/) { | ||||||
| 155 | 54 | 166 | $self->{GNU_HASH} = $1; | ||||
| 156 | } elsif (/^\s*RUNPATH\s+(\S+)/) { | ||||||
| 157 | # RUNPATH takes precedence over RPATH but is | ||||||
| 158 | # considered after LD_LIBRARY_PATH while RPATH | ||||||
| 159 | # is considered before (if RUNPATH is not set). | ||||||
| 160 | 0 | 0 | my $runpath = $1; | ||||
| 161 | 0 | 0 | $self->{RPATH} = [ split /:/, $runpath ]; | ||||
| 162 | } elsif (/^\s*RPATH\s+(\S+)/) { | ||||||
| 163 | 0 | 0 | my $rpath = $1; | ||||
| 164 | 0 0 | 0 0 | unless (scalar(@{$self->{RPATH}})) { | ||||
| 165 | 0 | 0 | $self->{RPATH} = [ split /:/, $rpath ]; | ||||
| 166 | } | ||||||
| 167 | } | ||||||
| 168 | } elsif ($section eq 'program') { | ||||||
| 169 | 864 | 1335 | if (/^\s*INTERP\s+/) { | ||||
| 170 | 18 | 38 | $self->{INTERP} = 1; | ||||
| 171 | } | ||||||
| 172 | } elsif ($section eq 'none') { | ||||||
| 173 | 216 | 763 | if (/^\s*.+:\s*file\s+format\s+(\S+)$/) { | ||||
| 174 | 72 | 360 | $self->{format} = $1; | ||||
| 175 | } elsif (/^architecture:\s*\S+,\s*flags\s*\S+:$/) { | ||||||
| 176 | # Parse 2 lines of "-f" | ||||||
| 177 | # architecture: i386, flags 0x00000112: | ||||||
| 178 | # EXEC_P, HAS_SYMS, D_PAGED | ||||||
| 179 | # start address 0x08049b50 | ||||||
| 180 | 72 | 127 | $_ = <$fh>; | ||||
| 181 | 72 | 117 | chomp; | ||||
| 182 | 72 | 557 | $self->{flags}{$_} = 1 foreach (split(/,\s*/)); | ||||
| 183 | } | ||||||
| 184 | } | ||||||
| 185 | } | ||||||
| 186 | # Update status of dynamic symbols given the relocations that have | ||||||
| 187 | # been parsed after the symbols... | ||||||
| 188 | 72 | 210 | $self->apply_relocations(); | ||||
| 189 | |||||||
| 190 | 72 | 248 | return $section ne 'none'; | ||||
| 191 | } | ||||||
| 192 | |||||||
| 193 | # Output format of objdump -w -T | ||||||
| 194 | # | ||||||
| 195 | # /lib/libc.so.6: file format elf32-i386 | ||||||
| 196 | # | ||||||
| 197 | # DYNAMIC SYMBOL TABLE: | ||||||
| 198 | # 00056ef0 g DF .text 000000db GLIBC_2.2 getwchar | ||||||
| 199 | # 00000000 g DO *ABS* 00000000 GCC_3.0 GCC_3.0 | ||||||
| 200 | # 00069960 w DF .text 0000001e GLIBC_2.0 bcmp | ||||||
| 201 | # 00000000 w D *UND* 00000000 _pthread_cleanup_pop_restore | ||||||
| 202 | # 0000b788 g DF .text 0000008e Base .protected xine_close | ||||||
| 203 | # 0000b788 g DF .text 0000008e .hidden IA__g_free | ||||||
| 204 | # | ||||||| | | | | | ||||||
| 205 | # | ||||||| | | Version str (.visibility) + Symbol name | ||||||
| 206 | # | ||||||| | Alignment | ||||||
| 207 | # | ||||||| Section name (or *UND* for an undefined symbol) | ||||||
| 208 | # | ||||||F=Function,f=file,O=object | ||||||
| 209 | # | |||||d=debugging,D=dynamic | ||||||
| 210 | # | ||||I=Indirect | ||||||
| 211 | # | |||W=warning | ||||||
| 212 | # | ||C=constructor | ||||||
| 213 | # | |w=weak | ||||||
| 214 | # | g=global,l=local,!=both global/local | ||||||
| 215 | # Size of the symbol | ||||||
| 216 | # | ||||||
| 217 | # GLIBC_2.2 is the version string associated to the symbol | ||||||
| 218 | # (GLIBC_2.2) is the same but the symbol is hidden, a newer version of the | ||||||
| 219 | # symbol exist | ||||||
| 220 | |||||||
| 221 | my $vis_re = qr/(\.protected|\.hidden|\.internal|0x\S+)/; | ||||||
| 222 | my $dynsym_re = qr< | ||||||
| 223 | ^ | ||||||
| 224 | [0-9a-f]+ # Symbol size | ||||||
| 225 | \ (.{7}) # Flags | ||||||
| 226 | \s+(\S+) # Section name | ||||||
| 227 | \s+[0-9a-f]+ # Alignment | ||||||
| 228 | (?:\s+(\S+))? # Version string | ||||||
| 229 | (?:\s+$vis_re)? # Visibility | ||||||
| 230 | \s+(.+) # Symbol name | ||||||
| 231 | >x; | ||||||
| 232 | |||||||
| 233 | sub parse_dynamic_symbol { | ||||||
| 234 | 32130 | 0 | 26846 | my ($self, $line) = @_; | |||
| 235 | 32130 | 91426 | if ($line =~ $dynsym_re) { | ||||
| 236 | 32130 | 53759 | my ($flags, $sect, $ver, $vis, $name) = ($1, $2, $3, $4, $5); | ||||
| 237 | |||||||
| 238 | # Special case if version is missing but extra visibility | ||||||
| 239 | # attribute replaces it in the match | ||||||
| 240 | 32130 | 90518 | if (defined($ver) and $ver =~ /^$vis_re$/) { | ||||
| 241 | 66 | 80 | $vis = $ver; | ||||
| 242 | 66 | 82 | $ver = ''; | ||||
| 243 | } | ||||||
| 244 | |||||||
| 245 | # Cleanup visibility field | ||||||
| 246 | 32130 | 28371 | $vis =~ s/^\.// if defined($vis); | ||||
| 247 | |||||||
| 248 | 32130 | 174186 | my $symbol = { | ||||
| 249 | name => $name, | ||||||
| 250 | version => $ver // '', | ||||||
| 251 | section => $sect, | ||||||
| 252 | dynamic => substr($flags, 5, 1) eq 'D', | ||||||
| 253 | debug => substr($flags, 5, 1) eq 'd', | ||||||
| 254 | type => substr($flags, 6, 1), | ||||||
| 255 | weak => substr($flags, 1, 1) eq 'w', | ||||||
| 256 | local => substr($flags, 0, 1) eq 'l', | ||||||
| 257 | global => substr($flags, 0, 1) eq 'g', | ||||||
| 258 | visibility => $vis // '', | ||||||
| 259 | hidden => '', | ||||||
| 260 | defined => $sect ne '*UND*' | ||||||
| 261 | }; | ||||||
| 262 | |||||||
| 263 | # Handle hidden symbols | ||||||
| 264 | 32130 | 66557 | if (defined($ver) and $ver =~ /^\((.*)\)$/) { | ||||
| 265 | 1584 | 2014 | $ver = $1; | ||||
| 266 | 1584 | 2044 | $symbol->{version} = $1; | ||||
| 267 | 1584 | 2026 | $symbol->{hidden} = 1; | ||||
| 268 | } | ||||||
| 269 | |||||||
| 270 | # Register symbol | ||||||
| 271 | 32130 | 29753 | $self->add_dynamic_symbol($symbol); | ||||
| 272 | } elsif ($line =~ /^[0-9a-f]+ (.{7})\s+(\S+)\s+[0-9a-f]+/) { | ||||||
| 273 | # Same start but no version and no symbol ... just ignore | ||||||
| 274 | } elsif ($line =~ /^REG_G\d+\s+/) { | ||||||
| 275 | # Ignore some s390-specific output like | ||||||
| 276 | # REG_G6 g R *UND* 0000000000000000 #scratch | ||||||
| 277 | } else { | ||||||
| 278 | 0 | 0 | warning(g_("couldn't parse dynamic symbol definition: %s"), $line); | ||||
| 279 | } | ||||||
| 280 | } | ||||||
| 281 | |||||||
| 282 | sub apply_relocations { | ||||||
| 283 | 72 | 0 | 90 | my $self = shift; | |||
| 284 | 72 72 | 81 2691 | foreach my $sym (values %{$self->{dynsyms}}) { | ||||
| 285 | # We want to mark as undefined symbols those which are currently | ||||||
| 286 | # defined but that depend on a copy relocation | ||||||
| 287 | 32130 | 41179 | next if not $sym->{defined}; | ||||
| 288 | |||||||
| 289 | 29304 | 16301 | my @relocs; | ||||
| 290 | |||||||
| 291 | # When objdump qualifies the symbol with a version it will use @ when | ||||||
| 292 | # the symbol is in an undefined section (which we discarded above, or | ||||||
| 293 | # @@ otherwise. | ||||||
| 294 | 29304 | 41987 | push @relocs, $sym->{name} . '@@' . $sym->{version} if $sym->{version}; | ||||
| 295 | |||||||
| 296 | # Symbols that are not versioned, or versioned but shown with objdump | ||||||
| 297 | # from binutils < 2.26, do not have a version appended. | ||||||
| 298 | 29304 | 21133 | push @relocs, $sym->{name}; | ||||
| 299 | |||||||
| 300 | 29304 | 18096 | foreach my $reloc (@relocs) { | ||||
| 301 | 57996 | 57509 | next if not exists $self->{dynrelocs}{$reloc}; | ||||
| 302 | 624 | 778 | next if not $self->{dynrelocs}{$reloc} =~ /^R_.*_COPY$/; | ||||
| 303 | |||||||
| 304 | 0 | 0 | $sym->{defined} = 0; | ||||
| 305 | 0 | 0 | last; | ||||
| 306 | } | ||||||
| 307 | } | ||||||
| 308 | } | ||||||
| 309 | |||||||
| 310 | sub add_dynamic_symbol { | ||||||
| 311 | 32142 | 0 | 24897 | my ($self, $symbol) = @_; | |||
| 312 | 32142 | 27186 | $symbol->{objid} = $symbol->{soname} = $self->get_id(); | ||||
| 313 | 32142 | 32235 | $symbol->{soname} =~ s{^.*/}{} unless $self->{SONAME}; | ||||
| 314 | 32142 | 27467 | if ($symbol->{version}) { | ||||
| 315 | 30522 | 111541 | $self->{dynsyms}{$symbol->{name} . '@' . $symbol->{version}} = $symbol; | ||||
| 316 | } else { | ||||||
| 317 | 1620 | 10592 | $self->{dynsyms}{$symbol->{name} . '@Base'} = $symbol; | ||||
| 318 | } | ||||||
| 319 | } | ||||||
| 320 | |||||||
| 321 | sub get_id { | ||||||
| 322 | 32142 | 0 | 20646 | my $self = shift; | |||
| 323 | 32142 | 57523 | return $self->{SONAME} || $self->{file}; | ||||
| 324 | } | ||||||
| 325 | |||||||
| 326 | sub get_symbol { | ||||||
| 327 | 114 | 0 | 200 | my ($self, $name) = @_; | |||
| 328 | 114 | 274 | if (exists $self->{dynsyms}{$name}) { | ||||
| 329 | 96 | 248 | return $self->{dynsyms}{$name}; | ||||
| 330 | } | ||||||
| 331 | 18 | 80 | if ($name !~ /@/) { | ||||
| 332 | 18 | 98 | if (exists $self->{dynsyms}{$name . '@Base'}) { | ||||
| 333 | 18 | 80 | return $self->{dynsyms}{$name . '@Base'}; | ||||
| 334 | } | ||||||
| 335 | } | ||||||
| 336 | 0 | 0 | return; | ||||
| 337 | } | ||||||
| 338 | |||||||
| 339 | sub get_exported_dynamic_symbols { | ||||||
| 340 | 105 | 0 | 168 | my $self = shift; | |||
| 341 | return grep { | ||||||
| 342 | $_->{defined} && $_->{dynamic} && !$_->{local} | ||||||
| 343 | 105 68919 105 | 159 272666 5131 | } values %{$self->{dynsyms}}; | ||||
| 344 | } | ||||||
| 345 | |||||||
| 346 | sub get_undefined_dynamic_symbols { | ||||||
| 347 | 6 | 0 | 12 | my $self = shift; | |||
| 348 | return grep { | ||||||
| 349 | (!$_->{defined}) && $_->{dynamic} | ||||||
| 350 | 6 13440 6 | 6 20836 1030 | } values %{$self->{dynsyms}}; | ||||
| 351 | } | ||||||
| 352 | |||||||
| 353 | sub get_needed_libraries { | ||||||
| 354 | 6 | 0 | 12 | my $self = shift; | |||
| 355 | 6 6 | 8 28 | return @{$self->{NEEDED}}; | ||||
| 356 | } | ||||||
| 357 | |||||||
| 358 | sub is_executable { | ||||||
| 359 | 24 | 0 | 40 | my $self = shift; | |||
| 360 | return (exists $self->{flags}{EXEC_P} && $self->{flags}{EXEC_P}) || | ||||||
| 361 | 24 | 216 | (exists $self->{INTERP} && $self->{INTERP}); | ||||
| 362 | } | ||||||
| 363 | |||||||
| 364 | sub is_public_library { | ||||||
| 365 | 24 | 0 | 30 | my $self = shift; | |||
| 366 | return exists $self->{flags}{DYNAMIC} && $self->{flags}{DYNAMIC} | ||||||
| 367 | 24 | 290 | && exists $self->{SONAME} && $self->{SONAME}; | ||||
| 368 | } | ||||||
| 369 | |||||||
| 370 - 376 | =head1 CHANGES =head2 Version 0.xx This is a private module. =cut | ||||||
| 377 | |||||||
| 378 | 1; | ||||||