• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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