mirror of
https://github.com/google/pebble.git
synced 2025-05-21 02:45:00 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
152
tools/gdb_scripts/gdb_heap.py
Normal file
152
tools/gdb_scripts/gdb_heap.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError:
|
||||
raise Exception("This file is a GDB module.\n"
|
||||
"It is not intended to be run outside of GDB.\n"
|
||||
"Hint: to load a script in GDB, use `source this_file.py`")
|
||||
|
||||
import gdb_utils
|
||||
import itertools
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class CorruptionCode(object):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __repr__(self):
|
||||
return 'CorruptionCode("{}")'.format(self.message)
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class HeapException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HeapBlock(namedtuple('HeapBlock', 'info data size allocated corruption_code')):
|
||||
def __new__(cls, info, data, size, allocated=False, corruption_code=None):
|
||||
return cls._make([info, data, size, allocated, corruption_code])
|
||||
|
||||
def cast(self, obj_type, clone=False):
|
||||
if clone:
|
||||
return HeapBlock(self.info, self.cast(obj_type), self.size,
|
||||
self.allocated, self.corruption_code)
|
||||
else:
|
||||
if obj_type:
|
||||
return self.data.cast(gdb.lookup_type(obj_type).pointer())
|
||||
else:
|
||||
return self.data
|
||||
|
||||
|
||||
class Heap(object):
|
||||
BlockSizeZero = CorruptionCode('Block size is zero')
|
||||
PrevSizeZero = CorruptionCode('Prev size is zero')
|
||||
WrongPrevSize = CorruptionCode('PrevSize is less than the size of the last block')
|
||||
|
||||
def __init__(self, heap, show_progress=False):
|
||||
self.heap_ptr = gdb.parse_and_eval(heap)
|
||||
|
||||
if self.heap_ptr.type != gdb.lookup_type("Heap").pointer():
|
||||
raise HeapException("Error: argument must be of type (Heap *)")
|
||||
|
||||
self.alignment_type = gdb.lookup_type("Alignment_t")
|
||||
self.alignment_size = int(self.alignment_type.sizeof)
|
||||
|
||||
self.start = self.heap_ptr["begin"]
|
||||
self.end = self.heap_ptr["end"]
|
||||
self.high_water_mark = self.heap_ptr["high_water_mark"]
|
||||
|
||||
self.heap_info_type = gdb.lookup_type("HeapInfo_t")
|
||||
self.size = gdb_utils.Address(str(self.end)) - gdb_utils.Address(str(self.start))
|
||||
|
||||
self.malloc_instrumentation = "pc" in self.heap_info_type.keys()
|
||||
self.corrupted = False
|
||||
self.show_progress = show_progress
|
||||
|
||||
self._process_heap()
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.block_list)
|
||||
|
||||
def _process_heap(self):
|
||||
self.block_list = []
|
||||
segment_ptr = self.start
|
||||
block_size = 0
|
||||
|
||||
loop_count = itertools.count()
|
||||
while segment_ptr < self.end:
|
||||
if self.show_progress:
|
||||
gdb.write('.')
|
||||
gdb.flush()
|
||||
if next(loop_count) > 10000 or self.corrupted:
|
||||
print "ERROR: heap corrupted"
|
||||
return
|
||||
|
||||
block_prev_size = int(segment_ptr["PrevSize"])
|
||||
block_size_prev = block_size
|
||||
is_allocated = bool(segment_ptr["is_allocated"])
|
||||
block_size = int(segment_ptr["Size"])
|
||||
|
||||
size_bytes = block_size * self.alignment_size
|
||||
|
||||
corruption_code = None
|
||||
if block_size <= 0:
|
||||
corruption_code = self.BlockSizeZero
|
||||
elif segment_ptr > self.start:
|
||||
if block_prev_size == 0:
|
||||
corruption_code = self.PrevSizeZero
|
||||
elif block_prev_size != block_size_prev:
|
||||
corruption_code = self.WrongPrevSize
|
||||
|
||||
if corruption_code:
|
||||
self.corrupted = True
|
||||
|
||||
block = HeapBlock(segment_ptr, segment_ptr["Data"].address,
|
||||
size_bytes, is_allocated, corruption_code)
|
||||
self.block_list.append(block)
|
||||
|
||||
segment_ptr = (segment_ptr.cast(self.alignment_type.pointer()) +
|
||||
block_size).cast(self.heap_info_type.pointer())
|
||||
|
||||
def block_size(self, bytes):
|
||||
offset = (int(gdb.lookup_type("HeapInfo_t").sizeof) -
|
||||
int(gdb.lookup_type("AlignmentStruct_t").sizeof))
|
||||
offset_blocks = offset / self.alignment_size
|
||||
|
||||
blocks = (bytes + self.alignment_size - 1) // self.alignment_size
|
||||
common_size = blocks * self.alignment_size + offset
|
||||
|
||||
# Heap blocks with less than one block's worth of space between it
|
||||
# and the next will grow to take up that space.
|
||||
return frozenset(common_size + x * self.alignment_size for x in xrange(offset_blocks+1))
|
||||
|
||||
def object_size(self, obj_type):
|
||||
bytes = int(gdb.lookup_type(obj_type).sizeof)
|
||||
return self.block_size(bytes)
|
||||
|
||||
def objects_of(self, obj_type):
|
||||
sizes = self.object_size(obj_type)
|
||||
return [block for block in self if block.size in sizes]
|
||||
|
||||
def allocated_blocks(self):
|
||||
return [block for block in self if block.allocated]
|
||||
|
||||
def free_blocks(self):
|
||||
return [block for block in self if not block.allocated]
|
521
tools/gdb_scripts/gdb_parser.py
Normal file
521
tools/gdb_scripts/gdb_parser.py
Normal file
|
@ -0,0 +1,521 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError:
|
||||
raise Exception('This file is a GDB module.\n'
|
||||
'It is not intended to be run outside of GDB.\n'
|
||||
'Hint: to load a script in GDB, use `source this_file.py`')
|
||||
|
||||
|
||||
import logging
|
||||
import string
|
||||
import types
|
||||
import datetime
|
||||
|
||||
from collections import namedtuple, defaultdict, OrderedDict
|
||||
from gdb_tintin import FreeRTOSMutex, Tasks, LinkedList
|
||||
from gdb_symbols import get_static_variable, get_static_function
|
||||
from gdb_tintin_metadata import TintinMetadata
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
recognizers = {}
|
||||
|
||||
_Recognizer = namedtuple('Recognizer', 'impl depends_on')
|
||||
|
||||
|
||||
def register_recognizer(name, fn, dependency=None):
|
||||
""" Registers a recognizer.
|
||||
|
||||
Recognizers are run against each block in the heap.
|
||||
They consist of a name, a dependency, and an implementation.
|
||||
The implementation consumes a block, a heap object, and a results dictionary and returns
|
||||
either a casted block or None.
|
||||
|
||||
See Recognizer for auto-registering declarative classes.
|
||||
"""
|
||||
recognizers[name] = _Recognizer(fn, dependency)
|
||||
|
||||
|
||||
def parse_heap(heap, recognizer_subset=None):
|
||||
results = {
|
||||
'Free': heap.free_blocks()
|
||||
}
|
||||
|
||||
heap_recognizers = recognizers
|
||||
if recognizer_subset:
|
||||
heap_recognizers = {name: recognizers[name] for name in recognizer_subset}
|
||||
|
||||
ordered_recognizers, hidden = _order_recognizers(heap_recognizers)
|
||||
logger.info('Running: {}'.format(', '.join(ordered_recognizers.keys())))
|
||||
for name, recognizer in ordered_recognizers.iteritems():
|
||||
try:
|
||||
results[name] = filter(None, (recognizer.impl(block, heap, results) for
|
||||
block in heap.allocated_blocks()))
|
||||
except:
|
||||
print name + " hit an exception. Skipping"
|
||||
|
||||
for dependency in hidden:
|
||||
del results[dependency]
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _order_recognizers(recognizer_subset):
|
||||
recognizer_subset = recognizer_subset.copy()
|
||||
ordered = OrderedDict()
|
||||
hidden = []
|
||||
last_length = -1
|
||||
|
||||
# Run until we stop adding recognizers to our ordered list
|
||||
while recognizer_subset and len(ordered) != last_length:
|
||||
last_length = len(ordered)
|
||||
|
||||
for name, recognizer in recognizer_subset.items():
|
||||
if not recognizer.depends_on or recognizer.depends_on in ordered:
|
||||
ordered[name] = recognizer
|
||||
del recognizer_subset[name]
|
||||
|
||||
# Add implicit dependencies
|
||||
for name, recognizer in recognizer_subset.iteritems():
|
||||
if recognizer.depends_on not in ordered:
|
||||
logger.info('Adding dependency: {}'.format(recognizer.depends_on))
|
||||
ordered[recognizer.depends_on] = recognizers[recognizer.depends_on]
|
||||
hidden.append(recognizer.depends_on)
|
||||
ordered[name] = recognizer
|
||||
|
||||
return [ordered, hidden]
|
||||
|
||||
|
||||
class RecognizerType(type):
|
||||
def __new__(cls, name, bases, dct):
|
||||
for key in dct:
|
||||
# Convert instance methods into class methods
|
||||
if isinstance(dct[key], types.FunctionType):
|
||||
dct[key] = classmethod(dct[key])
|
||||
|
||||
if dct.get('depends_on') == 'Heap':
|
||||
dct['depends_on'] = None
|
||||
dct['uses_heap'] = True
|
||||
else:
|
||||
dct['uses_heap'] = False
|
||||
|
||||
return type.__new__(cls, name, bases, dct)
|
||||
|
||||
def __init__(cls, name, bases, dct):
|
||||
if name != 'Recognizer':
|
||||
register_recognizer(name, cls, dct.get('depends_on'))
|
||||
|
||||
super(RecognizerType, cls).__init__(name, bases, dct)
|
||||
|
||||
def __call__(cls, block, heap, results):
|
||||
""" Returns either a casted block or None.
|
||||
|
||||
This allows the class object itself to implement the recognizer protocol.
|
||||
This means we can register the class as a recognizer without creating an instance of it.
|
||||
"""
|
||||
try:
|
||||
right_size = (block.size in heap.object_size(cls.type)) if cls.type else True
|
||||
except gdb.error:
|
||||
# Type doesn't exist
|
||||
return None
|
||||
|
||||
if cls.uses_heap:
|
||||
search_blocks = [r_block.data for r_block in heap]
|
||||
else:
|
||||
search_blocks = [r_block.data for r_block in results.get(cls.depends_on) or []]
|
||||
|
||||
if right_size and cls.is_type(block.cast(cls.type), search_blocks):
|
||||
return block.cast(cls.type, clone=True)
|
||||
else:
|
||||
return None
|
||||
|
||||
def __str__(cls):
|
||||
return cls.__name__
|
||||
|
||||
|
||||
class Recognizer(object):
|
||||
""" This is a declarative recognizer. It auto-registers with the recognizer dictionary.
|
||||
|
||||
Note that declarative recognizers are singletons that don't get instantiated, so
|
||||
__init__ is never called and any modifications to class members are persistent.
|
||||
This behavior holds for subclasses.
|
||||
|
||||
Example:
|
||||
>>> # A Recognizer's class name is its friendly name.
|
||||
... class Test(Recognizer):
|
||||
...
|
||||
... # Its (optional) type is the C struct it's searching for.
|
||||
... type = 'TestStruct'
|
||||
...
|
||||
... # Its (optional) dependency is the friendly name of the type
|
||||
... # whose results it depends on. Those results are passed to
|
||||
... # is_type as search_blocks.
|
||||
... depends_on = 'Heap'
|
||||
...
|
||||
... # is_type() is the comparison function. If type is not NULL,
|
||||
... # each block will be casted to 'type'. Note that self actually
|
||||
... # refers to the class.
|
||||
... def is_type(self, block, search_blocks):
|
||||
... return block['foo'] == 4
|
||||
"""
|
||||
__metaclass__ = RecognizerType
|
||||
|
||||
type = None
|
||||
depends_on = None
|
||||
|
||||
def is_type(self, block, search_blocks):
|
||||
return True
|
||||
|
||||
|
||||
# --- Recognizers --- #
|
||||
|
||||
|
||||
# --- Mutexes --- #
|
||||
class PebbleMutex(Recognizer):
|
||||
type = 'PebbleMutexCommon'
|
||||
|
||||
def is_type(self, pebble_mutex, search_blocks):
|
||||
mutex = FreeRTOSMutex(pebble_mutex['freertos_mutex'])
|
||||
return ((not mutex.locked()) == (pebble_mutex['lr'] == 0) and
|
||||
(0 <= mutex.num_waiters() <= 10))
|
||||
|
||||
|
||||
class LightMutex(Recognizer):
|
||||
type = 'LightMutex_t'
|
||||
depends_on = 'PebbleMutex'
|
||||
|
||||
def is_type(self, mutex, search_blocks):
|
||||
return mutex in [block['freertos_mutex'] for block in search_blocks]
|
||||
|
||||
|
||||
# --- Queues --- #
|
||||
class Queue(Recognizer):
|
||||
depends_on = 'Heap'
|
||||
|
||||
def is_type(self, data, search_blocks):
|
||||
queue_type = gdb.lookup_type('Queue_t')
|
||||
queue = data.cast(queue_type.pointer())
|
||||
queue_size = int(queue_type.sizeof)
|
||||
|
||||
storage_size = queue['uxLength'] * queue['uxItemSize']
|
||||
correct_head = (queue['pcHead'] >= data)
|
||||
correct_tail = (queue['pcTail'] == queue['pcHead'] + storage_size)
|
||||
return (queue['uxLength'] > 0 and queue['pcWriteTo'] != 0 and correct_head and correct_tail)
|
||||
|
||||
|
||||
class Semaphore(Recognizer):
|
||||
type = 'Queue_t'
|
||||
depends_on = 'Queue'
|
||||
|
||||
def is_type(self, semaphore, search_blocks):
|
||||
return semaphore['uxItemSize'] == 0 and semaphore in search_blocks
|
||||
|
||||
|
||||
# --- Data Logging --- #
|
||||
class DataLoggingSession(Recognizer):
|
||||
type = 'DataLoggingSession'
|
||||
|
||||
def is_type(self, dls, search_blocks):
|
||||
total_tasks = Tasks().total
|
||||
return 0 <= dls['task'] < total_tasks
|
||||
|
||||
|
||||
class ActiveDLS(Recognizer):
|
||||
type = 'DataLoggingActiveState'
|
||||
depends_on = 'DataLoggingSession'
|
||||
|
||||
def is_type(self, active_dls, search_blocks):
|
||||
return active_dls in [block['data'] for block in search_blocks]
|
||||
|
||||
|
||||
class DLSBuffer(Recognizer):
|
||||
depends_on = 'ActiveDLS'
|
||||
|
||||
def is_type(self, dls_buffer, search_blocks):
|
||||
return dls_buffer in [block['buffer_storage'] for block in search_blocks]
|
||||
|
||||
|
||||
# --- Tasks --- #
|
||||
class Task(Recognizer):
|
||||
type = 'TCB_t'
|
||||
|
||||
def is_type(self, task, search_blocks):
|
||||
return is_string(task['pcTaskName'])
|
||||
|
||||
|
||||
class TaskStack(Recognizer):
|
||||
depends_on = 'Task'
|
||||
|
||||
def is_type(self, stack, search_blocks):
|
||||
return stack in [block['pxStack'] for block in search_blocks]
|
||||
|
||||
|
||||
# --- String-related --- #
|
||||
def is_string(data):
|
||||
try:
|
||||
data = data.string().decode('utf-8')
|
||||
return len(data) > 2 and all(ord(codepoint) > 127 or
|
||||
codepoint in string.printable for codepoint in data)
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
|
||||
|
||||
class String(Recognizer):
|
||||
|
||||
def is_type(self, string, search_blocks):
|
||||
return is_string(string.cast(gdb.lookup_type('char').pointer()))
|
||||
|
||||
|
||||
class PFSFileNode(Recognizer):
|
||||
type = 'PFSFileChangedCallbackNode'
|
||||
depends_on = 'String'
|
||||
|
||||
def is_type(self, filenode, search_blocks):
|
||||
return filenode['name'] in search_blocks
|
||||
|
||||
|
||||
class SettingsFile(Recognizer):
|
||||
type = 'SettingsFile'
|
||||
depends_on = 'String'
|
||||
|
||||
def is_type(self, settings, search_blocks):
|
||||
try:
|
||||
timestamp = int(settings['last_modified'])
|
||||
date = datetime.datetime.fromtimestamp(timestamp)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
# If a user closes a settings file but forgets to free the
|
||||
# underlying memory the name will not be in search_blocks
|
||||
return (settings['max_used_space'] <= settings['max_space_total'] and
|
||||
settings['used_space'] <= settings['max_used_space'] and
|
||||
date.year > 2010 and date.year < 2038)
|
||||
|
||||
|
||||
# --- Analytics --- #
|
||||
class AnalyticsStopwatch(Recognizer):
|
||||
type = 'AnalyticsStopwatchNode'
|
||||
|
||||
def is_type(self, stopwatch, search_blocks):
|
||||
s_stopwatch_list = get_static_variable('s_stopwatch_list')
|
||||
stopwatch_list = LinkedList(gdb.parse_and_eval(s_stopwatch_list).dereference())
|
||||
return stopwatch in stopwatch_list
|
||||
|
||||
|
||||
class AnalyticsHeartbeatList(Recognizer):
|
||||
type = 'AnalyticsHeartbeatList'
|
||||
|
||||
def is_type(self, listnode, search_blocks):
|
||||
s_app_heartbeat_list = get_static_variable('s_app_heartbeat_list')
|
||||
heartbeat_list = LinkedList(gdb.parse_and_eval(s_app_heartbeat_list)['node'])
|
||||
return listnode in heartbeat_list
|
||||
|
||||
|
||||
class AnalyticsHeartbeat(Recognizer):
|
||||
depends_on = 'AnalyticsHeartbeatList'
|
||||
|
||||
def is_type(self, heartbeat, search_blocks):
|
||||
s_device_heartbeat = get_static_variable('s_device_heartbeat')
|
||||
device_heartbeat = gdb.parse_and_eval(s_device_heartbeat)
|
||||
return (heartbeat == device_heartbeat or
|
||||
heartbeat in [block['heartbeat'] for block in search_blocks])
|
||||
|
||||
|
||||
# --- Timers --- #
|
||||
class TaskTimer(Recognizer):
|
||||
type = 'TaskTimer'
|
||||
|
||||
def is_type(self, timer, search_blocks):
|
||||
timer_manager_ref = get_static_variable('s_task_timer_manager')
|
||||
timer_manager = gdb.parse_and_eval(timer_manager_ref)
|
||||
max_timer_id = int(timer_manager["next_id"]) - 1
|
||||
max_timer_id = int() - 1
|
||||
return 1 <= timer['id'] <= max_timer_id
|
||||
|
||||
|
||||
class EventedTimer(Recognizer):
|
||||
type = 'EventedTimer'
|
||||
|
||||
def is_type(self, timer, search_blocks):
|
||||
timer_manager_ref = get_static_variable('s_task_timer_manager')
|
||||
timer_manager = gdb.parse_and_eval(timer_manager_ref)
|
||||
max_timer_id = int(timer_manager["next_id"]) - 1
|
||||
total_tasks = Tasks().total
|
||||
|
||||
return (1 <= timer['sys_timer_id'] <= max_timer_id
|
||||
and 0 <= timer['target_task'] < total_tasks)
|
||||
|
||||
|
||||
# --- Communication --- #
|
||||
class CommSession(Recognizer):
|
||||
type = 'CommSession'
|
||||
depends_on = 'Heap'
|
||||
|
||||
def try_session_address(self, var_name):
|
||||
try:
|
||||
var_ref = get_static_variable(var_name)
|
||||
return gdb.parse_and_eval(var_ref).address
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def is_type(self, session, search_blocks):
|
||||
meta = TintinMetadata()
|
||||
hw_platform = meta.hw_platform()
|
||||
|
||||
transport_imp = session['transport_imp'].dereference().address
|
||||
|
||||
iap = self.try_session_address('s_iap_transport_implementation')
|
||||
spp = self.try_session_address('s_plain_spp_transport_implementation')
|
||||
ppogatt = self.try_session_address('s_ppogatt_transport_implementation')
|
||||
qemu = self.try_session_address('s_qemu_transport_implementation')
|
||||
pulse_pp = self.try_session_address('s_pulse_transport_implementation')
|
||||
|
||||
return transport_imp in [iap, spp, ppogatt, qemu, pulse_pp]
|
||||
|
||||
|
||||
# --- Windows --- #
|
||||
class NotificationNode(Recognizer):
|
||||
type = 'NotifList'
|
||||
|
||||
def is_type(self, node, search_blocks):
|
||||
s_presented_notifs = get_static_variable('s_presented_notifs')
|
||||
notification_list = LinkedList(gdb.parse_and_eval(s_presented_notifs)['list_node'])
|
||||
return node in notification_list
|
||||
|
||||
|
||||
class Modal(Recognizer):
|
||||
type = 'WindowStackItem'
|
||||
|
||||
def is_type(self, node, search_blocks):
|
||||
s_modal_window_stack = get_static_variable('s_modal_window_stack')
|
||||
modal_stack = LinkedList(gdb.parse_and_eval(s_modal_window_stack)['node'])
|
||||
return node in modal_stack
|
||||
|
||||
|
||||
# --- Misc --- #
|
||||
class EventService(Recognizer):
|
||||
type = 'EventServiceEntry'
|
||||
|
||||
def is_type(self, entry, search_blocks):
|
||||
subscribers = 0
|
||||
for x in xrange(Tasks().total):
|
||||
if entry['subscribers'][x] != 0:
|
||||
subscribers += 1
|
||||
return entry['num_subscribers'] == subscribers
|
||||
|
||||
|
||||
class VoiceEncoder(Recognizer):
|
||||
type = 'VoiceEncoder'
|
||||
|
||||
|
||||
class AlgState(Recognizer):
|
||||
type = 'AlgState'
|
||||
|
||||
def is_type(self, state, search_blocks):
|
||||
s_alg_state_ref = get_static_variable('s_alg_state')
|
||||
s_alg_state = gdb.parse_and_eval(s_alg_state_ref)
|
||||
return (state.dereference().address == s_alg_state)
|
||||
|
||||
|
||||
class KAlgState(Recognizer):
|
||||
type = 'KAlgState'
|
||||
|
||||
def is_type(self, state, search_blocks):
|
||||
s_alg_state_ref = get_static_variable('s_alg_state')
|
||||
s_alg_state = gdb.parse_and_eval(s_alg_state_ref)
|
||||
return (s_alg_state["k_state"] == state.dereference().address)
|
||||
|
||||
|
||||
class CachedResource(Recognizer):
|
||||
type = 'CachedResource'
|
||||
|
||||
def is_type(self, resource, search_blocks):
|
||||
s_resource_list = get_static_variable('s_resource_list')
|
||||
resources = LinkedList(gdb.parse_and_eval(s_resource_list)['list_node'])
|
||||
return resource in resources
|
||||
|
||||
|
||||
# --- Applib --- #
|
||||
class ModalWindow(Recognizer):
|
||||
depends_on = 'Modal'
|
||||
|
||||
def is_type(self, window, search_blocks):
|
||||
# Note that these are most likely dialogs. Try casting them.
|
||||
return window in [block['modal_window'] for block in search_blocks]
|
||||
|
||||
try:
|
||||
GBitmapFormats = gdb.types.make_enum_dict(gdb.lookup_type("enum GBitmapFormat"))
|
||||
except gdb.error:
|
||||
GBitmapFormats = None
|
||||
|
||||
# FIXME: This can be improved. It results in a lot of false negatives
|
||||
class GBitmap(Recognizer):
|
||||
type = 'GBitmap'
|
||||
|
||||
def is_type(self, bitmap, search_blocks):
|
||||
if GBitmapFormats is None:
|
||||
return False
|
||||
|
||||
row_size_bytes = bitmap['row_size_bytes']
|
||||
bounds_width = bitmap['bounds']['size']['w']
|
||||
format = int(bitmap['info']['format'])
|
||||
is_circular_format = (format == GBitmapFormats['GBitmapFormat8BitCircular'])
|
||||
is_valid_circular = (is_circular_format and row_size_bytes == 0)
|
||||
is_valid_rect = (not is_circular_format and row_size_bytes * 8 >= bounds_width)
|
||||
return (is_valid_circular or is_valid_rect) and (format in GBitmapFormats.values())
|
||||
|
||||
|
||||
class GBitmap_addr(Recognizer):
|
||||
depends_on = 'GBitmap'
|
||||
|
||||
def is_type(self, data, search_blocks):
|
||||
return data in [block['addr'] for block in search_blocks]
|
||||
|
||||
|
||||
class GBitmap_palette(Recognizer):
|
||||
depends_on = 'GBitmap'
|
||||
|
||||
def is_type(self, data, search_blocks):
|
||||
return data in [block['palette'] for block in search_blocks]
|
||||
|
||||
|
||||
# class AnimationAux(Recognizer):
|
||||
# type = 'AnimationAuxState'
|
||||
|
||||
# def is_type(self, aux, search_blocks):
|
||||
# possible_aux = [gdb.parse_and_eval('kernel_applib_get_animation_state().aux'),
|
||||
# gdb.parse_and_eval('s_app_state.animation_state.aux')]
|
||||
|
||||
# return aux in possible_aux
|
||||
|
||||
|
||||
# class Animation(Recognizer):
|
||||
# type = 'AnimationPrivate'
|
||||
|
||||
# def is_type(self, animation, search_blocks):
|
||||
# animations = []
|
||||
|
||||
# kernel_state = gdb.parse_and_eval('kernel_applib_get_animation_state()')
|
||||
# animations.extend(LinkedList(kernel_state['unscheduled_head'].dereference()))
|
||||
# animations.extend(LinkedList(kernel_state['scheduled_head'].dereference()))
|
||||
|
||||
# app_state = gdb.parse_and_eval('s_app_state.animation_state')
|
||||
# animations.extend(LinkedList(app_state['unscheduled_head'].dereference()))
|
||||
|
||||
# return animation in animations
|
80
tools/gdb_scripts/gdb_printers.py
Normal file
80
tools/gdb_scripts/gdb_printers.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError:
|
||||
raise Exception("This file is a GDB script.\n"
|
||||
"It is not intended to be run outside of GDB.\n"
|
||||
"Hint: to load a script in GDB, use `source this_file.py`")
|
||||
import gdb.printing
|
||||
|
||||
|
||||
class grectPrinter:
|
||||
"""Print a GRect struct as a fragment of C code."""
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
code = ("(GRect) { "
|
||||
".origin = { .x = %i, .y = %i }, "
|
||||
".size = { .w = %i, .h = %i } "
|
||||
"}")
|
||||
return code % (int(self.val["origin"]["x"]),
|
||||
int(self.val["origin"]["y"]),
|
||||
int(self.val["size"]["w"]),
|
||||
int(self.val["size"]["h"]))
|
||||
|
||||
|
||||
class gpathInfoPrinter:
|
||||
"""Print a GPathInfo struct as a fragment of C code."""
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
points_code = ""
|
||||
num_points = int(self.val["num_points"])
|
||||
array_val = self.val["points"]
|
||||
for i in xrange(0, num_points):
|
||||
point_val = array_val[i]
|
||||
if (points_code):
|
||||
points_code += ", "
|
||||
points_code += "{ %i, %i }" % (point_val["x"], point_val["y"])
|
||||
outer_code_fmt = ("(GPathInfo) { "
|
||||
".num_points = %i, "
|
||||
".points = (GPoint[]) {%s} "
|
||||
"}")
|
||||
return outer_code_fmt % (num_points, points_code)
|
||||
|
||||
|
||||
class UuidPrinter(object):
|
||||
"""Print a UUID."""
|
||||
|
||||
def __init__(self, val):
|
||||
bytes = ''.join(chr(int(val['byte%d' % n])) for n in xrange(16))
|
||||
self.uuid = uuid.UUID(bytes=bytes)
|
||||
|
||||
def to_string(self):
|
||||
return '{%s}' % self.uuid
|
||||
|
||||
pp = gdb.printing.RegexpCollectionPrettyPrinter('tintin')
|
||||
pp.add_printer('GRect', '^GRect$', grectPrinter)
|
||||
pp.add_printer('GPathInfo', '^GPathInfo$', gpathInfoPrinter)
|
||||
pp.add_printer('Uuid', '^Uuid$', UuidPrinter)
|
||||
# Register the pretty-printer globally
|
||||
gdb.printing.register_pretty_printer(None, pp, replace=True)
|
22
tools/gdb_scripts/gdb_python_path_fix.py
Normal file
22
tools/gdb_scripts/gdb_python_path_fix.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from subprocess import check_output
|
||||
import sys
|
||||
|
||||
# run the brewed python and get its sitepackages path
|
||||
output = check_output('python -c "import site; print(site.getsitepackages()[0])"', shell=True)
|
||||
|
||||
# add the brewed python's sitepackages path to our path
|
||||
sys.path.append(output.strip())
|
103
tools/gdb_scripts/gdb_symbols.py
Normal file
103
tools/gdb_scripts/gdb_symbols.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import gdb
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
INFO_LINE_RE = re.compile(r'(.+\s)*(\S+)\s+(\S+);')
|
||||
INFO_FILE_RE = re.compile(r'File ([^:]+):$')
|
||||
|
||||
def _do_info_line_match(line):
|
||||
m = INFO_LINE_RE.match(line)
|
||||
if not m:
|
||||
return None
|
||||
_type = m.group(2)
|
||||
if _type.startswith('0x'):
|
||||
# This is the address rather than the type
|
||||
_type = None
|
||||
symbol = m.group(3)
|
||||
if symbol.startswith('*'):
|
||||
symbol = symbol[1:]
|
||||
if _type is not None:
|
||||
_type += ' *'
|
||||
|
||||
paren_loc = symbol.find('[')
|
||||
if paren_loc > 0:
|
||||
symbol = symbol[:paren_loc]
|
||||
symbol = "'{}'".format(symbol)
|
||||
return (_type, symbol)
|
||||
|
||||
def _find_match_for_file(gdb_output, _file):
|
||||
matches = []
|
||||
in_file = False
|
||||
for line in gdb_output.split('\n'):
|
||||
if not in_file:
|
||||
# matching file
|
||||
m = INFO_FILE_RE.match(line)
|
||||
if m and m.group(1).endswith(_file):
|
||||
in_file = True
|
||||
else:
|
||||
if not line.strip():
|
||||
break # Done the file
|
||||
result = _do_info_line_match(line)
|
||||
if result is not None:
|
||||
matches.append(result)
|
||||
if len(matches) == 0:
|
||||
return (None, None)
|
||||
if len(matches) > 1:
|
||||
raise Exception('Error: Multiple statics by same name')
|
||||
return matches[0]
|
||||
|
||||
def _find_match(gdb_output, _file=None):
|
||||
if _file is not None:
|
||||
return _find_match_for_file(gdb_output, _file)
|
||||
matches = []
|
||||
for line in gdb_output.split('\n'):
|
||||
result = _do_info_line_match(line)
|
||||
if result is not None:
|
||||
matches.append(result)
|
||||
if len(matches) == 0:
|
||||
return (None, None)
|
||||
if len(matches) > 1:
|
||||
raise Exception('Error: Multiple statics by same name')
|
||||
return matches[0]
|
||||
|
||||
def _run_info(symbol_name, _type):
|
||||
out = gdb.execute('info {} {}\\b'.format(_type, symbol_name), False, True)
|
||||
return out
|
||||
|
||||
def get_static_variable(variable_name, _file=None, ref=False):
|
||||
if get_static_variable.cache[_file][variable_name]:
|
||||
return get_static_variable.cache[_file][variable_name]
|
||||
|
||||
out = _run_info(variable_name, 'variables')
|
||||
(_type, symbol) = _find_match(out, _file)
|
||||
if symbol is None:
|
||||
raise Exception('Error: Symbol matching "{}" DNE.'.format(variable_name))
|
||||
if ref:
|
||||
symbol = '&' + symbol
|
||||
if _type is not None:
|
||||
_type += ' *'
|
||||
if _type:
|
||||
ret = '(({}){})'.format(_type, symbol)
|
||||
ret = '({})'.format(symbol)
|
||||
get_static_variable.cache[_file][variable_name] = ret
|
||||
return ret
|
||||
get_static_variable.cache = defaultdict(lambda: defaultdict(lambda: {}))
|
||||
|
||||
def get_static_function(function_name):
|
||||
out = _run_info(function_name, 'functions')
|
||||
# TODO: Figure out what we need to do to properly find matches here.
|
||||
raise Exception('Not yet implemented.')
|
1334
tools/gdb_scripts/gdb_tintin.py
Normal file
1334
tools/gdb_scripts/gdb_tintin.py
Normal file
File diff suppressed because it is too large
Load diff
102
tools/gdb_scripts/gdb_tintin_metadata.py
Normal file
102
tools/gdb_scripts/gdb_tintin_metadata.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError:
|
||||
raise Exception("This file is a GDB script.\n"
|
||||
"It is not intended to be run outside of GDB.\n"
|
||||
"Hint: to load a script in GDB, use `source this_file.py`")
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from gdb_symbols import get_static_variable, get_static_function
|
||||
|
||||
|
||||
class TintinMetadata(object):
|
||||
""" Convenience Metadata struct for a tintin firmware """
|
||||
|
||||
def parse_hw_version(self, hw_version_num):
|
||||
board_name = None
|
||||
try:
|
||||
platform_enum = gdb.lookup_type("enum FirmwareMetadataPlatform")
|
||||
platform_types = gdb.types.make_enum_dict(platform_enum)
|
||||
except:
|
||||
return None, None
|
||||
|
||||
for k, v in platform_types.iteritems():
|
||||
if v == hw_version_num:
|
||||
board_name = k
|
||||
|
||||
platforms = {
|
||||
"One": "Tintin",
|
||||
"Two": "Tintin",
|
||||
"Snowy": "Snowy",
|
||||
"Bobby": "Snowy",
|
||||
"Spalding": "Spalding",
|
||||
"Silk": "Silk",
|
||||
"Robert": "Robert" }
|
||||
|
||||
platform_name = None
|
||||
for platform_key in platforms.keys():
|
||||
if platform_key.lower() in board_name.lower():
|
||||
platform_name = platforms[platform_key]
|
||||
return platform_name, board_name
|
||||
|
||||
def __init__(self):
|
||||
self.metadata = gdb.parse_and_eval(get_static_variable('TINTIN_METADATA'))
|
||||
|
||||
def version_timestamp(self, convert=True):
|
||||
val = int(self.metadata["version_timestamp"])
|
||||
if convert:
|
||||
return datetime.fromtimestamp(val)
|
||||
else:
|
||||
return val
|
||||
|
||||
def version_tag(self, raw=False):
|
||||
val = str(self.metadata["version_tag"])
|
||||
return val
|
||||
|
||||
def version_short(self, raw=False):
|
||||
val = str(self.metadata["version_short"])
|
||||
return val
|
||||
|
||||
def is_recovery_firmware(self, raw=False):
|
||||
val = bool(self.metadata["is_recovery_firmware"])
|
||||
return val
|
||||
|
||||
def hw_platform(self):
|
||||
val = int(self.metadata["hw_platform"])
|
||||
platform_name, board_name = self.parse_hw_version(val)
|
||||
return platform_name
|
||||
|
||||
def hw_board_name(self):
|
||||
val = int(self.metadata["hw_platform"])
|
||||
platform_name, board_name = self.parse_hw_version(val)
|
||||
return board_name
|
||||
|
||||
def hw_board_number(self):
|
||||
val = int(self.metadata["hw_platform"])
|
||||
return val
|
||||
|
||||
def __str__(self):
|
||||
str_rep = ""
|
||||
str_rep += "Build Timestamp: {}\n".format(self.version_timestamp())
|
||||
str_rep += "Version Tag: {}\n".format(self.version_tag())
|
||||
str_rep += "Version Short: {}\n".format(self.version_short())
|
||||
str_rep += "Is Recovery: {}\n".format(self.is_recovery_firmware())
|
||||
str_rep += "HW Platform: {}\n".format(self.hw_platform())
|
||||
str_rep += "HW Board Name: {}\n".format(self.hw_board_name())
|
||||
str_rep += "HW Board Num: {}".format(self.hw_board_number())
|
||||
return str_rep
|
196
tools/gdb_scripts/gdb_utils.py
Normal file
196
tools/gdb_scripts/gdb_utils.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
try:
|
||||
import gdb
|
||||
except ImportError:
|
||||
raise Exception("This file is a GDB script.\n"
|
||||
"It is not intended to be run outside of GDB.\n"
|
||||
"Hint: to load a script in GDB, use `source this_file.py`")
|
||||
import argparse
|
||||
import re
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class GdbArgumentParser(argparse.ArgumentParser):
|
||||
def parse_args(self, args=None, namespace=None):
|
||||
if args:
|
||||
args = gdb.string_to_argv(args)
|
||||
return argparse.ArgumentParser.parse_args(self, args, namespace)
|
||||
|
||||
def exit(self, status=0, msg=None):
|
||||
raise gdb.GdbError(msg)
|
||||
|
||||
|
||||
class AddressInfo(namedtuple('AddressInfo', 'filename line addr')):
|
||||
def __new__(cls, filename, line, addr):
|
||||
return cls._make([filename, line, addr])
|
||||
|
||||
def __str__(self):
|
||||
return "{}:{} (0x{:0>8x})".format(self.filename, self.line, self.addr)
|
||||
|
||||
|
||||
def addr2line(addr_value):
|
||||
""" Convenience function to return a string with code line for an address.
|
||||
The function takes an int or a gdb.Value that can be converted to an int.
|
||||
The format used is: `file_name:line_number (0xhex_address)`
|
||||
|
||||
"""
|
||||
addr = int(addr_value)
|
||||
s = gdb.find_pc_line(addr)
|
||||
filename = None
|
||||
line = None
|
||||
if s and s.symtab:
|
||||
filename = s.symtab.filename.lstrip("../")
|
||||
line = s.line
|
||||
if not filename:
|
||||
filename = "?"
|
||||
if not line:
|
||||
line = "?"
|
||||
return AddressInfo(filename, line, addr)
|
||||
|
||||
|
||||
class Address(int):
|
||||
""" Convenience subclass of `int` that accepts a hexadecimal string in its
|
||||
constructor. It also accepts a gdb.Value in its constructor, in which case
|
||||
the address of the value will be attempted to be used to create the object.
|
||||
Its `__repr__` prints its value formatted as hexadecimal.
|
||||
|
||||
"""
|
||||
ADDR_REGEX = re.compile("^\s*(0x[a-fA-F0-9]{7,8})")
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if args:
|
||||
val = args[0]
|
||||
if isinstance(val, gdb.Value):
|
||||
if val.address:
|
||||
# If the value has an address, use it:
|
||||
val = str(val.address)
|
||||
else:
|
||||
# Otherwise, attempt to use that the value as an int:
|
||||
val = int(val)
|
||||
if isinstance(val, str):
|
||||
# GDB tends to append symbolic info to the string, even for
|
||||
# a gdb.Value that was acquired from the `address` attribute...
|
||||
match = Address.ADDR_REGEX.match(val)
|
||||
if match:
|
||||
val = match.group(1)
|
||||
return super(Address, cls).__new__(cls, val, base=16)
|
||||
return super(Address, cls).__new__(cls, *args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "0x%08x" % self
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class ActionBreakpoint(gdb.Breakpoint):
|
||||
"""
|
||||
Convenience wrapper around gdb.Breakpoint.
|
||||
|
||||
The first argument to the constructor is a Callable, the ActionBreakpoint's
|
||||
attribute `action_callable` will be set to this object. The Callable must
|
||||
accept one argument, which will be set to the instance of the
|
||||
ActionBreakpoint instance itself.
|
||||
|
||||
The second (optional) argument to the constructor is the name (str) of the
|
||||
symbol on which to set the breakpoint. If not specified, the __name__ of
|
||||
the callable is used as the name of the symbol, for convenience.
|
||||
|
||||
ActionBreakpoint implements the method `def handle_break(self)` that is
|
||||
called when the breakpoint is hit. The base implementation just calls the
|
||||
`action_callable` attribute.
|
||||
|
||||
Usage example:
|
||||
|
||||
def window_stack_push(breakpoint):
|
||||
print "window_stack_push() called!"
|
||||
bp = ActionBreakpoint(window_stack_push)
|
||||
|
||||
"""
|
||||
def stop_handler(event):
|
||||
if isinstance(event, gdb.BreakpointEvent):
|
||||
for breakpoint in event.breakpoints:
|
||||
if isinstance(breakpoint, ActionBreakpoint):
|
||||
breakpoint.handle_break()
|
||||
gdb.events.stop.connect(stop_handler) # Register with gdb module
|
||||
|
||||
def __init__(self, action_callable, symbol_name=None, addr=None,
|
||||
auto_continue=True):
|
||||
if addr and symbol_name:
|
||||
raise Exception("Can't use arguments `symbol_name` and "
|
||||
"`addr` simultaneously!")
|
||||
if addr:
|
||||
# When an address is specified, the expression must be prepended
|
||||
# with an `*` (not to be confused with a dereference...):
|
||||
# https://sourceware.org/gdb/onlinedocs/gdb/Specify-Location.html
|
||||
symbol_name = "*" + str(addr)
|
||||
if not symbol_name:
|
||||
symbol_name = action_callable.__name__
|
||||
super(ActionBreakpoint, self).__init__(symbol_name)
|
||||
self.action_callable = action_callable
|
||||
self.auto_continue = auto_continue
|
||||
|
||||
def handle_break(self):
|
||||
self.action_callable(self)
|
||||
if self.auto_continue:
|
||||
gdb.execute("continue")
|
||||
|
||||
|
||||
class MonkeyPatch(ActionBreakpoint):
|
||||
"""
|
||||
Object that `overrides` an existing function in the program being
|
||||
debugged, using ActionBreakpoint and the `return` GDB command.
|
||||
|
||||
The first argument is the callable that provides the return value as a GDB
|
||||
expression (str) or returns `None` in case the function returns void.
|
||||
The (optional) second argument is a string of the symbol of the function to
|
||||
monkey-patch. If not specified, the __name__ of the callable is used as the
|
||||
name of the symbol, for convenience.
|
||||
|
||||
Usage example:
|
||||
|
||||
def my_function_override(monkey_patch):
|
||||
return "(int) 123"
|
||||
patch = MonkeyPatch(my_function_override, "my_existing_function")
|
||||
|
||||
"""
|
||||
def handle_break(self):
|
||||
return_value_str = self.action_callable(self)
|
||||
gdb.write("Hit monkey patch %s, returning `%s`" %
|
||||
(self, return_value_str))
|
||||
if return_value_str:
|
||||
gdb.execute("return (%s)" % return_value_str)
|
||||
else:
|
||||
gdb.execute("return")
|
||||
gdb.execute("continue")
|
||||
|
||||
|
||||
GET_ARGS_RE = re.compile("^[_A-z]+[_A-z0-9]*")
|
||||
|
||||
|
||||
def get_args():
|
||||
"""
|
||||
Returns a dict of gdb.Value objects of the arguments in scope.
|
||||
"""
|
||||
args = {}
|
||||
info_args_str = gdb.execute("info args", to_string=True)
|
||||
print info_args_str
|
||||
for line in info_args_str.splitlines():
|
||||
match = GET_ARGS_RE.search(line)
|
||||
var_name = match.group()
|
||||
args[var_name] = gdb.parse_and_eval(var_name)
|
||||
return args
|
Loading…
Add table
Add a link
Reference in a new issue