1#!/usr/bin/env python3 2# 3# Copyright 2017, 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""" 18A python program that simulates the plugin side of the dt_fd_forward transport for testing. 19 20This program will invoke a given java language runtime program and send down debugging arguments 21that cause it to use the dt_fd_forward transport. This will then create a normal server-port that 22debuggers can attach to. 23""" 24 25import argparse 26import array 27from multiprocessing import Process 28import contextlib 29import ctypes 30import os 31import select 32import socket 33import subprocess 34import sys 35import time 36 37NEED_HANDSHAKE_MESSAGE = b"HANDSHAKE:REQD\x00" 38LISTEN_START_MESSAGE = b"dt_fd_forward:START-LISTEN\x00" 39LISTEN_END_MESSAGE = b"dt_fd_forward:END-LISTEN\x00" 40ACCEPTED_MESSAGE = b"dt_fd_forward:ACCEPTED\x00" 41HANDSHAKEN_MESSAGE = b"dt_fd_forward:HANDSHAKE-COMPLETE\x00" 42CLOSE_MESSAGE = b"dt_fd_forward:CLOSING\x00" 43 44libc = ctypes.cdll.LoadLibrary("libc.so.6") 45def eventfd(init_val, flags): 46 """ 47 Creates an eventfd. See 'man 2 eventfd' for more information. 48 """ 49 return libc.eventfd(init_val, flags) 50 51@contextlib.contextmanager 52def make_eventfd(init): 53 """ 54 Creates an eventfd with given initial value that is closed after the manager finishes. 55 """ 56 fd = eventfd(init, 0) 57 yield fd 58 os.close(fd) 59 60@contextlib.contextmanager 61def make_sockets(): 62 """ 63 Make a (remote,local) socket pair. The remote socket is inheritable by forked processes. They are 64 both linked together. 65 """ 66 (rfd, lfd) = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET) 67 yield (rfd, lfd) 68 rfd.close() 69 lfd.close() 70 71def send_fds(sock, remote_read, remote_write, remote_event): 72 """ 73 Send the three fds over the given socket. 74 """ 75 sock.sendmsg([NEED_HANDSHAKE_MESSAGE], # We want the transport to handle the handshake. 76 [(socket.SOL_SOCKET, # Send over socket. 77 socket.SCM_RIGHTS, # Payload is file-descriptor array 78 array.array('i', [remote_read, remote_write, remote_event]))]) 79 80 81def HandleSockets(host, port, local_sock, finish_event): 82 """ 83 Handle the IO between the network and the runtime. 84 85 This is similar to what we will do with the plugin that controls the jdwp connection. 86 87 The main difference is it will keep around the connection and event-fd in order to let it send 88 ddms packets directly. 89 """ 90 listening = False 91 with socket.socket() as sock: 92 sock.bind((host, port)) 93 sock.listen() 94 while True: 95 sources = [local_sock, finish_event, sock] 96 print("Starting select on " + str(sources)) 97 (rf, _, _) = select.select(sources, [], []) 98 if local_sock in rf: 99 buf = local_sock.recv(1024) 100 print("Local_sock has data: " + str(buf)) 101 if buf == LISTEN_START_MESSAGE: 102 print("listening on " + str(sock)) 103 listening = True 104 elif buf == LISTEN_END_MESSAGE: 105 print("End listening") 106 listening = False 107 elif buf == ACCEPTED_MESSAGE: 108 print("Fds were accepted.") 109 elif buf == HANDSHAKEN_MESSAGE: 110 print("Handshake completed.") 111 elif buf == CLOSE_MESSAGE: 112 # TODO Dup the fds and send a fake DDMS message like the actual plugin would. 113 print("Fds were closed") 114 else: 115 print("Unknown data received from socket " + str(buf)) 116 return 117 elif sock in rf: 118 (conn, addr) = sock.accept() 119 with conn: 120 print("connection accepted from " + str(addr)) 121 if listening: 122 with make_eventfd(1) as efd: 123 print("sending fds ({}, {}, {}) to target.".format(conn.fileno(), conn.fileno(), efd)) 124 send_fds(local_sock, conn.fileno(), conn.fileno(), efd) 125 else: 126 print("Closing fds since we cannot accept them.") 127 if finish_event in rf: 128 print("woke up from finish_event") 129 return 130 131def StartChildProcess(cmd_pre, cmd_post, jdwp_lib, jdwp_ops, remote_sock, can_be_runtest): 132 """ 133 Open the child java-language runtime process. 134 """ 135 full_cmd = list(cmd_pre) 136 os.set_inheritable(remote_sock.fileno(), True) 137 jdwp_arg = jdwp_lib + "=" + \ 138 jdwp_ops + "transport=dt_fd_forward,address=" + str(remote_sock.fileno()) 139 if can_be_runtest and cmd_pre[0].endswith("run-test"): 140 print("Assuming run-test. Pass --no-run-test if this isn't true") 141 full_cmd += ["--with-agent", jdwp_arg] 142 else: 143 full_cmd.append("-agentpath:" + jdwp_arg) 144 full_cmd += cmd_post 145 print("Running " + str(full_cmd)) 146 # Start the actual process with the fd being passed down. 147 proc = subprocess.Popen(full_cmd, close_fds=False) 148 # Get rid of the extra socket. 149 remote_sock.close() 150 proc.wait() 151 152def main(): 153 parser = argparse.ArgumentParser(description=""" 154 Runs a socket that forwards to dt_fds. 155 156 Pass '--' to start passing in the program we will pass the debug 157 options down to. 158 """) 159 parser.add_argument("--host", type=str, default="localhost", 160 help="Host we will listen for traffic on. Defaults to 'localhost'.") 161 parser.add_argument("--debug-lib", type=str, default="libjdwp.so", 162 help="jdwp library we pass to -agentpath:. Default is 'libjdwp.so'") 163 parser.add_argument("--debug-options", type=str, default="server=y,suspend=y,", 164 help="non-address options we pass to jdwp agent, default is " + 165 "'server=y,suspend=y,'") 166 parser.add_argument("--port", type=int, default=12345, 167 help="port we will expose the traffic on. Defaults to 12345.") 168 parser.add_argument("--no-run-test", default=False, action="store_true", 169 help="don't pass in arguments for run-test even if it looks like that is " + 170 "the program") 171 parser.add_argument("--pre-end", type=int, default=1, 172 help="number of 'rest' arguments to put before passing in the debug options") 173 end_idx = 0 if '--' not in sys.argv else sys.argv.index('--') 174 if end_idx == 0 and ('--help' in sys.argv or '-h' in sys.argv): 175 parser.print_help() 176 return 177 args = parser.parse_args(sys.argv[:end_idx][1:]) 178 rest = sys.argv[1 + end_idx:] 179 180 with make_eventfd(0) as wakeup_event: 181 with make_sockets() as (remote_sock, local_sock): 182 invoker = Process(target=StartChildProcess, 183 args=(rest[:args.pre_end], 184 rest[args.pre_end:], 185 args.debug_lib, 186 args.debug_options, 187 remote_sock, 188 not args.no_run_test)) 189 socket_handler = Process(target=HandleSockets, 190 args=(args.host, args.port, local_sock, wakeup_event)) 191 socket_handler.start() 192 invoker.start() 193 invoker.join() 194 # Write any 64 bit value to the wakeup_event to make sure that the socket handler will wake 195 # up and exit. 196 os.write(wakeup_event, b'\x00\x00\x00\x00\x00\x00\x01\x00') 197 socket_handler.join() 198 199if __name__ == '__main__': 200 main() 201