mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-09-09 12:49:24 +00:00
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:
79
misc/tools/acrn-crashlog/usercrash/Makefile
Normal file
79
misc/tools/acrn-crashlog/usercrash/Makefile
Normal 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
|
88
misc/tools/acrn-crashlog/usercrash/README.rst
Normal file
88
misc/tools/acrn-crashlog/usercrash/README.rst
Normal 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.
|
238
misc/tools/acrn-crashlog/usercrash/client.c
Normal file
238
misc/tools/acrn-crashlog/usercrash/client.c
Normal 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;
|
||||
}
|
350
misc/tools/acrn-crashlog/usercrash/crash_dump.c
Normal file
350
misc/tools/acrn-crashlog/usercrash/crash_dump.c
Normal 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");
|
||||
}
|
64
misc/tools/acrn-crashlog/usercrash/debugger.c
Normal file
64
misc/tools/acrn-crashlog/usercrash/debugger.c
Normal 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;
|
||||
}
|
52
misc/tools/acrn-crashlog/usercrash/include/crash_dump.h
Normal file
52
misc/tools/acrn-crashlog/usercrash/include/crash_dump.h
Normal 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
|
31
misc/tools/acrn-crashlog/usercrash/include/packet.h
Normal file
31
misc/tools/acrn-crashlog/usercrash/include/packet.h
Normal 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
|
25
misc/tools/acrn-crashlog/usercrash/include/protocol.h
Normal file
25
misc/tools/acrn-crashlog/usercrash/include/protocol.h
Normal 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
|
221
misc/tools/acrn-crashlog/usercrash/protocol.c
Normal file
221
misc/tools/acrn-crashlog/usercrash/protocol.c
Normal 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;
|
||||
}
|
502
misc/tools/acrn-crashlog/usercrash/server.c
Normal file
502
misc/tools/acrn-crashlog/usercrash/server.c
Normal 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);
|
||||
}
|
Reference in New Issue
Block a user