]> 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 2016 Andrei Pangin
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
21 typedef HMODULE (WINAPI *GetModuleHandle_t)(LPCTSTR lpModuleName);
22 typedef FARPROC (WINAPI *GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName);
23 typedef int (__stdcall *JVM_EnqueueOperation_t)(char* cmd, char* arg0, char* arg1, char* arg2, char* pipename);
24
25 typedef struct {
26     GetModuleHandle_t GetModuleHandleA;
27     GetProcAddress_t GetProcAddress;
28     char strJvm[32];
29     char strEnqueue[32];
30     char pipeName[MAX_PATH];
31     char args[4][MAX_PATH];
32 } CallData;
33
34
35 #pragma check_stack(off)
36
37 // This code is executed in remote JVM process; be careful with memory it accesses
38 static DWORD WINAPI remote_thread_entry(LPVOID param) {
39     CallData* data = (CallData*)param;
40
41     HMODULE libJvm = data->GetModuleHandleA(data->strJvm);
42     if (libJvm == NULL) {
43         return 1001;
44     }
45
46     JVM_EnqueueOperation_t JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue + 1);
47     if (JVM_EnqueueOperation == NULL) {
48         // Try alternative name: _JVM_EnqueueOperation@20
49         data->strEnqueue[21] = '@';
50         data->strEnqueue[22] = '2';
51         data->strEnqueue[23] = '0';
52         data->strEnqueue[24] = 0;
53
54         JVM_EnqueueOperation = (JVM_EnqueueOperation_t)data->GetProcAddress(libJvm, data->strEnqueue);
55         if (JVM_EnqueueOperation == NULL) {
56             return 1002;
57         }
58     }
59
60     return (DWORD)JVM_EnqueueOperation(data->args[0], data->args[1], data->args[2], data->args[3], data->pipeName);
61 }
62
63 static VOID WINAPI remote_thread_entry_end() {
64 }
65
66 #pragma check_stack
67
68
69 // Allocate executable memory in remote process
70 static LPTHREAD_START_ROUTINE allocate_code(HANDLE hProcess) {
71     SIZE_T codeSize = (SIZE_T)remote_thread_entry_end - (SIZE_T)remote_thread_entry;
72     LPVOID code = VirtualAllocEx(hProcess, NULL, codeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
73     if (code != NULL) {
74         WriteProcessMemory(hProcess, code, remote_thread_entry, codeSize, NULL);
75     }
76     return (LPTHREAD_START_ROUTINE)code;
77 }
78
79 // Allocate memory for CallData in remote process
80 static LPVOID allocate_data(HANDLE hProcess, char* pipeName, int argc, char** argv) {
81     CallData data;
82     data.GetModuleHandleA = GetModuleHandleA;
83     data.GetProcAddress = GetProcAddress;
84     strcpy(data.strJvm, "jvm");
85     strcpy(data.strEnqueue, "_JVM_EnqueueOperation");
86     strcpy(data.pipeName, pipeName);
87
88     int i;
89     for (i = 0; i < 4; i++) {
90         strcpy(data.args[i], i < argc ? argv[i] : "");
91     }
92
93     LPVOID remoteData = VirtualAllocEx(hProcess, NULL, sizeof(CallData), MEM_COMMIT, PAGE_READWRITE);
94     if (remoteData != NULL) {
95         WriteProcessMemory(hProcess, remoteData, &data, sizeof(data), NULL);
96     }
97     return remoteData;
98 }
99
100 static void print_error(const char* msg, DWORD code) {
101     printf("%s (error code = %d)\n", msg, code);
102 }
103
104 // If the process is owned by another user, request SeDebugPrivilege to open it.
105 // Debug privileges are typically granted to Administrators.
106 static int enable_debug_privileges() {
107     HANDLE hToken;
108     if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
109         if (!ImpersonateSelf(SecurityImpersonation) ||
110             !OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
111             return 0;
112         }
113     }
114
115     LUID luid;
116     if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
117         return 0;
118     }
119
120     TOKEN_PRIVILEGES tp;
121     tp.PrivilegeCount = 1;
122     tp.Privileges[0].Luid = luid;
123     tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
124
125     BOOL success = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
126     CloseHandle(hToken);
127     return success ? 1 : 0;
128 }
129
130 // Fail if attaching 64-bit jattach to 32-bit JVM or vice versa
131 static int check_bitness(HANDLE hProcess) {
132 #ifdef _WIN64
133     BOOL targetWow64 = FALSE;
134     if (IsWow64Process(hProcess, &targetWow64) && targetWow64) {
135         printf("Cannot attach 64-bit process to 32-bit JVM\n");
136         return 0;
137     }
138 #else
139     BOOL thisWow64 = FALSE;
140     BOOL targetWow64 = FALSE;
141     if (IsWow64Process(GetCurrentProcess(), &thisWow64) && IsWow64Process(hProcess, &targetWow64)) {
142         if (thisWow64 != targetWow64)  {
143             printf("Cannot attach 32-bit process to 64-bit JVM\n");
144             return 0;
145         }
146     }
147 #endif
148     return 1;
149 }
150
151 // The idea of Dynamic Attach on Windows is to inject a thread into remote JVM
152 // that calls JVM_EnqueueOperation() function exported by HotSpot DLL
153 static int inject_thread(int pid, char* pipeName, int argc, char** argv) {
154     HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
155     if (hProcess == NULL && GetLastError() == ERROR_ACCESS_DENIED) {
156         if (!enable_debug_privileges()) {
157             print_error("Not enough privileges", GetLastError());
158             return 0;
159         }
160         hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);
161     }
162     if (hProcess == NULL) {
163         print_error("Could not open process", GetLastError());
164         return 0;
165     }
166
167     if (!check_bitness(hProcess)) {
168         CloseHandle(hProcess);
169         return 0;
170     }
171
172     LPTHREAD_START_ROUTINE code = allocate_code(hProcess);
173     LPVOID data = code != NULL ? allocate_data(hProcess, pipeName, argc, argv) : NULL;
174     if (data == NULL) {
175         print_error("Could not allocate memory in target process", GetLastError());
176         CloseHandle(hProcess);
177         return 0;
178     }
179
180     int success = 1;
181     HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, code, data, 0, NULL);
182     if (hThread == NULL) {
183         print_error("Could not create remote thread", GetLastError());
184         success = 0;
185     } else {
186         printf("Connected to remote process\n");
187         WaitForSingleObject(hThread, INFINITE);
188         DWORD exitCode;
189         GetExitCodeThread(hThread, &exitCode);
190         if (exitCode != 0) {
191             print_error("Attach is not supported by the target process", exitCode);
192             success = 0;
193         }
194         CloseHandle(hThread);
195     }
196
197     VirtualFreeEx(hProcess, code, 0, MEM_RELEASE);
198     VirtualFreeEx(hProcess, data, 0, MEM_RELEASE);
199     CloseHandle(hProcess);
200
201     return success;
202 }
203
204 // JVM response is read from the pipe and mirrored to stdout
205 static int read_response(HANDLE hPipe) {
206     ConnectNamedPipe(hPipe, NULL);
207
208     char buf[8192];
209     DWORD bytesRead;
210     if (!ReadFile(hPipe, buf, sizeof(buf) - 1, &bytesRead, NULL)) {
211         print_error("Error reading response", GetLastError());
212         return 1;
213     }
214
215     // First line of response is the command result code
216     buf[bytesRead] = 0;
217     int result = atoi(buf);
218
219     do {
220         fwrite(buf, 1, bytesRead, stdout);
221     } while (ReadFile(hPipe, buf, sizeof(buf), &bytesRead, NULL));
222
223     return result;
224 }
225
226 int main(int argc, char** argv) {
227     if (argc < 3) {
228         printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
229                "Copyright 2021 Andrei Pangin\n"
230                "\n"
231                "Usage: jattach <pid> <cmd> [args ...]\n"
232                "\n"
233                "Commands:\n"
234                "    load  threaddump   dumpheap  setflag    properties\n"
235                "    jcmd  inspectheap  datadump  printflag  agentProperties\n"
236                );
237         return 1;
238     }
239
240     int pid = atoi(argv[1]);
241
242     char pipeName[MAX_PATH];
243     sprintf(pipeName, "\\\\.\\pipe\\javatool%d", GetTickCount());
244     HANDLE hPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
245         1, 4096, 8192, NMPWAIT_USE_DEFAULT_WAIT, NULL);
246     if (hPipe == NULL) {
247         print_error("Could not create pipe", GetLastError());
248         return 1;
249     }
250
251     if (!inject_thread(pid, pipeName, argc - 2, argv + 2)) {
252         CloseHandle(hPipe);
253         return 1;
254     }
255
256     printf("Response code = ");
257     fflush(stdout);
258
259     int result = read_response(hPipe);
260     printf("\n");
261     CloseHandle(hPipe);
262
263     return result;
264 }