blob: c11d51a1b0bb442a97fce84806f328da4e0743a6 [file] [log] [blame]
Alex Light8a719052018-09-07 09:22:03 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Retrieves the counts of how many objects have a particular field null on all running processes.
19
20Prints a json map from pid -> (log-tag, field-name, null-count, total-count).
21"""
22
23
24import adb
25import argparse
26import concurrent.futures
27import itertools
28import json
29import logging
30import os
31import os.path
32import signal
33import subprocess
34import time
35
36def main():
37 parser = argparse.ArgumentParser(description="Get counts of null fields from a device.")
38 parser.add_argument("-S", "--serial", metavar="SERIAL", type=str,
39 required=False,
40 default=os.environ.get("ANDROID_SERIAL", None),
41 help="Android serial to use. Defaults to ANDROID_SERIAL")
42 parser.add_argument("-p", "--pid", required=False,
43 default=[], action="append",
44 help="Specific pids to check. By default checks all running dalvik processes")
45 has_out = "OUT" in os.environ
46 def_32 = os.path.join(os.environ.get("OUT", ""), "system", "lib", "libfieldnull.so")
47 def_64 = os.path.join(os.environ.get("OUT", ""), "system", "lib64", "libfieldnull.so")
48 has_32 = has_out and os.path.exists(def_32)
49 has_64 = has_out and os.path.exists(def_64)
50 def pushable_lib(name):
51 if os.path.isfile(name):
52 return name
53 else:
54 raise argparse.ArgumentTypeError(name + " is not a file!")
55 parser.add_argument('--lib32', type=pushable_lib,
56 required=not has_32,
57 action='store',
58 default=def_32,
59 help="Location of 32 bit agent to push")
60 parser.add_argument('--lib64', type=pushable_lib,
61 required=not has_64,
62 action='store',
63 default=def_64 if has_64 else None,
64 help="Location of 64 bit agent to push")
65 parser.add_argument("fields", nargs="+",
66 help="fields to check")
67
68 out = parser.parse_args()
69
70 device = adb.device.get_device(out.serial)
71 print("getting root")
72 device.root()
73
74 print("Disabling selinux")
75 device.shell("setenforce 0".split())
76
77 print("Pushing libraries")
78 lib32 = device.shell("mktemp".split())[0].strip()
79 lib64 = device.shell("mktemp".split())[0].strip()
80
81 print(out.lib32 + " -> " + lib32)
82 device.push(out.lib32, lib32)
83
84 print(out.lib64 + " -> " + lib64)
85 device.push(out.lib64, lib64)
86
87 cmd32 = "'{}={}'".format(lib32, ','.join(out.fields))
88 cmd64 = "'{}={}'".format(lib64, ','.join(out.fields))
89
90 if len(out.pid) == 0:
91 print("Getting jdwp pids")
92 new_env = dict(os.environ)
93 new_env["ANDROID_SERIAL"] = device.serial
94 p = subprocess.Popen([device.adb_path, "jdwp"], env=new_env, stdout=subprocess.PIPE)
95 # ADB jdwp doesn't ever exit so just kill it after 1 second to get a list of pids.
96 with concurrent.futures.ProcessPoolExecutor() as ppe:
97 ppe.submit(kill_it, p.pid).result()
98 out.pid = p.communicate()[0].strip().split()
99 p.wait()
100 print(out.pid)
101 print("Clearing logcat")
102 device.shell("logcat -c".split())
103 final = {}
104 print("Getting info from every process dumped to logcat")
105 for p in out.pid:
106 res = check_single_process(p, device, cmd32, cmd64);
107 if res is not None:
108 final[p] = res
109 device.shell('rm {}'.format(lib32).split())
110 device.shell('rm {}'.format(lib64).split())
111 print(json.dumps(final, indent=2))
112
113def kill_it(p):
114 time.sleep(1)
115 os.kill(p, signal.SIGINT)
116
117def check_single_process(pid, device, bit32, bit64):
118 try:
119 # Just try attaching both 32 and 64 bit. Wrong one will fail silently.
120 device.shell(['am', 'attach-agent', str(pid), bit32])
121 device.shell(['am', 'attach-agent', str(pid), bit64])
122 time.sleep(0.5)
123 device.shell('kill -3 {}'.format(pid).split())
124 time.sleep(0.5)
125 out = []
126 all_fields = []
127 lc_cmd = "logcat -d -b main --pid={} -e '^\\t.*\\t[0-9]*\\t[0-9]*$'".format(pid).split(' ')
128 for l in device.shell(lc_cmd)[0].strip().split('\n'):
129 # first 4 are just date and other useless data.
130 data = l.strip().split()[5:]
131 if len(data) < 4:
132 continue
133 # If we run multiple times many copies of the agent will be attached. Just choose one of any
134 # copies for each field.
135 field = data[1]
136 if field not in all_fields:
137 out.append((str(data[0]), str(data[1]), int(data[2]), int(data[3])))
138 all_fields.append(field)
139 if len(out) != 0:
140 print("pid: " + pid + " -> " + str(out))
141 return out
142 else:
143 return None
144 except adb.device.ShellError as e:
145 print("failed on pid " + repr(pid) + " because " + repr(e))
146 return None
147
148if __name__ == '__main__':
149 main()