1# -*- coding: utf-8 -*-
2
3import sys
4import os
5import time
6import string
7import shutil
8import subprocess
9import signal
10import argparse
11
12import common
13
14def getADBProgramPID(program):
15	adbCmd	= common.shellquote(common.ADB_BIN)
16	pid		= -1
17
18	process = subprocess.Popen("%s shell ps" % adbCmd, shell=True, stdout=subprocess.PIPE)
19
20	firstLine = True
21	for line in process.stdout.readlines():
22		if firstLine:
23			firstLine = False
24			continue
25
26		fields = string.split(line)
27		fields = filter(lambda x: len(x) > 0, fields)
28
29		if len(fields) < 9:
30			continue
31
32		if fields[8] == program:
33			assert pid == -1
34			pid = int(fields[1])
35
36	process.wait()
37
38	if process.returncode != 0:
39		print("adb shell ps returned %s" % str(process.returncode))
40		pid = -1
41
42	return pid
43
44def debug(
45	adbCmd,
46	deqpCmdLine,
47	targetGDBPort,
48	hostGDBPort,
49	jdbPort,
50	jdbCmd,
51	gdbCmd,
52	buildDir,
53	deviceLibs,
54	breakpoints
55	):
56
57	programPid			= -1
58	gdbServerProcess	= None
59	gdbProcess			= None
60	jdbProcess			= None
61	curDir				= os.getcwd()
62	debugDir			= os.path.join(common.ANDROID_DIR, "debug")
63
64	if os.path.exists(debugDir):
65		shutil.rmtree(debugDir)
66
67	os.makedirs(debugDir)
68	os.chdir(debugDir)
69
70	try:
71		# Start execution
72		print("Starting intent...")
73		common.execute("%s shell am start -W -D -n com.drawelements.deqp/android.app.NativeActivity -e cmdLine \"unused %s\"" % (adbCmd, deqpCmdLine.replace("\"", "\\\"")))
74		print("Intent started")
75
76		# Kill existing gdbservers
77		print("Check and kill existing gdbserver")
78		gdbPid = getADBProgramPID("lib/gdbserver")
79		if gdbPid != -1:
80			print("Found gdbserver with PID %i" % gdbPid)
81			common.execute("%s shell run-as com.drawelements.deqp kill -9 %i" % (adbCmd, gdbPid))
82			print("Killed gdbserver")
83		else:
84			print("Couldn't find existing gdbserver")
85
86		programPid = getADBProgramPID("com.drawelements.deqp:testercore")
87
88		print("Find process PID")
89		if programPid == -1:
90			common.die("Couldn't get PID of testercore")
91		print("Process running with PID %i" % programPid)
92
93		# Start gdbserver
94		print("Start gdbserver for PID %i redirect stdout to gdbserver-stdout.txt" % programPid)
95		gdbServerProcess = subprocess.Popen("%s shell run-as com.drawelements.deqp lib/gdbserver localhost:%i --attach %i" % (adbCmd, targetGDBPort, programPid), shell=True, stdin=subprocess.PIPE, stdout=open("gdbserver-stdout.txt", "wb"), stderr=open("gdbserver-stderr.txt", "wb"))
96		print("gdbserver started")
97
98		time.sleep(1)
99
100		gdbServerProcess.poll()
101
102		if gdbServerProcess.returncode != None:
103			common.die("gdbserver returned unexpectly with return code %i see gdbserver-stdout.txt for more info" % gdbServerProcess.returncode)
104
105		# Setup port forwarding
106		print("Forwarding local port to gdbserver port")
107		common.execute("%s forward tcp:%i tcp:%i" % (adbCmd, hostGDBPort, targetGDBPort))
108
109		# Pull some data files for debugger
110		print("Pull /system/bin/app_process from device")
111		common.execute("%s pull /system/bin/app_process" % adbCmd)
112
113		print("Pull /system/bin/linker from device")
114		common.execute("%s pull /system/bin/linker" % adbCmd)
115
116		for lib in deviceLibs:
117			print("Pull library %s from device" % lib)
118			common.execute("%s pull %s" % (adbCmd, lib))
119
120		print("Copy %s from build dir" % common.NATIVE_LIB_NAME)
121		shutil.copyfile(os.path.join(buildDir, common.NATIVE_LIB_NAME), common.NATIVE_LIB_NAME)
122
123		# Forward local port for jdb
124		print("Forward local port to jdb port")
125		common.execute("%s forward tcp:%i jdwp:%i" % (adbCmd, jdbPort, programPid))
126
127		# Connect JDB
128		print("Start jdb process redirectd stdout to jdb-stdout.txt")
129		jdbProcess = subprocess.Popen("%s -connect com.sun.jdi.SocketAttach:hostname=localhost,port=%i -sourcepath ../package" % (jdbCmd, jdbPort), shell=True, stdin=subprocess.PIPE, stdout=open("jdb-stdout.txt", "wb"), stderr=open("jdb-stderr.txt", "wb"))
130		print("Started jdb process")
131
132		# Write gdb.setup
133		print("Write gdb.setup")
134		gdbSetup = open("gdb.setup", "wb")
135		gdbSetup.write("file app_process\n")
136		gdbSetup.write("set solib-search-path .\n")
137		gdbSetup.write("target remote :%i\n" % hostGDBPort)
138		gdbSetup.write("set breakpoint pending on\n")
139
140		for breakpoint in breakpoints:
141			print("Set breakpoint at %s" % breakpoint)
142			gdbSetup.write("break %s\n" % breakpoint)
143
144		gdbSetup.write("set breakpoint pending off\n")
145		gdbSetup.close()
146
147		print("Start gdb")
148		gdbProcess = subprocess.Popen("%s -x gdb.setup" % common.shellquote(gdbCmd), shell=True)
149
150		gdbProcess.wait()
151
152		print("gdb returned with %i" % gdbProcess.returncode)
153		gdbProcess=None
154
155		print("Close jdb process with 'quit'")
156		jdbProcess.stdin.write("quit\n")
157		jdbProcess.wait()
158		print("JDB returned %s" % str(jdbProcess.returncode))
159		jdbProcess=None
160
161		print("Kill gdbserver process")
162		gdbServerProcess.kill()
163		gdbServerProcess=None
164		print("Killed gdbserver process")
165
166		print("Kill program %i" % programPid)
167		common.execute("%s shell run-as com.drawelements.deqp -9 %i" % (adbCmd, programPid))
168		print("Killed program")
169
170	finally:
171		if jdbProcess and jdbProcess.returncode == None:
172			print("Kill jdb")
173			jdbProcess.kill()
174		elif jdbProcess:
175			print("JDB returned %i" % jdbProcess.returncode)
176
177		if gdbProcess and gdbProcess.returncode == None:
178			print("Kill gdb")
179			gdbProcess.kill()
180		elif gdbProcess:
181			print("GDB returned %i" % gdbProcess.returncode)
182
183		if gdbServerProcess and gdbServerProcess.returncode == None:
184			print("Kill gdbserver")
185			gdbServerProcess.kill()
186		elif gdbServerProcess:
187			print("GDB server returned %i" % gdbServerProcess.returncode)
188
189		print("Kill program %i" % programPid)
190		common.execute("%s shell run-as com.drawelements.deqp kill -9 %i" % (adbCmd, programPid))
191		print("Killed program")
192
193		os.chdir(curDir)
194
195if __name__ == "__main__":
196	parser = argparse.ArgumentParser()
197
198	defaultDeviceLibs = {
199		"nexus-4" : [
200			"/system/lib/libgenlock.so",
201			"/system/lib/libmemalloc.so",
202			"/system/lib/libqdutils.so",
203			"/system/lib/libsc-a3xx.so"
204		]
205	}
206
207	defaultDevices = []
208
209	for device in defaultDeviceLibs:
210		defaultDevices += [device]
211
212	parser.add_argument('--adb',				dest='adbCmd',			default=common.shellquote(common.ADB_BIN), help="Path to adb command. Use absolute paths.")
213	parser.add_argument('--deqp-commandline',	dest='deqpCmdLine',		default="--deqp-log-filename=/sdcard/TestLog.qpa", help="Command line arguments passed to dEQP test binary.")
214
215	if common.getPlatform() == "linux":
216		parser.add_argument('--gdb',				dest='gdbCmd',			default=common.shellquote(os.path.join(common.ANDROID_NDK_PATH, "toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86/bin/arm-linux-androideabi-gdb")), help="gdb command used by script. Use absolute paths")
217	else:
218		parser.add_argument('--gdb',				dest='gdbCmd',			default=common.shellquote(os.path.join(common.ANDROID_NDK_PATH, "toolchains/arm-linux-androideabi-4.8/prebuilt/windows/bin/arm-linux-androideabi-gdb")), help="gdb command used by script. Use absolute paths")
219
220	parser.add_argument('--target-gdb-port',	dest='targetGDBPort',	default=60001, type=int, help="Port used by gdbserver on target.")
221	parser.add_argument('--host-gdb-port',		dest='hostGDBPort',		default=60002, type=int, help="Host port that is forwarded to device gdbserver port.")
222	parser.add_argument('--jdb',				dest='jdbCmd',			default="jdb", help="Path to jdb command. Use absolute paths.")
223	parser.add_argument('--jdb-port',			dest='jdbPort',			default=60003, type=int, help="Host port used to forward jdb commands to device.")
224	parser.add_argument('--build-dir',			dest='buildDir',		default="../../../deqp-build-android-9-armeabi-v7a-debug", help="Path to dEQP native build directory.")
225	parser.add_argument('--device-libs',		dest='deviceLibs',		default=[], nargs='+', help="List of libraries that should be pulled from device for debugging.")
226	parser.add_argument('--breakpoints',		dest='breakpoints',		default=["tcu::App::App"], nargs='+', help="List of breakpoints that are set by gdb.")
227	parser.add_argument('--device',				dest='device',			default=None, choices=defaultDevices, help="Pull default libraries for this device.")
228
229	args = parser.parse_args()
230
231	debug(adbCmd=os.path.normpath(args.adbCmd),
232	      gdbCmd=os.path.normpath(args.gdbCmd),
233	      targetGDBPort=args.targetGDBPort,
234	      hostGDBPort=args.hostGDBPort,
235          jdbCmd=os.path.normpath(args.jdbCmd),
236	      jdbPort=args.jdbPort,
237	      deqpCmdLine=args.deqpCmdLine,
238	      buildDir=args.buildDir,
239	      deviceLibs=["/system/lib/libc.so", "/system/lib/libdl.so"] + args.deviceLibs + (defaultDeviceLibs[args.device] if args.device else []),
240	      breakpoints=args.breakpoints)
241