1 | /* Process tracing interface `ptrace' for GNU Hurd. |
2 | Copyright (C) 1991-2024 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 | mach_msg_type_number_t nread; |
51 | *size = round_page (addr + data) - trunc_page (addr); |
52 | err = __vm_read (task, trunc_page (addr), *size, ourpage, &nread); |
53 | if (!err) |
54 | *size = nread; |
55 | return err; |
56 | } |
57 | |
58 | /* Fetch the thread port for PID's user thread. */ |
59 | error_t fetch_user_thread (task_t task, thread_t *thread) |
60 | { |
61 | thread_t threadbuf[3], *threads = threadbuf; |
62 | mach_msg_type_number_t nthreads = 3, i; |
63 | error_t err = __task_threads (task, &threads, &nthreads); |
64 | if (err) |
65 | return err; |
66 | if (nthreads == 0) |
67 | return EINVAL; |
68 | *thread = threads[0]; /* Assume user thread is first. */ |
69 | for (i = 1; i < nthreads; ++i) |
70 | __mach_port_deallocate (__mach_task_self (), threads[i]); |
71 | if (threads != threadbuf) |
72 | __vm_deallocate (__mach_task_self (), |
73 | (vm_address_t) threads, nthreads * sizeof threads[0]); |
74 | return 0; |
75 | } |
76 | |
77 | /* Fetch a thread state structure from PID and store it at ADDR. */ |
78 | int get_regs (int flavor, mach_msg_type_number_t count) |
79 | { |
80 | error_t err; |
81 | task_t task = __pid2task (pid); |
82 | thread_t thread; |
83 | if (task == MACH_PORT_NULL) |
84 | return -1; |
85 | err = fetch_user_thread (task, &thread); |
86 | __mach_port_deallocate (__mach_task_self (), task); |
87 | if (!err) |
88 | err = __thread_get_state (thread, flavor, addr, &count); |
89 | __mach_port_deallocate (__mach_task_self (), thread); |
90 | return err ? __hurd_fail (err) : 0; |
91 | } |
92 | |
93 | |
94 | switch (request) |
95 | { |
96 | case PTRACE_TRACEME: |
97 | /* Make this process be traced. */ |
98 | __sigfillset (&_hurdsig_traced); |
99 | __USEPORT (PROC, __proc_mark_traced (port)); |
100 | break; |
101 | |
102 | case PTRACE_CONT: |
103 | va_start (ap, request); |
104 | pid = va_arg (ap, pid_t); |
105 | addr = va_arg (ap, void *); |
106 | data = va_arg (ap, int); |
107 | va_end (ap); |
108 | { |
109 | /* Send a DATA signal to PID, telling it to take the signal |
110 | normally even if it's traced. */ |
111 | error_t err; |
112 | task_t task = __pid2task (pid); |
113 | if (task == MACH_PORT_NULL) |
114 | return -1; |
115 | if (data == SIGKILL) |
116 | err = __task_terminate (task); |
117 | else |
118 | { |
119 | if (addr != (void *) 1) |
120 | { |
121 | /* Move the user thread's PC to ADDR. */ |
122 | thread_t thread; |
123 | err = fetch_user_thread (task, &thread); |
124 | if (!err) |
125 | { |
126 | struct machine_thread_state state; |
127 | mach_msg_type_number_t count = MACHINE_THREAD_STATE_COUNT; |
128 | err = __thread_get_state (thread, |
129 | MACHINE_THREAD_STATE_FLAVOR, |
130 | (natural_t *) &state, &count); |
131 | if (!err) |
132 | { |
133 | MACHINE_THREAD_STATE_SET_PC (&state, addr); |
134 | err = __thread_set_state (thread, |
135 | MACHINE_THREAD_STATE_FLAVOR, |
136 | (natural_t *) &state, count); |
137 | } |
138 | |
139 | } |
140 | __mach_port_deallocate (__mach_task_self (), thread); |
141 | } |
142 | else |
143 | err = 0; |
144 | |
145 | if (! err) |
146 | /* Tell the process to take the signal (or just resume if 0). */ |
147 | err = HURD_MSGPORT_RPC |
148 | (__USEPORT (PROC, __proc_getmsgport (port, pid, &msgport)), |
149 | 0, 0, __msg_sig_post_untraced (msgport, data, 0, task)); |
150 | } |
151 | __mach_port_deallocate (__mach_task_self (), task); |
152 | return err ? __hurd_fail (err) : 0; |
153 | } |
154 | |
155 | case PTRACE_KILL: |
156 | va_start (ap, request); |
157 | pid = va_arg (ap, pid_t); |
158 | va_end (ap); |
159 | /* SIGKILL always just terminates the task, |
160 | so normal kill is just the same when traced. */ |
161 | return __kill (pid, SIGKILL); |
162 | |
163 | case PTRACE_SINGLESTEP: |
164 | /* This is a machine-dependent kernel RPC on |
165 | machines that support it. Punt. */ |
166 | return __hurd_fail (EOPNOTSUPP); |
167 | |
168 | case PTRACE_ATTACH: |
169 | case PTRACE_DETACH: |
170 | va_start (ap, request); |
171 | pid = va_arg (ap, pid_t); |
172 | va_end (ap); |
173 | { |
174 | /* Tell PID to set or clear its trace bit. */ |
175 | error_t err; |
176 | mach_port_t msgport; |
177 | task_t task = __pid2task (pid); |
178 | if (task == MACH_PORT_NULL) |
179 | return -1; |
180 | err = __USEPORT (PROC, __proc_getmsgport (port, pid, &msgport)); |
181 | if (! err) |
182 | { |
183 | err = __msg_set_init_int (msgport, task, INIT_TRACEMASK, |
184 | request == PTRACE_DETACH ? 0 |
185 | : ~(sigset_t) 0); |
186 | if (! err) |
187 | { |
188 | if (request == PTRACE_ATTACH) |
189 | /* Now stop the process. */ |
190 | err = __msg_sig_post (msgport, SIGSTOP, 0, task); |
191 | else |
192 | /* Resume the process from tracing stop. */ |
193 | err = __msg_sig_post_untraced (msgport, 0, 0, task); |
194 | } |
195 | __mach_port_deallocate (__mach_task_self (), msgport); |
196 | } |
197 | __mach_port_deallocate (__mach_task_self (), task); |
198 | return err ? __hurd_fail (err) : 0; |
199 | } |
200 | |
201 | case PTRACE_PEEKTEXT: |
202 | case PTRACE_PEEKDATA: |
203 | va_start (ap, request); |
204 | pid = va_arg (ap, pid_t); |
205 | addr = va_arg (ap, void *); |
206 | va_end (ap); |
207 | { |
208 | /* Read the page (or two pages, if the word lies on a boundary) |
209 | containing the addressed word. */ |
210 | error_t err; |
211 | vm_address_t ourpage; |
212 | vm_size_t size; |
213 | natural_t word; |
214 | task_t task = __pid2task (pid); |
215 | if (task == MACH_PORT_NULL) |
216 | return -1; |
217 | data = sizeof word; |
218 | ourpage = 0; |
219 | size = 0; |
220 | err = read_data (task, &ourpage, &size); |
221 | __mach_port_deallocate (__mach_task_self (), task); |
222 | if (err) |
223 | return __hurd_fail (err); |
224 | word = *(natural_t *) ((vm_address_t) addr - trunc_page (addr) |
225 | + ourpage); |
226 | __vm_deallocate (__mach_task_self (), ourpage, size); |
227 | return word; |
228 | } |
229 | |
230 | case PTRACE_PEEKUSER: |
231 | case PTRACE_POKEUSER: |
232 | /* U area, what's that? */ |
233 | return __hurd_fail (EOPNOTSUPP); |
234 | |
235 | case PTRACE_GETREGS: |
236 | case PTRACE_SETREGS: |
237 | va_start (ap, request); |
238 | pid = va_arg (ap, pid_t); |
239 | addr = va_arg (ap, void *); |
240 | va_end (ap); |
241 | return get_regs (MACHINE_THREAD_STATE_FLAVOR, |
242 | MACHINE_THREAD_STATE_COUNT); |
243 | |
244 | case PTRACE_GETFPREGS: |
245 | case PTRACE_SETFPREGS: |
246 | va_start (ap, request); |
247 | pid = va_arg (ap, pid_t); |
248 | addr = va_arg (ap, void *); |
249 | va_end (ap); |
250 | #ifdef MACHINE_THREAD_FLOAT_STATE_FLAVOR |
251 | return get_regs (MACHINE_THREAD_FLOAT_STATE_FLAVOR, |
252 | MACHINE_THREAD_FLOAT_STATE_COUNT); |
253 | #else |
254 | return __hurd_fail (EOPNOTSUPP); |
255 | #endif |
256 | |
257 | case PTRACE_GETFPAREGS: |
258 | case PTRACE_SETFPAREGS: |
259 | va_start (ap, request); |
260 | pid = va_arg (ap, pid_t); |
261 | addr = va_arg (ap, void *); |
262 | va_end (ap); |
263 | #ifdef MACHINE_THREAD_FPA_STATE_FLAVOR |
264 | return get_regs (MACHINE_THREAD_FPA_STATE_FLAVOR, |
265 | MACHINE_THREAD_FPA_STATE_COUNT); |
266 | #else |
267 | return __hurd_fail (EOPNOTSUPP); |
268 | #endif |
269 | |
270 | case PTRACE_POKETEXT: |
271 | case PTRACE_POKEDATA: |
272 | va_start (ap, request); |
273 | pid = va_arg (ap, pid_t); |
274 | addr = va_arg (ap, void *); |
275 | data = va_arg (ap, int); |
276 | va_end (ap); |
277 | { |
278 | /* Read the page (or two pages, if the word lies on a boundary) |
279 | containing the addressed word. */ |
280 | error_t err; |
281 | vm_address_t ourpage; |
282 | vm_size_t size; |
283 | task_t task = __pid2task (pid); |
284 | if (task == MACH_PORT_NULL) |
285 | return -1; |
286 | data = sizeof (natural_t); |
287 | ourpage = 0; |
288 | size = 0; |
289 | err = read_data (task, &ourpage, &size); |
290 | |
291 | if (!err) |
292 | { |
293 | /* Now modify the specified word and write the page back. */ |
294 | *(natural_t *) ((vm_address_t) addr - trunc_page (addr) |
295 | + ourpage) = data; |
296 | err = __vm_write (task, trunc_page (addr), ourpage, size); |
297 | __vm_deallocate (__mach_task_self (), ourpage, size); |
298 | } |
299 | |
300 | __mach_port_deallocate (__mach_task_self (), task); |
301 | return err ? __hurd_fail (err) : 0; |
302 | } |
303 | |
304 | case PTRACE_READDATA: |
305 | case PTRACE_READTEXT: |
306 | va_start (ap, request); |
307 | pid = va_arg (ap, pid_t); |
308 | addr = va_arg (ap, void *); |
309 | data = va_arg (ap, int); |
310 | addr2 = va_arg (ap, void *); |
311 | va_end (ap); |
312 | { |
313 | error_t err; |
314 | vm_address_t ourpage; |
315 | vm_size_t size; |
316 | task_t task = __pid2task (pid); |
317 | if (task == MACH_PORT_NULL) |
318 | return -1; |
319 | if (((vm_address_t) addr2 + data) % __vm_page_size == 0) |
320 | { |
321 | /* Perhaps we can write directly to the user's buffer. */ |
322 | ourpage = (vm_address_t) addr2; |
323 | size = data; |
324 | } |
325 | else |
326 | { |
327 | ourpage = 0; |
328 | size = 0; |
329 | } |
330 | err = read_data (task, &ourpage, &size); |
331 | __mach_port_deallocate (__mach_task_self (), task); |
332 | if (!err && ourpage != (vm_address_t) addr2) |
333 | { |
334 | memcpy (addr2, (void *) ourpage, data); |
335 | __vm_deallocate (__mach_task_self (), ourpage, size); |
336 | } |
337 | return err ? __hurd_fail (err) : 0; |
338 | } |
339 | |
340 | case PTRACE_WRITEDATA: |
341 | case PTRACE_WRITETEXT: |
342 | va_start (ap, request); |
343 | pid = va_arg (ap, pid_t); |
344 | addr = va_arg (ap, void *); |
345 | data = va_arg (ap, int); |
346 | addr2 = va_arg (ap, void *); |
347 | va_end (ap); |
348 | { |
349 | error_t err; |
350 | vm_address_t ourpage; |
351 | vm_size_t size; |
352 | task_t task = __pid2task (pid); |
353 | if (task == MACH_PORT_NULL) |
354 | return -1; |
355 | if ((vm_address_t) addr % __vm_page_size == 0 |
356 | && (vm_address_t) data % __vm_page_size == 0) |
357 | { |
358 | /* Writing whole pages; can go directly from the user's buffer. */ |
359 | ourpage = (vm_address_t) addr2; |
360 | size = data; |
361 | err = 0; |
362 | } |
363 | else |
364 | { |
365 | /* Read the task's pages and modify our own copy. */ |
366 | ourpage = 0; |
367 | size = 0; |
368 | err = read_data (task, &ourpage, &size); |
369 | if (!err) |
370 | memcpy ((void *) ((vm_address_t) addr - trunc_page (addr) |
371 | + ourpage), |
372 | addr2, |
373 | data); |
374 | } |
375 | if (!err) |
376 | /* Write back the modified pages. */ |
377 | err = __vm_write (task, trunc_page (addr), ourpage, size); |
378 | __mach_port_deallocate (__mach_task_self (), task); |
379 | return err ? __hurd_fail (err) : 0; |
380 | } |
381 | |
382 | default: |
383 | return __hurd_fail (EINVAL); |
384 | } |
385 | |
386 | return 0; |
387 | } |
388 | |