74a43ca212d93224da8b4c87706636c01ff9baad
[openpower-isa.git] / src / openpower / decoder / isa / mem.py
1 # SPDX-License-Identifier: LGPLv3+
2 # Copyright (C) 2020, 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
3 # Funded by NLnet http://nlnet.nl
4 """core of the python-based POWER9 simulator
5
6 this is part of a cycle-accurate POWER9 simulator. its primary purpose is
7 not speed, it is for both learning and educational purposes, as well as
8 a method of verifying the HDL.
9
10 related bugs:
11
12 * https://bugs.libre-soc.org/show_bug.cgi?id=424
13 """
14
15 from collections import defaultdict
16 from openpower.decoder.selectable_int import SelectableInt
17 from openpower.util import log, LogType
18 import math
19 import enum
20 from cached_property import cached_property
21 import mmap
22 import struct
23 from pickle import PicklingError
24 import ctypes
25 from nmutil import plain_data
26 from pathlib import Path
27 from openpower.syscalls import ppc_flags
28 import os
29 from elftools.elf.elffile import ELFFile
30 from elftools.elf.constants import P_FLAGS
31
32
33 def swap_order(x, nbytes):
34 x = x.to_bytes(nbytes, byteorder='little')
35 x = int.from_bytes(x, byteorder='big', signed=False)
36 return x
37
38
39 class MemException(Exception):
40 pass
41
42
43 def process_mem(initial_mem, row_bytes=8):
44 res = {}
45 # different types of memory data structures recognised (for convenience)
46 if isinstance(initial_mem, list):
47 initial_mem = (0, initial_mem)
48 if isinstance(initial_mem, tuple):
49 startaddr, mem = initial_mem
50 initial_mem = {}
51 for i, val in enumerate(mem):
52 initial_mem[startaddr + row_bytes*i] = (val, row_bytes)
53
54 for addr, val in initial_mem.items():
55 if isinstance(val, tuple):
56 (val, width) = val
57 else:
58 width = row_bytes # assume same width
59 # val = swap_order(val, width)
60 res[addr] = (val, width)
61
62 return res
63
64
65 @enum.unique
66 class _ReadReason(enum.Enum):
67 Read = enum.auto()
68 SubWordWrite = enum.auto()
69 Dump = enum.auto()
70 Execute = enum.auto()
71
72 @cached_property
73 def read_default(self):
74 if self in (self.SubWordWrite, self.Dump):
75 return 0
76 return None
77
78 @cached_property
79 def needed_mmap_page_flag(self):
80 if self is self.Execute:
81 return MMapPageFlags.X
82 return MMapPageFlags.R
83
84 @cached_property
85 def ld_logs(self):
86 return self is not self.Dump
87
88
89 class MemCommon:
90 def __init__(self, row_bytes, initial_mem, misaligned_ok):
91 self.bytes_per_word = row_bytes
92 self.word_log2 = math.ceil(math.log2(row_bytes))
93 self.last_ld_addr = None
94 self.last_st_addr = None
95 self.misaligned_ok = misaligned_ok
96 log("Sim-Mem", initial_mem, self.bytes_per_word, self.word_log2)
97 if not initial_mem:
98 return
99
100 self.initialize(row_bytes, initial_mem)
101
102 def initialize(self, row_bytes, initial_mem):
103 if isinstance(initial_mem, ELFFile):
104 return load_elf(self, initial_mem)
105 for addr, (val, width) in process_mem(initial_mem, row_bytes).items():
106 # val = swap_order(val, width)
107 self.st(addr, val, width, swap=False)
108
109 def _read_word(self, word_idx, reason):
110 raise NotImplementedError
111
112 def _write_word(self, word_idx, value):
113 raise NotImplementedError
114
115 def word_idxs(self):
116 raise NotImplementedError
117 yield 0
118
119 def make_sim_state_dict(self):
120 """ returns a dict equivalent to:
121 retval = {}
122 for k in list(self.word_idxs()):
123 data = self.ld(k*8, 8, False)
124 retval[k*8] = data
125 """
126 retval = {}
127 for k in list(self.word_idxs()):
128 data = self.ld(k*8, 8, False, reason=_ReadReason.Dump)
129 retval[k*8] = data
130 return retval
131
132 def _get_shifter_mask(self, wid, remainder, do_log=True):
133 shifter = ((self.bytes_per_word - wid) - remainder) * \
134 8 # bits per byte
135 # XXX https://bugs.libre-soc.org/show_bug.cgi?id=377
136 # BE/LE mode?
137 shifter = remainder * 8
138 mask = (1 << (wid * 8)) - 1
139 if do_log:
140 log("width,rem,shift,mask",
141 wid, remainder, hex(shifter), hex(mask))
142 return shifter, mask
143
144 # TODO: Implement ld/st of lesser width
145 def ld(self, address, width=8, swap=True, check_in_mem=False,
146 instr_fetch=False, reason=None):
147 do_log = reason is not None and reason.ld_logs
148 if do_log:
149 log("ld from addr 0x%x width %d" % (address, width),
150 swap, check_in_mem, instr_fetch)
151 self.last_ld_addr = address # record last load
152 ldaddr = address
153 remainder = address & (self.bytes_per_word - 1)
154 address = address >> self.word_log2
155 if remainder & (width - 1) != 0:
156 exc = MemException("unaligned",
157 "Unaligned access: remainder %x width %d" %
158 (remainder, width))
159 exc.dar = ldaddr
160 raise exc
161 if reason is None:
162 reason = _ReadReason.Execute if instr_fetch else _ReadReason.Read
163 val = self._read_word(address, reason)
164 if val is None:
165 if check_in_mem:
166 return None
167 else:
168 val = 0
169 if do_log:
170 log("ld mem @ 0x%x rem %d : 0x%x" % (ldaddr, remainder, val))
171
172 if width != self.bytes_per_word:
173 shifter, mask = self._get_shifter_mask(width, remainder, do_log)
174 if do_log:
175 log("masking", hex(val), hex(mask << shifter), shifter)
176 val = val & (mask << shifter)
177 val >>= shifter
178 if swap:
179 val = swap_order(val, width)
180 if do_log:
181 log("Read 0x%x from addr 0x%x" % (val, ldaddr))
182 return val
183
184 def _st(self, addr, v, width=8, swap=True):
185 staddr = addr
186 remainder = addr & (self.bytes_per_word - 1)
187 addr = addr >> self.word_log2
188 log("Writing 0x%x to ST 0x%x memaddr 0x%x/%x swap %s" %
189 (v, staddr, addr, remainder, str(swap)))
190 if not self.misaligned_ok and remainder & (width - 1) != 0:
191 exc = MemException("unaligned",
192 "Unaligned access: remainder %x width %d" %
193 (remainder, width))
194 exc.dar = staddr
195 raise exc
196 if swap:
197 v = swap_order(v, width)
198 if width != self.bytes_per_word:
199 val = self._read_word(addr, _ReadReason.SubWordWrite)
200 shifter, mask = self._get_shifter_mask(width, remainder)
201 val &= ~(mask << shifter)
202 val |= v << shifter
203 self._write_word(addr, val)
204 else:
205 val = v
206 self._write_word(addr, v)
207 log("mem @ 0x%x: 0x%x" % (staddr, val))
208
209 def st(self, st_addr, v, width=8, swap=True):
210 self.last_st_addr = st_addr # record last store
211 # misaligned not allowed: pass straight to Mem._st
212 if not self.misaligned_ok:
213 return self._st(st_addr, v, width, swap)
214 remainder = st_addr & (self.bytes_per_word - 1)
215 if swap:
216 v = swap_order(v, width)
217 # not misaligned: pass through to Mem._st but we've swapped already
218 misaligned = remainder & (width - 1)
219 if misaligned == 0 or (remainder + width <= self.bytes_per_word):
220 return self._st(st_addr, v, width, swap=False)
221 shifter, mask = self._get_shifter_mask(width, remainder)
222 # split into two halves. lower first
223 maxmask = (1 << (self.bytes_per_word)*8) - 1
224 val1 = ((v << shifter) & maxmask) >> shifter
225 self._st(st_addr, val1, width=width-misaligned, swap=False)
226 # now upper.
227 val2 = v >> ((width-misaligned)*8)
228 addr2 = (st_addr >> self.word_log2) << self.word_log2
229 addr2 += self.bytes_per_word
230 log("v, val2", hex(v), hex(val2), "ad", addr2)
231 self._st(addr2, val2, width=width-misaligned, swap=False)
232
233 def __call__(self, addr, sz):
234 val = self.ld(addr.value, sz, swap=False)
235 log("memread", addr, sz, hex(val), kind=LogType.InstrInOuts)
236 return SelectableInt(val, sz*8)
237
238 def memassign(self, addr, sz, val):
239 log("memassign", addr, sz, val, kind=LogType.InstrInOuts)
240 self.st(addr.value, val.value, sz, swap=False)
241
242 def dump(self, printout=True, asciidump=False):
243 keys = list(self.word_idxs())
244 keys.sort()
245 res = []
246 for k in keys:
247 v = self._read_word(k, _ReadReason.Dump)
248 res.append((k*8, v))
249 if not printout:
250 continue
251 s = ""
252 if asciidump:
253 for i in range(8):
254 c = chr(v >> (i*8) & 0xff)
255 if not c.isprintable():
256 c = "."
257 s += c
258 print("%016x: %016x" % ((k*8) & 0xffffffffffffffff, v), s)
259 return res
260
261 def log_fancy(self, *, kind=LogType.Default, name="Memory",
262 log2_line_size=4, log2_column_chunk_size=3, log=log):
263 line_size = 1 << log2_line_size
264 subline_mask = line_size - 1
265 column_chunk_size = 1 << log2_column_chunk_size
266
267 def make_line():
268 return bytearray(line_size)
269 mem_lines = defaultdict(make_line)
270 subword_range = range(1 << self.word_log2)
271 words = self.make_sim_state_dict()
272 for addr, word in words.items():
273 for i in subword_range:
274 v = (word >> i * 8) & 0xFF
275 mem_lines[addr >> log2_line_size][addr & subline_mask] = v
276 addr += 1
277
278 lines = []
279 last_line_index = None
280 for line_index in sorted(mem_lines.keys()):
281 line_addr = line_index << log2_line_size
282 if last_line_index is not None \
283 and last_line_index + 1 != line_index:
284 lines.append("*")
285 last_line_index = line_index
286 line_bytes = mem_lines[line_index]
287 line_str = f"0x{line_addr:08X}:"
288 for col_chunk in range(0, line_size,
289 column_chunk_size):
290 line_str += " "
291 for i in range(column_chunk_size):
292 line_str += f" {line_bytes[col_chunk + i]:02X}"
293 line_str += " |"
294 for i in range(line_size):
295 if 0x20 <= line_bytes[i] <= 0x7E:
296 line_str += chr(line_bytes[i])
297 else:
298 line_str += "."
299 line_str += "|"
300 lines.append(line_str)
301 lines = "\n".join(lines)
302 log(f"\n{name}:\n{lines}\n", kind=kind)
303
304 def read_cstr(self, addr):
305 """ returns a `bytearray` for the c string starting at addr, the
306 returned `bytearray` doesn't contain the nul terminator.
307
308 modifying the returned `bytearray` doesn't modify bytes in `self`
309 -- it is a copy.
310 """
311 retval = bytearray()
312 while True:
313 b = self.ld(addr, width=1)
314 if b:
315 retval.append(b)
316 addr += 1
317 else:
318 break
319 return retval
320
321
322 class Mem(MemCommon):
323 def __init__(self, row_bytes=8, initial_mem=None, misaligned_ok=False):
324 self.mem = {}
325 super().__init__(row_bytes, initial_mem, misaligned_ok)
326
327 def _read_word(self, word_idx, reason):
328 return self.mem.get(word_idx, reason.read_default)
329
330 def _write_word(self, word_idx, value):
331 self.mem[word_idx] = value
332
333 def word_idxs(self):
334 return self.mem.keys()
335
336
337 class MMapPageFlags(enum.IntFlag):
338 """ flags on each mmap-ped page
339
340 Note: these are *not* PowerISA MMU pages, but instead internal to Mem so
341 it can detect invalid accesses and assert rather than segfaulting.
342 """
343 R = 1
344 W = 2
345 X = 4
346 "readable when instr_fetch=True"
347
348 S = 8
349 "shared -- aka. not copy-on-write"
350
351 GROWS_DOWN = 16
352 """this memory block will grow when the address one page before the
353 beginning is accessed"""
354
355 RW = R | W
356 RWX = RW | X
357 NONE = 0
358
359
360 _ALLOWED_MMAP_NORMAL_FLAGS = MMapPageFlags.RWX | MMapPageFlags.S
361 _ALLOWED_MMAP_STACK_FLAGS = MMapPageFlags.RWX | MMapPageFlags.GROWS_DOWN
362
363
364 MMAP_PAGE_SIZE = 1 << 16 # size of chunk that we track
365 _PAGE_COUNT = (1 << 48) // MMAP_PAGE_SIZE # 48-bit address space
366 _NEG_PG_IDX_START = _PAGE_COUNT // 2 # start of negative half of address space
367 _USER_SPACE_SIZE = _NEG_PG_IDX_START * MMAP_PAGE_SIZE
368
369 # code assumes BLOCK_SIZE is a power of two
370 # BLOCK_SIZE = 1 << 32
371 BLOCK_SIZE = 1 << 28 # reduced so it works on armv7a
372
373 assert BLOCK_SIZE % MMAP_PAGE_SIZE == 0
374 assert MMAP_PAGE_SIZE % mmap.PAGESIZE == 0, "host's page size is too big"
375 assert 2 ** (mmap.PAGESIZE.bit_length() - 1) == mmap.PAGESIZE, \
376 "host's page size isn't a power of 2"
377
378 def _make_default_block_addrs():
379 needed_page_addrs = (
380 0, # low end of user space
381 0x10000000, # default ELF load address
382 _USER_SPACE_SIZE - MMAP_PAGE_SIZE, # high end of user space
383 )
384 block_addrs = set()
385 for page_addr in needed_page_addrs:
386 offset = page_addr % BLOCK_SIZE
387 block_addrs.add(page_addr - offset)
388 return tuple(sorted(block_addrs))
389
390 DEFAULT_BLOCK_ADDRS = _make_default_block_addrs()
391
392
393 @plain_data.plain_data(frozen=True, unsafe_hash=True, repr=False)
394 class MMapEmuBlock:
395 __slots__ = ("addrs", "flags", "file", "file_off")
396
397 def __init__(self, addrs, flags=MMapPageFlags.NONE, file=None, file_off=0):
398 # type: (range, MMapPageFlags, Path | str | None, int) -> None
399 if addrs.step != 1:
400 raise ValueError("bad address range, step must be 1")
401 if len(addrs) <= 0:
402 raise ValueError("bad address range, must be non-empty")
403 if addrs.start < 0:
404 raise ValueError("bad address range, must be non-negative")
405 if addrs.stop > 2 ** 64:
406 raise ValueError("bad address range -- goes beyond 2 ** 64")
407 if addrs.start % MMAP_PAGE_SIZE:
408 raise ValueError("bad address range -- start isn't page-aligned")
409 if addrs.stop % MMAP_PAGE_SIZE:
410 raise ValueError("bad address range -- stop isn't page-aligned")
411 if addrs[0] // BLOCK_SIZE != addrs[-1] // BLOCK_SIZE:
412 raise ValueError(
413 "bad address range -- crosses underlying block boundaries")
414 if file is not None:
415 if file_off < 0:
416 raise ValueError("bad file_off, must be non-negative")
417 if file_off % MMAP_PAGE_SIZE:
418 raise ValueError("bad file_off, must be page-aligned")
419 if flags & ~_ALLOWED_MMAP_NORMAL_FLAGS:
420 raise ValueError("invalid flags for mmap with file")
421 file = Path(file)
422 else:
423 if flags & ~_ALLOWED_MMAP_NORMAL_FLAGS:
424 if flags & ~_ALLOWED_MMAP_STACK_FLAGS:
425 raise ValueError("invalid flags for anonymous mmap")
426 file_off = 0 # no file -- clear offset
427 self.addrs = addrs
428 self.flags = flags
429 self.file = file
430 self.file_off = file_off
431 self.page_indexes # check that addresses can be mapped to pages
432
433 def intersects(self, other):
434 # type: (MMapEmuBlock | range) -> bool
435 if isinstance(other, MMapEmuBlock):
436 other = other.addrs
437 if len_(other) == 0:
438 return False
439 return other.start < self.addrs.stop and self.addrs.start < other.stop
440
441 @property
442 def is_private_anon(self):
443 return self.file is None and not self.flags & MMapPageFlags.S
444
445 @property
446 def underlying_block_key(self):
447 offset = self.addrs.start % BLOCK_SIZE
448 return self.addrs.start - offset
449
450 @property
451 def underlying_block_offsets(self):
452 start = self.addrs.start % BLOCK_SIZE
453 return range(start, start + len(self.addrs))
454
455 @property
456 def page_indexes(self):
457 first_page = MemMMap.addr_to_mmap_page_idx(self.addrs[0])
458 # can't just use stop, since that may be out-of-range
459 last_page = MemMMap.addr_to_mmap_page_idx(self.addrs[-1])
460 if first_page < _NEG_PG_IDX_START and last_page >= _NEG_PG_IDX_START:
461 raise ValueError(
462 "bad address range, crosses transition from positive "
463 "canonical addresses to negative canonical addresses")
464 return range(first_page, last_page + 1)
465
466 def difference(self, remove):
467 # type: (MMapEmuBlock) -> list[MMapEmuBlock]
468 """returns the blocks left after removing `remove` from `self`"""
469 if not self.intersects(remove):
470 return [self]
471 retval = []
472 addrs = range(self.addrs.start, remove.addrs.start)
473 if len(addrs):
474 retval.append(plain_data.replace(self, addrs=addrs))
475 addrs = range(remove.addrs.stop, self.addrs.stop)
476 if len(addrs):
477 file_off = self.file_off + addrs.start - self.addrs.start
478 retval.append(plain_data.replace(
479 self, addrs=addrs, file_off=file_off))
480 return retval
481
482 def __repr__(self):
483 parts = ["MMapEmuBlock(range(0x%X, 0x%X)"
484 % (self.addrs.start, self.addrs.stop)]
485 if self.flags != MMapPageFlags.NONE:
486 parts.append(", flags=%r" % (self.flags, ))
487 if self.file is not None:
488 parts.append(", file=%r" % (self.file, ))
489 if self.file_off != 0:
490 parts.append(", file_off=0x%X" % (self.file_off, ))
491 parts.append(")")
492 return "".join(parts)
493
494
495 # stuff marked "not available" is not in the powerpc64le headers on my system
496 LEGACY_MAP_MASK = (
497 ppc_flags.MAP_SHARED
498 | ppc_flags.MAP_PRIVATE
499 | ppc_flags.MAP_FIXED
500 | ppc_flags.MAP_ANONYMOUS
501 | ppc_flags.MAP_DENYWRITE
502 | ppc_flags.MAP_EXECUTABLE
503 # | ppc_flags.MAP_UNINITIALIZED # not available -- ignored for now
504 | ppc_flags.MAP_GROWSDOWN
505 | ppc_flags.MAP_LOCKED
506 | ppc_flags.MAP_NORESERVE
507 | ppc_flags.MAP_POPULATE
508 | ppc_flags.MAP_NONBLOCK
509 | ppc_flags.MAP_STACK
510 | ppc_flags.MAP_HUGETLB
511 # | ppc_flags.MAP_32BIT # not available -- ignored for now
512 # | ppc_flags.MAP_ABOVE4G # not available -- ignored for now
513 # | ppc_flags.MAP_HUGE_2MB # not available -- ignored for now
514 # | ppc_flags.MAP_HUGE_1GB # not available -- ignored for now
515 )
516
517 _MAP_GROWS = ppc_flags.MAP_GROWSDOWN
518 # _MAP_GROWS |= ppc_flags.MAP_GROWSUP # not available -- ignored for now
519
520 def len_(r):
521 """ len(), but with fix for len(range(2**64)) raising OverflowError """
522 try:
523 return len(r)
524 except OverflowError:
525 assert isinstance(r, range)
526 return 1 + (r.stop - r.start - 1) // r.step
527
528
529 class MemMMap(MemCommon):
530 def __init__(self, row_bytes=8, initial_mem=None, misaligned_ok=False,
531 block_addrs=DEFAULT_BLOCK_ADDRS, emulating_mmap=False):
532 # we can't allocate the entire 2 ** 47 byte address space, so split
533 # it into smaller blocks
534 self.mem_blocks = {
535 addr: mmap.mmap(-1, BLOCK_SIZE) for addr in sorted(block_addrs)}
536 assert all(addr % BLOCK_SIZE == 0 for addr in self.mem_blocks), \
537 "misaligned block address not supported"
538 self.__page_flags = {}
539 self.modified_pages = set()
540 self.__heap_range = None
541 self.__mmap_emu_alloc_blocks = set() # type: set[MMapEmuBlock] | None
542
543 for addr, block in self.mem_blocks.items():
544 block_addr = ctypes.addressof(ctypes.c_ubyte.from_buffer(block))
545 log("0x%X -> 0x%X len=0x%X" % (addr, block_addr, BLOCK_SIZE))
546
547 # build the list of unbacked blocks -- those address ranges that have
548 # no backing memory so mmap can't allocate there. These are maintained
549 # separately from __mmap_emu_alloc_blocks so munmap/mremap can't
550 # remove/modify them
551 addr_ranges = [
552 range(a, a + len(b)) for a, b in self.mem_blocks.items()]
553 self.__mmap_emu_unbacked_blocks = tuple(self.__gaps_in(addr_ranges))
554
555 if not emulating_mmap:
556 self.__mmap_emu_alloc_blocks = None
557 # mark blocks as readable/writable
558 for addr, block in self.mem_blocks.items():
559 start_page = self.addr_to_mmap_page_idx(addr)
560 end_page = start_page + len(block) // MMAP_PAGE_SIZE
561 for page_idx in range(start_page, end_page):
562 self.__page_flags[page_idx] = MMapPageFlags.RWX
563
564 super().__init__(row_bytes, initial_mem, misaligned_ok)
565
566 @property
567 def heap_range(self):
568 # type: () -> range | None
569 return self.__heap_range
570
571 @heap_range.setter
572 def heap_range(self, value):
573 # type: (range | None) -> None
574 if value is None:
575 self.__heap_range = value
576 return
577 if not self.emulating_mmap:
578 raise ValueError(
579 "can't set heap_range without emulating_mmap=True")
580 if not isinstance(value, range):
581 raise TypeError("heap_range must be a range or None")
582 if value.step != 1 or value.start > value.stop:
583 raise ValueError("heap_range is not a suitable range")
584 if value.start % MMAP_PAGE_SIZE != 0:
585 raise ValueError("heap_range.start must be aligned")
586 if value.stop % MMAP_PAGE_SIZE != 0:
587 raise ValueError("heap_range.stop must be aligned")
588 self.__heap_range = value
589
590 @staticmethod
591 def __gaps_in(sorted_ranges, start=0, stop=2 ** 64):
592 # type: (list[range] | tuple[range], int, int) -> list[range]
593 start = 0
594 gaps = []
595 for r in sorted_ranges:
596 gap = range(start, r.start)
597 if len(gap):
598 gaps.append(gap)
599 start = r.stop
600 gap = range(start, stop)
601 if len_(gap):
602 gaps.append(gap)
603 return gaps
604
605 @property
606 def emulating_mmap(self):
607 return self.__mmap_emu_alloc_blocks is not None
608
609 def __mmap_emu_map_fixed(self, block, replace, dry_run):
610 # type: (MMapEmuBlock, bool, bool) -> bool
611 """insert the block at the fixed address passed in, replacing the
612 parts of any other blocks that overlap if `replace` is `True`.
613
614 If `dry_run`, then don't make any changes, just check if it would
615 succeed.
616
617 This function requires the caller to check `block`'s permissions and to
618 perform the underlying `mmap` first.
619 """
620 if block.underlying_block_key not in self.mem_blocks:
621 return False # unbacked block
622 # intersecting_blocks must be separate list so we don't iterate while
623 # we modify self.__mmap_emu_alloc_blocks
624 intersecting_blocks = [
625 b for b in self.__mmap_emu_alloc_blocks if block.intersects(b)]
626 for b in intersecting_blocks:
627 if not replace:
628 return False
629 if not dry_run:
630 self.__mmap_emu_alloc_blocks.remove(b)
631 for replacement in b.difference(block):
632 self.__mmap_emu_alloc_blocks.add(replacement)
633 if not dry_run:
634 self.__mmap_emu_alloc_blocks.add(block)
635 for page_idx in block.page_indexes:
636 self.__page_flags[page_idx] = block.flags
637 return True
638
639 def __mmap_emu_unmap(self, block):
640 # type: (MMapEmuBlock) -> int
641 """unmap `block`, return 0 if no error, otherwise return -errno"""
642 assert block in self.__mmap_emu_alloc_blocks, \
643 "can't unmap already unmapped block"
644
645 # replace mapping with zeros
646 retval = self.__mmap_emu_zero_block(block)
647 if retval < 0:
648 return retval
649 # remove block
650 self.__mmap_emu_alloc_blocks.remove(block)
651 # mark pages as empty
652 for page_idx in block.page_indexes:
653 self.__page_flags.pop(page_idx)
654 self.modified_pages.discard(page_idx)
655 return retval
656
657 def __mmap_emu_zero_block(self, block):
658 # type: (MMapEmuBlock) -> int
659 """ mmap zeros over block, return 0 if no error,
660 otherwise return -errno
661 """
662 mblock = self.mem_blocks[block.underlying_block_key]
663 offsets = block.underlying_block_offsets
664 buf = (ctypes.c_ubyte * len(offsets)).from_buffer(mblock, offsets[0])
665 buf_addr = ctypes.addressof(buf)
666 libc = ctypes.CDLL(None)
667 syscall = libc.syscall
668 syscall.restype = ctypes.c_long
669 syscall.argtypes = (ctypes.c_long,) * 6
670 call_no = ctypes.c_long(ppc_flags.host_defines['SYS_mmap'])
671 host_prot = ppc_flags.host_defines['PROT_READ']
672 host_prot |= ppc_flags.host_defines['PROT_WRITE']
673 host_flags = ppc_flags.host_defines['MAP_ANONYMOUS']
674 host_flags |= ppc_flags.host_defines['MAP_FIXED']
675 host_flags |= ppc_flags.host_defines['MAP_PRIVATE']
676 # map a block of zeros over it
677 if -1 == int(syscall(
678 call_no, ctypes.c_long(buf_addr),
679 ctypes.c_long(len(offsets)),
680 ctypes.c_long(host_prot), ctypes.c_long(host_flags),
681 ctypes.c_long(-1), ctypes.c_long(0))):
682 return -ctypes.get_errno()
683 return 0
684
685 def __mmap_emu_resize_map_fixed(self, block, new_size):
686 # type: (MMapEmuBlock, int) -> MMapEmuBlock | None
687 assert block in self.__mmap_emu_alloc_blocks, \
688 "can't resize unmapped block"
689 if new_size == len(block.addrs):
690 return block
691 addrs = range(block.addrs.start, block.addrs.start + new_size)
692 new_block = plain_data.replace(block, addrs=addrs)
693 self.__mmap_emu_alloc_blocks.remove(block)
694 try:
695 if not self.__mmap_emu_map_fixed(
696 new_block, replace=False, dry_run=True):
697 return None
698 finally:
699 self.__mmap_emu_alloc_blocks.add(block)
700 if not block.is_private_anon:
701 # FIXME: implement resizing underlying mapping
702 raise NotImplementedError
703 else:
704 # clear newly mapped bytes
705 clear_addrs = range(block.addrs.stop, new_block.addrs.stop)
706 if len_(clear_addrs):
707 clear_block = MMapEmuBlock(clear_addrs)
708 if self.__mmap_emu_zero_block(clear_block) < 0:
709 return None
710
711 if new_size < len(block.addrs):
712 # shrinking -- unmap pages at end
713 r = range(new_block.page_indexes.stop, block.page_indexes.stop)
714 clear_block = MMapEmuBlock(r)
715 if self.__mmap_emu_zero_block(clear_block) < 0:
716 return None
717 for page_idx in r:
718 self.__page_flags.pop(page_idx)
719 self.modified_pages.discard(page_idx)
720 else:
721 # expanding -- map pages at end, they're cleared already
722 r = range(block.page_indexes.stop, new_block.page_indexes.stop)
723 for page_idx in r:
724 self.__page_flags[page_idx] = block.flags
725 self.modified_pages.discard(page_idx) # cleared page
726 self.__mmap_emu_alloc_blocks.remove(block)
727 self.__mmap_emu_alloc_blocks.add(new_block)
728 return new_block
729
730 def __mmap_emu_find_free_addr(self, block):
731 # type: (MMapEmuBlock) -> MMapEmuBlock | None
732 """find a spot where `block` will fit, returning the new block"""
733 blocks = [*self.__mmap_emu_alloc_blocks,
734 *self.__mmap_emu_unbacked_blocks]
735 blocks.sort(key=lambda b: b.addrs.start)
736 biggest_gap = range(0)
737 for gap in self.__gaps_in([b.addrs for b in blocks]):
738 if len(biggest_gap) < len(gap):
739 biggest_gap = gap
740 extra_size = len(biggest_gap) - len(block.addrs)
741 if extra_size < 0:
742 return None # no space anywhere
743 # try to allocate in the middle of the gap, so mmaps can grow later
744 offset = extra_size // 2
745
746 # align to page -- this depends on gap being aligned already.
747 #
748 # rounds down offset, so no need to check size again since it can't
749 # ever get closer to the end of the gap
750 offset -= offset % MMAP_PAGE_SIZE
751 start = biggest_gap.start + offset
752 addrs = range(start, start + len(block))
753 return plain_data.replace(block, addrs=addrs)
754
755 def __mmap_emu_try_grow_down(self, addr, needed_flag):
756 # type: (int, MMapPageFlags) -> bool
757 """ if addr is the page just before a GROW_DOWN block, try to grow it.
758 returns True if successful. """
759 return False # FIXME: implement
760
761 def brk_syscall(self, addr):
762 assert self.emulating_mmap, "brk syscall requires emulating_mmap=True"
763 assert self.heap_range is not None, "brk syscall requires a heap"
764
765 if addr < self.heap_range.start:
766 # can't shrink heap to negative size
767 return self.heap_range.stop # don't change heap
768
769 # round addr up to the nearest page
770 addr_div_page_size = -(-addr // MMAP_PAGE_SIZE) # ceil(addr / size)
771 addr = addr_div_page_size * MMAP_PAGE_SIZE
772
773 # something else could be mmap-ped in the middle of the heap,
774 # be careful...
775
776 block = None
777 if len_(self.heap_range) != 0:
778 for b in self.__mmap_emu_alloc_blocks:
779 # we check for the end matching so we get the last heap block
780 # if the heap was split.
781 # the heap must not be a file mapping.
782 # the heap must not be shared, and must be RW
783 if b.addrs.stop == self.heap_range.stop and b.file is None \
784 and b.flags == MMapPageFlags.RW:
785 block = b
786 break
787
788 if block is not None and addr < block.addrs.start:
789 # heap was split by something, we can't shrink beyond
790 # the start of the last heap block
791 return self.heap_range.stop # don't change heap
792
793 if block is not None and addr == block.addrs.start:
794 # unmap heap block
795 if self.__mmap_emu_unmap(block) < 0:
796 block = None # can't unmap heap block
797 elif addr > self.heap_range.stop and block is None:
798 # map new heap block
799 try:
800 addrs = range(self.heap_range.stop, addr)
801 block = MMapEmuBlock(addrs, flags=MMapPageFlags.RW)
802 if not self.__mmap_emu_map_fixed(block,
803 replace=False, dry_run=True):
804 block = None
805 elif 0 != self.__mmap_emu_zero_block(block):
806 block = None
807 else:
808 self.__mmap_emu_map_fixed(block,
809 replace=False, dry_run=False)
810 except (MemException, ValueError):
811 # caller could pass in invalid size, catch that
812 block = None
813 elif block is not None: # resize block
814 try:
815 block = self.__mmap_emu_resize_map_fixed(
816 block, addr - block.addrs.start)
817 except (MemException, ValueError):
818 # caller could pass in invalid size, catch that
819 block = None
820
821 if block is None and addr != self.heap_range.start:
822 # can't resize heap block
823 return self.heap_range.stop # don't change heap
824
825 # success! assign new heap_range
826 self.heap_range = range(self.heap_range.start, addr)
827 return self.heap_range.stop # return new brk address
828
829 def mmap_syscall(self, addr, length, prot, flags, fd, offset, is_mmap2):
830 assert self.emulating_mmap, "mmap syscall requires emulating_mmap=True"
831 if is_mmap2:
832 offset *= 4096 # specifically *not* the page size
833 prot_read = bool(prot & ppc_flags.PROT_READ)
834 prot_write = bool(prot & ppc_flags.PROT_WRITE)
835 prot_exec = bool(prot & ppc_flags.PROT_EXEC)
836 prot_all = (ppc_flags.PROT_READ | ppc_flags.PROT_WRITE
837 | ppc_flags.PROT_EXEC)
838 # checks based off the checks in linux
839 if prot & ~prot_all:
840 return -ppc_flags.EINVAL
841 if offset % MMAP_PAGE_SIZE:
842 return -ppc_flags.EINVAL
843 if flags & ppc_flags.MAP_HUGETLB:
844 # not supported
845 return -ppc_flags.EINVAL
846 if length <= 0 or offset < 0:
847 return -ppc_flags.EINVAL
848 if flags & ppc_flags.MAP_FIXED_NOREPLACE:
849 flags |= ppc_flags.MAP_FIXED
850 if not (flags & ppc_flags.MAP_FIXED):
851 addr &= MMAP_PAGE_SIZE - 1 # page-align address, rounding down
852 # page-align length, rounding up
853 length = (length + MMAP_PAGE_SIZE - 1) & ~(MMAP_PAGE_SIZE - 1)
854 if length + offset >= 2 ** 64:
855 # overflowed
856 return -ppc_flags.ENOMEM
857 block_flags = MMapPageFlags.NONE
858 if prot_read:
859 block_flags |= MMapPageFlags.R
860 if prot_write:
861 block_flags |= MMapPageFlags.W
862 if prot_exec:
863 block_flags |= MMapPageFlags.X
864 if flags & ppc_flags.MAP_GROWSDOWN:
865 block_flags |= MMapPageFlags.GROWS_DOWN
866 file = None
867 if fd >= 0:
868 try:
869 file = os.readlink("/proc/self/fd/%i" % fd)
870 except IOError:
871 return -ppc_flags.EBADF
872 try:
873 block = MMapEmuBlock(
874 range(addr, addr + length), block_flags, file, offset)
875 except (ValueError, MemException):
876 return -ppc_flags.EINVAL
877 if not (flags & ppc_flags.MAP_FIXED):
878 block = self.__mmap_emu_find_free_addr(block)
879 if block is None:
880 return -ppc_flags.ENOMEM
881 if flags & ppc_flags.MAP_LOCKED:
882 return -ppc_flags.EPERM
883 map_ty = flags & ppc_flags.MAP_TYPE
884 if file is not None:
885 fallthrough = False
886 if map_ty == ppc_flags.MAP_SHARED:
887 flags &= LEGACY_MAP_MASK
888 fallthrough = True
889 if fallthrough or map_ty == ppc_flags.MAP_SHARED_VALIDATE:
890 if flags & ~LEGACY_MAP_MASK:
891 return -ppc_flags.EOPNOTSUPP
892 raise NotImplementedError("MAP_SHARED on file")
893 fallthrough = True
894 if fallthrough or map_ty == ppc_flags.MAP_PRIVATE:
895 if flags & _MAP_GROWS:
896 return -ppc_flags.EINVAL
897 else:
898 return -ppc_flags.EINVAL
899 elif map_ty == ppc_flags.MAP_SHARED:
900 if flags & _MAP_GROWS:
901 return -ppc_flags.EINVAL
902 raise NotImplementedError("MAP_SHARED on memory")
903 elif map_ty != ppc_flags.MAP_PRIVATE:
904 return -ppc_flags.EINVAL
905 replace = not (flags & ppc_flags.MAP_FIXED_NOREPLACE)
906 if not self.__mmap_emu_map_fixed(block, replace, dry_run=True):
907 # failed, was that because there's an existing memory block or
908 # that was an invalid address?
909 if self.__mmap_emu_map_fixed(block, replace=True, dry_run=True):
910 return -ppc_flags.EEXIST # existing memory block
911 else:
912 return -ppc_flags.EINVAL # invalid address
913 mblock = self.mem_blocks[block.underlying_block_key]
914 offsets = block.underlying_block_offsets
915 buf = (ctypes.c_ubyte * len(offsets)).from_buffer(mblock, offsets[0])
916 buf_addr = ctypes.addressof(buf)
917 libc = ctypes.CDLL(None)
918 syscall = libc.syscall
919 syscall.restype = ctypes.c_long
920 syscall.argtypes = (ctypes.c_long,) * 6
921 call_no = ctypes.c_long(ppc_flags.host_defines['SYS_mmap'])
922 host_prot = ppc_flags.host_defines['PROT_READ']
923 if block.flags & MMapPageFlags.W:
924 host_prot |= ppc_flags.host_defines['PROT_WRITE']
925 host_flags = ppc_flags.host_defines['MAP_FIXED']
926 host_flags |= ppc_flags.host_defines['MAP_PRIVATE']
927 length = len(offsets)
928 extra_zeros_length = 0
929 extra_zeros_start = 0
930 if file is None:
931 host_flags |= ppc_flags.host_defines['MAP_ANONYMOUS']
932 # don't remove check, since we'll eventually have shared memory
933 if host_flags & ppc_flags.host_defines['MAP_PRIVATE']:
934 # always map private memory read/write,
935 # so we can clear it if needed
936 host_prot |= ppc_flags.host_defines['PROT_WRITE']
937 else:
938 file_sz = os.fstat(fd).st_size
939 # host-page-align file_sz, rounding up
940 file_sz = (file_sz + mmap.PAGESIZE - 1) & ~(mmap.PAGESIZE - 1)
941 extra_zeros_length = max(0, length - (file_sz - offset))
942 extra_zeros_start = buf_addr + (file_sz - offset)
943 length -= extra_zeros_length
944 res = int(syscall(
945 call_no, ctypes.c_long(buf_addr), ctypes.c_long(length),
946 ctypes.c_long(host_prot), ctypes.c_long(host_flags),
947 ctypes.c_long(fd), ctypes.c_long(offset)))
948 if res == -1:
949 return -ctypes.get_errno()
950 self.__mmap_emu_map_fixed(block, replace=True, dry_run=False)
951 if extra_zeros_length != 0:
952 host_flags = ppc_flags.host_defines['MAP_ANONYMOUS']
953 host_flags |= ppc_flags.host_defines['MAP_FIXED']
954 host_flags |= ppc_flags.host_defines['MAP_PRIVATE']
955 if -1 == int(syscall(
956 call_no, ctypes.c_long(extra_zeros_start),
957 ctypes.c_long(extra_zeros_length),
958 ctypes.c_long(host_prot), ctypes.c_long(host_flags),
959 ctypes.c_long(-1), ctypes.c_long(0))):
960 return -ctypes.get_errno()
961 if file is not None:
962 # memory could be non-zero, mark as modified
963 for page_idx in block.page_indexes:
964 self.modified_pages.add(page_idx)
965 log("mmap block=%s" % (block,), kind=LogType.InstrInOuts)
966 return block.addrs.start
967
968 @staticmethod
969 def mmap_page_idx_to_addr(page_idx):
970 assert 0 <= page_idx < _PAGE_COUNT
971 if page_idx >= _NEG_PG_IDX_START:
972 page_idx -= _PAGE_COUNT
973 return (page_idx * MMAP_PAGE_SIZE) % 2 ** 64
974
975 @staticmethod
976 def addr_to_mmap_page_idx(addr):
977 page_idx, offset = divmod(addr, MMAP_PAGE_SIZE)
978 page_idx %= _PAGE_COUNT
979 expected = MemMMap.mmap_page_idx_to_addr(page_idx) + offset
980 if addr != expected:
981 exc = MemException("not sign extended",
982 ("address not sign extended: 0x%X "
983 "expected 0x%X") % (addr, expected))
984 exc.dar = addr
985 raise exc
986 return page_idx
987
988 def __reduce_ex__(self, protocol):
989 raise PicklingError("MemMMap can't be deep-copied or pickled")
990
991 def __access_addr_range_err(self, start_addr, size, needed_flag):
992 assert needed_flag != MMapPageFlags.W, \
993 "can't write to address 0x%X size 0x%X" % (start_addr, size)
994 if self.emulating_mmap:
995 exc = MemException("access not allowed",
996 "memory access not allowed: addr=0x%X: %s"
997 % (start_addr, needed_flag))
998 exc.dar = start_addr
999 raise exc
1000 return None, 0
1001
1002 def __access_addr_range(self, start_addr, size, needed_flag):
1003 assert size > 0, "invalid size"
1004 page_idx = self.addr_to_mmap_page_idx(start_addr)
1005 last_addr = start_addr + size - 1
1006 last_page_idx = self.addr_to_mmap_page_idx(last_addr)
1007 block_addr = start_addr % BLOCK_SIZE
1008 block_k = start_addr - block_addr
1009 last_block_addr = last_addr % BLOCK_SIZE
1010 last_block_k = last_addr - last_block_addr
1011 if block_k != last_block_k:
1012 return self.__access_addr_range_err(start_addr, size, needed_flag)
1013 for i in range(page_idx, last_page_idx + 1):
1014 flags = self.__page_flags.get(i, 0)
1015 if flags & needed_flag == 0:
1016 if not self.__mmap_emu_try_grow_down(start_addr, needed_flag):
1017 return self.__access_addr_range_err(
1018 start_addr, size, needed_flag)
1019 if needed_flag is MMapPageFlags.W:
1020 self.modified_pages.add(page_idx)
1021 return self.mem_blocks[block_k], block_addr
1022
1023 def get_ctypes(self, start_addr, size, is_write):
1024 """ returns a ctypes ubyte array referring to the memory at
1025 `start_addr` with size `size`
1026 """
1027 flag = MMapPageFlags.W if is_write else MMapPageFlags.R
1028 block, block_addr = self.__access_addr_range(start_addr, size, flag)
1029 assert block is not None, \
1030 f"can't read from address 0x{start_addr:X} size 0x{size:X}"
1031 return (ctypes.c_ubyte * size).from_buffer(block, block_addr)
1032
1033 def _read_word(self, word_idx, reason):
1034 block, block_addr = self.__access_addr_range(
1035 word_idx * self.bytes_per_word, self.bytes_per_word,
1036 reason.needed_mmap_page_flag)
1037 if block is None:
1038 return reason.read_default
1039 bytes_ = block[block_addr:block_addr + self.bytes_per_word]
1040 return int.from_bytes(bytes_, 'little')
1041
1042 def _write_word(self, word_idx, value):
1043 block, block_addr = self.__access_addr_range(
1044 word_idx * self.bytes_per_word, self.bytes_per_word,
1045 MMapPageFlags.W)
1046 bytes_ = value.to_bytes(self.bytes_per_word, 'little')
1047 block[block_addr:block_addr + self.bytes_per_word] = bytes_
1048
1049 def word_idxs(self):
1050 zeros = bytes(self.bytes_per_word)
1051 for page_idx in self.modified_pages:
1052 start = self.mmap_page_idx_to_addr(page_idx)
1053 block, block_addr = self.__access_addr_range(
1054 start, MMAP_PAGE_SIZE, MMapPageFlags.R)
1055 end = start + MMAP_PAGE_SIZE
1056 for word_idx in range(start // self.bytes_per_word,
1057 end // self.bytes_per_word):
1058 next_block_addr = block_addr + self.bytes_per_word
1059 bytes_ = block[block_addr:next_block_addr]
1060 block_addr = next_block_addr
1061 if bytes_ != zeros:
1062 yield word_idx
1063
1064 def make_sim_state_dict(self):
1065 """ returns a dict equivalent to:
1066 retval = {}
1067 for k in list(self.word_idxs()):
1068 data = self.ld(k*8, 8, False)
1069 retval[k*8] = data
1070 """
1071 if self.bytes_per_word != 8:
1072 return super().make_sim_state_dict()
1073 retval = {}
1074 page_struct = struct.Struct("<%dQ" % (MMAP_PAGE_SIZE // 8,))
1075 assert page_struct.size == MMAP_PAGE_SIZE, "got wrong format"
1076 for page_idx in self.modified_pages:
1077 start = self.mmap_page_idx_to_addr(page_idx)
1078 block, block_addr = self.__access_addr_range(
1079 start, MMAP_PAGE_SIZE, MMapPageFlags.R)
1080 # written this way to avoid unnecessary allocations
1081 words = page_struct.unpack_from(block, block_addr)
1082 for i, v in zip(range(start, start + MMAP_PAGE_SIZE, 8), words):
1083 if v != 0:
1084 retval[i] = v
1085 return retval
1086
1087
1088 @plain_data.plain_data()
1089 class LoadedELF:
1090 __slots__ = "elf_file", "pc", "gprs", "fpscr"
1091
1092 def __init__(self, elf_file, pc, gprs, fpscr):
1093 self.elf_file = elf_file
1094 self.pc = pc
1095 self.gprs = gprs
1096 self.fpscr = fpscr
1097
1098
1099 def raise_if_syscall_err(result):
1100 if -4096 < result < 0:
1101 raise OSError(-result, os.strerror(-result))
1102 return result
1103
1104
1105 # TODO: change to much smaller size once GROWSDOWN is implemented
1106 DEFAULT_INIT_STACK_SZ = 4 << 20
1107
1108 # TODO: power9 identifies PowerISA v3.0, figure out if it's best...
1109 DEFAULT_AT_PLATFORM = "power9" # default value of AT_PLATFORM
1110 DEFAULT_AT_BASE_PLATFORM = "power9" # default value of AT_BASE_PLATFORM
1111 # TODO: use correct cache block sizes
1112 DEFAULT_AT_DCACHEBSIZE = 128 # default value of AT_DCACHEBSIZE
1113 DEFAULT_AT_ICACHEBSIZE = 128 # default value of AT_ICACHEBSIZE
1114 DEFAULT_AT_UCACHEBSIZE = 0 # default value of AT_UCACHEBSIZE
1115 DEFAULT_AT_L1I_CACHESIZE = 0x8000 # default value of AT_L1I_CACHESIZE
1116 DEFAULT_AT_L1I_CACHEGEOMETRY = 0x80 # default value of AT_L1I_CACHEGEOMETRY
1117 DEFAULT_AT_L1D_CACHESIZE = 0x8000 # default value of AT_L1D_CACHESIZE
1118 DEFAULT_AT_L1D_CACHEGEOMETRY = 0x80 # default value of AT_L1D_CACHEGEOMETRY
1119 DEFAULT_AT_L2_CACHESIZE = 0 # default value of AT_L2_CACHESIZE
1120 DEFAULT_AT_L2_CACHEGEOMETRY = 0 # default value of AT_L2_CACHEGEOMETRY
1121 DEFAULT_AT_L3_CACHESIZE = 0 # default value of AT_L3_CACHESIZE
1122 DEFAULT_AT_L3_CACHEGEOMETRY = 0 # default value of AT_L3_CACHEGEOMETRY
1123
1124 # default value of AT_HWCAP
1125 DEFAULT_AT_HWCAP = (ppc_flags.PPC_FEATURE_32 | ppc_flags.PPC_FEATURE_64 |
1126 ppc_flags.PPC_FEATURE_HAS_FPU | ppc_flags.PPC_FEATURE_HAS_MMU |
1127 ppc_flags.PPC_FEATURE_ARCH_2_06 | ppc_flags.PPC_FEATURE_TRUE_LE)
1128
1129 # default value of AT_HWCAP2
1130 DEFAULT_AT_HWCAP2 = (ppc_flags.PPC_FEATURE2_ARCH_2_07 |
1131 ppc_flags.PPC_FEATURE2_HAS_ISEL | ppc_flags.PPC_FEATURE2_HAS_TAR |
1132 ppc_flags.PPC_FEATURE2_ARCH_3_00 | ppc_flags.PPC_FEATURE2_DARN)
1133
1134 # FIXME: enable when scv emulation works
1135 # DEFAULT_AT_HWCAP2 |= ppc_flags.PPC_FEATURE2_SCV
1136
1137 # FIXME: enable if/when v3.1 PO1 prefixed insns work
1138 # DEFAULT_AT_HWCAP2 |= ppc_flags.PPC_FEATURE2_ARCH_3_1
1139
1140 CLOCKS_PER_SEC = 100
1141
1142
1143 def load_elf(
1144 mem, elf_file, args=(), env=(), stack_size=DEFAULT_INIT_STACK_SZ,
1145 platform=DEFAULT_AT_PLATFORM, base_platform=DEFAULT_AT_BASE_PLATFORM,
1146 rand_byte16=None, dcachebsize=DEFAULT_AT_DCACHEBSIZE,
1147 icachebsize=DEFAULT_AT_ICACHEBSIZE, ucachebsize=DEFAULT_AT_UCACHEBSIZE,
1148 l1i_cachesize=DEFAULT_AT_L1I_CACHESIZE,
1149 l1i_cachegeometry=DEFAULT_AT_L1I_CACHEGEOMETRY,
1150 l1d_cachesize=DEFAULT_AT_L1D_CACHESIZE,
1151 l1d_cachegeometry=DEFAULT_AT_L1D_CACHEGEOMETRY,
1152 l2_cachesize=DEFAULT_AT_L2_CACHESIZE,
1153 l2_cachegeometry=DEFAULT_AT_L2_CACHEGEOMETRY,
1154 l3_cachesize=DEFAULT_AT_L3_CACHESIZE,
1155 l3_cachegeometry=DEFAULT_AT_L3_CACHEGEOMETRY,
1156 hwcap=DEFAULT_AT_HWCAP, hwcap2=DEFAULT_AT_HWCAP2,
1157 was_setuid_like=False, execfd=None,
1158 ):
1159 if not isinstance(mem, MemMMap):
1160 raise TypeError("MemMMap required to load ELFs")
1161 if not isinstance(elf_file, ELFFile):
1162 raise TypeError()
1163 if elf_file.header['e_type'] != 'ET_EXEC':
1164 raise NotImplementedError("dynamic binaries aren't implemented")
1165 fd = elf_file.stream.fileno()
1166 heap_start = -1
1167 phnum = 0
1168 for segment in elf_file.iter_segments():
1169 phnum += 1
1170 if segment.header['p_type'] in ('PT_DYNAMIC', 'PT_INTERP'):
1171 raise NotImplementedError("dynamic binaries aren't implemented")
1172 elif segment.header['p_type'] == 'PT_LOAD':
1173 flags = segment.header['p_flags']
1174 offset = segment.header['p_offset']
1175 vaddr = segment.header['p_vaddr']
1176 filesz = segment.header['p_filesz']
1177 memsz = segment.header['p_memsz']
1178 align = segment.header['p_align']
1179 if align != 0x10000:
1180 raise NotImplementedError("non-default ELF segment alignment")
1181 if align < MMAP_PAGE_SIZE:
1182 raise NotImplementedError("align less than MMAP_PAGE_SIZE")
1183 prot = ppc_flags.PROT_NONE
1184 if flags & P_FLAGS.PF_R:
1185 prot |= ppc_flags.PROT_READ
1186 if flags & P_FLAGS.PF_W:
1187 prot |= ppc_flags.PROT_WRITE
1188 if flags & P_FLAGS.PF_X:
1189 prot |= ppc_flags.PROT_EXEC
1190 # align start to page
1191 adj = offset % MMAP_PAGE_SIZE
1192 offset -= adj
1193 assert offset >= 0
1194 vaddr -= adj
1195 filesz += adj
1196 memsz += adj
1197 # page-align, rounding up
1198 filesz_aligned = (
1199 filesz + MMAP_PAGE_SIZE - 1) & ~(MMAP_PAGE_SIZE - 1)
1200 # page-align, rounding up
1201 memsz_aligned = (
1202 memsz + MMAP_PAGE_SIZE - 1) & ~(MMAP_PAGE_SIZE - 1)
1203 page_end_init_needed = filesz < memsz and filesz < filesz_aligned
1204 zero_pages_needed = memsz > filesz_aligned
1205 adj_prot = prot # adjust prot for initialization
1206 if page_end_init_needed:
1207 # we need to initialize trailing bytes to zeros,
1208 # so we need write access
1209 adj_prot |= ppc_flags.PROT_WRITE
1210 flags = ppc_flags.MAP_FIXED_NOREPLACE | ppc_flags.MAP_PRIVATE
1211 result = mem.mmap_syscall(
1212 vaddr, filesz, adj_prot, flags, fd, offset, is_mmap2=False)
1213 raise_if_syscall_err(result)
1214 if page_end_init_needed:
1215 page_end = mem.get_ctypes(
1216 vaddr + filesz, filesz_aligned - filesz, True)
1217 ctypes.memset(page_end, 0, len(page_end))
1218 if zero_pages_needed:
1219 result = mem.mmap_syscall(
1220 vaddr + filesz_aligned, memsz - filesz_aligned,
1221 prot, flags, fd=-1, offset=0, is_mmap2=False)
1222 raise_if_syscall_err(result)
1223 heap_start = max(heap_start, vaddr + memsz_aligned)
1224 else:
1225 log("ignoring ELF segment of type " + segment.header['p_type'])
1226 # page-align stack_size, rounding up
1227 stack_size = (stack_size + MMAP_PAGE_SIZE - 1) & ~(MMAP_PAGE_SIZE - 1)
1228 stack_top = _USER_SPACE_SIZE
1229 stack_low = stack_top - stack_size
1230 prot = ppc_flags.PROT_READ | ppc_flags.PROT_WRITE
1231 flags = ppc_flags.MAP_FIXED_NOREPLACE | ppc_flags.MAP_PRIVATE
1232 result = mem.mmap_syscall(
1233 stack_low, stack_size, prot, flags, fd=-1, offset=0, is_mmap2=False)
1234 raise_if_syscall_err(result)
1235
1236 def copy_to_stack(buf):
1237 nonlocal stack_top
1238 l = len(buf)
1239 stack_top -= l
1240 addr = stack_top
1241 if l > 0:
1242 mem.get_ctypes(addr, l, True)[:] = buf
1243 return addr
1244
1245 def copy_cstr_to_stack(s):
1246 if s is None:
1247 return None
1248 if isinstance(s, str):
1249 s = s.encode()
1250 else:
1251 s = bytes(s)
1252 if b"\0" in s:
1253 raise ValueError("c string can't contain NUL")
1254 s += b"\0"
1255 return copy_to_stack(s)
1256
1257 stack_top -= 7 # weird, but matches linux
1258 program_name = copy_cstr_to_stack(os.readlink("/proc/self/fd/%i" % fd))
1259
1260 env_bytes = bytearray()
1261 env_offsets = []
1262 for env_entry in env:
1263 env_offsets.append(len(env_bytes))
1264 if isinstance(env_entry, str):
1265 env_entry = env_entry.encode()
1266 else:
1267 env_entry = bytes(env_entry)
1268 if b"\0" in env_entry:
1269 raise ValueError("env var byte-string can't contain NUL")
1270 if b"=" not in env_entry:
1271 raise ValueError("env var is missing =")
1272 env_bytes += env_entry
1273 env_bytes += b"\0"
1274
1275 stack_top -= len(env_bytes)
1276 env_bytes_addr = stack_top
1277 if len(env_bytes):
1278 mem.get_ctypes(env_bytes_addr, len(env_bytes), True)[:] = env_bytes
1279
1280 args = tuple(args)
1281 if len(args) == 0:
1282 args = ("",) # glibc depends on argc != 0
1283 args_bytes = bytearray()
1284 arg_offsets = []
1285 for arg in args:
1286 arg_offsets.append(len(args_bytes))
1287 if isinstance(arg, str):
1288 arg = arg.encode()
1289 else:
1290 arg = bytes(arg)
1291 if b"\0" in arg:
1292 raise ValueError("argument byte-string can't contain NUL")
1293 args_bytes += arg
1294 args_bytes += b"\0"
1295 args_bytes += b"\0"
1296 stack_top -= len(args_bytes)
1297 args_bytes_addr = stack_top
1298 mem.get_ctypes(args_bytes_addr, len(args_bytes), True)[:] = args_bytes
1299
1300 stack_top -= stack_top % 16 # align stack top for auxv
1301
1302 platform = copy_cstr_to_stack(platform)
1303 base_platform = copy_cstr_to_stack(base_platform)
1304
1305 if rand_byte16 is not None:
1306 rand_byte16 = bytes(rand_byte16)
1307 if len(rand_byte16) != 16:
1308 raise ValueError("rand_byte16 has wrong length, must be 16 bytes")
1309 rand_byte16 = copy_to_stack(rand_byte16)
1310
1311 auxv_entries = []
1312
1313 def auxv_entry(a_type, a_un):
1314 if a_type is None or a_un is None:
1315 return
1316 auxv_entries.append((a_type, a_un))
1317
1318 auxv_entry(ppc_flags.AT_IGNOREPPC, ppc_flags.AT_IGNOREPPC)
1319 auxv_entry(ppc_flags.AT_IGNOREPPC, ppc_flags.AT_IGNOREPPC)
1320 auxv_entry(ppc_flags.AT_DCACHEBSIZE, dcachebsize)
1321 auxv_entry(ppc_flags.AT_ICACHEBSIZE, icachebsize)
1322 auxv_entry(ppc_flags.AT_UCACHEBSIZE, ucachebsize)
1323 vdso_addr = None # TODO: add vdso
1324 auxv_entry(ppc_flags.AT_SYSINFO_EHDR, vdso_addr)
1325 auxv_entry(ppc_flags.AT_L1I_CACHESIZE, l1i_cachesize)
1326 auxv_entry(ppc_flags.AT_L1I_CACHEGEOMETRY, l1i_cachegeometry)
1327 auxv_entry(ppc_flags.AT_L1D_CACHESIZE, l1d_cachesize)
1328 auxv_entry(ppc_flags.AT_L1D_CACHEGEOMETRY, l1d_cachegeometry)
1329 auxv_entry(ppc_flags.AT_L2_CACHESIZE, l2_cachesize)
1330 auxv_entry(ppc_flags.AT_L2_CACHEGEOMETRY, l2_cachegeometry)
1331 auxv_entry(ppc_flags.AT_L3_CACHESIZE, l3_cachesize)
1332 auxv_entry(ppc_flags.AT_L3_CACHEGEOMETRY, l3_cachegeometry)
1333
1334 # TODO: latest linux kernel has AT_MINSIGSTKSZ,
1335 # ignoring for now since it's not included in Debian 10's kernel.
1336
1337 auxv_entry(ppc_flags.AT_HWCAP, hwcap)
1338 auxv_entry(ppc_flags.AT_PAGESZ, MMAP_PAGE_SIZE)
1339 auxv_entry(ppc_flags.AT_CLKTCK, CLOCKS_PER_SEC)
1340 auxv_entry(ppc_flags.AT_PHDR, 0) # FIXME: use correct value
1341 auxv_entry(ppc_flags.AT_PHENT, 56) # sizeof(elf_phdr)
1342 auxv_entry(ppc_flags.AT_PHNUM, phnum)
1343 auxv_entry(ppc_flags.AT_BASE, 0) # FIXME: use interpreter address
1344 flags = 0
1345 # FIXME: if using emulated binfmt_misc bit-or in AT_FLAGS_PRESERVE_ARGV0
1346 auxv_entry(ppc_flags.AT_FLAGS, flags)
1347 auxv_entry(ppc_flags.AT_ENTRY, elf_file.header['e_entry'])
1348 auxv_entry(ppc_flags.AT_UID, os.getuid())
1349 auxv_entry(ppc_flags.AT_EUID, os.geteuid())
1350 auxv_entry(ppc_flags.AT_GID, os.getgid())
1351 auxv_entry(ppc_flags.AT_EGID, os.getegid())
1352 auxv_entry(ppc_flags.AT_SECURE, bool(was_setuid_like))
1353 auxv_entry(ppc_flags.AT_RANDOM, rand_byte16)
1354 auxv_entry(ppc_flags.AT_HWCAP2, hwcap2)
1355 auxv_entry(ppc_flags.AT_NULL, 0) # final auxv entry
1356
1357 total_sz = ((len(arg_offsets) + 1) + (len(env_offsets) + 1) + 1) * 8
1358 total_sz += 16 * len(auxv_entries)
1359 stack_top -= total_sz
1360 stack_top -= stack_top % 16 # align stack
1361 write_addr = stack_top
1362
1363 def write8(v):
1364 nonlocal write_addr
1365 mem.get_ctypes(write_addr, 8, True)[:] = v.to_bytes(8, 'little')
1366 write_addr += 8
1367
1368 argc = len(arg_offsets)
1369 write8(argc)
1370 argv = write_addr
1371 for arg_offset in arg_offsets:
1372 write8(arg_offset + args_bytes_addr)
1373 write8(0)
1374 envp = write_addr
1375 for env_offset in env_offsets:
1376 write8(env_offset + env_bytes_addr)
1377 write8(0)
1378 auxv = write_addr
1379 for a_type, a_un in auxv_entries:
1380 write8(a_type)
1381 write8(a_un)
1382
1383 gprs = {}
1384
1385 if heap_start > 0:
1386 mem.heap_range = range(heap_start, heap_start) # empty heap to start
1387
1388 # TODO: dynamically-linked binaries need to use the entry-point of ld.so
1389 pc = elf_file.header['e_entry']
1390 gprs[1] = stack_top
1391 gprs[3] = 0 # argc -- apparently just zeroed by linux
1392 gprs[4] = 0 # argv -- apparently just zeroed by linux
1393 gprs[5] = 0 # envp -- apparently just zeroed by linux
1394 gprs[6] = 0 # auxv -- apparently just zeroed by linux
1395 gprs[7] = 0 # termination function pointer
1396 gprs[12] = pc
1397 fpscr = 0
1398 return LoadedELF(elf_file, pc, gprs, fpscr)