mirror of
https://github.com/exaloop/codon.git
synced 2025-06-03 15:03:52 +08:00
549 lines
15 KiB
C++
549 lines
15 KiB
C++
|
#include "lib.h"
|
||
|
#include "llvm/BinaryFormat/Dwarf.h"
|
||
|
#include <backtrace.h>
|
||
|
#include <cassert>
|
||
|
#include <cstdint>
|
||
|
#include <cstdio>
|
||
|
#include <cstdlib>
|
||
|
#include <cstring>
|
||
|
#include <iostream>
|
||
|
#include <string>
|
||
|
#include <vector>
|
||
|
|
||
|
struct BacktraceFrame {
|
||
|
char *function;
|
||
|
char *filename;
|
||
|
uintptr_t pc;
|
||
|
int32_t lineno;
|
||
|
};
|
||
|
|
||
|
struct Backtrace {
|
||
|
static const size_t LIMIT = 20;
|
||
|
struct BacktraceFrame *frames;
|
||
|
size_t count;
|
||
|
|
||
|
void push_back(const char *function, const char *filename, uintptr_t pc,
|
||
|
int32_t lineno) {
|
||
|
if (count >= LIMIT || !function || !filename) {
|
||
|
return;
|
||
|
} else if (count == 0) {
|
||
|
frames = (BacktraceFrame *)seq_alloc(LIMIT * sizeof(*frames));
|
||
|
}
|
||
|
|
||
|
size_t functionLen = strlen(function) + 1;
|
||
|
auto *functionDup = (char *)seq_alloc_atomic(functionLen);
|
||
|
memcpy(functionDup, function, functionLen);
|
||
|
|
||
|
size_t filenameLen = strlen(filename) + 1;
|
||
|
auto *filenameDup = (char *)seq_alloc_atomic(filenameLen);
|
||
|
memcpy(filenameDup, filename, filenameLen);
|
||
|
|
||
|
frames[count++] = {functionDup, filenameDup, pc, lineno};
|
||
|
}
|
||
|
|
||
|
void free() {
|
||
|
for (auto i = 0; i < count; i++) {
|
||
|
auto *frame = &frames[i];
|
||
|
seq_free(frame->function);
|
||
|
seq_free(frame->filename);
|
||
|
}
|
||
|
seq_free(frames);
|
||
|
frames = nullptr;
|
||
|
count = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void seq_backtrace_error_callback(void *data, const char *msg, int errnum) {
|
||
|
// nothing to do
|
||
|
}
|
||
|
|
||
|
int seq_backtrace_full_callback(void *data, uintptr_t pc, const char *filename,
|
||
|
int lineno, const char *function) {
|
||
|
auto *bt = ((Backtrace *)data);
|
||
|
bt->push_back(function, filename, pc, lineno);
|
||
|
return (bt->count < Backtrace::LIMIT) ? 0 : 1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This is largely based on
|
||
|
* llvm/examples/ExceptionDemo/ExceptionDemo.cpp
|
||
|
*/
|
||
|
|
||
|
namespace {
|
||
|
template <typename Type_> static uintptr_t ReadType(const uint8_t *&p) {
|
||
|
Type_ value;
|
||
|
memcpy(&value, p, sizeof(Type_));
|
||
|
p += sizeof(Type_);
|
||
|
return static_cast<uintptr_t>(value);
|
||
|
}
|
||
|
} // namespace
|
||
|
|
||
|
static int64_t ourBaseFromUnwindOffset;
|
||
|
|
||
|
static const unsigned char ourBaseExcpClassChars[] = {'o', 'b', 'j', '\0',
|
||
|
's', 'e', 'q', '\0'};
|
||
|
|
||
|
static uint64_t genClass(const unsigned char classChars[], size_t classCharsSize) {
|
||
|
uint64_t ret = classChars[0];
|
||
|
|
||
|
for (unsigned i = 1; i < classCharsSize; i++) {
|
||
|
ret <<= 8;
|
||
|
ret += classChars[i];
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static uint64_t ourBaseExceptionClass = 0;
|
||
|
|
||
|
struct OurExceptionType_t {
|
||
|
int type;
|
||
|
};
|
||
|
|
||
|
struct OurBaseException_t {
|
||
|
OurExceptionType_t type; // Seq exception type
|
||
|
void *obj; // Seq exception instance
|
||
|
Backtrace bt;
|
||
|
_Unwind_Exception unwindException;
|
||
|
};
|
||
|
|
||
|
typedef struct OurBaseException_t OurException;
|
||
|
|
||
|
struct SeqExcHeader_t {
|
||
|
seq_str_t type;
|
||
|
seq_str_t msg;
|
||
|
seq_str_t func;
|
||
|
seq_str_t file;
|
||
|
seq_int_t line;
|
||
|
seq_int_t col;
|
||
|
};
|
||
|
|
||
|
void seq_exc_init() {
|
||
|
ourBaseFromUnwindOffset = seq_exc_offset();
|
||
|
ourBaseExceptionClass = seq_exc_class();
|
||
|
}
|
||
|
|
||
|
static void seq_delete_exc(_Unwind_Exception *expToDelete) {
|
||
|
if (!expToDelete || expToDelete->exception_class != ourBaseExceptionClass)
|
||
|
return;
|
||
|
auto *exc = (OurException *)((char *)expToDelete + ourBaseFromUnwindOffset);
|
||
|
if (debug) {
|
||
|
exc->bt.free();
|
||
|
}
|
||
|
seq_free(exc);
|
||
|
}
|
||
|
|
||
|
static void seq_delete_unwind_exc(_Unwind_Reason_Code reason,
|
||
|
_Unwind_Exception *expToDelete) {
|
||
|
seq_delete_exc(expToDelete);
|
||
|
}
|
||
|
|
||
|
static struct backtrace_state *state = nullptr;
|
||
|
|
||
|
SEQ_FUNC void *seq_alloc_exc(int type, void *obj) {
|
||
|
const size_t size = sizeof(OurException);
|
||
|
auto *e = (OurException *)memset(seq_alloc(size), 0, size);
|
||
|
assert(e);
|
||
|
e->type.type = type;
|
||
|
e->obj = obj;
|
||
|
e->unwindException.exception_class = ourBaseExceptionClass;
|
||
|
e->unwindException.exception_cleanup = seq_delete_unwind_exc;
|
||
|
if (debug) {
|
||
|
e->bt.frames = nullptr;
|
||
|
e->bt.count = 0;
|
||
|
if (!state)
|
||
|
state = backtrace_create_state(/*filename=*/nullptr, /*threaded=*/0,
|
||
|
seq_backtrace_error_callback, /*data=*/nullptr);
|
||
|
backtrace_full(state, /*skip=*/1, seq_backtrace_full_callback,
|
||
|
seq_backtrace_error_callback, &e->bt);
|
||
|
}
|
||
|
return &(e->unwindException);
|
||
|
}
|
||
|
|
||
|
static void print_from_last_dot(seq_str_t s) {
|
||
|
char *p = s.str;
|
||
|
int64_t n = s.len;
|
||
|
|
||
|
for (int64_t i = n - 1; i >= 0; i--) {
|
||
|
if (p[i] == '.') {
|
||
|
p += (i + 1);
|
||
|
n -= (i + 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fwrite(p, 1, (size_t)n, stderr);
|
||
|
}
|
||
|
|
||
|
SEQ_FUNC void seq_terminate(void *exc) {
|
||
|
auto *base = (OurBaseException_t *)((char *)exc + seq_exc_offset());
|
||
|
void *obj = base->obj;
|
||
|
auto *hdr = (SeqExcHeader_t *)obj;
|
||
|
|
||
|
if (std::string(hdr->type.str, hdr->type.len) ==
|
||
|
"std.internal.types.error.SystemExit") {
|
||
|
seq_int_t status = *(seq_int_t *)(hdr + 1);
|
||
|
exit((int)status);
|
||
|
}
|
||
|
|
||
|
fprintf(stderr, "\033[1m");
|
||
|
print_from_last_dot(hdr->type);
|
||
|
if (hdr->msg.len > 0) {
|
||
|
fprintf(stderr, ": ");
|
||
|
fprintf(stderr, "\033[0m");
|
||
|
fwrite(hdr->msg.str, 1, (size_t)hdr->msg.len, stderr);
|
||
|
} else {
|
||
|
fprintf(stderr, "\033[0m");
|
||
|
}
|
||
|
|
||
|
fprintf(stderr, "\n\n");
|
||
|
fprintf(stderr, "\033[1mRaised from:\033[0m \033[32m");
|
||
|
fwrite(hdr->func.str, 1, (size_t)hdr->func.len, stderr);
|
||
|
fprintf(stderr, "\033[0m\n");
|
||
|
fwrite(hdr->file.str, 1, (size_t)hdr->file.len, stderr);
|
||
|
if (hdr->line > 0) {
|
||
|
fprintf(stderr, ":%lld", (long long)hdr->line);
|
||
|
if (hdr->col > 0)
|
||
|
fprintf(stderr, ":%lld", (long long)hdr->col);
|
||
|
}
|
||
|
fprintf(stderr, "\n");
|
||
|
|
||
|
if (debug) {
|
||
|
auto *bt = &base->bt;
|
||
|
if (bt->count > 0) {
|
||
|
fprintf(stderr, "\n\033[1mBacktrace:\033[0m\n");
|
||
|
for (unsigned i = 0; i < bt->count; i++) {
|
||
|
auto *frame = &bt->frames[i];
|
||
|
fprintf(stderr, " [\033[33m0x%lx\033[0m] \033[32m%s\033[0m %s:%d\n", frame->pc,
|
||
|
frame->function, frame->filename, frame->lineno);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
SEQ_FUNC void seq_throw(void *exc) {
|
||
|
_Unwind_Reason_Code code = _Unwind_RaiseException((_Unwind_Exception *)exc);
|
||
|
(void)code;
|
||
|
seq_terminate(exc);
|
||
|
}
|
||
|
|
||
|
static uintptr_t readULEB128(const uint8_t **data) {
|
||
|
uintptr_t result = 0;
|
||
|
uintptr_t shift = 0;
|
||
|
unsigned char byte;
|
||
|
const uint8_t *p = *data;
|
||
|
|
||
|
do {
|
||
|
byte = *p++;
|
||
|
result |= (byte & 0x7f) << shift;
|
||
|
shift += 7;
|
||
|
} while (byte & 0x80);
|
||
|
|
||
|
*data = p;
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static uintptr_t readSLEB128(const uint8_t **data) {
|
||
|
uintptr_t result = 0;
|
||
|
uintptr_t shift = 0;
|
||
|
unsigned char byte;
|
||
|
const uint8_t *p = *data;
|
||
|
|
||
|
do {
|
||
|
byte = *p++;
|
||
|
result |= (byte & 0x7f) << shift;
|
||
|
shift += 7;
|
||
|
} while (byte & 0x80);
|
||
|
|
||
|
*data = p;
|
||
|
|
||
|
if ((byte & 0x40) && (shift < (sizeof(result) << 3))) {
|
||
|
result |= (~0 << shift);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static unsigned getEncodingSize(uint8_t encoding) {
|
||
|
if (encoding == llvm::dwarf::DW_EH_PE_omit)
|
||
|
return 0;
|
||
|
|
||
|
switch (encoding & 0x0F) {
|
||
|
case llvm::dwarf::DW_EH_PE_absptr:
|
||
|
return sizeof(uintptr_t);
|
||
|
case llvm::dwarf::DW_EH_PE_udata2:
|
||
|
return sizeof(uint16_t);
|
||
|
case llvm::dwarf::DW_EH_PE_udata4:
|
||
|
return sizeof(uint32_t);
|
||
|
case llvm::dwarf::DW_EH_PE_udata8:
|
||
|
return sizeof(uint64_t);
|
||
|
case llvm::dwarf::DW_EH_PE_sdata2:
|
||
|
return sizeof(int16_t);
|
||
|
case llvm::dwarf::DW_EH_PE_sdata4:
|
||
|
return sizeof(int32_t);
|
||
|
case llvm::dwarf::DW_EH_PE_sdata8:
|
||
|
return sizeof(int64_t);
|
||
|
default:
|
||
|
// not supported
|
||
|
abort();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) {
|
||
|
uintptr_t result = 0;
|
||
|
const uint8_t *p = *data;
|
||
|
|
||
|
if (encoding == llvm::dwarf::DW_EH_PE_omit)
|
||
|
return result;
|
||
|
|
||
|
// first get value
|
||
|
switch (encoding & 0x0F) {
|
||
|
case llvm::dwarf::DW_EH_PE_absptr:
|
||
|
result = ReadType<uintptr_t>(p);
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_uleb128:
|
||
|
result = readULEB128(&p);
|
||
|
break;
|
||
|
// Note: This case has not been tested
|
||
|
case llvm::dwarf::DW_EH_PE_sleb128:
|
||
|
result = readSLEB128(&p);
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_udata2:
|
||
|
result = ReadType<uint16_t>(p);
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_udata4:
|
||
|
result = ReadType<uint32_t>(p);
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_udata8:
|
||
|
result = ReadType<uint64_t>(p);
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_sdata2:
|
||
|
result = ReadType<int16_t>(p);
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_sdata4:
|
||
|
result = ReadType<int32_t>(p);
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_sdata8:
|
||
|
result = ReadType<int64_t>(p);
|
||
|
break;
|
||
|
default:
|
||
|
// not supported
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
// then add relative offset
|
||
|
switch (encoding & 0x70) {
|
||
|
case llvm::dwarf::DW_EH_PE_absptr:
|
||
|
// do nothing
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_pcrel:
|
||
|
result += (uintptr_t)(*data);
|
||
|
break;
|
||
|
case llvm::dwarf::DW_EH_PE_textrel:
|
||
|
case llvm::dwarf::DW_EH_PE_datarel:
|
||
|
case llvm::dwarf::DW_EH_PE_funcrel:
|
||
|
case llvm::dwarf::DW_EH_PE_aligned:
|
||
|
default:
|
||
|
// not supported
|
||
|
abort();
|
||
|
}
|
||
|
|
||
|
// then apply indirection
|
||
|
if (encoding & llvm::dwarf::DW_EH_PE_indirect) {
|
||
|
result = *((uintptr_t *)result);
|
||
|
}
|
||
|
|
||
|
*data = p;
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static bool handleActionValue(int64_t *resultAction, uint8_t TTypeEncoding,
|
||
|
const uint8_t *ClassInfo, uintptr_t actionEntry,
|
||
|
uint64_t exceptionClass,
|
||
|
_Unwind_Exception *exceptionObject) {
|
||
|
bool ret = false;
|
||
|
|
||
|
if (!resultAction || !exceptionObject || (exceptionClass != ourBaseExceptionClass))
|
||
|
return ret;
|
||
|
|
||
|
auto *excp = (struct OurBaseException_t *)(((char *)exceptionObject) +
|
||
|
ourBaseFromUnwindOffset);
|
||
|
OurExceptionType_t *excpType = &(excp->type);
|
||
|
seq_int_t type = excpType->type;
|
||
|
|
||
|
const uint8_t *actionPos = (uint8_t *)actionEntry, *tempActionPos;
|
||
|
int64_t typeOffset = 0, actionOffset;
|
||
|
|
||
|
for (int i = 0;; i++) {
|
||
|
// Each emitted dwarf action corresponds to a 2 tuple of
|
||
|
// type info address offset, and action offset to the next
|
||
|
// emitted action.
|
||
|
typeOffset = (int64_t)readSLEB128(&actionPos);
|
||
|
tempActionPos = actionPos;
|
||
|
actionOffset = (int64_t)readSLEB128(&tempActionPos);
|
||
|
|
||
|
assert(typeOffset >= 0);
|
||
|
|
||
|
// Note: A typeOffset == 0 implies that a cleanup llvm.eh.selector
|
||
|
// argument has been matched.
|
||
|
if (typeOffset > 0) {
|
||
|
unsigned EncSize = getEncodingSize(TTypeEncoding);
|
||
|
const uint8_t *EntryP = ClassInfo - typeOffset * EncSize;
|
||
|
uintptr_t P = readEncodedPointer(&EntryP, TTypeEncoding);
|
||
|
auto *ThisClassInfo = reinterpret_cast<OurExceptionType_t *>(P);
|
||
|
// type=0 means catch-all
|
||
|
if (ThisClassInfo->type == 0 || ThisClassInfo->type == type) {
|
||
|
*resultAction = i + 1;
|
||
|
ret = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!actionOffset)
|
||
|
break;
|
||
|
|
||
|
actionPos += actionOffset;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static _Unwind_Reason_Code handleLsda(int version, const uint8_t *lsda,
|
||
|
_Unwind_Action actions, uint64_t exceptionClass,
|
||
|
_Unwind_Exception *exceptionObject,
|
||
|
_Unwind_Context *context) {
|
||
|
_Unwind_Reason_Code ret = _URC_CONTINUE_UNWIND;
|
||
|
|
||
|
if (!lsda)
|
||
|
return ret;
|
||
|
|
||
|
// Get the current instruction pointer and offset it before next
|
||
|
// instruction in the current frame which threw the exception.
|
||
|
uintptr_t pc = _Unwind_GetIP(context) - 1;
|
||
|
|
||
|
// Get beginning current frame's code (as defined by the
|
||
|
// emitted dwarf code)
|
||
|
uintptr_t funcStart = _Unwind_GetRegionStart(context);
|
||
|
uintptr_t pcOffset = pc - funcStart;
|
||
|
const uint8_t *ClassInfo = nullptr;
|
||
|
|
||
|
// Note: See JITDwarfEmitter::EmitExceptionTable(...) for corresponding
|
||
|
// dwarf emission
|
||
|
|
||
|
// Parse LSDA header.
|
||
|
uint8_t lpStartEncoding = *lsda++;
|
||
|
|
||
|
if (lpStartEncoding != llvm::dwarf::DW_EH_PE_omit) {
|
||
|
readEncodedPointer(&lsda, lpStartEncoding);
|
||
|
}
|
||
|
|
||
|
uint8_t ttypeEncoding = *lsda++;
|
||
|
uintptr_t classInfoOffset;
|
||
|
|
||
|
if (ttypeEncoding != llvm::dwarf::DW_EH_PE_omit) {
|
||
|
// Calculate type info locations in emitted dwarf code which
|
||
|
// were flagged by type info arguments to llvm.eh.selector
|
||
|
// intrinsic
|
||
|
classInfoOffset = readULEB128(&lsda);
|
||
|
ClassInfo = lsda + classInfoOffset;
|
||
|
}
|
||
|
|
||
|
// Walk call-site table looking for range that
|
||
|
// includes current PC.
|
||
|
|
||
|
uint8_t callSiteEncoding = *lsda++;
|
||
|
auto callSiteTableLength = (uint32_t)readULEB128(&lsda);
|
||
|
const uint8_t *callSiteTableStart = lsda;
|
||
|
const uint8_t *callSiteTableEnd = callSiteTableStart + callSiteTableLength;
|
||
|
const uint8_t *actionTableStart = callSiteTableEnd;
|
||
|
const uint8_t *callSitePtr = callSiteTableStart;
|
||
|
|
||
|
while (callSitePtr < callSiteTableEnd) {
|
||
|
uintptr_t start = readEncodedPointer(&callSitePtr, callSiteEncoding);
|
||
|
uintptr_t length = readEncodedPointer(&callSitePtr, callSiteEncoding);
|
||
|
uintptr_t landingPad = readEncodedPointer(&callSitePtr, callSiteEncoding);
|
||
|
|
||
|
// Note: Action value
|
||
|
uintptr_t actionEntry = readULEB128(&callSitePtr);
|
||
|
|
||
|
if (exceptionClass != ourBaseExceptionClass) {
|
||
|
// We have been notified of a foreign exception being thrown,
|
||
|
// and we therefore need to execute cleanup landing pads
|
||
|
actionEntry = 0;
|
||
|
}
|
||
|
|
||
|
if (landingPad == 0) {
|
||
|
continue; // no landing pad for this entry
|
||
|
}
|
||
|
|
||
|
if (actionEntry) {
|
||
|
actionEntry += (uintptr_t)actionTableStart - 1;
|
||
|
}
|
||
|
|
||
|
bool exceptionMatched = false;
|
||
|
|
||
|
if ((start <= pcOffset) && (pcOffset < (start + length))) {
|
||
|
int64_t actionValue = 0;
|
||
|
|
||
|
if (actionEntry) {
|
||
|
exceptionMatched =
|
||
|
handleActionValue(&actionValue, ttypeEncoding, ClassInfo, actionEntry,
|
||
|
exceptionClass, exceptionObject);
|
||
|
}
|
||
|
|
||
|
if (!(actions & _UA_SEARCH_PHASE)) {
|
||
|
// Found landing pad for the PC.
|
||
|
// Set Instruction Pointer to so we re-enter function
|
||
|
// at landing pad. The landing pad is created by the
|
||
|
// compiler to take two parameters in registers.
|
||
|
_Unwind_SetGR(context, __builtin_eh_return_data_regno(0),
|
||
|
(uintptr_t)exceptionObject);
|
||
|
|
||
|
// Note: this virtual register directly corresponds
|
||
|
// to the return of the llvm.eh.selector intrinsic
|
||
|
if (!actionEntry || !exceptionMatched) {
|
||
|
// We indicate cleanup only
|
||
|
_Unwind_SetGR(context, __builtin_eh_return_data_regno(1), 0);
|
||
|
} else {
|
||
|
// Matched type info index of llvm.eh.selector intrinsic
|
||
|
// passed here.
|
||
|
_Unwind_SetGR(context, __builtin_eh_return_data_regno(1),
|
||
|
(uintptr_t)actionValue);
|
||
|
}
|
||
|
|
||
|
// To execute landing pad set here
|
||
|
_Unwind_SetIP(context, funcStart + landingPad);
|
||
|
ret = _URC_INSTALL_CONTEXT;
|
||
|
} else if (exceptionMatched) {
|
||
|
ret = _URC_HANDLER_FOUND;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
SEQ_FUNC _Unwind_Reason_Code seq_personality(int version, _Unwind_Action actions,
|
||
|
uint64_t exceptionClass,
|
||
|
_Unwind_Exception *exceptionObject,
|
||
|
_Unwind_Context *context) {
|
||
|
const auto *lsda = (uint8_t *)_Unwind_GetLanguageSpecificData(context);
|
||
|
// The real work of the personality function is captured here
|
||
|
return handleLsda(version, lsda, actions, exceptionClass, exceptionObject, context);
|
||
|
}
|
||
|
|
||
|
SEQ_FUNC int64_t seq_exc_offset() {
|
||
|
static OurBaseException_t dummy = {};
|
||
|
return (int64_t)((uintptr_t)&dummy - (uintptr_t) & (dummy.unwindException));
|
||
|
}
|
||
|
|
||
|
SEQ_FUNC uint64_t seq_exc_class() {
|
||
|
return genClass(ourBaseExcpClassChars, sizeof(ourBaseExcpClassChars));
|
||
|
}
|