genode/repos/ports/src/app/gdb_monitor/gdbserver/genode-low.cc

752 lines
18 KiB
C++

/*
* \brief Genode backend for GDBServer (C++)
* \author Christian Prochaska
* \author Norman Feske
* \date 2011-05-06
*/
/*
* Copyright (C) 2011-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <base/env.h>
#include <base/attached_rom_dataspace.h>
/* GDB monitor includes */
#include "app_child.h"
#include "cpu_thread_component.h"
#include "genode_child_resources.h"
/* libc includes */
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "genode-low.h"
#include "server.h"
#include "linux-low.h"
void linux_detach_one_lwp (struct lwp_info *lwp);
static bool verbose = false;
Genode::Env *genode_env;
/*
* 'waitpid()' is implemented using 'select()'. When a new thread is created,
* 'select()' needs to unblock, so there is a dedicated pipe for that. The
* lwpid of the new thread needs to be read from the pipe in 'waitpid()', so
* that the next 'select()' call can block again. The lwpid needs to be stored
* in a variable until it is inquired later.
*/
static int _new_thread_pipe[2];
static unsigned long _new_thread_lwpid;
/*
* When 'waitpid()' reports a SIGTRAP, this variable stores the lwpid of the
* corresponding thread. This information is used in the initial breakpoint
* handler to let the correct thread handle the event.
*/
static unsigned long _sigtrap_lwpid;
using namespace Genode;
using namespace Gdb_monitor;
class Memory_model
{
private:
Lock _lock;
Region_map_component &_address_space;
Region_map &_rm;
/**
* Representation of a currently mapped region
*/
struct Mapped_region
{
Region_map_component::Region *_region;
unsigned char *_local_base;
Mapped_region() : _region(0), _local_base(0) { }
bool valid() { return _region != 0; }
bool loaded(Region_map_component::Region const * region)
{
return _region == region;
}
void flush(Region_map &rm)
{
if (!valid()) return;
rm.detach(_local_base);
_local_base = 0;
_region = 0;
}
void load(Region_map_component::Region *region, Region_map &rm)
{
if (region == _region)
return;
if (!region || valid())
flush(rm);
if (!region)
return;
try {
_region = region;
_local_base = rm.attach(_region->ds_cap(),
0, _region->offset());
}
catch (Region_map::Region_conflict) {
flush(rm);
error(__func__, ": RM attach failed (region conflict)");
}
catch (Region_map::Invalid_dataspace) {
flush(rm);
error(__func__, ": RM attach failed (invalid dataspace)");
}
}
unsigned char *local_base() { return _local_base; }
};
enum { NUM_MAPPED_REGIONS = 1 };
Mapped_region _mapped_region[NUM_MAPPED_REGIONS];
unsigned _evict_idx = 0;
/**
* Return local address of mapped region
*
* The function returns 0 if the mapping fails
*/
unsigned char *_update_curr_region(Region_map_component::Region *region)
{
for (unsigned i = 0; i < NUM_MAPPED_REGIONS; i++) {
if (_mapped_region[i].loaded(region))
return _mapped_region[i].local_base();
}
/* flush one currently mapped region */
_evict_idx++;
if (_evict_idx == NUM_MAPPED_REGIONS)
_evict_idx = 0;
_mapped_region[_evict_idx].load(region, _rm);
return _mapped_region[_evict_idx].local_base();
}
public:
Memory_model(Region_map_component &address_space,
Region_map &rm)
:
_address_space(address_space),
_rm(rm)
{ }
unsigned char read(void *addr)
{
Lock::Guard guard(_lock);
addr_t offset_in_region = 0;
Region_map_component::Region *region =
_address_space.find_region(addr, &offset_in_region);
unsigned char *local_base = _update_curr_region(region);
if (!local_base) {
warning(__func__, ": no memory at address ", addr);
throw No_memory_at_address();
}
unsigned char value =
local_base[offset_in_region];
if (verbose)
log(__func__, ": read addr=", addr, ", value=", Hex(value));
return value;
}
void write(void *addr, unsigned char value)
{
if (verbose)
log(__func__, ": write addr=", addr, ", value=", Hex(value));
Lock::Guard guard(_lock);
addr_t offset_in_region = 0;
Region_map_component::Region *region =
_address_space.find_region(addr, &offset_in_region);
unsigned char *local_base = _update_curr_region(region);
if (!local_base) {
warning(__func__, ": no memory at address=", addr);
warning("(attempted to write ", Hex(value), ")");
throw No_memory_at_address();
}
local_base[offset_in_region] = value;
}
};
static Genode_child_resources *_genode_child_resources = 0;
static Memory_model *_memory_model = 0;
Genode_child_resources &genode_child_resources()
{
if (!_genode_child_resources) {
Genode::error("_genode_child_resources is not set");
abort();
}
return *_genode_child_resources;
}
/**
* Return singleton instance of memory model
*/
Memory_model &memory_model()
{
if (!_memory_model) {
Genode::error("_memory_model is not set");
abort();
}
return *_memory_model;
}
static void genode_stop_thread(unsigned long lwpid)
{
Cpu_session_component &csc = genode_child_resources().cpu_session_component();
Cpu_thread_component *cpu_thread = csc.lookup_cpu_thread(lwpid);
if (!cpu_thread) {
error(__PRETTY_FUNCTION__, ": "
"could not find CPU thread object for lwpid ", lwpid);
return;
}
cpu_thread->pause();
}
pid_t my_waitpid(pid_t pid, int *status, int flags)
{
extern int remote_desc;
fd_set readset;
Cpu_session_component &csc = genode_child_resources().cpu_session_component();
while(1) {
FD_ZERO (&readset);
if (remote_desc != -1)
FD_SET (remote_desc, &readset);
if (pid == -1) {
FD_SET(_new_thread_pipe[0], &readset);
Thread_capability thread_cap = csc.first();
while (thread_cap.valid()) {
FD_SET(csc.signal_pipe_read_fd(thread_cap), &readset);
thread_cap = csc.next(thread_cap);
}
} else {
FD_SET(csc.signal_pipe_read_fd(csc.thread_cap(pid)), &readset);
}
struct timeval wnohang_timeout = {0, 0};
struct timeval *timeout = (flags & WNOHANG) ? &wnohang_timeout : NULL;
/* TODO: determine the highest fd in the set for optimization */
int res = select(FD_SETSIZE, &readset, 0, 0, timeout);
if (res > 0) {
if ((remote_desc != -1) && FD_ISSET(remote_desc, &readset)) {
/* received input from GDB */
int cc;
char c = 0;
cc = read (remote_desc, &c, 1);
if (cc == 1 && c == '\003' && current_thread != NULL) {
/* this causes a SIGINT to be delivered to one of the threads */
(*the_target->request_interrupt)();
continue;
} else {
if (verbose)
log("input_interrupt, "
"count=", cc, " c=", c, " ('", Char(c), "%c')");
}
} else if (FD_ISSET(_new_thread_pipe[0], &readset)) {
/*
* Linux 'ptrace(2)' manual text related to the main thread:
*
* "If the PTRACE_O_TRACEEXEC option is not in effect, all
* successful calls to execve(2) by the traced process will
* cause it to be sent a SIGTRAP signal, giving the parent a
* chance to gain control before the new program begins
* execution."
*
* Linux 'ptrace' manual text related to other threads:
*
* "PTRACE_O_CLONE
* ...
* A waitpid(2) by the tracer will return a status value such
* that
*
* status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8))
*
* The PID of the new process can be retrieved with
* PTRACE_GETEVENTMSG."
*/
*status = W_STOPCODE(SIGTRAP);
read(_new_thread_pipe[0], &_new_thread_lwpid,
sizeof(_new_thread_lwpid));
if (_new_thread_lwpid != GENODE_MAIN_LWPID) {
*status |= (PTRACE_EVENT_CLONE << 16);
genode_stop_thread(GENODE_MAIN_LWPID);
}
return GENODE_MAIN_LWPID;
} else {
/* received a signal */
Thread_capability thread_cap = csc.first();
while (thread_cap.valid()) {
if (FD_ISSET(csc.signal_pipe_read_fd(thread_cap), &readset))
break;
thread_cap = csc.next(thread_cap);
}
if (!thread_cap.valid())
continue;
int signal;
read(csc.signal_pipe_read_fd(thread_cap), &signal, sizeof(signal));
unsigned long lwpid = csc.lwpid(thread_cap);
if (verbose)
log("thread ", lwpid, " received signal ", signal);
if (signal == SIGTRAP) {
_sigtrap_lwpid = lwpid;
} else if (signal == SIGSTOP) {
/*
* Check if a SIGTRAP is pending
*
* This can happen if a single-stepped thread gets paused while gdbserver
* handles a signal of a different thread and the exception signal after
* the single step has not arrived yet. In this case, the SIGTRAP must be
* delivered first, otherwise gdbserver would single-step the thread again.
*/
Cpu_thread_component *cpu_thread = csc.lookup_cpu_thread(lwpid);
Thread_state thread_state = cpu_thread->state();
if (thread_state.exception) {
/* resend the SIGSTOP signal */
csc.send_signal(cpu_thread->cap(), SIGSTOP);
continue;
}
} else if (signal == SIGINFO) {
if (verbose)
log("received SIGINFO for new lwpid ", lwpid);
/*
* First signal of a new thread. On Genode originally a
* SIGTRAP, but gdbserver expects SIGSTOP.
*/
signal = SIGSTOP;
}
*status = W_STOPCODE(signal);
return lwpid;
}
} else {
return res;
}
}
}
extern "C" long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data)
{
const char *request_str = 0;
switch (request) {
case PTRACE_TRACEME: request_str = "PTRACE_TRACEME"; break;
case PTRACE_PEEKTEXT: request_str = "PTRACE_PEEKTEXT"; break;
case PTRACE_PEEKUSER: request_str = "PTRACE_PEEKUSER"; break;
case PTRACE_POKETEXT: request_str = "PTRACE_POKETEXT"; break;
case PTRACE_POKEUSER: request_str = "PTRACE_POKEUSER"; break;
case PTRACE_CONT: request_str = "PTRACE_CONT"; break;
case PTRACE_KILL: request_str = "PTRACE_KILL"; break;
case PTRACE_SINGLESTEP: request_str = "PTRACE_SINGLESTEP"; break;
case PTRACE_GETREGS: request_str = "PTRACE_GETREGS"; break;
case PTRACE_SETREGS: request_str = "PTRACE_SETREGS"; break;
case PTRACE_ATTACH: request_str = "PTRACE_ATTACH"; break;
case PTRACE_DETACH: request_str = "PTRACE_DETACH"; break;
case PTRACE_GETSIGINFO: request_str = "PTRACE_GETSIGINFO"; break;
case PTRACE_GETEVENTMSG:
/*
* Only PTRACE_EVENT_CLONE is currently supported.
*/
*(unsigned long*)data = _new_thread_lwpid;
return 0;
case PTRACE_GETREGSET: request_str = "PTRACE_GETREGSET"; break;
}
warning("ptrace(", request_str, " (", Hex(request), ")) called - not implemented!");
errno = EINVAL;
return -1;
}
extern "C" int vfork()
{
/* create the thread announcement pipe */
if (pipe(_new_thread_pipe) != 0) {
error("could not create the 'new thread' pipe");
return -1;
}
/* extract target filename from config file */
typedef String<32> Filename;
Filename filename { };
Genode::Attached_rom_dataspace config { *genode_env, "config" };
try {
Xml_node const target = config.xml().sub_node("target");
if (!target.has_attribute("name")) {
error("missing 'name' attribute of '<target>' sub node");
return -1;
}
filename = target.attribute_value("name", Filename());
} catch (Xml_node::Nonexistent_sub_node) {
error("missing '<target>' sub node");
return -1;
}
/* extract target node from config file */
Xml_node target_node = config.xml().sub_node("target");
/*
* preserve the configured amount of memory for gdb_monitor and give the
* remainder to the child
*/
Number_of_bytes preserved_ram_quota = 0;
try {
Xml_node preserve_node = config.xml().sub_node("preserve");
if (preserve_node.attribute("name").has_value("RAM"))
preserve_node.attribute("quantum").value(preserved_ram_quota);
else
throw Xml_node::Exception();
} catch (...) {
error("could not find a valid <preserve> config node");
return -1;
}
Number_of_bytes ram_quota = genode_env->pd().avail_ram().value - preserved_ram_quota;
Cap_quota const avail_cap_quota = genode_env->pd().avail_caps();
Genode::size_t const preserved_caps = 100;
if (avail_cap_quota.value < preserved_caps) {
error("not enough available caps for preservation of ", preserved_caps);
return -1;
}
Cap_quota const cap_quota { avail_cap_quota.value - preserved_caps };
/* start the application */
static Heap alloc(genode_env->ram(), genode_env->rm());
enum { SIGNAL_EP_STACK_SIZE = 2*1024*sizeof(addr_t) };
static Entrypoint signal_ep { *genode_env, SIGNAL_EP_STACK_SIZE,
"sig_handler", Affinity::Location() };
int breakpoint_len = 0;
unsigned char const *breakpoint_data =
the_target->sw_breakpoint_from_kind(0, &breakpoint_len);
App_child *child = new (alloc) App_child(*genode_env,
alloc,
filename.string(),
Ram_quota{ram_quota},
cap_quota,
signal_ep,
target_node,
_new_thread_pipe[1],
breakpoint_len,
breakpoint_data);
_genode_child_resources = child->genode_child_resources();
static Memory_model memory_model(genode_child_resources().region_map_component(), genode_env->rm());
_memory_model = &memory_model;
try { child->start(); }
catch (Out_of_caps) { error("out of caps during child startup"); return -1; }
catch (Out_of_ram) { error("out of RAM during child startup"); return -1; }
catch (Service_denied) { error("service denied during child startup"); return -1; }
catch (...) { error("could not start child process"); return -1; }
return GENODE_MAIN_LWPID;
}
extern "C" int kill(pid_t pid, int sig)
{
Cpu_session_component &csc = genode_child_resources().cpu_session_component();
if (pid <= 0) pid = GENODE_MAIN_LWPID;
Thread_capability thread_cap = csc.thread_cap(pid);
if (!thread_cap.valid()) {
error(__PRETTY_FUNCTION__, ": "
"could not find thread capability for pid ", pid);
return -1;
}
return csc.send_signal(thread_cap, sig);
}
extern "C" int initial_breakpoint_handler(CORE_ADDR addr)
{
Cpu_session_component &csc = genode_child_resources().cpu_session_component();
return csc.handle_initial_breakpoint(_sigtrap_lwpid);
}
void genode_set_initial_breakpoint_at(unsigned long addr)
{
set_breakpoint_at(addr, initial_breakpoint_handler);
}
void genode_remove_thread(unsigned long lwpid)
{
struct thread_info *thread_info =
find_thread_ptid(ptid_t(GENODE_MAIN_LWPID, lwpid, 0));
struct lwp_info *lwp = get_thread_lwp(thread_info);
linux_detach_one_lwp(lwp);
}
void genode_stop_all_threads()
{
Cpu_session_component &csc = genode_child_resources().cpu_session_component();
csc.pause_all_threads();
}
void genode_resume_all_threads()
{
Cpu_session_component &csc = genode_child_resources().cpu_session_component();
csc.resume_all_threads();
}
int genode_detach(int pid)
{
genode_resume_all_threads();
return 0;
}
int genode_kill(int pid)
{
/* TODO */
if (verbose) warning(__func__, " not implemented, just detaching instead...");
return genode_detach(pid);
}
void genode_continue_thread(unsigned long lwpid, int single_step)
{
Cpu_session_component &csc = genode_child_resources().cpu_session_component();
Cpu_thread_component *cpu_thread = csc.lookup_cpu_thread(lwpid);
if (!cpu_thread) {
error(__func__, ": " "could not find CPU thread object for lwpid ", lwpid);
return;
}
cpu_thread->single_step(single_step);
cpu_thread->resume();
}
void genode_fetch_registers(struct regcache *regcache, int regno)
{
const struct regs_info *regs_info = (*the_low_target.regs_info) ();
unsigned long reg_content = 0;
if (regno == -1) {
for (regno = 0; regno < regs_info->usrregs->num_regs; regno++) {
if (genode_fetch_register(regno, &reg_content) == 0)
supply_register(regcache, regno, &reg_content);
else
supply_register(regcache, regno, 0);
}
} else {
if (genode_fetch_register(regno, &reg_content) == 0)
supply_register(regcache, regno, &reg_content);
else
supply_register(regcache, regno, 0);
}
}
void genode_store_registers(struct regcache *regcache, int regno)
{
if (verbose) log(__func__, ": regno=", regno);
const struct regs_info *regs_info = (*the_low_target.regs_info) ();
unsigned long reg_content = 0;
if (regno == -1) {
for (regno = 0; regno < regs_info->usrregs->num_regs; regno++) {
if ((Genode::size_t)register_size(regcache->tdesc, regno) <=
sizeof(reg_content)) {
collect_register(regcache, regno, &reg_content);
genode_store_register(regno, reg_content);
}
}
} else {
if ((Genode::size_t)register_size(regcache->tdesc, regno) <=
sizeof(reg_content)) {
collect_register(regcache, regno, &reg_content);
genode_store_register(regno, reg_content);
}
}
}
unsigned char genode_read_memory_byte(void *addr)
{
return memory_model().read(addr);
}
int genode_read_memory(CORE_ADDR memaddr, unsigned char *myaddr, int len)
{
if (verbose)
log(__func__, "(", Hex(memaddr), ", ", myaddr, ", ", len, ")");
if (myaddr)
try {
for (int i = 0; i < len; i++)
myaddr[i] = genode_read_memory_byte((void*)((unsigned long)memaddr + i));
} catch (No_memory_at_address) {
return EFAULT;
}
return 0;
}
void genode_write_memory_byte(void *addr, unsigned char value)
{
memory_model().write(addr, value);
}
int genode_write_memory (CORE_ADDR memaddr, const unsigned char *myaddr, int len)
{
if (verbose)
log(__func__, "(", Hex(memaddr), ", ", myaddr, ", ", len, ")");
if (myaddr && (len > 0)) {
if (debug_threads) {
/* Dump up to four bytes. */
unsigned int val = * (unsigned int *) myaddr;
if (len == 1)
val = val & 0xff;
else if (len == 2)
val = val & 0xffff;
else if (len == 3)
val = val & 0xffffff;
fprintf(stderr, "Writing %0*x to 0x%08lx", 2 * ((len < 4) ? len : 4),
val, (long)memaddr);
}
for (int i = 0; i < len; i++)
try {
genode_write_memory_byte((void*)((unsigned long)memaddr + i), myaddr[i]);
} catch (No_memory_at_address) {
return EFAULT;
}
}
return 0;
}