]> git.sven.stormbind.net Git - sven/jattach.git/blob - src/windows/jattach.c
New upstream version 2.2
[sven/jattach.git] / src / windows / jattach.c
1 /*
2  * Copyright jattach authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <windows.h>
20 #include <sddl.h>
21
22 typedef HMODULE (WINAPI *GetModuleHandle_t)(LPCTSTR lpModuleName);
23 typedef FARPROC (WINAPI *GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName);
24 typedef int (__stdcall *JVM_EnqueueOperation_t)(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename);
25
26 typedef struct {
27     GetModuleHandle_t GetModuleHandleA;
28     GetProcAddress_t GetProcAddress;
29     char strJvm[32];
30     char strEnqueue[32];
31     char pipeName[MAX_PATH];
32     char args[4][MAX_PATH];
33 } CallData;
34
35
36 #pragma check_stack(off)
37
38 // This code is executed in remote JVM process; be careful with memory it accesses
39 static DWORD WINAPI remote_thread_entry(LPVOID param) {
40     CallData* data = (CallData*)param;
41
42     HMODULE libJvm = data->GetModuleHandleA(data->strJvm);
43     if (libJvm == NULL) {
44         return 1001;
45     }
46
47     JVM_EnqueueOperation_t JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue + 1);
48     if (JVM_EnqueueOperation == NULL) {
49         // Try alternative name: _JVM_EnqueueOperation@20
50         data->strEnqueue[21] = '@';
51         data->strEnqueue[22] = '2';
52         data->strEnqueue[23] = '0';
53         data->strEnqueue[24] = 0;
54
55         JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue);
56         if (JVM_EnqueueOperation == NULL) {
57             return 1002;
58         }
59     }
60
61     return (DWORD)JVM_EnqueueOperation(data->args[0], data->args[1], data->args[2], data->args[3], data->pipeName);
62 }
63
64 static VOID WINAPI remote_thread_entry_end() {
65 }
66
67 #pragma check_stack
68
69
70 // Allocate executable memory in remote process
71 static LPTHREAD_START_ROUTINE allocate_code(HANDLE hProcess) {
72     SIZE_T codeSize = (SIZE_T)remote_thread_entry_end - (SIZE_T)remote_thread_entry;
73     LPVOID code = VirtualAllocEx(hProcess, NULL, codeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
74     if (code != NULL) {
75         WriteProcessMemory(hProcess, code, remote_thread_entry, codeSize, NULL);
76     }
77     return (LPTHREAD_START_ROUTINE)code;
78 }
79
80 // Allocate memory for CallData in remote process
81 static LPVOID allocate_data(HANDLE hProcess, char* pipeName, int argc, char** argv) {
82     CallData data;
83     data.GetModuleHandleA = GetModuleHandleA;
84     data.GetProcAddress = GetProcAddress;
85     strcpy(data.strJvm, "jvm");
86     strcpy(data.strEnqueue, "_JVM_EnqueueOperation");
87     strcpy(data.pipeName, pipeName);
88     data.args[0][0] = data.args[1][0] = data.args[2][0] = data.args[3][0] = 0;
89
90     // jcmd has 2 arguments maximum; merge excessive arguments into one
91     int cmd_args = argc >= 2 && strcmp(argv[0], "jcmd") == 0 ? 2 : argc >= 4 ? 4 : argc;
92
93     size_t n = 0;
94     int i;
95     for (i = 0; i < argc; i++) {
96         if (i < cmd_args) {
97             n = snprintf(data.args[i], sizeof(data.args[i]), "%s", argv[i]);
98         } else if (n < sizeof(data.args[cmd_args - 1])) {
99             n += snprintf(data.args[cmd_args - 1] + n, sizeof(data.args[cmd_args - 1]) - n, " %s", argv[i]);
100         }
101     }
102
103     LPVOID remoteData = VirtualAllocEx(hProcess, NULL, sizeof(CallData), MEM_COMMIT, PAGE_READWRITE);
104     if (remoteData != NULL) {
105         WriteProcessMemory(hProcess, remoteData, &data, sizeof(data), NULL);
106     }
107     return remoteData;
108 }
109
110 static void print_error(const char* msg, DWORD code) {
111     printf("%s (error code = %d)\n", msg, code);
112 }
113
114 // If the process is owned by another user, request SeDebugPrivilege to open it.
115 // Debug privileges are typically granted to Administrators.
116 static int enable_debug_privileges() {
117     HANDLE hToken;
118     if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
119         if (!ImpersonateSelf(SecurityImpersonation) ||
120             !OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
121             return 0;
122         }
123     }
124
125     LUID luid;
126     if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
127         return 0;
128     }
129
130     TOKEN_PRIVILEGES tp;
131     tp.PrivilegeCount = 1;
132     tp.Privileges[0].Luid = luid;
133     tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
134
135     BOOL success = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
136     CloseHandle(hToken);
137     return success ? 1 : 0;
138 }
139
140 // Fail if attaching 64-bit jattach to 32-bit JVM or vice versa
141 static int check_bitness(HANDLE hProcess) {
142 #ifdef _WIN64
143     BOOL targetWow64 = FALSE;
144     if (IsWow64Process(hProcess, &targetWow64) && targetWow64) {
145         printf("Cannot attach 64-bit process to 32-bit JVM\n");
146         return 0;
147     }
148 #else
149     BOOL thisWow64 = FALSE;
150     BOOL targetWow64 = FALSE;
151     if (IsWow64Process(GetCurrentProcess(), &thisWow64) && IsWow64Process(hProcess, &targetWow64)) {
152         if (thisWow64 != targetWow64)  {
153             printf("Cannot attach 32-bit process to 64-bit JVM\n");
154             return 0;
155         }
156     }
157 #endif
158     return 1;
159 }
160
161 // The idea of Dynamic Attach on Windows is to inject a thread into remote JVM
162 // that calls JVM_EnqueueOperation() function exported by HotSpot DLL
163 static int inject_thread(int pid, char* pipeName, int argc, char** argv) {
164     HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
165     if (hProcess == NULL && GetLastError() == ERROR_ACCESS_DENIED) {
166         if (!enable_debug_privileges()) {
167             print_error("Not enough privileges", GetLastError());
168             return 0;
169         }
170         hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
171     }
172     if (hProcess == NULL) {
173         print_error("Could not open process", GetLastError());
174         return 0;
175     }
176
177     if (!check_bitness(hProcess)) {
178         CloseHandle(hProcess);
179         return 0;
180     }
181
182     LPTHREAD_START_ROUTINE code = allocate_code(hProcess);
183     LPVOID data = code != NULL ? allocate_data(hProcess, pipeName, argc, argv) : NULL;
184     if (data == NULL) {
185         print_error("Could not allocate memory in target process", GetLastError());
186         CloseHandle(hProcess);
187         return 0;
188     }
189
190     int success = 1;
191     HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, code, data, 0, NULL);
192     if (hThread == NULL) {
193         print_error("Could not create remote thread", GetLastError());
194         success = 0;
195     } else {
196         printf("Connected to remote process\n");
197         WaitForSingleObject(hThread, INFINITE);
198         DWORD exitCode;
199         GetExitCodeThread(hThread, &exitCode);
200         if (exitCode != 0) {
201             print_error("Attach is not supported by the target process", exitCode);
202             success = 0;
203         }
204         CloseHandle(hThread);
205     }
206
207     VirtualFreeEx(hProcess, code, 0, MEM_RELEASE);
208     VirtualFreeEx(hProcess, data, 0, MEM_RELEASE);
209     CloseHandle(hProcess);
210
211     return success;
212 }
213
214 // JVM response is read from the pipe and mirrored to stdout
215 static int read_response(HANDLE hPipe, int print_output) {
216     ConnectNamedPipe(hPipe, NULL);
217
218     char buf[8192];
219     DWORD bytesRead;
220     if (!ReadFile(hPipe, buf, sizeof(buf) - 1, &bytesRead, NULL)) {
221         print_error("Error reading response", GetLastError());
222         return 1;
223     }
224
225     // First line of response is the command result code
226     buf[bytesRead] = 0;
227     int result = atoi(buf);
228
229     if (print_output) {
230         // Mirror JVM response to stdout
231         printf("JVM response code = ");
232         do {
233             fwrite(buf, 1, bytesRead, stdout);
234         } while (ReadFile(hPipe, buf, sizeof(buf), &bytesRead, NULL));
235          printf("\n");
236      }
237
238     return result;
239 }
240
241 int jattach(int pid, int argc, char** argv, int print_output) {
242     // When attaching as an Administrator, make sure the target process can connect to our pipe,
243     // i.e. allow read-write access to everyone. For the complete format description, see
244     // https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format
245     SECURITY_ATTRIBUTES sec = {sizeof(SECURITY_ATTRIBUTES), NULL, FALSE};
246     ConvertStringSecurityDescriptorToSecurityDescriptor("D:(A;;GRGW;;;WD)", SDDL_REVISION_1,
247                                                         &sec.lpSecurityDescriptor, NULL);
248
249     char pipeName[MAX_PATH];
250     sprintf(pipeName, "\\\\.\\pipe\\javatool%d", GetTickCount());
251     HANDLE hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
252                                    1, 4096, 8192, NMPWAIT_USE_DEFAULT_WAIT, &sec);
253     if (hPipe == INVALID_HANDLE_VALUE) {
254         print_error("Could not create pipe", GetLastError());
255         LocalFree(sec.lpSecurityDescriptor);
256         return 1;
257     }
258
259     LocalFree(sec.lpSecurityDescriptor);
260
261     if (!inject_thread(pid, pipeName, argc, argv)) {
262         CloseHandle(hPipe);
263         return 1;
264     }
265
266     int result = read_response(hPipe, print_output);
267     CloseHandle(hPipe);
268
269     return result;
270 }
271
272 #ifdef JATTACH_VERSION
273
274 int main(int argc, char** argv) {
275     if (argc < 3) {
276         printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
277                "\n"
278                "Usage: jattach <pid> <cmd> [args ...]\n"
279                "\n"
280                "Commands:\n"
281                "    load  threaddump   dumpheap  setflag    properties\n"
282                "    jcmd  inspectheap  datadump  printflag  agentProperties\n"
283                );
284         return 1;
285     }
286
287     int pid = atoi(argv[1]);
288     if (pid <= 0) {
289         fprintf(stderr, "%s is not a valid process ID\n", argv[1]);
290         return 1;
291     }
292
293     return jattach(pid, argc - 2, argv + 2, 1);
294 }
295
296 #endif // JATTACH_VERSION