-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathshrinkelf.py
executable file
·1918 lines (1803 loc) · 95.5 KB
/
shrinkelf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python3
#
# Copyright 2019-2021, Fabian Lang <[email protected]>
# Copyright 2021, Andreas Ziegler <[email protected]>
#
# This file is part of shrinkelf.
#
# shrinkelf is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# shrinkelf 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 shrinkelf. If not, see <http://www.gnu.org/licenses/>.
import argparse
import bisect
import copy
import logging
import os
import time
from typing import Optional, Tuple, Dict
import gurobipy as gp
from gurobipy import GRB
from elfdefinitions import *
from util import *
# file suffix for output file appended to the input file when no output file was specified
FILESUFFIX: str = ".shrinked"
# command line option for permuting fragments using brute force
PERMUTE_WITH_BRUTE_FORCE: str = "brute-force"
# command line option for permuting fragments using gurobi
PERMUTE_WITH_GUROBI: str = "gurobi"
# command line option for permuting fragments using z3
PERMUTE_WITH_Z3: str = "z3"
class CleanUp(Exception):
""" Exception used to manage control flow and clean up open file and ELF descriptors. """
def __init__(self, level, exitstatus):
""" Initialize self.
:param level: indicates which file and ELF descriptors are open
:param exitstatus: exit status to exit the program
"""
super().__init__()
self.level = level
self.exitstatus = exitstatus
# global CleanUp object used for managing control flow
cu = CleanUp(0, 0)
class Done(Exception):
""" Exception used for managing control flow. """
pass
class EndOfSegmentException(Exception):
pass
def insertTuple(item_03: Tuple[int, int], list_of_items: List[Tuple[int, int]]) -> Optional[Tuple[int, int]]:
"""
Insert element `item` in sorted list of ranges and return `None` on success. On failure return the range `item`
overlaps with and do NOT insert `item` in list.
"""
length_01 = len(list_of_items)
if length_01 == 0:
# list is empty
list_of_items.append(item_03)
return None
else:
idx = bisect.bisect(list_of_items, item_03)
if idx != length_01:
next_item = list_of_items[idx + 1]
if item_03[1] < next_item[0]:
# item must be sorted in before current element
list_of_items.insert(idx, item_03)
if idx > 0:
prev_item = list_of_items[idx - 1]
if item_03[0] <= prev_item[1]:
# item overlaps with current element
return prev_item
# for i in range(0, length_01):
# current_item = list_of_items[i]
# if item_03[1] < current_item[0]:
# # item must be sorted in before current element
# list_of_items.insert(i, item_03)
# return None
# elif item_03[0] <= current_item[1]:
# # item overlaps with current element
# return current_item
list_of_items.append(item_03)
return None
def insertRange(item_04: FileFragment, list_of_items: List[FileFragment]) -> Optional[FileFragment]:
"""
Insert element `item` in sorted list of ranges and return `None` on success. On failure return the range `item`
overlaps with and do NOT insert `item` in list.
"""
length_02 = len(list_of_items)
if length_02 == 0:
# list is empty
list_of_items.append(item_04)
return None
else:
idx = bisect.bisect(list_of_items, item_04)
if idx != length_02:
next_item = list_of_items[idx + 1]
if item_04.end < next_item.start:
# item must be sorted in before next element
list_of_items.insert(idx, item_04)
return None
if idx > 0:
prev_item = list_of_items[idx - 1]
if item_04.start <= prev_item.end:
# item overlaps with previous element
return prev_item
#for i in range(0, length_02):
# current_item = list_of_items[i]
# if item_04.end < current_item.start:
# # item must be sorted in before current element
# list_of_items.insert(i, item_04)
# return None
# elif item_04.start <= current_item.end:
# # item overlaps with current element
# return current_item
list_of_items.append(item_04)
return None
def countLOADs(elf) -> int:
""" Count LOAD program headers in an ELF file.
:param elf: the file
:return: the number of LOAD program headers
"""
count = 0
# number of segments in file
phdrnum = c_size_t(0)
if libelf.elf_getphdrnum(elf, byref(phdrnum)) != 0:
logging.error("Could not retrieve number of segments from source file: %s",
libelf.elf_errmsg(-1).decode())
raise cu
phdr = GElf_Phdr()
for i in range(0, phdrnum.value):
if not libelf.gelf_getphdr(elf, i, byref(phdr)):
logging.error("Could not retrieve source phdr structure %d: %s",
i, libelf.elf_errmsg(-1).decode())
raise cu
if phdr.p_type == PT_LOAD.value:
count += 1
return count
def segments(section: List[FileFragment], section_start: int) -> Optional[List[FragmentRange]]:
""" Construct the FragmentRanges of a section.
:param section: list of FileFragments of a section
:param section_start: start address of this section in the new file
:return: a list of FragmentRanges fit for rearrangement or `None` in case of an error
"""
if section is None or len(section) == 0:
return None
ret: List[FragmentRange] = []
current = FragmentRange(offset=section[0].section_offset + section[0].start, vaddr=section[0].memory_info.start,
fsize=section[0].end - section[0].start, loadable=section[0].memory_info.loadable,
msize=section[0].memory_info.end - section[0].memory_info.start,
flags=section[0].memory_info.flags, section_start=section_start)
for i in range(1, len(section)):
if ((current.vaddr + current.msize) // PAGESIZE) == (section[i].memory_info.start // PAGESIZE):
# data of indexed range will be loaded in the same page as content of current range => merge the ranges
current.fsize = section[i].section_offset + section[i].end - current.offset
current.msize = section[i].memory_info.end - current.vaddr
current.loadable |= section[i].memory_info.loadable
current.flags |= section[i].memory_info.flags
else:
# data of indexed range will not be loaded in the same page as content of current range => create new range
ret.append(current)
current = FragmentRange(offset=section[i].section_offset + section[i].start, section_start=section_start,
fsize=section[i].end - section[i].start, vaddr=section[i].memory_info.start,
msize=section[i].memory_info.end - section[i].memory_info.start,
flags=section[i].memory_info.flags, loadable=section[i].memory_info.loadable)
ret.append(current)
return ret
def countLoadableSegmentRanges(segment_list: List[FragmentRange]):
""" Count the loadable FragmentRanges in a list.
:param segment_list: the list
:return: number of loadable FragmentRanges
"""
ret = 0
for item_05 in segment_list:
if item_05.loadable:
ret += 1
return ret
# todo: move to Permutation
def createPermutation(segments_01: List[List[FragmentRange]], index: int, current_size: int) -> Permutation:
""" Constructor for Permutation.
:param segments_01: List of lists of FragmentRanges imposing constraints on the permutation
:param index: the index for which section a Permutation is constructed
:param current_size: currently occupied size in the new file
:return: the new Permutation
"""
ret: Permutation = Permutation(num_entries=len(segments_01[index]))
ret.tmp = [0] * ret.num_entries
ret.result = [0] * ret.num_entries
if index == 1:
fix_first = (segments_01[index][0].offset // PAGESIZE == 0)
else:
fix_first = segments_01[index-1][-1].fsize > 0 and (segments_01[index-1][-1].vaddr + segments_01[index-1][-1].msize) // PAGESIZE == segments_01[index][0].vaddr // PAGESIZE
if fix_first:
# mark first element because it is on the same page as the previous section
ret.tmp[0] = -1
ret.result[0] = -1
if index != len(segments_01) - 1:
last = segments_01[index][-1]
ahead = segments_01[index + 1][0]
if (last.offset + last.fsize) // PAGESIZE == ahead.offset // PAGESIZE:
# mark last element because its on the same page as the next section
ret.tmp[-1] = -1
ret.result[-1] = -1
# mark size of the section under the currently best permutation as not determined yet
ret.size = -1
return ret
def calculateOffset(prior_offset: int, occupied_space: int) -> int:
""" Calculate offset of a fragment in new file.
Constraint: new offset needs to be equal to prior offset modulo page size because LOAD segments require that
`p_offset` (offset in file) is equal to `p_vaddr` (address in virtual address space) modulo page size.
:param prior_offset: offset of fragment in original file
:param occupied_space: number of already occupied bytes in new file
:return: offset in new file
"""
prior_page_offset = prior_offset % PAGESIZE
occupied_page_offset = occupied_space % PAGESIZE
if occupied_page_offset <= prior_page_offset:
return occupied_space - occupied_page_offset + prior_page_offset
else:
return occupied_space - occupied_page_offset + prior_page_offset + PAGESIZE
def evaluate(perm: Permutation, segments_02: List[FragmentRange]):
""" Evaluate the current permutation of FragmentRanges.
Compute the size of the section if the FragmentRanges it contains are inserted in the ordering described by the
current permutation. Update the currently best permutation and its resulting size if needed.
:param perm: the state of the permutation algorithm containing the current and the best permutation
:param segments_02: the FragmentRanges to insert
"""
start_01 = 0
end_01 = 0
offset_in_section = 0
# look up for every position (ranges from 1 to the number of segments) which segment to insert
for i in range(1, perm.num_entries + 1):
if i == 1 and perm.tmp[0] == -1:
# first position and the (in the input file) first segment is marked to be inserted first
start_01 = segments_02[0].offset
end_01 = segments_02[0].offset + segments_02[0].fsize
continue
elif i == perm.num_entries and perm.tmp[-1] == -1:
# last position and the (in the input file) last segment is marked to be inserted last
tmp_07 = segments_02[-1]
end_01 = calculateOffset(tmp_07.offset, end_01) + tmp_07.fsize
break
else:
# search the segment with the index for the current position
for j in range(0, perm.num_entries):
if i == perm.tmp[j]:
tmp_08 = segments_02[j]
if i == 1:
start_01 = tmp_08.offset
# if the chosen first fragment starts at an offset
# relative to the original section start, factor this
# into the size calculation
offset_in_section = calculateOffset(tmp_08.offset, tmp_08.section_start) - tmp_08.section_start
end_01 = tmp_08.offset
end_01 = calculateOffset(tmp_08.offset, end_01) + tmp_08.fsize
size = end_01 - start_01 + offset_in_section
if size < perm.size or perm.size == -1:
# update currently best permutation if current permutation is better
for i in range(0, perm.num_entries):
perm.result[i] = perm.tmp[i]
perm.size = size
def recursive_permute(perm: Permutation, segments_04: List[FragmentRange], index: int):
""" recursive backtracking algorithm for permutation of FragmentRanges
:param perm: the state of the algorithm
:param segments_04: the address ranges to permute
:param index: the current position where a address range is inserted (doubles as depth of recursion)
"""
if index > perm.num_entries:
# all address ranges are inserted
evaluate(perm, segments_04)
return
if index == 1 and perm.tmp[0] == -1:
# first address range is constrained by the first element of segments
recursive_permute(perm, segments_04, index + 1)
elif index == perm.num_entries and perm.tmp[-1] == -1:
# last address range is constrained by the last element of segments
recursive_permute(perm, segments_04, index + 1)
else:
for i in range(0, perm.num_entries):
# check if range is not inserted yet
if perm.tmp[i] == 0:
# insert range temporary
perm.tmp[i] = index
# try every possible permutation with the remaining ranges
recursive_permute(perm, segments_04, index + 1)
# remove range to try the next for this position
perm.tmp[i] = 0
def segmentOffsets(perm: Permutation, segments_07: List[FragmentRange], current_size: int):
""" Compute the offset of the FragmentRanges in the output file.
:param perm: the order in which the ranges are inserted
:param segments_07: the ranges that are inserted
:param current_size: the already occupied size in the output file
"""
section_start = 0
for i in range(1, perm.num_entries + 1):
if i == 1 and perm.result[0] == -1:
# the first element of segments is constrained to the first position
section_start = calculateOffset(segments_07[0].offset, current_size)
segments_07[0].shift = section_start - segments_07[0].offset
segments_07[0].section_start = section_start
current_size = section_start + segments_07[0].fsize
elif i == perm.num_entries and perm.result[-1] == -1:
# the last element of segments is constrained to the last position
tmp_11 = segments_07[-1]
tmp_11.shift = calculateOffset(tmp_11.offset, current_size) - tmp_11.offset
tmp_11.section_start = section_start
else:
# search the element with the matching index for the current position
for j in range(0, perm.num_entries):
if i == perm.result[j]:
tmp_12 = segments_07[j]
if i == 1:
section_start = calculateOffset(tmp_12.offset, current_size)
tmp_12.shift = calculateOffset(tmp_12.offset, current_size) - tmp_12.offset
tmp_12.section_start = section_start
current_size = calculateOffset(tmp_12.offset, current_size) + tmp_12.fsize
def permute(segments_08: List[List[FragmentRange]], current_size: int, section_aligns) -> int:
""" Permute the address ranges for all sections.
:param segments_08: the sections' contents
:type segments_08: list of lists of FragmentRanges
:param current_size: the currently occupied space in the output file
:return: the size of the output file after inserting all sections
"""
for i in range(1, len(segments_08)):
current_size = roundUp(current_size, section_aligns[i])
perm = createPermutation(segments_08, i, current_size)
# permute the address ranges of section i
recursive_permute(perm, segments_08[i], 1)
# calculate the offsets of the address ranges of section i
segmentOffsets(perm, segments_08[i], current_size)
# update current size
current_size = segments_08[i][0].section_start + perm.size
return current_size
def roundUp(value: int, base: int) -> int:
""" Round up `value` to the next multiple of `base`.
Compute a value `x` such that `x % base == 0`, `x >= value` and `x` is minimal.
:param value: the value for which the next bigger multiple is computed
:param base: the value of which the multiple is computed
:return: the multiple of base
"""
tmp_13 = value % base
if tmp_13 != 0:
return value - tmp_13 + base
else:
return value
def contains(segment: FragmentRange, datarange: FileFragment) -> bool:
""" Check if a FragmentRange contains a section fragment.
:param segment: the FragmentRange
:param datarange: the section fragment
:return: bool indicating if the FragmentRange contains the section fragment
"""
if datarange.section_offset + datarange.end <= segment.offset + segment.fsize:
if datarange.section_offset + datarange.start >= segment.offset:
return True
return False
def containsSegment(outer: FragmentRange, inner: FragmentRange) -> bool:
""" Check if a FragmentRange contains another.
:param outer: the containing FragmentRange
:param inner: the contained FragmentRange
:return: bool indicating if the FragmentRange contains the section fragment
"""
if inner.offset + inner.fsize <= outer.offset + outer.fsize:
if inner.offset >= outer.offset:
return True
return False
def calculateShift(ranges_07: List[List[FileFragment]], segments_17: List[List[FragmentRange]]):
""" Compute the section and fragment shift of all FileFragments.
:param ranges_07: the FileFragments
:param segments_17: the FragmentRanges defining the shift
"""
for i in range(1, len(ranges_07)):
last_idx = 0
for tmpSec in ranges_07[i]:
for idx_2, tmp_21 in enumerate(segments_17[i][last_idx:], last_idx):
if contains(tmp_21, tmpSec):
if idx_2 > last_idx:
last_idx += 1
tmpSec.section_shift = tmp_21.section_start - tmpSec.section_offset
tmpSec.fragment_shift = tmp_21.shift - tmpSec.section_shift
break
# FIXME: Doku
def calculatePHDRInfo(fileoffset: int, memoryoffset: int, elfclass: c_int, add_ehdr: bool) -> Tuple[int, int, int]:
if elfclass == ELFCLASS32:
realfileoffset = fileoffset if not add_ehdr else fileoffset + SIZEOF_ELF32_EHDR
realmemoryoffset = memoryoffset if not add_ehdr else memoryoffset + SIZEOF_ELF32_EHDR
phdr_start = roundUp(realfileoffset, PHDR32ALIGN)
phdr_vaddr = roundUp(realmemoryoffset, PHDR32ALIGN)
entry_size = SIZEOF_ELF32_PHDR
else:
realfileoffset = fileoffset if not add_ehdr else fileoffset + SIZEOF_ELF64_EHDR
realmemoryoffset = memoryoffset if not add_ehdr else memoryoffset + SIZEOF_ELF64_EHDR
phdr_start = roundUp(realfileoffset, PHDR64ALIGN)
phdr_vaddr = roundUp(realmemoryoffset, PHDR64ALIGN)
entry_size = SIZEOF_ELF64_PHDR
return phdr_start, phdr_vaddr, entry_size
# Fixme: Doku
# Given a tuplelist of edges, find the shortest subtour
def subtour(start, edges, size):
cycle = []
current_index = start[1]
for i in range(size):
neighbors = edges.select(current_index, '*')
current = neighbors[0]
cycle.append(current)
if current[1] == start[0]:
break
current_index = current[1]
return cycle
# Fixme: Doku
# Callback - use lazy constraints to eliminate sub-tours
# noinspection PyProtectedMember
def subtourelim(model, where):
if where == GRB.Callback.MIPSOL:
# make a list of edges selected in the solution
xvals = model.cbGetSolution(model._xvars)
selected = gp.tuplelist((i, j) for i, j in model._xvars.keys() if xvals[i, j] > 0.5)
yvals = model.cbGetSolution(model._yvars)
ring = gp.tuplelist((i, j) for i, j in model._yvars.keys() if yvals[i, j] > 0.5)
# find the shortest cycle in the selected edge list
tour: List[Tuple[int, int]] = subtour(ring[0], selected, model._size)
if len(tour) < model._size - 1:
# add subtour elimination constr. for every pair of cities in subtour
if len(tour) == 1:
model.cbLazy(model._xvars[tour[0][0], tour[0][1]] + model._yvars[ring[0][0], ring[0][1]] == 1)
else:
model.cbLazy(gp.quicksum(model._xvars[i, j] for i, j in tour) + model._yvars[ring[0][0], ring[0][1]] <= len(tour))
# Fixme: Doku
def solve_lp_instance(segments_37: List[FragmentRange], current_size, index, fix_first, fix_last, file, log):
size = len(segments_37)
if size == 1:
# Fixme: Doku
# simply push the address ranges together
segments_37[0].section_start = calculateOffset(segments_37[0].offset, current_size)
segments_37[0].shift = segments_37[0].section_start - segments_37[0].offset
return segments_37[0].section_start + segments_37[0].fsize
# xxx: mehr als das erste bzw. letzte Fragment fixieren, für kleine Fragmente
# xxx: funktioniert momentan, da es FragmentRanges geht, die die ganze Page abdecken
elif size == 2 and (fix_first or fix_last):
section_start = calculateOffset(segments_37[0].offset, current_size)
for tmp_111 in segments_37:
tmp_111.shift = calculateOffset(tmp_111.offset, current_size) - tmp_111.offset
tmp_111.section_start = section_start
current_size = calculateOffset(tmp_111.offset, current_size) + tmp_111.fsize
return current_size
# xxx: mehr als das erste bzw. letzte Fragment fixieren, für kleine Fragmente
# xxx: funktioniert momentan, da es FragmentRanges geht, die die ganze Page abdecken
elif size == 3 and fix_first and fix_last:
section_start = calculateOffset(segments_37[0].offset, current_size)
for tmp_111 in segments_37:
tmp_111.shift = calculateOffset(tmp_111.offset, current_size) - tmp_111.offset
tmp_111.section_start = section_start
current_size = calculateOffset(tmp_111.offset, current_size) + tmp_111.fsize
return current_size
else:
logging.debug('creating ILP instance for section %d: %d fragments, fix_first: %s, fix_last: %s',
index, size, fix_first, fix_last)
s: Dict[Tuple[int, int], int] = {}
d: Dict[Tuple[int, int], int] = {}
for i in range(size):
a = segments_37[i]
a_start = calculateOffset(a.offset, current_size) - current_size + a.fsize
for j in range(size):
if i == j:
continue
s[(j, i)] = a_start
b = segments_37[j]
b_add = ((b.offset % PAGESIZE) - (a.end_in_file() % PAGESIZE) + PAGESIZE) % PAGESIZE + b.fsize
d[(i, j)] = b_add
try:
env = gp.Env(empty=True)
env.setParam('OutputFlag', 0)
env.start()
m: gp.Model = gp.Model("section-{0}".format(index), env=env)
m.Params.LogToConsole = 0
if log:
m.Params.LogFile = "{0}.section_{1}.log".format(file, index)
# fragment pairs for determining their order
x = m.addVars(d.keys(), obj=d, name="x", vtype=GRB.BINARY)
# fragment pairs to choose the last/first one
y = m.addVars(s.keys(), obj=s, name="y", vtype=GRB.BINARY)
m.setAttr("ModelSense", GRB.MINIMIZE)
# number of connections between fragments. It is size-1 because every fragment has a successor except the
# last one
m.addLConstr(gp.quicksum(x), rhs=size-1, sense=GRB.EQUAL, name="frag")
# there is exactly one last/first fragment pair
m.addLConstr(gp.quicksum(y), rhs=1, sense=GRB.EQUAL, name="ring")
# there is exactly one 'connection' leaving a fragment: either to its successor or it is the last fragment
# and so it 'connects' to the first
m.addConstrs(((x.sum(j, '*') + y.sum(j, '*')) == 1 for j in range(size)), "out")
# there is exactly one incoming 'connection' to a fragment: either from its predecessor or it is the first
# fragment and so it is 'connected' to the last
m.addConstrs(((x.sum('*', j) + y.sum('*', j)) == 1 for j in range(size)), "in")
# xxx: mehr als das erste bzw. letzte Fragment fixieren, für kleine Fragmente
# xxx: funktioniert momentan, da es FragmentRanges geht, die die ganze Page abdecken
if fix_first:
# keep first fragment of the section in its place because it will be loaded in the same page as the end of
# the previous section
m.addLConstr(y.sum('*', 0) == 1, "fix_first_fragment")
if fix_last:
# keep last fragment of the section in its place because it will be loaded in the same page as the start of
# the next section
m.addLConstr(y.sum(size - 1, '*') == 1, "fix_last_fragment")
m._xvars = x
m._yvars = y
m._size = size
m.Params.lazyConstraints = 1
before = time.time()
m.optimize(subtourelim)
# m.optimize()
if m.status != GRB.OPTIMAL:
logging.error("Unable to solve ILP instance for section %d", index)
raise cu
time_taken = time.time() - before
current_fragment_index: int = -1
last_fragment_index: int = -1
for key in y:
if y[key].X > 0.5:
last_fragment_index = key[0]
current_fragment_index = key[1]
break
assert current_fragment_index >= 0, "current_fragment_index not set"
assert last_fragment_index >= 0, "last_fragment_index not set"
seq = [k for k, v in x.items() if v.X > 0.5]
sequence: List[int] = [current_fragment_index]
while len(sequence) < size:
sequence += [b for a, b in seq if a == sequence[-1]]
assert sequence[-1] == last_fragment_index, "does not include all fragments"
first_fragment = segments_37[sequence[0]]
section_start = calculateOffset(first_fragment.offset, current_size)
# If the first fragment inside the current section already
# starts at an offset to the original section start, account
# for this offset by moving the section start back to the original
# alignment for the start of the section.
first_offset_into_section = calculateOffset(first_fragment.offset,
first_fragment.section_start) \
- first_fragment.section_start
if first_offset_into_section > 0:
section_start -= first_offset_into_section
# The following case might happen if the first fragment is moved
# into a gap after the previous section (i.e., into a page that
# lies before the original beginning of the section). In order
# to keep correct alignment for the section and avoid wrongly
# overlapping any fragments, let's revert the forward shift for
# now and simply start in the following page.
if section_start < current_size:
section_start += PAGESIZE
current_size = section_start
assert section_start >= current_size
for fragment in sequence:
current_fragment = segments_37[fragment]
current_fragment.shift = calculateOffset(current_fragment.offset, current_size) - current_fragment.offset
current_fragment.section_start = section_start
current_size = current_fragment.offset + current_fragment.shift + current_fragment.fsize
m.dispose()
env.dispose()
except Exception as e:
print(e)
raise e
logging.debug('ILP: %d after %.3f seconds', current_size, time_taken)
return current_size
# Fixme: Doku
def solve_with_gurobi(segments_36: List[List[FragmentRange]], current_size, file_name, log, section_aligns):
for i in range(1, len(segments_36)):
# Make sure the next section starts with proper alignment
current_size = roundUp(current_size, section_aligns[i])
if i == 1:
fix_first = (segments_36[i][0].offset // PAGESIZE == 0)
else:
fix_first = segments_36[i-1][-1].fsize > 0 and (segments_36[i-1][-1].vaddr + segments_36[i-1][-1].msize) // PAGESIZE == segments_36[i][0].vaddr // PAGESIZE
# fix_first = current_size // PAGESIZE == segments_36[i][0].offset // PAGESIZE
fix_last = False
if i != len(segments_36) - 1:
last = segments_36[i][-1]
ahead = segments_36[i + 1][0]
fix_last = (last.offset + last.fsize) // PAGESIZE == ahead.offset // PAGESIZE
current_size = solve_lp_instance(segments_36[i], current_size, i, fix_first, fix_last, file_name, log)
return current_size
# Fixme: Doku
def solve_smt_instance(section: List[FragmentRange], current_size: int, index: int, fix_first: bool, fix_last: bool) -> int:
size = len(section)
if size == 1:
# Fixme: Doku
# simply push the address ranges together
section[0].section_start = calculateOffset(section[0].offset, current_size)
section[0].shift = section[0].section_start - section[0].offset
return section[0].section_start + section[0].fsize
# xxx: mehr als das erste bzw. letzte Fragment fixieren, für kleine Fragmente
# xxx: funktioniert momentan, da es FragmentRanges geht, die die ganze Page abdecken
elif size == 2 and (fix_first or fix_last):
section_start = calculateOffset(section[0].offset, current_size)
for tmp_111 in section:
tmp_111.shift = calculateOffset(tmp_111.offset, current_size) - tmp_111.offset
tmp_111.section_start = section_start
current_size = calculateOffset(tmp_111.offset, current_size) + tmp_111.fsize
return current_size
# xxx: mehr als das erste bzw. letzte Fragment fixieren, für kleine Fragmente
# xxx: funktioniert momentan, da es FragmentRanges geht, die die ganze Page abdecken
elif size == 3 and fix_first and fix_last:
section_start = calculateOffset(section[0].offset, current_size)
for tmp_111 in section:
tmp_111.shift = calculateOffset(tmp_111.offset, current_size) - tmp_111.offset
tmp_111.section_start = section_start
current_size = calculateOffset(tmp_111.offset, current_size) + tmp_111.fsize
return current_size
else:
# This import is rather expensive for small files (0.2s) so let's do
# it only if it's really required
import z3
z3.set_param("parallel.enable", True)
smt_constants = []
for fragment in section:
smt_constants.append(fragment.get_smt_constants())
# Workaround for invalid models with the new solver
z3.set_option("smt.arith.solver","2")
z3.set_option("smt.auto_config","false")
z3.set_option("smt.relevancy","0")
p = z3.IntVector("p", len(section))
end_13 = z3.Int("end")
start_13 = z3.Int("start")
optimizer = z3.SolverFor('QF_LIA')
# Try to search for a solution for 30 minutes max
optimizer.set('timeout', 30 * 60 * 1000)
end_terms = []
start_terms = []
# The start must be after the current end of file
optimizer.add(start_13 >= current_size)
# start must stay smaller than the end of this section
optimizer.add(start_13 < end_13)
# constraints
for i in range(len(section)):
# All fragments must start after the current end of file
optimizer.add(p[i] >= current_size // PAGESIZE)
start_i = smt_constants[i][0]
end_i = start_i + smt_constants[i][1]
# fragments can't overlap
for j in range(i + 1, len(section)):
if i == j:
continue
start_j = smt_constants[j][0]
end_j = start_j + smt_constants[j][1]
optimizer.add(z3.Or(p[j] * PAGESIZE + end_j <= p[i] * PAGESIZE + start_i,
p[i] * PAGESIZE + end_i <= p[j] * PAGESIZE + start_j
))
# fragments can only be placed after the current end of file
optimizer.add(p[i] * PAGESIZE + start_i >= current_size)
# variable "end" shall point after all fragments
optimizer.add(p[i] * PAGESIZE + end_i <= end_13)
# variable "end" shall point at the end of the last fragment
end_terms.append(p[i] * PAGESIZE + end_i == end_13)
# variable "start" shall point before all fragments
optimizer.add(p[i] * PAGESIZE + start_i >= start_13)
# variable "start" shall point at the start of the first fragment
start_terms.append(p[i] * PAGESIZE + start_i == start_13)
optimizer.add(z3.Or(end_terms))
optimizer.add(z3.Or(start_terms))
# xxx: mehr als das erste bzw. letzte Fragment fixieren, für kleine Fragmente
# xxx: funktioniert momentan, da es FragmentRanges geht, die die ganze Page abdecken
# constraints for first range
if fix_first:
if current_size % PAGESIZE > smt_constants[0][0]:
# first fragment is in the page after the current end of the file
optimizer.add(p[0] - 1 == current_size // PAGESIZE)
else:
# first fragment is in the same page as the current end of the file
optimizer.add(p[0] == current_size // PAGESIZE)
# constraints for last range
if fix_last:
# last range must come last in file
for i in range(len(section) - 1):
optimizer.add((p[len(section) - 1] * PAGESIZE + smt_constants[-1][0]) + smt_constants[-1][1]
> (p[i] * PAGESIZE + smt_constants[i][0]) + smt_constants[i][1])
last_model = None
cur_end = section[-1].offset + section[-1].fsize
before = time.time()
while True:
# Check for a smaller possible value of 'end', pin start and all
# p[i] * PAGESIZE to be smaller than the current end
terms = [end_13 < cur_end, start_13 < cur_end]
for i in range(len(section)):
terms.append(p[i] <= cur_end // PAGESIZE)
res = optimizer.check(z3.And(terms))
if res != z3.sat:
break
model = optimizer.model()
# This check is only required if the first check is considered buggy
terms = []
for i in range(len(section)):
terms.append(p[i] == model[p[i]])
res = optimizer.check(z3.And(terms))
if res != z3.sat:
logging.warning('weird unsat')
logging.debug('%s', str(model))
break
else:
last_model = copy.deepcopy(model)
# ... until here.
cur_end = model[end_13].as_long()
logging.debug('SMT: %d after %.3f seconds', cur_end, time.time() - before)
if last_model is None:
logging.error("Z3 could not find a solution for section %d", index)
raise cu
else:
section_start = last_model[start_13].as_long()
# Same reasoning as in the ILP case: if the first fragment
# started at an offset relative to the beginning of the section
# in the original file, section_start will be shifted by this
# amount in the model. In this case, move the beginning of the
# section back again.
_, min_idx = min((last_model[p[i]].as_long(), i) for i in range(len(section)))
first = section[min_idx]
first_offset_into_section = calculateOffset(first.offset, first.section_start) - first.section_start
if first_offset_into_section > 0:
section_start -= first_offset_into_section
for i in range(len(section)):
section[i].section_start = section_start
section[i].shift = (last_model.eval(p[i] * PAGESIZE + smt_constants[i][0])).as_long() - section[i].offset
return last_model[end_13].as_long()
# Fixme: Doku
def solve_with_z3(segments_13: List[List[FragmentRange]], current_size: int, section_aligns: List[int]) -> int:
for i in range(1, len(segments_13)):
current_size = roundUp(current_size, section_aligns[i])
if i == 1:
fix_first = (segments_13[i][0].offset // PAGESIZE == 0)
else:
fix_first = segments_13[i-1][-1].fsize > 0 and ((segments_13[i-1][-1].vaddr + segments_13[i-1][-1].msize) // PAGESIZE == segments_13[i][0].vaddr // PAGESIZE)
# fix_first = current_size // PAGESIZE == segments_13[i][0].offset // PAGESIZE
fix_last = False
if i != len(segments_13) - 1:
last = segments_13[i][-1]
ahead = segments_13[i + 1][0]
fix_last = (last.offset + last.fsize) // PAGESIZE == ahead.offset // PAGESIZE
current_size = solve_smt_instance(segments_13[i], current_size, i, fix_first, fix_last)
return current_size
def maybe_merge_with_next_fragment(ret, frag, ahead_frag):
# last memory page with content from the current range
current_page = (frag.vaddr + frag.msize) // PAGESIZE
# first memory page with content from the next range
tmp_page = ahead_frag.vaddr // PAGESIZE
# last file page with content from the current range
current_filepage = (frag.offset + frag.fsize) // PAGESIZE
# first file page with content from the next range
tmp_filepage = (ahead_frag.offset + ahead_frag.shift) // PAGESIZE
if current_page + 1 == tmp_page and current_filepage + 1 == tmp_filepage:
# data of next range will be loaded in the following page as content of current range
# => merge the ranges
frag.fsize = ahead_frag.offset + ahead_frag.shift + ahead_frag.fsize - frag.offset
frag.msize = ahead_frag.vaddr + ahead_frag.msize - frag.vaddr
frag.loadable |= ahead_frag.loadable
frag.flags |= ahead_frag.flags
ret.list_entries -= 1
ret.phdr_entries -= 1
ret.segment_list.remove(ahead_frag)
# FIXME: Doku
# \brief Calculates the new file layout
#
# \param ranges Array of list of data ranges to incorporate in the new file
# \param size Size of ranges
# \param oldEntries Number of PHDR entries of original file that are NOT LOADs
# \param elfclass Elf Class (32bit or 64bit)
# \param permuteRanges Flag if the address ranges of sections should be permuted
#
# \return The [description of the file layout](@ref layoutDescription) of the output file
def calculateNewFilelayout(ranges_13: List[List[FileFragment]], old_entries: int, elfclass: c_int,
permute_ranges: str, file_name, log) -> LayoutDescription:
size = len(ranges_13)
ret: LayoutDescription = LayoutDescription()
ret.segment_num = size
ret.segments = [[]] * ret.segment_num
ret.section_aligns = [1] * ret.segment_num
# number of LOAD entries in new PHDR table
# Start with one for file header and one for PHDR table
loads = 2
if elfclass == ELFCLASS32:
current_size = SIZEOF_ELF32_EHDR
else:
current_size = SIZEOF_ELF64_EHDR
# ignore section 0
for i in range(1, size):
# determine the address ranges from the data ranges of a section
ret.segments[i] = segments(ranges_13[i], ranges_13[i][0].section_offset)
ret.section_aligns[i] = ranges_13[i][0].section_align
loads += countLoadableSegmentRanges(ret.segments[i])
# check if user want to permute address ranges
if permute_ranges == PERMUTE_WITH_BRUTE_FORCE:
current_size = permute(ret.segments, current_size, ret.section_aligns)
if current_size == 0:
raise cu
elif permute_ranges == PERMUTE_WITH_GUROBI:
current_size = solve_with_gurobi(ret.segments, current_size, file_name, log, ret.section_aligns)
elif permute_ranges == PERMUTE_WITH_Z3:
current_size = solve_with_z3(ret.segments, current_size, ret.section_aligns)
else:
# simply push the address ranges together
for i in range(1, size):
current_size = roundUp(current_size, ret.section_aligns[i])
section_start = calculateOffset(ret.segments[i][0].section_start, current_size)
for tmp_111 in ret.segments[i]:
tmp_111.shift = calculateOffset(tmp_111.offset, current_size) - tmp_111.offset
tmp_111.section_start = section_start
current_size = calculateOffset(tmp_111.offset, current_size) + tmp_111.fsize
for section_no, seg in enumerate(ret.segments):
start_end_pairs = []
logging.debug('calculated segments for section no. %d', section_no)
for fr_idx, fr_rng in enumerate(seg):
logging.debug('fr_idx %d, offset %x, shift %x -> (%x, size %x, end %x)',
fr_idx, fr_rng.offset, fr_rng.shift,
fr_rng.offset + fr_rng.shift, fr_rng.fsize,
fr_rng.offset + fr_rng.shift + fr_rng.fsize)
start_end_pairs.append((fr_rng.offset + fr_rng.shift, fr_rng.offset + fr_rng.shift + fr_rng.fsize))
start_end_pairs.sort()
for idx, k in enumerate(start_end_pairs[:-1]):
end_cur = k[1]
start_next = start_end_pairs[idx + 1][0]
if end_cur > start_next:
logging.debug('--- end_cur > start_next: %x > %x', end_cur, start_next)
# join address ranges between sections
current_fragment: FragmentRange = FragmentRange()
ret.list_entries = loads + old_entries
current_fragment.offset = 0
current_fragment.fsize = ret.segments[1][0].offset + ret.segments[1][0].shift + ret.segments[1][0].fsize
current_fragment.vaddr = (ret.segments[1][0].vaddr // PAGESIZE) * PAGESIZE
current_fragment.msize = current_fragment.fsize
current_fragment.flags = ret.segments[1][0].flags
current_fragment.loadable = True
current_index = 0
ret.list_entries -= 1
for i in range(1, size):
if i == 1:
to_iterate = ret.segments[i][1:]
else:
to_iterate = ret.segments[i]
for tmp_113 in to_iterate:
# last memory page with content from the current range
current_page = (current_fragment.vaddr + current_fragment.msize) // PAGESIZE
# first memory page with content from the tmp_113 range
tmp_page = tmp_113.vaddr // PAGESIZE
# last file page with content from the current range
current_filepage = (current_fragment.offset + current_fragment.fsize) // PAGESIZE
# first file page with content from the tmp_113 range
tmp_filepage = (tmp_113.offset + tmp_113.shift) // PAGESIZE
if (current_page == tmp_page and current_filepage == tmp_filepage) or (current_page + 1 == tmp_page and current_filepage + 1 == tmp_filepage):
# data of tmp_113 range will be loaded in the same or the following page as content of current range
# => merge the ranges
current_fragment.fsize = tmp_113.offset + tmp_113.shift + tmp_113.fsize - current_fragment.offset
current_fragment.msize = tmp_113.vaddr + tmp_113.msize - current_fragment.vaddr
current_fragment.loadable |= tmp_113.loadable
current_fragment.flags |= tmp_113.flags
# If we're in a loadable fragment, we can reduce the number of
# PHDR entries by one due to this merge
if current_fragment.loadable:
ret.list_entries -= 1
else:
# data of tmp_113 range will be loaded in a page farther away from the content of current range
# => create new ranges
ret.segment_list.append(current_fragment)
current_index += 1
current_fragment = FragmentRange()
current_fragment.offset = tmp_113.offset + tmp_113.shift
current_fragment.fsize = tmp_113.fsize
current_fragment.vaddr = tmp_113.vaddr
current_fragment.msize = tmp_113.msize
current_fragment.flags = tmp_113.flags
current_fragment.loadable = tmp_113.loadable
tmp_113.contained_in = current_index
# If the last fragment is completely unloadable, we need one less entry
# for the PHDR table, otherwise we need to add this fragment to the list
if not current_fragment.loadable:
ret.list_entries -= 1
else:
ret.segment_list.append(current_fragment)
# insert PHDR table
i = 0
try:
for i in range(0, size):
# determine which start addresses the PHDR table would have if it were inserted after section i (i == 0
# meaning inserting after file header)
if i == 0:
tmp = ret.segments[1][0]
phdr_start, phdr_vaddr, entry_size = calculatePHDRInfo(0, tmp.vaddr - (tmp.offset + tmp.shift), elfclass, True)
else:
tmp = ret.segments[i][-1]
fileoffset = tmp.offset + tmp.fsize + tmp.shift
memoryoffset = tmp.vaddr + tmp.msize
phdr_start, phdr_vaddr, entry_size = calculatePHDRInfo(fileoffset, memoryoffset, elfclass, False)
# check if PHDR table is in the first segment
if not containsSegment(ret.segment_list[0], tmp):
raise EndOfSegmentException()
# check if PHDR table fits in the space in memory after section i
if i == size - 1:
# insert after all sections
# xxx: untested
# todo: Alignment not given after NOBITS sections
ret.phdr_start = phdr_start
ret.phdr_vaddr = phdr_vaddr
ret.phdr_entries = ret.list_entries
table_size = entry_size * ret.phdr_entries
current_size = ret.phdr_start + table_size
raise Done()
else:
if i == 0:
# Maybe there is space directly after the ELF header
current_start = 0
current_shift = 0
current_end = (SIZEOF_ELF32_EHDR if elfclass == ELFCLASS32 else SIZEOF_ELF64_EHDR)
else:
# If there isn't, check against the last segment