Skip to content

Commit

Permalink
Merge pull request #272 from HassoPlattnerInstituteHCI/serial/gdb
Browse files Browse the repository at this point in the history
automatically convert backtrace into readable function stacktrace when detecting crash
  • Loading branch information
lukaswagner authored Sep 26, 2019
2 parents f666c65 + a4060d1 commit 94c7ad9
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 18 deletions.
1 change: 1 addition & 0 deletions Firmware/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ build_flags =
-I${protocol.include_dir}
-D_GLIBCXX_USE_C99
-DENABLE_FPS
-g
#-DENABLE_CRASHDUMP
#-DENABLE_PERFMON
# use this while debugging
Expand Down
51 changes: 35 additions & 16 deletions Utils/Scripts/run.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/* eslint-disable require-jsdoc */
'use strict';

const {exec, remove, color} = require('./tools');
const os = require('os');
const path = require('path');
const {exec, remove, color, escape} = require('./tools');

function log(message, messageColor) {
console.log(`${messageColor}${message}${color.reset}`);
Expand All @@ -17,22 +19,26 @@ const buildHandlers = {
return exec('node', ['./voice-command/build/build-release.js']);
},
'serial-plugin': () => {
return exec('node-gyp', ['configure'])
const gypDef = '--cppdefs="NODE_GYP ' + escape(cppDefines.join(' ')) + '"';
return exec('node-gyp', ['configure', gypDef])
& exec('node-gyp', ['build']);
},
'serial-standalone': () => {
// eslint-disable-next-line max-len
return exec(cppExec, cppArgs.concat([
'Utils/Serial/src/standalone/main.cpp',
'Utils/Serial/src/standalone/standalone.cpp',
'Utils/Serial/src/serial/shared.cpp',
process.platform == 'win32' ?
'Utils/Serial/src/serial/win.cpp' :
'Utils/Serial/src/serial/unix.cpp',
'Protocol/src/protocol/protocol.cpp',
'-IUtils/Serial/include',
'-IProtocol/include',
'-o Utils/Serial/serial']));
return exec(
cppExec,
cppArgs.concat(cppDefines.map((d) => cppDefinePrefix + d)).concat([
'Utils/Serial/src/standalone/main.cpp',
'Utils/Serial/src/standalone/standalone.cpp',
'Utils/Serial/src/serial/shared.cpp',
'Utils/Serial/src/crashAnalyzer/analyze.cpp',
'Utils/Serial/src/crashAnalyzer/buffer.cpp',
process.platform == 'win32' ?
'Utils/Serial/src/serial/win.cpp' :
'Utils/Serial/src/serial/unix.cpp',
'Protocol/src/protocol/protocol.cpp',
'-IUtils/Serial/include',
'-IProtocol/include',
'-o Utils/Serial/serial']));
},
'firmware': () => {
return config(process.argv[4])
Expand Down Expand Up @@ -139,24 +145,37 @@ const handlers = {
'docs': docs
};

const platformioDir = os.homedir() + '/.platformio';
const xtensaUtilDir = platformioDir + '/packages/toolchain-xtensa32/bin/';
const addr2linePath = path.join(xtensaUtilDir, 'xtensa-esp32-elf-addr2line');

let platformioExec;
let cppExec;
let cppDefinePrefix;
let cppArgs;
const cppDefines = [
escape('ADDR2LINE_PATH=\"' + addr2linePath + '\"')
];

if (process.platform == 'win32') {
platformioExec = '"%userprofile%/.platformio/penv/Scripts/platformio"';
platformioExec = path.join(platformioDir, '/penv/Scripts/platformio');
cppExec = 'cl';
cppDefinePrefix = '/D';
cppArgs = ['/Fo:Utils\\Serial\\'];
cppDefines.push('WINDOWS');
} else {
if (exec('which', ['platformio'])) {
platformioExec = 'platformio';
} else {
platformioExec = '~/.platformio/penv/bin/platformio';
platformioExec = platformioDir + '/penv/bin/platformio';
}
if (process.platform == 'linux') {
cppExec = 'g++';
cppDefinePrefix = '-D';
cppArgs = ['-std=c++11'];
} else {
cppExec = 'clang++';
cppDefinePrefix = '-D';
cppArgs = ['-std=c++11'];
}
}
Expand Down
7 changes: 6 additions & 1 deletion Utils/Scripts/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@ function remove(target) {
return true;
}

function escape(string) {
return string.replace(/\\/g, '\\\\').replace(/"/g, '\\\"');
}

module.exports = {
exec,
remove,
color
color,
escape
};
35 changes: 35 additions & 0 deletions Utils/Serial/include/crashAnalyzer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include <string>
#include <vector>

#define SAFEMOD(a, b) ((a) % (b) + (b)) % (b)

class CrashAnalyzer
{
private:
static const uint16_t c_bufferLength = 1024;
static const uint16_t c_dumpLineWidth = 32;
static uint8_t s_buffer[c_bufferLength];
static uint16_t s_length;
static uint16_t s_index;

static void clearBuffer();
static uint8_t getChar(uint16_t offset);

static const std::string c_rebootString;
static const std::string c_backtraceString;

static bool findString(
uint16_t startOffset,
uint16_t endOffset,
const std::string string,
uint16_t& foundOffset);
static std::vector<std::string> getBacktraceAddresses(
uint16_t startOffset, uint16_t endOffset);
static void addr2line(std::vector<std::string> addresses);

static void checkOutput();
public:
static void push_back(const uint8_t character);
};
146 changes: 146 additions & 0 deletions Utils/Serial/src/crashAnalyzer/analyze.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include "crashAnalyzer.hpp"

#include <array>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <regex>
#include <sstream>

const std::string CrashAnalyzer::c_rebootString = "Rebooting...";
const std::string CrashAnalyzer::c_backtraceString = "Backtrace:";

bool CrashAnalyzer::findString(
uint16_t startOffset,
uint16_t endOffset,
const std::string string,
uint16_t& foundOffset)
{
const auto length = string.length();
int32_t index = length - 1;
auto offset = startOffset;

while (index > -1 && offset <= endOffset)
{
if(getChar(offset) == string.at(index))
{
index--;
}
else
{
index = length - 1;
}
offset++;
}

if(index == -1)
{
foundOffset = offset;
return true;
}

return false;
}

std::vector<std::string> CrashAnalyzer::getBacktraceAddresses(
uint16_t startOffset, uint16_t endOffset)
{
std::string data(startOffset - endOffset + 1, 'a');
for(auto i = startOffset; i >= endOffset; --i)
{
data.at(startOffset - i) = getChar(i);
}
std::regex regex("(0x.{8}):0x.{8}");
auto it = std::sregex_iterator(data.begin(), data.end(), regex);
auto end = std::sregex_iterator();

std::vector<std::string> result;
while(it != end)
{
result.push_back((*it++).str(1));
}

return result;
}

std::string exec(const char* cmd) {
#ifdef WINDOWS
#define popen _popen
#define pclose _pclose
#endif

std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe) {
return "popen() failed!";
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;
}

void CrashAnalyzer::addr2line(std::vector<std::string> addresses)
{
std::cout << std::endl << "===== STACKTRACE BEGIN =====" << std::endl;

#ifdef ADDR2LINE_PATH

std::ostringstream command;
command
<< ADDR2LINE_PATH
<< " -e ./firmware/.pio/build/esp32dev/firmware.elf"
<< " -fpCis"; // see https://linux.die.net/man/1/addr2line
for (const auto &address : addresses)
{
command << " " << address;
}

const auto result = exec(command.str().c_str());

std::cout
<< "Stacktrace (most recent call first):" << std::endl
<< result;

#else

std::cout
<< "Path to addr2line executable not set. Can't analyze stacktrace."
<< std::endl;

#endif

std::cout << "====== STACKTRACE END ======" << std::endl;
}

void CrashAnalyzer::checkOutput()
{
uint16_t rebootOffset;
if(!findString(
0,
c_rebootString.length(),
c_rebootString,
rebootOffset))
{
return;
}

uint16_t backtraceOffset;
if(!findString(
rebootOffset,
s_length,
c_backtraceString,
backtraceOffset))
{
std::cout << "Reboot detected, but no backtrace found." << std::endl;
return;
}

auto addresses = getBacktraceAddresses(
backtraceOffset - c_backtraceString.length() - 1,
rebootOffset);

addr2line(addresses);
clearBuffer();
}
28 changes: 28 additions & 0 deletions Utils/Serial/src/crashAnalyzer/buffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "crashAnalyzer.hpp"

#include <iomanip>
#include <iostream>

uint8_t CrashAnalyzer::s_buffer[c_bufferLength];
uint16_t CrashAnalyzer::s_length = 0;
uint16_t CrashAnalyzer::s_index = 0;

void CrashAnalyzer::clearBuffer()
{
s_index = 0;
s_length = 0;
}

uint8_t CrashAnalyzer::getChar(uint16_t offset)
{
return s_buffer[SAFEMOD((s_index - offset), c_bufferLength)];
}

void CrashAnalyzer::push_back(const uint8_t character)
{
s_buffer[s_index] = character;
s_index = (s_index + 1) % c_bufferLength;
s_length = (s_length >= c_bufferLength) ? c_bufferLength : (s_length + 1);

checkOutput();
}
3 changes: 3 additions & 0 deletions Utils/Serial/src/serial/shared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <iomanip>
#include <fstream>

#include "crashAnalyzer.hpp"

std::string DPSerial::s_path;
uint8_t DPSerial::s_headerBuffer[DPSerial::c_headerSize];
Header DPSerial::s_header = Header();
Expand All @@ -25,6 +27,7 @@ void DPSerial::receivePacket()
else
{
std::cout << received;
CrashAnalyzer::push_back(received);
index = 0;
}
}
Expand Down
9 changes: 8 additions & 1 deletion binding.gyp
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"variables": {
"cppdefs": "NODE_GYP",
},
"targets": [{
"target_name": "serial",
"sources": [
Expand All @@ -9,6 +12,8 @@
"./Utils/Serial/src/node/receiveHelpers.cpp",
"./Utils/Serial/src/node/sendHelpers.cpp",
"./Utils/Serial/src/serial/shared.cpp",
"./Utils/Serial/src/crashAnalyzer/buffer.cpp",
"./Utils/Serial/src/crashAnalyzer/analyze.cpp",
"./Protocol/src/protocol/protocol.cpp"
],
"conditions": [
Expand All @@ -23,6 +28,8 @@
"./Utils/Serial/include",
"./Protocol/include"
],
"defines": [ "NODE_GYP" ]
"defines": [
"<@(cppdefs)"
]
}]
}

0 comments on commit 94c7ad9

Please sign in to comment.