386 lines
11 KiB
C
386 lines
11 KiB
C
|
/* Process tracing interface `ptrace' for GNU Hurd.
|
||
|
Copyright (C) 1991-2022 Free Software Foundation, Inc.
|
||
|
This file is part of the GNU C Library.
|
||
|
|
||
|
The GNU C Library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Lesser General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2.1 of the License, or (at your option) any later version.
|
||
|
|
||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
Lesser General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser General Public
|
||
|
License along with the GNU C Library; if not, see
|
||
|
<https://www.gnu.org/licenses/>. */
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <sys/ptrace.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <hurd.h>
|
||
|
#include <hurd/signal.h>
|
||
|
#include <hurd/msg.h>
|
||
|
#include <thread_state.h>
|
||
|
|
||
|
/* Perform process tracing functions. REQUEST is one of the values
|
||
|
in <sys/ptrace.h>, and determines the action to be taken.
|
||
|
For all requests except PTRACE_TRACEME, PID specifies the process to be
|
||
|
traced.
|
||
|
|
||
|
PID and the other arguments described above for the various requests should
|
||
|
appear (those that are used for the particular request) as:
|
||
|
pid_t PID, void *ADDR, int DATA, void *ADDR2
|
||
|
after PID. */
|
||
|
int
|
||
|
ptrace (enum __ptrace_request request, ... )
|
||
|
{
|
||
|
pid_t pid;
|
||
|
void *addr, *addr2;
|
||
|
natural_t data;
|
||
|
va_list ap;
|
||
|
|
||
|
/* Read data from PID's address space, from ADDR for DATA bytes. */
|
||
|
error_t read_data (task_t task, vm_address_t *ourpage, vm_size_t *size)
|
||
|
{
|
||
|
/* Read the pages containing the addressed range. */
|
||
|
error_t err;
|
||
|
*size = round_page (addr + data) - trunc_page (addr);
|
||
|
err = __vm_read (task, trunc_page (addr), *size, ourpage, size);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
/* Fetch the thread port for PID's user thread. */
|
||
|
error_t fetch_user_thread (task_t task, thread_t *thread)
|
||
|
{
|
||
|
thread_t threadbuf[3], *threads = threadbuf;
|
||
|
mach_msg_type_number_t nthreads = 3, i;
|
||
|
error_t err = __task_threads (task, &threads, &nthreads);
|
||
|
if (err)
|
||
|
return err;
|
||
|
if (nthreads == 0)
|
||
|
return EINVAL;
|
||
|
*thread = threads[0]; /* Assume user thread is first. */
|
||
|
for (i = 1; i < nthreads; ++i)
|
||
|
__mach_port_deallocate (__mach_task_self (), threads[i]);
|
||
|
if (threads != threadbuf)
|
||
|
__vm_deallocate (__mach_task_self (),
|
||
|
(vm_address_t) threads, nthreads * sizeof threads[0]);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Fetch a thread state structure from PID and store it at ADDR. */
|
||
|
int get_regs (int flavor, mach_msg_type_number_t count)
|
||
|
{
|
||
|
error_t err;
|
||
|
task_t task = __pid2task (pid);
|
||
|
thread_t thread;
|
||
|
if (task == MACH_PORT_NULL)
|
||
|
return -1;
|
||
|
err = fetch_user_thread (task, &thread);
|
||
|
__mach_port_deallocate (__mach_task_self (), task);
|
||
|
if (!err)
|
||
|
err = __thread_get_state (thread, flavor, addr, &count);
|
||
|
__mach_port_deallocate (__mach_task_self (), thread);
|
||
|
return err ? __hurd_fail (err) : 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
switch (request)
|
||
|
{
|
||
|
case PTRACE_TRACEME:
|
||
|
/* Make this process be traced. */
|
||
|
__sigfillset (&_hurdsig_traced);
|
||
|
__USEPORT (PROC, __proc_mark_traced (port));
|
||
|
break;
|
||
|
|
||
|
case PTRACE_CONT:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
addr = va_arg (ap, void *);
|
||
|
data = va_arg (ap, int);
|
||
|
va_end (ap);
|
||
|
{
|
||
|
/* Send a DATA signal to PID, telling it to take the signal
|
||
|
normally even if it's traced. */
|
||
|
error_t err;
|
||
|
task_t task = __pid2task (pid);
|
||
|
if (task == MACH_PORT_NULL)
|
||
|
return -1;
|
||
|
if (data == SIGKILL)
|
||
|
err = __task_terminate (task);
|
||
|
else
|
||
|
{
|
||
|
if (addr != (void *) 1)
|
||
|
{
|
||
|
/* Move the user thread's PC to ADDR. */
|
||
|
thread_t thread;
|
||
|
err = fetch_user_thread (task, &thread);
|
||
|
if (!err)
|
||
|
{
|
||
|
struct machine_thread_state state;
|
||
|
mach_msg_type_number_t count = MACHINE_THREAD_STATE_COUNT;
|
||
|
err = __thread_get_state (thread,
|
||
|
MACHINE_THREAD_STATE_FLAVOR,
|
||
|
(natural_t *) &state, &count);
|
||
|
if (!err)
|
||
|
{
|
||
|
MACHINE_THREAD_STATE_SET_PC (&state, addr);
|
||
|
err = __thread_set_state (thread,
|
||
|
MACHINE_THREAD_STATE_FLAVOR,
|
||
|
(natural_t *) &state, count);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
__mach_port_deallocate (__mach_task_self (), thread);
|
||
|
}
|
||
|
else
|
||
|
err = 0;
|
||
|
|
||
|
if (! err)
|
||
|
/* Tell the process to take the signal (or just resume if 0). */
|
||
|
err = HURD_MSGPORT_RPC
|
||
|
(__USEPORT (PROC, __proc_getmsgport (port, pid, &msgport)),
|
||
|
0, 0, __msg_sig_post_untraced (msgport, data, 0, task));
|
||
|
}
|
||
|
__mach_port_deallocate (__mach_task_self (), task);
|
||
|
return err ? __hurd_fail (err) : 0;
|
||
|
}
|
||
|
|
||
|
case PTRACE_KILL:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
va_end (ap);
|
||
|
/* SIGKILL always just terminates the task,
|
||
|
so normal kill is just the same when traced. */
|
||
|
return __kill (pid, SIGKILL);
|
||
|
|
||
|
case PTRACE_SINGLESTEP:
|
||
|
/* This is a machine-dependent kernel RPC on
|
||
|
machines that support it. Punt. */
|
||
|
return __hurd_fail (EOPNOTSUPP);
|
||
|
|
||
|
case PTRACE_ATTACH:
|
||
|
case PTRACE_DETACH:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
va_end (ap);
|
||
|
{
|
||
|
/* Tell PID to set or clear its trace bit. */
|
||
|
error_t err;
|
||
|
mach_port_t msgport;
|
||
|
task_t task = __pid2task (pid);
|
||
|
if (task == MACH_PORT_NULL)
|
||
|
return -1;
|
||
|
err = __USEPORT (PROC, __proc_getmsgport (port, pid, &msgport));
|
||
|
if (! err)
|
||
|
{
|
||
|
err = __msg_set_init_int (msgport, task, INIT_TRACEMASK,
|
||
|
request == PTRACE_DETACH ? 0
|
||
|
: ~(sigset_t) 0);
|
||
|
if (! err)
|
||
|
{
|
||
|
if (request == PTRACE_ATTACH)
|
||
|
/* Now stop the process. */
|
||
|
err = __msg_sig_post (msgport, SIGSTOP, 0, task);
|
||
|
else
|
||
|
/* Resume the process from tracing stop. */
|
||
|
err = __msg_sig_post_untraced (msgport, 0, 0, task);
|
||
|
}
|
||
|
__mach_port_deallocate (__mach_task_self (), msgport);
|
||
|
}
|
||
|
__mach_port_deallocate (__mach_task_self (), task);
|
||
|
return err ? __hurd_fail (err) : 0;
|
||
|
}
|
||
|
|
||
|
case PTRACE_PEEKTEXT:
|
||
|
case PTRACE_PEEKDATA:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
addr = va_arg (ap, void *);
|
||
|
va_end (ap);
|
||
|
{
|
||
|
/* Read the page (or two pages, if the word lies on a boundary)
|
||
|
containing the addressed word. */
|
||
|
error_t err;
|
||
|
vm_address_t ourpage;
|
||
|
vm_size_t size;
|
||
|
natural_t word;
|
||
|
task_t task = __pid2task (pid);
|
||
|
if (task == MACH_PORT_NULL)
|
||
|
return -1;
|
||
|
data = sizeof word;
|
||
|
ourpage = 0;
|
||
|
size = 0;
|
||
|
err = read_data (task, &ourpage, &size);
|
||
|
__mach_port_deallocate (__mach_task_self (), task);
|
||
|
if (err)
|
||
|
return __hurd_fail (err);
|
||
|
word = *(natural_t *) ((vm_address_t) addr - trunc_page (addr)
|
||
|
+ ourpage);
|
||
|
__vm_deallocate (__mach_task_self (), ourpage, size);
|
||
|
return word;
|
||
|
}
|
||
|
|
||
|
case PTRACE_PEEKUSER:
|
||
|
case PTRACE_POKEUSER:
|
||
|
/* U area, what's that? */
|
||
|
return __hurd_fail (EOPNOTSUPP);
|
||
|
|
||
|
case PTRACE_GETREGS:
|
||
|
case PTRACE_SETREGS:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
addr = va_arg (ap, void *);
|
||
|
va_end (ap);
|
||
|
return get_regs (MACHINE_THREAD_STATE_FLAVOR,
|
||
|
MACHINE_THREAD_STATE_COUNT);
|
||
|
|
||
|
case PTRACE_GETFPREGS:
|
||
|
case PTRACE_SETFPREGS:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
addr = va_arg (ap, void *);
|
||
|
va_end (ap);
|
||
|
#ifdef MACHINE_THREAD_FLOAT_STATE_FLAVOR
|
||
|
return get_regs (MACHINE_THREAD_FLOAT_STATE_FLAVOR,
|
||
|
MACHINE_THREAD_FLOAT_STATE_COUNT);
|
||
|
#else
|
||
|
return __hurd_fail (EOPNOTSUPP);
|
||
|
#endif
|
||
|
|
||
|
case PTRACE_GETFPAREGS:
|
||
|
case PTRACE_SETFPAREGS:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
addr = va_arg (ap, void *);
|
||
|
va_end (ap);
|
||
|
#ifdef MACHINE_THREAD_FPA_STATE_FLAVOR
|
||
|
return get_regs (MACHINE_THREAD_FPA_STATE_FLAVOR,
|
||
|
MACHINE_THREAD_FPA_STATE_COUNT);
|
||
|
#else
|
||
|
return __hurd_fail (EOPNOTSUPP);
|
||
|
#endif
|
||
|
|
||
|
case PTRACE_POKETEXT:
|
||
|
case PTRACE_POKEDATA:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
addr = va_arg (ap, void *);
|
||
|
data = va_arg (ap, int);
|
||
|
va_end (ap);
|
||
|
{
|
||
|
/* Read the page (or two pages, if the word lies on a boundary)
|
||
|
containing the addressed word. */
|
||
|
error_t err;
|
||
|
vm_address_t ourpage;
|
||
|
vm_size_t size;
|
||
|
task_t task = __pid2task (pid);
|
||
|
if (task == MACH_PORT_NULL)
|
||
|
return -1;
|
||
|
data = sizeof (natural_t);
|
||
|
ourpage = 0;
|
||
|
size = 0;
|
||
|
err = read_data (task, &ourpage, &size);
|
||
|
|
||
|
if (!err)
|
||
|
{
|
||
|
/* Now modify the specified word and write the page back. */
|
||
|
*(natural_t *) ((vm_address_t) addr - trunc_page (addr)
|
||
|
+ ourpage) = data;
|
||
|
err = __vm_write (task, trunc_page (addr), ourpage, size);
|
||
|
__vm_deallocate (__mach_task_self (), ourpage, size);
|
||
|
}
|
||
|
|
||
|
__mach_port_deallocate (__mach_task_self (), task);
|
||
|
return err ? __hurd_fail (err) : 0;
|
||
|
}
|
||
|
|
||
|
case PTRACE_READDATA:
|
||
|
case PTRACE_READTEXT:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
addr = va_arg (ap, void *);
|
||
|
data = va_arg (ap, int);
|
||
|
addr2 = va_arg (ap, void *);
|
||
|
va_end (ap);
|
||
|
{
|
||
|
error_t err;
|
||
|
vm_address_t ourpage;
|
||
|
vm_size_t size;
|
||
|
task_t task = __pid2task (pid);
|
||
|
if (task == MACH_PORT_NULL)
|
||
|
return -1;
|
||
|
if (((vm_address_t) addr2 + data) % __vm_page_size == 0)
|
||
|
{
|
||
|
/* Perhaps we can write directly to the user's buffer. */
|
||
|
ourpage = (vm_address_t) addr2;
|
||
|
size = data;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ourpage = 0;
|
||
|
size = 0;
|
||
|
}
|
||
|
err = read_data (task, &ourpage, &size);
|
||
|
__mach_port_deallocate (__mach_task_self (), task);
|
||
|
if (!err && ourpage != (vm_address_t) addr2)
|
||
|
{
|
||
|
memcpy (addr2, (void *) ourpage, data);
|
||
|
__vm_deallocate (__mach_task_self (), ourpage, size);
|
||
|
}
|
||
|
return err ? __hurd_fail (err) : 0;
|
||
|
}
|
||
|
|
||
|
case PTRACE_WRITEDATA:
|
||
|
case PTRACE_WRITETEXT:
|
||
|
va_start (ap, request);
|
||
|
pid = va_arg (ap, pid_t);
|
||
|
addr = va_arg (ap, void *);
|
||
|
data = va_arg (ap, int);
|
||
|
addr2 = va_arg (ap, void *);
|
||
|
va_end (ap);
|
||
|
{
|
||
|
error_t err;
|
||
|
vm_address_t ourpage;
|
||
|
vm_size_t size;
|
||
|
task_t task = __pid2task (pid);
|
||
|
if (task == MACH_PORT_NULL)
|
||
|
return -1;
|
||
|
if ((vm_address_t) addr % __vm_page_size == 0
|
||
|
&& (vm_address_t) data % __vm_page_size == 0)
|
||
|
{
|
||
|
/* Writing whole pages; can go directly from the user's buffer. */
|
||
|
ourpage = (vm_address_t) addr2;
|
||
|
size = data;
|
||
|
err = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Read the task's pages and modify our own copy. */
|
||
|
ourpage = 0;
|
||
|
size = 0;
|
||
|
err = read_data (task, &ourpage, &size);
|
||
|
if (!err)
|
||
|
memcpy ((void *) ((vm_address_t) addr - trunc_page (addr)
|
||
|
+ ourpage),
|
||
|
addr2,
|
||
|
data);
|
||
|
}
|
||
|
if (!err)
|
||
|
/* Write back the modified pages. */
|
||
|
err = __vm_write (task, trunc_page (addr), ourpage, size);
|
||
|
__mach_port_deallocate (__mach_task_self (), task);
|
||
|
return err ? __hurd_fail (err) : 0;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
errno = EINVAL;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|