File Coverage

blib/lib/ELF/Writer.pm
Criterion Covered Total %
statement 144 202 71.2
branch 43 132 32.5
condition 12 43 27.9
subroutine 28 32 87.5
pod 0 14 0.0
total 227 423 53.6


line stmt bran cond sub pod time code
1             package ELF::Writer;
2 1     1   33058 use Moo 2;
  1         9538  
  1         5  
3 1     1   1019 use Carp;
  1         1  
  1         44  
4 1     1   376 use IO::File;
  1         6186  
  1         96  
5 1     1   407 use namespace::clean;
  1         6840  
  1         3  
6              
7             our $VERSION= '0.000_004';
8              
9             # ABSTRACT: Encode elf files with pure-perl
10              
11              
12             sub _init_enum {
13 7     7   18 my ($to_sym, $from_sym, @name_to_num)= @_;
14 7         40 %$from_sym= @name_to_num;
15 7         38 %$to_sym= reverse @name_to_num;
16             }
17              
18              
19             our (%class_to_sym, %class_from_sym);
20             _init_enum(\%class_to_sym, \%class_from_sym,
21             '32bit' => 1,
22             '64bit' => 2,
23             );
24              
25             has class => ( is => 'rw', coerce => sub {
26             my $x= $class_from_sym{$_[0]};
27             defined $x? $x
28             : (int($_[0]) == $_[0])? $_[0]
29             : croak "$_[0] is not a valid 'class'"
30             });
31              
32             sub class_sym {
33 2     2 0 12 my $self= shift;
34 2 50       5 $self->class($_[0]) if @_;
35 2         16 my $v= $self->class;
36 2 50       413 $class_to_sym{$v} || $v
37             }
38              
39              
40             our (%data_to_sym, %data_from_sym);
41             _init_enum(\%data_to_sym, \%data_from_sym,
42             '2LSB' => 1,
43             '2MSB' => 2,
44             );
45              
46             has data => ( is => 'rw', coerce => sub {
47             my $x= $data_from_sym{$_[0]};
48             defined $x? $x
49             : (int($_[0]) == $_[0])? $_[0]
50             : croak "$_[0] is not a valid 'data'"
51             });
52              
53             sub data_sym {
54 2     2 0 11 my $self= shift;
55 2 50       5 $self->data($_[0]) if @_;
56 2         16 my $v= $self->data;
57 2 50       392 $data_to_sym{$v} || $v
58             }
59              
60              
61             has header_version => ( is => 'rw', default => sub { 1 } );
62              
63              
64             our (%osabi_to_sym, %osabi_from_sym);
65             _init_enum(\%osabi_to_sym, \%osabi_from_sym,
66             'SystemV' => 0,
67             'HP-UX' => 1,
68             'NetBSD' => 2,
69             'Linux' => 3,
70             'Solaris' => 6,
71             'AIX' => 7,
72             'IRIX' => 8,
73             'FreeBSD' => 9,
74             'OpenBSD' => 0x0C,
75             'OpenVMS' => 0x0D,
76             );
77              
78             has osabi => ( is => 'rw', coerce => sub {
79             my $x= $osabi_from_sym{$_[0]};
80             defined $x? $x
81             : (int($_[0]) == $_[0])? $_[0]
82             : croak "$_[0] is not a valid 'osabi'"
83             });
84              
85             sub osabi_sym {
86 2     2 0 12 my $self= shift;
87 2 50       6 $self->osabi($_[0]) if @_;
88 2         15 my $v= $self->osabi;
89 2 50       408 $osabi_to_sym{$v} || $v
90             }
91              
92              
93             has osabi_version => ( is => 'rw', default => sub { 0 } );
94              
95             our (%type_to_sym, %type_from_sym);
96             _init_enum(\%type_to_sym, \%type_from_sym,
97             'none' => 0,
98             'relocatable' => 1,
99             'executable' => 2,
100             'shared' => 3,
101             'core' => 4,
102             );
103              
104             has type => ( is => 'rw', coerce => sub {
105             my $x= $type_from_sym{$_[0]};
106             defined $x? $x
107             : (int($_[0]) == $_[0])? $_[0]
108             : croak "$_[0] is not a valid 'type'"
109             });
110              
111             sub type_sym {
112 5     5 0 297 my $self= shift;
113 5 50       8 $self->type($_[0]) if @_;
114 5         60 my $v= $self->type;
115 5 100       445 $type_to_sym{$v} || $v
116             }
117              
118              
119             our (%machine_to_sym, %machine_from_sym);
120             _init_enum(\%machine_to_sym, \%machine_from_sym,
121             'SPARC' => 0x02,
122             'i386' => 0x03,
123             'Motorola68K' => 0x04,
124             'Motorola88K' => 0x05,
125             'i860' => 0x07,
126             'MIPS-RS3000' => 0x08,
127             'MIPS-RS4000' => 0xA0,
128             'PowerPC' => 0x14,
129             'ARM' => 0x28,
130             'SuperH' => 0x2A,
131             'IA-64' => 0x32,
132             'x86-64' => 0x3E,
133             'AArch64' => 0xB7,
134             );
135              
136             has machine => ( is => 'rw', coerce => sub {
137             my $x= $machine_from_sym{$_[0]};
138             defined $x? $x
139             : (int($_[0]) == $_[0])? $_[0]
140             : croak "$_[0] is not a valid 'machine'"
141             });
142              
143             sub machine_sym {
144 1     1 0 7 my $self= shift;
145 1 50       3 $self->machine($_[0]) if @_;
146 1         3 my $v= $self->machine;
147 1 50       390 $machine_to_sym{$v} || $v
148             }
149              
150              
151             has version => ( is => 'rw', default => sub { 1 } );
152              
153             has flags => ( is => 'rw', default => sub { 0 } );
154              
155             has entry_point => ( is => 'rw' );
156              
157              
158             our $Magic= "\x7fELF";
159              
160             sub elf_header_len {
161 5     5 0 138 my $class= shift->class;
162 5 50       422 return $class == 1? 52
    50          
163             : $class == 2? 64
164             : croak "Don't know structs for elf class $class";
165             }
166             our @Elf_Header_Pack= (
167             'a4 C C C C C a7 S< S< L< L< L< L< L< S< S< S< S< S< S<', # 32-bit LE
168             'a4 C C C C C a7 S> S> L> L> L> L> L> S> S> S> S> S> S>', # 32-bit BE
169             'a4 C C C C C a7 S< S< L< Q< Q< Q< L< S< S< S< S< S< S<', # 64-bit LE
170             'a4 C C C C C a7 S> S> L> Q> Q> Q> L> S> S> S> S> S> S>', # 64-bit BE
171             );
172             sub _elf_header_packstr {
173 1     1   2 my ($self, $encoding)= @_;
174 1 50       4 $encoding= $self->_encoding unless defined $encoding;
175 1         17 $Elf_Header_Pack[ $encoding ];
176             }
177              
178             sub segment_header_elem_len {
179 2     2 0 27 my $class= shift->class;
180 2 50       15 return $class == 1? 32
    50          
181             : $class == 2? 56
182             : croak "Don't know structs for elf class $class";
183             }
184             # Note! there is also a field swap between 32bit and 64bit
185             our @Segment_Header_Pack= (
186             'L< L< L< L< L< L< L< L<',
187             'L> L> L> L> L> L> L> L>',
188             'L< L< Q< Q< Q< Q< Q< Q<',
189             'L> L> Q> Q> Q> Q> Q> Q>',
190             );
191             sub _segment_header_packstr {
192 1     1   1 my ($self, $encoding)= @_;
193 1 50       3 $encoding= $self->_encoding unless defined $encoding;
194 1         14 $Segment_Header_Pack[ $encoding ];
195             }
196              
197             sub section_header_elem_len {
198 1     1 0 18 my $class= shift->class;
199 1 50       10 return $class == 1? 40
    50          
200             : $class == 2? 64
201             : croak "Don't know structs for elf class $class";
202             }
203             our @Section_Header_Pack= (
204             'L< L< L< L< L< L< L< L< L< L<',
205             'L> L> L> L> L> L> L> L> L> L>',
206             'L< L< Q< Q< Q< Q< L< L< Q< Q<',
207             'L> L> Q> Q> Q> Q> L> L> Q> Q>',
208             );
209             sub _section_header_packstr {
210 0     0   0 my ($self, $encoding)= @_;
211 0 0       0 $encoding= $self->_encoding unless defined $encoding;
212 0         0 $Section_Header_Pack[ $encoding ];
213             }
214              
215             # Returns a number 0..3 used by the various routines when packing binary data
216             sub _encoding {
217 3     3   3 my $self= shift;
218 3         44 my $endian= $self->data;
219 3         43 my $bits= $self->class;
220 3 50 33     24 defined $endian && $endian > 0 && $endian < 3 or croak "Can't encode for data=$endian";
      33        
221 3 50 33     17 defined $bits && $bits > 0 && $bits < 3 or croak "Can't encode for class=$bits";
      33        
222 3         12 return ($bits-1)*2 + ($endian-1);
223             }
224              
225              
226             has segments => ( is => 'rw', coerce => \&_coerce_segments, default => sub { [] } );
227 1     1 0 11 sub segment_count { scalar @{ shift->segments } }
  1         13  
228 1     1 0 2 sub segment_list { @{ shift->segments } }
  1         17  
229              
230              
231             has sections => ( is => 'rw', coerce => \&_coerce_sections, default => sub { [] } );
232 1     1 0 1 sub section_count { scalar @{ shift->sections } }
  1         19  
233 1     1 0 1 sub section_list { @{ shift->sections } }
  1         5  
234              
235             has section_name_string_table_idx => ( is => 'rw' );
236              
237              
238             sub serialize {
239 1     1 0 460 my $self= shift;
240            
241             # Faster than checking bit lengths on every field ourself
242 1     1   1544 use warnings FATAL => 'pack';
  1         1  
  1         720  
243            
244             # Make sure all required attributes are defined
245             defined($self->$_) || croak "Attribute $_ is not defined"
246 1   50     17 for qw( class data osabi type machine header_version osabi_version version entry_point );
247            
248             # Clone the segments and sections so that our changes don't affect the
249             # configuration the user built.
250 1         1185 my @segments= map { $_->clone } $self->segment_list;
  1         7  
251 1         5 my @sections= map { $_->clone } $self->section_list;
  0         0  
252 1         358 my $segment_table;
253             my $section_table;
254            
255             # Now apply defaults and set numbering for diagostics of errors
256 1         2 my $i= 0;
257 1         3 for (@segments) {
258 1         3 $_->_index($i++);
259 1         4 $self->_apply_segment_defaults($_);
260            
261             # There can be one segment which loads the segment table itself
262             # into the program's address space. If used, we track the pointer
263             # to that segment. We also clear it's 'data' and set it's 'size'
264             # to keep from confusing the code below.
265 1 50       14 if ($_->type == 6) {
266 0 0       0 croak "There can be only one segment of type 'phdr'"
267             if defined $segment_table;
268 0         0 $segment_table= $_;
269 0         0 $segment_table->data(undef);
270 0         0 $segment_table->size($self->segment_header_len * @segments);
271             }
272             }
273 1         7 $i= 0;
274 1         2 for (@sections) {
275 0         0 $_->_index($i++);
276 0         0 $self->_apply_section_defaults($_);
277             }
278            
279             # Build a list of every defined range of data in the file,
280             # and a list of every segment/section which needs automatically placed.
281 1         1 my @defined_ranges;
282             my @auto_offset;
283 1         2 for (@segments, @sections) {
284             # size is guaranteed to be defined by "_apply...defaults()"
285             # Data might not be defined if the user just wanted to point the
286             # segment at something, and offset might not be defined if the user
287             # just wants it appended wherever.
288 1 50       3 if (!defined $_->offset) {
289 0         0 push @auto_offset, $_;
290             }
291             else {
292 1 50       10 $_->offset >= 0 or croak $_->_name." offset cannot be negative";
293 1 50 33     8 push @defined_ranges, $_
294             if defined $_->data && length $_->data;
295             }
296             }
297            
298 1 50       5 if (@sections) {
299             # First section must always be the NULL section. If the user forgot this
300             # then their indicies might be off.
301 0 0       0 $sections[0]->type == 0
302             or croak "Section 0 must be type NULL";
303             # Sections may not overlap, regardless of whether the user attached data to them
304 0         0 my $prev_end= 0;
305 0         0 my $prev;
306 0         0 for (sort { $a->offset <=> $b->offset } $self->section_list) {
  0         0  
307 0 0       0 croak 'Section overlap between '.$_->_name.' and '.$prev->_name
308             if $_->offset < $prev_end;
309 0         0 $prev_end= $_->offset + $_->size;
310             }
311             }
312            
313             # Each segment and section can define data to be written to the file,
314             # but segments can overlap sections. Make sure their defined data doesn't
315             # conflict, or we wouldn't know which to write.
316 1         2 my $prev;
317 1         4 my $prev_end= $self->elf_header_len;
318 1         2 my $first_data;
319 1         2 @defined_ranges= sort { $a->data_offset <=> $b->data_offset } @defined_ranges;
  0         0  
320 1         2 for (@defined_ranges) {
321 1 0       4 croak 'Data overlap between '.$_->_name.' and '.($prev? $prev->_name : 'ELF header')
    50          
322             if $_->data_offset < $prev_end;
323 1         1 $prev= $_;
324 1         2 $prev_end= $_->data_offset + $_->size;
325             }
326            
327             # For each segment or section that needs an offset assigned, append to
328             # end of file.
329 1         1 for (@auto_offset) {
330 0         0 my $align= $_->_required_file_alignment;
331 0         0 $prev_end= int(($prev_end + $align - 1) / $align) * $align;
332 0         0 $_->offset($prev_end);
333 0 0 0     0 push @defined_ranges, $_ if defined $_->data && length $_->data;
334 0         0 $prev_end += $_->size;
335             }
336            
337             # Now, every segment and section have an offset and a length.
338             # We can now encode the tables.
339 1         2 my @insert;
340 1 50       2 if (@segments) {
341 1         1 my $segment_table_data= '';
342             $segment_table_data .= $self->_serialize_segment_header($_)
343 1         6 for @segments;
344             # The user might have defined this segment on their own.
345             # Otherwise we just create a dummy to use below.
346 1 50       27 if (!defined $segment_table) {
347 1         19 $segment_table= ELF::Writer::Segment->new(
348             align => 8,
349             filesize => length($segment_table_data),
350             data => $segment_table_data,
351             );
352 1         4 push @insert, $segment_table;
353             } else {
354 0         0 $segment_table->data($segment_table_data);
355             }
356             }
357 1 50       4 if (@sections) {
358 0         0 my $section_table_data= '';
359             $section_table_data .= $self->_serialize_section_header($_)
360 0         0 for @sections;
361            
362 0         0 $section_table= ELF::Writer::Segment->new(
363             align => 8,
364             filesize => length($section_table_data),
365             data => $section_table_data,
366             );
367 0         0 push @insert, $section_table;
368             }
369            
370             # Find a spot for the segment and/or section tables.
371             # Due to alignment, there is probably room to squeeze the table(s) inbetween
372             # other defined ranges. Else, put them at the end.
373 1         2 $prev_end= $self->elf_header_len;
374 1   66     6 for (my $i= 0; @insert and $i <= @defined_ranges; ++$i) {
375 1         4 my $align= $insert[0]->_required_file_alignment;
376 1         4 $prev_end= int(($prev_end + $align-1) / $align) * $align;
377 1 50 33     6 if ($i == @defined_ranges
378             or $prev_end + $insert[0]->size <= $defined_ranges[$i]->data_offset
379             ) {
380 1         6 $insert[0]->offset($prev_end);
381 1         4 splice @defined_ranges, $i, 0, shift @insert;
382             }
383             }
384            
385             # Now, we can finally encode the ELF header.
386 1 50 50     7 my $header= pack($self->_elf_header_packstr,
    50          
387             $Magic, $self->class, $self->data, $self->header_version,
388             $self->osabi, $self->osabi_version, '',
389             $self->type, $self->machine, $self->version, $self->entry_point,
390             ($segment_table? $segment_table->offset : 0),
391             ($section_table? $section_table->offset : 0),
392             $self->flags, $self->elf_header_len,
393             $self->segment_header_elem_len, $self->segment_count,
394             $self->section_header_elem_len, $self->section_count,
395             $self->section_name_string_table_idx || 0,
396             );
397             # sanity check
398 1 50       21 length($header) == $self->elf_header_len
399             or croak "Elf header len mismatch";
400            
401             # Write out the header and each range of defined bytes, padded with NULs as needed.
402 1         2 my $data= $header;
403 1         2 for (@defined_ranges) {
404 2         5 my $pad= $_->data_offset - length($data);
405 2 50       4 $data .= "\0" x $pad if $pad;
406 2         7 $data .= $_->data;
407             }
408 1         6 return $data;
409             }
410              
411             sub _serialize_segment_header {
412 1     1   1 my ($self, $seg)= @_;
413            
414             # Faster than checking bit lengths on every field ourself
415 1     1   4 use warnings FATAL => 'pack';
  1         1  
  1         185  
416            
417             # Make sure all required attributes are defined
418             defined $seg->$_ or croak "Attribute $_ is not defined"
419 1   50     17 for qw( type offset virt_addr align );
420            
421 1         20 my $filesize= $seg->filesize;
422 1 50       2 $filesize= length($seg->data) + $seg->data_offset
423             unless defined $filesize;
424            
425 1         3 my $align= $seg->align;
426 1         2 my $memsize= $seg->memsize;
427 1 50       5 $memsize= int(($filesize + $align - 1) / $align) * $align
428             unless defined $memsize;
429            
430             # 'flags' moves depending on 32 vs 64 bit, so changing the pack string isn't enough
431 1 50 0     4 return $self->_encoding < 2?
      50        
432             pack($self->_segment_header_packstr,
433             $seg->type, $seg->offset, $seg->virt_addr, $seg->phys_addr || 0,
434             $filesize, $memsize, $seg->flags, $seg->align
435             )
436             : pack($self->_segment_header_packstr,
437             $seg->type, $seg->flags, $seg->offset, $seg->virt_addr,
438             $seg->phys_addr || 0, $filesize, $memsize, $seg->align
439             );
440             }
441              
442             sub _serialize_section_header {
443 0     0   0 my ($self, $sec)= @_;
444            
445             # Make sure all required attributes are defined
446             defined $sec->$_ or croak "Attribute $_ is not defined"
447 0   0     0 for qw( type name flags addr offset size link info addralign entsize );
448            
449             # Faster than checking bit lengths on every field ourself
450 1     1   4 use warnings FATAL => 'pack';
  1         1  
  1         468  
451            
452 0         0 return pack($self->_section_header_packstr,
453             $sec->name, $sec->type, $sec->flags, $sec->addr, $sec->offset,
454             $sec->size, $sec->link, $sec->info, $sec->align, $sec->entry_size
455             );
456             }
457              
458              
459             sub write_file {
460 0     0 0 0 my ($self, $filename, $mode)= @_;
461 0 0       0 $mode= 0755 unless defined $mode;
462 0         0 require File::Temp;
463 0         0 my ($fh, $tmpname)= File::Temp::tempfile( $filename.'-XXXXXX' );
464 0 0       0 print $fh $self->serialize or croak "write: $!";
465 0 0       0 close $fh or croak "close: $!";
466 0 0       0 chmod($mode, $tmpname) or croak "chmod: $!";
467 0 0       0 rename($tmpname, $filename) or croak "rename: $!";
468             }
469              
470             # coerce arrayref of hashrefs into arrayref of objects
471             sub _coerce_segments {
472 13     13   17 my $spec= shift;
473 13         149 return [ map { (__PACKAGE__.'::Segment')->coerce($_) } @$spec ];
  1         4  
474             }
475              
476             # coerce arrayref of hashrefs into arrayref of objects
477             sub _coerce_sections {
478 13     13   11 my $spec= shift;
479 13         158 return [ map { (__PACKAGE__.'::Section')->coerce($_) } @$spec ];
  0         0  
480             }
481              
482             # Overridden by subclasses for machine-specific defaults
483             sub _apply_section_defaults {
484 0     0   0 my ($self, $sec)= @_;
485             # Undef type is "null" type 0
486 0         0 my $type= $sec->type;
487 0 0       0 defined $type
488             or $sec->type($type= 0);
489 0         0 my $offset= $sec->offset;
490 0         0 my $size= $sec->size;
491            
492 0 0       0 if ($type == 0) { # 'null'
    0          
493             # Ensure length and offset are zero
494 0 0       0 $size= $sec->size(0) unless defined $size;
495 0 0       0 $offset= $sec->offset(0) unless defined $offset;
496 0 0 0     0 croak "null section should have offset=0 and size=0"
497             if $offset || $size;
498             }
499             elsif ($type == 8) { # 'nobits'
500             # Offset can be set but ensure size is zero
501 0 0       0 $size= $sec->size(0) unless defined $size;
502 0 0       0 croak "nobits section should have size=0"
503             if $size;
504            
505             }
506             else {
507             # 'size' is required, but can be computed from 'data' and 'data_offset'.
508 0 0       0 if (!defined $size) {
509 0 0       0 defined $sec->data or croak "Section must define 'size' or 'data'";
510 0         0 $sec->size($sec->data_start + length($sec->data));
511             }
512             }
513             }
514              
515             # Overridden by subclasses for machine-specific defaults
516             sub _apply_segment_defaults {
517 1     1   2 my ($self, $seg)= @_;
518             # Undef type is "null" type 0
519 1         15 my $type= $seg->type;
520 1 50       7 defined $type
521             or $seg->type($type= 0);
522 1         8 my $offset= $seg->offset;
523 1         2 my $filesize= $seg->filesize;
524            
525 1 50       3 if ($type == 0) { # 'null'
526             # Ensure length and offset are zero
527 0 0       0 $filesize= $seg->filesize(0) unless defined $filesize;
528 0 0       0 $offset= $seg->offset(0) unless defined $offset;
529 0 0 0     0 croak "null segment should have offset=0 and filesize=0"
530             if $offset || $filesize;
531             }
532             else {
533             # 'filesize' is required, but can be computed from 'data' and 'data_offset'
534 1 50       2 if (!defined $filesize) {
535 1 50       3 defined $seg->data or croak "Segment must define 'filesize' or 'data'";
536 1         5 $filesize= $seg->filesize($seg->data_start + length($seg->data));
537             }
538             # Default memsize to filesize
539 1 50       5 $seg->memsize($filesize) unless defined $seg->memsize;
540             }
541             }
542              
543             # Load last so make sure data is initialized
544             require ELF::Writer::Segment;
545             require ELF::Writer::Section;
546              
547             1;
548              
549             __END__