mirror of
https://github.com/clearlinux/telemetrics-client.git
synced 2026-08-29 06:15:39 +00:00
eedcb88cb7
Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
862 lines
26 KiB
C
862 lines
26 KiB
C
/*
|
|
* This program is part of the Clear Linux Project
|
|
*
|
|
* Copyright 2015 Intel Corporation
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms and conditions of the GNU Lesser General Public License, as
|
|
* published by the Free Software Foundation; either version 2.1 of the License,
|
|
* or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
|
* details.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include <unistd.h>
|
|
|
|
#include "oops_parser.h"
|
|
#include "log.h"
|
|
#include "probe.h"
|
|
|
|
#define NUM_TAINTED_FLAGS 16
|
|
|
|
enum tm_severity {
|
|
TM_LOW = 1,
|
|
TM_MEDIUM,
|
|
TM_HIGH,
|
|
TM_CRITICAL,
|
|
};
|
|
|
|
struct oops_pattern oops_patterns_arr[] = {
|
|
{
|
|
"NETDEV WATCHDOG: ",
|
|
"org.clearlinux/kernel/warning",
|
|
TM_MEDIUM,
|
|
false,
|
|
},
|
|
{
|
|
"sysctl table check failed: ",
|
|
"org.clearlinux/kernel/warning",
|
|
TM_MEDIUM,
|
|
},
|
|
{
|
|
"IRQ handler type mismatch for IRQ",
|
|
"org.clearlinux/kernel/warning",
|
|
TM_MEDIUM,
|
|
false,
|
|
},
|
|
{
|
|
"^ALSA (.*): BUG(.*)",
|
|
"crash/kernel/bug",
|
|
TM_MEDIUM,
|
|
true,
|
|
},
|
|
{
|
|
"irq [[:digit:]]+: nobody cared",
|
|
"crash/kernel/warning",
|
|
TM_MEDIUM,
|
|
true,
|
|
},
|
|
{
|
|
"WARNING: ",
|
|
"org.clearlinux/kernel/warning",
|
|
TM_MEDIUM,
|
|
false,
|
|
},
|
|
{
|
|
"general protection fault: ",
|
|
"org.clearlinux/kernel/bug",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"BUG: unable to handle kernel ",
|
|
"org.clearlinux/kernel/bug",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"BUG:",
|
|
"org.clearlinux/kernel/bug",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"kernel BUG at",
|
|
"org.clearlinux/kernel/bug",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"general protection fault:",
|
|
"org.clearlinux/kernel/bug",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"do_IRQ: stack overflow:", //revisit
|
|
"org.clearlinux/kernel/stackoverflow",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"RTNL: assertion failed",
|
|
"org.clearlinux/kernel/warning",
|
|
TM_MEDIUM,
|
|
false,
|
|
},
|
|
{
|
|
"Eeek! page_mapcount(page) went negative!",
|
|
"org.clearlinux/kernel/bug",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"near stack overflow (cur:",
|
|
"org.clearlinux/kernel/bug", //revisit
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"double fault",
|
|
"org.clearlinux/kernel/bug",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"Badness",
|
|
"org.clearlinux/kernel/bug",
|
|
TM_CRITICAL,
|
|
false,
|
|
},
|
|
{
|
|
"list_del corruption.",
|
|
"org.clearlinux/kernel/warning",
|
|
TM_MEDIUM,
|
|
false,
|
|
},
|
|
{
|
|
"list_add corruption.",
|
|
"org.clearlinux/kernel/warning",
|
|
TM_MEDIUM,
|
|
false,
|
|
},
|
|
};
|
|
|
|
int oops_patterns_cnt = sizeof(oops_patterns_arr) / sizeof(struct oops_pattern);
|
|
|
|
char *skip_log_level(char *line)
|
|
{
|
|
char *start = line;
|
|
|
|
if (*start == '<') {
|
|
while (*start && *start != '>') {
|
|
start++;
|
|
}
|
|
if (*start) {
|
|
start++;
|
|
}
|
|
}
|
|
return start;
|
|
}
|
|
|
|
char *skip_timestamp(char *line)
|
|
{
|
|
char *start = line;
|
|
|
|
if (*start == '[') {
|
|
while (*start && *start != ']') {
|
|
start++;
|
|
}
|
|
if (*start) {
|
|
start++;
|
|
}
|
|
}
|
|
return start;
|
|
}
|
|
|
|
char *skip_space(char *line)
|
|
{
|
|
char *start = line;
|
|
|
|
if (*start && isspace(*start)) {
|
|
start++;
|
|
}
|
|
return start;
|
|
}
|
|
|
|
char *skip_spaces(char *line)
|
|
{
|
|
char *start = line;
|
|
|
|
while (*start && isspace(*start)) {
|
|
start++;
|
|
}
|
|
return start;
|
|
}
|
|
|
|
bool starts_with(const char *line, const char *line_end, const char *substr)
|
|
{
|
|
size_t substrlen = strlen(substr);
|
|
|
|
if (substrlen <= (line_end - line) && memcmp(line, substr, substrlen) == 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool str_starts_with_casei(const char *line, const char *substr)
|
|
{
|
|
size_t len = strlen(substr);
|
|
|
|
if (strncasecmp(line, substr, len) == 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool pattern_matches(char *line, char *line_end, struct oops_pattern *pattern)
|
|
{
|
|
if (pattern->is_regex) {
|
|
return (regexec(&(pattern->regex), line, 0, NULL, 0) == 0);
|
|
} else {
|
|
return starts_with(line, line_end, pattern->begin_line);
|
|
}
|
|
}
|
|
|
|
void init_pattern_regex(void)
|
|
{
|
|
for (int i = 0; i < oops_patterns_cnt; i++) {
|
|
struct oops_pattern *pattern = &oops_patterns_arr[i];
|
|
if (pattern->is_regex) {
|
|
regcomp(&(pattern->regex), pattern->begin_line,
|
|
REG_ICASE | REG_EXTENDED | REG_NOSUB);
|
|
}
|
|
}
|
|
}
|
|
|
|
void free_pattern_regex(void)
|
|
{
|
|
for (int i = 0; i < oops_patterns_cnt; i++) {
|
|
struct oops_pattern *pattern = &oops_patterns_arr[i];
|
|
if (pattern->is_regex) {
|
|
regfree(&(pattern->regex));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool handle_entire_oops(char *buf, long size, struct oops_log_msg *msg)
|
|
{
|
|
char *line_end;
|
|
struct oops_pattern *pattern = NULL;
|
|
int i;
|
|
|
|
msg->length = 0;
|
|
init_pattern_regex();
|
|
|
|
line_end = memchr(buf, '\n', (size_t)size);
|
|
if (line_end == NULL) {
|
|
return false;
|
|
}
|
|
|
|
*line_end = '\0';
|
|
|
|
for (i = 0; i < oops_patterns_cnt; i++) {
|
|
pattern = &oops_patterns_arr[i];
|
|
if (pattern_matches(buf, line_end, pattern)) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == oops_patterns_cnt) {
|
|
/* Nothin matched! */
|
|
return false;
|
|
}
|
|
|
|
msg->pattern = pattern;
|
|
msg->length = 0;
|
|
msg->lines[msg->length] = strdup(buf);
|
|
if (msg->lines[msg->length] == NULL) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
msg->length++;
|
|
|
|
size = size - (line_end - buf + 1);
|
|
buf = line_end + 1;
|
|
|
|
while (size > 0) {
|
|
line_end = memchr(buf, '\n', (size_t)size);
|
|
if (line_end == NULL) {
|
|
line_end = buf + size;
|
|
}
|
|
|
|
*line_end = '\0';
|
|
msg->lines[msg->length] = strndup(buf, (size_t)(line_end - buf + 1));
|
|
if (msg->lines[msg->length] == NULL) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
msg->length++;
|
|
size -= (line_end - buf + 1);
|
|
buf = line_end + 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void oops_msg_cleanup(struct oops_log_msg *msg)
|
|
{
|
|
for (int i = 0; i < msg->length; i++) {
|
|
free(msg->lines[i]);
|
|
}
|
|
msg->length = 0;
|
|
free_pattern_regex();
|
|
}
|
|
|
|
static struct oops_log_msg oops_msg;
|
|
|
|
static bool in_stack_dump = false;
|
|
|
|
static oops_handler_t oops_handler;
|
|
|
|
void oops_parser_init(oops_handler_t handler)
|
|
{
|
|
oops_handler = handler;
|
|
init_pattern_regex();
|
|
}
|
|
|
|
void handle_msg_end(void)
|
|
{
|
|
for (int i = 0; i < oops_msg.length; i++) {
|
|
free(oops_msg.lines[i]);
|
|
}
|
|
oops_msg.length = 0;
|
|
}
|
|
|
|
void oops_parser_cleanup()
|
|
{
|
|
handle_msg_end();
|
|
free_pattern_regex();
|
|
}
|
|
|
|
void parse_single_line(char *line, size_t size)
|
|
{
|
|
char *start;
|
|
char *line_end;
|
|
bool end_found = false;
|
|
|
|
line_end = line + size;
|
|
start = skip_log_level(line);
|
|
start = skip_timestamp(start);
|
|
start = skip_space(start);
|
|
|
|
struct oops_pattern *pattern;
|
|
if (oops_msg.length == 0) {
|
|
for (int i = 0; i < oops_patterns_cnt; i++) {
|
|
pattern = &oops_patterns_arr[i];
|
|
if (!pattern_matches(start, line_end, pattern)) {
|
|
continue;
|
|
}
|
|
telem_log(LOG_DEBUG, "Oops start has been detected\n");
|
|
|
|
oops_msg.pattern = pattern;
|
|
oops_msg.lines[oops_msg.length] = strndup(start, (size_t)(line_end - start));
|
|
if (oops_msg.lines[oops_msg.length] == NULL) {
|
|
//telem_perror("Failed to copy string");
|
|
exit(EXIT_FAILURE);
|
|
return;
|
|
} else {
|
|
oops_msg.length++;
|
|
}
|
|
in_stack_dump = false;
|
|
break;
|
|
}
|
|
} else {
|
|
// If in the middle of oops
|
|
if (oops_msg.length >= MAX_LINES) {
|
|
end_found = true;
|
|
// } else if (oops_msg.end_line && strstr(start, oops_msg.end_line)) {
|
|
// end_found = false;
|
|
} else if (strstr(start, "[ end trace")) {
|
|
end_found = true;
|
|
} else if (!in_stack_dump) {
|
|
// This line indicates the beginning of a stack trace;
|
|
// the next line is most likely the topmost frame.
|
|
if (starts_with(start, line_end, "Call Trace:")) {
|
|
in_stack_dump = true;
|
|
}
|
|
} else if (in_stack_dump) {
|
|
// In Linux 4.10+, the oops format changed, so we look
|
|
// for a single leading space character to indicate a
|
|
// stack frame line.
|
|
if (!(starts_with(start, line_end, " ")) ||
|
|
(strlen(start) < 8) ||
|
|
(strstr(start, "Code:") != NULL) ||
|
|
(strstr(start, "Instruction Dump::") != NULL)) {
|
|
in_stack_dump = false;
|
|
end_found = true;
|
|
}
|
|
}
|
|
|
|
/* if a new oops starts, this one has ended */
|
|
if (strstr(start, "WARNING:") != NULL || strstr(start, "Unable to handle") != NULL) {
|
|
if (!oops_msg.pattern->is_regex || !str_starts_with_casei(oops_msg.pattern->begin_line, "^ALSA")) {
|
|
end_found = true;
|
|
}
|
|
}
|
|
|
|
if (end_found) {
|
|
oops_handler(&oops_msg);
|
|
handle_msg_end();
|
|
} else {
|
|
oops_msg.lines[oops_msg.length] = strndup(start, (size_t)(line_end - start));
|
|
if (oops_msg.lines[oops_msg.length] == NULL) {
|
|
telem_perror("Failed to copy string");
|
|
return;
|
|
} else {
|
|
oops_msg.length++;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
struct stack_frame {
|
|
char *module;
|
|
char *function;
|
|
uint64_t addr;
|
|
int64_t offset;
|
|
struct stack_frame *next;
|
|
};
|
|
|
|
void stack_frame_append(struct stack_frame **head, struct stack_frame **tail, char *start)
|
|
{
|
|
/*
|
|
* Format is:
|
|
*
|
|
* [<ffffffffa1002128>] do_one_initcall+0xb8/0x1e0
|
|
* [<ffffffffa118784a>] ? __vunmap+0x9a/0x100
|
|
*
|
|
* OR
|
|
*
|
|
* <IRQ> [<ffffffff816606a8>] dump_stack+0x19/0x1b
|
|
*
|
|
* OR (in 4.10+)
|
|
*
|
|
* do_one_initcall+0xb8/0x1e0
|
|
* ? __vunmap+0x9a/0x100
|
|
*
|
|
*/
|
|
|
|
struct stack_frame *frame = NULL;
|
|
char *offset_ptr = NULL, *end = NULL;
|
|
|
|
if (!start) {
|
|
return;
|
|
}
|
|
|
|
// Skip leading spaces
|
|
start = skip_spaces(start);
|
|
|
|
//case: <IRQ> [<ffffffff816606a8>] dump_stack+0x19/0x1b
|
|
if (!strncmp(start, "<IRQ>", 5) || !strncmp(start, "<NMI>", 5) ||
|
|
!strncmp(start, "<EOI>", 5) || !strncmp(start, "<<EOE>>", 7)) {
|
|
while (*start && !isspace(*start)) {
|
|
start++;
|
|
}
|
|
if (start && *start == '\0') {
|
|
return;
|
|
}
|
|
|
|
// Skip intervening space
|
|
start = skip_spaces(start);
|
|
}
|
|
|
|
frame = malloc(sizeof(struct stack_frame));
|
|
if (!frame) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
frame->next = *head;
|
|
*head = frame;
|
|
|
|
if (*tail == NULL) {
|
|
*tail = frame;
|
|
}
|
|
|
|
// Skip memory address parsing if the address is absent (as in Linux 4.10+).
|
|
if (!strncmp(start, "[", 1)) {
|
|
start++;
|
|
|
|
if (*start == '<') {
|
|
start++;
|
|
frame->addr = (uint64_t)strtoll(start, (char **)&start, 16);
|
|
} else {
|
|
frame->addr = 0;
|
|
}
|
|
|
|
// Skip up to the space after the closing bracket
|
|
while (*start && !isspace(*start)) {
|
|
start++;
|
|
}
|
|
if (start && *start == '\0') {
|
|
return;
|
|
}
|
|
|
|
// Skip intervening space
|
|
start = skip_spaces(start);
|
|
} else {
|
|
frame->addr = 0;
|
|
}
|
|
|
|
offset_ptr = strchr(start, '+');
|
|
end = offset_ptr ? offset_ptr : (start + strlen(start));
|
|
frame->function = strndup(start, (size_t)(end - start));
|
|
if (!frame->function) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
start = end;
|
|
|
|
/* Parse the offset */
|
|
if (offset_ptr) {
|
|
frame->offset = strtoll(offset_ptr + 1, (char **)&start, 16);
|
|
} else {
|
|
frame->offset = -1;
|
|
}
|
|
|
|
frame->module = "kernel";
|
|
}
|
|
|
|
void stack_frame_free(struct stack_frame **head)
|
|
{
|
|
struct stack_frame *frame;
|
|
|
|
while (*head != NULL) {
|
|
frame = (*head)->next;
|
|
free(*head);
|
|
*head = frame;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Function parses lines of the format :
|
|
* CPU: 2 PID: 6429 Comm: insmod Tainted: P OE 3.19.0-18-generic #18-Ubuntu$
|
|
* CPU: 2 PID: 0 Comm: swapper/2 Not tainted 3.10.4-100.fc18.x86_64 #1
|
|
* CPU: 3 PID: 0 Comm: swapper/3 Not tainted 4.0.5-300.fc22.x86_64 #1
|
|
*/
|
|
void parse_kernel_cpu_line(char *line, char **kernel_version, char **tainted)
|
|
{
|
|
char *end = NULL;
|
|
|
|
end = strstr(line, " Not tainted ");
|
|
if (end) {
|
|
*tainted = strdup("Not tainted");
|
|
line = end + strlen(" Not tainted");
|
|
} else {
|
|
end = strstr(line, "Tainted: ");
|
|
if (end) {
|
|
*tainted = strndup(end + strlen("Tainted: "), NUM_TAINTED_FLAGS);
|
|
if (*tainted == NULL) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
line = end + strlen("Tainted: ") + 1;
|
|
}
|
|
}
|
|
|
|
end = strchr(line, '#');
|
|
if (!end || !isspace(*(end - 1))) {
|
|
return;
|
|
}
|
|
end -= 2;
|
|
|
|
while (end >= line && !isspace(*end)) {
|
|
end--;
|
|
}
|
|
|
|
if (end >= line) {
|
|
*kernel_version = strdup(end + 1);
|
|
}
|
|
}
|
|
|
|
static const char *registers[] = { "RIP", "RSP", "CR0", "CR2", "CR3", "CR4",
|
|
"DR0", "DR1", "DR2", "DR3", "DR6", "DR7", "EFLAGS",
|
|
"RAX", "RBX", "RCX", "RDX", "RSI", "RDI", "RBP",
|
|
"R08", "R09", "R10", "R11",
|
|
"R12", "R13", "R14", "R15",
|
|
"FS", "GS", "knlGS", "CS", "ES", NULL, };
|
|
|
|
struct reg_s {
|
|
char *reg_name;
|
|
uint64_t reg_value;
|
|
struct reg_s *next;
|
|
};
|
|
|
|
static struct reg_s *reg_head = NULL;
|
|
|
|
/* Parse lines conatining any register values
|
|
* We handle lines in the various forms present, e.g.:
|
|
|
|
* RIP: 0010:[<ffffffffc00b7012>] [<ffffffffc00b7012>] my_oops_init+0x12/0x1000 [oops]
|
|
*
|
|
* RSP: 0018:ffff8800543e3d18 EFLAGS: 00010292
|
|
*
|
|
* RAX: 0000000000000002 RBX: ffffffff81c1a080 RCX: 0000000000000002
|
|
*
|
|
* FS: 00007f5e7cab1700(0000) GS:ffff88014b240000(0000) knlGS:0000000000000000
|
|
*
|
|
* IP: [<ffffffff98231bc6>] sysrq_handle_crash+0x16/0x20
|
|
*
|
|
* EIP is at iret_exc+0x7d0/0xa59
|
|
*
|
|
* RIP: 0010:[<fff...1d8>] [<fff...1d8>] sysrq_handle_crash+0x16/0x20
|
|
*
|
|
* RSP: 0018:ffff8800775f3e68 EFLAGS: 00010092
|
|
*
|
|
* RSP <ffff9900628f3e40>
|
|
*
|
|
*/
|
|
|
|
void parse_registers(char *line)
|
|
{
|
|
uint64_t value;
|
|
struct reg_s *reg_entry = NULL;
|
|
|
|
if (!line) {
|
|
return;
|
|
}
|
|
|
|
while (*line) {
|
|
while (isspace(*line)) {
|
|
line++;
|
|
}
|
|
if (*line == '\0') {
|
|
break;
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; registers[i] != NULL; i++) {
|
|
if (str_starts_with_casei(line, registers[i])) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (registers[i] == NULL) {
|
|
//Register not found
|
|
break;
|
|
}
|
|
|
|
line = line + strlen(registers[i]);
|
|
if (*line == ':') {
|
|
line++;
|
|
}
|
|
|
|
if (isspace(*line)) {
|
|
line++;
|
|
}
|
|
if (*line == '\0') {
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* strtoll extracts the entire number if valid, else stores the first invalid
|
|
* character at line
|
|
* Try to extract the value first.
|
|
* case : RSP: 0018:ffff8800543e3d18 EFLAGS: 00010292
|
|
*/
|
|
value = (uint64_t)strtoll(line, &line, 16);
|
|
|
|
//case : RSP: 0018:ffff8800775f3e68
|
|
if (*line == ':') {
|
|
line++;
|
|
value = (uint64_t)strtoll(line, &line, 16);
|
|
}
|
|
|
|
// case: FS: 00007f5e7cab1700(0000) GS:ffff88014b240000(0000)
|
|
if (*line == '(') {
|
|
// strchrnul returns first occurrence of ) or end of string
|
|
line = strchrnul(line, ')');
|
|
if (*line != ')') {
|
|
break;
|
|
}
|
|
}
|
|
//case: RSP <ffff9900628f3e40>
|
|
else if (*line == '<') {
|
|
line++;
|
|
value = (uint64_t)strtoll(line, &line, 16);
|
|
if (*line != '>') {
|
|
continue;
|
|
}
|
|
line++;
|
|
|
|
}
|
|
// case: IP: [<ffffffff98231bc6>] sysrq_handle_crash+0x16/0x20
|
|
else if (*line == '[' && *(line + 1) == '<') {
|
|
value = (uint64_t)strtoll(line, &line, 16);
|
|
if (*line != '>' && *(line + 1) != ']') {
|
|
continue;
|
|
}
|
|
line = line + 2;
|
|
|
|
}
|
|
//case: EIP is at iret_exc+0x7d0/0xa59
|
|
else if (starts_with(line, line + strlen(line), "is at")) {
|
|
while (*line != '\0' && *line != '+') {
|
|
line++;
|
|
}
|
|
if (*line == '+') {
|
|
value = (uint64_t)strtoll(line, &line, 16);
|
|
line += strlen(line);
|
|
}
|
|
} else if (*line != '\0' && !isspace(*line)) {
|
|
continue;
|
|
}
|
|
|
|
reg_entry = malloc(sizeof(struct reg_s));
|
|
if (!reg_entry) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
reg_entry->reg_name = (char *)registers[i];
|
|
reg_entry->reg_value = value;
|
|
|
|
if (reg_head == NULL) {
|
|
reg_head = reg_entry;
|
|
reg_entry->next = NULL;
|
|
} else {
|
|
reg_entry->next = reg_head;
|
|
reg_head = reg_entry;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void append_registers_to_bt(nc_string **backtrace)
|
|
{
|
|
struct reg_s *reg_entry = reg_head;
|
|
|
|
while (reg_entry != NULL) {
|
|
// Global override for privacy filters
|
|
if (access(TM_PRIVACY_FILTERS_OVERRIDE, F_OK) == 0) {
|
|
nc_string_append_printf(*backtrace, "Register %s: %" PRIx64 "\n", reg_entry->reg_name,
|
|
reg_entry->reg_value);
|
|
} else {
|
|
nc_string_append_printf(*backtrace, "Register %s: %s\n", reg_entry->reg_name,
|
|
reg_entry->reg_value ? "Non-zero" : "Zero");
|
|
}
|
|
reg_entry = reg_entry->next;
|
|
}
|
|
|
|
while (reg_head != NULL) {
|
|
reg_entry = reg_head->next;
|
|
free(reg_head);
|
|
reg_head = reg_entry;
|
|
}
|
|
}
|
|
|
|
nc_string *parse_backtrace(struct oops_log_msg *msg)
|
|
{
|
|
struct stack_frame *head = NULL, *tail = NULL, *elem = NULL;
|
|
//int in_stack_dump = 0;
|
|
char *line = NULL;
|
|
nc_string *backtrace = NULL;
|
|
int frame_counter = 1;
|
|
char *modules = NULL, *kernel_version = NULL, *tainted = NULL;
|
|
// Since lines are processed from last to first, the stack trace lines
|
|
// will appear first.
|
|
bool in_trace = true;
|
|
|
|
for (int i = msg->length - 1; i > 0; i--) {
|
|
/* Check if this line is part of a stack trace */
|
|
line = msg->lines[i];
|
|
|
|
if (in_trace && starts_with(line, line + strlen(line), " ")) {
|
|
stack_frame_append(&head, &tail, line);
|
|
continue;
|
|
}
|
|
|
|
// We've reached the end of the stack trace
|
|
if (starts_with(line, line + strlen(line), "Call Trace:")) {
|
|
in_trace = false;
|
|
}
|
|
|
|
// Stack trace lines must be consecutive, so if this point is
|
|
// reached, we're outside the trace boundary.
|
|
in_trace = false;
|
|
|
|
if (str_starts_with_casei(line, "Modules linked in: ")) {
|
|
modules = line + strlen("Modules linked in: ");
|
|
continue;
|
|
}
|
|
|
|
if (str_starts_with_casei(line, "CPU: ") ||
|
|
str_starts_with_casei(line, "PID: ")) {
|
|
parse_kernel_cpu_line(line, &kernel_version, &tainted);
|
|
continue;
|
|
}
|
|
parse_registers(line);
|
|
}
|
|
|
|
backtrace = nc_string_dup("");
|
|
if (kernel_version) {
|
|
nc_string_append_printf(backtrace, "Kernel Version : %s\n", kernel_version);
|
|
free(kernel_version);
|
|
}
|
|
|
|
if (tainted) {
|
|
nc_string_append_printf(backtrace, "Tainted : %s\n", tainted);
|
|
free(tainted);
|
|
}
|
|
|
|
if (modules) {
|
|
nc_string_append_printf(backtrace, "Modules : %s\n", modules);
|
|
}
|
|
|
|
if (head) {
|
|
nc_string_append_printf(backtrace, "Backtrace :\n");
|
|
}
|
|
|
|
if (reg_head) {
|
|
append_registers_to_bt(&backtrace);
|
|
}
|
|
|
|
for (elem = head; elem != NULL; elem = elem->next, frame_counter++) {
|
|
nc_string_append_printf(backtrace, "#%d %s - [%s]\n", frame_counter,
|
|
elem->function ? elem->function : "???",
|
|
elem->module);
|
|
}
|
|
|
|
stack_frame_free(&head);
|
|
return backtrace;
|
|
}
|
|
|
|
nc_string *parse_payload(struct oops_log_msg *msg)
|
|
{
|
|
nc_string *payload, *backtrace;
|
|
|
|
payload = nc_string_dup("Crash Report:\n");
|
|
nc_string_append_printf(payload, "Reason: %s\n", msg->lines[0]);
|
|
backtrace = parse_backtrace(msg);
|
|
nc_string_cat(payload, backtrace->str);
|
|
nc_string_free(backtrace);
|
|
return payload;
|
|
|
|
}
|
|
|
|
/*TODO implement a more informative reason line */
|
|
/*void parse_bugline(struct oops_log_msg msg) {
|
|
|
|
return;
|
|
}
|
|
*/
|
|
|
|
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
|