haiku/src/servers/debug/DebugServer.cpp

1326 lines
31 KiB
C++

/*
* Copyright 2019, Adrien Destugues, pulkomandy@pulkomandy.tk.
* Copyright 2011-2014, Rene Gollent, rene@gollent.com.
* Copyright 2005-2009, Ingo Weinhold, bonefish@users.sf.net.
* Distributed under the terms of the MIT License.
*/
#include "DebugWindow.h"
#include <map>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <AppMisc.h>
#include <AutoDeleter.h>
#include <Autolock.h>
#include <debug_support.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <Invoker.h>
#include <Path.h>
#include <DriverSettings.h>
#include <MessengerPrivate.h>
#include <RegExp.h>
#include <RegistrarDefs.h>
#include <RosterPrivate.h>
#include <Server.h>
#include <StringList.h>
#include <util/DoublyLinkedList.h>
static const char* kDebuggerSignature = "application/x-vnd.Haiku-Debugger";
static const int32 MSG_DEBUG_THIS_TEAM = 'dbtt';
//#define TRACE_DEBUG_SERVER
#ifdef TRACE_DEBUG_SERVER
# define TRACE(x) debug_printf x
#else
# define TRACE(x) ;
#endif
using std::map;
using std::nothrow;
static const char *kSignature = "application/x-vnd.Haiku-debug_server";
static status_t
action_for_string(const char* action, int32& _action)
{
if (strcmp(action, "kill") == 0)
_action = kActionKillTeam;
else if (strcmp(action, "debug") == 0)
_action = kActionDebugTeam;
else if (strcmp(action, "log") == 0
|| strcmp(action, "report") == 0) {
_action = kActionSaveReportTeam;
} else if (strcasecmp(action, "core") == 0)
_action = kActionWriteCoreFile;
else if (strcasecmp(action, "user") == 0)
_action = kActionPromptUser;
else
return B_BAD_VALUE;
return B_OK;
}
static bool
match_team_name(const char* teamName, const char* parameterName)
{
RegExp expressionMatcher;
if (expressionMatcher.SetPattern(parameterName,
RegExp::PATTERN_TYPE_WILDCARD)) {
BString value = teamName;
if (parameterName[0] != '/') {
// the expression in question is a team name match only,
// so we need to extract that.
BPath path(teamName);
if (path.InitCheck() == B_OK)
value = path.Leaf();
}
RegExp::MatchResult match = expressionMatcher.Match(value);
if (match.HasMatched())
return true;
}
return false;
}
static status_t
action_for_team(const char* teamName, int32& _action,
bool& _explicitActionFound)
{
status_t error = B_OK;
BPath path;
error = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
if (error != B_OK)
return error;
path.Append("system/debug_server/settings");
BDriverSettings settings;
error = settings.Load(path.Path());
if (error != B_OK)
return error;
int32 tempAction;
if (action_for_string(settings.GetParameterValue("default_action",
"user", "user"), tempAction) == B_OK) {
_action = tempAction;
} else
_action = kActionPromptUser;
_explicitActionFound = false;
BDriverParameter parameter = settings.GetParameter("executable_actions");
for (BDriverParameterIterator iterator = parameter.ParameterIterator();
iterator.HasNext();) {
BDriverParameter child = iterator.Next();
if (!match_team_name(teamName, child.Name()))
continue;
if (child.CountValues() > 0) {
if (action_for_string(child.ValueAt(0), tempAction) == B_OK) {
_action = tempAction;
_explicitActionFound = true;
}
}
break;
}
return B_OK;
}
static void
KillTeam(team_id team, const char *appName = NULL)
{
// get a team info to verify the team still lives
team_info info;
if (!appName) {
status_t error = get_team_info(team, &info);
if (error != B_OK) {
debug_printf("debug_server: KillTeam(): Error getting info for "
"team %" B_PRId32 ": %s\n", team, strerror(error));
info.args[0] = '\0';
}
appName = info.args;
}
debug_printf("debug_server: Killing team %" B_PRId32 " (%s)\n", team,
appName);
kill_team(team);
}
// #pragma mark -
class DebugMessage : public DoublyLinkedListLinkImpl<DebugMessage> {
public:
DebugMessage()
{
}
void SetCode(debug_debugger_message code) { fCode = code; }
debug_debugger_message Code() const { return fCode; }
debug_debugger_message_data &Data() { return fData; }
const debug_debugger_message_data &Data() const { return fData; }
private:
debug_debugger_message fCode;
debug_debugger_message_data fData;
};
typedef DoublyLinkedList<DebugMessage> DebugMessageList;
class TeamDebugHandler : public BLocker {
public:
TeamDebugHandler(team_id team);
~TeamDebugHandler();
status_t Init(port_id nubPort);
team_id Team() const;
status_t PushMessage(DebugMessage *message);
private:
status_t _PopMessage(DebugMessage *&message);
thread_id _EnterDebugger(bool saveReport);
status_t _SetupGDBArguments(BStringList &arguments, bool usingConsoled);
status_t _WriteCoreFile();
void _KillTeam();
int32 _HandleMessage(DebugMessage *message);
void _LookupSymbolAddress(debug_symbol_lookup_context *lookupContext,
const void *address, char *buffer, int32 bufferSize);
void _PrintStackTrace(thread_id thread);
void _NotifyAppServer(team_id team);
void _NotifyRegistrar(team_id team, bool openAlert, bool stopShutdown);
status_t _InitGUI();
static status_t _HandlerThreadEntry(void *data);
status_t _HandlerThread();
bool _ExecutableNameEquals(const char *name) const;
bool _IsAppServer() const;
bool _IsInputServer() const;
bool _IsRegistrar() const;
bool _IsGUIServer() const;
static const char *_LastPathComponent(const char *path);
static team_id _FindTeam(const char *name);
static bool _AreGUIServersAlive();
private:
DebugMessageList fMessages;
sem_id fMessageCountSem;
team_id fTeam;
team_info fTeamInfo;
char fExecutablePath[B_PATH_NAME_LENGTH];
thread_id fHandlerThread;
debug_context fDebugContext;
};
class TeamDebugHandlerRoster : public BLocker {
private:
TeamDebugHandlerRoster()
:
BLocker("team debug handler roster")
{
}
public:
static TeamDebugHandlerRoster *CreateDefault()
{
if (!sRoster)
sRoster = new(nothrow) TeamDebugHandlerRoster;
return sRoster;
}
static TeamDebugHandlerRoster *Default()
{
return sRoster;
}
bool AddHandler(TeamDebugHandler *handler)
{
if (!handler)
return false;
BAutolock _(this);
fHandlers[handler->Team()] = handler;
return true;
}
TeamDebugHandler *RemoveHandler(team_id team)
{
BAutolock _(this);
TeamDebugHandler *handler = NULL;
TeamDebugHandlerMap::iterator it = fHandlers.find(team);
if (it != fHandlers.end()) {
handler = it->second;
fHandlers.erase(it);
}
return handler;
}
TeamDebugHandler *HandlerFor(team_id team)
{
BAutolock _(this);
TeamDebugHandler *handler = NULL;
TeamDebugHandlerMap::iterator it = fHandlers.find(team);
if (it != fHandlers.end())
handler = it->second;
return handler;
}
status_t DispatchMessage(DebugMessage *message)
{
if (!message)
return B_BAD_VALUE;
ObjectDeleter<DebugMessage> messageDeleter(message);
team_id team = message->Data().origin.team;
// get the responsible team debug handler
BAutolock _(this);
TeamDebugHandler *handler = HandlerFor(team);
if (!handler) {
// no handler yet, we need to create one
handler = new(nothrow) TeamDebugHandler(team);
if (!handler) {
KillTeam(team);
return B_NO_MEMORY;
}
status_t error = handler->Init(message->Data().origin.nub_port);
if (error != B_OK) {
delete handler;
KillTeam(team);
return error;
}
if (!AddHandler(handler)) {
delete handler;
KillTeam(team);
return B_NO_MEMORY;
}
}
// hand over the message to it
handler->PushMessage(message);
messageDeleter.Detach();
return B_OK;
}
private:
typedef map<team_id, TeamDebugHandler*> TeamDebugHandlerMap;
static TeamDebugHandlerRoster *sRoster;
TeamDebugHandlerMap fHandlers;
};
TeamDebugHandlerRoster *TeamDebugHandlerRoster::sRoster = NULL;
class DebugServer : public BServer {
public:
DebugServer(status_t &error);
status_t Init();
virtual bool QuitRequested();
private:
static status_t _ListenerEntry(void *data);
status_t _Listener();
void _DeleteTeamDebugHandler(TeamDebugHandler *handler);
private:
typedef map<team_id, TeamDebugHandler*> TeamDebugHandlerMap;
port_id fListenerPort;
thread_id fListener;
bool fTerminating;
};
// #pragma mark -
TeamDebugHandler::TeamDebugHandler(team_id team)
:
BLocker("team debug handler"),
fMessages(),
fMessageCountSem(-1),
fTeam(team),
fHandlerThread(-1)
{
fDebugContext.nub_port = -1;
fDebugContext.reply_port = -1;
fExecutablePath[0] = '\0';
}
TeamDebugHandler::~TeamDebugHandler()
{
// delete the message count semaphore and wait for the thread to die
if (fMessageCountSem >= 0)
delete_sem(fMessageCountSem);
if (fHandlerThread >= 0 && find_thread(NULL) != fHandlerThread) {
status_t result;
wait_for_thread(fHandlerThread, &result);
}
// destroy debug context
if (fDebugContext.nub_port >= 0)
destroy_debug_context(&fDebugContext);
// delete the remaining messages
while (DebugMessage *message = fMessages.Head()) {
fMessages.Remove(message);
delete message;
}
}
status_t
TeamDebugHandler::Init(port_id nubPort)
{
// get the team info for the team
status_t error = get_team_info(fTeam, &fTeamInfo);
if (error != B_OK) {
debug_printf("debug_server: TeamDebugHandler::Init(): Failed to get "
"info for team %" B_PRId32 ": %s\n", fTeam, strerror(error));
return error;
}
// get the executable path
error = BPrivate::get_app_path(fTeam, fExecutablePath);
if (error != B_OK) {
debug_printf("debug_server: TeamDebugHandler::Init(): Failed to get "
"executable path of team %" B_PRId32 ": %s\n", fTeam,
strerror(error));
fExecutablePath[0] = '\0';
}
// init a debug context for the handler
error = init_debug_context(&fDebugContext, fTeam, nubPort);
if (error != B_OK) {
debug_printf("debug_server: TeamDebugHandler::Init(): Failed to init "
"debug context for team %" B_PRId32 ", port %" B_PRId32 ": %s\n",
fTeam, nubPort, strerror(error));
return error;
}
// set team flags
debug_nub_set_team_flags message;
message.flags = B_TEAM_DEBUG_PREVENT_EXIT;
send_debug_message(&fDebugContext, B_DEBUG_MESSAGE_SET_TEAM_FLAGS, &message,
sizeof(message), NULL, 0);
// create the message count semaphore
char name[B_OS_NAME_LENGTH];
snprintf(name, sizeof(name), "team %" B_PRId32 " message count", fTeam);
fMessageCountSem = create_sem(0, name);
if (fMessageCountSem < 0) {
debug_printf("debug_server: TeamDebugHandler::Init(): Failed to create "
"message count semaphore: %s\n", strerror(fMessageCountSem));
return fMessageCountSem;
}
// spawn the handler thread
snprintf(name, sizeof(name), "team %" B_PRId32 " handler", fTeam);
fHandlerThread = spawn_thread(&_HandlerThreadEntry, name, B_NORMAL_PRIORITY,
this);
if (fHandlerThread < 0) {
debug_printf("debug_server: TeamDebugHandler::Init(): Failed to spawn "
"handler thread: %s\n", strerror(fHandlerThread));
return fHandlerThread;
}
resume_thread(fHandlerThread);
return B_OK;
}
team_id
TeamDebugHandler::Team() const
{
return fTeam;
}
status_t
TeamDebugHandler::PushMessage(DebugMessage *message)
{
BAutolock _(this);
fMessages.Add(message);
release_sem(fMessageCountSem);
return B_OK;
}
status_t
TeamDebugHandler::_PopMessage(DebugMessage *&message)
{
// acquire the semaphore
status_t error;
do {
error = acquire_sem(fMessageCountSem);
} while (error == B_INTERRUPTED);
if (error != B_OK)
return error;
// get the message
BAutolock _(this);
message = fMessages.Head();
fMessages.Remove(message);
return B_OK;
}
status_t
TeamDebugHandler::_SetupGDBArguments(BStringList &arguments, bool usingConsoled)
{
// prepare the argument vector
BString teamString;
teamString.SetToFormat("--pid=%" B_PRId32, fTeam);
status_t error;
BPath terminalPath;
if (usingConsoled) {
error = find_directory(B_SYSTEM_BIN_DIRECTORY, &terminalPath);
if (error != B_OK) {
debug_printf("debug_server: can't find system-bin directory: %s\n",
strerror(error));
return error;
}
error = terminalPath.Append("consoled");
if (error != B_OK) {
debug_printf("debug_server: can't append to system-bin path: %s\n",
strerror(error));
return error;
}
} else {
error = find_directory(B_SYSTEM_APPS_DIRECTORY, &terminalPath);
if (error != B_OK) {
debug_printf("debug_server: can't find system-apps directory: %s\n",
strerror(error));
return error;
}
error = terminalPath.Append("Terminal");
if (error != B_OK) {
debug_printf("debug_server: can't append to system-apps path: %s\n",
strerror(error));
return error;
}
}
arguments.MakeEmpty();
if (!arguments.Add(terminalPath.Path()))
return B_NO_MEMORY;
if (!usingConsoled) {
BString windowTitle;
windowTitle.SetToFormat("Debug of Team %" B_PRId32 ": %s", fTeam,
_LastPathComponent(fExecutablePath));
if (!arguments.Add("-t") || !arguments.Add(windowTitle))
return B_NO_MEMORY;
}
BPath gdbPath;
error = find_directory(B_SYSTEM_BIN_DIRECTORY, &gdbPath);
if (error != B_OK) {
debug_printf("debug_server: can't find system-bin directory: %s\n",
strerror(error));
return error;
}
error = gdbPath.Append("gdb");
if (error != B_OK) {
debug_printf("debug_server: can't append to system-bin path: %s\n",
strerror(error));
return error;
}
if (!arguments.Add(gdbPath.Path()) || !arguments.Add(teamString))
return B_NO_MEMORY;
if (strlen(fExecutablePath) > 0 && !arguments.Add(fExecutablePath))
return B_NO_MEMORY;
return B_OK;
}
thread_id
TeamDebugHandler::_EnterDebugger(bool saveReport)
{
TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): team %" B_PRId32
"\n", fTeam));
// prepare a debugger handover
TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): preparing "
"debugger handover for team %" B_PRId32 "...\n", fTeam));
status_t error = send_debug_message(&fDebugContext,
B_DEBUG_MESSAGE_PREPARE_HANDOVER, NULL, 0, NULL, 0);
if (error != B_OK) {
debug_printf("debug_server: Failed to prepare debugger handover: %s\n",
strerror(error));
return error;
}
BStringList arguments;
const char *argv[16];
int argc = 0;
bool debugInConsoled = _IsGUIServer() || !_AreGUIServersAlive();
#ifdef HANDOVER_USE_GDB
error = _SetupGDBArguments(arguments, debugInConsoled);
if (error != B_OK) {
debug_printf("debug_server: Failed to set up gdb arguments: %s\n",
strerror(error));
return error;
}
// start the terminal
TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): starting "
"terminal (debugger) for team %" B_PRId32 "...\n", fTeam));
#elif defined(HANDOVER_USE_DEBUGGER)
if (!debugInConsoled && !saveReport
&& be_roster->IsRunning(kDebuggerSignature)) {
// for graphical handovers, check if Debugger is already running,
// and if it is, simply send it a message to attach to the requested
// team.
BMessenger messenger(kDebuggerSignature);
BMessage message(MSG_DEBUG_THIS_TEAM);
if (message.AddInt32("team", fTeam) == B_OK
&& messenger.SendMessage(&message) == B_OK) {
return 0;
}
}
// prepare the argument vector
BPath debuggerPath;
if (debugInConsoled) {
error = find_directory(B_SYSTEM_BIN_DIRECTORY, &debuggerPath);
if (error != B_OK) {
debug_printf("debug_server: can't find system-bin directory: %s\n",
strerror(error));
return error;
}
error = debuggerPath.Append("consoled");
if (error != B_OK) {
debug_printf("debug_server: can't append to system-bin path: %s\n",
strerror(error));
return error;
}
if (!arguments.Add(debuggerPath.Path()))
return B_NO_MEMORY;
}
error = find_directory(B_SYSTEM_APPS_DIRECTORY, &debuggerPath);
if (error != B_OK) {
debug_printf("debug_server: can't find system-apps directory: %s\n",
strerror(error));
return error;
}
error = debuggerPath.Append("Debugger");
if (error != B_OK) {
debug_printf("debug_server: can't append to system-apps path: %s\n",
strerror(error));
return error;
}
if (!arguments.Add(debuggerPath.Path()))
return B_NO_MEMORY;
if (debugInConsoled && !arguments.Add("--cli"))
return B_NO_MEMORY;
BString debuggerParam;
debuggerParam.SetToFormat("%" B_PRId32, fTeam);
if (saveReport) {
if (!arguments.Add("--save-report"))
return B_NO_MEMORY;
}
if (!arguments.Add("--team") || !arguments.Add(debuggerParam))
return B_NO_MEMORY;
// start the debugger
TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): starting "
"%s debugger for team %" B_PRId32 "...\n",
debugInConsoled ? "command line" : "graphical", fTeam));
#endif
for (int32 i = 0; i < arguments.CountStrings(); i++)
argv[argc++] = arguments.StringAt(i).String();
argv[argc] = NULL;
thread_id thread = load_image(argc, argv, (const char**)environ);
if (thread < 0) {
debug_printf("debug_server: Failed to start debugger: %s\n",
strerror(thread));
return thread;
}
resume_thread(thread);
TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): debugger started "
"for team %" B_PRId32 ": thread: %" B_PRId32 "\n", fTeam, thread));
return thread;
}
void
TeamDebugHandler::_KillTeam()
{
KillTeam(fTeam, fTeamInfo.args);
}
status_t
TeamDebugHandler::_WriteCoreFile()
{
// get a usable path for the core file
BPath directoryPath;
status_t error = find_directory(B_DESKTOP_DIRECTORY, &directoryPath);
if (error != B_OK) {
debug_printf("debug_server: Couldn't get desktop directory: %s\n",
strerror(error));
return error;
}
const char* executableName = strrchr(fExecutablePath, '/');
if (executableName == NULL)
executableName = fExecutablePath;
else
executableName++;
BString fileBaseName("core-");
fileBaseName << executableName << '-' << fTeam;
BPath filePath;
for (int32 index = 0;; index++) {
BString fileName(fileBaseName);
if (index > 0)
fileName << '-' << index;
error = filePath.SetTo(directoryPath.Path(), fileName.String());
if (error != B_OK) {
debug_printf("debug_server: Couldn't get core file path for team %"
B_PRId32 ": %s\n", fTeam, strerror(error));
return error;
}
struct stat st;
if (lstat(filePath.Path(), &st) != 0) {
if (errno == B_ENTRY_NOT_FOUND)
break;
}
if (index > 1000) {
debug_printf("debug_server: Couldn't get usable core file path for "
"team %" B_PRId32 "\n", fTeam);
return B_ERROR;
}
}
debug_nub_write_core_file message;
message.reply_port = fDebugContext.reply_port;
strlcpy(message.path, filePath.Path(), sizeof(message.path));
debug_nub_write_core_file_reply reply;
error = send_debug_message(&fDebugContext, B_DEBUG_WRITE_CORE_FILE,
&message, sizeof(message), &reply, sizeof(reply));
if (error == B_OK)
error = reply.error;
if (error != B_OK) {
debug_printf("debug_server: Failed to write core file for team %"
B_PRId32 ": %s\n", fTeam, strerror(error));
}
return error;
}
int32
TeamDebugHandler::_HandleMessage(DebugMessage *message)
{
// This method is called only for the first message the debugger gets for
// a team. That means only a few messages are actually possible, while
// others wouldn't trigger the debugger in the first place. So we deal with
// all of them the same way, by popping up an alert.
TRACE(("debug_server: TeamDebugHandler::_HandleMessage(): team %" B_PRId32
", code: %" B_PRId32 "\n", fTeam, (int32)message->Code()));
thread_id thread = message->Data().origin.thread;
// get some user-readable message
char buffer[512];
switch (message->Code()) {
case B_DEBUGGER_MESSAGE_TEAM_DELETED:
// This shouldn't happen.
debug_printf("debug_server: Got a spurious "
"B_DEBUGGER_MESSAGE_TEAM_DELETED message for team %" B_PRId32
"\n", fTeam);
return true;
case B_DEBUGGER_MESSAGE_EXCEPTION_OCCURRED:
get_debug_exception_string(
message->Data().exception_occurred.exception, buffer,
sizeof(buffer));
break;
case B_DEBUGGER_MESSAGE_DEBUGGER_CALL:
{
// get the debugger() message
void *messageAddress = message->Data().debugger_call.message;
char messageBuffer[128];
status_t error = B_OK;
ssize_t bytesRead = debug_read_string(&fDebugContext,
messageAddress, messageBuffer, sizeof(messageBuffer));
if (bytesRead < 0)
error = bytesRead;
if (error == B_OK) {
sprintf(buffer, "Debugger call: `%s'", messageBuffer);
} else {
snprintf(buffer, sizeof(buffer), "Debugger call: %p "
"(Failed to read message: %s)", messageAddress,
strerror(error));
}
break;
}
default:
get_debug_message_string(message->Code(), buffer, sizeof(buffer));
break;
}
debug_printf("debug_server: Thread %" B_PRId32 " entered the debugger: %s\n",
thread, buffer);
_PrintStackTrace(thread);
int32 debugAction = kActionPromptUser;
bool explicitActionFound = false;
if (action_for_team(fExecutablePath, debugAction, explicitActionFound)
!= B_OK) {
debugAction = kActionPromptUser;
explicitActionFound = false;
}
// ask the user whether to debug or kill the team
if (_IsGUIServer()) {
// App server, input server, or registrar. We always debug those.
// if not specifically overridden.
if (!explicitActionFound)
debugAction = kActionDebugTeam;
} else if (debugAction == kActionPromptUser && USE_GUI
&& _AreGUIServersAlive() && _InitGUI() == B_OK) {
// normal app -- tell the user
_NotifyAppServer(fTeam);
_NotifyRegistrar(fTeam, true, false);
DebugWindow *alert = new DebugWindow(fTeamInfo.args);
// TODO: It would be nice if the alert would go away automatically
// if someone else kills our teams.
debugAction = alert->Go();
if (debugAction < 0) {
// Happens when closed by escape key
debugAction = kActionKillTeam;
}
_NotifyRegistrar(fTeam, false, debugAction != kActionKillTeam);
}
return debugAction;
}
void
TeamDebugHandler::_LookupSymbolAddress(
debug_symbol_lookup_context *lookupContext, const void *address,
char *buffer, int32 bufferSize)
{
// lookup the symbol
void *baseAddress;
char symbolName[1024];
char imageName[B_PATH_NAME_LENGTH];
bool exactMatch;
bool lookupSucceeded = false;
if (lookupContext) {
status_t error = debug_lookup_symbol_address(lookupContext, address,
&baseAddress, symbolName, sizeof(symbolName), imageName,
sizeof(imageName), &exactMatch);
lookupSucceeded = (error == B_OK);
}
if (lookupSucceeded) {
// we were able to look something up
if (strlen(symbolName) > 0) {
// we even got a symbol
snprintf(buffer, bufferSize, "%s + %#lx%s", symbolName,
(addr_t)address - (addr_t)baseAddress,
(exactMatch ? "" : " (closest symbol)"));
} else {
// no symbol: image relative address
snprintf(buffer, bufferSize, "(%s + %#lx)", imageName,
(addr_t)address - (addr_t)baseAddress);
}
} else {
// lookup failed: find area containing the IP
bool useAreaInfo = false;
area_info info;
ssize_t cookie = 0;
while (get_next_area_info(fTeam, &cookie, &info) == B_OK) {
if ((addr_t)info.address <= (addr_t)address
&& (addr_t)info.address + info.size > (addr_t)address) {
useAreaInfo = true;
break;
}
}
if (useAreaInfo) {
snprintf(buffer, bufferSize, "(%s + %#lx)", info.name,
(addr_t)address - (addr_t)info.address);
} else if (bufferSize > 0)
buffer[0] = '\0';
}
}
void
TeamDebugHandler::_PrintStackTrace(thread_id thread)
{
// print a stacktrace
void *ip = NULL;
void *stackFrameAddress = NULL;
status_t error = debug_get_instruction_pointer(&fDebugContext, thread, &ip,
&stackFrameAddress);
if (error == B_OK) {
// create a symbol lookup context
debug_symbol_lookup_context *lookupContext = NULL;
error = debug_create_symbol_lookup_context(fTeam, -1, &lookupContext);
if (error != B_OK) {
debug_printf("debug_server: Failed to create symbol lookup "
"context: %s\n", strerror(error));
}
// lookup the IP
char symbolBuffer[2048];
_LookupSymbolAddress(lookupContext, ip, symbolBuffer,
sizeof(symbolBuffer) - 1);
debug_printf("stack trace, current PC %p %s:\n", ip, symbolBuffer);
for (int32 i = 0; i < 50; i++) {
debug_stack_frame_info stackFrameInfo;
error = debug_get_stack_frame(&fDebugContext, stackFrameAddress,
&stackFrameInfo);
if (error < B_OK || stackFrameInfo.parent_frame == NULL)
break;
// lookup the return address
_LookupSymbolAddress(lookupContext, stackFrameInfo.return_address,
symbolBuffer, sizeof(symbolBuffer) - 1);
debug_printf(" (%p) %p %s\n", stackFrameInfo.frame,
stackFrameInfo.return_address, symbolBuffer);
stackFrameAddress = stackFrameInfo.parent_frame;
}
// delete the symbol lookup context
if (lookupContext)
debug_delete_symbol_lookup_context(lookupContext);
}
}
void
TeamDebugHandler::_NotifyAppServer(team_id team)
{
// This will remove any kWindowScreenFeels of the application, so that
// the debugger alert is visible on screen
BRoster::Private roster;
roster.ApplicationCrashed(team);
}
void
TeamDebugHandler::_NotifyRegistrar(team_id team, bool openAlert,
bool stopShutdown)
{
BMessage notify(BPrivate::B_REG_TEAM_DEBUGGER_ALERT);
notify.AddInt32("team", team);
notify.AddBool("open", openAlert);
notify.AddBool("stop shutdown", stopShutdown);
BRoster::Private roster;
BMessage reply;
roster.SendTo(&notify, &reply, false);
}
status_t
TeamDebugHandler::_InitGUI()
{
DebugServer *app = dynamic_cast<DebugServer*>(be_app);
BAutolock _(app);
return app->InitGUIContext();
}
status_t
TeamDebugHandler::_HandlerThreadEntry(void *data)
{
return ((TeamDebugHandler*)data)->_HandlerThread();
}
status_t
TeamDebugHandler::_HandlerThread()
{
TRACE(("debug_server: TeamDebugHandler::_HandlerThread(): team %" B_PRId32
"\n", fTeam));
// get initial message
TRACE(("debug_server: TeamDebugHandler::_HandlerThread(): team %" B_PRId32
": getting message...\n", fTeam));
DebugMessage *message;
status_t error = _PopMessage(message);
int32 debugAction = kActionKillTeam;
if (error == B_OK) {
// handle the message
debugAction = _HandleMessage(message);
delete message;
} else {
debug_printf("TeamDebugHandler::_HandlerThread(): Failed to pop "
"initial message: %s", strerror(error));
}
// kill the team or hand it over to the debugger
thread_id debuggerThread = -1;
if (debugAction == kActionKillTeam) {
// The team shall be killed. Since that is also the handling in case
// an error occurs while handing over the team to the debugger, we do
// nothing here.
} else if (debugAction == kActionWriteCoreFile) {
_WriteCoreFile();
debugAction = kActionKillTeam;
} else if ((debuggerThread = _EnterDebugger(
debugAction == kActionSaveReportTeam)) >= 0) {
// wait for the "handed over" or a "team deleted" message
bool terminate = false;
do {
error = _PopMessage(message);
if (error != B_OK) {
debug_printf("TeamDebugHandler::_HandlerThread(): Failed to "
"pop message: %s", strerror(error));
debugAction = kActionKillTeam;
break;
}
if (message->Code() == B_DEBUGGER_MESSAGE_HANDED_OVER) {
// The team has successfully been handed over to the debugger.
// Nothing to do.
terminate = true;
} else if (message->Code() == B_DEBUGGER_MESSAGE_TEAM_DELETED) {
// The team died. Nothing to do.
terminate = true;
} else {
// Some message we can ignore. The debugger will take care of
// it.
// check whether the debugger thread still lives
thread_info threadInfo;
if (get_thread_info(debuggerThread, &threadInfo) != B_OK) {
// the debugger is gone
debug_printf("debug_server: The debugger for team %"
B_PRId32 " seems to be gone.", fTeam);
debugAction = kActionKillTeam;
terminate = true;
}
}
delete message;
} while (!terminate);
} else
debugAction = kActionKillTeam;
if (debugAction == kActionKillTeam) {
// kill the team
_KillTeam();
}
// remove this handler from the roster and delete it
TeamDebugHandlerRoster::Default()->RemoveHandler(fTeam);
delete this;
return B_OK;
}
bool
TeamDebugHandler::_ExecutableNameEquals(const char *name) const
{
return strcmp(_LastPathComponent(fExecutablePath), name) == 0;
}
bool
TeamDebugHandler::_IsAppServer() const
{
return _ExecutableNameEquals("app_server");
}
bool
TeamDebugHandler::_IsInputServer() const
{
return _ExecutableNameEquals("input_server");
}
bool
TeamDebugHandler::_IsRegistrar() const
{
return _ExecutableNameEquals("registrar");
}
bool
TeamDebugHandler::_IsGUIServer() const
{
// app or input server
return _IsAppServer() || _IsInputServer() || _IsRegistrar();
}
const char *
TeamDebugHandler::_LastPathComponent(const char *path)
{
const char *lastSlash = strrchr(path, '/');
return lastSlash ? lastSlash + 1 : path;
}
team_id
TeamDebugHandler::_FindTeam(const char *name)
{
// Iterate through all teams and check their executable name.
int32 cookie = 0;
team_info teamInfo;
while (get_next_team_info(&cookie, &teamInfo) == B_OK) {
entry_ref ref;
if (BPrivate::get_app_ref(teamInfo.team, &ref) == B_OK) {
if (strcmp(ref.name, name) == 0)
return teamInfo.team;
}
}
return B_ENTRY_NOT_FOUND;
}
bool
TeamDebugHandler::_AreGUIServersAlive()
{
return _FindTeam("app_server") >= 0 && _FindTeam("input_server") >= 0
&& _FindTeam("registrar");
}
// #pragma mark -
DebugServer::DebugServer(status_t &error)
:
BServer(kSignature, false, &error),
fListenerPort(-1),
fListener(-1),
fTerminating(false)
{
}
status_t
DebugServer::Init()
{
// create listener port
fListenerPort = create_port(10, "kernel listener");
if (fListenerPort < 0)
return fListenerPort;
// spawn the listener thread
fListener = spawn_thread(_ListenerEntry, "kernel listener",
B_NORMAL_PRIORITY, this);
if (fListener < 0)
return fListener;
// register as default debugger
// TODO: could set default flags
status_t error = install_default_debugger(fListenerPort);
if (error != B_OK)
return error;
// resume the listener
resume_thread(fListener);
return B_OK;
}
bool
DebugServer::QuitRequested()
{
// Never give up, never surrender. ;-)
return false;
}
status_t
DebugServer::_ListenerEntry(void *data)
{
return ((DebugServer*)data)->_Listener();
}
status_t
DebugServer::_Listener()
{
while (!fTerminating) {
// receive the next debug message
DebugMessage *message = new DebugMessage;
int32 code;
ssize_t bytesRead;
do {
bytesRead = read_port(fListenerPort, &code, &message->Data(),
sizeof(debug_debugger_message_data));
} while (bytesRead == B_INTERRUPTED);
if (bytesRead < 0) {
debug_printf("debug_server: Failed to read from listener port: "
"%s. Terminating!\n", strerror(bytesRead));
exit(1);
}
TRACE(("debug_server: Got debug message: team: %" B_PRId32 ", code: %" B_PRId32
"\n", message->Data().origin.team, code));
message->SetCode((debug_debugger_message)code);
// dispatch the message
TeamDebugHandlerRoster::Default()->DispatchMessage(message);
}
return B_OK;
}
// #pragma mark -
int
main()
{
status_t error;
// for the time being let the debug server print to the syslog
int console = open("/dev/dprintf", O_RDONLY);
if (console < 0) {
debug_printf("debug_server: Failed to open console: %s\n",
strerror(errno));
}
dup2(console, STDOUT_FILENO);
dup2(console, STDERR_FILENO);
close(console);
// create the team debug handler roster
if (!TeamDebugHandlerRoster::CreateDefault()) {
debug_printf("debug_server: Failed to create team debug handler "
"roster.\n");
exit(1);
}
// create application
DebugServer server(error);
if (error != B_OK) {
debug_printf("debug_server: Failed to create BApplication: %s\n",
strerror(error));
exit(1);
}
// init application
error = server.Init();
if (error != B_OK) {
debug_printf("debug_server: Failed to init application: %s\n",
strerror(error));
exit(1);
}
server.Run();
return 0;
}