Adjust memory layout for 2.6.22+ kernels with 32KB setup code
[mknbi.git] / Elf.pm
1 # Class to handle Elf images
2 # Placed under GNU Public License by Ken Yap, December 2000
3
4 package Elf;
5
6 use strict;
7 use IO::Seekable;
8
9 use constant;
10 use constant TFTPBLOCKSIZE => 512;
11 # ELF magic header in first 4 bytes
12 use constant MAGIC => "\x7FELF";
13 # This is defined by the bootrom layout
14 use constant HEADERSIZE => 512;
15 # Size of ELF header
16 use constant ELF_HDR_LEN => 52;
17 # Type code
18 use constant ELFCLASS32 => 1;
19 # Byte order
20 use constant ELFDATA2LSB => 1;
21 # ELF version
22 use constant EV_CURRENT => 1;
23 # File type
24 use constant ET_EXEC => 2;
25 # Machine type
26 use constant EM_386 => 3;
27 # Size of each program header
28 use constant PROG_HDR_LEN => 32;
29 # Type of header
30 use constant PT_LOAD => 1;
31 use constant PT_NOTE => 4;
32 # Size of each section header (there is just one)
33 use constant SECT_HDR_LEN => 40;
34 # Note types
35 use constant EIN_PROGRAM_NAME     => 0x00000001;
36 use constant EIN_PROGRAM_VERSION  => 0x00000002;
37 use constant EIN_PROGRAM_CHECKSUM => 0x00000003;
38
39 sub new {
40         my $class = shift;
41         my $self = { };
42         $self->{libdir} = shift;
43         $self->{segdescs} = [];
44         $self->{offset} = 0;    # cumulative offset from beginning of file
45         $self->{checksum} = 0;  # cumulative checksum of the file
46         $self->{summed} = 0;    # number of bytes checksummed
47         $self->{data} = "";     # string buffer containing the output file
48         bless $self, $class;
49 #       $self->_initialize();
50         return $self;
51 }
52
53 sub add_pm_header ($$$$$)
54 {
55         my ($self, $vendorinfo, $headerseg, $bootaddr, $progreturns) = @_;
56
57         push(@{$self->{segdescs}}, pack('A4C4@16v2V5v6',
58                 MAGIC, ELFCLASS32, ELFDATA2LSB, EV_CURRENT,
59                 255,            # embedded ABI
60                 ET_EXEC,        # e_type
61                 EM_386,         # e_machine
62                 EV_CURRENT,     # e_version
63                 $bootaddr,      # e_entry
64                 ELF_HDR_LEN,    # e_phoff
65                 0,              # e_shoff (come back and patch this)
66                 ($progreturns ? 0x8000000 : 0),         # e_flags
67                 ELF_HDR_LEN,    # e_ehsize
68                 PROG_HDR_LEN,   # e_phentsize
69                 0,              # e_phnum (come back and patch this)
70                 SECT_HDR_LEN,   # e_shentsize
71                 1,              # e_shnum, one mandatory entry 0
72                 0));            # e_shstrndx
73         $self->{offset} = HEADERSIZE;
74 }
75
76 sub compute_ip_checksum
77 {
78         my ($str) = @_;
79         my ($checksum, $i, $size, $shorts);
80         $checksum = 0;
81         $size = length($$str);
82         $shorts = $size >> 1;
83         # Perl has a fairly large loop overhead so a straight forward
84         # implementation of the ip checksum is intolerably slow.
85         # Instead we use the unpack checksum computation function,
86         # and sum 16bit little endian words into a 32bit number, on at
87         # most 64K of data at a time.  This ensures we do not overflow
88         # the 32bit sum allowing carry wrap around to be implemented by
89         # hand.
90         for($i = 0; $i < $shorts; $i += 32768) {
91                 $checksum += unpack("%32v32768", substr($$str, $i <<1, 65536));
92                 while($checksum > 0xffff) {
93                         $checksum = ($checksum & 0xffff) + ($checksum >> 16);
94                 }
95         }
96         if ($size & 1) {
97                 $checksum += unpack('C', substr($$str, -1, 1));
98                 while($checksum > 0xffff) {
99                         $checksum = ($checksum & 0xffff) + ($checksum >> 16);
100                 }
101         }
102         $checksum = (~$checksum) & 0xFFFF;
103         return $checksum;
104 }
105
106 sub add_summed_data
107 {
108         my ($self, $str) = @_;
109         my $new_sum = compute_ip_checksum($str);
110         my $new = $new_sum;
111         my $sum = $self->{checksum};
112         my $checksum;
113         $sum = ~$sum & 0xFFFF;
114         $new = ~$new & 0xFFFF;
115         if ($self->{summed} & 1) {
116                 $new = (($new >> 8) & 0xff) | (($new << 8) & 0xff00);
117         }
118         $checksum = $sum + $new;
119         if ($checksum > 0xFFFF) {
120                 $checksum -= 0xFFFF;
121         }
122         $self->{checksum} = (~$checksum) & 0xFFFF;
123         $self->{summed} += length($$str);
124         print "$$str";
125 #       $self->{data} .= $$str;
126 #       print STDERR sprintf("sum: %02x %02x sz: %08x summed: %08x\n",
127 #               $new_sum, $self->{checksum}, length($$str), $self->{summed});
128 }
129
130
131 # This should not get called as we don't cater for real mode calls but
132 # is here just in case
133 sub add_header ($$$$$)
134 {
135         my ($self, $vendorinfo, $headerseg, $bootseg, $bootoff) = @_;
136
137         $self->add_pm_header($vendorinfo, $headerseg, ($bootseg << 4) + $bootoff, 0);
138 }
139
140 sub roundup ($$)
141 {
142 # Round up to next multiple of $blocksize, assumes that it's a power of 2
143         my ($size, $blocksize) = @_;
144
145         # Default to TFTPBLOCKSIZE if not specified
146         $blocksize = TFTPBLOCKSIZE if (!defined($blocksize));
147         return ($size + $blocksize - 1) & ~($blocksize - 1);
148 }
149
150 # Grab N bytes from a file
151 sub peek_file ($$$$)
152 {
153         my ($self, $descriptor, $dataptr, $datalen) = @_;
154         my ($file, $fromoff, $status);
155
156         $file = $$descriptor{'file'} if exists $$descriptor{'file'};
157         $fromoff = $$descriptor{'fromoff'} if exists $$descriptor{'fromoff'};
158         return 0 if !defined($file) or !open(R, "$file");
159         binmode(R);
160         if (defined($fromoff)) {
161                 return 0 if !seek(R, $fromoff, SEEK_SET);
162         }
163         # Read up to $datalen bytes
164         $status = read(R, $$dataptr, $datalen);
165         close(R);
166         return ($status);
167 }
168
169 # Add a segment descriptor from a file or a string
170 sub add_segment ($$$)
171 {
172         my ($self, $descriptor, $vendorinfo) = @_;
173         my ($file, $string, $segment, $len, $maxlen, $fromoff, $align,
174                 $id, $end, $vilen);
175
176         $end = 0;
177         $file = $$descriptor{'file'} if exists $$descriptor{'file'};
178         $string = $$descriptor{'string'} if exists $$descriptor{'string'};
179         $segment = $$descriptor{'segment'} if exists $$descriptor{'segment'};
180         $len = $$descriptor{'len'} if exists $$descriptor{'len'};
181         $maxlen = $$descriptor{'maxlen'} if exists $$descriptor{'maxlen'};
182         $fromoff = $$descriptor{'fromoff'} if exists $$descriptor{'fromoff'};
183         $align = $$descriptor{'align'} if exists $$descriptor{'align'};
184         $id = $$descriptor{'id'} if exists $$descriptor{'id'};
185         $end = $$descriptor{'end'} if exists $$descriptor{'end'};
186         if (!defined($len)) {
187                 if (defined($string)) {
188                         $len = length($string);
189                 } else {
190                         if (defined($fromoff)) {
191                                 $len = (-s $file) - $fromoff;
192                         } else {
193                                 $len = -s $file;
194                         }
195                         return 0 if !defined($len);             # no such file
196                 }
197         }
198         if (defined($align)) {
199                 $len = &roundup($len, $align);
200         } else {
201                 $len = &roundup($len);
202         }
203         $maxlen = $len if (!defined($maxlen));
204         push(@{$self->{segdescs}}, pack('V8',
205                 PT_LOAD,
206                 $self->{offset},        # p_offset
207                 $segment << 4,          # p_vaddr
208                 $segment << 4,          # p_paddr
209                 $len,                   # p_filesz
210                 $len,                   # p_memsz == p_filesz
211                 7,                      # p_flags == rwx
212                 TFTPBLOCKSIZE));        # p_align
213         $self->{offset} += $len;
214         return ($len);                  # assumes always > 0
215 }
216
217 sub pad_with_nulls ($$$)
218 {
219         my ($self, $i, $blocksize) = @_;
220
221         $blocksize = TFTPBLOCKSIZE if (!defined($blocksize));
222         # Pad with nulls to next block boundary
223         $i %= $blocksize;
224         if ($i != 0) {
225                 # Nulls do not change the checksum
226                 print "\0" x ($blocksize - $i);
227 #               $self->{data} .= "\0" x ($blocksize - $i);
228                 $self->{summed} += ($blocksize - $i);
229         }
230 }
231
232 # Copy data from file to stdout
233 sub copy_file ($$)
234 {
235         my ($self, $descriptor) = @_;
236         my ($i, $file, $fromoff, $align, $len, $seglen, $nread, $data, $status);
237
238         $file = $$descriptor{'file'} if exists $$descriptor{'file'};
239         $fromoff = $$descriptor{'fromoff'} if exists $$descriptor{'fromoff'};
240         $align = $$descriptor{'align'} if exists $$descriptor{'align'};
241         $len = $$descriptor{'len'} if exists $$descriptor{'len'};
242         return 0 if !open(R, "$file");
243         if (defined($fromoff)) {
244                 return 0 if !seek(R, $fromoff, SEEK_SET);
245                 $len = (-s $file) - $fromoff if !defined($len);
246         } else {
247                 $len = -s $file if !defined($len);
248         }
249         binmode(R);
250         # Copy file in TFTPBLOCKSIZE chunks
251         $nread = 0;
252         while ($nread != $len) {
253                 $status = read(R, $data, TFTPBLOCKSIZE);
254                 last if (!defined($status) or $status == 0);
255                 $self->add_summed_data(\$data);
256                 $nread += $status;
257         }
258         close(R);
259         if (defined($align)) {
260                 $self->pad_with_nulls($nread, $align);
261         } else {
262                 $self->pad_with_nulls($nread);
263         }
264         return ($nread);
265 }
266
267 # Copy data from string to stdout
268 sub copy_string ($$)
269 {
270         my ($self, $descriptor) = @_;
271         my ($i, $string, $len, $align, $data);
272
273         $string = $$descriptor{'string'} if exists $$descriptor{'string'};
274         $len = $$descriptor{'len'} if exists $$descriptor{'len'};
275         $align = $$descriptor{'align'} if exists $$descriptor{'align'};
276         return 0 if !defined($string);
277         $len = length($string) if !defined($len);
278         $data = substr($string, 0, $len);
279         $self->add_summed_data(\$data);
280         defined($align) ? $self->pad_with_nulls($len, $align) : $self->pad_with_nulls($len);
281         return ($len);
282 }
283
284 sub dump_segments
285 {
286         my ($self) = @_;
287         my ($s, $nsegs, @segdescs);
288
289         # generate the note header
290         my $notes = pack('V3Z8S2',
291                 8,                      # n_namesz
292                 2,                      # n_descsz
293                 EIN_PROGRAM_CHECKSUM,   # n_type
294                 "ELFBoot",              # n_name
295                 0,                      # n_desc (Initial checksum value)
296                 0);                     # padding to a 4byte boundary
297         my $note_len = length($notes);
298
299         # Add the note header
300         push(@{$self->{segdescs}}, pack('V8',
301                 PT_NOTE,                # p_type
302                 HEADERSIZE - $note_len, # p_offset
303                 0,                      # p_vaddr
304                 0,                      # p_paddr
305                 $note_len,              # p_filesz
306                 0,                      # p_memsz == p_filesz
307                 0,                      # p_flags
308                 0));                    # p_align
309                                         
310         @segdescs = @{$self->{segdescs}};
311         $nsegs = $#segdescs;    # number of program header entries
312         # fill in e_phnum
313         substr($segdescs[0], 44, 2) = pack('v', $nsegs);
314         # fill in e_shoff to point to a record after program headers
315         substr($segdescs[0], 32, 4) = pack('V',
316                 ELF_HDR_LEN + PROG_HDR_LEN * $nsegs);
317         $self->{checksum} = 0;
318         $self->{summed} = 0;
319         while ($s = shift(@segdescs)) {
320                 $self->add_summed_data(\$s);
321         }
322         # insert section header 0
323         # we just need to account for the length, the null fill
324         # will create the record we want
325         # warn if we have overflowed allocated header area
326         print STDERR "Warning, too many segments in file\n"
327                 if ($self->{summed} > HEADERSIZE - SECT_HDR_LEN - $note_len);
328         print "\0" x (HEADERSIZE - $self->{summed});
329 #       $self->{data} .= "\0" x (HEADERSIZE - $self->{summed});
330
331         # Write the note header;
332         seek(STDOUT, HEADERSIZE - $note_len, SEEK_SET) or die "Cannot seek to note header\n";
333         print "$notes";
334 #       substr($self->{data}, HEADERSIZE - $note_len, $note_len) = $notes
335 }
336
337 sub finalise_image 
338 {
339         my ($self) = @_;
340         # Fill in the checksum
341         seek(STDOUT, HEADERSIZE - 4, SEEK_SET) or die "Cannot seek to checksum\n";
342         print pack('S', $self->{checksum});
343 #       substr($self->{data}, (HEADERSIZE - 4), 2) = pack('S', $self->{checksum});
344 #       print $self->{data};
345 }
346
347
348 1;