HV:Acrn-hypvervisor Root Directory Clean-up and create misc/ folder for Acrn daemons, services and tools.

This patch is to clean-up acrn-hypervisor root directory, targt only 5 folders under acrn-hypervisor:1.hypervisor,2.devicemodel,3.misc,4.doc,5.build

Tracked-On: #3482
Signed-off-by: Terry Zou <terry.zou@intel.com>
Acked-by: Eddie Dong <eddie.dong@intel.com>
This commit is contained in:
Terry Zou
2019-07-29 12:21:54 +08:00
committed by Xie, Nanlin
parent 555a03db99
commit a9c38a5cfb
119 changed files with 62 additions and 57 deletions

View File

@@ -0,0 +1,79 @@
MAJOR_VERSION=1
MINOR_VERSION=0
VERSION_H = $(BUILDDIR)/include/usercrash/version.h
.PHONY: all check_obj
all: $(VERSION_H) check_obj usercrash_s usercrash_c debugger
rm -f $(VERSION_H)
INCLUDE += -I $(CURDIR)/include/
INCLUDE += -I $(BUILDDIR)/include/usercrash
LIBS = -levent -lpthread $(EXTRA_LIBS)
usercrash_s: $(BUILDDIR)/usercrash/obj/protocol.o \
$(BUILDDIR)/usercrash/obj/server.o \
$(BUILDDIR)/common/obj/log_sys.o
$(CC) -g $(CFLAGS) $(INCLUDE) $^ -o $(BUILDDIR)/usercrash/bin/$@ $(LIBS) $(LDFLAGS)
usercrash_c: $(BUILDDIR)/usercrash/obj/protocol.o \
$(BUILDDIR)/usercrash/obj/client.o \
$(BUILDDIR)/usercrash/obj/crash_dump.o \
$(BUILDDIR)/common/obj/log_sys.o \
$(BUILDDIR)/common/obj/cmdutils.o \
$(BUILDDIR)/common/obj/fsutils.o \
$(BUILDDIR)/common/obj/strutils.o
$(CC) -g $(CFLAGS) $(INCLUDE) $^ -o $(BUILDDIR)/usercrash/bin/$@ $(LIBS) $(LDFLAGS)
debugger: $(BUILDDIR)/usercrash/obj/debugger.o \
$(BUILDDIR)/usercrash/obj/crash_dump.o \
$(BUILDDIR)/common/obj/log_sys.o \
$(BUILDDIR)/common/obj/cmdutils.o \
$(BUILDDIR)/common/obj/fsutils.o \
$(BUILDDIR)/common/obj/strutils.o
$(CC) -g $(CFLAGS) $(INCLUDE) $^ -o $(BUILDDIR)/usercrash/bin/$@ $(LIBS) $(LDFLAGS)
$(BUILDDIR)/usercrash/obj/%.o:%.c
$(CC) $(CFLAGS) $(INCLUDE) -o $@ -c $<
$(VERSION_H):
@if [ ! -d $(BUILDDIR)/include/usercrash ]; then \
mkdir -p $(BUILDDIR)/include/usercrash ; \
fi
touch $(VERSION_H)
@COMMIT=`git log -1 --pretty=format:%h . 2>/dev/null`;\
DIRTY=`git diff --name-only $(CURDIR)`;\
if [ -n "$$DIRTY" ];then PATCH="$$COMMIT-dirty";else PATCH="$$COMMIT";fi;\
TIME=`date "+%Y-%m-%d %H:%M:%S"`;\
USER=`id -u -n`; \
cat $(CURDIR)/../license_header > $(VERSION_H);\
echo "#define UC_MAJOR_VERSION $(MAJOR_VERSION)" >> $(VERSION_H);\
echo "#define UC_MINOR_VERSION $(MINOR_VERSION)" >> $(VERSION_H);\
echo "#define UC_BUILD_VERSION "\""$$PATCH"\""" >> $(VERSION_H);\
echo "#define UC_BUILD_TIME "\""$$TIME"\""" >> $(VERSION_H);\
echo "#define UC_BUILD_USER "\""$$USER"\""" >> $(VERSION_H)
check_obj:
@if [ ! -d $(BUILDDIR)/usercrash/bin ]; then \
mkdir -p $(BUILDDIR)/usercrash/bin ; \
fi
@if [ ! -d $(BUILDDIR)/usercrash/obj ]; then \
mkdir -p $(BUILDDIR)/usercrash/obj ; \
fi
.PHONY: clean
clean:
@echo "Clean objects and binaries"
@if [ -e $(VERSION_H) ]; then \
$(RM) -f $(VERSION_H); \
fi
@if [ -d $(BUILDDIR)/usercrash/obj ]; then \
find $(BUILDDIR)/usercrash/obj -name "*.o" -exec $(RM) {} \; 2>&1 || exit 0; \
fi
@if [ -d $(BUILDDIR)/usercrash/bin ]; then \
$(RM) -r $(BUILDDIR)/usercrash/bin ; \
fi
@if [ -d $(BUILDDIR)/usercrash/obj ]; then \
$(RM) -r $(BUILDDIR)/usercrash/obj ; \
fi

View File

@@ -0,0 +1,88 @@
.. _usercrash_doc:
usercrash
#########
Description
***********
The ``usercrash`` tool gets the crash info for the crashing process in
userspace. The collected information is saved as usercrash_xx under
``/var/log/usercrashes/``.
Design
******
``usercrash`` is designed using a Client/Server model. The server is
autostarted at boot. The client is configured in ``core_pattern``, which
will be triggered when a crash occurs in userspace. The client then
sends the crash event to the server. The server checks the files under
``/var/log/usercrashes/`` and creates a new file usercrash_xx (xx means
the index of the crash file). Then it sends the file descriptor (fd) to
the client. The client is responsible for collecting crash information
and saving it in the crashlog file. After the saving work is done, the
client notifies server and the server will clean up.
The work flow diagram:
::
+--------------------------------------------------+
| |
| Server Client |
| + + |
| | Send crash event | |
| | <-----------------------+ |
| | | |
| Create usercrash_xx | |
| | | |
| | Send usercrash_xx fd | |
| +-----------------------> | |
| | | |
| | Fill usercrash_xx |
| | | |
| | Notify completion | |
| | <-----------------------+ |
| | | |
| Clean up | |
| | | |
| v v |
| |
+--------------------------------------------------+
Usage
*****
- The server is launched automatically at boot after this tool is enabled with
instruction ``sudo crashlogctl enable``, and the client is configured in
``usercrash-wrapper``, which is set as the app of ``core_pattern``. In
``usercrash-wrapper``, it will collect and reorder the parameters of the
client and default app. Once a crash occurs in user space, the client and
default app will be invoked separately.
- The ``debugger`` is an independent tool to dump the debug information of the
specific process, including backtrace, stack, opened files, registers value,
memory content around registers, and etc.
.. code-block:: none
$ debugger <pid>
.. note::
You need to be ``root`` to use the ``debugger``.
Source Code
***********
- client.c : This file is the implementation for client of ``usercrash``, which
is responsible for delivering the ``usercrash`` event to the server, and
collecting crash information and saving it to the crashfile.
- crash_dump.c : This file is the implementation for dumping the crash
information, including backtrace stack, opened files, registers value, memory
content around registers, and etc.
- debugger.c : This file is to implement a tool, which runs in command line to
dump the process information list above.
- protocol.c : This file is the socket protocol implement file.
- server.c : This file is the implement file for server of ``usercrash``, which
is responsible for creating the crashfile and handle the events from client.

View File

@@ -0,0 +1,238 @@
/*
* Copyright (C) <2018> Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "crash_dump.h"
#include "packet.h"
#include "log_sys.h"
#include "protocol.h"
#include "version.h"
/**
* Usercrash works as C/S model: usercrash_c works as usercrash client to
* collect crash logs and information once crash event occurs. For each time,
* usercrash_c receives 3 params from core_dump and sends connect request event
* to usercrash_s, then it receives file fd from server to fill crash info into
* the file. After this work is done, it will notify server that dump work is
* completed.
*/
/**
* @sockfd: the socket fd.
* set_timeout is used to set timeout for the sockfd, in case client is blocked
* when client cannot receive the data from server, or send data to server
*/
static int set_timeout(int sockfd)
{
struct timeval timeout = {50, 0};
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
sizeof(timeout)) != 0) {
LOGE("failed to set receive timeout\n");
return -1;
}
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout,
sizeof(timeout)) != 0) {
LOGE("failed to set send timeout\n");
return -1;
}
return 0;
}
/**
* @pid: crash process pid.
* @usercrashd_socket: client socket fd pointer, get the value from
* usercrashd_connect function.
* @output_fd: file fd, receives from server to store dump file.
* @name: crash process name
* usercrashd_connect is used to connect to server, and get the crash file fd
* from server
*/
static int usercrashd_connect(int pid, int *usercrashd_socket,
int *output_fd, const char *name)
{
int sockfd;
int tmp_output_fd;
ssize_t rc;
int flags;
struct crash_packet packet = {0};
if (name == NULL) {
LOGE("crash process name is NULL\n");
return -1;
}
sockfd = socket_local_client(SOCKET_NAME, strlen(SOCKET_NAME),
SOCK_SEQPACKET | SOCK_CLOEXEC);
if (sockfd == -1) {
LOGE("failed to connect to usercrashd, error (%s)\n",
strerror(errno));
return -1;
}
packet.packet_type = kDumpRequest;
packet.pid = pid;
strncpy(packet.name, name, COMM_NAME_LEN);
packet.name[COMM_NAME_LEN - 1] = '\0';
if (set_timeout(sockfd)) {
close(sockfd);
return -1;
}
if (write(sockfd, &packet, sizeof(packet)) !=
sizeof(packet)) {
LOGE("failed to write DumpRequest packet, error (%s)\n",
strerror(errno));
close(sockfd);
return -1;
}
rc = recv_fd(sockfd, &packet, sizeof(packet),
&tmp_output_fd);
if (rc == -1) {
LOGE("failed to read response to DumpRequest packet, ");
LOGE("error (%s)\n", strerror(errno));
close(sockfd);
return -1;
} else if (rc != sizeof(packet)) {
LOGE("received DumpRequest response packet of incorrect ");
LOGE("length (expected %lu, got %ld)\n", sizeof(packet), rc);
goto fail;
}
if (packet.packet_type != kPerformDump) {
LOGE("unexpected dump response:%d\n", packet.packet_type);
goto fail;
}
/**
* Make the fd O_APPEND so that our output is guaranteed to be at the
* end of a file. (This also makes selinux rules consistent, because
* selinux distinguishes between writing to a regular fd, and writing
* to an fd with O_APPEND)
*/
flags = fcntl(tmp_output_fd, F_GETFL);
if (fcntl(tmp_output_fd, F_SETFL, flags | O_APPEND) != 0) {
LOGE("failed to set output fd flags, error (%s)\n",
strerror(errno));
goto fail;
}
*usercrashd_socket = sockfd;
*output_fd = tmp_output_fd;
return 0;
fail:
close(sockfd);
close(tmp_output_fd);
return -1;
}
/**
* @usercrashd_socket: client socket fd, used to communicate with server.
* usercrashd_notify_completion is used to tell the server it has done the
* dump, the server will pop another crash from the queue and execute the dump
* process
*/
static int usercrashd_notify_completion(int usercrashd_socket)
{
struct crash_packet packet = {0};
packet.packet_type = kCompletedDump;
if (set_timeout(usercrashd_socket)) {
close(usercrashd_socket);
return -1;
}
if (write(usercrashd_socket, &packet,
sizeof(packet)) != sizeof(packet)) {
return -1;
}
return 0;
}
static void print_usage(void)
{
printf("usercrash - tool to dump crash info for the crashes in the ");
printf("userspace on sos. It's the client role of usercrash.\n");
printf("[Usage]\n");
printf("\t--coredump, usercrash_c <pid> <comm> <sig> ");
printf("(root role to run)\n");
printf("[Option]\n");
printf("\t-h: print this usage message\n");
printf("\t-v: print usercrash_c version\n");
}
int main(int argc, char *argv[])
{
int pid;
int sig;
int out_fd;
int sock;
int ret;
if (argc > 1) {
if (strcmp(argv[1], "-v") == 0) {
printf("version is %d.%d-%s, build by %s@%s\n",
UC_MAJOR_VERSION, UC_MINOR_VERSION,
UC_BUILD_VERSION, UC_BUILD_USER,
UC_BUILD_TIME);
return 0;
}
if (strcmp(argv[1], "-h") == 0) {
print_usage();
return 0;
}
} else
print_usage();
if (getuid() != 0) {
LOGE("failed to execute usercrash_c, root is required\n");
exit(EXIT_FAILURE);
}
if (argc == 4) {
/* it's from coredump */
pid = (int)strtol(argv[1], NULL, 10);
sig = (int)strtol(argv[3], NULL, 10);
ret = usercrashd_connect(pid, &sock, &out_fd, argv[2]);
if (ret) {
LOGE("usercrashd_connect failed, error (%s)\n",
strerror(errno));
exit(EXIT_FAILURE);
}
crash_dump(pid, sig, out_fd);
close(out_fd);
if (usercrashd_notify_completion(sock)) {
LOGE("failed to notify usercrashd of completion");
close(sock);
exit(EXIT_FAILURE);
}
close(sock);
} else {
print_usage();
return 0;
}
return 0;
}

View File

@@ -0,0 +1,350 @@
/*
* Copyright (C) <2018> Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include "log_sys.h"
#include "crash_dump.h"
#include "cmdutils.h"
#include "fsutils.h"
#include "strutils.h"
#define GET_TID "/proc/%d/status"
#define GET_COMM "/proc/%d/exe"
#define GET_K_STACK "/proc/%d/stack"
#define GET_MAPS "/proc/%d/maps"
#define GET_OPEN_FILES "/proc/%d/fd"
#define DEBUGGER_SIGNAL (__SIGRTMIN + 3)
#define DUMP_FILE "/tmp/core"
#define BUFFER_SIZE 8196
#define LINK_LEN 512
#define TID "Tgid:"
/* 128 means the length of the DUMP_FILE */
#define FORMAT_LENGTH (LINK_LEN + 128)
static void loginfo(int fd, const char *fmt, ...)
{
char *buf;
va_list ap;
int len, ret;
va_start(ap, fmt);
len = vasprintf(&buf, fmt, ap);
va_end(ap);
if (len == -1) {
LOGE("write to buf failed\n");
return;
}
ret = write(fd, buf, len);
if (ret != len) {
LOGE("write in loginfo failed\n");
}
free(buf);
}
static const char *get_signame(int sig)
{
switch (sig) {
case SIGABRT:
return "SIGABRT";
case SIGBUS:
return "SIGBUS";
case SIGFPE:
return "SIGFPE";
case SIGILL:
return "SIGILL";
case SIGSEGV:
return "SIGSEGV";
#if defined(SIGSTKFLT)
case SIGSTKFLT:
return "SIGSTKFLT";
#endif
case SIGSTOP:
return "SIGSTOP";
case SIGSYS:
return "SIGSYS";
case SIGTRAP:
return "SIGTRAP";
case DEBUGGER_SIGNAL:
return "<debuggerd signal>";
default:
return "?";
}
}
/**
* Save core dump to file.
* @filename: core dump file name
* return 0 on success, or -1 on error
*/
static int save_coredump(const char *filename)
{
size_t read_len;
size_t write_len;
char input_buffer[BUFFER_SIZE];
FILE *dump_file;
/* open dump file in write and binary mode */
dump_file = fopen(filename, "wb");
if (dump_file == NULL) {
LOGE("fopen core file failed\n");
return -1;
}
/* Read from STDIN and write to dump file */
while (1) {
read_len = fread(input_buffer, 1, BUFFER_SIZE, stdin);
if (read_len == 0)
break;
write_len = fwrite(input_buffer, 1, read_len, dump_file);
if (write_len == 0) {
LOGE("fwrite error\n");
fclose(dump_file);
return -1;
}
}
fclose(dump_file);
return 0;
}
static int get_backtrace(int pid, int fd, int sig, const char *comm)
{
char *membkt;
char format[FORMAT_LENGTH];
size_t len, ret;
int flen;
loginfo(fd, "\nBackTrace:\n\n");
memset(format, 0, sizeof(format));
if (sig == DEBUGGER_SIGNAL) {
flen = snprintf(format, sizeof(format), "-p %d", pid);
} else {
flen = snprintf(format, sizeof(format), "%s %s", comm,
DUMP_FILE);
if (save_coredump(DUMP_FILE) == -1) {
LOGE("save core file failed\n");
return -1;
}
}
if (s_not_expect(flen, sizeof(format))) {
LOGE("failed to generate format\n");
return -1;
}
len = exec_out2mem(&membkt, GET_GDB_INFO, format);
if (len <= 0) {
LOGE("get gdb info failed\n");
return -1;
}
ret = write(fd, membkt, len);
free(membkt);
if (ret != len) {
LOGE("write file failed\n");
return -1;
}
return 0;
}
/**
* Save process proc info to file.
* @pid: process pid
* @fd: usercrash file fd
* @path: process proc path
* @name: a string save to file
* return 0 on success, or -1 on error
*/
static int save_proc_info(int pid, int fd, const char *path, const char *name)
{
int ret;
char *data;
char format[128];
unsigned long size;
loginfo(fd, "\n%s:\n\n", name);
memset(format, 0, sizeof(format));
ret = snprintf(format, sizeof(format), path, pid);
if (s_not_expect(ret, sizeof(format))) {
LOGE("failed to generate format");
return -1;
}
ret = read_file(format, &size, (void *)&data);
if (ret) {
LOGE("read file failed\n");
return -1;
}
ret = write(fd, data, size);
if ((unsigned long)ret != size) {
LOGE("write file failed\n");
return -1;
}
free(data);
return 0;
}
static int get_openfiles(int pid, int fd, const char *path, const char *name)
{
int ret;
int loop;
int fdcount;
char *fdname;
char format[128];
char *files[256];
char linkname[LINK_LEN];
loginfo(fd, "\n%s:\n\n", name);
memset(format, 0, sizeof(format));
ret = snprintf(format, sizeof(format), path, pid);
if (s_not_expect(ret, sizeof(format))) {
LOGE("failed to generate format");
return -1;
}
fdcount = lsdir(format, files, ARRAY_SIZE(files));
if (fdcount < 0) {
LOGE("get fd list failed\n");
return -1;
}
for (loop = 2; loop < fdcount; loop++) {
memset(linkname, 0, LINK_LEN);
ret = readlink(files[loop], linkname, LINK_LEN);
if (ret < 0 || ret >= LINK_LEN) {
LOGE("get fd link fd failed\n");
continue;
}
fdname = strrchr(files[loop], '/') + 1;
loginfo(fd, "%s -> %s\n", fdname, linkname);
}
for (loop = 0; loop < fdcount; loop++)
free(files[loop]);
return 0;
}
static int save_usercrash_file(int pid, int tgid, const char *comm,
int sig, int out_fd)
{
int ret;
loginfo(out_fd, "*** *** *** *** *** *** *** *** *** *** *** *** *** ");
loginfo(out_fd, "*** *** ***\n");
loginfo(out_fd, "pid: %d, tgid: %d, comm: %s\n\n\n", pid, tgid, comm);
loginfo(out_fd, "signal: %d (%s)\n", sig, get_signame(sig));
ret = save_proc_info(pid, out_fd, GET_K_STACK, "Stack");
if (ret) {
LOGE("save stack failed\n");
return -1;
}
ret = save_proc_info(pid, out_fd, GET_MAPS, "Maps");
if (ret) {
LOGE("save maps failed\n");
return -1;
}
ret = get_openfiles(pid, out_fd, GET_OPEN_FILES, "Open files");
if (ret) {
LOGE("save openfiles failed\n");
return -1;
}
ret = get_backtrace(pid, out_fd, sig, comm);
if (ret) {
LOGE("save backtrace failed\n");
return -1;
}
return 0;
}
static int get_key_value(int pid, const char *path, const char *key,
const size_t klen, char *value, const size_t value_len)
{
int len;
int ret;
unsigned long size;
char *data;
char *msg;
char *start;
char *end;
char format[128];
memset(format, 0, sizeof(format));
ret = snprintf(format, sizeof(format), path, pid);
if (s_not_expect(ret, sizeof(format))) {
LOGE("failed to generate format");
return -1;
}
ret = read_file(format, &size, (void *)&data);
if (ret || !data) {
LOGE("read file failed\n");
return -1;
}
msg = strstr(data, key);
if (!msg) {
LOGE("find key:%s failed\n", key);
free(data);
return -1;
}
end = strchr(msg, '\n');
if (end == NULL)
end = data + size;
start = msg + klen;
len = end - start;
if (len >= (int)value_len) {
free(data);
return -1;
}
memcpy(value, start, len);
*(value + len) = 0;
free(data);
return 0;
}
/**
* Get and save process info to out_fd.
* @pid: process pid
* @sig: signal from core dump
* @out_fd: file fd to save info
*/
void crash_dump(int pid, int sig, int out_fd)
{
int tgid;
int ret;
char comm[LINK_LEN];
char result[16];
char format[128];
memset(format, 0, sizeof(format));
ret = snprintf(format, sizeof(format), GET_COMM, pid);
if (s_not_expect(ret, sizeof(format))) {
LOGE("failed to generate format\n");
return;
}
ret = readlink(format, comm, LINK_LEN);
if (ret < 0 || ret >= LINK_LEN) {
LOGE("get process exe link failed\n");
return;
}
comm[ret] = '\0';
ret = get_key_value(pid, GET_TID, TID, strlen(TID),
result, sizeof(result));
if (ret) {
LOGE("get Tgid error\n");
return;
}
tgid = (int)strtol(result, NULL, 10);
if (!sig)
sig = DEBUGGER_SIGNAL;
if (save_usercrash_file(pid, tgid, comm, sig, out_fd))
LOGE("dump log file failed\n");
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) <2018> Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "crash_dump.h"
#include "log_sys.h"
#include "version.h"
/**
* Debugger can work without server when uses "debugger pid" commands to
* debug the running process. This function will dump the process info on the
* screen, also you can relocate the info to a file.
*/
static void print_usage(void)
{
printf("debugger - tool to dump process info of a running process.\n");
printf("[Usage]\n");
printf("\t--shell cmd, debugger <pid> (root role to run)\n");
printf("[Option]\n");
printf("\t-h: print this usage message\n");
printf("\t-v: print debugger version\n");
}
int main(int argc, char *argv[])
{
int pid;
if (argc > 1) {
if (strcmp(argv[1], "-v") == 0) {
printf("version is %d.%d-%s, build by %s@%s\n",
UC_MAJOR_VERSION, UC_MINOR_VERSION,
UC_BUILD_VERSION, UC_BUILD_USER,
UC_BUILD_TIME);
return 0;
}
if (strcmp(argv[1], "-h") == 0) {
print_usage();
return 0;
}
} else
print_usage();
if (getuid() != 0) {
printf("failed to execute debugger, root is required\n");
exit(EXIT_FAILURE);
}
if (argc == 2) {
/* it's from shell cmd */
pid = (int)strtol(argv[1], NULL, 10);
crash_dump(pid, 0, STDOUT_FILENO);
} else {
print_usage();
return 0;
}
return 0;
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) <2018> Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef CRASH_DUMP_H
#define CRASH_DUMP_H
/**
* So far, crash_dump use gdb command to dump process info. But in the future,
* we will replace gdb command with a better implementation.
*/
#define GET_GDB_INFO "/usr/bin/gdb %s -batch "\
"-ex 'bt' "\
"-ex 'printf \"\nRegisters\n\"' "\
"-ex 'info registers' "\
"-ex 'printf \"\n\nMemory near rax\n\"' "\
"-ex 'x/16x $rax-0x20' "\
"-ex 'x/48x $rax' "\
"-ex 'printf \"\n\nMemory near rbx\n\"' "\
"-ex 'x/16x $rbx-0x20' "\
"-ex 'x/48x $rbx' "\
"-ex 'printf \"\n\nMemory near rcx\n\"' "\
"-ex 'x/16x $rcx-0x20' "\
"-ex 'x/48x $rcx' "\
"-ex 'printf \"\n\nMemory near rdx\n\"' "\
"-ex 'x/16x $rdx-0x20' "\
"-ex 'x/48x $rdx' "\
"-ex 'printf \"\n\nMemory near rsi\n\"' "\
"-ex 'x/16x $rsi-0x20' "\
"-ex 'x/48x $rsi' "\
"-ex 'printf \"\n\nMemory near rdi\n\"' "\
"-ex 'x/16x $rdi-0x20' "\
"-ex 'x/48x $rdi' "\
"-ex 'printf \"\n\nMemory near rbp\n\"' "\
"-ex 'x/16x $rbp-0x20' "\
"-ex 'x/48x $rbp' "\
"-ex 'printf \"\n\nMemory near rsp\n\"' "\
"-ex 'x/16x $rsp-0x20' "\
"-ex 'x/48x $rsp' "\
"-ex 'printf \"\n\ncode around rip\n\"' "\
"-ex 'x/8i $rip-0x20' "\
"-ex 'x/48i $rip' "\
"-ex 'printf \"\nThreads\n\n\"' "\
"-ex 'info threads' "\
"-ex 'printf \"\nThreads backtrace\n\n\"' "\
"-ex 'thread apply all bt' "\
"-ex 'quit'"
void crash_dump(int pid, int sig, int out_fd);
#endif

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) <2018> Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef CLIENT_H
#define CLIENT_H
#define COMM_NAME_LEN 64
#define SOCKET_NAME "user_crash"
#include <stdio.h>
enum CrashPacketType {
/* Initial request from crash_dump */
kDumpRequest = 0,
/* Notification of a completed crash dump */
kCompletedDump,
/* Responses to kRequest */
kPerformDump
};
struct crash_packet {
enum CrashPacketType packet_type;
int pid;
char name[COMM_NAME_LEN];
};
#endif

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) <2018> Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/cdefs.h>
#define RESERVED_SOCKET_PREFIX "/tmp/"
#define SOCKET_PATH_MAX 128
int create_socket_server(const char *name, int type);
int socket_local_client(const char *name, const size_t len, int type);
ssize_t send_fd(int sockfd, const void *data, size_t len, int fd);
ssize_t recv_fd(int sockfd, void *data, size_t len, int *out_fd);
#endif

View File

@@ -0,0 +1,221 @@
/*
* Copyright (C) <2018> Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stddef.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include "protocol.h"
#include "log_sys.h"
#define SUN_PATH_MAX \
(sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path))
/* Documented in header file. */
static int socket_make_sockaddr_un(const char *name, const size_t len,
struct sockaddr_un *p_addr, socklen_t *alen)
{
size_t socket_len;
socket_len = strlen(RESERVED_SOCKET_PREFIX);
if (socket_len >= SUN_PATH_MAX)
return -1;
strncpy(p_addr->sun_path, RESERVED_SOCKET_PREFIX, socket_len + 1);
if (len >= (SUN_PATH_MAX - socket_len))
return -1;
strncat(p_addr->sun_path, name, len);
p_addr->sun_family = AF_LOCAL;
*alen = len + socket_len +
offsetof(struct sockaddr_un, sun_path) + 1;
return 0;
}
/**
* connect to peer named "name" on fd
* returns same fd or -1 on error.
* fd is not closed on error. that's your job.
*
*/
static int socket_local_client_connect(int fd, const char *name,
const size_t len)
{
struct sockaddr_un addr;
socklen_t alen;
int err;
err = socket_make_sockaddr_un(name, len, &addr, &alen);
if (err < 0)
goto error;
if (connect(fd, (struct sockaddr *) &addr, alen) < 0) {
LOGE("connect to usercrashd failed ,error (%s)\n",
strerror(errno));
goto error;
}
return fd;
error:
return -1;
}
/**
* connect to peer named "name"
* returns fd or -1 on error
*/
int socket_local_client(const char *name, const size_t len, int type)
{
int s;
s = socket(AF_LOCAL, type, 0);
if (s < 0)
return -1;
if (socket_local_client_connect(s, name, len) < 0) {
close(s);
return -1;
}
return s;
}
static int socket_bind(int fd, const char *name)
{
struct sockaddr_un addr;
socklen_t alen;
size_t name_len;
addr.sun_family = AF_UNIX;
name_len = strnlen(name, SOCKET_PATH_MAX);
if (name_len >= SUN_PATH_MAX)
return -1;
*(char *)mempcpy(addr.sun_path, name, name_len) = '\0';
unlink(addr.sun_path);
alen = strnlen(addr.sun_path, SUN_PATH_MAX) + sizeof(addr.sun_family);
if (bind(fd, (struct sockaddr *)&addr, alen) == -1)
return -1;
return fd;
}
int create_socket_server(const char *name, int type)
{
int err;
int fd;
fd = socket(AF_UNIX, type, 0);
if (fd < 0)
return -1;
err = socket_bind(fd, name);
if (err < 0) {
close(fd);
return -1;
}
return fd;
}
ssize_t send_fd(int sockfd, const void *data, size_t len, int fd)
{
char cmsg_buf[CMSG_SPACE(sizeof(int))];
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsg;
memset(&iov, 0, sizeof(iov));
iov.iov_base = (void *)data;
iov.iov_len = len;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
*(int *)(CMSG_DATA(cmsg)) = fd;
return sendmsg(sockfd, &msg, 0);
}
ssize_t recv_fd(int sockfd, void *data, size_t len, int *out_fd)
{
char cmsg_buf[CMSG_SPACE(sizeof(int))];
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsg;
ssize_t result;
bool received_fd;
int fd;
memset(&iov, 0, sizeof(iov));
iov.iov_base = (void *)data;
iov.iov_len = len;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
msg.msg_flags = 0;
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
result = recvmsg(sockfd, &msg, 0);
if (result == -1)
return -1;
received_fd = msg.msg_controllen == sizeof(cmsg_buf);
if (received_fd)
fd = *(int *)(CMSG_DATA(cmsg));
else
return -1;
if ((msg.msg_flags & MSG_TRUNC) != 0) {
errno = EFBIG;
goto fail;
} else if ((msg.msg_flags & MSG_CTRUNC) != 0) {
errno = ERANGE;
goto fail;
}
if (out_fd) {
*out_fd = fd;
} else if (received_fd) {
errno = ERANGE;
goto fail;
}
return result;
fail:
close(fd);
return -1;
}

View File

@@ -0,0 +1,502 @@
/*
* Copyright (C) <2018> Intel Corporation
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Copyright (C) 2018 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/queue.h>
#include <pthread.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <malloc.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/thread.h>
#include <signal.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include "packet.h"
#include "protocol.h"
#include "log_sys.h"
#include "version.h"
#include "strutils.h"
#define FILE_PATH_LEN_MAX 256
/**
* Usercrash works as C/S model: usercrash_s works as usercrash server, which
* is to handle events from client in endless loop. Once server receives events
* from client, it will create usercrash_0x file under /var/log/usercrashes/
* and send file fd to client. Then server will wait for client filling the
* event info completely to the crash file. After client's work has been done,
* server will be responsiable to free the crash node and process other events.
*/
struct crash_node {
int crash_fd;
int pid;
int out_fd;
char name[COMM_NAME_LEN];
struct event *crash_event;
char crash_path[FILE_PATH_LEN_MAX];
TAILQ_ENTRY(crash_node) entries;
};
static const char usercrash_directory[] = "/var/log/usercrashes";
static int usercrash_count = 50;
static int next_usercrash;
static int kMaxConcurrentDumps = 1;
static int num_concurrent_dumps;
TAILQ_HEAD(, crash_node) queue_t;
static pthread_mutex_t queue_mtx = PTHREAD_MUTEX_INITIALIZER;
static void push_back(struct crash_node *crash);
static struct crash_node *pop_front(void);
/* Forward declare the callbacks so they can be placed in a sensible order */
static void crash_accept_cb(struct evconnlistener *listener,
evutil_socket_t sockfd, struct sockaddr*, int, void*);
static void crash_request_cb(evutil_socket_t sockfd, short ev, void *arg);
static void crash_completed_cb(evutil_socket_t sockfd, short ev, void *arg);
static void __attribute__((__unused__)) debuggerd_register_handlers(
struct sigaction *action)
{
sigaction(SIGABRT, action, NULL);
sigaction(SIGBUS, action, NULL);
sigaction(SIGFPE, action, NULL);
sigaction(SIGILL, action, NULL);
sigaction(SIGSEGV, action, NULL);
#if defined(SIGSTKFLT)
sigaction(SIGSTKFLT, action, NULL);
#endif
sigaction(SIGSYS, action, NULL);
sigaction(SIGTRAP, action, NULL);
}
static void free_crash(struct crash_node *crash)
{
event_free(crash->crash_event);
close(crash->crash_fd);
free(crash);
}
static void push_back(struct crash_node *crash)
{
pthread_mutex_lock(&queue_mtx);
TAILQ_INSERT_TAIL(&queue_t, crash, entries);
pthread_mutex_unlock(&queue_mtx);
}
static struct crash_node *pop_front(void)
{
struct crash_node *crash = NULL;
pthread_mutex_lock(&queue_mtx);
if (!TAILQ_EMPTY(&queue_t)) {
crash = TAILQ_FIRST(&queue_t);
TAILQ_REMOVE(&queue_t, crash, entries);
}
pthread_mutex_unlock(&queue_mtx);
return crash;
}
static void find_oldest_usercrash(void)
{
int i;
int len;
int oldest_usercrash = 0;
time_t oldest_time = LONG_MAX;
char path[FILE_PATH_LEN_MAX];
struct stat st;
memset(path, 0, FILE_PATH_LEN_MAX);
for (i = 0; i < usercrash_count; ++i) {
len = snprintf(path, sizeof(path), "%s/usercrash_%02d",
usercrash_directory, i);
if (s_not_expect(len, sizeof(path))) {
LOGE("failed to generate path\n");
continue;
}
if (stat(path, &st) != 0) {
if (errno == ENOENT) {
oldest_usercrash = i;
break;
}
LOGE("failed to stat %s\n", path);
continue;
}
if (st.st_mtime < oldest_time) {
oldest_usercrash = i;
oldest_time = st.st_mtime;
}
}
next_usercrash = oldest_usercrash;
}
static int get_usercrash(struct crash_node *crash)
{
/**
* If kMaxConcurrentDumps is greater than 1, then theoretically the
* same filename could be handed out to multiple processes. Unlink and
* create the file, instead of using O_TRUNC, to avoid two processes
* interleaving their output
*/
int result;
int len;
char file_name[FILE_PATH_LEN_MAX];
memset(file_name, 0, FILE_PATH_LEN_MAX);
len = snprintf(file_name, sizeof(file_name), "%s/usercrash_%02d",
usercrash_directory, next_usercrash);
if (s_not_expect(len, sizeof(file_name))) {
LOGE("failed to generate file name\n");
return -1;
}
if (unlink(file_name) != 0 && errno != ENOENT) {
LOGE("failed to unlink usercrash at %s\n", file_name);
return -1;
}
result = open(file_name, O_CREAT | O_WRONLY, 0644);
if (result == -1) {
LOGE("failed to create usercrash at %s\n", file_name);
return -1;
}
next_usercrash = (next_usercrash + 1) % usercrash_count;
crash->out_fd = result;
strncpy(crash->crash_path, file_name, FILE_PATH_LEN_MAX);
crash->crash_path[FILE_PATH_LEN_MAX - 1] = '\0';
return 0;
}
static void perform_request(struct crash_node *crash)
{
ssize_t rc;
struct crash_packet response = {0};
if (get_usercrash(crash)) {
LOGE("server exit for open usercrash file failed\n");
exit(EXIT_FAILURE);
}
LOGI("Prepare to write the '%s' log to %s\n",
crash->name, crash->crash_path);
response.packet_type = kPerformDump;
rc = send_fd(crash->crash_fd, &response,
sizeof(response), crash->out_fd);
close(crash->out_fd);
if (rc == -1) {
LOGE("failed to send response to CrashRequest\n");
goto fail;
} else if (rc != sizeof(response)) {
LOGE("crash socket write returned short\n");
goto fail;
} else {
struct timeval timeout = { 100, 0 };
struct event_base *base = event_get_base(crash->crash_event);
event_assign(crash->crash_event, base, crash->crash_fd,
EV_TIMEOUT | EV_READ,
crash_completed_cb, crash);
event_add(crash->crash_event, &timeout);
}
++num_concurrent_dumps;
return;
fail:
free_crash(crash);
}
static void dequeue_requests(void)
{
while (!TAILQ_EMPTY(&queue_t) &&
num_concurrent_dumps < kMaxConcurrentDumps) {
struct crash_node *next_crash = pop_front();
perform_request(next_crash);
}
}
static void crash_accept_cb(struct evconnlistener *listener,
evutil_socket_t sockfd,
struct sockaddr *sa __attribute__((unused)),
int socklen __attribute__((unused)),
void *user_data __attribute__((unused)))
{
struct event_base *base = evconnlistener_get_base(listener);
struct crash_node *crash = (struct crash_node *)malloc(
sizeof(struct crash_node));
struct timeval timeout = { 1, 0 };
struct event *crash_event = event_new(base, sockfd,
EV_TIMEOUT | EV_READ, crash_request_cb, crash);
if (!crash) {
LOGE("Malloc memory for crash failed.\n");
exit(EXIT_FAILURE);
}
memset(crash, 0, sizeof(struct crash_node));
crash->crash_fd = sockfd;
crash->crash_event = crash_event;
event_add(crash_event, &timeout);
}
static void crash_request_cb(evutil_socket_t sockfd, short ev, void *arg)
{
ssize_t rc;
struct crash_node *crash = arg;
struct crash_packet request = {0};
if ((ev & EV_TIMEOUT) != 0) {
LOGE("crash request timed out\n");
goto fail;
} else if ((ev & EV_READ) == 0) {
LOGE("usercrash server received unexpected event ");
LOGE("from crash socket\n");
goto fail;
}
rc = read(sockfd, &request, sizeof(request));
if (rc == -1) {
LOGE("failed to read from crash socket\n");
goto fail;
} else if (rc != sizeof(request)) {
LOGE("crash socket received short read of length %lu ", rc);
LOGE("(expected %lu)\n", sizeof(request));
goto fail;
}
if (request.packet_type != kDumpRequest) {
LOGE("unexpected crash packet type, expected kDumpRequest, ");
LOGE("received %d\n", request.packet_type);
goto fail;
}
crash->pid = request.pid;
strncpy(crash->name, request.name, COMM_NAME_LEN);
crash->name[COMM_NAME_LEN - 1] = '\0';
LOGI("received crash request from pid %d, name: %s\n",
crash->pid, crash->name);
if (num_concurrent_dumps == kMaxConcurrentDumps) {
LOGI("enqueueing crash request for pid %d\n", crash->pid);
push_back(crash);
} else {
perform_request(crash);
}
return;
fail:
free_crash(crash);
}
static void crash_completed_cb(evutil_socket_t sockfd, short ev, void *arg)
{
ssize_t rc;
struct crash_node *crash = arg;
struct crash_packet request = {0};
--num_concurrent_dumps;
if ((ev & EV_TIMEOUT) != 0) {
LOGE("error for crash request timed out, error (%s)\n",
strerror(errno));
goto out;
} else if ((ev & EV_READ) == 0) {
LOGE("usercrash server received unexpected event ");
LOGE("from crash socket\n");
goto out;
}
rc = read(sockfd, &request, sizeof(request));
if (rc == -1) {
LOGE("failed to read from crash socket\n");
goto out;
} else if (rc != sizeof(request)) {
LOGE("crash socket received short read of length %lu, ", rc);
LOGE("(expected %lu)\n", sizeof(request));
goto out;
}
if (request.packet_type != kCompletedDump) {
LOGE("unexpected crash packet type, expected kCompletedDump, ");
LOGE("received %d\n", request.packet_type);
goto out;
}
if (crash->crash_path) {
LOGI("usercrash log written to: %s, ", crash->crash_path);
LOGI("crash process name is: %s\n", crash->name);
}
out:
free_crash(crash);
/* If there's something queued up, let them proceed */
dequeue_requests();
}
static void sig_handler(int sig)
{
LOGE("received fatal signal: %d\n", sig);
exit(EXIT_FAILURE);
}
static void print_usage(void)
{
printf("usercrash - tool to dump crash info for the crashes in the ");
printf("userspace on sos. It's the server role of usercrash.\n");
printf("[Usage] usercrash_s (root role to run)\n");
printf("[Option]\n");
printf("\t-h: print this usage message\n");
printf("\t-v: print usercrash_s version\n");
printf("[Output] crash log is in %s folder\n", usercrash_directory);
}
int main(int argc, char *argv[])
{
char socket_path[SOCKET_PATH_MAX];
DIR *dir;
int fd;
int len;
evutil_socket_t crash_socket;
int opt;
struct sigaction action;
struct event_base *base;
struct evconnlistener *listener;
if (argc > 1) {
while ((opt = getopt(argc, argv, "vh")) != -1) {
switch (opt) {
case 'v':
printf("version is %d.%d-%s, build by %s@%s\n",
UC_MAJOR_VERSION, UC_MINOR_VERSION,
UC_BUILD_VERSION, UC_BUILD_USER,
UC_BUILD_TIME);
break;
case 'h':
print_usage();
break;
default:
printf("unknown option\n");
exit(EXIT_FAILURE);
}
}
return 0;
}
if (getuid() != 0) {
LOGE("failed to boot usercrash_s, root is required\n");
exit(EXIT_FAILURE);
}
dir = opendir(usercrash_directory);
if (dir == NULL) {
fd = mkdir(usercrash_directory, 0755);
if (fd == -1) {
LOGE("create log folder failed, error (%s)\n",
strerror(errno));
exit(EXIT_FAILURE);
}
if (chmod(usercrash_directory, 0755) == -1) {
LOGE("failed to change usercrash folder priority, ");
LOGE("error (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
} else {
closedir(dir);
}
/* Don't try to connect to ourselves if we crash */
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = sig_handler;
debuggerd_register_handlers(&action);
find_oldest_usercrash();
len = snprintf(socket_path, sizeof(socket_path), "%s/%s",
RESERVED_SOCKET_PREFIX, SOCKET_NAME);
if (s_not_expect(len , sizeof(socket_path))) {
LOGE("construct socket path error\n");
exit(EXIT_FAILURE);
}
/**
* evutil_socket_t on other platform except WIN32 platform is int
* type, but on WIN32 platform evutil_socket_t is intptr_t type.
* So, considering compatibility, here need to transfer socket fd to
* evutil_socket_t type.
*/
crash_socket = (evutil_socket_t)create_socket_server(socket_path,
SOCK_SEQPACKET);
if (crash_socket == -1) {
LOGE("failed to get socket from init, error (%s)\n",
strerror(errno));
exit(EXIT_FAILURE);
}
if (chmod(socket_path, 0622) == -1) {
LOGE("failed to change usercrashd_crash priority\n");
goto fail;
}
evutil_make_socket_nonblocking(crash_socket);
base = event_base_new();
if (!base) {
LOGE("failed to create event_base\n");
goto fail;
}
TAILQ_INIT(&queue_t);
listener = evconnlistener_new(base, crash_accept_cb, NULL,
LEV_OPT_CLOSE_ON_FREE, -1, crash_socket);
if (!listener) {
LOGE("failed to create evconnlistener: %s\n", strerror(errno));
goto fail;
}
LOGI("usercrash_s successfully initialized\n");
event_base_dispatch(base);
evconnlistener_free(listener);
event_base_free(base);
close(crash_socket);
return 0;
fail:
close(crash_socket);
exit(EXIT_FAILURE);
}