• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os
2import re
3
4# If doing it on device with gdbserver
5DEVICE = os.environ.get('GDBSCRIPT_ON_DEVICE', False)
6# Path of the file on device
7DEVICE_FILEPATH = os.environ.get('GDBSCRIPT_FILENAME', None)
8# GDBServer's port
9DEVICE_PORT = os.environ.get('GDBSCRIPT_DEVICE_PORT', 4444)
10# Serial number of device for adb
11DEVICE_SERIAL = os.environ.get('GDBSCRIPT_DEVICE_SERIAL', None)
12
13def check_device_args():
14    """
15    Checks if FILEPATH is provided if the execution is on device
16    """
17    if not DEVICE:
18        return
19
20    if not DEVICE_FILEPATH:
21        raise ValueError("Filename (GDBSCRIPT_FILEPATH) not provided")
22
23class RecordPoint(gdb.Breakpoint):
24    """
25    A custom breakpoint that records the arguments when the breakpoint is hit and continues
26    Also enables the next breakpoint and disables all the ones after it
27    """
28    def stop(self):
29        """
30        The function that's called when a breakpoint is hit. If we return true,
31        it halts otherwise it continues
32        We always return false because we just need to record the value and we
33        can do it without halting the program
34        """
35        self.args[self.times_hit % self.count] = get_function_args()
36        self.times_hit += 1
37
38        if self.next_bp != None:
39            self.next_bp.previous_hit()
40
41        return False
42
43    def previous_hit(self):
44        """
45        This function is called if the previous breakpoint is hit so it can enable
46        itself and disable the next ones
47        """
48        self.enabled = True
49
50        if self.next_bp != None:
51            self.next_bp.propagate_disable()
52
53    def propagate_disable(self):
54        """
55        Disabled all the breakpoints after itself
56        """
57        self.enabled = False
58        if self.next_bp != None:
59            self.next_bp.propagate_disable()
60
61    def process_arguments(self):
62        """
63        Orders the recorded arguments into the right order (oldest to newest)
64        """
65        current_hit_point = self.times_hit % self.count
66        # Split at the point of current_hit_point because all the entries after it
67        # are older than the ones before it
68        self.processed_args = self.args[current_hit_point:] + self.args[:current_hit_point]
69        self.current_arg_idx = 0
70
71    def get_arguments(self):
72        """
73        Gets the current argument value.
74        Should be called the same amount of times as the function was called
75        in the stacktrace
76        First call returns the arguments recorded for the first call in the stacktrace
77        and so on.
78        """
79        if self.current_arg_idx >= len(self.processed_args):
80            raise ValueError("Cannot get arguments more times than the function \
81                    was present in stacktrace")
82
83        cur = self.processed_args[self.current_arg_idx]
84        self.current_arg_idx += 1
85        return cur
86
87def init_gdb():
88    """
89    Initialized the GDB specific stuff
90    """
91    gdb.execute('set pagination off')
92    gdb.execute('set print frame-arguments all')
93    if DEVICE:
94        gdb.execute('target extended-remote :{}'.format(DEVICE_PORT))
95        gdb.execute('set remote exec-file /data/local/tmp/{}'.format(DEVICE_FILEPATH))
96
97def initial_run():
98    """
99    The initial run of the program which captures the stacktrace in init.log file
100    """
101    gdb.execute('r > init.log 2>&1',from_tty=True, to_string=True)
102    if DEVICE:
103        if DEVICE_SERIAL:
104            os.system('adb -s "{}" pull /data/local/tmp/init.log'.format(DEVICE_SERIAL))
105        else:
106            os.system("adb pull /data/local/tmp/init.log")
107    with open("init.log", "rb") as f:
108        out = f.read().decode()
109    return out
110
111def gdb_exit():
112    """
113    Exits the GDB instance
114    """
115    gdb.execute('q')
116
117def get_stacktrace_functions(stacktrace):
118    """
119    Gets the functions from ASAN/HWASAN's stacktrace
120    Args:
121        stacktrace: (string) ASAN/HWASAN's stacktrace output
122    Returns:
123        functions: (list) functions in the stacktrace
124    """
125    stacktrace_start = stacktrace[stacktrace.index('==ERROR: '):].split("\n")
126    functions = []
127
128    # skip the first two lines of stacktrace
129    for line in stacktrace_start[2:]:
130        if line == "":
131            break
132
133        # Extracts the function name from a line like this
134        # "#0 0xaddress in function_name() file/path.cc:xx:yy"
135        func_name = line.strip().split(" ")[3]
136        if '(' in func_name:
137            func_name = func_name[:func_name.index('(')]
138
139        functions.append(func_name)
140
141    #remove last function from stacktrace because it would be _start
142    return functions
143
144def parse_function_arguments(func_info):
145    """
146    Parses the output of 'whatis' command into a list of arguments
147    "void (teststruct)" --> ["teststruct"]
148    "int (int (*)(int, char **, char **), int, char **, int (*)(int, char **, char **),
149    void (*)(void), void (*)(void), void *)" --> ['int (*)(int, char **, char **)',
150    'int', 'char **', 'int (*)(int, char **, char **)', 'void (*)(void)',
151    'void (*)(void)', ' void *']
152
153    Args:
154        func_info: (string) output of gdb's 'whatis' command for a function
155    Returns:
156        parsed_params: (list) parsed parameters of the function
157    """
158    if '(' not in func_info:
159        return []
160    func_params = func_info[func_info.index('(')+1:-1]
161    parentheses_count = 0
162    current_param = ""
163    parsed_params = []
164
165    for token in func_params:
166        # Essentially trying to get the data types from a function declaration
167        if token == '(':
168            parentheses_count += 1
169        elif token == ')':
170            parentheses_count -= 1
171
172        # If we are not inside any paren and see a ',' it signals the start of
173        #the next parameter
174        if token == ',' and parentheses_count == 0:
175            parsed_params.append(current_param.strip())
176            current_param = ""
177        else:
178            current_param += token
179
180    parsed_params.append(current_param)
181    return parsed_params
182
183def parse_stacktrace(stacktrace):
184    """
185    Parses the ASAN/HWASAN's stacktrace to a list of functions, their addresses
186    and argument types
187    Args:
188        stacktrace: (string) ASAN/HWASAN's stacktrace output
189    Returns:
190        functions_info: (list) parsed function information as a dictionary
191    """
192    stacktrace_functions = get_stacktrace_functions(stacktrace)[:-1]
193    functions_info = []
194    for function in stacktrace_functions:
195        # Gets the value right hand side of gdb's whatis command.
196        # "type = {function info}" -> "{function info}"
197        func_info = gdb.execute('whatis {}'.format(function),
198                to_string=True).split(' = ')[1].strip()
199        # Uses gdb's x/i to print its address and parse it from hex to int
200        address = int(gdb.execute("x/i {}".format(function),
201            to_string=True).strip().split(" ")[0], 16)
202        functions_info.append({'name': function, 'address':address,
203            'arguments' : parse_function_arguments(func_info)})
204    #In the order they are called in the execution
205    return functions_info[::-1]
206
207def get_function_args():
208    """
209    Gets the current function arguments
210    """
211    args = gdb.execute('info args -q', to_string=True).strip()
212    return args
213
214def functions_to_breakpoint(parsed_functions):
215    """
216    Sets the breakpoint at every function and returns a dictionary mapping the
217    function to it's breakpoint
218    Args:
219        parsed_functions: (list) functions in the stacktrace (in the same order) as
220        dictionary with "name" referring to the function name
221        ({"name" : function_name})
222    Returns:
223        function_breakpoints: (dictionary) maps the function name to its
224        breakpoint object
225    """
226    function_breakpoints = {}
227    last_bp = None
228
229    for function in reversed(parsed_functions):
230        function_name = function['name']
231        if function_name in function_breakpoints:
232            function_breakpoints[function_name].count += 1
233            function_breakpoints[function_name].args.append(None)
234            continue
235
236        cur_bp = RecordPoint("{}".format(function_name))
237        cur_bp.count = 1
238        cur_bp.times_hit = 0
239        cur_bp.args = []
240        cur_bp.args.append(None)
241        cur_bp.next_bp = last_bp
242
243        function_breakpoints[function['name']] = cur_bp
244        last_bp = cur_bp
245
246    return function_breakpoints
247
248def run(parsed_functions):
249    """
250    Runs the whole thing by setting up breakpoints and printing them after
251    excecution is done
252    Args:
253        parsed_functions: A list of functions in the stacktrace (in the same order)
254        as dictionary with "name" referring to the function name
255        ({"name" : function_name})
256    """
257    names = [function['name'] for function in parsed_functions]
258    breakpoints = functions_to_breakpoint(parsed_functions)
259
260    #Disable all breakpoints at start
261    for bp in breakpoints:
262        breakpoints[bp].enabled = False
263
264    breakpoints[names[0]].enabled = True
265
266    gdb.execute('r')
267    for breakpoint in breakpoints:
268        breakpoints[breakpoint].process_arguments()
269
270    function_args = []
271    for name in names:
272        print("-----------")
273        print("Function -> {}".format(name))
274
275        function_args.append({'function':name,
276            'arguments' : breakpoints[name].get_arguments()})
277        print(function_args[-1]['arguments'])
278
279    return function_args
280
281
282if __name__ == '__main__':
283    check_device_args()
284    init_gdb()
285    initial_out = initial_run()
286    function_data = parse_stacktrace(initial_out)
287    run(function_data)
288    gdb_exit()
289