diff --git a/Action.c b/Action.c index 4049a952f..4221c824d 100644 --- a/Action.c +++ b/Action.c @@ -27,7 +27,9 @@ in the source distribution for its full text. #include "ListItem.h" #include "Macros.h" #include "MainPanel.h" +#include "Object.h" #include "OpenFilesScreen.h" +#include "Panel.h" #include "Process.h" #include "ProcessLocksScreen.h" #include "ProvideCurses.h" @@ -47,6 +49,10 @@ in the source distribution for its full text. #include "AffinityPanel.h" #endif +#if defined(HAVE_BACKTRACE) +#include "BacktraceScreen.h" +#endif + Object* Action_pickFromVector(State* st, Panel* list, int x, bool follow) { MainPanel* mainPanel = st->mainPanel; @@ -595,6 +601,24 @@ static Htop_Reaction actionShowLocks(State* st) { return HTOP_REFRESH | HTOP_REDRAW_BAR; } +#if defined(HAVE_BACKTRACE) +static Htop_Reaction actionBacktrace(State *st) { + const Process* process = (Process*) Panel_getSelected((Panel*)st->mainPanel); + if (!process) + return HTOP_OK; + + BacktracePanel *panel = BacktracePanel_new(process); + ScreenManager *screenManager = ScreenManager_new(NULL, st->host, st, false); + ScreenManager_add(screenManager, (Panel *)panel, 0); + + ScreenManager_run(screenManager, NULL, NULL, NULL); + BacktracePanel_delete((Object *)panel); + ScreenManager_delete(screenManager); + + return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR; +} +#endif + static Htop_Reaction actionStrace(State* st) { if (!Action_writeableProcess(st)) return HTOP_OK; @@ -678,6 +702,9 @@ static const struct { { .key = " F8 [: ", .roInactive = true, .info = "lower priority (+ nice)" }, #if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)) { .key = " a: ", .roInactive = true, .info = "set CPU affinity" }, +#endif +#if defined(HAVE_BACKTRACE) + { .key = " b: ", .roInactive = false, .info = "show the backtrace of user process" }, #endif { .key = " e: ", .roInactive = false, .info = "show process environment" }, { .key = " i: ", .roInactive = true, .info = "set IO priority" }, @@ -918,6 +945,9 @@ void Action_setBindings(Htop_Action* keys) { keys['\\'] = actionIncFilter; keys[']'] = actionHigherPriority; keys['a'] = actionSetAffinity; +#if defined(HAVE_BACKTRACE) + keys['b'] = actionBacktrace; +#endif keys['c'] = actionTagAllChildren; keys['e'] = actionShowEnvScreen; keys['h'] = actionHelp; diff --git a/Action.h b/Action.h index caade03cd..274ea0b8f 100644 --- a/Action.h +++ b/Action.h @@ -6,6 +6,7 @@ htop - Action.h Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ +#include "config.h" // IWYU pragma: keep #include #include diff --git a/BacktraceScreen.c b/BacktraceScreen.c new file mode 100644 index 000000000..000d05892 --- /dev/null +++ b/BacktraceScreen.c @@ -0,0 +1,197 @@ +/* +htop - BacktraceScreen.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "BacktraceScreen.h" + +#if defined(HAVE_BACKTRACE) + +#include +#include +#include +#include +#include + +#include + +#include "CRT.h" +#include "FunctionBar.h" +#include "ListItem.h" +#include "Object.h" +#include "Panel.h" +#include "Platform.h" +#include "Process.h" +#include "RichString.h" +#include "Vector.h" +#include "XUtils.h" + +typedef enum BacktraceScreenDisplayOptions_ { + NO_OPTION = 0, + DEMANGLE_NAME_FUNCTION = 1 << 0, +} BacktraceScreenDisplayOptions; + +static BacktraceScreenDisplayOptions displayOptions = DEMANGLE_NAME_FUNCTION; + +static const char* const BacktraceScreenFunctions[] = { + "Refresh ", + "Show demangled function name", + "Done ", + NULL +}; + +static const char* const BacktraceScreenKeys[] = { + "F1", + "F2", + "Esc" +}; + +static const int BacktraceScreenEvents[] = { + KEY_F(1), + KEY_F(2), + 27, +}; + +static void Frame_display(const Object* super, RichString* out) { + const BacktraceFrame* const frame = (const BacktraceFrame * const)super; + + char bufferNumberOfFrame[16] = {'\0'}; + int len = snprintf(bufferNumberOfFrame, sizeof(bufferNumberOfFrame), "#%-3d ", frame->index); + RichString_appendnAscii(out, CRT_colors[DYNAMIC_GREEN], bufferNumberOfFrame, len); + + char bufferAddress[32] = {'\0'}; + len = snprintf(bufferAddress, sizeof(bufferAddress), "0x%016zx ", frame->address); + RichString_appendnAscii(out, CRT_colors[DYNAMIC_BLUE], bufferAddress, len); + + if ((displayOptions & DEMANGLE_NAME_FUNCTION) == DEMANGLE_NAME_FUNCTION && + frame->demangleFunctionName) { + RichString_appendAscii(out, CRT_colors[DEFAULT_COLOR], frame->demangleFunctionName); + } else { + RichString_appendAscii(out, CRT_colors[DEFAULT_COLOR], frame->functionName); + } + if (frame->isSignalFrame) { + RichString_appendAscii(out, CRT_colors[DYNAMIC_RED], " signal frame"); + } + + char bufferFrameOffset[16] = {'\0'}; + len = snprintf(bufferFrameOffset, sizeof(bufferFrameOffset), "+%zu", frame->offset); + RichString_appendAscii(out, CRT_colors[DYNAMIC_YELLOW], bufferFrameOffset); +} + +static int Frame_compare(const void* object1, const void* object2) { + const BacktraceFrame* frame1 = (const BacktraceFrame*)object1; + const BacktraceFrame* frame2 = (const BacktraceFrame*)object2; + return String_eq(frame1->functionName, frame2->functionName); +} + +static void Frame_delete(Object* object) { + BacktraceFrame* this = (BacktraceFrame*)object; + if (this->functionName) { + free(this->functionName); + } + + if (this->demangleFunctionName) { + free(this->demangleFunctionName); + } + + free(this); +} + +static void BacktracePanel_setError(BacktracePanel* this, char* error) { + assert(error != NULL); + assert(this->error == false); + + Panel* super = &this->super; + Panel_prune(super); + + Vector* lines = this->super.items; + Vector_delete(lines); + this->super.items = Vector_new(Class(ListItem), true, DEFAULT_SIZE); + Panel_set(super, 0, (Object*)ListItem_new(error, 0)); +} + +static void BacktracePanel_populateFrames(BacktracePanel* this) { + if (this->error) { + return; + } + + char* error = NULL; + Vector* lines = this->super.items; + Platform_getBacktrace(lines, this->process, &error); + if (error) { + BacktracePanel_setError(this, error); + free(error); + } + + this->super.needsRedraw = true; +} + +BacktracePanel* BacktracePanel_new(const Process* process) { + BacktracePanel* this = CallocThis(BacktracePanel); + this->process = process; + this->error = false; + + Panel* super = (Panel*) this; + Panel_init(super, 1, 1, 1, 1, Class(BacktraceFrame), true, FunctionBar_new(BacktraceScreenFunctions, BacktraceScreenKeys, BacktraceScreenEvents)); + + char* header = NULL; + xAsprintf(&header, "Backtrace of '%s' (%d)", process->procComm, Process_getPid(process)); + Panel_setHeader(super, header); + free(header); + + BacktracePanel_populateFrames(this); + return this; +} + +BacktraceFrame* BacktraceFrame_new(void) { + BacktraceFrame* this = CallocThis(BacktraceFrame); + return this; +} + +static HandlerResult BacktracePanel_eventHandler(Panel* super, int ch) { + BacktracePanel* this = (BacktracePanel*)super; + + HandlerResult result = IGNORED; + switch (ch) { + case KEY_F(1): + Panel_prune(super); + BacktracePanel_populateFrames(this); + break; + + case KEY_F(2): + if ((displayOptions & DEMANGLE_NAME_FUNCTION) == DEMANGLE_NAME_FUNCTION) { + displayOptions &= ~DEMANGLE_NAME_FUNCTION; + FunctionBar_setLabel(super->defaultBar, KEY_F(2), "Show mangled function name"); + } else { + displayOptions |= DEMANGLE_NAME_FUNCTION; + FunctionBar_setLabel(super->defaultBar, KEY_F(2), "Show demangled function name"); + } + this->super.needsRedraw = true; + break; + } + return result; +} + +void BacktracePanel_delete(Object* object) { + Panel_delete(object); +} + +const PanelClass BacktracePanel_class = { + .super = { + .extends = Class(Panel), + .delete = BacktracePanel_delete, + }, + .eventHandler = BacktracePanel_eventHandler, +}; + +const ObjectClass BacktraceFrame_class = { + .extends = Class(Object), + .compare = Frame_compare, + .delete = Frame_delete, + .display = Frame_display, +}; +#endif diff --git a/BacktraceScreen.h b/BacktraceScreen.h new file mode 100644 index 000000000..17395b113 --- /dev/null +++ b/BacktraceScreen.h @@ -0,0 +1,44 @@ +#ifndef HEADER_BacktraceScreen +#define HEADER_BacktraceScreen +/* +htop - BacktraceScreen.h +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#if defined(HAVE_BACKTRACE) +#include +#include + +#include "Object.h" +#include "Panel.h" +#include "Process.h" + +typedef struct BacktracePanel_ { + Panel super; + const Process* process; + bool error; +} BacktracePanel; + +typedef struct BacktraceFrame_ { + Object super; + int index; + size_t address; + size_t offset; + char* functionName; + char* demangleFunctionName; + bool isSignalFrame; +} BacktraceFrame; + +BacktracePanel* BacktracePanel_new(const Process* process); +void BacktracePanel_delete(Object* object); +BacktraceFrame* BacktraceFrame_new(void); + +extern const PanelClass BacktracePanel_class; +extern const ObjectClass BacktraceFrame_class; + +#endif +#endif diff --git a/Makefile.am b/Makefile.am index ed92afac4..df313f0e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,7 @@ myhtopsources = \ AffinityPanel.c \ AvailableColumnsPanel.c \ AvailableMetersPanel.c \ + BacktraceScreen.c \ BatteryMeter.c \ CategoriesPanel.c \ ClockMeter.c \ @@ -99,6 +100,7 @@ myhtopheaders = \ AffinityPanel.h \ AvailableColumnsPanel.h \ AvailableMetersPanel.h \ + BacktraceScreen.h \ BatteryMeter.h \ CPUMeter.h \ CRT.h \ diff --git a/Object.h b/Object.h index 4d7d653ee..fa6797af5 100644 --- a/Object.h +++ b/Object.h @@ -33,6 +33,7 @@ typedef void(*Object_Delete)(Object*); #define Class(class_) ((const ObjectClass*)(&(class_ ## _class))) #define AllocThis(class_) (class_*) xMalloc(sizeof(class_)); Object_setClass(this, Class(class_)) +#define CallocThis(class_) (class_*) xCalloc(sizeof(class_), 1); Object_setClass(this, Class(class_)) typedef struct ObjectClass_ { const void* const extends; diff --git a/darwin/Platform.c b/darwin/Platform.c index 77b9bcdca..0883798b4 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -533,6 +533,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { *percent = NAN; *isOnAC = AC_ERROR; diff --git a/darwin/Platform.h b/darwin/Platform.h index f67db8ff4..9ed5b4f46 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -76,6 +76,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index f3412ef9e..2729b5406 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -337,6 +337,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { int life; size_t life_len = sizeof(life); diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h index 606b004c0..2e18b5373 100644 --- a/dragonflybsd/Platform.h +++ b/dragonflybsd/Platform.h @@ -67,6 +67,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/freebsd/Platform.c b/freebsd/Platform.c index 9be7195e5..63dd90f3f 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -371,6 +371,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { int life; size_t life_len = sizeof(life); diff --git a/freebsd/Platform.h b/freebsd/Platform.h index c358d85d3..5a57c13f3 100644 --- a/freebsd/Platform.h +++ b/freebsd/Platform.h @@ -67,6 +67,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/linux/Platform.c b/linux/Platform.c index af81a694c..95f64fd74 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -21,12 +21,16 @@ in the source distribution for its full text. #include #include #include +#include #include +#include +#include +#include "BacktraceScreen.h" #include "BatteryMeter.h" +#include "CPUMeter.h" #include "ClockMeter.h" #include "Compat.h" -#include "CPUMeter.h" #include "DateMeter.h" #include "DateTimeMeter.h" #include "DiskIOMeter.h" @@ -37,19 +41,21 @@ in the source distribution for its full text. #include "Machine.h" #include "Macros.h" #include "MainPanel.h" -#include "Meter.h" #include "MemoryMeter.h" #include "MemorySwapMeter.h" +#include "Meter.h" #include "NetworkIOMeter.h" #include "Object.h" #include "Panel.h" #include "PressureStallMeter.h" +#include "Process.h" #include "ProvideCurses.h" #include "Settings.h" #include "SwapMeter.h" #include "SysArchMeter.h" #include "TasksMeter.h" #include "UptimeMeter.h" +#include "Vector.h" #include "XUtils.h" #include "linux/IOPriority.h" #include "linux/IOPriorityPanel.h" @@ -68,6 +74,14 @@ in the source distribution for its full text. #include #endif +#ifdef HAVE_LIBIBERTY +#include +#endif + +#ifdef HAVE_LIBUNWIND_PTRACE +#include +#endif + #ifdef HAVE_SENSORS_SENSORS_H #include "LibSensors.h" #endif @@ -662,6 +676,97 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { +#ifdef HAVE_LIBUNWIND_PTRACE + unw_addr_space_t addrSpace = unw_create_addr_space(&_UPT_accessors, 0); + if (!addrSpace) { + xAsprintf(error, "Unable to initialize libunwind."); + return; + } + + const pid_t pid = Process_getPid(process); + + if (pid <= 0) { + xAsprintf(error, "Unable to get the pid"); + goto addr_space_error; + } + + if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) { + int ptraceErrno = errno; + xAsprintf(error, "ptrace: %s (%d)", strerror(ptraceErrno), ptraceErrno); + goto addr_space_error; + } + + int waitStatus = 0; + if (wait(&waitStatus) == -1) { + int waitErrno = errno; + xAsprintf(error, "wait: %s (%d)", strerror(waitErrno), waitErrno); + goto ptrace_error; + } + + if (WIFSTOPPED(waitStatus) == 0) { + *error = xStrdup("The process chosen is not stopped correctly."); + goto ptrace_error; + } + + struct UPT_info* context = _UPT_create(pid); + if (!context) { + xAsprintf(error, "Unable to create the context of libunwind-ptrace"); + goto ptrace_error; + } + + unw_cursor_t cursor; + int ret = unw_init_remote(&cursor, addrSpace, context); + if (ret < 0) { + xAsprintf(error, "libunwind cursor: ret=%d", ret); + goto context_error; + } + + int index = 0; + do { + char procName[2048] = "?"; + unw_word_t offset; + unw_word_t pc; + + if (unw_get_proc_name(&cursor, procName, sizeof(procName), &offset) == 0) { + ret = unw_get_reg(&cursor, UNW_REG_IP, &pc); + if (ret < 0) { + xAsprintf(error, "unable to get register rip : %d", ret); + break; + } + + BacktraceFrame* frame = BacktraceFrame_new(); + frame->index = index; + frame->address = pc; + frame->offset = offset; + frame->isSignalFrame = unw_is_signal_frame(&cursor); +#if defined(HAVE_LIBIBERTY) + char* demangledName = cplus_demangle(procName, + DMGL_PARAMS | DMGL_ANSI | DMGL_VERBOSE | DMGL_RET_POSTFIX); + frame->demangleFunctionName = demangledName; +#endif + + frame->functionName = xStrdup(procName); + Vector_add(frames, (Object *)frame); + } + index++; + } while (unw_step(&cursor) > 0); + +context_error: + _UPT_destroy(context); + +ptrace_error: + ptrace(PTRACE_DETACH, pid, 0, 0); + +addr_space_error: + unw_destroy_addr_space(addrSpace); +#else + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +#endif +} + // Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com). #define PROC_BATTERY_DIR PROCDIR "/acpi/battery" diff --git a/linux/Platform.h b/linux/Platform.h index e99d1a226..9e50b203c 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -28,6 +28,7 @@ in the source distribution for its full text. #include "Settings.h" #include "SignalsPanel.h" #include "CommandLine.h" +#include "Vector.h" #include "generic/gettime.h" #include "generic/hostname.h" #include "generic/uname.h" @@ -86,6 +87,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/netbsd/Platform.c b/netbsd/Platform.c index f458c239f..c0db9ab34 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -429,6 +429,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { prop_dictionary_t dict, fields, props; prop_object_t device, class; diff --git a/netbsd/Platform.h b/netbsd/Platform.h index a543f52dd..d554d1b4f 100644 --- a/netbsd/Platform.h +++ b/netbsd/Platform.h @@ -73,6 +73,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/openbsd/Platform.c b/openbsd/Platform.c index a8b5d212d..5d50f0369 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -342,6 +342,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return false; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +} + static bool findDevice(const char* name, int* mib, struct sensordev* snsrdev, size_t* sdlen) { for (int devn = 0;; devn++) { mib[2] = devn; diff --git a/openbsd/Platform.h b/openbsd/Platform.h index 339616c11..1124aae10 100644 --- a/openbsd/Platform.h +++ b/openbsd/Platform.h @@ -65,6 +65,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/pcp/Platform.c b/pcp/Platform.c index d50edd254..10460e60c 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -771,6 +771,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getFileDescriptors(double* used, double* max) { *used = NAN; *max = 65536; diff --git a/pcp/Platform.h b/pcp/Platform.h index f43ed54f2..f06dafabb 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -108,6 +108,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); void Platform_getHostname(char* buffer, size_t size); diff --git a/solaris/Platform.c b/solaris/Platform.c index 3934f7896..11e521d90 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -336,6 +336,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return false; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { *percent = NAN; *isOnAC = AC_ERROR; diff --git a/solaris/Platform.h b/solaris/Platform.h index 1a31c2e7a..2bc2b76ef 100644 --- a/solaris/Platform.h +++ b/solaris/Platform.h @@ -92,6 +92,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); static inline void Platform_getHostname(char* buffer, size_t size) { diff --git a/unsupported/Platform.c b/unsupported/Platform.c index dbfddd916..e4cdc5f44 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -151,6 +151,12 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return false; } +void Platform_getBacktrace(Vector* frames, const Process* process, char **error) { + (void)frames; + (void)process; + xAsprintf(error, "The backtrace screen is not implemented"); +} + void Platform_getBattery(double* percent, ACPresence* isOnAC) { *percent = NAN; *isOnAC = AC_ERROR; diff --git a/unsupported/Platform.h b/unsupported/Platform.h index c4cd06a04..fec5abbd0 100644 --- a/unsupported/Platform.h +++ b/unsupported/Platform.h @@ -61,6 +61,8 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); +void Platform_getBacktrace(Vector* frames, const Process* process, char **error); + void Platform_getBattery(double* percent, ACPresence* isOnAC); void Platform_getHostname(char* buffer, size_t size);