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