93027f442f
but also prints post syscall entries not matching the filter, if the respective pre syscall entry matched. This way one also sees the syscall return values. Works only in forward direction ATM. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25095 a95241bf-73f2-0310-859d-f6bbb57e9c96
626 lines
14 KiB
C++
626 lines
14 KiB
C++
/*
|
|
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
|
|
* Copyright 2004-2006, Haiku Inc. All rights reserved.
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
/* Big case statement for dispatching syscalls */
|
|
|
|
#include <kernel.h>
|
|
#include <ksyscalls.h>
|
|
#include <syscalls.h>
|
|
#include <generic_syscall.h>
|
|
#include <debug.h>
|
|
#include <int.h>
|
|
#include <elf.h>
|
|
#include <vfs.h>
|
|
#include <vm.h>
|
|
#include <thread.h>
|
|
#include <sem.h>
|
|
#include <port.h>
|
|
#include <cpu.h>
|
|
#include <arch_config.h>
|
|
#include <disk_device_manager/ddm_userland_interface.h>
|
|
#include <sys/resource.h>
|
|
#include <fs/fd.h>
|
|
#include <fs/node_monitor.h>
|
|
#include <kimage.h>
|
|
#include <ksignal.h>
|
|
#include <real_time_clock.h>
|
|
#include <safemode.h>
|
|
#include <system_info.h>
|
|
#include <tracing.h>
|
|
#include <user_atomic.h>
|
|
#include <arch/system_info.h>
|
|
#include <messaging.h>
|
|
#include <frame_buffer_console.h>
|
|
#include <usergroup.h>
|
|
#include <wait_for_objects.h>
|
|
|
|
#include <malloc.h>
|
|
#include <string.h>
|
|
|
|
#include "syscall_numbers.h"
|
|
|
|
|
|
typedef struct generic_syscall generic_syscall;
|
|
|
|
struct generic_syscall {
|
|
list_link link;
|
|
char subsystem[B_FILE_NAME_LENGTH];
|
|
syscall_hook hook;
|
|
uint32 version;
|
|
uint32 flags;
|
|
generic_syscall *previous;
|
|
};
|
|
|
|
static struct mutex sGenericSyscallLock;
|
|
static struct list sGenericSyscalls;
|
|
|
|
|
|
static int dump_syscall_tracing(int argc, char** argv);
|
|
|
|
|
|
static generic_syscall *
|
|
find_generic_syscall(const char *subsystem)
|
|
{
|
|
generic_syscall *syscall = NULL;
|
|
|
|
ASSERT_LOCKED_MUTEX(&sGenericSyscallLock);
|
|
|
|
while ((syscall = (generic_syscall*)list_get_next_item(&sGenericSyscalls,
|
|
syscall)) != NULL) {
|
|
if (!strcmp(syscall->subsystem, subsystem))
|
|
return syscall;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/** Calls the generic syscall subsystem if any.
|
|
* Also handles the special generic syscall function \c B_SYSCALL_INFO.
|
|
* Returns \c B_NAME_NOT_FOUND if either the subsystem was not found, or
|
|
* the subsystem does not support the requested function.
|
|
* All other return codes are depending on the generic syscall implementation.
|
|
*/
|
|
|
|
static inline status_t
|
|
_user_generic_syscall(const char *userSubsystem, uint32 function,
|
|
void *buffer, size_t bufferSize)
|
|
{
|
|
char subsystem[B_FILE_NAME_LENGTH];
|
|
generic_syscall *syscall;
|
|
status_t status = B_NAME_NOT_FOUND;
|
|
|
|
if (!IS_USER_ADDRESS(userSubsystem)
|
|
|| user_strlcpy(subsystem, userSubsystem, sizeof(subsystem)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
//dprintf("generic_syscall(subsystem = \"%s\", function = %lu)\n", subsystem, function);
|
|
|
|
mutex_lock(&sGenericSyscallLock);
|
|
|
|
syscall = find_generic_syscall(subsystem);
|
|
if (syscall == NULL)
|
|
goto out;
|
|
|
|
if (function >= B_RESERVED_SYSCALL_BASE) {
|
|
if (function != B_SYSCALL_INFO) {
|
|
// this is all we know
|
|
status = B_NAME_NOT_FOUND;
|
|
goto out;
|
|
}
|
|
|
|
// special info syscall
|
|
if (bufferSize != sizeof(uint32))
|
|
status = B_BAD_VALUE;
|
|
else {
|
|
uint32 requestedVersion;
|
|
|
|
// retrieve old version
|
|
status = user_memcpy(&requestedVersion, buffer, sizeof(uint32));
|
|
if (status == B_OK && requestedVersion != 0 && requestedVersion < syscall->version)
|
|
status = B_BAD_TYPE;
|
|
|
|
// return current version
|
|
if (status == B_OK)
|
|
status = user_memcpy(buffer, &syscall->version, sizeof(uint32));
|
|
}
|
|
} else {
|
|
while (syscall != NULL) {
|
|
generic_syscall *next;
|
|
|
|
mutex_unlock(&sGenericSyscallLock);
|
|
|
|
status = syscall->hook(subsystem, function, buffer, bufferSize);
|
|
|
|
mutex_lock(&sGenericSyscallLock);
|
|
if (status != B_BAD_HANDLER)
|
|
break;
|
|
|
|
// the syscall may have been removed in the mean time
|
|
next = find_generic_syscall(subsystem);
|
|
if (next == syscall)
|
|
syscall = syscall->previous;
|
|
else
|
|
syscall = next;
|
|
}
|
|
|
|
if (syscall == NULL)
|
|
status = B_NAME_NOT_FOUND;
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&sGenericSyscallLock);
|
|
return status;
|
|
}
|
|
|
|
|
|
static inline int
|
|
_user_is_computer_on(void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// map to the arch specific call
|
|
|
|
static inline int64
|
|
_user_restore_signal_frame()
|
|
{
|
|
syscall_64_bit_return_value();
|
|
|
|
return arch_restore_signal_frame();
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
int32
|
|
syscall_dispatcher(uint32 call_num, void *args, uint64 *call_ret)
|
|
{
|
|
bigtime_t startTime;
|
|
|
|
// dprintf("syscall_dispatcher: thread 0x%x call 0x%x, arg0 0x%x, arg1 0x%x arg2 0x%x arg3 0x%x arg4 0x%x\n",
|
|
// thread_get_current_thread_id(), call_num, arg0, arg1, arg2, arg3, arg4);
|
|
|
|
user_debug_pre_syscall(call_num, args);
|
|
|
|
startTime = system_time();
|
|
|
|
switch (call_num) {
|
|
// the cases are auto-generated
|
|
#include "syscall_dispatcher.h"
|
|
|
|
default:
|
|
*call_ret = (uint64)B_BAD_VALUE;
|
|
}
|
|
|
|
user_debug_post_syscall(call_num, args, *call_ret, startTime);
|
|
|
|
// dprintf("syscall_dispatcher: done with syscall 0x%x\n", call_num);
|
|
|
|
return B_HANDLED_INTERRUPT;
|
|
}
|
|
|
|
|
|
status_t
|
|
generic_syscall_init(void)
|
|
{
|
|
list_init(&sGenericSyscalls);
|
|
if (mutex_init(&sGenericSyscallLock, "generic syscall") != B_OK) {
|
|
panic("generic_syscall_init(): mutex init failed");
|
|
return B_ERROR;
|
|
}
|
|
|
|
#if ENABLE_TRACING && defined(SYSCALL_TRACING)
|
|
add_debugger_command_etc("straced", &dump_syscall_tracing,
|
|
"Dump recorded syscall trace entries",
|
|
"Prints recorded trace entries. It is wrapper for the \"traced\"\n"
|
|
"command and supports all of its command line options (though\n"
|
|
"backward tracing doesn't really work). The difference is that if a\n"
|
|
"pre syscall trace entry is encountered, the corresponding post\n"
|
|
"syscall traced entry is also printed, even if it doesn't match the\n"
|
|
"given filter.\n", 0);
|
|
#endif // ENABLE_TRACING
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
// public API
|
|
|
|
|
|
status_t
|
|
register_generic_syscall(const char *subsystem, syscall_hook hook,
|
|
uint32 version, uint32 flags)
|
|
{
|
|
struct generic_syscall *previous, *syscall;
|
|
status_t status;
|
|
|
|
if (hook == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
mutex_lock(&sGenericSyscallLock);
|
|
|
|
previous = find_generic_syscall(subsystem);
|
|
if (previous != NULL) {
|
|
if ((flags & B_DO_NOT_REPLACE_SYSCALL) != 0
|
|
|| version < previous->version) {
|
|
status = B_NAME_IN_USE;
|
|
goto out;
|
|
}
|
|
if (previous->flags & B_SYSCALL_NOT_REPLACEABLE) {
|
|
status = B_NOT_ALLOWED;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
syscall = (generic_syscall *)malloc(sizeof(struct generic_syscall));
|
|
if (syscall == NULL) {
|
|
status = B_NO_MEMORY;
|
|
goto out;
|
|
}
|
|
|
|
strlcpy(syscall->subsystem, subsystem, sizeof(syscall->subsystem));
|
|
syscall->hook = hook;
|
|
syscall->version = version;
|
|
syscall->flags = flags;
|
|
syscall->previous = previous;
|
|
list_add_item(&sGenericSyscalls, syscall);
|
|
|
|
if (previous != NULL)
|
|
list_remove_link(&previous->link);
|
|
|
|
status = B_OK;
|
|
|
|
out:
|
|
mutex_unlock(&sGenericSyscallLock);
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
unregister_generic_syscall(const char *subsystem, uint32 version)
|
|
{
|
|
// ToDo: we should only remove the syscall with the matching version
|
|
generic_syscall *syscall;
|
|
status_t status;
|
|
|
|
mutex_lock(&sGenericSyscallLock);
|
|
|
|
syscall = find_generic_syscall(subsystem);
|
|
if (syscall != NULL) {
|
|
if (syscall->previous != NULL) {
|
|
// reestablish the old syscall
|
|
list_add_item(&sGenericSyscalls, syscall->previous);
|
|
}
|
|
list_remove_link(&syscall->link);
|
|
free(syscall);
|
|
status = B_OK;
|
|
} else
|
|
status = B_NAME_NOT_FOUND;
|
|
|
|
mutex_unlock(&sGenericSyscallLock);
|
|
return status;
|
|
}
|
|
|
|
|
|
// #pragma mark - syscall tracing
|
|
|
|
|
|
#ifdef SYSCALL_TRACING
|
|
|
|
namespace SyscallTracing {
|
|
|
|
|
|
static const char*
|
|
get_syscall_name(uint32 syscall)
|
|
{
|
|
if (syscall >= (uint32)kSyscallCount)
|
|
return "<invalid syscall number>";
|
|
|
|
return kExtendedSyscallInfos[syscall].name;
|
|
}
|
|
|
|
|
|
class PreSyscall : public AbstractTraceEntry {
|
|
public:
|
|
PreSyscall(uint32 syscall, const void* parameters)
|
|
:
|
|
fSyscall(syscall),
|
|
fParameters(NULL)
|
|
{
|
|
if (syscall < (uint32)kSyscallCount) {
|
|
fParameters = alloc_tracing_buffer_memcpy(parameters,
|
|
kSyscallInfos[syscall].parameter_size, false);
|
|
|
|
// copy string parameters, if any
|
|
if (fParameters != NULL && syscall != SYSCALL_KTRACE_OUTPUT) {
|
|
int32 stringIndex = 0;
|
|
const extended_syscall_info& syscallInfo
|
|
= kExtendedSyscallInfos[fSyscall];
|
|
for (int i = 0; i < syscallInfo.parameter_count; i++) {
|
|
const syscall_parameter_info& paramInfo
|
|
= syscallInfo.parameters[i];
|
|
if (paramInfo.type != B_STRING_TYPE)
|
|
continue;
|
|
|
|
const uint8* data
|
|
= (uint8*)fParameters + paramInfo.offset;
|
|
if (stringIndex < MAX_PARAM_STRINGS) {
|
|
fParameterStrings[stringIndex++]
|
|
= alloc_tracing_buffer_strcpy(
|
|
*(const char**)data, 64, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Initialized();
|
|
}
|
|
|
|
virtual void AddDump(TraceOutput& out)
|
|
{
|
|
out.Print("syscall pre: %s(", get_syscall_name(fSyscall));
|
|
|
|
if (fParameters != NULL) {
|
|
int32 stringIndex = 0;
|
|
const extended_syscall_info& syscallInfo
|
|
= kExtendedSyscallInfos[fSyscall];
|
|
for (int i = 0; i < syscallInfo.parameter_count; i++) {
|
|
const syscall_parameter_info& paramInfo
|
|
= syscallInfo.parameters[i];
|
|
const uint8* data = (uint8*)fParameters + paramInfo.offset;
|
|
uint64 value = 0;
|
|
bool printValue = true;
|
|
switch (paramInfo.type) {
|
|
case B_INT8_TYPE:
|
|
value = *(uint8*)data;
|
|
break;
|
|
case B_INT16_TYPE:
|
|
value = *(uint16*)data;
|
|
break;
|
|
case B_INT32_TYPE:
|
|
value = *(uint32*)data;
|
|
break;
|
|
case B_INT64_TYPE:
|
|
value = *(uint64*)data;
|
|
break;
|
|
case B_POINTER_TYPE:
|
|
value = (uint64)*(void**)data;
|
|
break;
|
|
case B_STRING_TYPE:
|
|
if (stringIndex < MAX_PARAM_STRINGS
|
|
&& fSyscall != SYSCALL_KTRACE_OUTPUT) {
|
|
out.Print("%s\"%s\"",
|
|
(i == 0 ? "" : ", "),
|
|
fParameterStrings[stringIndex++]);
|
|
printValue = false;
|
|
} else
|
|
value = (uint64)*(void**)data;
|
|
break;
|
|
}
|
|
|
|
if (printValue)
|
|
out.Print("%s0x%llx", (i == 0 ? "" : ", "), value);
|
|
}
|
|
}
|
|
|
|
out.Print(")");
|
|
}
|
|
|
|
private:
|
|
enum { MAX_PARAM_STRINGS = 3 };
|
|
|
|
uint32 fSyscall;
|
|
void* fParameters;
|
|
const char* fParameterStrings[MAX_PARAM_STRINGS];
|
|
};
|
|
|
|
|
|
class PostSyscall : public AbstractTraceEntry {
|
|
public:
|
|
PostSyscall(uint32 syscall, uint64 returnValue)
|
|
:
|
|
fSyscall(syscall),
|
|
fReturnValue(returnValue)
|
|
{
|
|
Initialized();
|
|
#if 0
|
|
if (syscall < (uint32)kSyscallCount
|
|
&& returnValue != (returnValue & 0xffffffff)
|
|
&& kExtendedSyscallInfos[syscall].return_type.size <= 4) {
|
|
panic("syscall return value 64 bit although it should be 32 "
|
|
"bit");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
virtual void AddDump(TraceOutput& out)
|
|
{
|
|
out.Print("syscall post: %s() -> 0x%llx",
|
|
get_syscall_name(fSyscall), fReturnValue);
|
|
}
|
|
|
|
private:
|
|
uint32 fSyscall;
|
|
uint64 fReturnValue;
|
|
};
|
|
|
|
} // namespace SyscallTracing
|
|
|
|
|
|
extern "C" void trace_pre_syscall(uint32 syscallNumber, const void* parameters);
|
|
|
|
void
|
|
trace_pre_syscall(uint32 syscallNumber, const void* parameters)
|
|
{
|
|
#ifdef SYSCALL_TRACING_IGNORE_KTRACE_OUTPUT
|
|
if (syscallNumber != SYSCALL_KTRACE_OUTPUT)
|
|
#endif
|
|
{
|
|
new(std::nothrow) SyscallTracing::PreSyscall(syscallNumber, parameters);
|
|
}
|
|
}
|
|
|
|
|
|
extern "C" void trace_post_syscall(int syscallNumber, uint64 returnValue);
|
|
|
|
void
|
|
trace_post_syscall(int syscallNumber, uint64 returnValue)
|
|
{
|
|
#ifdef SYSCALL_TRACING_IGNORE_KTRACE_OUTPUT
|
|
if (syscallNumber != SYSCALL_KTRACE_OUTPUT)
|
|
#endif
|
|
{
|
|
new(std::nothrow) SyscallTracing::PostSyscall(syscallNumber,
|
|
returnValue);
|
|
}
|
|
}
|
|
|
|
|
|
using namespace SyscallTracing;
|
|
|
|
class SyscallWrapperTraceFilter : public WrapperTraceFilter {
|
|
public:
|
|
virtual void Init(TraceFilter* filter, int direction, bool continued)
|
|
{
|
|
fFilter = filter;
|
|
fHitThreadLimit = false;
|
|
fDirection = direction;
|
|
|
|
if (!continued)
|
|
fPendingThreadCount = 0;
|
|
}
|
|
|
|
virtual bool Filter(const TraceEntry* _entry, LazyTraceOutput& out)
|
|
{
|
|
if (fFilter == NULL)
|
|
return true;
|
|
|
|
if (fDirection < 0)
|
|
return fFilter->Filter(_entry, out);
|
|
|
|
if (const PreSyscall* entry = dynamic_cast<const PreSyscall*>(_entry)) {
|
|
_RemovePendingThread(entry->Thread());
|
|
|
|
bool accepted = fFilter->Filter(entry, out);
|
|
if (accepted)
|
|
_AddPendingThread(entry->Thread());
|
|
return accepted;
|
|
|
|
} else if (const PostSyscall* entry
|
|
= dynamic_cast<const PostSyscall*>(_entry)) {
|
|
bool wasPending = _RemovePendingThread(entry->Thread());
|
|
|
|
return wasPending || fFilter->Filter(entry, out);
|
|
|
|
} else if (const AbstractTraceEntry* entry
|
|
= dynamic_cast<const AbstractTraceEntry*>(_entry)) {
|
|
bool isPending = _IsPendingThread(entry->Thread());
|
|
|
|
return isPending || fFilter->Filter(entry, out);
|
|
|
|
} else {
|
|
return fFilter->Filter(_entry, out);
|
|
}
|
|
}
|
|
|
|
bool HitThreadLimit() const
|
|
{
|
|
return fHitThreadLimit;
|
|
}
|
|
|
|
int Direction() const
|
|
{
|
|
return fDirection;
|
|
}
|
|
|
|
private:
|
|
enum {
|
|
MAX_PENDING_THREADS = 32
|
|
};
|
|
|
|
bool _AddPendingThread(thread_id thread)
|
|
{
|
|
int32 index = _PendingThreadIndex(thread);
|
|
if (index >= 0)
|
|
return true;
|
|
|
|
if (fPendingThreadCount == MAX_PENDING_THREADS) {
|
|
fHitThreadLimit = true;
|
|
return false;
|
|
}
|
|
|
|
fPendingThreads[fPendingThreadCount++] = thread;
|
|
return true;
|
|
}
|
|
|
|
bool _RemovePendingThread(thread_id thread)
|
|
{
|
|
int32 index = _PendingThreadIndex(thread);
|
|
if (index < 0)
|
|
return false;
|
|
|
|
if (index + 1 < fPendingThreadCount) {
|
|
memmove(fPendingThreads + index, fPendingThreads + index + 1,
|
|
fPendingThreadCount - index - 1);
|
|
}
|
|
|
|
fPendingThreadCount--;
|
|
return true;
|
|
}
|
|
|
|
bool _IsPendingThread(thread_id thread)
|
|
{
|
|
return _PendingThreadIndex(thread) >= 0;
|
|
}
|
|
|
|
int32 _PendingThreadIndex(thread_id thread)
|
|
{
|
|
for (int32 i = 0; i < fPendingThreadCount; i++) {
|
|
if (fPendingThreads[i] == thread)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
TraceFilter* fFilter;
|
|
thread_id fPendingThreads[MAX_PENDING_THREADS];
|
|
int32 fPendingThreadCount;
|
|
int fDirection;
|
|
bool fHitThreadLimit;
|
|
};
|
|
|
|
|
|
static SyscallWrapperTraceFilter sFilter;
|
|
|
|
static int
|
|
dump_syscall_tracing(int argc, char** argv)
|
|
{
|
|
new(&sFilter) SyscallWrapperTraceFilter;
|
|
int result = dump_tracing(argc, argv, &sFilter);
|
|
|
|
if (sFilter.HitThreadLimit()) {
|
|
kprintf("Warning: The thread buffer was too small to track all "
|
|
"threads!\n");
|
|
} else if (sFilter.HitThreadLimit()) {
|
|
kprintf("Warning: Can't track syscalls backwards!\n");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
#endif // SYSCALL_TRACING
|
|
|
|
|
|
/*
|
|
* kSyscallCount and kSyscallInfos here
|
|
*/
|
|
// generated by gensyscalls
|
|
#include "syscall_table.h"
|