1 | /* Process tracing interface `ptrace' for GNU Hurd. |
2 | Copyright (C) 1991-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <errno.h> |
20 | #include <sys/ptrace.h> |
21 | #include <sys/types.h> |
22 | #include <stdarg.h> |
23 | #include <hurd.h> |
24 | #include <hurd/signal.h> |
25 | #include <hurd/msg.h> |
26 | #include <thread_state.h> |
27 | |
28 | /* Perform process tracing functions. REQUEST is one of the values |
29 | in <sys/ptrace.h>, and determines the action to be taken. |
30 | For all requests except PTRACE_TRACEME, PID specifies the process to be |
31 | traced. |
32 | |
33 | PID and the other arguments described above for the various requests should |
34 | appear (those that are used for the particular request) as: |
35 | pid_t PID, void *ADDR, int DATA, void *ADDR2 |
36 | after PID. */ |
37 | int |
38 | ptrace (enum __ptrace_request request, ... ) |
39 | { |
40 | pid_t pid; |
41 | void *addr, *addr2; |
42 | natural_t data; |
43 | va_list ap; |
44 | |
45 | /* Read data from PID's address space, from ADDR for DATA bytes. */ |
46 | error_t read_data (task_t task, vm_address_t *ourpage, vm_size_t *size) |
47 | { |
48 | /* Read the pages containing the addressed range. */ |
49 | error_t err; |
50 | *size = round_page (addr + data) - trunc_page (addr); |
51 | err = __vm_read (task, trunc_page (addr), *size, ourpage, size); |
52 | return err; |
53 | } |
54 | |
55 | /* Fetch the thread port for PID's user thread. */ |
56 | error_t fetch_user_thread (task_t task, thread_t *thread) |
57 | { |
58 | thread_t threadbuf[3], *threads = threadbuf; |
59 | mach_msg_type_number_t nthreads = 3, i; |
60 | error_t err = __task_threads (task, &threads, &nthreads); |
61 | if (err) |
62 | return err; |
63 | if (nthreads == 0) |
64 | return EINVAL; |
65 | *thread = threads[0]; /* Assume user thread is first. */ |
66 | for (i = 1; i < nthreads; ++i) |
67 | __mach_port_deallocate (__mach_task_self (), threads[i]); |
68 | if (threads != threadbuf) |
69 | __vm_deallocate (__mach_task_self (), |
70 | (vm_address_t) threads, nthreads * sizeof threads[0]); |
71 | return 0; |
72 | } |
73 | |
74 | /* Fetch a thread state structure from PID and store it at ADDR. */ |
75 | int get_regs (int flavor, mach_msg_type_number_t count) |
76 | { |
77 | error_t err; |
78 | task_t task = __pid2task (pid); |
79 | thread_t thread; |
80 | if (task == MACH_PORT_NULL) |
81 | return -1; |
82 | err = fetch_user_thread (task, &thread); |
83 | __mach_port_deallocate (__mach_task_self (), task); |
84 | if (!err) |
85 | err = __thread_get_state (thread, flavor, addr, &count); |
86 | __mach_port_deallocate (__mach_task_self (), thread); |
87 | return err ? __hurd_fail (err) : 0; |
88 | } |
89 | |
90 | |
91 | switch (request) |
92 | { |
93 | case PTRACE_TRACEME: |
94 | /* Make this process be traced. */ |
95 | __sigfillset (&_hurdsig_traced); |
96 | __USEPORT (PROC, __proc_mark_traced (port)); |
97 | break; |
98 | |
99 | case PTRACE_CONT: |
100 | va_start (ap, request); |
101 | pid = va_arg (ap, pid_t); |
102 | addr = va_arg (ap, void *); |
103 | data = va_arg (ap, int); |
104 | va_end (ap); |
105 | { |
106 | /* Send a DATA signal to PID, telling it to take the signal |
107 | normally even if it's traced. */ |
108 | error_t err; |
109 | task_t task = __pid2task (pid); |
110 | if (task == MACH_PORT_NULL) |
111 | return -1; |
112 | if (data == SIGKILL) |
113 | err = __task_terminate (task); |
114 | else |
115 | { |
116 | if (addr != (void *) 1) |
117 | { |
118 | /* Move the user thread's PC to ADDR. */ |
119 | thread_t thread; |
120 | err = fetch_user_thread (task, &thread); |
121 | if (!err) |
122 | { |
123 | struct machine_thread_state state; |
124 | mach_msg_type_number_t count = MACHINE_THREAD_STATE_COUNT; |
125 | err = __thread_get_state (thread, |
126 | MACHINE_THREAD_STATE_FLAVOR, |
127 | (natural_t *) &state, &count); |
128 | if (!err) |
129 | { |
130 | MACHINE_THREAD_STATE_SET_PC (&state, addr); |
131 | err = __thread_set_state (thread, |
132 | MACHINE_THREAD_STATE_FLAVOR, |
133 | (natural_t *) &state, count); |
134 | } |
135 | |
136 | } |
137 | __mach_port_deallocate (__mach_task_self (), thread); |
138 | } |
139 | else |
140 | err = 0; |
141 | |
142 | if (! err) |
143 | /* Tell the process to take the signal (or just resume if 0). */ |
144 | err = HURD_MSGPORT_RPC |
145 | (__USEPORT (PROC, __proc_getmsgport (port, pid, &msgport)), |
146 | 0, 0, __msg_sig_post_untraced (msgport, data, 0, task)); |
147 | } |
148 | __mach_port_deallocate (__mach_task_self (), task); |
149 | return err ? __hurd_fail (err) : 0; |
150 | } |
151 | |
152 | case PTRACE_KILL: |
153 | va_start (ap, request); |
154 | pid = va_arg (ap, pid_t); |
155 | va_end (ap); |
156 | /* SIGKILL always just terminates the task, |
157 | so normal kill is just the same when traced. */ |
158 | return __kill (pid, SIGKILL); |
159 | |
160 | case PTRACE_SINGLESTEP: |
161 | /* This is a machine-dependent kernel RPC on |
162 | machines that support it. Punt. */ |
163 | return __hurd_fail (EOPNOTSUPP); |
164 | |
165 | case PTRACE_ATTACH: |
166 | case PTRACE_DETACH: |
167 | va_start (ap, request); |
168 | pid = va_arg (ap, pid_t); |
169 | va_end (ap); |
170 | { |
171 | /* Tell PID to set or clear its trace bit. */ |
172 | error_t err; |
173 | mach_port_t msgport; |
174 | task_t task = __pid2task (pid); |
175 | if (task == MACH_PORT_NULL) |
176 | return -1; |
177 | err = __USEPORT (PROC, __proc_getmsgport (port, pid, &msgport)); |
178 | if (! err) |
179 | { |
180 | err = __msg_set_init_int (msgport, task, INIT_TRACEMASK, |
181 | request == PTRACE_DETACH ? 0 |
182 | : ~(sigset_t) 0); |
183 | if (! err) |
184 | { |
185 | if (request == PTRACE_ATTACH) |
186 | /* Now stop the process. */ |
187 | err = __msg_sig_post (msgport, SIGSTOP, 0, task); |
188 | else |
189 | /* Resume the process from tracing stop. */ |
190 | err = __msg_sig_post_untraced (msgport, 0, 0, task); |
191 | } |
192 | __mach_port_deallocate (__mach_task_self (), msgport); |
193 | } |
194 | __mach_port_deallocate (__mach_task_self (), task); |
195 | return err ? __hurd_fail (err) : 0; |
196 | } |
197 | |
198 | case PTRACE_PEEKTEXT: |
199 | case PTRACE_PEEKDATA: |
200 | va_start (ap, request); |
201 | pid = va_arg (ap, pid_t); |
202 | addr = va_arg (ap, void *); |
203 | va_end (ap); |
204 | { |
205 | /* Read the page (or two pages, if the word lies on a boundary) |
206 | containing the addressed word. */ |
207 | error_t err; |
208 | vm_address_t ourpage; |
209 | vm_size_t size; |
210 | natural_t word; |
211 | task_t task = __pid2task (pid); |
212 | if (task == MACH_PORT_NULL) |
213 | return -1; |
214 | data = sizeof word; |
215 | ourpage = 0; |
216 | size = 0; |
217 | err = read_data (task, &ourpage, &size); |
218 | __mach_port_deallocate (__mach_task_self (), task); |
219 | if (err) |
220 | return __hurd_fail (err); |
221 | word = *(natural_t *) ((vm_address_t) addr - trunc_page (addr) |
222 | + ourpage); |
223 | __vm_deallocate (__mach_task_self (), ourpage, size); |
224 | return word; |
225 | } |
226 | |
227 | case PTRACE_PEEKUSER: |
228 | case PTRACE_POKEUSER: |
229 | /* U area, what's that? */ |
230 | return __hurd_fail (EOPNOTSUPP); |
231 | |
232 | case PTRACE_GETREGS: |
233 | case PTRACE_SETREGS: |
234 | va_start (ap, request); |
235 | pid = va_arg (ap, pid_t); |
236 | addr = va_arg (ap, void *); |
237 | va_end (ap); |
238 | return get_regs (MACHINE_THREAD_STATE_FLAVOR, |
239 | MACHINE_THREAD_STATE_COUNT); |
240 | |
241 | case PTRACE_GETFPREGS: |
242 | case PTRACE_SETFPREGS: |
243 | va_start (ap, request); |
244 | pid = va_arg (ap, pid_t); |
245 | addr = va_arg (ap, void *); |
246 | va_end (ap); |
247 | #ifdef MACHINE_THREAD_FLOAT_STATE_FLAVOR |
248 | return get_regs (MACHINE_THREAD_FLOAT_STATE_FLAVOR, |
249 | MACHINE_THREAD_FLOAT_STATE_COUNT); |
250 | #else |
251 | return __hurd_fail (EOPNOTSUPP); |
252 | #endif |
253 | |
254 | case PTRACE_GETFPAREGS: |
255 | case PTRACE_SETFPAREGS: |
256 | va_start (ap, request); |
257 | pid = va_arg (ap, pid_t); |
258 | addr = va_arg (ap, void *); |
259 | va_end (ap); |
260 | #ifdef MACHINE_THREAD_FPA_STATE_FLAVOR |
261 | return get_regs (MACHINE_THREAD_FPA_STATE_FLAVOR, |
262 | MACHINE_THREAD_FPA_STATE_COUNT); |
263 | #else |
264 | return __hurd_fail (EOPNOTSUPP); |
265 | #endif |
266 | |
267 | case PTRACE_POKETEXT: |
268 | case PTRACE_POKEDATA: |
269 | va_start (ap, request); |
270 | pid = va_arg (ap, pid_t); |
271 | addr = va_arg (ap, void *); |
272 | data = va_arg (ap, int); |
273 | va_end (ap); |
274 | { |
275 | /* Read the page (or two pages, if the word lies on a boundary) |
276 | containing the addressed word. */ |
277 | error_t err; |
278 | vm_address_t ourpage; |
279 | vm_size_t size; |
280 | task_t task = __pid2task (pid); |
281 | if (task == MACH_PORT_NULL) |
282 | return -1; |
283 | data = sizeof (natural_t); |
284 | ourpage = 0; |
285 | size = 0; |
286 | err = read_data (task, &ourpage, &size); |
287 | |
288 | if (!err) |
289 | { |
290 | /* Now modify the specified word and write the page back. */ |
291 | *(natural_t *) ((vm_address_t) addr - trunc_page (addr) |
292 | + ourpage) = data; |
293 | err = __vm_write (task, trunc_page (addr), ourpage, size); |
294 | __vm_deallocate (__mach_task_self (), ourpage, size); |
295 | } |
296 | |
297 | __mach_port_deallocate (__mach_task_self (), task); |
298 | return err ? __hurd_fail (err) : 0; |
299 | } |
300 | |
301 | case PTRACE_READDATA: |
302 | case PTRACE_READTEXT: |
303 | va_start (ap, request); |
304 | pid = va_arg (ap, pid_t); |
305 | addr = va_arg (ap, void *); |
306 | data = va_arg (ap, int); |
307 | addr2 = va_arg (ap, void *); |
308 | va_end (ap); |
309 | { |
310 | error_t err; |
311 | vm_address_t ourpage; |
312 | vm_size_t size; |
313 | task_t task = __pid2task (pid); |
314 | if (task == MACH_PORT_NULL) |
315 | return -1; |
316 | if (((vm_address_t) addr2 + data) % __vm_page_size == 0) |
317 | { |
318 | /* Perhaps we can write directly to the user's buffer. */ |
319 | ourpage = (vm_address_t) addr2; |
320 | size = data; |
321 | } |
322 | else |
323 | { |
324 | ourpage = 0; |
325 | size = 0; |
326 | } |
327 | err = read_data (task, &ourpage, &size); |
328 | __mach_port_deallocate (__mach_task_self (), task); |
329 | if (!err && ourpage != (vm_address_t) addr2) |
330 | { |
331 | memcpy (addr2, (void *) ourpage, data); |
332 | __vm_deallocate (__mach_task_self (), ourpage, size); |
333 | } |
334 | return err ? __hurd_fail (err) : 0; |
335 | } |
336 | |
337 | case PTRACE_WRITEDATA: |
338 | case PTRACE_WRITETEXT: |
339 | va_start (ap, request); |
340 | pid = va_arg (ap, pid_t); |
341 | addr = va_arg (ap, void *); |
342 | data = va_arg (ap, int); |
343 | addr2 = va_arg (ap, void *); |
344 | va_end (ap); |
345 | { |
346 | error_t err; |
347 | vm_address_t ourpage; |
348 | vm_size_t size; |
349 | task_t task = __pid2task (pid); |
350 | if (task == MACH_PORT_NULL) |
351 | return -1; |
352 | if ((vm_address_t) addr % __vm_page_size == 0 |
353 | && (vm_address_t) data % __vm_page_size == 0) |
354 | { |
355 | /* Writing whole pages; can go directly from the user's buffer. */ |
356 | ourpage = (vm_address_t) addr2; |
357 | size = data; |
358 | err = 0; |
359 | } |
360 | else |
361 | { |
362 | /* Read the task's pages and modify our own copy. */ |
363 | ourpage = 0; |
364 | size = 0; |
365 | err = read_data (task, &ourpage, &size); |
366 | if (!err) |
367 | memcpy ((void *) ((vm_address_t) addr - trunc_page (addr) |
368 | + ourpage), |
369 | addr2, |
370 | data); |
371 | } |
372 | if (!err) |
373 | /* Write back the modified pages. */ |
374 | err = __vm_write (task, trunc_page (addr), ourpage, size); |
375 | __mach_port_deallocate (__mach_task_self (), task); |
376 | return err ? __hurd_fail (err) : 0; |
377 | } |
378 | |
379 | default: |
380 | errno = EINVAL; |
381 | return -1; |
382 | } |
383 | |
384 | return 0; |
385 | } |
386 | |