xref: /xnu-10063.101.15/tools/lldbmacros/core/kernelcore.py (revision 94d3b452840153a99b38a3a9659680b2a006908e)
1
2""" Please make sure you read the README COMPLETELY BEFORE reading anything below.
3    It is very critical that you read coding guidelines in Section E in README file.
4"""
5from .cvalue import value
6from . import collections as ccol
7from .caching import (
8    LazyTarget,
9    dyn_cached_property,
10    cache_dynamically,
11    cache_statically,
12)
13from utils import *
14
15import lldb
16
17class UnsupportedArchitectureError(RuntimeError):
18    def __init__(self, arch, msg="Unsupported architecture"):
19        self._arch = arch
20        self._msg = msg
21        super().__init__(msg)
22
23    def __str__(self):
24        return '%s: %s' % (self._arch, self._msg)
25
26
27def IterateTAILQ_HEAD(headval, element_name, list_prefix=''):
28    """ iterate over a TAILQ_HEAD in kernel. refer to bsd/sys/queue.h
29        params:
30            headval      - value : value object representing the head of the list
31            element_name - str   :  string name of the field which holds the list links.
32            list_prefix  - str   : use 's' here to iterate STAILQ_HEAD instead
33        returns:
34            A generator does not return. It is used for iterating.
35            value : an object that is of type as headval->tqh_first. Always a pointer object
36        example usage:
37          list_head = kern.GetGlobalVariable('mountlist')
38          for entryobj in IterateTAILQ_HEAD(list_head, 'mnt_list'):
39            print GetEntrySummary(entryobj)
40    """
41
42    next_path = ".{}.{}tqe_next".format(element_name, list_prefix)
43    head = headval.GetSBValue()
44
45    return (value(e.AddressOf()) for e in ccol.iter_linked_list(
46        head.Dereference() if head.TypeIsPointerType() else head,
47        next_path,
48        list_prefix + 'tqh_first',
49    ))
50
51
52def IterateLinkedList(headval, field_name):
53    """ iterate over a linked list.
54        This is equivalent to elt = headval; while(elt) { do_work(elt); elt = elt-><field_name>; }
55        params:
56            headval - value : value object representing element in the list.
57            field_name - str       : name of field that holds pointer to next element
58        returns: Nothing. This is used as iterable
59        example usage:
60            first_zone = kern.GetGlobalVariable('first_zone')
61            for zone in IterateLinkedList(first_zone, 'next_zone'):
62                print GetZoneSummary(zone)
63    """
64
65    head = headval.GetSBValue()
66
67    return (value(e.AddressOf()) for e in ccol.iter_linked_list(head, field_name))
68
69
70def IterateListEntry(headval, field_name, list_prefix=''):
71    """ iterate over a list as defined with LIST_HEAD in bsd/sys/queue.h
72        params:
73            headval      - value : Value object for lh_first
74            field_name   - str   : Name of the field in next element's structure
75            list_prefix  - str   : use 's' here to iterate SLIST_HEAD instead
76        returns:
77            A generator does not return. It is used for iterating
78            value  : an object thats of type (element_type) head->le_next. Always a pointer object
79        example usage:
80            headp = kern.globals.initproc.p_children
81            for pp in IterateListEntry(headp, 'p_sibling'):
82                print GetProcInfo(pp)
83    """
84
85    next_path = ".{}.{}le_next".format(field_name, list_prefix)
86    head = headval.GetSBValue()
87
88    return (value(e.AddressOf()) for e in ccol.iter_linked_list(
89        head.Dereference() if head.TypeIsPointerType() else head,
90        next_path,
91        list_prefix + 'lh_first',
92    ))
93
94
95def IterateLinkageChain(queue_head, element_type, field_name):
96    """ Iterate over a Linkage Chain queue in kernel of type queue_head_t. (osfmk/kern/queue.h method 1)
97        This is equivalent to the qe_foreach_element() macro
98        params:
99            queue_head   - value       : Value object for queue_head.
100            element_type - lldb.SBType : pointer type of the element which contains the queue_chain_t. Typically its structs like thread, task etc..
101                         - str         : OR a string describing the type. ex. 'task *'
102            field_name   - str         : Name of the field (in element) which holds a queue_chain_t
103        returns:
104            A generator does not return. It is used for iterating.
105            value  : An object thats of type (element_type). Always a pointer object
106        example usage:
107            coalq = kern.GetGlobalVariable('coalitions_q')
108            for coal in IterateLinkageChain(coalq, 'struct coalition *', 'coalitions'):
109                print GetCoalitionInfo(coal)
110    """
111
112    if isinstance(element_type, str):
113        element_type = gettype(element_type)
114
115    head = queue_head.GetSBValue()
116
117    return (value(e.AddressOf()) for e in ccol.iter_queue_entries(
118        head.Dereference() if head.TypeIsPointerType() else head,
119        element_type.GetPointeeType(),
120        field_name,
121    ))
122
123
124def IterateCircleQueue(queue_head, element_type, field_name):
125    """ iterate over a circle queue in kernel of type circle_queue_head_t. refer to osfmk/kern/circle_queue.h
126        params:
127            queue_head    - lldb.SBValue : Value object for queue_head.
128            element_type  - lldb.SBType : a type of the element 'next' points to. Typically its structs like thread, task etc..
129            field_name    - str : name of the field in target struct.
130        returns:
131            A generator does not return. It is used for iterating.
132            SBValue  : an object thats of type (element_type) queue_head->next. Always a pointer object
133    """
134
135    if isinstance(element_type, str):
136        element_type = gettype(element_type)
137
138    head = queue_head.GetSBValue()
139
140    return (value(e.AddressOf()) for e in ccol.iter_circle_queue(
141        head.Dereference() if head.TypeIsPointerType() else head,
142        element_type,
143        field_name,
144    ))
145
146
147def IterateQueue(queue_head, element_ptr_type, element_field_name, backwards=False, unpack_ptr_fn=None):
148    """ Iterate over an Element Chain queue in kernel of type queue_head_t. (osfmk/kern/queue.h method 2)
149        params:
150            queue_head         - value : Value object for queue_head.
151            element_ptr_type   - lldb.SBType : a pointer type of the element 'next' points to. Typically its structs like thread, task etc..
152                               - str         : OR a string describing the type. ex. 'task *'
153            element_field_name - str : name of the field in target struct.
154            backwards          - backwards : traverse the queue backwards
155            unpack_ptr_fn      - function : a function ptr of signature def unpack_ptr(long v) which returns long.
156        returns:
157            A generator does not return. It is used for iterating.
158            value  : an object thats of type (element_type) queue_head->next. Always a pointer object
159        example usage:
160            for page_meta in IterateQueue(kern.globals.first_zone.pages.all_free, 'struct zone_page_metadata *', 'pages'):
161                print page_meta
162    """
163
164    if isinstance(element_ptr_type, str):
165        element_ptr_type = gettype(element_ptr_type)
166
167    head = queue_head.GetSBValue()
168
169    return (value(e.AddressOf()) for e in ccol.iter_queue(
170        head.Dereference() if head.TypeIsPointerType() else head,
171        element_ptr_type.GetPointeeType(),
172        element_field_name,
173        backwards=backwards,
174        unpack=unpack_ptr_fn,
175    ))
176
177
178def IterateRBTreeEntry(rootelt, field_name):
179    """ iterate over a rbtree as defined with RB_HEAD in libkern/tree.h
180            rootelt      - value : Value object for rbh_root
181            field_name   - str   : Name of the field in link element's structure
182        returns:
183            A generator does not return. It is used for iterating
184            value  : an object thats of type (element_type) head->sle_next. Always a pointer object
185    """
186
187    return (value(e.AddressOf()) for e in ccol.iter_RB_HEAD(rootelt.GetSBValue(), field_name))
188
189
190def IterateSchedPriorityQueue(root, element_type, field_name):
191    """ iterate over a priority queue as defined with struct priority_queue from osfmk/kern/priority_queue.h
192            root         - value : Value object for the priority queue
193            element_type - str   : Type of the link element
194            field_name   - str   : Name of the field in link element's structure
195        returns:
196            A generator does not return. It is used for iterating
197            value  : an object thats of type (element_type). Always a pointer object
198    """
199
200    if isinstance(element_type, str):
201        element_type = gettype(element_type)
202
203    root = root.GetSBValue()
204
205    return (value(e.AddressOf()) for e in ccol.iter_priority_queue(
206        root.Dereference() if root.TypeIsPointerType() else root,
207        element_type,
208        field_name,
209    ))
210
211
212def IterateMPSCQueue(root, element_type, field_name):
213    """ iterate over an MPSC queue as defined with struct mpsc_queue_head from osfmk/kern/mpsc_queue.h
214            root         - value : Value object for the mpsc queue
215            element_type - str   : Type of the link element
216            field_name   - str   : Name of the field in link element's structure
217        returns:
218            A generator does not return. It is used for iterating
219            value  : an object thats of type (element_type). Always a pointer object
220    """
221    if isinstance(element_type, str):
222        element_type = gettype(element_type)
223
224    return (value(e.AddressOf()) for e in ccol.iter_mpsc_queue(
225        root.GetSBValue(), element_type, field_name
226    ))
227
228
229class KernelTarget(object):
230    """ A common kernel object that provides access to kernel objects and information.
231        The class holds global lists for  task, terminated_tasks, procs, zones, zombroc etc.
232        It also provides a way to symbolicate an address or create a value from an address.
233    """
234    def __init__(self, debugger):
235        """ Initialize the kernel debugging environment.
236            Target properties like architecture and connectedness are lazy-evaluted.
237        """
238
239        self.symbolicator = None
240
241        class _GlobalVariableFind(object):
242            def __init__(self, kern):
243                self._xnu_kernobj_12obscure12 = kern
244            def __getattr__(self, name):
245                v = self._xnu_kernobj_12obscure12.GetGlobalVariable(name)
246                if not v.GetSBValue().IsValid():
247                    # Python 2 swallows all exceptions in hasattr(). That makes it work
248                    # even when global variable is not found. Python 3 has fixed the behavior
249                    # and we can raise only AttributeError here to keep original behavior.
250                    raise AttributeError('No such global variable by name: %s '%str(name))
251                return v
252        self.globals = _GlobalVariableFind(self)
253
254    def _GetSymbolicator(self):
255        """ Internal function: To initialize the symbolication from lldb.utils
256        """
257        if not self.symbolicator is None:
258            return self.symbolicator
259
260        from lldb.utils import symbolication
261        symbolicator = symbolication.Symbolicator()
262        symbolicator.target = LazyTarget.GetTarget()
263        self.symbolicator = symbolicator
264        return self.symbolicator
265
266    def Symbolicate(self, addr):
267        """ simple method to get name of function/variable from an address. this is equivalent of gdb 'output /a 0xaddress'
268            params:
269                addr - int : typically hex value like 0xffffff80002c0df0
270            returns:
271                str - '' if no symbol found else the symbol name.
272            Note: this function only finds the first symbol. If you expect multiple symbol conflict please use SymbolicateFromAddress()
273        """
274        ret_str = ''
275        syms = self.SymbolicateFromAddress(addr)
276        if len(syms) > 0:
277            ret_str +=syms[0].GetName()
278        return ret_str
279
280    def SymbolicateFromAddress(self, addr, fullSymbol=False):
281        """ symbolicates any given address based on modules loaded in the target.
282            params:
283                addr - int : typically hex value like 0xffffff80002c0df0
284            returns:
285                [] of SBSymbol: In case we don't find anything than empty array is returned.
286                      Note: a type of symbol can be figured out by gettype() function of SBSymbol.
287            example usage:
288                syms = kern.Symbolicate(0xffffff80002c0df0)
289                for s in syms:
290                  if s.GetType() == lldb.eSymbolTypeCode:
291                    print "Function", s.GetName()
292                  if s.GetType() == lldb.eSymbolTypeData:
293                    print "Variable", s.GetName()
294        """
295        if type(int(1)) != type(addr):
296            if str(addr).strip().find("0x") == 0 :
297                addr = int(addr, 16)
298            else:
299                addr = int(addr)
300        addr = self.StripKernelPAC(addr)
301        ret_array = []
302        symbolicator = self._GetSymbolicator()
303        syms = symbolicator.symbolicate(addr)
304        if not syms:
305            return ret_array
306        for s in syms:
307            if fullSymbol:
308                ret_array.append(s)
309            else:
310                ret_array.append(s.get_symbol_context().symbol)
311        return ret_array
312
313    def IsDebuggerConnected(self):
314        proc_state = LazyTarget.GetProcess().state
315        if proc_state == lldb.eStateInvalid : return False
316        if proc_state in [lldb.eStateStopped, lldb.eStateSuspended] : return True
317
318    @staticmethod
319    @cache_statically
320    def GetGlobalVariable(name, target=None):
321        """ Get the value object representation for a kernel global variable
322            params:
323              name : str - name of the variable. ex. version
324            returns: value - python object representing global variable.
325            raises : Exception in case the variable is not found.
326        """
327
328        return value(target.FindGlobalVariables(name, 1).GetValueAtIndex(0))
329
330    def PERCPU_BASE(self, cpu):
331        """ Get the PERCPU base for the given cpu number
332            params:
333              cpu  : int - the cpu# for this variable
334            returns: int - the base for PERCPU for this cpu index
335        """
336        if self.arch == 'x86_64':
337            return unsigned(self.globals.cpu_data_ptr[cpu].cpu_pcpu_base)
338        elif self.arch.startswith('arm'):
339            data_entries = self.GetGlobalVariable('CpuDataEntries')
340            BootCpuData = addressof(self.GetGlobalVariable('percpu_slot_cpu_data'))
341            return unsigned(data_entries[cpu].cpu_data_vaddr) - unsigned(BootCpuData)
342
343    def PERCPU_GET(self, name, cpu):
344        """ Get the value object representation for a kernel percpu global variable
345            params:
346              name : str - name of the variable. ex. version
347              cpu  : int - the cpu# for this variable
348            returns: value - python object representing global variable.
349            raises : Exception in case the variable is not found.
350        """
351        var = addressof(self.GetGlobalVariable('percpu_slot_' + name))
352        addr = unsigned(var) + self.PERCPU_BASE(cpu)
353        return dereference(self.GetValueFromAddress(addr, var))
354
355    def GetLoadAddressForSymbol(self, name):
356        """ Get the load address of a symbol in the kernel.
357            params:
358              name : str - name of the symbol to lookup
359            returns: int - the load address as an integer. Use GetValueFromAddress to cast to a value.
360            raises : LookupError - if the symbol is not found.
361        """
362        name = str(name)
363        target = LazyTarget.GetTarget()
364        syms_arr = target.FindSymbols(name)
365        if syms_arr.IsValid() and len(syms_arr) > 0:
366            symbol = syms_arr[0].GetSymbol()
367            if symbol.IsValid():
368                return int(symbol.GetStartAddress().GetLoadAddress(target))
369
370        raise LookupError("Symbol not found: " + name)
371
372    def GetValueFromAddress(self, addr, type_str = 'void *'):
373        """ convert a address to value
374            params:
375                addr - int : typically hex value like 0xffffff80008dc390
376                type_str - str: type to cast to. Default type will be void *
377            returns:
378                value : a value object which has address as addr and type is type_str
379        """
380        obj = value(self.globals.version.GetSBValue().CreateValueFromExpression(None,'(void *)'+str(addr)))
381        obj = cast(obj, type_str)
382        return obj
383
384    def CreateTypedPointerFromAddress(self, addr, type_str = "char"):
385        """ convert a address to pointer value
386
387            Note: This is obsolete and here as a temporary solution
388                  for people to migrate to using references instead.
389
390            params:
391                addr - int : typically hex value like 0xffffff80008dc390
392                type_str - str: type to cast to, must not be a pointer type.
393            returns:
394                value : a value object which has address as addr
395                        and type is `type_str *`
396        """
397
398        target = LazyTarget.GetTarget()
399        sbv    = target.xCreateValueFromAddress(None, addr, gettype(type_str))
400        return value(sbv.AddressOf())
401
402
403    def GetValueAsType(self, v, t):
404        """ Retrieves a global variable 'v' of type 't' wrapped in a vue object.
405            If 'v' is an address, creates a vue object of the appropriate type.
406            If 'v' is a name, looks for the global variable and asserts its type.
407            Throws:
408                NameError - If 'v' cannot be found
409                TypeError - If 'v' is of the wrong type
410        """
411        if islong(v):
412            return self.GetValueFromAddress(v, t)
413        else:
414            var = LazyTarget.GetTarget().FindGlobalVariables(v, 1)[0]
415            if not var:
416                raise NameError("Failed to find global variable '{0}'".format(v))
417            if var.GetTypeName() != t:
418                raise TypeError("{0} must be of type '{1}', not '{2}'".format(v, t, var.GetTypeName()))
419            return value(var)
420
421    def _GetIterator(self, iter_head_name, next_element_name='next', iter_head_type=None):
422        """ returns an iterator for a collection in kernel memory.
423            params:
424                iter_head_name - str : name of queue_head or list head variable.
425                next_element_name - str : name of the element that leads to next element.
426                                          for ex. in struct zone list 'next_zone' is the linking element.
427            returns:
428                iterable : typically used in conjunction with "for varname in iterable:"
429        """
430        head_element = self.GetGlobalVariable(iter_head_name)
431        return head_element.GetSBValue().linked_list_iter(next_element_name)
432
433    def TruncPage(self, addr):
434        return (addr & ~(unsigned(self.GetGlobalVariable("page_size")) - 1))
435
436    def RoundPage(self, addr):
437        return trunc_page(addr + unsigned(self.GetGlobalVariable("page_size")) - 1)
438
439    def StraddlesPage(self, addr, size):
440        if size > unsigned(self.GetGlobalVariable("page_size")):
441            return True
442        val = ((addr + size) & (unsigned(self.GetGlobalVariable("page_size"))-1))
443        return (val < size and val > 0)
444
445    def StripUserPAC(self, addr):
446        if self.arch != 'arm64e':
447            return addr
448        T0Sz = self.GetGlobalVariable('gT0Sz')
449        return StripPAC(addr, T0Sz)
450
451    def StripKernelPAC(self, addr):
452        if self.arch != 'arm64e':
453            return addr
454        T1Sz = self.GetGlobalVariable('gT1Sz')
455        return StripPAC(addr, T1Sz)
456
457    PAGE_PROTECTION_TYPE_NONE = 0
458    PAGE_PROTECTION_TYPE_PPL = 1
459    PAGE_PROTECTION_TYPE_SPTM = 2
460
461    def PhysToKVARM64(self, addr):
462        if self.globals.page_protection_type <= self.PAGE_PROTECTION_TYPE_PPL:
463            ptov_table = self.globals.ptov_table
464            for i in range(0, self.globals.ptov_index):
465                if (addr >= int(unsigned(ptov_table[i].pa))) and (addr < (int(unsigned(ptov_table[i].pa)) + int(unsigned(ptov_table[i].len)))):
466                    return (addr - int(unsigned(ptov_table[i].pa)) + int(unsigned(ptov_table[i].va)))
467        else:
468            papt_table = self.globals.libsptm_papt_ranges
469            page_size = self.globals.page_size
470            for i in range(0, self.globals.libsptm_n_papt_ranges):
471                if (addr >= int(unsigned(papt_table[i].paddr_start))) and (addr < (int(unsigned(papt_table[i].paddr_start)) + int(unsigned(papt_table[i].num_mappings) * page_size))):
472                    return (addr - int(unsigned(papt_table[i].paddr_start)) + int(unsigned(papt_table[i].papt_start)))
473            raise ValueError("PA {:#x} not found in physical region lookup table".format(addr))
474        return (addr - unsigned(self.globals.gPhysBase) + unsigned(self.globals.gVirtBase))
475
476    def PhysToKernelVirt(self, addr):
477        if self.arch == 'x86_64':
478            return (addr + unsigned(self.GetGlobalVariable('physmap_base')))
479        elif self.arch.startswith('arm64'):
480            return self.PhysToKVARM64(addr)
481        elif self.arch.startswith('arm'):
482            return (addr - unsigned(self.GetGlobalVariable("gPhysBase")) + unsigned(self.GetGlobalVariable("gVirtBase")))
483        else:
484            raise ValueError("PhysToVirt does not support {0}".format(self.arch))
485
486    @cache_statically
487    def GetUsecDivisor(self, target=None):
488        if self.arch == 'x86_64':
489            return 1000
490
491        rtclockdata_addr = self.GetLoadAddressForSymbol('RTClockData')
492        rtc = self.GetValueFromAddress(rtclockdata_addr, 'struct _rtclock_data_ *')
493        return unsigned(rtc.rtc_usec_divisor)
494
495    def GetNanotimeFromAbstime(self, abstime):
496        """ convert absolute time (which is in MATUs) to nano seconds.
497            Since based on architecture the conversion may differ.
498            params:
499                abstime - int absolute time as shown by mach_absolute_time
500            returns:
501                int - nanosecs of time
502        """
503        return (abstime * 1000) // self.GetUsecDivisor()
504
505    @property
506    @cache_statically
507    def zones(self, target=None):
508        za = target.chkFindFirstGlobalVariable('zone_array')
509        zs = target.chkFindFirstGlobalVariable('zone_security_array')
510        n  = target.chkFindFirstGlobalVariable('num_zones').xGetValueAsInteger()
511
512        iter_za = za.chkGetChildAtIndex(0).xIterSiblings(0, n)
513        iter_zs = zs.chkGetChildAtIndex(0).xIterSiblings(0, n)
514
515        return [
516            (value(next(iter_za).AddressOf()), value(next(iter_zs).AddressOf()))
517            for i in range(n)
518        ]
519
520    @property
521    def threads(self):
522        target = LazyTarget.GetTarget()
523
524        return (value(t.AddressOf()) for t in ccol.iter_queue(
525            target.chkFindFirstGlobalVariable('threads'),
526            gettype('thread'),
527            'threads',
528        ))
529
530    @dyn_cached_property
531    def tasks(self, target=None):
532        return [value(t.AddressOf()) for t in ccol.iter_queue(
533            target.chkFindFirstGlobalVariable('tasks'),
534            gettype('task'),
535            'tasks',
536        )]
537
538    @property
539    def coalitions(self):
540        target = LazyTarget.GetTarget()
541
542        return (value(coal.AddressOf()) for coal in ccol.SMRHash(
543            target.chkFindFirstGlobalVariable('coalition_hash'),
544            target.chkFindFirstGlobalVariable('coal_hash_traits'),
545        ))
546
547    @property
548    def thread_groups(self):
549        target = LazyTarget.GetTarget()
550
551        return (value(tg.AddressOf()) for tg in ccol.iter_queue_entries(
552            target.chkFindFirstGlobalVariable('tg_queue'),
553            gettype('thread_group'),
554            'tg_queue_chain',
555        ))
556
557    @property
558    def terminated_tasks(self):
559        target = LazyTarget.GetTarget()
560
561        return (value(t.AddressOf()) for t in ccol.iter_queue(
562            target.chkFindFirstGlobalVariable('terminated_tasks'),
563            gettype('task'),
564            'tasks',
565        ))
566
567    @property
568    def terminated_threads(self):
569        target = LazyTarget.GetTarget()
570
571        return (value(t.AddressOf()) for t in ccol.iter_queue(
572            target.chkFindFirstGlobalVariable('terminated_threads'),
573            gettype('thread'),
574            'threads',
575        ))
576
577    @property
578    def procs(self):
579        target = LazyTarget.GetTarget()
580
581        return (value(p.AddressOf()) for p in ccol.iter_LIST_HEAD(
582            target.chkFindFirstGlobalVariable('allproc'),
583            'p_list',
584        ))
585
586    @property
587    def interrupt_stats(self):
588        target = LazyTarget.GetTarget()
589
590        return (value(stat.AddressOf()) for stat in ccol.iter_queue(
591            target.chkFindFirstGlobalVariable('gInterruptAccountingDataList'),
592            gettype('IOInterruptAccountingData'),
593            'chain',
594        ))
595
596    @property
597    def zombprocs(self):
598        target = LazyTarget.GetTarget()
599
600        return (value(p.AddressOf()) for p in ccol.iter_LIST_HEAD(
601            target.chkFindFirstGlobalVariable('zombproc'),
602            'p_list',
603        ))
604
605    @property
606    def version(self):
607        return str(self.globals.version)
608
609    @property
610    def arch(self):
611        return LazyTarget.GetTarget().triple.split('-', 1)[0]
612
613    @property
614    def ptrsize(self):
615        return LazyTarget.GetTarget().GetAddressByteSize()
616
617    @property
618    def VM_MIN_KERNEL_ADDRESS(self):
619        if self.arch == 'x86_64':
620            return 0xffffff8000000000
621        else:
622            return 0xffffffe00000000
623
624    @property
625    def VM_MIN_KERNEL_AND_KEXT_ADDRESS(self):
626        if self.arch == 'x86_64':
627            return 0xffffff8000000000 - 0x80000000
628        else:
629            return 0xffffffe00000000
630