Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View 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]

View 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

View 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)

View 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())

View 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.')

File diff suppressed because it is too large Load diff

View 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

View 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